diff --git a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs index 4739173f6..57fcd85e9 100644 --- a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs +++ b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs @@ -251,11 +251,21 @@ impl<'a> ListenerMap for AstNode<'a> { AstKind::Class(class) => { class.report_effects_when_called(options); } + AstKind::ImportDefaultSpecifier(specifier) => { + if !has_comment_about_side_effect_check(specifier.span, options.ctx) { + options.ctx.diagnostic(NoSideEffectsDiagnostic::CallImport(specifier.span)); + } + } + AstKind::ImportSpecifier(specifier) => { + let span = specifier.local.span; + if !has_comment_about_side_effect_check(span, options.ctx) { + options.ctx.diagnostic(NoSideEffectsDiagnostic::CallImport(span)); + } + } _ => {} } } fn report_effects_when_mutated(&self, options: &NodeListenerOptions) { - #[allow(clippy::single_match)] match self.kind() { AstKind::VariableDeclarator(decl) => { if let Some(init) = &decl.init { @@ -272,6 +282,15 @@ impl<'a> ListenerMap for AstNode<'a> { .ctx .diagnostic(NoSideEffectsDiagnostic::MutateParameter(Span::new(start, end))); } + AstKind::ImportDefaultSpecifier(specifier) => { + options.ctx.diagnostic(NoSideEffectsDiagnostic::MutateImport(specifier.span)); + } + AstKind::ImportSpecifier(specifier) => { + options.ctx.diagnostic(NoSideEffectsDiagnostic::MutateImport(specifier.local.span)); + } + AstKind::ImportNamespaceSpecifier(specifier) => { + options.ctx.diagnostic(NoSideEffectsDiagnostic::MutateImport(specifier.local.span)); + } _ => {} } } @@ -754,6 +773,11 @@ impl<'a> ListenerMap for IdentifierReference<'a> { } fn report_effects_when_called(&self, options: &NodeListenerOptions) { + // TODO: change to `isPureFunction` + if has_pure_notation(self.span, options.ctx) { + return; + } + let ctx = options.ctx; if let Some(symbol_id) = get_symbol_id_of_variable(self, ctx) { let symbol_table = ctx.semantic().symbols(); diff --git a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs index fb930c2a7..3651e9e67 100644 --- a/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs +++ b/crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/mod.rs @@ -38,6 +38,10 @@ enum NoSideEffectsDiagnostic { #[diagnostic(severity(warning))] MutateOfThis(#[label] Span), + #[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating imported variable")] + #[diagnostic(severity(warning))] + MutateImport(#[label] Span), + #[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling")] #[diagnostic(severity(warning))] Call(#[label] Span), @@ -54,6 +58,10 @@ enum NoSideEffectsDiagnostic { #[diagnostic(severity(warning))] CallParameter(#[label] Span), + #[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling imported function")] + #[diagnostic(severity(warning))] + CallImport(#[label] Span), + #[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Debugger statements are side-effects")] #[diagnostic(severity(warning))] Debugger(#[label] Span), @@ -234,23 +242,24 @@ fn test() { "const x = ext, y = ()=>{const x = ()=>{}; x()}; y()", // Identifier when mutated "const x = {}; x.y = ext", - // // IfStatement + // IfStatement "let y;if (ext > 0) {y = 1} else {y = 2}", "if (false) {ext()}", "if (true) {} else {ext()}", - // // ImportDeclaration - // r#"import "import""#, - // r#"import x from "import-default""#, - // r#"import {x} from "import""#, - // r#"import {x as y} from "import""#, - // r#"import * as x from "import""#, - // r#"import /* tree-shaking no-side-effects-when-called */ x from "import-default-no-effects"; x()"#, - // r#"import /* test */ /*tree-shaking no-side-effects-when-called */ x from "import-default-no-effects"; x()"#, + // ImportDeclaration + r#"import "import""#, + r#"import x from "import-default""#, + r#"import {x} from "import""#, + r#"import {x as y} from "import""#, + r#"import * as x from "import""#, + r#"import /* tree-shaking no-side-effects-when-called */ x from "import-default-no-effects"; x()"#, + r#"import /* test */ /*tree-shaking no-side-effects-when-called */ x from "import-default-no-effects"; x()"#, + // TODO: Current only support the comment next to code. // r#"import /* tree-shaking no-side-effects-when-called*/ /* test */ x from "import-default-no-effects"; x()"#, - // r#"import {/* tree-shaking no-side-effects-when-called */ x} from "import-no-effects"; x()"#, - // r#"import {x as /* tree-shaking no-side-effects-when-called */ y} from "import-no-effects"; y()"#, - // r#"import {x} from "import"; /*@__PURE__*/ x()"#, - // r#"import {x} from "import"; /* @__PURE__ */ x()"#, + r#"import {/* tree-shaking no-side-effects-when-called */ x} from "import-no-effects"; x()"#, + r#"import {x as /* tree-shaking no-side-effects-when-called */ y} from "import-no-effects"; y()"#, + r#"import {x} from "import"; /*@__PURE__*/ x()"#, + r#"import {x} from "import"; /* @__PURE__ */ x()"#, // // JSXAttribute // r#"class X {}; const x = "#, // "class X {}; const x = ", @@ -532,15 +541,15 @@ fn test() { "if (1>0){ext()}", "if (1<0){} else {ext()}", "if (ext>0){ext()} else {ext()}", - // // ImportDeclaration - // r#"import x from "import-default"; x()"#, - // r#"import x from "import-default"; x.z = 1"#, - // r#"import {x} from "import"; x()"#, - // r#"import {x} from "import"; x.z = 1"#, - // r#"import {x as y} from "import"; y()"#, - // r#"import {x as y} from "import"; y.a = 1"#, - // r#"import * as y from "import"; y.x()"#, - // r#"import * as y from "import"; y.x = 1"#, + // ImportDeclaration + r#"import x from "import-default"; x()"#, + r#"import x from "import-default"; x.z = 1"#, + r#"import {x} from "import"; x()"#, + r#"import {x} from "import"; x.z = 1"#, + r#"import {x as y} from "import"; y()"#, + r#"import {x as y} from "import"; y.a = 1"#, + r#"import * as y from "import"; y.x()"#, + r#"import * as y from "import"; y.x = 1"#, // // JSXAttribute // "class X {}; const x = ", // "class X {}; class Y {constructor(){ext()}}; const x = />", diff --git a/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap b/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap index 84f006f27..f55643a0f 100644 --- a/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap +++ b/crates/oxc_linter/src/snapshots/no_side_effects_in_initialization.snap @@ -764,6 +764,54 @@ expression: no_side_effects_in_initialization · ─── ╰──── + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling imported function + ╭─[no_side_effects_in_initialization.tsx:1:8] + 1 │ import x from "import-default"; x() + · ─ + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating imported variable + ╭─[no_side_effects_in_initialization.tsx:1:8] + 1 │ import x from "import-default"; x.z = 1 + · ─ + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling imported function + ╭─[no_side_effects_in_initialization.tsx:1:9] + 1 │ import {x} from "import"; x() + · ─ + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating imported variable + ╭─[no_side_effects_in_initialization.tsx:1:9] + 1 │ import {x} from "import"; x.z = 1 + · ─ + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling imported function + ╭─[no_side_effects_in_initialization.tsx:1:14] + 1 │ import {x as y} from "import"; y() + · ─ + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating imported variable + ╭─[no_side_effects_in_initialization.tsx:1:14] + 1 │ import {x as y} from "import"; y.a = 1 + · ─ + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling + ╭─[no_side_effects_in_initialization.tsx:1:30] + 1 │ import * as y from "import"; y.x() + · ─── + ╰──── + + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating imported variable + ╭─[no_side_effects_in_initialization.tsx:1:13] + 1 │ import * as y from "import"; y.x = 1 + · ─ + ╰──── + ⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext` ╭─[no_side_effects_in_initialization.tsx:1:15] 1 │ const x = new ext()