feat(linter/tree-shaking): check this in different environment (#2901)

This commit is contained in:
Wang Wenzhe 2024-04-06 13:58:47 +08:00 committed by GitHub
parent acb6eb2573
commit 59869d0a96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 30 deletions

View file

@ -26,7 +26,7 @@ pub struct NodeListenerOptions<'a, 'b> {
checked_mutated_nodes: RefCell<FxHashSet<SymbolId>>, checked_mutated_nodes: RefCell<FxHashSet<SymbolId>>,
ctx: &'b LintContext<'a>, ctx: &'b LintContext<'a>,
has_valid_this: Cell<bool>, has_valid_this: Cell<bool>,
call_with_new: Cell<bool>, called_with_new: Cell<bool>,
} }
impl<'a, 'b> NodeListenerOptions<'a, 'b> { impl<'a, 'b> NodeListenerOptions<'a, 'b> {
@ -41,7 +41,7 @@ impl<'a, 'b> NodeListenerOptions<'a, 'b> {
checked_mutated_nodes: RefCell::new(FxHashSet::default()), checked_mutated_nodes: RefCell::new(FxHashSet::default()),
ctx, ctx,
has_valid_this: Cell::new(false), has_valid_this: Cell::new(false),
call_with_new: Cell::new(false), called_with_new: Cell::new(false),
} }
} }
} }
@ -115,6 +115,12 @@ impl<'a> ListenerMap for AstNode<'a> {
.ctx .ctx
.diagnostic(NoSideEffectsDiagnostic::CallParameter(Span::new(start, end))); .diagnostic(NoSideEffectsDiagnostic::CallParameter(Span::new(start, end)));
} }
AstKind::Function(function) => {
let old_val = options.has_valid_this.get();
options.has_valid_this.set(options.called_with_new.get());
function.report_effects_when_called(options);
options.has_valid_this.set(old_val);
}
_ => {} _ => {}
} }
} }
@ -246,7 +252,10 @@ impl<'a> ListenerMap for Expression<'a> {
expr.report_effects_when_called(options); expr.report_effects_when_called(options);
} }
Self::FunctionExpression(expr) => { Self::FunctionExpression(expr) => {
let old_val = options.has_valid_this.get();
options.has_valid_this.set(options.called_with_new.get());
expr.report_effects_when_called(options); expr.report_effects_when_called(options);
options.has_valid_this.set(old_val);
} }
Self::ArrowFunctionExpression(expr) => { Self::ArrowFunctionExpression(expr) => {
expr.report_effects_when_called(options); expr.report_effects_when_called(options);
@ -290,10 +299,10 @@ impl<'a> ListenerMap for NewExpression<'a> {
return; return;
} }
self.arguments.iter().for_each(|arg| arg.report_effects(options)); self.arguments.iter().for_each(|arg| arg.report_effects(options));
let old_val = options.call_with_new.get(); let old_val = options.called_with_new.get();
options.call_with_new.set(true); options.called_with_new.set(true);
self.callee.report_effects_when_called(options); self.callee.report_effects_when_called(options);
options.call_with_new.set(old_val); options.called_with_new.set(old_val);
} }
} }
@ -318,22 +327,17 @@ impl<'a> ListenerMap for ParenthesizedExpression<'a> {
impl<'a> ListenerMap for ArrowFunctionExpression<'a> { impl<'a> ListenerMap for ArrowFunctionExpression<'a> {
fn report_effects_when_called(&self, options: &NodeListenerOptions) { fn report_effects_when_called(&self, options: &NodeListenerOptions) {
self.params.items.iter().for_each(|param| param.report_effects(options)); self.params.items.iter().for_each(|param| param.report_effects(options));
let old_val = options.has_valid_this.get();
options.has_valid_this.set(options.call_with_new.get());
self.body.statements.iter().for_each(|stmt| stmt.report_effects(options)); self.body.statements.iter().for_each(|stmt| stmt.report_effects(options));
options.has_valid_this.set(old_val);
} }
} }
impl<'a> ListenerMap for Function<'a> { impl<'a> ListenerMap for Function<'a> {
fn report_effects_when_called(&self, options: &NodeListenerOptions) { fn report_effects_when_called(&self, options: &NodeListenerOptions) {
self.params.items.iter().for_each(|param| param.report_effects(options)); self.params.items.iter().for_each(|param| param.report_effects(options));
let old_val = options.has_valid_this.get();
options.has_valid_this.set(options.call_with_new.get());
if let Some(body) = &self.body { if let Some(body) = &self.body {
body.statements.iter().for_each(|stmt| stmt.report_effects(options)); body.statements.iter().for_each(|stmt| stmt.report_effects(options));
} }
options.has_valid_this.set(old_val);
} }
} }
@ -347,7 +351,10 @@ impl<'a> ListenerMap for CallExpression<'a> {
fn report_effects(&self, options: &NodeListenerOptions) { fn report_effects(&self, options: &NodeListenerOptions) {
self.arguments.iter().for_each(|arg| arg.report_effects(options)); self.arguments.iter().for_each(|arg| arg.report_effects(options));
if defined_custom_report_effects_when_called(&self.callee) { if defined_custom_report_effects_when_called(&self.callee) {
let old_value = options.called_with_new.get();
options.called_with_new.set(false);
self.callee.report_effects_when_called(options); self.callee.report_effects_when_called(options);
options.called_with_new.set(old_value);
} else { } else {
// TODO: Not work now // TODO: Not work now
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.callee.span())); options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.callee.span()));

View file

@ -328,12 +328,12 @@ fn test() {
// "const x = `Literal ${ext}`", // "const x = `Literal ${ext}`",
// r#"const x = ()=>"a"; const y = `Literal ${x()}`"#, // r#"const x = ()=>"a"; const y = `Literal ${x()}`"#,
// // ThisExpression // // ThisExpression
// "const y = this.x", "const y = this.x",
// // ThisExpression when mutated // // ThisExpression when mutated
// "const y = new (function (){this.x = 1})()", "const y = new (function (){this.x = 1})()",
// "const y = new (function (){{this.x = 1}})()", "const y = new (function (){{this.x = 1}})()",
// "const y = new (function (){(()=>{this.x = 1})()})()", "const y = new (function (){(()=>{this.x = 1})()})()",
// "function x(){this.y = 1}; const y = new x()", "function x(){this.y = 1}; const y = new x()",
// // TryStatement // // TryStatement
// "try {} catch (error) {}", // "try {} catch (error) {}",
// "try {} finally {}", // "try {} finally {}",
@ -365,13 +365,13 @@ fn test() {
]; ];
let fail = vec![ let fail = vec![
// // ArrayExpression // ArrayExpression
"const x = [ext()]", "const x = [ext()]",
"const x = [,,ext(),]", "const x = [,,ext(),]",
// // ArrayPattern // ArrayPattern
"const [x = ext()] = []", "const [x = ext()] = []",
"const [,x = ext(),] = []", "const [,x = ext(),] = []",
// // ArrowFunctionExpression when called // ArrowFunctionExpression when called
"(()=>{ext()})()", "(()=>{ext()})()",
"(({a = ext()})=>{})()", "(({a = ext()})=>{})()",
"(a=>{a()})(ext)", "(a=>{a()})(ext)",
@ -381,7 +381,7 @@ fn test() {
"(a=>{const b = a;b.x = 1})(ext)", "(a=>{const b = a;b.x = 1})(ext)",
"((...a)=>{a.x = 1})(ext)", "((...a)=>{a.x = 1})(ext)",
"(({a})=>{a.x = 1})(ext)", "(({a})=>{a.x = 1})(ext)",
// // AssignmentExpression // AssignmentExpression
"ext = 1", "ext = 1",
"ext += 1", "ext += 1",
"ext.x = 1", "ext.x = 1",
@ -399,12 +399,12 @@ fn test() {
// "{ext()}", // "{ext()}",
// "var x=()=>{};{var x=ext}x()", // "var x=()=>{};{var x=ext}x()",
// "var x=ext;{x(); var x=()=>{}}", // "var x=ext;{x(); var x=()=>{}}",
// // CallExpression // CallExpression
"(()=>{})(ext(), 1)", "(()=>{})(ext(), 1)",
"(()=>{})(1, ext())", "(()=>{})(1, ext())",
// // CallExpression when called // CallExpression when called
"const x = ()=>ext; const y = x(); y()", "const x = ()=>ext; const y = x(); y()",
// // CallExpression when mutated // CallExpression when mutated
"const x = ()=>ext; const y = x(); y.z = 1", "const x = ()=>ext; const y = x(); y.z = 1",
// // CatchClause // // CatchClause
// "try {} catch (error) {ext()}", // "try {} catch (error) {ext()}",
@ -580,7 +580,7 @@ fn test() {
// "const x = {y: ext};delete x.y.z", // "const x = {y: ext};delete x.y.z",
// // MethodDefinition // // MethodDefinition
// "class x {static [ext()](){}}", // "class x {static [ext()](){}}",
// // NewExpression // NewExpression
"const x = new ext()", "const x = new ext()",
"new ext()", "new ext()",
// // ObjectExpression // // ObjectExpression
@ -612,12 +612,12 @@ fn test() {
// "const x = ()=>{}; const y = x`${ext()}`", // "const x = ()=>{}; const y = x`${ext()}`",
// // TemplateLiteral // // TemplateLiteral
// "const x = `Literal ${ext()}`", // "const x = `Literal ${ext()}`",
// // ThisExpression when mutated // ThisExpression when mutated
// "this.x = 1", "this.x = 1",
// "(()=>{this.x = 1})()", "(()=>{this.x = 1})()",
// "(function(){this.x = 1}())", "(function(){this.x = 1}())",
// "const y = new (function (){(function(){this.x = 1}())})()", "const y = new (function (){(function(){this.x = 1}())})()",
// "function x(){this.y = 1}; x()", "function x(){this.y = 1}; x()",
// // ThrowStatement // // ThrowStatement
// r#"throw new Error("Hello Error")"#, // r#"throw new Error("Hello Error")"#,
// // TryStatement // // TryStatement

View file

@ -145,3 +145,33 @@ expression: no_side_effects_in_initialization
1 │ new ext() 1 │ new ext()
· ─── · ───
╰──── ╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating unknown this value
╭─[no_side_effects_in_initialization.tsx:1:1]
1 │ this.x = 1
· ────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating unknown this value
╭─[no_side_effects_in_initialization.tsx:1:7]
1 │ (()=>{this.x = 1})()
· ────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating unknown this value
╭─[no_side_effects_in_initialization.tsx:1:13]
1 │ (function(){this.x = 1}())
· ────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating unknown this value
╭─[no_side_effects_in_initialization.tsx:1:40]
1 │ const y = new (function (){(function(){this.x = 1}())})()
· ────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating unknown this value
╭─[no_side_effects_in_initialization.tsx:1:14]
1 │ function x(){this.y = 1}; x()
· ────
╰────