From 29c78db3589d3623aa654e11140556b117fefae4 Mon Sep 17 00:00:00 2001 From: kaykdm <34934746+kaykdm@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:45:02 +0900 Subject: [PATCH] feat(linter): implement @typescript-eslint/explicit-function-return-type (#3455) Related issue: https://github.com/oxc-project/oxc/issues/2180 original implementation - code: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/rules/explicit-function-return-type.ts - test: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts - doc: https://typescript-eslint.io/rules/explicit-function-return-type/ --------- Co-authored-by: Boshen --- .typos.toml | 1 + crates/oxc_linter/src/rules.rs | 2 + .../explicit_function_return_type.rs | 2136 +++++++++++++++++ .../explicit_function_return_type.snap | 682 ++++++ 4 files changed, 2821 insertions(+) create mode 100644 crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs create mode 100644 crates/oxc_linter/src/snapshots/explicit_function_return_type.snap diff --git a/.typos.toml b/.typos.toml index 392526d1fa5ab..dc2a675e9baa5 100644 --- a/.typos.toml +++ b/.typos.toml @@ -30,3 +30,4 @@ labeledby = "labeledby" [default.extend-identifiers] IIFEs = "IIFEs" +allowIIFEs = "allowIIFEs" diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index a401fd41b80e1..b8e41bd1b5d96 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -126,6 +126,7 @@ mod typescript { pub mod ban_types; pub mod consistent_indexed_object_style; pub mod consistent_type_definitions; + pub mod explicit_function_return_type; pub mod no_duplicate_enum_values; pub mod no_empty_interface; pub mod no_explicit_any; @@ -531,6 +532,7 @@ oxc_macros::declare_all_lint_rules! { typescript::prefer_ts_expect_error, typescript::triple_slash_reference, typescript::prefer_literal_enum_member, + typescript::explicit_function_return_type, jest::expect_expect, jest::max_expects, jest::max_nested_describe, diff --git a/crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs b/crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs new file mode 100644 index 0000000000000..4e79e621a5c33 --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/explicit_function_return_type.rs @@ -0,0 +1,2136 @@ +use std::collections::HashSet; + +use oxc_ast::{ + ast::{ + ArrowFunctionExpression, BindingPatternKind, Expression, FunctionType, JSXAttributeItem, + PropertyKind, Statement, TSType, TSTypeName, + }, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use oxc_syntax::operator::UnaryOperator; + +use crate::{ + ast_util::outermost_paren_parent, + context::LintContext, + rule::Rule, + rules::eslint::array_callback_return::return_checker::{ + check_statement, StatementReturnStatus, + }, + AstNode, +}; + +#[derive(Debug, Default, Clone)] +pub struct ExplicitFunctionReturnType(Box); + +#[derive(Debug, Default, Clone)] +pub struct ExplicitFunctionReturnTypeConfig { + allow_expressions: bool, + allow_typed_function_expressions: bool, + allow_direct_const_assertion_in_arrow_functions: bool, + allow_concise_arrow_function_expressions_starting_with_void: bool, + allow_functions_without_type_parameters: bool, + allowed_names: HashSet, + allow_higher_order_functions: bool, + allow_iifes: bool, +} + +impl std::ops::Deref for ExplicitFunctionReturnType { + type Target = ExplicitFunctionReturnTypeConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// This rule enforces that functions do have an explicit return type annotation. + /// + /// ### Why is this bad? + /// Explicit return types do make it visually more clear what type is returned by a function. + /// They can also speed up TypeScript type checking performance in large codebases with many large functions. + /// + /// ### Example + /// ```javascript + /// ``` + ExplicitFunctionReturnType, + restriction, +); + +fn explicit_function_return_type_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn( + "typescript-eslint(explicit-function-return-type): Missing return type on function.", + ) + .with_help("Require explicit return types on functions and class methods.") + .with_labels([span0.into()]) +} + +impl Rule for ExplicitFunctionReturnType { + fn from_configuration(value: serde_json::Value) -> Self { + let options: Option<&serde_json::Value> = value.get(0); + Self(Box::new(ExplicitFunctionReturnTypeConfig { + allow_expressions: options + .and_then(|x| x.get("allowExpressions")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false), + allow_typed_function_expressions: options + .and_then(|x| x.get("allowTypedFunctionExpressions")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(true), + allow_direct_const_assertion_in_arrow_functions: options + .and_then(|x| x.get("allowDirectConstAssertionInArrowFunctions")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(true), + allow_concise_arrow_function_expressions_starting_with_void: options + .and_then(|x| x.get("allowConciseArrowFunctionExpressionsStartingWithVoid")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false), + allow_functions_without_type_parameters: options + .and_then(|x| x.get("allowFunctionsWithoutTypeParameters")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false), + allowed_names: options + .and_then(|x| x.get("allowedNames")) + .and_then(serde_json::Value::as_array) + .map(|v| { + v.iter() + .filter_map(serde_json::Value::as_str) + .map(ToString::to_string) + .collect() + }) + .unwrap_or_default(), + allow_higher_order_functions: options + .and_then(|x| x.get("allowHigherOrderFunctions")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(true), + allow_iifes: options + .and_then(|x| x.get("allowIIFEs")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false), + })) + } + + 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) = get_parent_node(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) = get_parent_node(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, + ))); + } + _ => {} + } + } +} + +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) = get_parent_node(node, ctx) else { return false }; + match parent.kind() { + AstKind::VariableDeclarator(decl) => { + let BindingPatternKind::BindingIdentifier(id) = &decl.id.kind 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_str()) + } + AstKind::PropertyDefinition(def) => { + let Some(name) = def.key.name() else { return false }; + def.key.is_identifier() + && !def.computed + && self.allowed_names.contains(name.as_str()) + } + AstKind::ObjectProperty(prop) => { + let Some(name) = prop.key.name() else { return false }; + prop.key.is_identifier() + && !prop.computed + && self.allowed_names.contains(name.as_str()) + } + _ => 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 Some(parent) = ctx.nodes().parent_node(node.id()) else { + return false; + }; + 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 }; + + 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: + * ``` + * () => () => ... + * () => 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() { + if 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(parent) = get_parent_node(node, ctx) else { + return false; + }; + matches!(parent.kind(), AstKind::CallExpression(_)) +} +/** + * Checks if a node belongs to: + * ``` + * 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 Some(parent) = ctx.nodes().parent_node(node.id()) else { + return false; + }; + 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 get_parent_node<'a, 'b>( + node: &'b AstNode<'a>, + ctx: &'b LintContext<'a>, +) -> Option<&'b AstNode<'a>> { + let parent = outermost_paren_parent(node, ctx)?; + match parent.kind() { + AstKind::Argument(_) => outermost_paren_parent(parent, ctx), + _ => Some(parent), + } +} + +fn check_typed_function_expression<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { + let Some(parent) = get_parent_node(node, ctx) else { return false }; + is_typed_parent(parent, Some(node)) + || is_property_of_object_with_type(parent, ctx) + || is_constructor_argument(parent) +} + +fn is_typed_parent(parent: &AstNode, callee: Option<&AstNode>) -> bool { + is_type_assertion(parent) + || is_variable_declarator_with_type_annotation(parent) + || is_default_function_parameter_with_type_annotation(parent) + || 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.id.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_parenthesized() { + 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) -> bool { + let AstKind::AssignmentPattern(assign) = node.kind() else { return false }; + + assign.left.type_annotation.is_some() +} + +/** + * Checks if a node is a class property with a type annotation. + * ``` + * 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 + * ``` + * {}} /> + * {() => {}} + * + * ``` + */ +fn is_typed_jsx(node: &AstNode) -> bool { + if matches!(node.kind(), AstKind::JSXExpressionContainer(_) | AstKind::JSXSpreadAttribute(_)) { + return true; + } + + let AstKind::JSXAttributeItem(jsx_attr_item) = node.kind() else { return false }; + matches!(jsx_attr_item, JSXAttributeItem::SpreadAttribute(_)) +} + +fn is_function(expr: &Expression) -> bool { + matches!(expr, Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)) +} + +fn ancestor_has_return_type<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool { + let Some(parent) = get_parent_node(node, ctx) else { return false }; + + if let AstKind::ObjectProperty(prop) = parent.kind() { + if let Expression::ArrowFunctionExpression(func) = &prop.value { + if func.body.statements.is_empty() { + return false; + } + if func.return_type.is_some() { + return true; + } + } + } else if check_return_statement_and_bodyless(parent) { + return false; + } + + for ancestor in ctx.nodes().ancestors(node.id()).skip(1) { + match ctx.nodes().kind(ancestor) { + 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.id.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 { + match node.kind() { + AstKind::ArrowFunctionExpression(arrow_func_expr) => { + check_return_statements(&arrow_func_expr.body.statements) + } + AstKind::Function(func) => { + if let Some(func_body) = &func.body { + check_return_statements(&func_body.statements) + } else { + false + } + } + _ => false, + } +} + +fn check_return_statement_and_bodyless(node: &AstNode) -> bool { + match node.kind() { + AstKind::ReturnStatement(_) => true, + AstKind::ArrowFunctionExpression(func) => func.body.statements.is_empty(), + _ => false, + } +} + +fn check_return_statements<'a>(statements: &'a [Statement<'a>]) -> bool { + if statements.is_empty() { + return false; + } + + let mut has_return = false; + + let all_statements_valid = statements.iter().all(|stmt| { + if let Statement::ReturnStatement(return_stmt) = stmt { + if let Some(arg) = &return_stmt.argument { + has_return = true; + return is_function(arg); + } + false + } else { + let status = check_statement(stmt); + if status == StatementReturnStatus::AlwaysExplicit { + has_return = true; + } + matches!( + status, + StatementReturnStatus::NotReturn | StatementReturnStatus::AlwaysExplicit + ) + } + }); + + has_return && all_statements_valid +} + +/** + * Checks if a node is a property or a nested property of a typed object: + * ``` + * const x: Foo = { prop: () => {} } + * const x = { prop: () => {} } as Foo + * const x = { 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; + } + if !matches!(node.kind(), AstKind::ObjectProperty(_)) { + return false; + } + let Some(parent) = ctx.nodes().parent_node(node.id()) else { + return false; + }; + if !matches!(parent.kind(), AstKind::ObjectExpression(_)) { + return false; + } + let Some(obj_expr_parent) = get_parent_node(parent, ctx) else { + return false; + }; + is_typed_parent(obj_expr_parent, None) || is_property_of_object_with_type(obj_expr_parent, ctx) +} + +#[test] +fn test() { + use crate::tester::Tester; + use std::path::PathBuf; + + 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() {} + 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 = (() => {});", + 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: () => {}, + }; + ", + 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: { 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 =