feat(es/lints): Add no-prototype-builtins rule (#8684)

This commit is contained in:
Artur 2024-03-11 07:17:40 +03:00 committed by GitHub
parent 8c024895f9
commit a5dbb17612
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 419 additions and 0 deletions

View File

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

View File

@ -0,0 +1,56 @@
foo.hasOwnProperty('bar');
foo.isPrototypeOf('bar');
foo.propertyIsEnumerable('bar');
foo.bar.hasOwnProperty('bar');
foo.bar.baz.isPrototypeOf('bar');
bar?.foo?.hasOwnProperty('bar');
foo?.bar.hasOwnProperty('baz');
foo.hasOwnProperty?.('bar');
foo?.hasOwnProperty('bar').baz;
foo.hasOwnProperty('bar')?.baz;
(a,b).hasOwnProperty('bar');
(foo?.hasOwnProperty)('bar');
(((foo?.hasOwnProperty)))('dlya-tex-kto-dumaet-cho-on-samiy-umniy');
(foo?.anotherProp, foo?.hasOwnProperty)('bar');
(foo.hasOwnProperty('ok'), foo?.hasOwnProperty)('bar');
foo['hasOwnProperty']('bar');
foo[`isPrototypeOf`]('bar').baz;
// valid
Object.prototype.hasOwnProperty.call(foo, 'bar');
Object.prototype.isPrototypeOf.call(foo, 'bar');
Object.prototype.propertyIsEnumerable.call(foo, 'bar');
Object.prototype.hasOwnProperty.apply(foo, ['bar']);
Object.prototype.isPrototypeOf.apply(foo, ['bar']);
Object.prototype.propertyIsEnumerable.apply(foo, ['bar']);
foo.hasOwnProperty;
foo.hasOwnProperty.bar();
foo(hasOwnProperty);
hasOwnProperty(foo, 'bar');
isPrototypeOf(foo, 'bar');
propertyIsEnumerable(foo, 'bar');
({}.hasOwnProperty.call(foo, 'bar'));
({}.isPrototypeOf.call(foo, 'bar'));
({}.propertyIsEnumerable.call(foo, 'bar'));
({}.hasOwnProperty.apply(foo, ['bar']));
({}.isPrototypeOf.apply(foo, ['bar']));
({}.propertyIsEnumerable.apply(foo, ['bar']));
foo[hasOwnProperty]('bar');
foo['HasOwnProperty']('bar');

View File

@ -0,0 +1,194 @@
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[1:1]
1 | foo.hasOwnProperty('bar');
: ^^^^^^^^^^^^^^
2 |
3 | foo.isPrototypeOf('bar');
`----
x Do not access Object.prototype method 'isPrototypeOf' from target object
,-[1:1]
1 | foo.hasOwnProperty('bar');
2 |
3 | foo.isPrototypeOf('bar');
: ^^^^^^^^^^^^^
4 |
5 | foo.propertyIsEnumerable('bar');
`----
x Do not access Object.prototype method 'propertyIsEnumerable' from target object
,-[2:1]
2 |
3 | foo.isPrototypeOf('bar');
4 |
5 | foo.propertyIsEnumerable('bar');
: ^^^^^^^^^^^^^^^^^^^^
6 |
7 | foo.bar.hasOwnProperty('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[4:1]
4 |
5 | foo.propertyIsEnumerable('bar');
6 |
7 | foo.bar.hasOwnProperty('bar');
: ^^^^^^^^^^^^^^
8 |
9 | foo.bar.baz.isPrototypeOf('bar');
`----
x Do not access Object.prototype method 'isPrototypeOf' from target object
,-[6:1]
6 |
7 | foo.bar.hasOwnProperty('bar');
8 |
9 | foo.bar.baz.isPrototypeOf('bar');
: ^^^^^^^^^^^^^
10 |
11 | bar?.foo?.hasOwnProperty('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[8:1]
8 |
9 | foo.bar.baz.isPrototypeOf('bar');
10 |
11 | bar?.foo?.hasOwnProperty('bar');
: ^^^^^^^^^^^^^^
12 |
13 | foo?.bar.hasOwnProperty('baz');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[10:1]
10 |
11 | bar?.foo?.hasOwnProperty('bar');
12 |
13 | foo?.bar.hasOwnProperty('baz');
: ^^^^^^^^^^^^^^
14 |
15 | foo.hasOwnProperty?.('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[12:1]
12 |
13 | foo?.bar.hasOwnProperty('baz');
14 |
15 | foo.hasOwnProperty?.('bar');
: ^^^^^^^^^^^^^^
16 |
17 | foo?.hasOwnProperty('bar').baz;
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[14:1]
14 |
15 | foo.hasOwnProperty?.('bar');
16 |
17 | foo?.hasOwnProperty('bar').baz;
: ^^^^^^^^^^^^^^
18 |
19 | foo.hasOwnProperty('bar')?.baz;
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[16:1]
16 |
17 | foo?.hasOwnProperty('bar').baz;
18 |
19 | foo.hasOwnProperty('bar')?.baz;
: ^^^^^^^^^^^^^^
20 |
21 | (a,b).hasOwnProperty('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[18:1]
18 |
19 | foo.hasOwnProperty('bar')?.baz;
20 |
21 | (a,b).hasOwnProperty('bar');
: ^^^^^^^^^^^^^^
22 |
23 | (foo?.hasOwnProperty)('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[20:1]
20 |
21 | (a,b).hasOwnProperty('bar');
22 |
23 | (foo?.hasOwnProperty)('bar');
: ^^^^^^^^^^^^^^
24 |
25 | (((foo?.hasOwnProperty)))('dlya-tex-kto-dumaet-cho-on-samiy-umniy');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[22:1]
22 |
23 | (foo?.hasOwnProperty)('bar');
24 |
25 | (((foo?.hasOwnProperty)))('dlya-tex-kto-dumaet-cho-on-samiy-umniy');
: ^^^^^^^^^^^^^^
26 |
27 | (foo?.anotherProp, foo?.hasOwnProperty)('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[24:1]
24 |
25 | (((foo?.hasOwnProperty)))('dlya-tex-kto-dumaet-cho-on-samiy-umniy');
26 |
27 | (foo?.anotherProp, foo?.hasOwnProperty)('bar');
: ^^^^^^^^^^^^^^
28 |
29 | (foo.hasOwnProperty('ok'), foo?.hasOwnProperty)('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[26:1]
26 |
27 | (foo?.anotherProp, foo?.hasOwnProperty)('bar');
28 |
29 | (foo.hasOwnProperty('ok'), foo?.hasOwnProperty)('bar');
: ^^^^^^^^^^^^^^
30 |
31 | foo['hasOwnProperty']('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[26:1]
26 |
27 | (foo?.anotherProp, foo?.hasOwnProperty)('bar');
28 |
29 | (foo.hasOwnProperty('ok'), foo?.hasOwnProperty)('bar');
: ^^^^^^^^^^^^^^
30 |
31 | foo['hasOwnProperty']('bar');
`----
x Do not access Object.prototype method 'hasOwnProperty' from target object
,-[28:1]
28 |
29 | (foo.hasOwnProperty('ok'), foo?.hasOwnProperty)('bar');
30 |
31 | foo['hasOwnProperty']('bar');
: ^^^^^^^^^^^^^^^^
32 |
33 | foo[`isPrototypeOf`]('bar').baz;
`----
x Do not access Object.prototype method 'isPrototypeOf' from target object
,-[30:1]
30 |
31 | foo['hasOwnProperty']('bar');
32 |
33 | foo[`isPrototypeOf`]('bar').baz;
: ^^^^^^^^^^^^^^^
34 |
35 | // valid
`----

View File

@ -217,6 +217,8 @@ pub struct LintConfig {
pub no_cond_assign: RuleConfig<()>, pub no_cond_assign: RuleConfig<()>,
#[cfg(feature = "non_critical_lints")] #[cfg(feature = "non_critical_lints")]
#[serde(default, alias = "noPrototypeBuiltins")]
pub no_prototype_builtins: RuleConfig<()>,
#[serde(default, alias = "noNewObject")] #[serde(default, alias = "noNewObject")]
pub no_new_object: RuleConfig<()>, pub no_new_object: RuleConfig<()>,
} }

View File

@ -35,6 +35,7 @@ pub(crate) mod non_critical_lints {
pub mod no_new_symbol; pub mod no_new_symbol;
pub mod no_obj_calls; pub mod no_obj_calls;
pub mod no_param_reassign; pub mod no_param_reassign;
pub mod no_prototype_builtins;
pub mod no_restricted_syntax; pub mod no_restricted_syntax;
pub mod no_sparse_arrays; pub mod no_sparse_arrays;
pub mod no_throw_literal; pub mod no_throw_literal;
@ -196,6 +197,10 @@ pub fn all(lint_params: LintParams) -> Vec<Box<dyn Rule>> {
rules.extend(no_cond_assign::no_cond_assign(&lint_config.no_cond_assign)); rules.extend(no_cond_assign::no_cond_assign(&lint_config.no_cond_assign));
rules.extend(no_prototype_builtins::no_prototype_builtins(
&lint_config.no_prototype_builtins,
));
rules.extend(no_new_object::no_new_object( rules.extend(no_new_object::no_new_object(
unresolved_ctxt, unresolved_ctxt,
&lint_config.no_new_object, &lint_config.no_new_object,

View File

@ -0,0 +1,153 @@
use swc_atoms::Atom;
use swc_common::{errors::HANDLER, Span};
use swc_ecma_ast::*;
use swc_ecma_utils::ExprExt;
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use crate::{
config::{LintRuleReaction, RuleConfig},
rule::{visitor_rule, Rule},
};
const METHODS: [&str; 3] = ["hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"];
pub fn no_prototype_builtins(config: &RuleConfig<()>) -> Option<Box<dyn Rule>> {
let rule_reaction = config.get_rule_reaction();
match rule_reaction {
LintRuleReaction::Off => None,
_ => Some(visitor_rule(NoPrototypeBuiltins::new(rule_reaction))),
}
}
#[derive(Debug, Default)]
struct CallInfo {
chain: Vec<Atom>,
method_span: Option<Span>,
}
#[derive(Debug, Default)]
struct NoPrototypeBuiltins {
expected_reaction: LintRuleReaction,
call_info: CallInfo,
}
impl NoPrototypeBuiltins {
fn new(expected_reaction: LintRuleReaction) -> Self {
Self {
expected_reaction,
call_info: Default::default(),
}
}
fn emit_error(&self, span: Span, method: &str) {
let message = format!(
"Do not access Object.prototype method '{}' from target object",
method
);
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 extend_chain(&mut self, span: Span, atom: Atom) {
if self.call_info.method_span.is_none() {
self.call_info.method_span = Some(span);
}
self.call_info.chain.push(atom);
}
fn extract_path(&mut self, expr: &Expr) {
match expr {
Expr::Member(member) => {
match &member.prop {
MemberProp::Ident(ident) => {
self.extend_chain(ident.span, ident.sym.clone());
}
MemberProp::Computed(computed_prop_name) => {
match computed_prop_name.expr.as_ref() {
Expr::Lit(_) | Expr::Tpl(_) | Expr::Paren(_) | Expr::Seq(_) => {
self.extract_path(&computed_prop_name.expr);
}
_ => {}
}
}
_ => {}
}
self.extract_path(member.obj.as_ref());
}
Expr::OptChain(OptChainExpr { base, .. }) => {
if let Some(member_expr) = base.as_member() {
if let Some(ident) = member_expr.prop.as_ident() {
self.extend_chain(ident.span, ident.sym.clone());
}
self.extract_path(member_expr.obj.as_ref());
}
}
Expr::Paren(ParenExpr { expr, .. }) => {
self.extract_path(expr.as_ref());
}
Expr::Seq(SeqExpr { exprs, .. }) => {
self.extract_path(exprs.last().unwrap().as_ref());
}
Expr::Lit(Lit::Str(lit_str)) => {
self.extend_chain(lit_str.span, lit_str.value.clone());
}
Expr::Tpl(tpl) => {
if tpl.exprs.is_empty() && tpl.quasis.len() == 1 {
self.extend_chain(tpl.span, tpl.quasis[0].raw.clone());
}
}
Expr::Ident(ident) => {
self.extend_chain(ident.span, ident.sym.clone());
}
_ => {}
}
}
fn check(&mut self, expr: &Expr) {
let prev_call_info = std::mem::take(&mut self.call_info);
self.extract_path(expr);
if self.call_info.chain.len() > 1 {
let method_name = self.call_info.chain[0].as_str();
if METHODS.contains(&method_name) {
self.emit_error(self.call_info.method_span.unwrap(), method_name);
}
}
self.call_info = prev_call_info;
}
}
impl Visit for NoPrototypeBuiltins {
noop_visit_type!();
fn visit_opt_chain_base(&mut self, opt_chain_base: &OptChainBase) {
if let OptChainBase::Call(opt_call) = opt_chain_base {
self.check(opt_call.callee.as_expr());
}
opt_chain_base.visit_children_with(self);
}
fn visit_call_expr(&mut self, call_expr: &CallExpr) {
if let Some(expr) = call_expr.callee.as_expr() {
self.check(expr.as_ref());
}
call_expr.visit_children_with(self);
}
}