feat(linter/tree-shaking): add isPureFunction (#3175)

This commit is contained in:
Wang Wenzhe 2024-05-07 00:03:04 +08:00 committed by GitHub
parent 07076d9765
commit c0abbbd204
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 460 additions and 21 deletions

View file

@ -25,7 +25,8 @@ use crate::{
ast_util::{get_declaration_of_variable, get_symbol_id_of_variable},
utils::{
calculate_binary_operation, calculate_logical_operation, calculate_unary_operation,
get_write_expr, has_comment_about_side_effect_check, has_pure_notation, no_effects, Value,
get_write_expr, has_comment_about_side_effect_check, has_pure_notation, is_pure_function,
no_effects, FunctionName, Value,
},
LintContext,
};
@ -1053,7 +1054,6 @@ impl<'a> ListenerMap for CallExpression<'a> {
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()));
}
}
@ -1131,8 +1131,7 @@ 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) {
if is_pure_function(&FunctionName::Identifier(self), options.ctx) {
return;
}
@ -1281,10 +1280,16 @@ impl<'a> ListenerMap for StaticMemberExpression<'a> {
}
}
if let Expression::Identifier(ident) = node {
ident.report_effects_when_called(options);
} else {
let Expression::Identifier(ident) = node else {
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallMember(node.span()));
return;
};
if get_declaration_of_variable(ident, options.ctx)
.is_some_and(|_| !has_pure_notation(self.span, options.ctx))
|| !is_pure_function(&FunctionName::StaticMemberExpr(self), options.ctx)
{
options.ctx.diagnostic(NoSideEffectsDiagnostic::CallMember(self.span));
}
}
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {

View file

@ -311,7 +311,7 @@ fn test() {
r#"const x = ext["y"]"#,
"let x = ()=>{}; x.y = 1",
// MemberExpression when called
// "const x = Object.keys({})",
"const x = Object.keys({})",
// MemberExpression when mutated
"const x = {};x.y = ext",
"const x = {y: 1};delete x.y",

View file

@ -806,10 +806,10 @@ 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:13]
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function
╭─[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
@ -944,16 +944,16 @@ expression: no_side_effects_in_initialization
· ───
╰────
⚠ 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 member function
╭─[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]
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function
╭─[no_side_effects_in_initialization.tsx:1:15]
1 │ const x = {}; x.y()
· ──
· ──
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function
@ -962,10 +962,10 @@ expression: no_side_effects_in_initialization
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling
╭─[no_side_effects_in_initialization.tsx:1:16]
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling member function
╭─[no_side_effects_in_initialization.tsx:1:30]
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`

View file

@ -1,10 +1,17 @@
use oxc_ast::{ast::Expression, AstKind, CommentKind};
use lazy_static::lazy_static;
use oxc_ast::{
ast::{Expression, IdentifierReference, StaticMemberExpression},
AstKind, CommentKind,
};
use oxc_semantic::AstNodeId;
use oxc_span::Span;
use oxc_span::{CompactStr, GetSpan, Span};
use oxc_syntax::operator::{BinaryOperator, LogicalOperator, UnaryOperator};
use rustc_hash::FxHashSet;
use crate::LintContext;
mod pure_functions;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Value {
Boolean(bool),
@ -85,6 +92,63 @@ pub fn get_write_expr<'a, 'b>(
pub fn no_effects() {}
lazy_static! {
static ref PURE_FUNCTIONS_SET: FxHashSet<&'static str> = {
let mut set = FxHashSet::default();
set.extend(pure_functions::PURE_FUNCTIONS);
set
};
}
pub enum FunctionName<'a> {
Identifier(&'a IdentifierReference<'a>),
StaticMemberExpr(&'a StaticMemberExpression<'a>),
}
impl<'a> FunctionName<'a> {
fn from_expression(expr: &'a Expression<'a>) -> Option<Self> {
match expr {
Expression::Identifier(ident) => Some(FunctionName::Identifier(ident)),
Expression::StaticMemberExpression(member_expr) => {
Some(FunctionName::StaticMemberExpr(member_expr))
}
_ => None,
}
}
}
impl GetSpan for FunctionName<'_> {
fn span(&self) -> Span {
match self {
FunctionName::Identifier(ident) => ident.span,
FunctionName::StaticMemberExpr(member_expr) => member_expr.span,
}
}
}
pub fn is_pure_function(function_name: &FunctionName, ctx: &LintContext) -> bool {
if has_pure_notation(function_name.span(), ctx) {
return true;
}
let name = flatten_member_expr_if_possible(function_name);
PURE_FUNCTIONS_SET.contains(name.as_str())
}
fn flatten_member_expr_if_possible(function_name: &FunctionName) -> CompactStr {
match function_name {
FunctionName::StaticMemberExpr(static_member_expr) => {
let Some(parent_name) = FunctionName::from_expression(&static_member_expr.object)
else {
return CompactStr::from("");
};
let flattened_parent = flatten_member_expr_if_possible(&parent_name);
CompactStr::from(format!("{}.{}", flattened_parent, static_member_expr.property.name))
}
FunctionName::Identifier(ident) => ident.name.to_compact_str(),
}
}
/// Comments containing @__PURE__ or #__PURE__ mark a specific function call
/// or constructor invocation as side effect free.
///

View file

@ -0,0 +1,370 @@
pub const PURE_FUNCTIONS: &[&str] = &[
"Array.isArray",
"Error",
"EvalError",
"InternalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError",
"isFinite",
"isNaN",
"parseFloat",
"parseInt",
"decodeURI",
"decodeURIComponent",
"encodeURI",
"encodeURIComponent",
"escape",
"unescape",
"Object",
"Object.create",
"Object.getNotifier",
"Object.getOwn",
"Object.getOwnPropertyDescriptor",
"Object.getOwnPropertyNames",
"Object.getOwnPropertySymbols",
"Object.getPrototypeOf",
"Object.is",
"Object.isExtensible",
"Object.isFrozen",
"Object.isSealed",
"Object.keys",
"Function",
"Boolean",
"Number",
"Number.isFinite",
"Number.isInteger",
"Number.isNaN",
"Number.isSafeInteger",
"Number.parseFloat",
"Number.parseInt",
"Symbol",
"Symbol.for",
"Symbol.keyFor",
"Math.abs",
"Math.acos",
"Math.acosh",
"Math.asin",
"Math.asinh",
"Math.atan",
"Math.atan2",
"Math.atanh",
"Math.cbrt",
"Math.ceil",
"Math.clz32",
"Math.cos",
"Math.cosh",
"Math.exp",
"Math.expm1",
"Math.floor",
"Math.fround",
"Math.hypot",
"Math.imul",
"Math.log",
"Math.log10",
"Math.log1p",
"Math.log2",
"Math.max",
"Math.min",
"Math.pow",
"Math.random",
"Math.round",
"Math.sign",
"Math.sin",
"Math.sinh",
"Math.sqrt",
"Math.tan",
"Math.tanh",
"Math.trunc",
"Date",
"Date.UTC",
"Date.now",
"Date.parse",
"String",
"String.fromCharCode",
"String.fromCodePoint",
"String.raw",
"RegExp",
"Map",
"Set",
"WeakMap",
"WeakSet",
"ArrayBuffer",
"ArrayBuffer.isView",
"DataView",
"JSON.parse",
"JSON.stringify",
"Promise",
"Promise.all",
"Promise.race",
"Promise.reject",
"Promise.resolve",
"Intl.Collator",
"Intl.Collator.supportedLocalesOf",
"Intl.DateTimeFormat",
"Intl.DateTimeFormat.supportedLocalesOf",
"Intl.NumberFormat",
"Intl.NumberFormat.supportedLocalesOf",
"Array",
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
"Array.from",
"Int8Array.from",
"Uint8Array.from",
"Uint8ClampedArray.from",
"Int16Array.from",
"Uint16Array.from",
"Int32Array.from",
"Uint32Array.from",
"Float32Array.from",
"Float64Array.from",
"Array.of",
"Int8Array.of",
"Uint8Array.of",
"Uint8ClampedArray.of",
"Int16Array.of",
"Uint16Array.of",
"Int32Array.of",
"Uint32Array.of",
"Float32Array.of",
"Float64Array.of",
"SIMD.Int8x16",
"SIMD.Int16x8",
"SIMD.Int32x4",
"SIMD.Float32x4",
"SIMD.Float64x2",
"SIMD.Int8x16.abs",
"SIMD.Int8x16.add",
"SIMD.Int8x16.and",
"SIMD.Int8x16.bool",
"SIMD.Int8x16.check",
"SIMD.Int8x16.div",
"SIMD.Int8x16.equal",
"SIMD.Int8x16.extractLane",
"SIMD.Int8x16.fromFloat32x4",
"SIMD.Int8x16.fromFloat32x4Bits",
"SIMD.Int8x16.fromFloat64x2",
"SIMD.Int8x16.fromFloat64x2Bits",
"SIMD.Int8x16.fromInt16x8Bits",
"SIMD.Int8x16.fromInt32x4",
"SIMD.Int8x16.fromInt32x4Bits",
"SIMD.Int8x16.fromInt8x16Bits",
"SIMD.Int8x16.greaterThan",
"SIMD.Int8x16.greaterThanOrEqual",
"SIMD.Int8x16.lessThan",
"SIMD.Int8x16.lessThanOrEqual",
"SIMD.Int8x16.load",
"SIMD.Int8x16.max",
"SIMD.Int8x16.maxNum",
"SIMD.Int8x16.min",
"SIMD.Int8x16.minNum",
"SIMD.Int8x16.mul",
"SIMD.Int8x16.neg",
"SIMD.Int8x16.not",
"SIMD.Int8x16.notEqual",
"SIMD.Int8x16.or",
"SIMD.Int8x16.reciprocalApproximation",
"SIMD.Int8x16.reciprocalSqrtApproximation",
"SIMD.Int8x16.replaceLane",
"SIMD.Int8x16.select",
"SIMD.Int8x16.selectBits",
"SIMD.Int8x16.shiftLeftByScalar",
"SIMD.Int8x16.shiftRightArithmeticByScalar",
"SIMD.Int8x16.shiftRightLogicalByScalar",
"SIMD.Int8x16.shuffle",
"SIMD.Int8x16.splat",
"SIMD.Int8x16.sqrt",
"SIMD.Int8x16.store",
"SIMD.Int8x16.sub",
"SIMD.Int8x16.swizzle",
"SIMD.Int8x16.xor",
"SIMD.Int16x8.abs",
"SIMD.Int16x8.add",
"SIMD.Int16x8.and",
"SIMD.Int16x8.bool",
"SIMD.Int16x8.check",
"SIMD.Int16x8.div",
"SIMD.Int16x8.equal",
"SIMD.Int16x8.extractLane",
"SIMD.Int16x8.fromFloat32x4",
"SIMD.Int16x8.fromFloat32x4Bits",
"SIMD.Int16x8.fromFloat64x2",
"SIMD.Int16x8.fromFloat64x2Bits",
"SIMD.Int16x8.fromInt16x8Bits",
"SIMD.Int16x8.fromInt32x4",
"SIMD.Int16x8.fromInt32x4Bits",
"SIMD.Int16x8.fromInt8x16Bits",
"SIMD.Int16x8.greaterThan",
"SIMD.Int16x8.greaterThanOrEqual",
"SIMD.Int16x8.lessThan",
"SIMD.Int16x8.lessThanOrEqual",
"SIMD.Int16x8.load",
"SIMD.Int16x8.max",
"SIMD.Int16x8.maxNum",
"SIMD.Int16x8.min",
"SIMD.Int16x8.minNum",
"SIMD.Int16x8.mul",
"SIMD.Int16x8.neg",
"SIMD.Int16x8.not",
"SIMD.Int16x8.notEqual",
"SIMD.Int16x8.or",
"SIMD.Int16x8.reciprocalApproximation",
"SIMD.Int16x8.reciprocalSqrtApproximation",
"SIMD.Int16x8.replaceLane",
"SIMD.Int16x8.select",
"SIMD.Int16x8.selectBits",
"SIMD.Int16x8.shiftLeftByScalar",
"SIMD.Int16x8.shiftRightArithmeticByScalar",
"SIMD.Int16x8.shiftRightLogicalByScalar",
"SIMD.Int16x8.shuffle",
"SIMD.Int16x8.splat",
"SIMD.Int16x8.sqrt",
"SIMD.Int16x8.store",
"SIMD.Int16x8.sub",
"SIMD.Int16x8.swizzle",
"SIMD.Int16x8.xor",
"SIMD.Int32x4.abs",
"SIMD.Int32x4.add",
"SIMD.Int32x4.and",
"SIMD.Int32x4.bool",
"SIMD.Int32x4.check",
"SIMD.Int32x4.div",
"SIMD.Int32x4.equal",
"SIMD.Int32x4.extractLane",
"SIMD.Int32x4.fromFloat32x4",
"SIMD.Int32x4.fromFloat32x4Bits",
"SIMD.Int32x4.fromFloat64x2",
"SIMD.Int32x4.fromFloat64x2Bits",
"SIMD.Int32x4.fromInt16x8Bits",
"SIMD.Int32x4.fromInt32x4",
"SIMD.Int32x4.fromInt32x4Bits",
"SIMD.Int32x4.fromInt8x16Bits",
"SIMD.Int32x4.greaterThan",
"SIMD.Int32x4.greaterThanOrEqual",
"SIMD.Int32x4.lessThan",
"SIMD.Int32x4.lessThanOrEqual",
"SIMD.Int32x4.load",
"SIMD.Int32x4.max",
"SIMD.Int32x4.maxNum",
"SIMD.Int32x4.min",
"SIMD.Int32x4.minNum",
"SIMD.Int32x4.mul",
"SIMD.Int32x4.neg",
"SIMD.Int32x4.not",
"SIMD.Int32x4.notEqual",
"SIMD.Int32x4.or",
"SIMD.Int32x4.reciprocalApproximation",
"SIMD.Int32x4.reciprocalSqrtApproximation",
"SIMD.Int32x4.replaceLane",
"SIMD.Int32x4.select",
"SIMD.Int32x4.selectBits",
"SIMD.Int32x4.shiftLeftByScalar",
"SIMD.Int32x4.shiftRightArithmeticByScalar",
"SIMD.Int32x4.shiftRightLogicalByScalar",
"SIMD.Int32x4.shuffle",
"SIMD.Int32x4.splat",
"SIMD.Int32x4.sqrt",
"SIMD.Int32x4.store",
"SIMD.Int32x4.sub",
"SIMD.Int32x4.swizzle",
"SIMD.Int32x4.xor",
"SIMD.Float32x4.abs",
"SIMD.Float32x4.add",
"SIMD.Float32x4.and",
"SIMD.Float32x4.bool",
"SIMD.Float32x4.check",
"SIMD.Float32x4.div",
"SIMD.Float32x4.equal",
"SIMD.Float32x4.extractLane",
"SIMD.Float32x4.fromFloat32x4",
"SIMD.Float32x4.fromFloat32x4Bits",
"SIMD.Float32x4.fromFloat64x2",
"SIMD.Float32x4.fromFloat64x2Bits",
"SIMD.Float32x4.fromInt16x8Bits",
"SIMD.Float32x4.fromInt32x4",
"SIMD.Float32x4.fromInt32x4Bits",
"SIMD.Float32x4.fromInt8x16Bits",
"SIMD.Float32x4.greaterThan",
"SIMD.Float32x4.greaterThanOrEqual",
"SIMD.Float32x4.lessThan",
"SIMD.Float32x4.lessThanOrEqual",
"SIMD.Float32x4.load",
"SIMD.Float32x4.max",
"SIMD.Float32x4.maxNum",
"SIMD.Float32x4.min",
"SIMD.Float32x4.minNum",
"SIMD.Float32x4.mul",
"SIMD.Float32x4.neg",
"SIMD.Float32x4.not",
"SIMD.Float32x4.notEqual",
"SIMD.Float32x4.or",
"SIMD.Float32x4.reciprocalApproximation",
"SIMD.Float32x4.reciprocalSqrtApproximation",
"SIMD.Float32x4.replaceLane",
"SIMD.Float32x4.select",
"SIMD.Float32x4.selectBits",
"SIMD.Float32x4.shiftLeftByScalar",
"SIMD.Float32x4.shiftRightArithmeticByScalar",
"SIMD.Float32x4.shiftRightLogicalByScalar",
"SIMD.Float32x4.shuffle",
"SIMD.Float32x4.splat",
"SIMD.Float32x4.sqrt",
"SIMD.Float32x4.store",
"SIMD.Float32x4.sub",
"SIMD.Float32x4.swizzle",
"SIMD.Float32x4.xor",
"SIMD.Float64x2.abs",
"SIMD.Float64x2.add",
"SIMD.Float64x2.and",
"SIMD.Float64x2.bool",
"SIMD.Float64x2.check",
"SIMD.Float64x2.div",
"SIMD.Float64x2.equal",
"SIMD.Float64x2.extractLane",
"SIMD.Float64x2.fromFloat32x4",
"SIMD.Float64x2.fromFloat32x4Bits",
"SIMD.Float64x2.fromFloat64x2",
"SIMD.Float64x2.fromFloat64x2Bits",
"SIMD.Float64x2.fromInt16x8Bits",
"SIMD.Float64x2.fromInt32x4",
"SIMD.Float64x2.fromInt32x4Bits",
"SIMD.Float64x2.fromInt8x16Bits",
"SIMD.Float64x2.greaterThan",
"SIMD.Float64x2.greaterThanOrEqual",
"SIMD.Float64x2.lessThan",
"SIMD.Float64x2.lessThanOrEqual",
"SIMD.Float64x2.load",
"SIMD.Float64x2.max",
"SIMD.Float64x2.maxNum",
"SIMD.Float64x2.min",
"SIMD.Float64x2.minNum",
"SIMD.Float64x2.mul",
"SIMD.Float64x2.neg",
"SIMD.Float64x2.not",
"SIMD.Float64x2.notEqual",
"SIMD.Float64x2.or",
"SIMD.Float64x2.reciprocalApproximation",
"SIMD.Float64x2.reciprocalSqrtApproximation",
"SIMD.Float64x2.replaceLane",
"SIMD.Float64x2.select",
"SIMD.Float64x2.selectBits",
"SIMD.Float64x2.shiftLeftByScalar",
"SIMD.Float64x2.shiftRightArithmeticByScalar",
"SIMD.Float64x2.shiftRightLogicalByScalar",
"SIMD.Float64x2.shuffle",
"SIMD.Float64x2.splat",
"SIMD.Float64x2.sqrt",
"SIMD.Float64x2.store",
"SIMD.Float64x2.sub",
"SIMD.Float64x2.swizzle",
"SIMD.Float64x2.xor",
];