Skip to content

Commit

Permalink
feat(linter): implement unicorn/no-unnecessary-await (#856)
Browse files Browse the repository at this point in the history
related to #684
  • Loading branch information
Devin-Yeung authored Sep 4, 2023
1 parent be1be2e commit 286049b
Show file tree
Hide file tree
Showing 3 changed files with 407 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ mod jest {

mod unicorn {
pub mod no_instanceof_array;
pub mod no_unnecessary_await;
}

oxc_macros::declare_all_lint_rules! {
Expand Down Expand Up @@ -191,4 +192,5 @@ oxc_macros::declare_all_lint_rules! {
jest::no_alias_methods,
jest::no_conditional_expect,
unicorn::no_instanceof_array,
unicorn::no_unnecessary_await
}
162 changes: 162 additions & 0 deletions crates/oxc_linter/src/rules/unicorn/no_unnecessary_await.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use oxc_ast::{ast::Expression, AstKind};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_formatter::Gen;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

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

#[derive(Debug, Error, Diagnostic)]
#[error("Disallow awaiting non-promise values.")]
#[diagnostic(severity(warning), help("consider to remove the `await`"))]
struct NoUnnecessaryAwaitDiagnostic(#[label] pub Span);

#[derive(Debug, Default, Clone)]
pub struct NoUnnecessaryAwait;

declare_oxc_lint!(
/// ### What it does
/// Disallow awaiting on non-promise values.
///
/// ### Why is this bad?
/// The `await` operator should only be used on `Promise` values.
///
/// ### Example
/// ```javascript
/// await await promise;
/// ```
NoUnnecessaryAwait,
correctness
);

impl Rule for NoUnnecessaryAwait {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::AwaitExpression(expr) = node.kind() {
if !not_promise(&expr.argument) {
return;
}
if {
// Removing `await` may change them to a declaration, if there is no `id` will cause SyntaxError
matches!(expr.argument, Expression::FunctionExpression(_))
|| matches!(expr.argument, Expression::ClassExpression(_))
} || {
// `+await +1` -> `++1`
ctx.nodes().parent_node(node.id()).map_or(false, |parent| {
if let (
AstKind::UnaryExpression(parent_unary),
Expression::UnaryExpression(inner_unary),
) = (parent.kind(), &expr.argument)
{
parent_unary.operator == inner_unary.operator
} else {
false
}
})
} {
ctx.diagnostic(NoUnnecessaryAwaitDiagnostic(expr.span));
} else {
ctx.diagnostic_with_fix(NoUnnecessaryAwaitDiagnostic(expr.span), || {
let mut formatter = ctx.formatter();
expr.argument.gen(&mut formatter);
Fix::new(formatter.into_code(), expr.span)
});
};
}
}
}

fn not_promise(expr: &Expression) -> bool {
match expr {
Expression::ArrayExpression(_)
| Expression::ArrowExpression(_)
| Expression::AwaitExpression(_)
| Expression::BinaryExpression(_)
| Expression::ClassExpression(_)
| Expression::FunctionExpression(_)
| Expression::JSXElement(_)
| Expression::JSXFragment(_)
| Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::NumberLiteral(_)
| Expression::BigintLiteral(_)
| Expression::RegExpLiteral(_)
| Expression::StringLiteral(_)
| Expression::TemplateLiteral(_)
| Expression::UnaryExpression(_)
| Expression::UpdateExpression(_) => true,
Expression::SequenceExpression(expr) => not_promise(expr.expressions.last().unwrap()),
Expression::ParenthesizedExpression(expr) => not_promise(&expr.expression),
_ => false,
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
("await {then}", None),
("await a ? b : c", None),
("await a || b", None),
("await a && b", None),
("await a ?? b", None),
("await new Foo()", None),
("await tagged``", None),
("class A { async foo() { await this }}", None),
("async function * foo() {await (yield bar);}", None),
("await (1, Promise.resolve())", None),
];

let fail = vec![
("await []", None),
("await [Promise.resolve()]", None),
("await (() => {})", None),
("await (() => Promise.resolve())", None),
("await (a === b)", None),
("await (a instanceof Promise)", None),
("await (a > b)", None),
("await class {}", None),
("await class extends Promise {}", None),
("await function() {}", None),
("await function name() {}", None),
("await function() { return Promise.resolve() }", None),
("await (<></>)", None),
("await (<a></a>)", None),
("await 0", None),
("await 1", None),
("await \"\"", None),
("await \"string\"", None),
("await true", None),
("await false", None),
("await null", None),
("await 0n", None),
("await 1n", None),
("await `${Promise.resolve()}`", None),
("await !Promise.resolve()", None),
("await void Promise.resolve()", None),
("await +Promise.resolve()", None),
("await ~1", None),
("await ++foo", None),
("await foo--", None),
("await (Promise.resolve(), 1)", None),
("async function foo() {+await +1}", None),
("async function foo() {-await-1}", None),
("async function foo() {+await -1}", None),
];

let fix = vec![
("await []", "[]", None),
("await (a == b)", "(a == b)", None),
("+await -1", "+ -1", None),
("-await +1", "- +1", None),
("await function() {}", "await function() {}", None), // no autofix
("await class {}", "await class {}", None), // no autofix
("+await +1", "+await +1", None), // no autofix
("-await -1", "-await -1", None), // no autofix
];

Tester::new(NoUnnecessaryAwait::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
}
Loading

0 comments on commit 286049b

Please sign in to comment.