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>>,
ctx: &'b LintContext<'a>,
has_valid_this: Cell<bool>,
call_with_new: Cell<bool>,
called_with_new: Cell<bool>,
}
impl<'a, 'b> NodeListenerOptions<'a, 'b> {
@ -41,7 +41,7 @@ impl<'a, 'b> NodeListenerOptions<'a, 'b> {
checked_mutated_nodes: RefCell::new(FxHashSet::default()),
ctx,
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
.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);
}
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);
options.has_valid_this.set(old_val);
}
Self::ArrowFunctionExpression(expr) => {
expr.report_effects_when_called(options);
@ -290,10 +299,10 @@ impl<'a> ListenerMap for NewExpression<'a> {
return;
}
self.arguments.iter().for_each(|arg| arg.report_effects(options));
let old_val = options.call_with_new.get();
options.call_with_new.set(true);
let old_val = options.called_with_new.get();
options.called_with_new.set(true);
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> {
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
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));
options.has_valid_this.set(old_val);
}
}
impl<'a> ListenerMap for Function<'a> {
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
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 {
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) {
self.arguments.iter().for_each(|arg| arg.report_effects(options));
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);
options.called_with_new.set(old_value);
} else {
// TODO: Not work now
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.callee.span()));

View file

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

View file

@ -145,3 +145,33 @@ expression: no_side_effects_in_initialization
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()
· ────
╰────