diff --git a/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs b/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs index 82015f81bdb74..a0fa7e4492172 100644 --- a/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/array_callback_return/mod.rs @@ -159,15 +159,21 @@ impl Rule for ArrayCallbackReturn { } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let (function_body, always_explicit_return) = match node.kind() { + let (function_body, always_explicit_return, is_async_empty) = match node.kind() { // Async, generator, and single expression arrow functions // always have explicit return value - AstKind::ArrowFunctionExpression(arrow) => { - (&arrow.body, arrow.r#async || arrow.expression) - } + AstKind::ArrowFunctionExpression(arrow) => ( + &arrow.body, + arrow.r#async || arrow.expression, + arrow.r#async && arrow.body.statements.is_empty(), + ), AstKind::Function(function) => { if let Some(body) = &function.body { - (body, function.r#async || function.generator) + ( + body, + function.r#async || function.generator, + function.r#async && body.statements.is_empty(), + ) } else { return; } @@ -198,6 +204,19 @@ impl Rule for ArrayCallbackReturn { )); } } + ("fromAsync", _, false) => { + if !return_status.must_return() + || return_status.may_return_implicit() + || is_async_empty + { + ctx.diagnostic(expect_return( + &full_array_method_name(array_method), + array_method_span, + function_body, + self.allow_implicit, + )); + } + } (_, _, true) => { if !return_status.must_return() { ctx.diagnostic(expect_return( @@ -286,6 +305,17 @@ pub fn get_array_method_info<'a>( } } + // Array.fromAsync + if callee.is_specific_member_access("Array", "fromAsync") { + // Check that current node is parent's second argument + if call.arguments.len() == 2 + && let Some(call_arg) = call.arguments[1].as_expression() + && call_arg.span() == current_node.kind().span() + { + return Some((callee.span(), "fromAsync")); + } + } + // "methods", let (array_method_span, array_method) = callee.static_property_info()?; @@ -330,6 +360,7 @@ const TARGET_METHODS: [&str; 14] = [ fn full_array_method_name(array_method: &str) -> Cow<'static, str> { match array_method { "from" => Cow::Borrowed("Array.from"), + "fromAsync" => Cow::Borrowed("Array.fromAsync"), s => Cow::Owned(format!("Array.prototype.{s}")), } } @@ -497,6 +528,26 @@ fn test() { "foo.every(function() { return; })", Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), ), + /*( + "foo.forEach((x) => void x)", + Some(serde_json::json!([{"allowVoid": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => void bar(x))", + Some(serde_json::json!([{"allowVoid": true, "checkForEach": true}])), + ), + ( + "foo.forEach(function (x) { return void bar(x); })", + Some(serde_json::json!([{"allowVoid": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { return void bar(x); })", + Some(serde_json::json!([{"allowVoid": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", + Some(serde_json::json!([{"allowVoid": true, "checkForEach": true}])), + ),*/ ("Arrow.from(x, function() {})", None), ("foo.abc(function() {})", None), ("every(function() {})", None), @@ -509,6 +560,16 @@ fn test() { "array.map((node) => { if (isTaskNode(node)) { return someObj; } else if (isOtherNode(node)) { return otherObj; } else { throw new Error('Unsupported'); } })", None, ), + ("Array.fromAsync(x, function() { return true; })", None), + ("Array.fromAsync(x, async function() { return true; })", None), + ( + "Array.fromAsync(x, function() { return; })", + Some(serde_json::json!([{"allowImplicit": true}])), + ), + ("Array.fromAsync(x, async () => true)", None), + ("Array.fromAsync(x, function * () {})", None), + ("Float64Array.fromAsync(x, function() {})", None), + ("Array.fromAsync(function() {})", None), ]; let fail = vec![ @@ -609,6 +670,42 @@ const _test = fruits.map((fruit) => { "foo.forEach(x => x)", Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), ), + ( + "foo.forEach(x => !x)", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach(x => (x))", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { return x; })", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { return !x; })", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { return(x); })", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { return (x + 1); })", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { if (a === b) { return x; } })", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { if (a === b) { return !x; } })", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), + ( + "foo.forEach((x) => { if (a === b) { return (x + a); } })", + Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), + ), ( "foo.forEach(function(x) { if (a == b) {return x;}})", Some(serde_json::json!([{"allowImplicit": true, "checkForEach": true}])), @@ -637,10 +734,6 @@ const _test = fruits.map((fruit) => { "foo.forEach(function bar(x) { return x;})", Some(serde_json::json!([{"checkForEach": true}])), ), - ( - "foo.forEach(function bar(x) { return x;})", - Some(serde_json::json!([{"checkForEach": true}])), - ), ( "foo.bar().forEach(function bar(x) { return x;})", Some(serde_json::json!([{"checkForEach": true}])), @@ -676,6 +769,17 @@ const _test = fruits.map((fruit) => { "foo.forEach((bar) => { if (bar) { return; } else { return bar ; } })", Some(serde_json::json!([{"checkForEach": true}])), ), + ("foo.forEach(x => (x))", Some(serde_json::json!([{"checkForEach": true}]))), + ("foo.forEach((x) => void x)", Some(serde_json::json!([{"checkForEach": true}]))), + ("foo.forEach((x) => void bar(x))", Some(serde_json::json!([{"checkForEach": true}]))), + ( + "foo.forEach((x) => { return void bar(x); })", + Some(serde_json::json!([{"checkForEach": true}])), + ), + ( + "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", + Some(serde_json::json!([{"checkForEach": true}])), + ), ("foo.filter(function(){})", None), ("foo.filter(function (){})", None), ("foo.filter(function\n(){})", None), @@ -689,11 +793,23 @@ const _test = fruits.map((fruit) => { "foo.forEach(function () { \nif (baz) return bar\nelse return\n })", Some(serde_json::json!([{"checkForEach": true}])), ), + ( + r"Array.fromAsync(x, + async function \\u0066oo // bar + () {})", + None, + ), ("foo?.filter(() => { console.log('hello') })", None), ("(foo?.filter)(() => { console.log('hello') })", None), ("Array?.from([], () => { console.log('hello') })", None), ("(Array?.from)([], () => { console.log('hello') })", None), ("foo?.filter((function() { return () => { console.log('hello') } })?.())", None), + ("Array.fromAsync(x, function() {})", None), + ("Array.fromAsync(x, function() {})", Some(serde_json::json!([{"allowImplicit": true}]))), + ("Array.fromAsync(x, () => {})", None), + ("Array.fromAsync(x, function foo() {})", None), + ("Array.fromAsync(x, async function() {})", None), + ("Array.fromAsync(x, async () => {})", None), ]; Tester::new(ArrayCallbackReturn::NAME, ArrayCallbackReturn::PLUGIN, pass, fail) diff --git a/crates/oxc_linter/src/snapshots/eslint_array_callback_return.snap b/crates/oxc_linter/src/snapshots/eslint_array_callback_return.snap index 3f0cc187fbcc6..12594818197d8 100644 --- a/crates/oxc_linter/src/snapshots/eslint_array_callback_return.snap +++ b/crates/oxc_linter/src/snapshots/eslint_array_callback_return.snap @@ -579,6 +579,87 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach(x => !x) + · ───┬─── ─┬ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach(x => (x)) + · ───┬─── ─┬─ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { return x; }) + · ───┬─── ┬ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { return !x; }) + · ───┬─── ─┬ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { return(x); }) + · ───┬─── ─┬─ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { return (x + 1); }) + · ───┬─── ───┬─── + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { if (a === b) { return x; } }) + · ───┬─── ┬ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { if (a === b) { return !x; } }) + · ───┬─── ─┬ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { if (a === b) { return (x + a); } }) + · ───┬─── ───┬─── + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" ╭─[array_callback_return.tsx:1:5] 1 │ foo.forEach(function(x) { if (a == b) {return x;}}) @@ -669,15 +750,6 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. - ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" - ╭─[array_callback_return.tsx:1:5] - 1 │ foo.forEach(function bar(x) { return x;}) - · ───┬─── ┬ - · │ ╰── This returned value is ignored. - · ╰── "Array.prototype.forEach" is called here. - ╰──── - help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. - ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" ╭─[array_callback_return.tsx:1:11] 1 │ foo.bar().forEach(function bar(x) { return x;}) @@ -824,6 +896,51 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach(x => (x)) + · ───┬─── ─┬─ + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => void x) + · ───┬─── ───┬── + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => void bar(x)) + · ───┬─── ─────┬───── + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { return void bar(x); }) + · ───┬─── ─────┬───── + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + + ⚠ eslint(array-callback-return): Unexpected return value in callback for "Array.prototype.forEach" + ╭─[array_callback_return.tsx:1:5] + 1 │ foo.forEach((x) => { if (a === b) { return void a; } bar(x) }) + · ───┬─── ───┬── + · │ ╰── This returned value is ignored. + · ╰── "Array.prototype.forEach" is called here. + ╰──── + help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + ⚠ eslint(array-callback-return): Callback for array method "Array.prototype.filter" does not return on all code paths ╭─[array_callback_return.tsx:1:22] 1 │ foo.filter(function(){}) @@ -910,6 +1027,14 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: "Array.prototype.forEach" ignores the callback's return value. Remove the returned value (use `return;` or no `return`), or use `map`/`flatMap` if you meant to produce a new array. + × Invalid Unicode escape sequence + ╭─[array_callback_return.tsx:2:29] + 1 │ Array.fromAsync(x, + 2 │ async function \\u0066oo // bar + · ─ + 3 │ () {}) + ╰──── + ⚠ eslint(array-callback-return): Callback for array method "Array.prototype.filter" does not return on all code paths ╭─[array_callback_return.tsx:1:19] 1 │ foo?.filter(() => { console.log('hello') }) @@ -949,3 +1074,51 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: "Array.prototype.filter" uses the callback's return value. Add a `return` on every possible code path. Return a value on each path (or enable `allowImplicit` to allow `return;`). + + ⚠ eslint(array-callback-return): Callback for array method "Array.fromAsync" does not return on all code paths + ╭─[array_callback_return.tsx:1:31] + 1 │ Array.fromAsync(x, function() {}) + · ── + ╰──── + help: "Array.fromAsync" uses the callback's return value. Add a `return` on every possible code path. + Return a value on each path (or enable `allowImplicit` to allow `return;`). + + ⚠ eslint(array-callback-return): Callback for array method "Array.fromAsync" does not return on all code paths + ╭─[array_callback_return.tsx:1:31] + 1 │ Array.fromAsync(x, function() {}) + · ── + ╰──── + help: "Array.fromAsync" uses the callback's return value. Add a `return` on every possible code path. + note: With `allowImplicit`, callbacks that don't explicitly return a value are considered to return `undefined`. + + ⚠ eslint(array-callback-return): Callback for array method "Array.fromAsync" does not return on all code paths + ╭─[array_callback_return.tsx:1:26] + 1 │ Array.fromAsync(x, () => {}) + · ── + ╰──── + help: "Array.fromAsync" uses the callback's return value. Add a `return` on every possible code path. + Return a value on each path (or enable `allowImplicit` to allow `return;`). + + ⚠ eslint(array-callback-return): Callback for array method "Array.fromAsync" does not return on all code paths + ╭─[array_callback_return.tsx:1:35] + 1 │ Array.fromAsync(x, function foo() {}) + · ── + ╰──── + help: "Array.fromAsync" uses the callback's return value. Add a `return` on every possible code path. + Return a value on each path (or enable `allowImplicit` to allow `return;`). + + ⚠ eslint(array-callback-return): Callback for array method "Array.fromAsync" does not return on all code paths + ╭─[array_callback_return.tsx:1:37] + 1 │ Array.fromAsync(x, async function() {}) + · ── + ╰──── + help: "Array.fromAsync" uses the callback's return value. Add a `return` on every possible code path. + Return a value on each path (or enable `allowImplicit` to allow `return;`). + + ⚠ eslint(array-callback-return): Callback for array method "Array.fromAsync" does not return on all code paths + ╭─[array_callback_return.tsx:1:32] + 1 │ Array.fromAsync(x, async () => {}) + · ── + ╰──── + help: "Array.fromAsync" uses the callback's return value. Add a `return` on every possible code path. + Return a value on each path (or enable `allowImplicit` to allow `return;`).