mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): improve eslint(no-eval) compat (#419)
This commit is contained in:
parent
3263f2b654
commit
9df1545602
3 changed files with 311 additions and 31 deletions
|
|
@ -446,6 +446,26 @@ impl<'a> MemberExpression<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn static_property_info(&'a self) -> Option<(Span, &'a str)> {
|
||||
match self {
|
||||
MemberExpression::ComputedMemberExpression(expr) => match &expr.expression {
|
||||
Expression::StringLiteral(lit) => Some((lit.span, &lit.value)),
|
||||
Expression::TemplateLiteral(lit) => {
|
||||
if lit.expressions.is_empty() && lit.quasis.len() == 1 {
|
||||
Some((lit.span, &lit.quasis[0].value.raw))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
MemberExpression::StaticMemberExpression(expr) => {
|
||||
Some((expr.property.span, &expr.property.name))
|
||||
}
|
||||
MemberExpression::PrivateFieldExpression(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether it is a static member access `object.property`
|
||||
pub fn is_specific_member_access(&'a self, object: &str, property: &str) -> bool {
|
||||
self.object().is_specific_id(object)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use oxc_ast::AstKind;
|
||||
// Ported from https://github.com/eslint/eslint/tree/main/lib/rules/no-eval.js
|
||||
|
||||
use oxc_ast::{ast::Expression, AstKind};
|
||||
use oxc_diagnostics::{
|
||||
miette::{self, Diagnostic},
|
||||
thiserror::Error,
|
||||
|
|
@ -12,10 +14,26 @@ use crate::{context::LintContext, rule::Rule};
|
|||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint(no-eval): eval can be harmful.")]
|
||||
#[diagnostic(severity(warning))]
|
||||
struct NoEvalDiagnostic(#[label("eval can be harmful")] pub Span);
|
||||
struct NoEvalDiagnostic(#[label] pub Span);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NoEval;
|
||||
pub struct NoEval {
|
||||
/// Whether to allow references to the `eval` function as long as they are
|
||||
/// not called. For example, the following code is valid if this property is
|
||||
/// true:
|
||||
///
|
||||
/// ```javascript
|
||||
/// const foo = eval;
|
||||
/// foo();
|
||||
///
|
||||
/// (function(exec) {
|
||||
/// exec();
|
||||
/// })(eval);
|
||||
/// ```
|
||||
///
|
||||
/// The default value is `false`.
|
||||
pub allow_indirect: bool,
|
||||
}
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
|
|
@ -35,12 +53,66 @@ declare_oxc_lint!(
|
|||
);
|
||||
|
||||
impl Rule for NoEval {
|
||||
fn from_configuration(value: serde_json::Value) -> Self {
|
||||
let allow_indirect = value.get(0).map_or(false, |config| {
|
||||
config.get("allowIndirect").and_then(serde_json::Value::as_bool).unwrap_or(false)
|
||||
});
|
||||
|
||||
Self { allow_indirect }
|
||||
}
|
||||
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::IdentifierReference(ident) = node.get().kind()
|
||||
&& ident.name == "eval"
|
||||
{
|
||||
let kind = node.get().kind();
|
||||
|
||||
if let AstKind::IdentifierReference(ident) = kind {
|
||||
if ident.name == "eval" {
|
||||
ctx.diagnostic(NoEvalDiagnostic(ident.span));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let AstKind::MemberExpression(data) = kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((eval_span, "eval")) = data.static_property_info() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut object = Some(data.object().get_inner_expression());
|
||||
|
||||
loop {
|
||||
let (new_object, name) = match object {
|
||||
Some(Expression::MemberExpression(member)) => {
|
||||
(Some(member.object().get_inner_expression()), member.static_property_name())
|
||||
}
|
||||
Some(Expression::Identifier(ident)) => (None, Some(ident.name.as_str())),
|
||||
Some(Expression::ThisExpression(_)) => (None, Some("this")),
|
||||
None => break,
|
||||
_ => return,
|
||||
};
|
||||
object = new_object;
|
||||
|
||||
match name {
|
||||
Some("this") => {
|
||||
let scope = ctx.scope(node);
|
||||
|
||||
if scope.is_get_accessor()
|
||||
|| scope.is_set_accessor()
|
||||
|| scope.is_static_block()
|
||||
|| scope.is_constructor()
|
||||
|| (scope.is_function() && scope.strict_mode())
|
||||
|| (scope.is_top() && ctx.source_type().is_module())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some("window" | "global" | "globalThis") => {}
|
||||
_ => return,
|
||||
};
|
||||
}
|
||||
|
||||
ctx.diagnostic(NoEvalDiagnostic(eval_span));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,18 +121,121 @@ fn test() {
|
|||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec }", None),
|
||||
// ("global.eval('foo')", None),
|
||||
// ("global.eval('foo')", None),
|
||||
("global.noeval('foo')", None),
|
||||
// ("function foo() { var eval = 'foo'; global[eval]('foo') }", None),
|
||||
// ("globalThis.eval('foo')", None),
|
||||
// ("globalThis.eval('foo')", None),
|
||||
// ("globalThis.eval('foo')", None),
|
||||
("globalThis.noneval('foo')", None),
|
||||
// ("function foo() { var eval = 'foo'; globalThis[eval]('foo') }", None),
|
||||
("this.noeval('foo');", None),
|
||||
("function foo() { 'use strict'; this.eval('foo'); }", None),
|
||||
("'use strict'; this.eval('foo');", None),
|
||||
("this.eval('foo');", None),
|
||||
("function foo() { this.eval('foo'); }", None),
|
||||
("function foo() { this.eval('foo'); }", None),
|
||||
("var obj = {foo: function() { this.eval('foo'); }}", None),
|
||||
("var obj = {}; obj.foo = function() { this.eval('foo'); }", None),
|
||||
("() => { this.eval('foo') }", None),
|
||||
("function f() { 'use strict'; () => { this.eval('foo') } }", None),
|
||||
("(function f() { 'use strict'; () => { this.eval('foo') } })", None),
|
||||
("class A { foo() { this.eval(); } }", None),
|
||||
("class A { static foo() { this.eval(); } }", None),
|
||||
("class A { field = this.eval(); }", None),
|
||||
("class A { field = () => this.eval(); }", None),
|
||||
("class A { static { this.eval(); } }", None),
|
||||
// ("(0, eval)('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("(0, window.eval)('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("(0, window['eval'])('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("var EVAL = eval; EVAL('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("var EVAL = this.eval; EVAL('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// (
|
||||
// "(function(exe){ exe('foo') })(eval);",
|
||||
// Some(serde_json::json!([{ "allowIndirect": true }])),
|
||||
// ),
|
||||
// ("window.eval('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("window.window.eval('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("window.window['eval']('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("global.eval('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("global.global.eval('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("this.eval('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// (
|
||||
// "function foo() { this.eval('foo') }",
|
||||
// Some(serde_json::json!([{ "allowIndirect": true }])),
|
||||
// ),
|
||||
// ("(0, globalThis.eval)('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("(0, globalThis['eval'])('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// (
|
||||
// "var EVAL = globalThis.eval; EVAL('foo')",
|
||||
// Some(serde_json::json!([{ "allowIndirect": true }])),
|
||||
// ),
|
||||
// (
|
||||
// "function foo() { globalThis.eval('foo') }",
|
||||
// Some(serde_json::json!([{ "allowIndirect": true }])),
|
||||
// ),
|
||||
// (
|
||||
// "globalThis.globalThis.eval('foo');",
|
||||
// Some(serde_json::json!([{ "allowIndirect": true }])),
|
||||
// ),
|
||||
// ("eval?.('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("window?.eval('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("(window?.eval)('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
("eval();", None),
|
||||
("eval('...');", None),
|
||||
("eval('...');", None),
|
||||
("let a = eval;", None),
|
||||
("const foo = { asdf: eval };", None),
|
||||
("eval(foo)", None),
|
||||
("eval('foo')", None),
|
||||
("function foo(eval) { eval('foo') }", None),
|
||||
// ("eval(foo)", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// ("eval('foo')", Some(serde_json::json!([{ "allowIndirect": true }]))),
|
||||
// (
|
||||
// "function foo(eval) { eval('foo') }",
|
||||
// Some(serde_json::json!([{ "allowIndirect": true }])),
|
||||
// ),
|
||||
("(0, eval)('foo')", None),
|
||||
("(0, window.eval)('foo')", None),
|
||||
("(0, window['eval'])('foo')", None),
|
||||
// ("var EVAL = eval; EVAL('foo')", None),
|
||||
// ("var EVAL = this.eval; EVAL('foo')", None),
|
||||
// ("'use strict'; var EVAL = this.eval; EVAL('foo')", None),
|
||||
// ("() => { this.eval('foo'); }", None),
|
||||
// ("() => { 'use strict'; this.eval('foo'); }", None),
|
||||
// ("'use strict'; () => { this.eval('foo'); }", None),
|
||||
// ("() => { 'use strict'; () => { this.eval('foo'); } }", None),
|
||||
// ("(function(exe){ exe('foo') })(eval);", None),
|
||||
("window.eval('foo')", None),
|
||||
("window.window.eval('foo')", None),
|
||||
("window.window['eval']('foo')", None),
|
||||
("global.eval('foo')", None),
|
||||
("global.global.eval('foo')", None),
|
||||
("global.global[`eval`]('foo')", None),
|
||||
// ("this.eval('foo')", None),
|
||||
// ("'use strict'; this.eval('foo')", None),
|
||||
// ("function foo() { this.eval('foo') }", None),
|
||||
("var EVAL = globalThis.eval; EVAL('foo')", None),
|
||||
("globalThis.eval('foo')", None),
|
||||
("globalThis.globalThis.eval('foo')", None),
|
||||
("globalThis.globalThis['eval']('foo')", None),
|
||||
("(0, globalThis.eval)('foo')", None),
|
||||
("(0, globalThis['eval'])('foo')", None),
|
||||
("window?.eval('foo')", None),
|
||||
("(window?.eval)('foo')", None),
|
||||
// ("(window?.window).eval('foo')", None),
|
||||
// ("class C { [this.eval('foo')] }", None),
|
||||
// ("'use strict'; class C { [this.eval('foo')] }", None),
|
||||
// ("class A { static {} [this.eval()]; }", None),
|
||||
// ("function foo() { 'use strict'; this.eval(); }", None),
|
||||
];
|
||||
|
||||
Tester::new(NoEval::NAME, pass, fail).test_and_snapshot();
|
||||
|
|
|
|||
|
|
@ -4,37 +4,122 @@ expression: no_eval
|
|||
---
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ eval();
|
||||
· ──┬─
|
||||
· ╰── eval can be harmful
|
||||
1 │ eval(foo)
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ eval('...');
|
||||
· ──┬─
|
||||
· ╰── eval can be harmful
|
||||
1 │ eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ eval('...');
|
||||
· ──┬─
|
||||
· ╰── eval can be harmful
|
||||
1 │ function foo(eval) { eval('foo') }
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ let a = eval;
|
||||
· ──┬─
|
||||
· ╰── eval can be harmful
|
||||
1 │ (0, eval)('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ const foo = { asdf: eval };
|
||||
· ──┬─
|
||||
· ╰── eval can be harmful
|
||||
1 │ (0, window.eval)('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ (0, window['eval'])('foo')
|
||||
· ──────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ window.eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ window.window.eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ window.window['eval']('foo')
|
||||
· ──────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ global.eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ global.global.eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ global.global[`eval`]('foo')
|
||||
· ──────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ var EVAL = globalThis.eval; EVAL('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ globalThis.eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ globalThis.globalThis.eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ globalThis.globalThis['eval']('foo')
|
||||
· ──────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ (0, globalThis.eval)('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ (0, globalThis['eval'])('foo')
|
||||
· ──────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ window?.eval('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-eval): eval can be harmful.
|
||||
╭─[no_eval.tsx:1:1]
|
||||
1 │ (window?.eval)('foo')
|
||||
· ────
|
||||
╰────
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue