From 5a2457451d2ecc1800c65733dfb9d2d1218759a1 Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Tue, 5 Aug 2025 09:11:24 +0000 Subject: [PATCH] fix(linter/func-style): fix more false positives (#12828) fixes #11445 --- .../oxc_linter/src/rules/eslint/func_style.rs | 352 ++++++++++++++++-- .../src/snapshots/eslint_func_style.snap | 209 ++++++++++- 2 files changed, 511 insertions(+), 50 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/func_style.rs b/crates/oxc_linter/src/rules/eslint/func_style.rs index df767ef6781d7..04df1a99da3c6 100644 --- a/crates/oxc_linter/src/rules/eslint/func_style.rs +++ b/crates/oxc_linter/src/rules/eslint/func_style.rs @@ -235,6 +235,14 @@ impl Rule for FuncStyle { let parent = semantic.nodes().parent_node(node.id()); match func.r#type { FunctionType::FunctionDeclaration => { + if func.body.is_none() + || func.id.as_ref().is_some_and(|id| { + !ctx.scoping().symbol_redeclarations(id.symbol_id()).is_empty() + }) + { + continue; + } + // There are two situations to diagnostic // 1) if style not equal to "declaration" // we need to consider whether the parent node is ExportDefaultDeclaration or ExportNamedDeclaration @@ -338,6 +346,7 @@ impl Rule for FuncStyle { #[test] fn test() { use crate::tester::Tester; + let pass = vec![ ( "function foo(){} @@ -460,35 +469,111 @@ fn test() { ( "export var foo = () => {};", Some( - serde_json::json!(["declaration", { "allowArrowFunctions": true, "overrides": { "namedExports": "ignore" } }]), + serde_json::json!(["declaration",{ "allowArrowFunctions": true, "overrides": { "namedExports": "ignore" }}]), ), ), + ("$1: function $2() { }", Some(serde_json::json!(["declaration"]))), // { "sourceType": "script" }, + ("switch ($0) { case $1: function $2() { } }", Some(serde_json::json!(["declaration"]))), ( - "const arrow: Fn = () => {}", + "function foo(): void {} + function bar(): void {}", + Some(serde_json::json!(["declaration"])), + ), + ("(function(): void { /* code */ }());", Some(serde_json::json!(["declaration"]))), + ( + "const module = (function(): { [key: string]: any } { return {}; }());", + Some(serde_json::json!(["declaration"])), + ), + ( + "const object: { foo: () => void } = { foo: function(): void {} };", + Some(serde_json::json!(["declaration"])), + ), + ("Array.prototype.foo = function(): void {};", Some(serde_json::json!(["declaration"]))), + ( + "const foo: () => void = function(): void {}; + const bar: () => void = function(): void {};", + Some(serde_json::json!(["expression"])), + ), + ( + "const foo: () => void = (): void => {}; + const bar: () => void = (): void => {}", + Some(serde_json::json!(["expression"])), + ), + ( + "const foo: () => void = function(): void { this; }.bind(this);", + Some(serde_json::json!(["declaration"])), + ), + ( + "const foo: () => void = (): void => { this; };", + Some(serde_json::json!(["declaration"])), + ), + ( + "class C extends D { foo(): void { const bar: () => void = (): void => { super.baz(); }; } }", + Some(serde_json::json!(["declaration"])), + ), + ( + "const obj: { foo(): void } = { foo(): void { const bar: () => void = (): void => super.baz; } }", + Some(serde_json::json!(["declaration"])), + ), + ( + "const foo: () => void = (): void => {};", + Some(serde_json::json!(["declaration", { "allowArrowFunctions": true }])), + ), + ( + "const foo: () => void = (): void => { function foo(): void { this; } };", + Some(serde_json::json!(["declaration", { "allowArrowFunctions": true }])), + ), + ( + "const foo: () => { bar(): void } = (): { bar(): void } => ({ bar(): void { super.baz(); } });", + Some(serde_json::json!(["declaration", { "allowArrowFunctions": true }])), + ), + ("export function foo(): void {};", Some(serde_json::json!(["declaration"]))), + ( + "export function foo(): void {};", Some( - serde_json::json!(["declaration", { "allowTypeAnnotation": true, "allowArrowFunctions": false }]), + serde_json::json!(["expression",{ "overrides": { "namedExports": "declaration" } }, ]), ), ), ( - "const arrow: Fn = () => {}", + "export function foo(): void {};", Some( - serde_json::json!(["expression", { "allowTypeAnnotation": false, "allowArrowFunctions": false }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "declaration" } }, ]), ), ), ( - "export const foo: () => void = function () {}", + "export function foo(): void {};", + Some(serde_json::json!(["expression", { "overrides": { "namedExports": "ignore" } }])), + ), + ( + "export function foo(): void {};", + Some(serde_json::json!(["declaration", { "overrides": { "namedExports": "ignore" } }])), + ), + ( + "export const foo: () => void = function(): void {};", + Some(serde_json::json!(["expression"])), + ), + ( + "export const foo: () => void = function(): void {};", Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "expression" } }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "expression" } }]), ), ), ( - "export const foo: () => void = function () {}", + "export const foo: () => void = function(): void {};", Some( - serde_json::json!(["expression", { "allowTypeAnnotation": true, "overrides": { "namedExports": "declaration" } }]), + serde_json::json!(["expression",{ "overrides": { "namedExports": "expression" } }]), ), ), ( - "export const foo: () => void = function () {}", + "export const foo: () => void = function(): void {};", + Some(serde_json::json!(["declaration", { "overrides": { "namedExports": "ignore" } }])), + ), + ( + "export const foo: () => void = function(): void {};", + Some(serde_json::json!(["expression", { "overrides": { "namedExports": "ignore" } }])), + ), + ( + "const expression: Fn = function () {}", Some(serde_json::json!(["declaration", { "allowTypeAnnotation": true }])), ), ( @@ -505,10 +590,80 @@ fn test() { ), ( "export const expression: Fn = function () {}", + Some( + serde_json::json!(["expression", { "allowTypeAnnotation": true, "overrides": { "namedExports": "declaration" } }]), + ), + ), + ( + "export const arrow: Fn = () => {}", Some( serde_json::json!(["expression", { "allowTypeAnnotation": true, "overrides": { "namedExports": "declaration" } }]), ), ), + ("$1: function $2(): void { }", Some(serde_json::json!(["declaration"]))), + ( + "switch ($0) { case $1: function $2(): void { } }", + Some(serde_json::json!(["declaration"])), + ), + ( + " + function test(a: string): string; + function test(a: number): number; + function test(a: unknown) { + return a; + } + ", + None, + ), + ( + " + export function test(a: string): string; + export function test(a: number): number; + export function test(a: unknown) { + return a; + } + ", + None, + ), + ( + " + export function test(a: string): string; + export function test(a: number): number; + export function test(a: unknown) { + return a; + } + ", + Some( + serde_json::json!(["expression", { "overrides": { "namedExports": "expression" } }]), + ), + ), + ( + " + switch ($0) { + case $1: + function test(a: string): string; + function test(a: number): number; + function test(a: unknown) { + return a; + } + } + ", + None, + ), + ( + " + switch ($0) { + case $1: + function test(a: string): string; + break; + case $2: + function test(a: unknown) { + return a; + } + } + ", + None, + ), ]; let fail = vec![ @@ -518,92 +673,217 @@ fn test() { ("var foo = () => ({ bar() { super.baz(); } });", Some(serde_json::json!(["declaration"]))), // { "ecmaVersion": 6 }, ("function foo(){}", Some(serde_json::json!(["expression"]))), ("export function foo(){}", Some(serde_json::json!(["expression"]))), - ( - "export const foo = () => {}", - Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "declaration" } }]), - ), - ), ( "export function foo() {};", Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "expression" } }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "expression" } }]), ), ), ( "export function foo() {};", Some( - serde_json::json!(["expression", { "overrides": { "namedExports": "expression" } }]), + serde_json::json!(["expression",{ "overrides": { "namedExports": "expression" } }]), ), ), ("export var foo = function(){};", Some(serde_json::json!(["declaration"]))), // { "ecmaVersion": 6 }, ( "export var foo = function(){};", Some( - serde_json::json!(["expression", { "overrides": { "namedExports": "declaration" } }]), + serde_json::json!(["expression",{ "overrides": { "namedExports": "declaration" } }]), ), ), // { "ecmaVersion": 6 }, ( "export var foo = function(){};", Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "declaration" } }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "declaration" } }]), ), ), // { "ecmaVersion": 6 }, ("export var foo = () => {};", Some(serde_json::json!(["declaration"]))), // { "ecmaVersion": 6 }, ( "export var b = () => {};", Some( - serde_json::json!(["expression", { "overrides": { "namedExports": "declaration" } }]), + serde_json::json!(["expression",{ "overrides": { "namedExports": "declaration" } }]), ), ), // { "ecmaVersion": 6 }, ( "export var c = () => {};", Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "declaration" } }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "declaration" } }]), ), ), // { "ecmaVersion": 6 }, ( "function foo() {};", Some( - serde_json::json!(["expression", { "overrides": { "namedExports": "declaration" } }]), + serde_json::json!(["expression",{ "overrides": { "namedExports": "declaration" } }]), ), ), // { "ecmaVersion": 6 }, ( "var foo = function() {};", Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "expression" } }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "expression" } }]), ), ), // { "ecmaVersion": 6 }, ( "var foo = () => {};", Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "expression" } }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "expression" } }]), ), - ), // { "ecmaVersion": 6 } + ), // { "ecmaVersion": 6 }, ( - "const arrow: Fn = () => {}", + "const foo = function() {};", + Some(serde_json::json!(["declaration", { "allowTypeAnnotation": true }])), + ), + ("$1: function $2() { }", None), // { "sourceType": "script" }, + ( + "const foo = () => {};", + Some(serde_json::json!(["declaration", { "allowTypeAnnotation": true }])), + ), + ( + "export const foo = function() {};", Some( - serde_json::json!(["declaration", { "allowTypeAnnotation": false, "allowArrowFunctions": false }]), + serde_json::json!(["expression", { "allowTypeAnnotation": true, "overrides": { "namedExports": "declaration" } }]), ), ), ( - "const foo = function() {}", - Some(serde_json::json!(["declaration", { "allowTypeAnnotation": true }])), + "export const foo = () => {};", + Some( + serde_json::json!(["expression", { "allowTypeAnnotation": true, "overrides": { "namedExports": "declaration" } }]), + ), ), + ("if (foo) function bar() {}", None), // { "sourceType": "script" }, + ("const foo: () => void = function(): void {};", Some(serde_json::json!(["declaration"]))), + ("const foo: () => void = (): void => {};", Some(serde_json::json!(["declaration"]))), ( - "export const foo = function() {}", - Some(serde_json::json!(["declaration", { "allowTypeAnnotation": true }])), + "const foo: () => void = (): void => { function foo(): void { this; } };", + Some(serde_json::json!(["declaration"])), ), ( - "export const foo = () => {}", - Some(serde_json::json!(["declaration", { "allowTypeAnnotation": true }])), + "const foo: () => { bar(): void } = (): { bar(): void } => ({ bar(): void { super.baz(); } });", + Some(serde_json::json!(["declaration"])), ), + ("function foo(): void {}", Some(serde_json::json!(["expression"]))), + ("export function foo(): void {}", Some(serde_json::json!(["expression"]))), ( - "export const foo: () => void = function () {}", + "export function foo(): void {};", Some( - serde_json::json!(["declaration", { "overrides": { "namedExports": "declaration" } }]), + serde_json::json!(["declaration",{ "overrides": { "namedExports": "expression" } }]), ), ), + ( + "export function foo(): void {};", + Some( + serde_json::json!(["expression",{ "overrides": { "namedExports": "expression" } }]), + ), + ), + ( + "export const foo: () => void = function(): void {};", + Some(serde_json::json!(["declaration"])), + ), + ( + "export const foo: () => void = function(): void {};", + Some( + serde_json::json!(["expression",{ "overrides": { "namedExports": "declaration" } }]), + ), + ), + ( + "export const foo: () => void = function(): void {};", + Some( + serde_json::json!(["declaration",{ "overrides": { "namedExports": "declaration" } }]), + ), + ), + ( + "export const foo: () => void = (): void => {};", + Some(serde_json::json!(["declaration"])), + ), + ( + "export const b: () => void = (): void => {};", + Some( + serde_json::json!(["expression", { "overrides": { "namedExports": "declaration" } } ]), + ), + ), + ( + "export const c: () => void = (): void => {};", + Some( + serde_json::json!(["declaration", { "overrides": { "namedExports": "declaration" } } ]), + ), + ), + ( + "function foo(): void {};", + Some( + serde_json::json!(["expression",{ "overrides": { "namedExports": "declaration" } }]), + ), + ), + ( + "const foo: () => void = function(): void {};", + Some( + serde_json::json!(["declaration",{ "overrides": { "namedExports": "expression" } }]), + ), + ), + ( + "const foo: () => void = (): void => {};", + Some( + serde_json::json!(["declaration",{ "overrides": { "namedExports": "expression" } }]), + ), + ), + ("$1: function $2(): void { }", None), + ("if (foo) function bar(): string {}", None), + ( + " + function test1(a: string): string; + function test2(a: number): number; + function test3(a: unknown) { + return a; + }", + None, + ), + ( + " + export function test1(a: string): string; + export function test2(a: number): number; + export function test3(a: unknown) { + return a; + }", + None, + ), + ( + " + export function test1(a: string): string; + export function test2(a: number): number; + export function test3(a: unknown) { + return a; + } + ", + Some( + serde_json::json!(["expression", { "overrides": { "namedExports": "expression" } }]), + ), + ), + ( + " + switch ($0) { + case $1: + function test1(a: string): string; + function test2(a: number): number; + function test3(a: unknown) { + return a; + } + } + ", + None, + ), + ( + " + switch ($0) { + case $1: + function test1(a: string): string; + break; + case $2: + function test2(a: unknown) { + return a; + } + } + ", + None, + ), ]; Tester::new(FuncStyle::NAME, FuncStyle::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/eslint_func_style.snap b/crates/oxc_linter/src/snapshots/eslint_func_style.snap index a34a858420725..870cec8e026bf 100644 --- a/crates/oxc_linter/src/snapshots/eslint_func_style.snap +++ b/crates/oxc_linter/src/snapshots/eslint_func_style.snap @@ -43,13 +43,6 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Enforce the consistent use of either `function` declarations or expressions assigned to variables - ⚠ eslint(func-style): Expected a function declaration. - ╭─[func_style.tsx:1:14] - 1 │ export const foo = () => {} - · ────────────── - ╰──── - help: Enforce the consistent use of either `function` declarations or expressions assigned to variables - ⚠ eslint(func-style): Expected a function expression. ╭─[func_style.tsx:1:8] 1 │ export function foo() {}; @@ -129,35 +122,223 @@ source: crates/oxc_linter/src/tester.rs ⚠ eslint(func-style): Expected a function declaration. ╭─[func_style.tsx:1:7] - 1 │ const arrow: Fn = () => {} - · ──────────────────── + 1 │ const foo = function() {}; + · ─────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:5] + 1 │ $1: function $2() { } + · ───────────────── ╰──── help: Enforce the consistent use of either `function` declarations or expressions assigned to variables ⚠ eslint(func-style): Expected a function declaration. ╭─[func_style.tsx:1:7] - 1 │ const foo = function() {} - · ─────────────────── + 1 │ const foo = () => {}; + · ────────────── ╰──── help: Enforce the consistent use of either `function` declarations or expressions assigned to variables ⚠ eslint(func-style): Expected a function declaration. ╭─[func_style.tsx:1:14] - 1 │ export const foo = function() {} + 1 │ export const foo = function() {}; · ─────────────────── ╰──── help: Enforce the consistent use of either `function` declarations or expressions assigned to variables ⚠ eslint(func-style): Expected a function declaration. ╭─[func_style.tsx:1:14] - 1 │ export const foo = () => {} + 1 │ export const foo = () => {}; · ────────────── ╰──── help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:10] + 1 │ if (foo) function bar() {} + · ───────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:7] + 1 │ const foo: () => void = function(): void {}; + · ───────────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:7] + 1 │ const foo: () => void = (): void => {}; + · ──────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:7] + 1 │ const foo: () => void = (): void => { function foo(): void { this; } }; + · ──────────────────────────────────────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:7] + 1 │ const foo: () => { bar(): void } = (): { bar(): void } => ({ bar(): void { super.baz(); } }); + · ────────────────────────────────────────────────────────────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:1] + 1 │ function foo(): void {} + · ─────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:8] + 1 │ export function foo(): void {} + · ─────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:8] + 1 │ export function foo(): void {}; + · ─────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:8] + 1 │ export function foo(): void {}; + · ─────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + ⚠ eslint(func-style): Expected a function declaration. ╭─[func_style.tsx:1:14] - 1 │ export const foo: () => void = function () {} + 1 │ export const foo: () => void = function(): void {}; + · ───────────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:14] + 1 │ export const foo: () => void = function(): void {}; + · ───────────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:14] + 1 │ export const foo: () => void = function(): void {}; + · ───────────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:14] + 1 │ export const foo: () => void = (): void => {}; · ──────────────────────────────── ╰──── help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:14] + 1 │ export const b: () => void = (): void => {}; + · ────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:14] + 1 │ export const c: () => void = (): void => {}; + · ────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:1] + 1 │ function foo(): void {}; + · ─────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:7] + 1 │ const foo: () => void = function(): void {}; + · ───────────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function declaration. + ╭─[func_style.tsx:1:7] + 1 │ const foo: () => void = (): void => {}; + · ──────────────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:5] + 1 │ $1: function $2(): void { } + · ─────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:1:10] + 1 │ if (foo) function bar(): string {} + · ───────────────────────── + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:4:7] + 3 │ function test2(a: number): number; + 4 │ ╭─▶ function test3(a: unknown) { + 5 │ │ return a; + 6 │ ╰─▶ } + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:4:14] + 3 │ export function test2(a: number): number; + 4 │ ╭─▶ export function test3(a: unknown) { + 5 │ │ return a; + 6 │ ╰─▶ } + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:4:17] + 3 │ export function test2(a: number): number; + 4 │ ╭─▶ export function test3(a: unknown) { + 5 │ │ return a; + 6 │ ╰─▶ } + 7 │ + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:6:8] + 5 │ function test2(a: number): number; + 6 │ ╭─▶ function test3(a: unknown) { + 7 │ │ return a; + 8 │ ╰─▶ } + 9 │ } + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables + + ⚠ eslint(func-style): Expected a function expression. + ╭─[func_style.tsx:7:8] + 6 │ case $2: + 7 │ ╭─▶ function test2(a: unknown) { + 8 │ │ return a; + 9 │ ╰─▶ } + 10 │ } + ╰──── + help: Enforce the consistent use of either `function` declarations or expressions assigned to variables