From 3b2b6a013a798ae88597af0605ae140d28b6b72d Mon Sep 17 00:00:00 2001 From: Ken-HH24 <62000888+Ken-HH24@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:26:29 +0800 Subject: [PATCH] feat(linter): eslint-plugin-unicorn no-await-expression-member (style) (#1569) --- crates/oxc_linter/src/rules.rs | 2 + .../unicorn/no_await_expression_member.rs | 104 ++++++++++ .../snapshots/no_await_expression_member.snap | 180 ++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs create mode 100644 crates/oxc_linter/src/snapshots/no_await_expression_member.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 5b590195e..0c21a8910 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -154,6 +154,7 @@ mod unicorn { pub mod filename_case; pub mod new_for_builtins; pub mod no_abusive_eslint_disable; + pub mod no_await_expression_member; pub mod no_console_spaces; pub mod no_document_cookie; pub mod no_empty_file; @@ -330,6 +331,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::filename_case, unicorn::new_for_builtins, unicorn::no_abusive_eslint_disable, + unicorn::no_await_expression_member, unicorn::no_console_spaces, unicorn::no_document_cookie, unicorn::no_empty_file, diff --git a/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs new file mode 100644 index 000000000..159fe83ab --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs @@ -0,0 +1,104 @@ +use oxc_ast::{ast::Expression, AstKind}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression")] +#[diagnostic(severity(warning), help("When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable."))] +struct NoAwaitExpressionMemberDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoAwaitExpressionMember; + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule disallows member access from await expression + /// + /// ### Why is this bad? + /// + /// When accessing a member from an await expression, + /// the await expression has to be parenthesized, which is not readable. + /// + /// ### Example + /// ```javascript + /// // Bad + /// const secondElement = (await getArray())[1]; + /// + /// // Good + /// const [, secondElement] = await getArray(); + /// ``` + NoAwaitExpressionMember, + style +); + +impl Rule for NoAwaitExpressionMember { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::MemberExpression(member_expr) = node.kind() else { return }; + + let Expression::ParenthesizedExpression(paren_expr) = member_expr.object() else { + return; + }; + + if matches!(paren_expr.expression, Expression::AwaitExpression(_)) { + let node_span = member_expr.span(); + ctx.diagnostic(NoAwaitExpressionMemberDiagnostic(node_span)); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + (r"const foo = await promise", None), + (r"const {foo: bar} = await promise", None), + (r"const foo = !await promise", None), + (r"const foo = typeof await promise", None), + (r"const foo = await notPromise.method()", None), + (r"const foo = foo[await promise]", None), + // These await expression need parenthesized, but rarely used + (r"new (await promiseReturnsAClass)", None), + (r"(await promiseReturnsAFunction)()", None), + // typescript + (r"async function foo () {return (await promise) as string;}", None), + ]; + + let fail = vec![ + (r"(await promise)[0]", None), + (r"(await promise).property", None), + (r"const foo = (await promise).bar()", None), + (r"const foo = (await promise).bar?.()", None), + (r"const foo = (await promise)?.bar()", None), + (r"const firstElement = (await getArray())[0]", None), + (r"const secondElement = (await getArray())[1]", None), + (r"const thirdElement = (await getArray())[2]", None), + (r"const optionalFirstElement = (await getArray())?.[0]", None), + (r"const {propertyOfFirstElement} = (await getArray())[0]", None), + (r"const [firstElementOfFirstElement] = (await getArray())[0]", None), + (r"let foo, firstElement = (await getArray())[0]", None), + (r"var firstElement = (await getArray())[0], bar", None), + (r"const property = (await getObject()).property", None), + (r"const renamed = (await getObject()).property", None), + (r"const property = (await getObject())[property]", None), + (r"const property = (await getObject())?.property", None), + (r"const {propertyOfProperty} = (await getObject()).property", None), + (r"const {propertyOfProperty} = (await getObject()).propertyOfProperty", None), + (r"const [firstElementOfProperty] = (await getObject()).property", None), + (r"const [firstElementOfProperty] = (await getObject()).firstElementOfProperty", None), + (r"firstElement = (await getArray())[0]", None), + (r"property = (await getArray()).property", None), + // typescript + (r"const foo: Type = (await promise)[0]", None), + (r"const foo: Type | A = (await promise).foo", None), + ]; + + Tester::new(NoAwaitExpressionMember::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_await_expression_member.snap b/crates/oxc_linter/src/snapshots/no_await_expression_member.snap new file mode 100644 index 000000000..c81161bc0 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_await_expression_member.snap @@ -0,0 +1,180 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_await_expression_member +--- + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ (await promise)[0] + · ────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ (await promise).property + · ──────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const foo = (await promise).bar() + · ─────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const foo = (await promise).bar?.() + · ─────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const foo = (await promise)?.bar() + · ──────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const firstElement = (await getArray())[0] + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const secondElement = (await getArray())[1] + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const thirdElement = (await getArray())[2] + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const optionalFirstElement = (await getArray())?.[0] + · ─────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const {propertyOfFirstElement} = (await getArray())[0] + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const [firstElementOfFirstElement] = (await getArray())[0] + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ let foo, firstElement = (await getArray())[0] + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ var firstElement = (await getArray())[0], bar + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const property = (await getObject()).property + · ──────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const renamed = (await getObject()).property + · ──────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const property = (await getObject())[property] + · ───────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const property = (await getObject())?.property + · ───────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const {propertyOfProperty} = (await getObject()).property + · ──────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const {propertyOfProperty} = (await getObject()).propertyOfProperty + · ────────────────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const [firstElementOfProperty] = (await getObject()).property + · ──────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const [firstElementOfProperty] = (await getObject()).firstElementOfProperty + · ────────────────────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ firstElement = (await getArray())[0] + · ───────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ property = (await getArray()).property + · ─────────────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const foo: Type = (await promise)[0] + · ────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + + ⚠ eslint-plugin-unicorn(no-await-expression-member): Disallow member access from await expression + ╭─[no_await_expression_member.tsx:1:1] + 1 │ const foo: Type | A = (await promise).foo + · ─────────────────── + ╰──── + help: When accessing a member from an await expression, the await expression has to be parenthesized, which is not readable. + +