use oxc_ast::{
    AstKind,
    ast::{
        ArrowFunctionExpression, BindingPattern, Expression, FunctionType, PropertyKind,
        ReturnStatement, TSType, TSTypeName,
    },
};
use oxc_ast_visit::Visit;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use oxc_str::CompactStr;
use oxc_syntax::operator::UnaryOperator;
use rustc_hash::FxHashSet;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{
    AstNode,
    ast_util::{iter_outer_expressions, outermost_paren_parent},
    context::{ContextHost, LintContext},
    rule::{DefaultRuleConfig, Rule},
};

#[derive(Debug, Default, Clone, Deserialize)]
pub struct ExplicitFunctionReturnType(Box<ExplicitFunctionReturnTypeConfig>);

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
pub struct ExplicitFunctionReturnTypeConfig {
    /// Whether to allow expressions as function return types. When `true`, allows functions that immediately return an expression without a return type annotation.
    allow_expressions: bool,
    /// Whether to allow typed function expressions. When `true`, allows function expressions that are assigned to a typed variable or parameter.
    allow_typed_function_expressions: bool,
    /// Whether to allow arrow functions that use `as const` assertion on their return value.
    allow_direct_const_assertion_in_arrow_functions: bool,
    /// Whether to allow concise arrow functions that start with the `void` keyword.
    allow_concise_arrow_function_expressions_starting_with_void: bool,
    /// Whether to allow functions that do not have generic type parameters.
    allow_functions_without_type_parameters: bool,
    /// Array of function names that are exempt from requiring return type annotations.
    allowed_names: FxHashSet<CompactStr>,
    /// Whether to allow higher-order functions (functions that return another function) without return type annotations.
    allow_higher_order_functions: bool,
    /// Whether to allow immediately invoked function expressions (IIFEs) without return type annotations.
    #[serde(rename = "allowIIFEs")]
    allow_iifes: bool,
}

impl std::ops::Deref for ExplicitFunctionReturnType {
    type Target = ExplicitFunctionReturnTypeConfig;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Default for ExplicitFunctionReturnTypeConfig {
    fn default() -> Self {
        Self {
            allow_expressions: false,
            allow_typed_function_expressions: true,
            allow_direct_const_assertion_in_arrow_functions: true,
            allow_concise_arrow_function_expressions_starting_with_void: false,
            allow_functions_without_type_parameters: false,
            allowed_names: FxHashSet::default(),
            allow_higher_order_functions: true,
            allow_iifes: false,
        }
    }
}

declare_oxc_lint!(
    /// ### What it does
    ///
    /// This rule enforces that functions have an explicit return type annotation.
    ///
    /// ### Why is this bad?
    ///
    /// Explicit return types make it clearer what type is returned by a function. Making the
    /// type returned by a function obvious allows the reader to infer what the function does
    /// and how it can be used from a quick glance.
    ///
    /// Another benefit of explicit return types is the potential for a speed up of type
    /// checking in large codebases with many large functions.
    ///
    /// ### Examples
    ///
    /// Examples of **incorrect** code for this rule:
    /// ```ts
    /// // Should indicate that no value is returned (void)
    /// function test() {
    ///     return
    /// }
    ///
    /// // Should indicate that a number is returned
    /// var fn = function () {
    ///     return 1
    /// }
    ///
    /// // Should indicate that a string is returned
    /// var arrowFn = () => 'test'
    ///
    /// class Test {
    ///     // Should indicate that no value is returned (void)
    ///     method() {
    ///         return
    ///     }
    /// }
    /// ```
    ///
    /// Examples of **correct** code for this rule:
    /// ```ts
    /// // No return value should be expected (void)
    /// function test(): void {
    ///     return
    /// }
    ///
    /// // A return value of type number
    /// var fn = function (): number {
    ///     return 1
    /// }
    ///
    /// // A return value of type string
    /// var arrowFn = (): string => 'test'
    ///
    /// class Test {
    ///     // No return value should be expected (void)
    ///     method(): void {
    ///         return
    ///     }
    /// }
    /// ```
    ExplicitFunctionReturnType,
    typescript,
    restriction,
    config = ExplicitFunctionReturnTypeConfig,
    version = "0.4.4",
    short_description = "This rule enforces that functions have an explicit return type annotation.",
);

fn explicit_function_return_type_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Missing return type on function.")
        // TODO: actually provide a helpful message.
        .with_help("Require explicit return types on functions and class methods.")
        .with_label(span)
}

impl Rule for ExplicitFunctionReturnType {
    fn from_configuration(value: serde_json::Value) -> Result<Self, serde_json::error::Error> {
        serde_json::from_value::<DefaultRuleConfig<Self>>(value).map(DefaultRuleConfig::into_inner)
    }

    fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
        match node.kind() {
            AstKind::Function(func) => {
                if !func.is_declaration() && !func.is_expression() {
                    return;
                }

                if func.return_type.is_some() || is_constructor_or_setter(node, ctx) {
                    return;
                }
                if self.is_allowed_function(node, ctx) {
                    return;
                }
                if matches!(func.r#type, FunctionType::FunctionDeclaration) {
                    if self.allow_typed_function_expressions && func.return_type.is_some() {
                        return;
                    }
                    if self.does_immediately_return_function_expression(node) {
                        return;
                    }
                } else {
                    if self.allow_typed_function_expressions
                        && (self.is_valid_function_expression_return_type(node, ctx)
                            || ancestor_has_return_type(node, ctx))
                    {
                        return;
                    }

                    if self.does_immediately_return_function_expression(node) {
                        return;
                    }
                }

                if let Some(parent) = outermost_paren_parent(node, ctx) {
                    match parent.kind() {
                        AstKind::MethodDefinition(def) => {
                            ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                                def.span.start,
                                def.value.params.span.start,
                            )));
                            return;
                        }
                        AstKind::PropertyDefinition(def) => {
                            ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                                def.span.start,
                                func.params.span.start,
                            )));

                            return;
                        }
                        AstKind::ObjectProperty(prop) => {
                            ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                                prop.span.start,
                                func.params.span.start,
                            )));

                            return;
                        }
                        _ => {}
                    }
                }
                if func.is_expression() {
                    ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                        func.span.start,
                        func.params.span.start,
                    )));
                } else if let Some(id) = &func.id {
                    ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                        func.span.start,
                        id.span.end,
                    )));
                } else {
                    ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                        func.span.start,
                        func.params.span.start,
                    )));
                }
            }
            AstKind::ArrowFunctionExpression(func) => {
                if func.return_type.is_some() {
                    return;
                }
                if self.check_arrow_function_with_void(func) {
                    return;
                }
                if self.is_allowed_function(node, ctx) {
                    return;
                }
                if self.allow_typed_function_expressions
                    && (self.is_valid_function_expression_return_type(node, ctx)
                        || ancestor_has_return_type(node, ctx))
                {
                    return;
                }
                if self.returns_const_assertion_directly(node) {
                    return;
                }

                if self.does_immediately_return_function_expression(node) {
                    return;
                }

                if let Some(parent) = outermost_paren_parent(node, ctx) {
                    match parent.kind() {
                        AstKind::MethodDefinition(def) => {
                            ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                                def.span.start,
                                def.value.params.span.start,
                            )));
                            return;
                        }
                        AstKind::PropertyDefinition(def) => {
                            ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                                def.span.start,
                                func.params.span.start,
                            )));

                            return;
                        }
                        AstKind::ObjectProperty(prop) => {
                            ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                                prop.span.start,
                                func.params.span.start,
                            )));

                            return;
                        }
                        _ => {}
                    }
                }
                ctx.diagnostic(explicit_function_return_type_diagnostic(Span::new(
                    func.params.span.end + 1,
                    func.params.span.end + 3,
                )));
            }
            _ => {}
        }
    }

    fn should_run(&self, ctx: &ContextHost) -> bool {
        ctx.source_type().is_typescript()
    }
}

impl ExplicitFunctionReturnType {
    fn check_arrow_function_with_void(&self, func: &ArrowFunctionExpression) -> bool {
        if !self.allow_concise_arrow_function_expressions_starting_with_void {
            return false;
        }
        if !func.expression {
            return false;
        }
        let Some(expr) = func.get_expression() else { return false };
        let Expression::UnaryExpression(unary_expr) = expr else { return false };
        matches!(unary_expr.operator, UnaryOperator::Void)
    }

    fn is_allowed_function<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
        match node.kind() {
            AstKind::Function(func) => {
                if self.allow_functions_without_type_parameters && func.type_parameters.is_none() {
                    return true;
                }
                if self.allow_iifes && is_iife(node, ctx) {
                    return true;
                }
                if self.allowed_names.is_empty() {
                    return false;
                }
                if let Some(id) = &func.id {
                    return self.allowed_names.contains(id.name.as_str());
                }
                self.check_parent_for_is_allowed_function(node, ctx)
            }
            AstKind::ArrowFunctionExpression(func) => {
                if self.allow_functions_without_type_parameters && func.type_parameters.is_none() {
                    return true;
                }
                if self.allow_iifes && is_iife(node, ctx) {
                    return true;
                }
                if self.allowed_names.is_empty() {
                    return false;
                }

                self.check_parent_for_is_allowed_function(node, ctx)
            }
            _ => false,
        }
    }

    fn check_parent_for_is_allowed_function<'a>(
        &self,
        node: &AstNode<'a>,
        ctx: &LintContext<'a>,
    ) -> bool {
        let Some(parent) = outermost_paren_parent(node, ctx) else { return false };
        match parent.kind() {
            AstKind::VariableDeclarator(decl) => {
                let BindingPattern::BindingIdentifier(id) = &decl.id else {
                    return false;
                };

                self.allowed_names.contains(id.name.as_str())
            }
            AstKind::MethodDefinition(def) => {
                let Some(name) = def.key.name() else { return false };
                def.key.is_identifier()
                    && !def.computed
                    && self.allowed_names.contains(name.as_ref())
            }
            AstKind::PropertyDefinition(def) => {
                let Some(name) = def.key.name() else { return false };
                def.key.is_identifier()
                    && !def.computed
                    && self.allowed_names.contains(name.as_ref())
            }
            AstKind::ObjectProperty(prop) => {
                let Some(name) = prop.key.name() else { return false };
                prop.key.is_identifier()
                    && !prop.computed
                    && self.allowed_names.contains(name.as_ref())
            }
            _ => false,
        }
    }

    fn is_valid_function_expression_return_type<'a>(
        &self,
        node: &AstNode<'a>,
        ctx: &LintContext<'a>,
    ) -> bool {
        if check_typed_function_expression(node, ctx) {
            return true;
        }
        self.check_allow_expressions(node, ctx)
    }

    fn check_allow_expressions(&self, node: &AstNode, ctx: &LintContext) -> bool {
        let parent = ctx.nodes().parent_node(node.id());
        self.allow_expressions
            && !matches!(
                parent.kind(),
                AstKind::VariableDeclarator(_)
                    | AstKind::MethodDefinition(_)
                    | AstKind::ExportDefaultDeclaration(_)
                    | AstKind::PropertyDefinition(_)
            )
    }

    fn returns_const_assertion_directly(&self, node: &AstNode) -> bool {
        if !self.allow_direct_const_assertion_in_arrow_functions {
            return false;
        }
        let AstKind::ArrowFunctionExpression(func) = node.kind() else { return false };
        let Some(expr) = func.get_expression() else { return false };

        let mut expr = expr;
        loop {
            expr = match expr {
                Expression::TSSatisfiesExpression(e) => &e.expression,
                _ => break,
            };
        }

        match expr {
            Expression::TSAsExpression(ts_expr) => {
                let TSType::TSTypeReference(ts_type) = &ts_expr.type_annotation else {
                    return false;
                };
                let TSTypeName::IdentifierReference(id_ref) = &ts_type.type_name else {
                    return false;
                };

                id_ref.name == "const"
            }
            Expression::TSTypeAssertion(ts_expr) => {
                let TSType::TSTypeReference(ts_type) = &ts_expr.type_annotation else {
                    return false;
                };
                let TSTypeName::IdentifierReference(id_ref) = &ts_type.type_name else {
                    return false;
                };

                id_ref.name == "const"
            }
            _ => false,
        }
    }

    /**
     * Checks if a function belongs to:
     * ```text
     * () => () => ...
     * () => function () { ... }
     * () => { return () => ... }
     * () => { return function () { ... } }
     * function fn() { return () => ... }
     * function fn() { return function() { ... } }
     * ```
     */
    fn does_immediately_return_function_expression(&self, node: &AstNode) -> bool {
        if !self.allow_higher_order_functions {
            return false;
        }
        if let AstKind::ArrowFunctionExpression(arrow_func_expr) = node.kind()
            && let Some(func_body_expr) = arrow_func_expr.get_expression()
        {
            return is_function(func_body_expr);
        }
        all_return_statements_are_functions(node)
    }
}

// check function is IIFE (Immediately Invoked Function Expression)
fn is_iife<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
    let Some(AstKind::CallExpression(call)) = iter_outer_expressions(ctx.nodes(), node.id()).next()
    else {
        return false;
    };
    call.callee.span().contains_inclusive(node.span())
}

/**
 * Checks if a node belongs to:
 * ```text
 * new Foo(() => {})
 *         ^^^^^^^^
 * ```
 */
fn is_constructor_argument(node: &AstNode) -> bool {
    matches!(node.kind(), AstKind::NewExpression(_))
}

fn is_constructor_or_setter(node: &AstNode, ctx: &LintContext) -> bool {
    let parent = ctx.nodes().parent_node(node.id());
    is_constructor(parent) || is_setter(parent)
}

fn is_constructor(node: &AstNode) -> bool {
    let AstKind::MethodDefinition(method_def) = node.kind() else { return false };
    method_def.kind.is_constructor()
}

fn is_setter(node: &AstNode) -> bool {
    match node.kind() {
        AstKind::MethodDefinition(method_def) => method_def.kind.is_set(),
        AstKind::ObjectProperty(obj_prop) => {
            matches!(obj_prop.kind, PropertyKind::Set)
        }
        _ => false,
    }
}

fn check_typed_function_expression<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
    let Some(parent) = outermost_paren_parent(node, ctx) else { return false };
    is_typed_parent(parent, Some(node), ctx)
        || is_property_of_object_with_type(parent, ctx)
        || is_constructor_argument(parent)
}

fn is_typed_parent(parent: &AstNode, callee: Option<&AstNode>, ctx: &LintContext) -> bool {
    is_type_assertion(parent)
        || is_variable_declarator_with_type_annotation(parent)
        || is_default_function_parameter_with_type_annotation(parent, ctx)
        || is_property_definition_with_type_annotation(parent)
        || is_function_argument(parent, callee)
        || is_typed_jsx(parent)
}

fn is_variable_declarator_with_type_annotation(node: &AstNode) -> bool {
    let AstKind::VariableDeclarator(var_decl) = node.kind() else { return false };

    var_decl.type_annotation.is_some()
}

fn is_function_argument(parent: &AstNode, callee: Option<&AstNode>) -> bool {
    let AstKind::CallExpression(call_expr) = parent.kind() else { return false };

    if callee.is_none() {
        return true;
    }

    match call_expr.callee.without_parentheses() {
        Expression::FunctionExpression(func_expr) => {
            let AstKind::Function(callee_func_expr) = callee.unwrap().kind() else { return false };
            func_expr.span != callee_func_expr.span
        }
        Expression::ArrowFunctionExpression(arrow_func_expr) => {
            let AstKind::ArrowFunctionExpression(callee_arrow_func_expr) = callee.unwrap().kind()
            else {
                return false;
            };
            arrow_func_expr.span != callee_arrow_func_expr.span
        }
        _ => true,
    }
}

fn is_type_assertion(node: &AstNode) -> bool {
    matches!(node.kind(), AstKind::TSAsExpression(_) | AstKind::TSTypeAssertion(_))
}
fn is_default_function_parameter_with_type_annotation(node: &AstNode, ctx: &LintContext) -> bool {
    let AstKind::AssignmentPattern(_assign) = node.kind() else { return false };
    match ctx.nodes().parent_kind(node.id()) {
        AstKind::FormalParameter(f) => f.type_annotation.is_some(),
        AstKind::FormalParameterRest(f) => f.type_annotation.is_some(),
        _ => false,
    }
}

/**
 * Checks if a node is a class property with a type annotation.
 * ```text
 * public x: Foo = ...
 * ```
 */
fn is_property_definition_with_type_annotation(node: &AstNode) -> bool {
    let AstKind::PropertyDefinition(prop_def) = node.kind() else { return false };
    prop_def.type_annotation.is_some()
}

/**
 * Checks if a node is type-constrained in JSX
 * ```text
 * <Foo x={() => {}} />
 * <Bar>{() => {}}</Bar>
 * <Baz {...props} />
 * ```
 */
fn is_typed_jsx(node: &AstNode) -> bool {
    matches!(node.kind(), AstKind::JSXExpressionContainer(_) | AstKind::JSXSpreadAttribute(_))
}

fn is_function(expr: &Expression) -> bool {
    matches!(
        expr.get_inner_expression(),
        Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)
    )
}

fn ancestor_has_return_type<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
    let Some(parent) = outermost_paren_parent(node, ctx) else { return false };

    if let AstKind::ObjectProperty(prop) = parent.kind()
        && let Expression::ArrowFunctionExpression(func) = &prop.value
        && !func.body.statements.is_empty()
        && func.return_type.is_some()
    {
        return true;
    }

    for ancestor in ctx.nodes().ancestors(node.id()) {
        match ancestor.kind() {
            AstKind::ArrowFunctionExpression(func) if func.return_type.is_some() => {
                return true;
            }
            AstKind::Function(func) if func.return_type.is_some() => {
                return true;
            }
            AstKind::VariableDeclarator(decl) => {
                return decl.type_annotation.is_some();
            }
            AstKind::PropertyDefinition(def) => {
                return def.type_annotation.is_some();
            }
            AstKind::ExpressionStatement(expr)
                if !matches!(expr.expression, Expression::ArrowFunctionExpression(_)) =>
            {
                return false;
            }
            _ => {}
        }
    }

    false
}

fn all_return_statements_are_functions(node: &AstNode) -> bool {
    let function_body = match node.kind() {
        AstKind::ArrowFunctionExpression(arrow_func_expr) => &arrow_func_expr.body,
        AstKind::Function(func) => {
            if let Some(func_body) = &func.body {
                func_body
            } else {
                return false;
            }
        }
        _ => return false,
    };

    let mut checker = ReturnStatementChecker { has_return: false, all_returns_are_functions: true };

    checker.visit_function_body(function_body);

    checker.has_return && checker.all_returns_are_functions
}

struct ReturnStatementChecker {
    has_return: bool,
    all_returns_are_functions: bool,
}

impl<'a> Visit<'a> for ReturnStatementChecker {
    fn visit_return_statement(&mut self, return_statement: &ReturnStatement<'a>) {
        self.has_return = true;
        self.all_returns_are_functions &=
            return_statement.argument.as_ref().is_some_and(|argument| is_function(argument));
    }

    fn visit_function(
        &mut self,
        _func: &oxc_ast::ast::Function<'a>,
        _flags: oxc_semantic::ScopeFlags,
    ) {
    }

    fn visit_arrow_function_expression(&mut self, _it: &ArrowFunctionExpression<'a>) {}
}

/**
 * Checks if a node is a property or a nested property of a typed object:
 * ```text
 * const x: Foo = { prop: () => {} }
 * const x = { prop: () => {} } as Foo
 * const x = <Foo>{ prop: () => {} }
 * const x: Foo = { bar: { prop: () => {} } }
 * ```
 */
fn is_property_of_object_with_type(node: &AstNode, ctx: &LintContext) -> bool {
    if !matches!(node.kind(), AstKind::ObjectProperty(_)) {
        return false;
    }
    let parent = ctx.nodes().parent_node(node.id());
    if !matches!(parent.kind(), AstKind::ObjectExpression(_)) {
        return false;
    }
    let Some(obj_expr_parent) = outermost_paren_parent(parent, ctx) else {
        return false;
    };
    is_typed_parent(obj_expr_parent, None, ctx)
        || is_property_of_object_with_type(obj_expr_parent, ctx)
}

#[test]
fn test() {
    use std::path::PathBuf;

    use crate::tester::Tester;

    let pass = vec![
        ("return;", None, None, None),
        (
            "
            function test(): void {
              return;
            }
            ",
            None,
            None,
            None,
        ),
        (
            "
            var fn = function (): number {
              return 1;
            };
            ",
            None,
            None,
            None,
        ),
        ("var arrowFn = (): string => 'test';", None, None, None),
        (
            "
            class Test {
              constructor() {}
              get prop(): number {
                return 1;
              }
              set prop(_foo) {}
              method(): void {
                return;
              }
              arrow = (): string => 'arrow';
            }
            ",
            None,
            None,
            None,
        ),
        ("fn(() => {});", Some(serde_json::json!([ { "allowExpressions": true,  }, ])), None, None),
        (
            "fn(function () {});",
            Some(serde_json::json!([ { "allowExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "[function () {}, () => {}];",
            Some(serde_json::json!([ { "allowExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "(function () {});",
            Some(serde_json::json!([ { "allowExpressions": true,  }, ])),
            None,
            None,
        ),
        ("(() => {})();", Some(serde_json::json!([ { "allowExpressions": true,  }, ])), None, None),
        (
            "export default (): void => {};",
            Some(serde_json::json!([ { "allowExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "var arrowFn: Foo = () => 'test';",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "
            var funcExpr: Foo = function () {
              return 'test';
            };
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "const x = (() => {}) as Foo;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "const x = <Foo>(() => {});",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            Some(PathBuf::from("test.ts")),
        ),
        (
            "
            const x = {
              foo: () => {},
            } as Foo;
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            const x = <Foo>{
              foo: () => {},
            };
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            Some(PathBuf::from("test.ts")),
        ),
        (
            "
            const x: Foo = {
              foo: () => {},
            };
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            const x = {
              foo: { bar: () => {} },
            } as Foo;
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            const x = <Foo>{
              foo: { bar: () => {} },
            };
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            Some(PathBuf::from("test.ts")),
        ),
        (
            "
            const x: Foo = {
              foo: { bar: () => {} },
            };
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            type MethodType = () => void;

            class App {
              private method: MethodType = () => {};
            }
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "const foo = <button onClick={() => {}} />;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "const foo = <button on={{ click: () => {} }} />;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "const foo = <Bar>{() => {}}</Bar>;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "const foo = <Bar>{{ on: () => {} }}</Bar>;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "const foo = <button {...{ onClick: () => {} }} />;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            const myObj = {
              set myProp(val) {
                this.myProp = val;
              },
            };
            ",
            None,
            None,
            None,
        ),
        (
            "() => (): void => {};",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "() => function (): void {};",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            () => {
              return (): void => {};
            };
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            () => {
              return function (): void {};
            };
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            () => {
              const foo = 'foo';
              return function (): string {
                return foo;
              };
            };
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn() {
              return (): void => {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn() {
              return function (): void {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn() {
              const bar = () => (): number => 1;
              return function (): void {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn(arg: boolean) {
              if (arg) {
                return () => (): number => 1;
              } else {
                return function (): string {
                  return 'foo';
                };
              }

              return function (): void {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function FunctionDeclaration() {
              return function FunctionExpression_Within_FunctionDeclaration() {
                return function FunctionExpression_Within_FunctionExpression() {
                  return () => {
                    // ArrowFunctionExpression_Within_FunctionExpression
                    return () =>
                      // ArrowFunctionExpression_Within_ArrowFunctionExpression
                      (): number =>
                        1; // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody
                  };
                };
              };
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            () => () => {
              return (): void => {
                return;
              };
            };
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            declare function foo(arg: () => void): void;
            foo(() => 1);
            foo(() => {});
            foo(() => null);
            foo(() => true);
            foo(() => '');
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "
            declare function foo(arg: () => void): void;
            foo?.(() => 1);
            foo?.bar(() => {});
            foo?.bar?.(() => null);
            foo.bar?.(() => true);
            foo?.(() => '');
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "
            class Accumulator {
              private count: number = 0;

              public accumulate(fn: () => number): void {
                this.count += fn();
              }
            }

            new Accumulator().accumulate(() => 1);
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "
            declare function foo(arg: { meth: () => number }): void;
            foo({
              meth() {
                return 1;
              },
            });
            foo({
              meth: function () {
                return 1;
              },
            });
            foo({
              meth: () => {
                return 1;
              },
            });
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "
            const func1 = (value: number) => ({ type: 'X', value }) as const;
            const func2 = (value: number) => ({ type: 'X', value }) as const;
            const func3 = (value: number) => x as const;
            const func4 = (value: number) => x as const;
            ",
            Some(serde_json::json!([ { "allowDirectConstAssertionInArrowFunctions": true,  }, ])),
            None,
            None,
        ),
        (
            "
            interface R {
                type: string;
                value: number;
                }

                const func = (value: number) => ({ type: 'X', value }) as const satisfies R;
                ",
            Some(serde_json::json!([ { "allowDirectConstAssertionInArrowFunctions": true } ])),
            None,
            None,
        ),
        (
            "
                interface R {
                    type: string;
                    value: number;
                    }

                    const func = (value: number) =>
                    ({ type: 'X', value }) as const satisfies R satisfies R;
                    ",
            Some(serde_json::json!([ { "allowDirectConstAssertionInArrowFunctions": true } ])),
            None,
            None,
        ),
        (
            "
                    interface R {
                        type: string;
                        value: number;
                        }

                        const func = (value: number) =>
                        ({ type: 'X', value }) as const satisfies R satisfies R satisfies R;
                        ",
            Some(serde_json::json!([ { "allowDirectConstAssertionInArrowFunctions": true } ])),
            None,
            None,
        ),
        (
            "
            new Promise(resolve => {});
            new Foo(1, () => {});
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "const log = (message: string) => void console.log(message);",
            Some(
                serde_json::json!([{ "allowConciseArrowFunctionExpressionsStartingWithVoid": true }]),
            ),
            None,
            None,
        ),
        (
            "const log = (a: string) => a;",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "const log = <A,>(a: A): A => a;",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            function log<A>(a: A): A {
              return a;
            }
            ",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            function log(a: string) {
              return a;
            }
            ",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            const log = function <A>(a: A): A {
              return a;
            };
            ",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            const log = function (a: A): string {
              return a;
            };
            ",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            function test1() {
              return;
            }

            const foo = function test2() {
              return;
            };
            ",
            Some(serde_json::json!([ { "allowedNames": ["test1", "test2"],  }, ])),
            None,
            None,
        ),
        (
            "
            const test1 = function () {
              return;
            };
            const foo = function () {
              return function test2() {};
            };
            ",
            Some(serde_json::json!([ { "allowedNames": ["test1", "test2"],  }, ])),
            None,
            None,
        ),
        (
            "
            const test1 = () => {
              return;
            };
            export const foo = {
              test2() {
                return 0;
              },
            };
            ",
            Some(serde_json::json!([ { "allowedNames": ["test1", "test2"],  }, ])),
            None,
            None,
        ),
        (
            "
            class Test {
              constructor() {}
              get prop() {
                return 1;
              }
              set prop(_foo) {}
              method() {
                return;
              }
              arrow = () => 'arrow';
              private method() {
                return;
              }
            }
            ",
            Some(serde_json::json!([ { "allowedNames": ["prop", "method", "arrow"],  }, ])),
            None,
            None,
        ),
        (
            "
            const x = {
              arrowFn: () => {
                return;
              },
              fn: function () {
                return;
              },
            };
            ",
            Some(serde_json::json!([ { "allowedNames": ["arrowFn", "fn"],  }, ])),
            None,
            None,
        ),
        (
            "
            type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
            const x: HigherOrderType = () => arg1 => arg2 => 'foo';
            ",
            Some(
                serde_json::json!([ { "allowTypedFunctionExpressions": true, "allowHigherOrderFunctions": true }, ]),
            ),
            None,
            None,
        ),
        (
            "
            type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
            const x: HigherOrderType = () => arg1 => arg2 => 'foo';
            ",
            Some(
                serde_json::json!([ { "allowTypedFunctionExpressions": true, "allowHigherOrderFunctions": false }, ]),
            ),
            None,
            None,
        ),
        (
            "
            interface Foo {
              foo: string;
              arrowFn: () => string;
            }

            function foo(): Foo {
              return {
                foo: 'foo',
                arrowFn: () => 'test',
              };
            }
            ",
            Some(
                serde_json::json!([ { "allowTypedFunctionExpressions": true, "allowHigherOrderFunctions": true }, ]),
            ),
            None,
            None,
        ),
        (
            "
            type Foo = (arg1: string) => string;
            type Bar<T> = (arg2: string) => T;
            const x: Bar<Foo> = arg1 => arg2 => arg1 + arg2;
            ",
            Some(
                serde_json::json!([ { "allowTypedFunctionExpressions": true, "allowHigherOrderFunctions": true }, ]),
            ),
            None,
            None,
        ),
        (
            "
            let foo = function (): number {
              return 1;
            };
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            const foo = (function () {
              return 1;
            })();
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            const foo = (() => {
              return 1;
            })();
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            const foo = ((arg: number): number => {
              return arg;
            })(0);
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "const foo = (() => (() => 'foo')())();",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            let foo = (() => (): string => {
              return 'foo';
            })()();
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            let foo = (() => (): string => {
              return 'foo';
            })();
            ",
            Some(
                serde_json::json!([ { "allowIIFEs": true, "allowHigherOrderFunctions": false }, ]),
            ),
            None,
            None,
        ),
        (
            "
            let foo = (() => (): string => {
              return 'foo';
            })()();
            ",
            Some(serde_json::json!([ { "allowIIFEs": true, "allowHigherOrderFunctions": true }, ])),
            None,
            None,
        ),
        (
            "
            let foo = (() => (): void => {})()();
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            let foo = (() => (() => {})())();
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            class Bar {
              bar: Foo = {
                foo: x => x + 1,
              };
            }
            ",
            None,
            None,
            None,
        ),
        (
            "
            class Bar {
              bar: Foo[] = [
                {
                  foo: x => x + 1,
                },
              ];
            }
            ",
            None,
            None,
            None,
        ),
        (
            "
            type CallBack = () => void;

            function f(gotcha: CallBack = () => {}): void {}
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            type CallBack = () => void;

            const f = (gotcha: CallBack = () => {}): void => {};
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            type ObjectWithCallback = { callback: () => void };

            const f = (gotcha: ObjectWithCallback = { callback: () => {} }): void => {};
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "export function define(): (callback: (() => void)) => void {
                return () => {xxxxxxx }
            }
            ",
            None,
            None,
            None,
        ),
        (
            "clients.filter((client) => searchWords.every((word) => client.name.toLowerCase().includes(word)) || client.cats.some((cat) => searchWords.every((word) => cat.name.toLowerCase().includes(word))))",
            None,
            None,
            None,
        ),
    ];

    let fail = vec![
        (
            "
            function test(a: number, b: number) {
              return;
            }
            ",
            None,
            None,
            None,
        ),
        (
            "
            function test() {
              return;
            }
            ",
            None,
            None,
            None,
        ),
        (
            "
            var fn = function () {
              return 1;
            };
            ",
            None,
            None,
            None,
        ),
        (
            "
            var arrowFn = () => 'test';
            ",
            None,
            None,
            None,
        ),
        (
            "
            class Test {
              constructor() {}
              get prop() {
                return 1;
              }
              set prop(_foo) {}
              method() {
                return;
              }
              arrow = () => 'arrow';
              private method() {
                return;
              }
            }
            ",
            None,
            None,
            None,
        ),
        (
            "
            function test() {
              return;
            }
            ",
            Some(serde_json::json!([{ "allowExpressions": true }])),
            None,
            None,
        ),
        (
            "const foo = () => {};",
            Some(serde_json::json!([{ "allowExpressions": true }])),
            None,
            None,
        ),
        (
            "const foo = function () {};",
            Some(serde_json::json!([{ "allowExpressions": true }])),
            None,
            None,
        ),
        (
            "export default () => {};",
            Some(serde_json::json!([{ "allowExpressions": true }])),
            None,
            None,
        ),
        (
            "export default function () {}",
            Some(serde_json::json!([{ "allowExpressions": true }])),
            None,
            None,
        ),
        (
            "
            class Foo {
              public a = () => {};
              public b = function () {};
              public c = function test() {};

              static d = () => {};
              static e = function () {};
            }
            ",
            Some(serde_json::json!([{ "allowExpressions": true }])),
            None,
            None,
        ),
        (
            "var arrowFn = () => 'test';",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "
            function foo(): any {
              const bar = () => () => console.log('aa');
            }
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true }, ])),
            None,
            None,
        ),
        (
            "
            let anyValue: any;
            function foo(): any {
              anyValue = () => () => console.log('aa');
            }
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true }, ])),
            None,
            None,
        ),
        (
            "
            class Foo {
              foo(): any {
                const bar = () => () => {
                  return console.log('foo');
                };
              }
            }
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true }, ])),
            None,
            None,
        ),
        (
            "
            var funcExpr = function () {
              return 'test';
            };
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": true }])),
            None,
            None,
        ),
        (
            "const x = (() => {}) as Foo;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "
            interface Foo {}
            const x = {
              foo: () => {},
            } as Foo;
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "
            interface Foo {}
            const x: Foo = {
              foo: () => {},
            };
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "const foo = <button onClick={() => {}} />;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "const foo = <button on={{ click: () => {} }} />;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "const foo = <Bar>{() => {}}</Bar>;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "const foo = <Bar>{{ on: () => {} }}</Bar>;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "const foo = <button {...{ onClick: () => {} }} />;",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "
            function foo(): any {
              class Foo {
                foo = () => () => {
                  return console.log('foo');
                };
              }
            }
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": true,  }, ])),
            None,
            None,
        ),
        (
            "() => () => {};",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "() => function () {};",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            () => {
              return () => {};
            };
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            () => {
              return function () {};
            };
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn() {
              return () => {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn() {
              return function () {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn() {
              const bar = () => (): number => 1;
              const baz = () => () => 'baz';
              return function (): void {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function fn(arg: boolean) {
              if (arg) return 'string';
              return function (): void {};
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            function FunctionDeclaration() {
              return function FunctionExpression_Within_FunctionDeclaration() {
                return function FunctionExpression_Within_FunctionExpression() {
                  return () => {
                    // ArrowFunctionExpression_Within_FunctionExpression
                    return () =>
                      // ArrowFunctionExpression_Within_ArrowFunctionExpression
                      () =>
                        1; // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody
                  };
                };
              };
            }
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            () => () => {
              return () => {
                return;
              };
            };
            ",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
        (
            "
            declare function foo(arg: () => void): void;
            foo(() => 1);
            foo(() => {});
            foo(() => null);
            foo(() => true);
            foo(() => '');
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": false,  }, ])),
            None,
            None,
        ),
        (
            "
            class Accumulator {
              private count: number = 0;

              public accumulate(fn: () => number): void {
                this.count += fn();
              }
            }

            new Accumulator().accumulate(() => 1);
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": false }, ])),
            None,
            None,
        ),
        (
            "(() => true)();",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": false }, ])),
            None,
            None,
        ),
        (
            "
            declare function foo(arg: { meth: () => number }): void;
            foo({
              meth() {
                return 1;
              },
            });
            foo({
              meth: function () {
                return 1;
              },
            });
            foo({
              meth: () => {
                return 1;
              },
            });
            ",
            Some(serde_json::json!([ { "allowTypedFunctionExpressions": false } ])),
            None,
            None,
        ),
        (
            "
            type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
            const x: HigherOrderType = () => arg1 => arg2 => 'foo';
            ",
            Some(
                serde_json::json!([ { "allowTypedFunctionExpressions": false, "allowHigherOrderFunctions": true } ]),
            ),
            None,
            None,
        ),
        (
            "
            type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
            const x: HigherOrderType = () => arg1 => arg2 => 'foo';
            ",
            Some(
                serde_json::json!([ { "allowTypedFunctionExpressions": false, "allowHigherOrderFunctions": false } ]),
            ),
            None,
            None,
        ),
        (
            "
            const func1 = (value: number) => ({ type: 'X', value }) as any;
            const func2 = (value: number) => ({ type: 'X', value }) as Action;
            ",
            Some(serde_json::json!([ { "allowDirectConstAssertionInArrowFunctions": true } ])),
            None,
            None,
        ),
        (
            "const func = (value: number) => ({ type: 'X', value }) as const;",
            Some(serde_json::json!([ { "allowDirectConstAssertionInArrowFunctions": false } ])),
            None,
            None,
        ),
        (
            "const log = (message: string) => void console.log(message);",
            Some(
                serde_json::json!([ { "allowConciseArrowFunctionExpressionsStartingWithVoid": false } ]),
            ),
            None,
            None,
        ),
        (
            "
            const log = (message: string) => {
              void console.log(message);
            };
            ",
            Some(
                serde_json::json!([{ "allowConciseArrowFunctionExpressionsStartingWithVoid": true }]),
            ),
            None,
            None,
        ),
        (
            "const log = <A,>(a: A) => a;",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            function log<A>(a: A) {
              return a;
            }
            ",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            const log = function <A>(a: A) {
              return a;
            };
            ",
            Some(serde_json::json!([{ "allowFunctionsWithoutTypeParameters": true }])),
            None,
            None,
        ),
        (
            "
            function hoge() {
              return;
            }
            const foo = () => {
              return;
            };
            const baz = function () {
              return;
            };
            let [test, test2] = function () {
              return;
            };
            class X {
              [test] = function () {
                return;
              };
            }
            const x = {
              1: function () {
                return;
              },
            };
            ",
            Some(serde_json::json!([ { "allowedNames": ["test", "1"] }, ])),
            None,
            None,
        ),
        (
            "
            const ignoredName = 'notIgnoredName';
            class Foo {
              [ignoredName]() {}
            }
            ",
            Some(serde_json::json!([{ "allowedNames": ["ignoredName"] }])),
            None,
            None,
        ),
        (
            "
            class Bar {
              bar = [
                {
                  foo: x => x + 1,
                },
              ];
            }
            ",
            None,
            None,
            None,
        ),
        (
            "
            const foo = (function () {
              return 'foo';
            })();
            ",
            Some(serde_json::json!([ { "allowIIFEs": false }, ])),
            None,
            None,
        ),
        (
            "
            const foo = (function () {
              return () => {
                return 1;
              };
            })();
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            let foo = function () {
              return 'foo';
            };
            ",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "let foo = (() => () => {})()();",
            Some(serde_json::json!([ { "allowIIFEs": true }, ])),
            None,
            None,
        ),
        (
            "
            type CallBack = () => void;

            function f(gotcha: CallBack = () => {}): void {}
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "
            type CallBack = () => void;

            const f = (gotcha: CallBack = () => {}): void => {};
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "
            type ObjectWithCallback = { callback: () => void };

            const f = (gotcha: ObjectWithCallback = { callback: () => {} }): void => {};
            ",
            Some(serde_json::json!([{ "allowTypedFunctionExpressions": false }])),
            None,
            None,
        ),
        (
            "export function myFunction2() { try { return 'something'; } catch (error) { throw new Error('some wrapped', { cause: error }); } }",
            None,
            None,
            None,
        ),
        (
            "export function myFunction3() { try { throw new Error('some error'); } catch (error) { throw error; } }",
            Some(serde_json::json!([{ "allowHigherOrderFunctions": true }])),
            None,
            None,
        ),
    ];

    Tester::new(ExplicitFunctionReturnType::NAME, ExplicitFunctionReturnType::PLUGIN, pass, fail)
        .test_and_snapshot();
}
