feat(es/lints): Implement no-param-reassign rule (#4134)

This commit is contained in:
Artur 2022-03-24 13:27:26 +03:00 committed by GitHub
parent a293adca0a
commit 47712de0da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 814 additions and 2 deletions

View File

@ -0,0 +1,14 @@
{
"jsc": {
"lints": {
"no-param-reassign": [
"error",
{
"ignorePropertyModificationsForRegex": [
"^ignore"
]
}
]
}
}
}

View File

@ -0,0 +1,4 @@
function f(ignoreMe, catchMe) {
ignoreMe = 1;
catchMe = 1;
}

View File

@ -0,0 +1,6 @@
x Assignment to function parameter 'catchMe'
,----
3 | catchMe = 1;
: ^^^^^^^
`----

View File

@ -0,0 +1,14 @@
{
"jsc": {
"lints": {
"no-param-reassign": [
"error",
{
"ignorePropertyModificationsFor": [
"b"
]
}
]
}
}
}

View File

@ -0,0 +1,4 @@
function f(a, b) {
a = 1;
b = 1;
}

View File

@ -0,0 +1,6 @@
x Assignment to function parameter 'a'
,----
2 | a = 1;
: ^
`----

View File

@ -0,0 +1,12 @@
{
"jsc": {
"lints": {
"no-param-reassign": [
"error",
{
"props": false
}
]
}
}
}

View File

@ -0,0 +1,60 @@
function f(a) {
a = 1;
}
function f(a) {
function f3() {
a = 1;
}
}
function f(a) {
a.prop = 1;
(a).prop = 1;
(((((((a))))))).prop = 1;
(void 0, a).prop = 1;
(a.prop = 1);
(void 0, a.prop = 1, void 0)
a.prop.b = 1;
}
function f(a) {
a++;
++a;
a.prop++;
++a.prop;
}
function f(a) {
for (a of []) {}
for (const a of []) {}
for (a in []) {}
for (const a in []) {}
}
function f(a) {
delete a.x;
}
function f(a, b, c) {
({ a } = {});
({ a, b } = {});
({ a, b, k: { c } } = {});
([a] = []);
([{a}, [b]] = [])
}
function f(a, { b, k: { c }, k2: [ d ] }, [e, [f], { g }]) {
a = 1;
b = 1;
c = 1;
d = 1;
e = 1;
f = 1;
g = 1;
}
function f(a) {
alert(a);
}

View File

@ -0,0 +1,132 @@
x Assignment to function parameter 'a'
,----
2 | a = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
7 | a = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
22 | a++;
: ^
`----
x Assignment to function parameter 'a'
,----
23 | ++a;
: ^
`----
x Assignment to function parameter 'a'
,----
29 | for (a of []) {}
: ^
`----
x Assignment to function parameter 'a'
,----
32 | for (a in []) {}
: ^
`----
x Assignment to function parameter 'a'
,----
41 | ({ a } = {});
: ^
`----
x Assignment to function parameter 'a'
,----
42 | ({ a, b } = {});
: ^
`----
x Assignment to function parameter 'b'
,----
42 | ({ a, b } = {});
: ^
`----
x Assignment to function parameter 'a'
,----
43 | ({ a, b, k: { c } } = {});
: ^
`----
x Assignment to function parameter 'b'
,----
43 | ({ a, b, k: { c } } = {});
: ^
`----
x Assignment to function parameter 'c'
,----
43 | ({ a, b, k: { c } } = {});
: ^
`----
x Assignment to function parameter 'a'
,----
44 | ([a] = []);
: ^
`----
x Assignment to function parameter 'a'
,----
45 | ([{a}, [b]] = [])
: ^
`----
x Assignment to function parameter 'b'
,----
45 | ([{a}, [b]] = [])
: ^
`----
x Assignment to function parameter 'a'
,----
49 | a = 1;
: ^
`----
x Assignment to function parameter 'b'
,----
50 | b = 1;
: ^
`----
x Assignment to function parameter 'c'
,----
51 | c = 1;
: ^
`----
x Assignment to function parameter 'd'
,----
52 | d = 1;
: ^
`----
x Assignment to function parameter 'e'
,----
53 | e = 1;
: ^
`----
x Assignment to function parameter 'f'
,----
54 | f = 1;
: ^
`----
x Assignment to function parameter 'g'
,----
55 | g = 1;
: ^
`----

View File

@ -0,0 +1,9 @@
{
"jsc": {
"lints": {
"no-param-reassign": [
"error"
]
}
}
}

View File

@ -0,0 +1,60 @@
function f(a) {
a = 1;
}
function f(a) {
function f3() {
a = 1;
}
}
function f(a) {
a.prop = 1;
(a).prop = 1;
(((((((a))))))).prop = 1;
(void 0, a).prop = 1;
(a.prop = 1);
(void 0, a.prop = 1, void 0)
a.prop.b = 1;
}
function f(a) {
a++;
++a;
a.prop++;
++a.prop;
}
function f(a) {
for (a of []) {}
for (const a of []) {}
for (a in []) {}
for (const a in []) {}
}
function f(a) {
delete a.x;
}
function f(a, b, c) {
({ a } = {});
({ a, b } = {});
({ a, b, k: { c } } = {});
([a] = []);
([{a}, [b]] = [])
}
function f(a, { b, k: { c }, k2: [ d ] }, [e, [f], { g }]) {
a = 1;
b = 1;
c = 1;
d = 1;
e = 1;
f = 1;
g = 1;
}
function f(a) {
alert(a);
}

View File

@ -0,0 +1,192 @@
x Assignment to function parameter 'a'
,----
2 | a = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
7 | a = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
12 | a.prop = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
13 | (a).prop = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
14 | (((((((a))))))).prop = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
15 | (void 0, a).prop = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
16 | (a.prop = 1);
: ^
`----
x Assignment to function parameter 'a'
,----
17 | (void 0, a.prop = 1, void 0)
: ^
`----
x Assignment to function parameter 'a'
,----
18 | a.prop.b = 1;
: ^
`----
x Assignment to function parameter 'a'
,----
22 | a++;
: ^
`----
x Assignment to function parameter 'a'
,----
23 | ++a;
: ^
`----
x Assignment to function parameter 'a'
,----
24 | a.prop++;
: ^
`----
x Assignment to function parameter 'a'
,----
25 | ++a.prop;
: ^
`----
x Assignment to function parameter 'a'
,----
29 | for (a of []) {}
: ^
`----
x Assignment to function parameter 'a'
,----
32 | for (a in []) {}
: ^
`----
x Assignment to function parameter 'a'
,----
37 | delete a.x;
: ^
`----
x Assignment to function parameter 'a'
,----
41 | ({ a } = {});
: ^
`----
x Assignment to function parameter 'a'
,----
42 | ({ a, b } = {});
: ^
`----
x Assignment to function parameter 'b'
,----
42 | ({ a, b } = {});
: ^
`----
x Assignment to function parameter 'a'
,----
43 | ({ a, b, k: { c } } = {});
: ^
`----
x Assignment to function parameter 'b'
,----
43 | ({ a, b, k: { c } } = {});
: ^
`----
x Assignment to function parameter 'c'
,----
43 | ({ a, b, k: { c } } = {});
: ^
`----
x Assignment to function parameter 'a'
,----
44 | ([a] = []);
: ^
`----
x Assignment to function parameter 'a'
,----
45 | ([{a}, [b]] = [])
: ^
`----
x Assignment to function parameter 'b'
,----
45 | ([{a}, [b]] = [])
: ^
`----
x Assignment to function parameter 'a'
,----
49 | a = 1;
: ^
`----
x Assignment to function parameter 'b'
,----
50 | b = 1;
: ^
`----
x Assignment to function parameter 'c'
,----
51 | c = 1;
: ^
`----
x Assignment to function parameter 'd'
,----
52 | d = 1;
: ^
`----
x Assignment to function parameter 'e'
,----
53 | e = 1;
: ^
`----
x Assignment to function parameter 'f'
,----
54 | f = 1;
: ^
`----
x Assignment to function parameter 'g'
,----
55 | g = 1;
: ^
`----

View File

@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize};
use crate::rules::non_critical_lints::{
dot_notation::DotNotationConfig, eqeqeq::EqeqeqConfig, no_bitwise::NoBitwiseConfig,
no_console::NoConsoleConfig, no_empty_function::NoEmptyFunctionConfig,
no_restricted_syntax::NoRestrictedSyntaxConfig, no_use_before_define::NoUseBeforeDefineConfig,
no_param_reassign::NoParamReassignConfig, no_restricted_syntax::NoRestrictedSyntaxConfig,
no_use_before_define::NoUseBeforeDefineConfig,
prefer_regex_literals::PreferRegexLiteralsConfig, quotes::QuotesConfig, radix::RadixConfig,
use_is_nan::UseIsNanConfig, valid_typeof::ValidTypeofConfig, yoda::YodaConfig,
};
@ -150,4 +151,8 @@ pub struct LintConfig {
#[cfg(feature = "non_critical_lints")]
#[serde(default, alias = "validTypeof")]
pub valid_typeof: RuleConfig<ValidTypeofConfig>,
#[cfg(feature = "non_critical_lints")]
#[serde(default, alias = "noParamReassign")]
pub no_param_reassign: RuleConfig<NoParamReassignConfig>,
}

View File

@ -27,6 +27,7 @@ pub(crate) mod non_critical_lints {
pub mod no_loop_func;
pub mod no_new;
pub mod no_new_symbol;
pub mod no_param_reassign;
pub mod no_restricted_syntax;
pub mod no_use_before_define;
pub mod prefer_regex_literals;
@ -146,6 +147,10 @@ pub fn all(lint_params: LintParams) -> Vec<Box<dyn Rule>> {
));
rules.extend(valid_typeof::valid_typeof(&lint_config.valid_typeof));
rules.extend(no_param_reassign::no_param_reassign(
&lint_config.no_param_reassign,
));
}
rules

View File

@ -0,0 +1,281 @@
use dashmap::DashMap;
use regex::Regex;
use serde::{Deserialize, Serialize};
use swc_common::{
collections::{AHashMap, AHashSet},
errors::HANDLER,
sync::Lazy,
Span,
};
use swc_ecma_ast::*;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
rules::utils::unwrap_seqs_and_parens,
};
const INVALID_REGEX_MESSAGE: &str = "no-param-reassign: invalid regex pattern in allowPattern. Check syntax documentation https://docs.rs/regex/latest/regex/#syntax";
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NoParamReassignConfig {
props: Option<bool>,
ignore_property_modifications_for: Option<AHashSet<String>>,
ignore_property_modifications_for_regex: Option<Vec<String>>,
}
pub fn no_param_reassign(config: &RuleConfig<NoParamReassignConfig>) -> Option<Box<dyn Rule>> {
match config.get_rule_reaction() {
LintRuleReaction::Off => None,
_ => Some(visitor_rule(NoParamReassign::new(config))),
}
}
#[derive(Debug, Default)]
struct NoParamReassign {
expected_reaction: LintRuleReaction,
scoped_params: AHashMap<Span, AHashSet<Id>>,
scopes: Vec<Span>,
check_props: bool,
ignore_names: Option<AHashSet<String>>,
ignore_names_patterns: Option<Vec<String>>,
}
impl NoParamReassign {
fn new(config: &RuleConfig<NoParamReassignConfig>) -> Self {
let rule_config = config.get_rule_config();
Self {
expected_reaction: config.get_rule_reaction(),
scoped_params: Default::default(),
scopes: vec![],
check_props: rule_config.props.unwrap_or(true),
ignore_names: rule_config.ignore_property_modifications_for.clone(),
ignore_names_patterns: rule_config.ignore_property_modifications_for_regex.clone(),
}
}
fn emit_report(&self, span: Span, name: &str) {
let message = format!("Assignment to function parameter '{}'", name);
HANDLER.with(|handler| match self.expected_reaction {
LintRuleReaction::Error => {
handler.struct_span_err(span, &message).emit();
}
LintRuleReaction::Warning => {
handler.struct_span_warn(span, &message).emit();
}
_ => {}
});
}
fn collect_function_params(&mut self, pat: &Pat) {
match pat {
Pat::Ident(BindingIdent { id, .. }) => {
self.scoped_params
.get_mut(self.scopes.last().unwrap())
.unwrap()
.insert(id.to_id());
}
Pat::Object(ObjectPat { props, .. }) => props.iter().for_each(|prop| {
match prop {
ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
self.scoped_params
.get_mut(self.scopes.last().unwrap())
.unwrap()
.insert(key.to_id());
}
ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
self.collect_function_params(value.as_ref());
}
_ => {}
};
}),
Pat::Array(ArrayPat { elems, .. }) => elems.iter().for_each(|elem| {
if let Some(elem) = elem {
self.collect_function_params(elem);
}
}),
_ => {}
}
}
fn is_satisfying_function_param(&self, ident: &Ident) -> bool {
if let Some(ignore_names) = &self.ignore_names {
if ignore_names.contains(&*ident.sym) {
return false;
}
}
let is_function_param = self.scopes.iter().rev().any(|scope| {
self.scoped_params
.get(scope)
.unwrap()
.contains(&ident.to_id())
});
if !is_function_param {
return false;
}
if let Some(ignore_names_patterns) = &self.ignore_names_patterns {
static REGEX_CACHE: Lazy<DashMap<String, Regex, ahash::RandomState>> =
Lazy::new(Default::default);
let sym = &*ident.sym;
let ignored_by_pattern = ignore_names_patterns.iter().any(|pattern| {
if !REGEX_CACHE.contains_key(pattern) {
REGEX_CACHE.insert(
pattern.clone(),
Regex::new(pattern).expect(INVALID_REGEX_MESSAGE),
);
}
return REGEX_CACHE.get(pattern).unwrap().is_match(sym);
});
if ignored_by_pattern {
return false;
}
}
true
}
fn check_obj_member(&self, member_expr: &MemberExpr) {
if !self.check_props {
return;
}
match unwrap_seqs_and_parens(member_expr.obj.as_ref()) {
Expr::Ident(ident) => {
if self.is_satisfying_function_param(ident) {
self.emit_report(ident.span, &ident.sym);
}
}
Expr::Member(member_expr) => {
self.check_obj_member(member_expr);
}
_ => {}
}
}
fn check_pat_or_expr(&self, pat_or_expr: &PatOrExpr) {
match pat_or_expr {
PatOrExpr::Pat(pat) => {
self.check_pat(pat.as_ref());
}
PatOrExpr::Expr(expr) => {
self.check_expr(expr.as_ref());
}
}
}
fn check_expr(&self, expr: &Expr) {
match unwrap_seqs_and_parens(expr) {
Expr::Ident(ident) => {
if self.is_satisfying_function_param(ident) {
self.emit_report(ident.span, &ident.sym);
}
}
Expr::Member(member_expr) => {
self.check_obj_member(member_expr);
}
_ => {}
}
}
fn check_pat(&self, pat: &Pat) {
match pat {
Pat::Ident(BindingIdent { id, .. }) => {
if self.is_satisfying_function_param(id) {
self.emit_report(id.span, &id.sym);
}
}
Pat::Expr(expr) => {
if let Expr::Member(member_expr) = expr.as_ref() {
self.check_obj_member(member_expr);
}
}
Pat::Object(ObjectPat { props, .. }) => {
props.iter().for_each(|prop| match prop {
ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
if self.is_satisfying_function_param(key) {
self.emit_report(key.span, &key.sym);
}
}
ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
self.check_pat(value.as_ref());
}
_ => {}
});
}
Pat::Array(ArrayPat { elems, .. }) => {
elems.iter().for_each(|elem| {
if let Some(elem) = elem {
self.check_pat(elem);
}
});
}
_ => {}
}
}
}
impl Visit for NoParamReassign {
noop_visit_type!();
fn visit_function(&mut self, function: &Function) {
self.scopes.push(function.span);
self.scoped_params.insert(function.span, Default::default());
function.params.iter().for_each(|param| {
self.collect_function_params(&param.pat);
});
function.visit_children_with(self);
self.scopes.pop();
self.scoped_params.remove(&function.span);
}
fn visit_assign_expr(&mut self, assign_expr: &AssignExpr) {
self.check_pat_or_expr(&assign_expr.left);
assign_expr.visit_children_with(self);
}
fn visit_update_expr(&mut self, update_expr: &UpdateExpr) {
self.check_expr(update_expr.arg.as_ref());
update_expr.visit_children_with(self);
}
fn visit_for_of_stmt(&mut self, for_of_stmt: &ForOfStmt) {
if let VarDeclOrPat::Pat(pat) = &for_of_stmt.left {
self.check_pat(pat);
}
for_of_stmt.visit_children_with(self);
}
fn visit_for_in_stmt(&mut self, for_in_stmt: &ForInStmt) {
if let VarDeclOrPat::Pat(pat) = &for_in_stmt.left {
self.check_pat(pat);
}
for_in_stmt.visit_children_with(self);
}
fn visit_unary_expr(&mut self, unary_expr: &UnaryExpr) {
if let op!("delete") = unary_expr.op {
self.check_expr(unary_expr.arg.as_ref());
}
unary_expr.visit_children_with(self);
}
}

View File

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use swc_atoms::JsWord;
use swc_common::{collections::AHashSet, SourceMap, Span, SyntaxContext};
use swc_ecma_ast::{
Expr, Id, Lit, MemberExpr, MemberProp, Number, ParenExpr, Regex, Str, TaggedTpl, Tpl,
Expr, Id, Lit, MemberExpr, MemberProp, Number, ParenExpr, Regex, SeqExpr, Str, TaggedTpl, Tpl,
};
use swc_ecma_utils::ident::IdentLike;
@ -131,3 +131,11 @@ pub fn extract_arg_val(
_ => ArgValue::Other,
}
}
pub fn unwrap_seqs_and_parens(expr: &Expr) -> &Expr {
match expr {
Expr::Seq(SeqExpr { exprs, .. }) => unwrap_seqs_and_parens(exprs.last().unwrap()),
Expr::Paren(ParenExpr { expr, .. }) => unwrap_seqs_and_parens(expr.as_ref()),
_ => expr,
}
}