mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter/tree_shaking): support LogicExpression and MemberExpression (#3148)
MemberExpression's message is not accurate, I will update later.
This commit is contained in:
parent
80cf0b2b2e
commit
8cdd5b0fd8
4 changed files with 373 additions and 62 deletions
|
|
@ -9,23 +9,24 @@ use oxc_ast::{
|
||||||
ConditionalExpression, Declaration, ExportSpecifier, Expression, ForStatementInit,
|
ConditionalExpression, Declaration, ExportSpecifier, Expression, ForStatementInit,
|
||||||
FormalParameter, Function, IdentifierReference, JSXAttribute, JSXAttributeItem,
|
FormalParameter, Function, IdentifierReference, JSXAttribute, JSXAttributeItem,
|
||||||
JSXAttributeValue, JSXChild, JSXElement, JSXElementName, JSXExpression,
|
JSXAttributeValue, JSXChild, JSXElement, JSXElementName, JSXExpression,
|
||||||
JSXExpressionContainer, JSXFragment, JSXIdentifier, JSXOpeningElement, MemberExpression,
|
JSXExpressionContainer, JSXFragment, JSXIdentifier, JSXOpeningElement, LogicalExpression,
|
||||||
ModuleExportName, NewExpression, ObjectExpression, ObjectPropertyKind,
|
MemberExpression, ModuleExportName, NewExpression, ObjectExpression, ObjectPropertyKind,
|
||||||
ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey,
|
ParenthesizedExpression, PrivateFieldExpression, Program, PropertyKey,
|
||||||
SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression,
|
SimpleAssignmentTarget, Statement, StaticMemberExpression, ThisExpression, UnaryExpression,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
},
|
},
|
||||||
AstKind,
|
AstKind,
|
||||||
};
|
};
|
||||||
use oxc_semantic::{AstNode, SymbolId};
|
use oxc_semantic::{AstNode, SymbolId};
|
||||||
use oxc_span::{GetSpan, Span};
|
use oxc_span::{GetSpan, Span};
|
||||||
|
use oxc_syntax::operator::{LogicalOperator, UnaryOperator};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast_util::{get_declaration_of_variable, get_symbol_id_of_variable},
|
ast_util::{get_declaration_of_variable, get_symbol_id_of_variable},
|
||||||
utils::{
|
utils::{
|
||||||
calculate_binary_operation, get_write_expr, has_comment_about_side_effect_check,
|
calculate_binary_operation, calculate_logical_operation, get_write_expr,
|
||||||
has_pure_notation, no_effects, Value,
|
has_comment_about_side_effect_check, has_pure_notation, no_effects, Value,
|
||||||
},
|
},
|
||||||
LintContext,
|
LintContext,
|
||||||
};
|
};
|
||||||
|
|
@ -177,6 +178,9 @@ impl<'a> ListenerMap for Statement<'a> {
|
||||||
stmt.right.report_effects(options);
|
stmt.right.report_effects(options);
|
||||||
stmt.body.report_effects(options);
|
stmt.body.report_effects(options);
|
||||||
}
|
}
|
||||||
|
Self::LabeledStatement(stmt) => {
|
||||||
|
stmt.body.report_effects(options);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +269,12 @@ impl<'a> ListenerMap for AstNode<'a> {
|
||||||
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallImport(span));
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallImport(span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AstKind::ImportNamespaceSpecifier(specifier) => {
|
||||||
|
let span = specifier.local.span;
|
||||||
|
if !has_comment_about_side_effect_check(span, options.ctx) {
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallImport(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -503,6 +513,21 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
Self::ObjectExpression(expr) => {
|
Self::ObjectExpression(expr) => {
|
||||||
expr.report_effects(options);
|
expr.report_effects(options);
|
||||||
}
|
}
|
||||||
|
Self::LogicalExpression(expr) => {
|
||||||
|
expr.get_value_and_report_effects(options);
|
||||||
|
}
|
||||||
|
Self::StaticMemberExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::ComputedMemberExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::PrivateFieldExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::UnaryExpression(expr) => {
|
||||||
|
expr.get_value_and_report_effects(options);
|
||||||
|
}
|
||||||
Self::ArrowFunctionExpression(_)
|
Self::ArrowFunctionExpression(_)
|
||||||
| Self::FunctionExpression(_)
|
| Self::FunctionExpression(_)
|
||||||
| Self::Identifier(_)
|
| Self::Identifier(_)
|
||||||
|
|
@ -558,6 +583,15 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
expr.report_effects_when_called(options);
|
expr.report_effects_when_called(options);
|
||||||
}
|
}
|
||||||
Self::ConditionalExpression(expr) => expr.report_effects_when_called(options),
|
Self::ConditionalExpression(expr) => expr.report_effects_when_called(options),
|
||||||
|
Self::StaticMemberExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
Self::ComputedMemberExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
Self::PrivateFieldExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Default behavior
|
// Default behavior
|
||||||
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.span()));
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::Call(self.span()));
|
||||||
|
|
@ -572,6 +606,7 @@ impl<'a> ListenerMap for Expression<'a> {
|
||||||
| Self::TemplateLiteral(_) => Value::new(self),
|
| Self::TemplateLiteral(_) => Value::new(self),
|
||||||
Self::BinaryExpression(expr) => expr.get_value_and_report_effects(options),
|
Self::BinaryExpression(expr) => expr.get_value_and_report_effects(options),
|
||||||
Self::ConditionalExpression(expr) => expr.get_value_and_report_effects(options),
|
Self::ConditionalExpression(expr) => expr.get_value_and_report_effects(options),
|
||||||
|
Self::LogicalExpression(expr) => expr.get_value_and_report_effects(options),
|
||||||
_ => {
|
_ => {
|
||||||
self.report_effects(options);
|
self.report_effects(options);
|
||||||
Value::Unknown
|
Value::Unknown
|
||||||
|
|
@ -596,6 +631,48 @@ fn defined_custom_report_effects_when_called(expr: &Expression) -> bool {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> ListenerMap for UnaryExpression<'a> {
|
||||||
|
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
|
||||||
|
if self.operator == UnaryOperator::Delete {
|
||||||
|
match &self.argument {
|
||||||
|
Expression::StaticMemberExpression(expr) => {
|
||||||
|
expr.object.report_effects_when_mutated(options);
|
||||||
|
}
|
||||||
|
Expression::ComputedMemberExpression(expr) => {
|
||||||
|
expr.object.report_effects_when_mutated(options);
|
||||||
|
}
|
||||||
|
Expression::PrivateFieldExpression(expr) => {
|
||||||
|
expr.object.report_effects_when_mutated(options);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
Value::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ListenerMap for LogicalExpression<'a> {
|
||||||
|
fn get_value_and_report_effects(&self, options: &NodeListenerOptions) -> Value {
|
||||||
|
let left = self.left.get_value_and_report_effects(options);
|
||||||
|
// `false && foo`
|
||||||
|
if self.operator == LogicalOperator::And
|
||||||
|
&& left.get_falsy_value().is_some_and(|is_falsy| is_falsy)
|
||||||
|
{
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
// `true || foo`
|
||||||
|
if self.operator == LogicalOperator::Or
|
||||||
|
&& left.get_falsy_value().is_some_and(|is_falsy| !is_falsy)
|
||||||
|
{
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
let right = self.right.get_value_and_report_effects(options);
|
||||||
|
calculate_logical_operation(self.operator, left, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for ObjectExpression<'a> {
|
impl<'a> ListenerMap for ObjectExpression<'a> {
|
||||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
self.properties.iter().for_each(|property| match property {
|
self.properties.iter().for_each(|property| match property {
|
||||||
|
|
@ -769,6 +846,18 @@ impl<'a> ListenerMap for JSXExpression<'a> {
|
||||||
Self::ObjectExpression(expr) => {
|
Self::ObjectExpression(expr) => {
|
||||||
expr.report_effects(options);
|
expr.report_effects(options);
|
||||||
}
|
}
|
||||||
|
Self::StaticMemberExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::ComputedMemberExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::PrivateFieldExpression(expr) => {
|
||||||
|
expr.report_effects(options);
|
||||||
|
}
|
||||||
|
Self::UnaryExpression(expr) => {
|
||||||
|
expr.get_value_and_report_effects(options);
|
||||||
|
}
|
||||||
Self::ArrowFunctionExpression(_)
|
Self::ArrowFunctionExpression(_)
|
||||||
| Self::EmptyExpression(_)
|
| Self::EmptyExpression(_)
|
||||||
| Self::FunctionExpression(_)
|
| Self::FunctionExpression(_)
|
||||||
|
|
@ -1046,16 +1135,26 @@ impl<'a> ListenerMap for MemberExpression<'a> {
|
||||||
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
|
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
|
||||||
match self {
|
match self {
|
||||||
Self::ComputedMemberExpression(expr) => {
|
Self::ComputedMemberExpression(expr) => {
|
||||||
expr.report_effects(options);
|
expr.report_effects_when_assigned(options);
|
||||||
expr.object.report_effects_when_mutated(options);
|
|
||||||
}
|
}
|
||||||
Self::StaticMemberExpression(expr) => {
|
Self::StaticMemberExpression(expr) => {
|
||||||
expr.report_effects(options);
|
expr.report_effects_when_assigned(options);
|
||||||
expr.object.report_effects_when_mutated(options);
|
|
||||||
}
|
}
|
||||||
Self::PrivateFieldExpression(expr) => {
|
Self::PrivateFieldExpression(expr) => {
|
||||||
expr.report_effects(options);
|
expr.report_effects_when_assigned(options);
|
||||||
expr.object.report_effects_when_mutated(options);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
match self {
|
||||||
|
Self::ComputedMemberExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
Self::StaticMemberExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
|
}
|
||||||
|
Self::PrivateFieldExpression(expr) => {
|
||||||
|
expr.report_effects_when_called(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1066,18 +1165,99 @@ impl<'a> ListenerMap for ComputedMemberExpression<'a> {
|
||||||
self.expression.report_effects(options);
|
self.expression.report_effects(options);
|
||||||
self.object.report_effects(options);
|
self.object.report_effects(options);
|
||||||
}
|
}
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
self.report_effects(options);
|
||||||
|
|
||||||
|
let mut node = &self.object;
|
||||||
|
loop {
|
||||||
|
match node {
|
||||||
|
Expression::ComputedMemberExpression(expr) => {
|
||||||
|
node = &expr.object;
|
||||||
|
}
|
||||||
|
Expression::StaticMemberExpression(expr) => node = &expr.object,
|
||||||
|
Expression::PrivateInExpression(expr) => node = &expr.right,
|
||||||
|
_ => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Expression::Identifier(ident) = node {
|
||||||
|
ident.report_effects_when_called(options);
|
||||||
|
} else {
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallMember(node.span()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
|
||||||
|
self.report_effects(options);
|
||||||
|
self.object.report_effects_when_mutated(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for StaticMemberExpression<'a> {
|
impl<'a> ListenerMap for StaticMemberExpression<'a> {
|
||||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
self.object.report_effects(options);
|
self.object.report_effects(options);
|
||||||
}
|
}
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
self.report_effects(options);
|
||||||
|
|
||||||
|
let mut node = &self.object;
|
||||||
|
loop {
|
||||||
|
match node {
|
||||||
|
Expression::ComputedMemberExpression(expr) => {
|
||||||
|
node = &expr.object;
|
||||||
|
}
|
||||||
|
Expression::StaticMemberExpression(expr) => node = &expr.object,
|
||||||
|
Expression::PrivateInExpression(expr) => node = &expr.right,
|
||||||
|
_ => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Expression::Identifier(ident) = node {
|
||||||
|
ident.report_effects_when_called(options);
|
||||||
|
} else {
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallMember(node.span()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
|
||||||
|
self.report_effects(options);
|
||||||
|
self.object.report_effects_when_mutated(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for PrivateFieldExpression<'a> {
|
impl<'a> ListenerMap for PrivateFieldExpression<'a> {
|
||||||
fn report_effects(&self, options: &NodeListenerOptions) {
|
fn report_effects(&self, options: &NodeListenerOptions) {
|
||||||
self.object.report_effects(options);
|
self.object.report_effects(options);
|
||||||
}
|
}
|
||||||
|
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
|
||||||
|
self.report_effects(options);
|
||||||
|
|
||||||
|
let mut node = &self.object;
|
||||||
|
loop {
|
||||||
|
match node {
|
||||||
|
Expression::ComputedMemberExpression(expr) => {
|
||||||
|
node = &expr.object;
|
||||||
|
}
|
||||||
|
Expression::StaticMemberExpression(expr) => node = &expr.object,
|
||||||
|
Expression::PrivateInExpression(expr) => node = &expr.right,
|
||||||
|
_ => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Expression::Identifier(ident) = node {
|
||||||
|
ident.report_effects_when_called(options);
|
||||||
|
} else {
|
||||||
|
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallMember(node.span()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
|
||||||
|
self.report_effects(options);
|
||||||
|
self.object.report_effects_when_mutated(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ListenerMap for ArrayExpressionElement<'a> {
|
impl<'a> ListenerMap for ArrayExpressionElement<'a> {
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ enum NoSideEffectsDiagnostic {
|
||||||
#[diagnostic(severity(warning))]
|
#[diagnostic(severity(warning))]
|
||||||
CallImport(#[label] Span),
|
CallImport(#[label] Span),
|
||||||
|
|
||||||
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function")]
|
||||||
|
#[diagnostic(severity(warning))]
|
||||||
|
CallMember(#[label] Span),
|
||||||
|
|
||||||
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Debugger statements are side-effects")]
|
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Debugger statements are side-effects")]
|
||||||
#[diagnostic(severity(warning))]
|
#[diagnostic(severity(warning))]
|
||||||
Debugger(#[label] Span),
|
Debugger(#[label] Span),
|
||||||
|
|
@ -280,29 +284,29 @@ fn test() {
|
||||||
r#"class X {}; const x = <X test="3"/>"#,
|
r#"class X {}; const x = <X test="3"/>"#,
|
||||||
// JSXSpreadAttribute
|
// JSXSpreadAttribute
|
||||||
"class X {}; const x = <X {...{x: 3}}/>",
|
"class X {}; const x = <X {...{x: 3}}/>",
|
||||||
// // LabeledStatement
|
// LabeledStatement
|
||||||
// "loop: for(;true;){continue loop}",
|
"loop: for(;true;){continue loop}",
|
||||||
// // Literal
|
// Literal
|
||||||
// "const x = 3",
|
"const x = 3",
|
||||||
// "if (false) ext()",
|
"if (false) ext()",
|
||||||
// r#""use strict""#,
|
r#""use strict""#,
|
||||||
// // LogicalExpression
|
// LogicalExpression
|
||||||
// "const x = 3 || 4",
|
"const x = 3 || 4",
|
||||||
// "true || ext()",
|
"true || ext()",
|
||||||
// "false && ext()",
|
"false && ext()",
|
||||||
// "if (false && false) ext()",
|
"if (false && false) ext()",
|
||||||
// "if (true && false) ext()",
|
"if (true && false) ext()",
|
||||||
// "if (false && true) ext()",
|
"if (false && true) ext()",
|
||||||
// "if (false || false) ext()",
|
"if (false || false) ext()",
|
||||||
// // MemberExpression
|
// MemberExpression
|
||||||
// "const x = ext.y",
|
"const x = ext.y",
|
||||||
// r#"const x = ext["y"]"#,
|
r#"const x = ext["y"]"#,
|
||||||
// "let x = ()=>{}; x.y = 1",
|
"let x = ()=>{}; x.y = 1",
|
||||||
// // MemberExpression when called
|
// MemberExpression when called
|
||||||
// "const x = Object.keys({})",
|
// "const x = Object.keys({})",
|
||||||
// // MemberExpression when mutated
|
// MemberExpression when mutated
|
||||||
// "const x = {};x.y = ext",
|
"const x = {};x.y = ext",
|
||||||
// "const x = {y: 1};delete x.y",
|
"const x = {y: 1};delete x.y",
|
||||||
// // MetaProperty
|
// // MetaProperty
|
||||||
// "function x(){const y = new.target}; x()",
|
// "function x(){const y = new.target}; x()",
|
||||||
// // MethodDefinition
|
// // MethodDefinition
|
||||||
|
|
@ -568,30 +572,30 @@ fn test() {
|
||||||
"class X {}; const x = <X test={ext()}/>",
|
"class X {}; const x = <X test={ext()}/>",
|
||||||
// JSXSpreadAttribute
|
// JSXSpreadAttribute
|
||||||
"class X {}; const x = <X {...{x: ext()}}/>",
|
"class X {}; const x = <X {...{x: ext()}}/>",
|
||||||
// // LabeledStatement
|
// LabeledStatement
|
||||||
// "loop: for(;true;){ext()}",
|
"loop: for(;true;){ext()}",
|
||||||
// // Literal
|
// Literal
|
||||||
// "if (true) ext()",
|
"if (true) ext()",
|
||||||
// // LogicalExpression
|
// LogicalExpression
|
||||||
// "ext() && true",
|
"ext() && true",
|
||||||
// "true && ext()",
|
"true && ext()",
|
||||||
// "false || ext()",
|
"false || ext()",
|
||||||
// "if (true && true) ext()",
|
"if (true && true) ext()",
|
||||||
// "if (false || true) ext()",
|
"if (false || true) ext()",
|
||||||
// "if (true || false) ext()",
|
"if (true || false) ext()",
|
||||||
// "if (true || true) ext()",
|
"if (true || true) ext()",
|
||||||
// // MemberExpression
|
// MemberExpression
|
||||||
// "const x = {};const y = x[ext()]",
|
"const x = {};const y = x[ext()]",
|
||||||
// // MemberExpression when called
|
// MemberExpression when called
|
||||||
// "ext.x()",
|
"ext.x()",
|
||||||
// "const x = {}; x.y()",
|
"const x = {}; x.y()",
|
||||||
// "const x = ()=>{}; x().y()",
|
"const x = ()=>{}; x().y()",
|
||||||
// "const Object = {}; const x = Object.keys({})",
|
"const Object = {}; const x = Object.keys({})",
|
||||||
// "const x = {}; x[ext()]()",
|
"const x = {}; x[ext()]()",
|
||||||
// // MemberExpression when mutated
|
// MemberExpression when mutated
|
||||||
// "const x = {y: ext};x.y.z = 1",
|
"const x = {y: ext};x.y.z = 1",
|
||||||
// "const x = {y:ext};const y = x.y; y.z = 1",
|
"const x = {y:ext};const y = x.y; y.z = 1",
|
||||||
// "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
|
||||||
|
|
|
||||||
|
|
@ -800,10 +800,10 @@ expression: no_side_effects_in_initialization
|
||||||
· ─
|
· ─
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling imported function
|
||||||
╭─[no_side_effects_in_initialization.tsx:1:30]
|
╭─[no_side_effects_in_initialization.tsx:1:13]
|
||||||
1 │ import * as y from "import"; y.x()
|
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
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating imported variable
|
||||||
|
|
@ -878,6 +878,120 @@ 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:19]
|
||||||
|
1 │ loop: for(;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:11]
|
||||||
|
1 │ if (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:1]
|
||||||
|
1 │ ext() && true
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ 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 │ 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:10]
|
||||||
|
1 │ false || 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 │ if (true && 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:20]
|
||||||
|
1 │ if (false || 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:20]
|
||||||
|
1 │ if (true || false) 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 │ if (true || 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:26]
|
||||||
|
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:1]
|
||||||
|
1 │ ext.x()
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
|
||||||
|
╭─[no_side_effects_in_initialization.tsx:1:11]
|
||||||
|
1 │ const x = {}; x.y()
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function
|
||||||
|
╭─[no_side_effects_in_initialization.tsx:1:19]
|
||||||
|
1 │ const x = ()=>{}; x().y()
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
|
||||||
|
╭─[no_side_effects_in_initialization.tsx:1:16]
|
||||||
|
1 │ const Object = {}; const x = Object.keys({})
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ 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:17]
|
||||||
|
1 │ const x = {}; x[ext()]()
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
|
||||||
|
╭─[no_side_effects_in_initialization.tsx:1:11]
|
||||||
|
1 │ const x = {}; x[ext()]()
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating
|
||||||
|
╭─[no_side_effects_in_initialization.tsx:1:20]
|
||||||
|
1 │ const x = {y: ext};x.y.z = 1
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating
|
||||||
|
╭─[no_side_effects_in_initialization.tsx:1:29]
|
||||||
|
1 │ const x = {y:ext};const y = x.y; y.z = 1
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
||||||
|
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating
|
||||||
|
╭─[no_side_effects_in_initialization.tsx:1:27]
|
||||||
|
1 │ const x = {y: ext};delete x.y.z
|
||||||
|
· ───
|
||||||
|
╰────
|
||||||
|
|
||||||
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `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:15]
|
╭─[no_side_effects_in_initialization.tsx:1:15]
|
||||||
1 │ const x = new ext()
|
1 │ const x = new ext()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use oxc_ast::{ast::Expression, AstKind, CommentKind};
|
use oxc_ast::{ast::Expression, AstKind, CommentKind};
|
||||||
use oxc_semantic::AstNodeId;
|
use oxc_semantic::AstNodeId;
|
||||||
use oxc_span::Span;
|
use oxc_span::Span;
|
||||||
use oxc_syntax::operator::BinaryOperator;
|
use oxc_syntax::operator::{BinaryOperator, LogicalOperator};
|
||||||
|
|
||||||
use crate::LintContext;
|
use crate::LintContext;
|
||||||
|
|
||||||
|
|
@ -192,7 +192,6 @@ pub fn calculate_binary_operation(op: BinaryOperator, left: Value, right: Value)
|
||||||
_ => Value::Unknown,
|
_ => Value::Unknown,
|
||||||
},
|
},
|
||||||
// <https://tc39.es/ecma262/#sec-islessthan>
|
// <https://tc39.es/ecma262/#sec-islessthan>
|
||||||
#[allow(clippy::single_match)]
|
|
||||||
BinaryOperator::LessThan => match (left, right) {
|
BinaryOperator::LessThan => match (left, right) {
|
||||||
// <https://tc39.es/ecma262/#sec-numeric-types-number-lessThan>
|
// <https://tc39.es/ecma262/#sec-numeric-types-number-lessThan>
|
||||||
(Value::Unknown, Value::Number(_)) | (Value::Number(_), Value::Unknown) => {
|
(Value::Unknown, Value::Number(_)) | (Value::Number(_), Value::Unknown) => {
|
||||||
|
|
@ -205,6 +204,20 @@ pub fn calculate_binary_operation(op: BinaryOperator, left: Value, right: Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn calculate_logical_operation(op: LogicalOperator, left: Value, right: Value) -> Value {
|
||||||
|
match op {
|
||||||
|
LogicalOperator::And => match (left, right) {
|
||||||
|
(Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a && b),
|
||||||
|
_ => Value::Unknown,
|
||||||
|
},
|
||||||
|
LogicalOperator::Or => match (left, right) {
|
||||||
|
(Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a || b),
|
||||||
|
_ => Value::Unknown,
|
||||||
|
},
|
||||||
|
LogicalOperator::Coalesce => Value::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_binary_operation() {
|
fn test_calculate_binary_operation() {
|
||||||
use oxc_syntax::operator::BinaryOperator;
|
use oxc_syntax::operator::BinaryOperator;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue