From 650615c15d1656e59cea5f1a916476817f5ea78e Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 17 Jul 2024 11:32:00 -0400 Subject: [PATCH 1/6] build: compile macro crates with some optimizations (#4327) ## What This PR Does Compile `oxc_macros` and `oxc_ast_macros` with `O1` in debug builds. This should make consuming crates compile faster, as well as test binaries (oxc_linter tests take a while to compile when developing rules locally) --- Cargo.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index b7e62aa77c175..08bd15f4716ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -192,6 +192,11 @@ ignored = ["napi", "oxc_transform_napi", "prettyplease"] # and we don't rely on it for debugging that much. debug = false +[profile.dev.package] +# Compile macros with some optimizations to make consuming crates build faster +oxc_macros.opt-level = 1 +oxc_ast_macros.opt-level = 1 + [profile.release.package.oxc_wasm] opt-level = 'z' From 1458d8126843fc750909b39f4d00b586d4cd1439 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:10:36 +0000 Subject: [PATCH 2/6] refactor(visit): add `#[inline]` to empty functions (#4330) Add `#[inline]` to empty default implementations of `enter_node` etc. Hopefully compiler will inline them automatically within Oxc even cross-crate because we compile with LTO, but maybe not for external consumers who don't use LTO. --- crates/oxc_ast/src/generated/visit.rs | 4 ++++ crates/oxc_ast/src/generated/visit_mut.rs | 4 ++++ tasks/ast_codegen/src/generators/visit.rs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/crates/oxc_ast/src/generated/visit.rs b/crates/oxc_ast/src/generated/visit.rs index b953314fc94e6..79a63005754d4 100644 --- a/crates/oxc_ast/src/generated/visit.rs +++ b/crates/oxc_ast/src/generated/visit.rs @@ -27,10 +27,14 @@ use walk::*; /// Syntax tree traversal pub trait Visit<'a>: Sized { + #[inline] fn enter_node(&mut self, kind: AstKind<'a>) {} + #[inline] fn leave_node(&mut self, kind: AstKind<'a>) {} + #[inline] fn enter_scope(&mut self, flags: ScopeFlags, scope_id: &Cell>) {} + #[inline] fn leave_scope(&mut self) {} #[inline] diff --git a/crates/oxc_ast/src/generated/visit_mut.rs b/crates/oxc_ast/src/generated/visit_mut.rs index d0961c65f275d..37343df6f46fe 100644 --- a/crates/oxc_ast/src/generated/visit_mut.rs +++ b/crates/oxc_ast/src/generated/visit_mut.rs @@ -27,10 +27,14 @@ use walk_mut::*; /// Syntax tree traversal pub trait VisitMut<'a>: Sized { + #[inline] fn enter_node(&mut self, kind: AstType) {} + #[inline] fn leave_node(&mut self, kind: AstType) {} + #[inline] fn enter_scope(&mut self, flags: ScopeFlags, scope_id: &Cell>) {} + #[inline] fn leave_scope(&mut self) {} #[inline] diff --git a/tasks/ast_codegen/src/generators/visit.rs b/tasks/ast_codegen/src/generators/visit.rs index 67a988dcaee45..a912c5941022c 100644 --- a/tasks/ast_codegen/src/generators/visit.rs +++ b/tasks/ast_codegen/src/generators/visit.rs @@ -122,12 +122,16 @@ fn generate_visit(ctx: &CodegenCtx) -> TokenStream { /// Syntax tree traversal pub trait #trait_name <'a>: Sized { + #[inline] fn enter_node(&mut self, kind: #ast_kind_type #ast_kind_life) {} + #[inline] fn leave_node(&mut self, kind: #ast_kind_type #ast_kind_life) {} endl!(); + #[inline] fn enter_scope(&mut self, flags: ScopeFlags, scope_id: &Cell>) {} + #[inline] fn leave_scope(&mut self) {} endl!(); From db2fd70432cb7912f507dba27df279aee84fc1c5 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 17 Jul 2024 22:13:54 +0200 Subject: [PATCH 3/6] feat(linter/eslint-plugin-promise): implement no-webpack-loader-syntax (#4331) Rule detail: [link](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-webpack-loader-syntax.md) --- crates/oxc_linter/src/rules.rs | 2 + .../rules/import/no_webpack_loader_syntax.rs | 118 ++++++++++++++++++ .../snapshots/no_webpack_loader_syntax.snap | 58 +++++++++ 3 files changed, 178 insertions(+) create mode 100644 crates/oxc_linter/src/rules/import/no_webpack_loader_syntax.rs create mode 100644 crates/oxc_linter/src/snapshots/no_webpack_loader_syntax.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index ad727107d6f06..c9d85f32933bc 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -21,6 +21,7 @@ mod import { pub mod no_named_as_default_member; pub mod no_self_import; // pub mod no_unused_modules; + pub mod no_webpack_loader_syntax; } mod eslint { @@ -741,6 +742,7 @@ oxc_macros::declare_all_lint_rules! { // import::no_unused_modules, import::no_duplicates, import::no_default_export, + import::no_webpack_loader_syntax, jsx_a11y::alt_text, jsx_a11y::anchor_has_content, jsx_a11y::anchor_is_valid, diff --git a/crates/oxc_linter/src/rules/import/no_webpack_loader_syntax.rs b/crates/oxc_linter/src/rules/import/no_webpack_loader_syntax.rs new file mode 100644 index 0000000000000..1100ae680791d --- /dev/null +++ b/crates/oxc_linter/src/rules/import/no_webpack_loader_syntax.rs @@ -0,0 +1,118 @@ +use oxc_ast::{ + ast::{Argument, Expression}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::AstNode; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule}; + +fn no_named_as_default_diagnostic(x0: &str, span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!( + "eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `{x0}`." + )) + .with_help("Do not use import syntax to configure webpack loaders") + .with_label(span0) +} + +#[derive(Debug, Default, Clone)] +pub struct NoWebpackLoaderSyntax; + +declare_oxc_lint!( + /// ### What it does + /// + /// Forbid Webpack loader syntax in imports. + /// + /// ### Why is this bad? + /// + /// This loader syntax is non-standard, so it couples the code to Webpack. The recommended way to + /// specify Webpack loader configuration is in a [Webpack configuration file](https://webpack.js.org/concepts/loaders/#configuration). + /// + /// ### Example + /// ```javascript + /// import myModule from 'my-loader!my-module'; + /// import theme from 'style!css!./theme.css'; + /// + /// var myModule = require('my-loader!./my-module'); + /// var theme = require('style!css!./theme.css'); + /// ``` + NoWebpackLoaderSyntax, + restriction, +); + +impl Rule for NoWebpackLoaderSyntax { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // not in top level + if node.scope_id() != ctx.scopes().root_scope_id() { + return; + } + + match node.kind() { + AstKind::CallExpression(call_expr) => { + if let Expression::Identifier(identifier) = &call_expr.callee { + if identifier.name != "require" { + return; + } + + if call_expr.arguments.len() != 1 { + return; + } + + let Argument::StringLiteral(ident) = &call_expr.arguments[0] else { + return; + }; + + if ident.value.contains('!') { + ctx.diagnostic(no_named_as_default_diagnostic( + ident.value.as_str(), + ident.span, + )); + } + } + } + AstKind::ImportDeclaration(import_decl) => { + if import_decl.source.value.contains('!') { + ctx.diagnostic(no_named_as_default_diagnostic( + &import_decl.source.value, + import_decl.source.span, + )); + } + } + _ => {} + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "import _ from 'lodash'", + "import find from 'lodash.find'", + "import foo from './foo.css'", + "import data from '@scope/my-package/data.json'", + "var _ = require('lodash')", + "var find = require('lodash.find')", + "var foo = require('./foo')", + "var foo = require('../foo')", + "var foo = require('foo')", + "var foo = require('./')", + "var foo = require('@scope/foo')", + ]; + + let fail = vec![ + "import _ from 'babel!lodash'", + "import find from '-babel-loader!lodash.find'", + "import foo from 'style!css!./foo.css'", + "import data from 'json!@scope/my-package/data.json'", + "var _ = require('babel!lodash')", + "var find = require('-babel-loader!lodash.find')", + "var foo = require('style!css!./foo.css')", + "var data = require('json!@scope/my-package/data.json')", + ]; + + Tester::new(NoWebpackLoaderSyntax::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_webpack_loader_syntax.snap b/crates/oxc_linter/src/snapshots/no_webpack_loader_syntax.snap new file mode 100644 index 0000000000000..68026934379c1 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_webpack_loader_syntax.snap @@ -0,0 +1,58 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `babel!lodash`. + ╭─[no_webpack_loader_syntax.tsx:1:15] + 1 │ import _ from 'babel!lodash' + · ────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders + + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `-babel-loader!lodash.find`. + ╭─[no_webpack_loader_syntax.tsx:1:18] + 1 │ import find from '-babel-loader!lodash.find' + · ─────────────────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders + + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `style!css!./foo.css`. + ╭─[no_webpack_loader_syntax.tsx:1:17] + 1 │ import foo from 'style!css!./foo.css' + · ───────────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders + + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `json!@scope/my-package/data.json`. + ╭─[no_webpack_loader_syntax.tsx:1:18] + 1 │ import data from 'json!@scope/my-package/data.json' + · ────────────────────────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders + + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `babel!lodash`. + ╭─[no_webpack_loader_syntax.tsx:1:17] + 1 │ var _ = require('babel!lodash') + · ────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders + + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `-babel-loader!lodash.find`. + ╭─[no_webpack_loader_syntax.tsx:1:20] + 1 │ var find = require('-babel-loader!lodash.find') + · ─────────────────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders + + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `style!css!./foo.css`. + ╭─[no_webpack_loader_syntax.tsx:1:19] + 1 │ var foo = require('style!css!./foo.css') + · ───────────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders + + ⚠ eslint-plugin-import(no-webpack-loader-syntax): Unexpected `!` in `json!@scope/my-package/data.json`. + ╭─[no_webpack_loader_syntax.tsx:1:20] + 1 │ var data = require('json!@scope/my-package/data.json') + · ────────────────────────────────── + ╰──── + help: Do not use import syntax to configure webpack loaders From 4463eb4c48f848e040211224ae80c47195c4c4b9 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 17 Jul 2024 22:21:31 +0200 Subject: [PATCH 4/6] chore(linter): add fixer test cases for prefer-dom-node-text-content (#4315) --- .../src/rules/unicorn/prefer_dom_node_text_content.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs b/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs index 01dc85e53745d..6813fab02552d 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs @@ -123,5 +123,13 @@ fn test() { ("for (const [{innerText}] of elements);", None), ]; - Tester::new(PreferDomNodeTextContent::NAME, pass, fail).test_and_snapshot(); + // TODO: implement a fixer for destructuring assignment cases + let fix = vec![ + ("node.innerText;", "node.textContent;"), + ("node?.innerText;", "node?.textContent;"), + ("node.innerText = 'foo';", "node.textContent = 'foo';"), + ("innerText.innerText = 'foo';", "innerText.textContent = 'foo';"), + ]; + + Tester::new(PreferDomNodeTextContent::NAME, pass, fail).expect_fix(fix).test_and_snapshot(); } From 2213f9393baf4883d42c1f4c33a682f66dd42cb2 Mon Sep 17 00:00:00 2001 From: cinchen Date: Thu, 18 Jul 2024 04:30:35 +0800 Subject: [PATCH 5/6] feat(linter): eslint-plugin-vitest/no-alias-methods (#4301) support [eslint-plugin-vitest/no-alias-methods](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-alias-method.md) --- .../src/rules/jest/no_alias_methods.rs | 72 ++++++++++++++++--- .../src/snapshots/no_alias_methods.snap | 22 ++++++ crates/oxc_linter/src/utils/mod.rs | 1 + 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/crates/oxc_linter/src/rules/jest/no_alias_methods.rs b/crates/oxc_linter/src/rules/jest/no_alias_methods.rs index 672752b15d0e6..1b59f07bfc1a7 100644 --- a/crates/oxc_linter/src/rules/jest/no_alias_methods.rs +++ b/crates/oxc_linter/src/rules/jest/no_alias_methods.rs @@ -6,13 +6,21 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{collect_possible_jest_call_node, parse_expect_jest_fn_call, PossibleJestNode}, + utils::{ + collect_possible_jest_call_node, get_test_plugin_name, parse_expect_jest_fn_call, + PossibleJestNode, TestPluginName, + }, }; -fn no_alias_methods_diagnostic(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { - OxcDiagnostic::warn(format!("eslint-plugin-jest(no-alias-methods): Unexpected alias {x0:?}")) - .with_help(format!("Replace {x0:?} with its canonical name of {x1:?}")) - .with_label(span2) +fn no_alias_methods_diagnostic( + x0: TestPluginName, + x1: &str, + x2: &str, + span3: Span, +) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("{x0}(no-alias-methods): Unexpected alias {x1:?}")) + .with_help(format!("Replace {x1:?} with its canonical name of {x2:?}")) + .with_label(span3) } #[derive(Debug, Default, Clone)] @@ -42,6 +50,17 @@ declare_oxc_lint!( /// expect(a).nthReturnedWith(); /// expect(a).toThrowError(); /// ``` + /// + /// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-alias-methods.md), + /// to use it, add the following configuration to your `.eslintrc.json`: + /// + /// ```json + /// { + /// "rules": { + /// "vitest/no-alias-methods": "error" + /// } + /// } + /// ``` NoAliasMethods, style ); @@ -77,8 +96,10 @@ fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) span.end -= 1; } + let plugin_name = get_test_plugin_name(ctx); + ctx.diagnostic_with_fix( - no_alias_methods_diagnostic(name, canonical_name, matcher.span), + no_alias_methods_diagnostic(plugin_name, name, canonical_name, matcher.span), // || Fix::new(canonical_name, Span::new(start, end)), |fixer| fixer.replace(span, canonical_name), ); @@ -140,7 +161,7 @@ impl BadAliasMethodName { fn test() { use crate::tester::Tester; - let pass = vec![ + let mut pass = vec![ ("expect(a).toHaveBeenCalled()", None), ("expect(a).toHaveBeenCalledTimes()", None), ("expect(a).toHaveBeenCalledWith()", None), @@ -156,7 +177,7 @@ fn test() { ("expect(a);", None), ]; - let fail = vec![ + let mut fail = vec![ ("expect(a).toBeCalled()", None), ("expect(a).toBeCalledTimes()", None), ("expect(a).toBeCalledWith()", None), @@ -174,14 +195,47 @@ fn test() { ("expect(a).not['toThrowError']()", None), ]; - let fix = vec![ + let mut fix = vec![ ("expect(a).toBeCalled()", "expect(a).toHaveBeenCalled()", None), ("expect(a).not['toThrowError']()", "expect(a).not['toThrow']()", None), ("expect(a).not[`toThrowError`]()", "expect(a).not[`toThrow`]()", None), ]; + let pass_vitest = vec![ + "expect(a).toHaveBeenCalled()", + "expect(a).toHaveBeenCalledTimes()", + "expect(a).toHaveBeenCalledWith()", + "expect(a).toHaveBeenLastCalledWith()", + "expect(a).toHaveBeenNthCalledWith()", + "expect(a).toHaveReturned()", + "expect(a).toHaveReturnedTimes()", + "expect(a).toHaveReturnedWith()", + "expect(a).toHaveLastReturnedWith()", + "expect(a).toHaveNthReturnedWith()", + "expect(a).toThrow()", + "expect(a).rejects;", + "expect(a);", + ]; + + let fail_vitest = vec![ + "expect(a).toBeCalled()", + "expect(a).toBeCalledTimes()", + r#"expect(a).not["toThrowError"]()"#, + ]; + + let fix_vitest = vec![ + ("expect(a).toBeCalled()", "expect(a).toHaveBeenCalled()", None), + ("expect(a).toBeCalledTimes()", "expect(a).toHaveBeenCalledTimes()", None), + ("expect(a).not['toThrowError']()", "expect(a).not['toThrow']()", None), + ]; + + pass.extend(pass_vitest.into_iter().map(|x| (x, None))); + fail.extend(fail_vitest.into_iter().map(|x| (x, None))); + fix.extend(fix_vitest); + Tester::new(NoAliasMethods::NAME, pass, fail) .with_jest_plugin(true) + .with_vitest_plugin(true) .expect_fix(fix) .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/snapshots/no_alias_methods.snap b/crates/oxc_linter/src/snapshots/no_alias_methods.snap index d4a4d39090751..30605125a282f 100644 --- a/crates/oxc_linter/src/snapshots/no_alias_methods.snap +++ b/crates/oxc_linter/src/snapshots/no_alias_methods.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +assertion_line: 216 --- ⚠ eslint-plugin-jest(no-alias-methods): Unexpected alias "toBeCalled" ╭─[no_alias_methods.tsx:1:11] @@ -105,3 +106,24 @@ source: crates/oxc_linter/src/tester.rs · ────────────── ╰──── help: Replace "toThrowError" with its canonical name of "toThrow" + + ⚠ eslint-plugin-jest(no-alias-methods): Unexpected alias "toBeCalled" + ╭─[no_alias_methods.tsx:1:11] + 1 │ expect(a).toBeCalled() + · ────────── + ╰──── + help: Replace "toBeCalled" with its canonical name of "toHaveBeenCalled" + + ⚠ eslint-plugin-jest(no-alias-methods): Unexpected alias "toBeCalledTimes" + ╭─[no_alias_methods.tsx:1:11] + 1 │ expect(a).toBeCalledTimes() + · ─────────────── + ╰──── + help: Replace "toBeCalledTimes" with its canonical name of "toHaveBeenCalledTimes" + + ⚠ eslint-plugin-jest(no-alias-methods): Unexpected alias "toThrowError" + ╭─[no_alias_methods.tsx:1:15] + 1 │ expect(a).not["toThrowError"]() + · ────────────── + ╰──── + help: Replace "toThrowError" with its canonical name of "toThrow" diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index 331c67ad05ecc..431c742d3287e 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -18,6 +18,7 @@ pub use self::{ pub fn is_jest_rule_adapted_to_vitest(rule_name: &str) -> bool { let jest_rules: &[&str] = &[ "consistent-test-it", + "no-alias-methods", "no-disabled-tests", "no-focused-tests", "no-test-prefixes", From 9df7b5675fda33745176b753903cde9e44a46b71 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 17 Jul 2024 22:31:19 +0200 Subject: [PATCH 6/6] feat(jsx-a11y/no-autofocus): implement fixer support (#4171) Let oxlint automatically remove the autoFocus attribute when `--fix` is passed. --- .../src/rules/jsx_a11y/no_autofocus.rs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs index 04babdff906f8..07f4f988cec11 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs @@ -96,14 +96,18 @@ impl Rule for NoAutofocus { if self.ignore_non_dom { if HTML_TAG.contains(&element_type) { if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus { - ctx.diagnostic(no_autofocus_diagnostic(attr.span)); + ctx.diagnostic_with_fix(no_autofocus_diagnostic(attr.span), |fixer| { + fixer.delete(&attr.span) + }); } } return; } if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus { - ctx.diagnostic(no_autofocus_diagnostic(attr.span)); + ctx.diagnostic_with_fix(no_autofocus_diagnostic(attr.span), |fixer| { + fixer.delete(&attr.span) + }); } } } @@ -154,5 +158,15 @@ fn test() { ("