feat(linter/tree-shaking): support import statement (#3138)

This commit is contained in:
Wang Wenzhe 2024-04-30 11:23:23 +08:00 committed by GitHub
parent d76507699d
commit 16a31e95b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 104 additions and 23 deletions

View file

@ -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();

View file

@ -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 = <X test="3"/>"#,
// "class X {}; const x = <X test={3}/>",
@ -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 = <X test={ext()}/>",
// "class X {}; class Y {constructor(){ext()}}; const x = <X test=<Y/>/>",

View file

@ -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()