Skip to content

Commit

Permalink
fix(linter): fix diagnostic spans for oxc/no-async-await (#8721)
Browse files Browse the repository at this point in the history
Fixes broken diagnostic spans for async functions that did not correctly report on the `async` keyword. I changed the diagnostic reporting to look for the `async` keyword itself within a given span which is a little slower but worth it for accuracy I think.

I also updated the diagnostic to be async-specific as we don't report on await yet.
  • Loading branch information
camchenry committed Jan 25, 2025
1 parent d318238 commit 77ef61a
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 38 deletions.
83 changes: 65 additions & 18 deletions crates/oxc_linter/src/rules/oxc/no_async_await.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::NodeId;
use oxc_span::Span;
use oxc_span::{GetSpan, Span};

use crate::{context::LintContext, rule::Rule, AstNode};

fn no_async_await_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Unexpected async/await")
.with_help("Async/await is not allowed")
fn no_async_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("async is not allowed")
.with_help("Remove the `async` keyword")
.with_label(span)
}

Expand Down Expand Up @@ -37,29 +36,58 @@ impl Rule for NoAsyncAwait {
match node.kind() {
AstKind::Function(func_decl) => {
if func_decl.r#async {
report(node.id(), func_decl.span, ctx);
let parent_kind = ctx.nodes().parent_kind(node.id());
let async_span = match &func_decl.id {
// named function like `async function run() {}`
Some(id) => Span::new(func_decl.span.start, id.span.end),
// anonymous function like `async function() {}`
None => match parent_kind {
// Actually part of a method definition like:
// ```
// class Foo {
// async bar() {}
// }
// ```
Some(AstKind::MethodDefinition(method_def)) => {
Span::new(method_def.span.start, method_def.key.span().start)
}
// The function is part of an object property like:
// ```
// const obj = {
// async foo() {}
// };
// ```
Some(AstKind::ObjectProperty(obj_prop)) => {
Span::new(obj_prop.span.start, obj_prop.key.span().start)
}
_ => func_decl.span,
},
};
report_on_async_span(async_span, ctx);
}
}
AstKind::ArrowFunctionExpression(arrow_expr) => {
if arrow_expr.r#async {
report(node.id(), arrow_expr.span, ctx);
let async_span = Span::new(arrow_expr.span.start, arrow_expr.params.span.start);
report_on_async_span(async_span, ctx);
}
}
_ => {}
}
}
}

fn report(node_id: NodeId, func_span: Span, ctx: &LintContext<'_>) {
/// "async".len()
const ASYNC_LEN: u32 = 5;
/// "async".len()
const ASYNC_LEN: u32 = 5;

let parent = ctx.nodes().parent_kind(node_id);
if let Some(AstKind::ObjectProperty(obj_prop)) = parent {
ctx.diagnostic(no_async_await_diagnostic(Span::sized(obj_prop.span.start, ASYNC_LEN)));
} else {
ctx.diagnostic(no_async_await_diagnostic(Span::sized(func_span.start, ASYNC_LEN)));
}
#[allow(clippy::cast_possible_truncation)]
fn report_on_async_span(async_span: Span, ctx: &LintContext<'_>) {
// find the `async` keyword within the span and report on it
let Some(async_keyword_offset) = ctx.source_range(async_span).find("async") else {
return;
};
let async_keyword_span = Span::sized(async_span.start + async_keyword_offset as u32, ASYNC_LEN);
ctx.diagnostic(no_async_diagnostic(async_keyword_span));
}

#[test]
Expand All @@ -71,6 +99,9 @@ fn test() {
"const foo = () => {}",
"function foo () { return bar(); }",
"class Foo { foo() {} }",
"class async { }",
"const async = {};",
"class async { async() { async(); } }",
];

let fail = vec![
Expand All @@ -83,7 +114,6 @@ fn test() {
async test() {}
};
",
// FIXME: diagnostics on method `foo` have incorrect spans
"
class Foo {
async foo() {}
Expand All @@ -94,12 +124,29 @@ fn test() {
public async foo() {}
}
",
// this one is fine
"
const obj = {
async foo() {}
}
",
"
class async {
async async() {
async();
}
}
",
"
class async {
async async() {
function async() {
const async = {
async: async () => {},
}
}
}
}
",
];

Tester::new(NoAsyncAwait::NAME, NoAsyncAwait::PLUGIN, pass, fail).test_and_snapshot();
Expand Down
67 changes: 47 additions & 20 deletions crates/oxc_linter/src/snapshots/oxc_no_async_await.snap
Original file line number Diff line number Diff line change
@@ -1,66 +1,93 @@
---
source: crates/oxc_linter/src/tester.rs
---
oxc(no-async-await): Unexpected async/await
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:1:1]
1async function foo() {}
· ─────
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): Unexpected async/await
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:1:13]
1const foo = async () => {}
· ─────
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): Unexpected async/await
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:1:1]
1async () => {}
· ─────
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): Unexpected async/await
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:1:14]
1const test = async () => {};
· ─────
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): Unexpected async/await
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:3:17]
2const test = {
3 │ async test() {}
· ─────
4 │ };
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): Unexpected async/await
╭─[no_async_await.tsx:3:22]
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:3:13]
2class Foo {
3async foo() {}
· ─────
· ─────
4 │ }
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): Unexpected async/await
╭─[no_async_await.tsx:3:29]
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:3:20]
2class Foo {
3public async foo() {}
· ─────
· ─────
4 │ }
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): Unexpected async/await
oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:3:13]
2const obj = {
3 │ async foo() {}
· ─────
4 │ }
╰────
help: Async/await is not allowed
help: Remove the `async` keyword

oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:3:13]
2class async {
3async async() {
· ─────
4async();
╰────
help: Remove the `async` keyword

oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:3:13]
2class async {
3async async() {
· ─────
4function async() {
╰────
help: Remove the `async` keyword

oxc(no-async-await): async is not allowed
╭─[no_async_await.tsx:6:32]
5const async = {
6 │ async: async () => {},
· ─────
7 │ }
╰────
help: Remove the `async` keyword

0 comments on commit 77ef61a

Please sign in to comment.