mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(linter/tree-shaking): support While/Switch/Yield Statement (#3155)
All test cases passed now. There are something remain. I'll change the category when it's ready. - [ ] Port [`isPureFuncion`](96f0d1c825/src/utils/helpers.ts (L53-L67)), [pure-function](198432ecc9/src/utils/pure-functions.ts) - [ ] Support [options](463fa1f0be/src/rules/no-side-effects-in-initialization.ts (L1130-L1138)) - [ ] Clean TODO - [ ] Add more support of operator computation. [The original version](463fa1f0be/src/rules/no-side-effects-in-initialization.ts (L139-L210)) is straightforward benefit of JS. We only [support Number](f91a063616/crates/oxc_linter/src/utils/tree_shaking.rs (L194)) now.
This commit is contained in:
parent
8290421e72
commit
32df6d779c
3 changed files with 256 additions and 64 deletions
|
|
@ -10,8 +10,8 @@ use oxc_ast::{
|
|||
JSXExpressionContainer, JSXFragment, JSXIdentifier, JSXOpeningElement, LogicalExpression,
|
||||
MemberExpression, ModuleExportName, NewExpression, ObjectExpression, ObjectPropertyKind,
|
||||
ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey, SequenceExpression,
|
||||
SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression, UnaryExpression,
|
||||
VariableDeclarator,
|
||||
SimpleAssignmentTarget, Statement, StaticMemberExpression, SwitchCase, ThisExpression,
|
||||
UnaryExpression, VariableDeclarator,
|
||||
},
|
||||
AstKind,
|
||||
};
|
||||
|
|
@ -183,6 +183,23 @@ impl<'a> ListenerMap for Statement<'a> {
|
|||
Self::LabeledStatement(stmt) => {
|
||||
stmt.body.report_effects(options);
|
||||
}
|
||||
Self::WhileStatement(stmt) => {
|
||||
if stmt
|
||||
.test
|
||||
.get_value_and_report_effects(options)
|
||||
.get_falsy_value()
|
||||
.is_some_and(|is_falsy| is_falsy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
stmt.body.report_effects(options);
|
||||
}
|
||||
Self::SwitchStatement(stmt) => {
|
||||
stmt.discriminant.report_effects(options);
|
||||
stmt.cases.iter().for_each(|case| {
|
||||
case.report_effects(options);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -537,6 +554,20 @@ impl<'a> ListenerMap for Expression<'a> {
|
|||
Self::SequenceExpression(expr) => {
|
||||
expr.get_value_and_report_effects(options);
|
||||
}
|
||||
Self::YieldExpression(expr) => {
|
||||
expr.argument.iter().for_each(|arg| arg.report_effects(options));
|
||||
}
|
||||
Self::TaggedTemplateExpression(expr) => {
|
||||
expr.tag.report_effects_when_called(options);
|
||||
expr.quasi.expressions.iter().for_each(|expr| {
|
||||
expr.report_effects(options);
|
||||
});
|
||||
}
|
||||
Self::TemplateLiteral(expr) => {
|
||||
expr.expressions.iter().for_each(|expr| {
|
||||
expr.report_effects(options);
|
||||
});
|
||||
}
|
||||
Self::ArrowFunctionExpression(_)
|
||||
| Self::FunctionExpression(_)
|
||||
| Self::Identifier(_)
|
||||
|
|
@ -641,6 +672,17 @@ fn defined_custom_report_effects_when_called(expr: &Expression) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
impl<'a> ListenerMap for SwitchCase<'a> {
|
||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||
if let Some(test) = &self.test {
|
||||
test.report_effects(options);
|
||||
}
|
||||
self.consequent.iter().for_each(|stmt| {
|
||||
stmt.report_effects(options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ListenerMap for SequenceExpression<'a> {
|
||||
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
|
||||
let mut val = Value::Unknown;
|
||||
|
|
|
|||
|
|
@ -339,22 +339,22 @@ fn test() {
|
|||
// SequenceExpression
|
||||
"let x = 1; x++, x++",
|
||||
"if (ext, false) ext()",
|
||||
// // SwitchCase
|
||||
// "switch(ext){case ext:const x = 1;break;default:}",
|
||||
// // SwitchStatement
|
||||
// "switch(ext){}",
|
||||
// "const x = ()=>{}; switch(ext){case 1:const x = ext}; x()",
|
||||
// "const x = ext; switch(ext){case 1:const x = ()=>{}; x()}",
|
||||
// // TaggedTemplateExpression
|
||||
// "const x = ()=>{}; const y = x``",
|
||||
// // TemplateLiteral
|
||||
// "const x = ``",
|
||||
// "const x = `Literal`",
|
||||
// "const x = `Literal ${ext}`",
|
||||
// r#"const x = ()=>"a"; const y = `Literal ${x()}`"#,
|
||||
// // ThisExpression
|
||||
// SwitchCase
|
||||
"switch(ext){case ext:const x = 1;break;default:}",
|
||||
// SwitchStatement
|
||||
"switch(ext){}",
|
||||
"const x = ()=>{}; switch(ext){case 1:const x = ext}; x()",
|
||||
"const x = ext; switch(ext){case 1:const x = ()=>{}; x()}",
|
||||
// TaggedTemplateExpression
|
||||
"const x = ()=>{}; const y = x``",
|
||||
// TemplateLiteral
|
||||
"const x = ``",
|
||||
"const x = `Literal`",
|
||||
"const x = `Literal ${ext}`",
|
||||
r#"const x = ()=>"a"; const y = `Literal ${x()}`"#,
|
||||
// ThisExpression
|
||||
"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})()})()",
|
||||
|
|
@ -370,23 +370,23 @@ fn test() {
|
|||
// UpdateExpression
|
||||
"let x=1;x++",
|
||||
"const x = {};x.y++",
|
||||
// // VariableDeclaration
|
||||
// "const x = 1",
|
||||
// // VariableDeclarator
|
||||
// "var x, y",
|
||||
// "var x = 1, y = 2",
|
||||
// "const x = 1, y = 2",
|
||||
// "let x = 1, y = 2",
|
||||
// "const {x} = {}",
|
||||
// // WhileStatement
|
||||
// "while(true){}",
|
||||
// "while(ext > 0){}",
|
||||
// "const x = ()=>{}; while(true)x()",
|
||||
// // YieldExpression
|
||||
// "function* x(){const a = yield}; x()",
|
||||
// "function* x(){yield ext}; x()",
|
||||
// // Supports TypeScript nodes
|
||||
// "interface Blub {}",
|
||||
// VariableDeclaration
|
||||
"const x = 1",
|
||||
// VariableDeclarator
|
||||
"var x, y",
|
||||
"var x = 1, y = 2",
|
||||
"const x = 1, y = 2",
|
||||
"let x = 1, y = 2",
|
||||
"const {x} = {}",
|
||||
// WhileStatement
|
||||
"while(true){}",
|
||||
"while(ext > 0){}",
|
||||
"const x = ()=>{}; while(true)x()",
|
||||
// YieldExpression
|
||||
"function* x(){const a = yield}; x()",
|
||||
"function* x(){yield ext}; x()",
|
||||
// Supports TypeScript nodes
|
||||
"interface Blub {}",
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
|
|
@ -622,22 +622,22 @@ fn test() {
|
|||
"1, ext()",
|
||||
"if (1, true) ext()",
|
||||
"if (1, ext) ext()",
|
||||
// // Super when called
|
||||
// "class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()",
|
||||
// "class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()",
|
||||
// "class y{}; class x extends y{constructor(){super()}}; const z = new x()",
|
||||
// // SwitchCase
|
||||
// "switch(ext){case ext():}",
|
||||
// "switch(ext){case 1:ext()}",
|
||||
// // SwitchStatement
|
||||
// "switch(ext()){}",
|
||||
// Super when called
|
||||
"class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()",
|
||||
"class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()",
|
||||
"class y{}; class x extends y{constructor(){super()}}; const z = new x()",
|
||||
// SwitchCase
|
||||
"switch(ext){case ext():}",
|
||||
"switch(ext){case 1:ext()}",
|
||||
// SwitchStatement
|
||||
"switch(ext()){}",
|
||||
// "var x=()=>{}; switch(ext){case 1:var x=ext}; x()",
|
||||
// // TaggedTemplateExpression
|
||||
// "const x = ext``",
|
||||
// "ext``",
|
||||
// "const x = ()=>{}; const y = x`${ext()}`",
|
||||
// // TemplateLiteral
|
||||
// "const x = `Literal ${ext()}`",
|
||||
// TaggedTemplateExpression
|
||||
"const x = ext``",
|
||||
"ext``",
|
||||
"const x = ()=>{}; const y = x`${ext()}`",
|
||||
// TemplateLiteral
|
||||
"const x = `Literal ${ext()}`",
|
||||
// ThisExpression when mutated
|
||||
"this.x = 1",
|
||||
"(()=>{this.x = 1})()",
|
||||
|
|
@ -657,21 +657,21 @@ fn test() {
|
|||
// UpdateExpression
|
||||
"ext++",
|
||||
"const x = {};x[ext()]++",
|
||||
// // VariableDeclaration
|
||||
// "const x = ext()",
|
||||
// // VariableDeclarator
|
||||
// "var x = ext(),y = ext()",
|
||||
// "const x = ext(),y = ext()",
|
||||
// "let x = ext(),y = ext()",
|
||||
// "const {x = ext()} = {}",
|
||||
// // WhileStatement
|
||||
// "while(ext()){}",
|
||||
// "while(true)ext()",
|
||||
// "while(true){ext()}",
|
||||
// // YieldExpression
|
||||
// "function* x(){yield ext()}; x()",
|
||||
// // YieldExpression when called
|
||||
// "function* x(){yield ext()}; x()"
|
||||
// VariableDeclaration
|
||||
"const x = ext()",
|
||||
// VariableDeclarator
|
||||
"var x = ext(),y = ext()",
|
||||
"const x = ext(),y = ext()",
|
||||
"let x = ext(),y = ext()",
|
||||
"const {x = ext()} = {}",
|
||||
// WhileStatement
|
||||
"while(ext()){}",
|
||||
"while(true)ext()",
|
||||
"while(true){ext()}",
|
||||
// YieldExpression
|
||||
"function* x(){yield ext()}; x()",
|
||||
// YieldExpression when called
|
||||
"function* x(){yield ext()}; x()"
|
||||
];
|
||||
|
||||
Tester::new(NoSideEffectsInInitialization::NAME, pass, fail).test_and_snapshot();
|
||||
|
|
|
|||
|
|
@ -1070,6 +1070,78 @@ expression: no_side_effects_in_initialization
|
|||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:24]
|
||||
1 │ class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
|
||||
╭─[no_side_effects_in_initialization.tsx:1:66]
|
||||
1 │ class y {constructor(){ext()}}; class x extends y {constructor(){super()}}; const z = new x()
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
|
||||
╭─[no_side_effects_in_initialization.tsx:1:44]
|
||||
1 │ class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function
|
||||
╭─[no_side_effects_in_initialization.tsx:1:53]
|
||||
1 │ class y{}; class x extends y{constructor(){super(); super.test()}}; const z = new x()
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
|
||||
╭─[no_side_effects_in_initialization.tsx:1:44]
|
||||
1 │ class y{}; class x extends y{constructor(){super()}}; const z = new x()
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ 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:18]
|
||||
1 │ switch(ext){case ext():}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:20]
|
||||
1 │ switch(ext){case 1:ext()}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:8]
|
||||
1 │ switch(ext()){}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:11]
|
||||
1 │ const x = ext``
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:1]
|
||||
1 │ ext``
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:33]
|
||||
1 │ const x = ()=>{}; const y = x`${ext()}`
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:22]
|
||||
1 │ const x = `Literal ${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
|
||||
|
|
@ -1153,3 +1225,81 @@ expression: no_side_effects_in_initialization
|
|||
1 │ const x = {};x[ext()]++
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:11]
|
||||
1 │ const x = ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:9]
|
||||
1 │ var x = ext(),y = ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:19]
|
||||
1 │ var x = ext(),y = ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:11]
|
||||
1 │ const x = ext(),y = ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:21]
|
||||
1 │ const x = ext(),y = ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:9]
|
||||
1 │ let x = ext(),y = ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:19]
|
||||
1 │ let x = ext(),y = ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:12]
|
||||
1 │ const {x = ext()} = {}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:7]
|
||||
1 │ while(ext()){}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:12]
|
||||
1 │ while(true)ext()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:13]
|
||||
1 │ while(true){ext()}
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:21]
|
||||
1 │ function* x(){yield ext()}; x()
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ 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:21]
|
||||
1 │ function* x(){yield ext()}; x()
|
||||
· ───
|
||||
╰────
|
||||
|
|
|
|||
Loading…
Reference in a new issue