use super::Context;
use super::LintRule;
use crate::swc_util::StringRepr;
use swc_common::Span;
use swc_ecmascript::ast::CallExpr;
use swc_ecmascript::ast::Expr;
use swc_ecmascript::ast::ExprOrSuper;
use swc_ecmascript::ast::ParenExpr;
use swc_ecmascript::ast::VarDeclarator;
use swc_ecmascript::visit::noop_visit_type;
use swc_ecmascript::visit::Node;
use swc_ecmascript::visit::Visit;
pub struct NoEval;
const CODE: &str = "no-eval";
const MESSAGE: &str = "`eval` call is not allowed";
const HINT: &str = "Remove the use of `eval`";
impl LintRule for NoEval {
fn new() -> Box<Self> {
Box::new(NoEval)
}
fn code(&self) -> &'static str {
CODE
}
fn lint_program(
&self,
context: &mut Context,
program: &swc_ecmascript::ast::Program,
) {
let mut visitor = NoEvalVisitor::new(context);
visitor.visit_program(program, program);
}
fn docs(&self) -> &'static str {
r#"Disallows the use of `eval`
`eval` is a potentially dangerous function which can open your code to a number
of security vulnerabilities. In addition to being slow, `eval` is also often
unnecessary with better solutions available.
### Invalid:
```typescript
const obj = { x: "foo" };
const key = "x",
const value = eval("obj." + key);
```
### Valid:
```typescript
const obj = { x: "foo" };
const value = obj[x];
```
"#
}
}
struct NoEvalVisitor<'c> {
context: &'c mut Context,
}
impl<'c> NoEvalVisitor<'c> {
fn new(context: &'c mut Context) -> Self {
Self { context }
}
fn maybe_add_diagnostic(&mut self, source: &dyn StringRepr, span: Span) {
if source.string_repr().as_deref() == Some("eval") {
self.add_diagnostic(span);
}
}
fn add_diagnostic(&mut self, span: Span) {
self
.context
.add_diagnostic_with_hint(span, CODE, MESSAGE, HINT);
}
fn handle_paren_callee(&mut self, p: &ParenExpr) {
match p.expr.as_ref() {
Expr::Paren(paren) => self.handle_paren_callee(paren),
Expr::Ident(ident) => self.maybe_add_diagnostic(ident, ident.span),
Expr::Seq(seq) => {
for expr in &seq.exprs {
if let Expr::Ident(ident) = expr.as_ref() {
self.maybe_add_diagnostic(ident, ident.span)
}
}
}
_ => {}
}
}
}
impl<'c> Visit for NoEvalVisitor<'c> {
noop_visit_type!();
fn visit_var_declarator(&mut self, v: &VarDeclarator, _: &dyn Node) {
if let Some(expr) = &v.init {
if let Expr::Ident(ident) = expr.as_ref() {
self.maybe_add_diagnostic(ident, v.span);
}
}
}
fn visit_call_expr(&mut self, call_expr: &CallExpr, _parent: &dyn Node) {
if let ExprOrSuper::Expr(expr) = &call_expr.callee {
match expr.as_ref() {
Expr::Ident(ident) => self.maybe_add_diagnostic(ident, call_expr.span),
Expr::Paren(paren) => self.handle_paren_callee(paren),
_ => {}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_eval_valid() {
assert_lint_ok! {
NoEval,
"foo.eval('bar');",
}
}
#[test]
fn no_eval_invalid() {
assert_lint_err! {
NoEval,
"eval('123');": [{col: 0, message: MESSAGE, hint: HINT}],
"(0, eval)('var a = 0');": [{col: 4, message: MESSAGE, hint: HINT}],
"((eval))('var a = 0');": [{col: 2, message: MESSAGE, hint: HINT}],
"var foo = eval;": [{col: 4, message: MESSAGE, hint: HINT}],
}
}
}