diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 8c217ea98088b..49e5088fbd97c 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -61,6 +61,7 @@ mod eslint { pub mod no_setter_return; pub mod no_shadow_restricted_names; pub mod no_sparse_arrays; + pub mod no_undef; pub mod no_unsafe_finally; pub mod no_unsafe_negation; pub mod no_unsafe_optional_chaining; @@ -144,6 +145,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_setter_return, eslint::no_shadow_restricted_names, eslint::no_sparse_arrays, + eslint::no_undef, eslint::no_unsafe_finally, eslint::no_unsafe_negation, eslint::no_unsafe_optional_chaining, diff --git a/crates/oxc_linter/src/rules/eslint/no_undef.rs b/crates/oxc_linter/src/rules/eslint/no_undef.rs new file mode 100644 index 0000000000000..03d0015c914c7 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_undef.rs @@ -0,0 +1,189 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{Atom, Span}; +use oxc_syntax::operator::UnaryOperator; + +use crate::{context::LintContext, globals::BUILTINS, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(no-undef): Disallow the use of undeclared variables")] +#[diagnostic(severity(warning), help("'{0}' is not defined."))] +struct NoUndefDiagnostic(Atom, #[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoUndef { + #[allow(dead_code)] + type_of: bool, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow the use of undeclared variables. + /// + /// ### Why is this bad? + /// + /// It is most likely a potential ReferenceError caused by a misspelling of a variable or parameter name. + /// + /// ### Example + /// ```javascript + /// var foo = someFunction(); + /// var bar = a + 1; + /// ``` + NoUndef, + correctness +); + +impl Rule for NoUndef { + fn from_configuration(value: serde_json::Value) -> Self { + let type_of = value + .get(0) + .and_then(|config| config.get("typeof")) + .and_then(serde_json::Value::as_bool) + .unwrap_or_default(); + Self { type_of } + } + fn run_once(&self, ctx: &LintContext) { + let symbol_table = ctx.symbols(); + + for reference_id_list in ctx.scopes().root_unresolved_references().values() { + for &reference_id in reference_id_list { + let reference = symbol_table.get_reference(reference_id); + if BUILTINS.contains_key(reference.name().as_str()) { + return; + } + + let node = ctx.nodes().get_node(reference.node_id()); + if !self.type_of && has_typeof_operator(node, ctx) { + return; + } + + ctx.diagnostic(NoUndefDiagnostic(reference.name().clone(), reference.span())); + } + } + } +} + +fn has_typeof_operator(node: &AstNode<'_>, ctx: &LintContext<'_>) -> bool { + ctx.nodes().parent_node(node.id()).map_or(false, |parent| match parent.kind() { + AstKind::UnaryExpression(expr) => expr.operator == UnaryOperator::Typeof, + AstKind::ParenthesizedExpression(_) => has_typeof_operator(parent, ctx), + _ => false, + }) +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var a = 1, b = 2; a;", None), + // ("/*global b*/ function f() { b; }", None), + // { code: "function f() { b; }", globals: { b: false } }, + // ("/*global b a:false*/ a; function f() { b; a; }", None), + ("function a(){} a();", None), + ("function f(b) { b; }", None), + ("var a; a = 1; a++;", None), + ("var a; function f() { a = 1; }", None), + // ("/*global b:true*/ b++;", None), + // ("/*eslint-env browser*/ window;", None), + // ("/*eslint-env node*/ require(\"a\");", None), + ("Object; isNaN();", None), + ("toString()", None), + ("hasOwnProperty()", None), + ("function evilEval(stuffToEval) { var ultimateAnswer; ultimateAnswer = 42; eval(stuffToEval); }", None), + ("typeof a", None), + ("typeof (a)", None), + ("var b = typeof a", None), + ("typeof a === 'undefined'", None), + ("if (typeof a === 'undefined') {}", None), + ("function foo() { var [a, b=4] = [1, 2]; return {a, b}; }", None), + ("var toString = 1;", None), + ("function myFunc(...foo) { return foo;}", None), + ("var React, App, a=1; React.render();", None), + ("var console; [1,2,3].forEach(obj => {\n console.log(obj);\n});", None), + ("var Foo; class Bar extends Foo { constructor() { super(); }}", None), + ("import Warning from '../lib/warning'; var warn = new Warning('text');", None), + ("import * as Warning from '../lib/warning'; var warn = new Warning('text');", None), + ("var a; [a] = [0];", None), + ("var a; ({a} = {});", None), + ("var a; ({b: a} = {});", None), + ("var obj; [obj.a, obj.b] = [0, 1];", None), + // ("URLSearchParams;", None), + // ("Intl;", None), + // ("IntersectionObserver;", None), + // ("Credential;", None), + // ("requestIdleCallback;", None), + // ("customElements;", None), + // ("PromiseRejectionEvent;", None), + ("(foo, bar) => { foo ||= WeakRef; bar ??= FinalizationRegistry; }", None), + // ("/*global b:false*/ function f() { b = 1; }", None), + // { code: "function f() { b = 1; }", globals: { b: false } }, + // ("/*global b:false*/ function f() { b++; }", None), + // ("/*global b*/ b = 1;", None), + // ("/*global b:false*/ var b = 1;", None), + ("Array = 1;", None), + ("class A { constructor() { new.target; } }", None), + // { + // code: "var {bacon, ...others} = stuff; foo(others)", + // parserOptions: { + // ecmaVersion: 2018 + // }, + // globals: { stuff: false, foo: false } + // }, + ("export * as ns from \"source\"", None), + ("import.meta", None), + ("let a; class C { static {} } a;", None), + ("var a; class C { static {} } a;", None), + ("a; class C { static {} } var a;", None), + ("class C { static { C; } }", None), + ("const C = class { static { C; } }", None), + ("class C { static { a; } } var a;", None), + ("class C { static { a; } } let a;", None), + ("class C { static { var a; a; } }", None), + ("class C { static { a; var a; } }", None), + ("class C { static { a; { var a; } } }", None), + ("class C { static { let a; a; } }", None), + ("class C { static { a; let a; } }", None), + ("class C { static { function a() {} a; } }", None), + ("class C { static { a; function a() {} } }", None) + ]; + + let fail = vec![ + ("a = 1;", None), + ( + "if (typeof anUndefinedVar === 'string') {}", + Some(serde_json::json!([{ "typeof": true }])), + ), + ("var a = b;", None), + ("function f() { b; }", None), + ("window;", None), + ("require(\"a\");", None), + ("var React; React.render();", None), + ("var React, App; React.render();", None), + ("[a] = [0];", None), + ("({a} = {});", None), + ("({b: a} = {});", None), + ("[obj.a, obj.b] = [0, 1];", None), + ("const c = 0; const a = {...b, c};", None), + ("class C { static { a; } }", None), + ("class C { static { { let a; } a; } }", None), + ("class C { static { { function a() {} } a; } }", None), + ("class C { static { function foo() { var a; } a; } }", None), + ("class C { static { var a; } static { a; } }", None), + ("class C { static { let a; } static { a; } }", None), + ("class C { static { function a(){} } static { a; } }", None), + ("class C { static { var a; } foo() { a; } }", None), + ("class C { static { let a; } foo() { a; } }", None), + ("class C { static { var a; } [a]; }", None), + ("class C { static { let a; } [a]; }", None), + ("class C { static { function a() {} } [a]; }", None), + ("class C { static { var a; } } a;", None), + ]; + + Tester::new(NoUndef::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_undef.snap b/crates/oxc_linter/src/snapshots/no_undef.snap new file mode 100644 index 0000000000000..3d7f9df9a1403 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_undef.snap @@ -0,0 +1,194 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_undef +--- + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ a = 1; + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ if (typeof anUndefinedVar === 'string') {} + · ────────────── + ╰──── + help: 'anUndefinedVar' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ var a = b; + · ─ + ╰──── + help: 'b' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ function f() { b; } + · ─ + ╰──── + help: 'b' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ window; + · ────── + ╰──── + help: 'window' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ require("a"); + · ─────── + ╰──── + help: 'require' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ var React; React.render(); + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ var React, App; React.render(); + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ [a] = [0]; + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ ({a} = {}); + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ ({b: a} = {}); + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ [obj.a, obj.b] = [0, 1]; + · ─── + ╰──── + help: 'obj' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ [obj.a, obj.b] = [0, 1]; + · ─── + ╰──── + help: 'obj' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ const c = 0; const a = {...b, c}; + · ─ + ╰──── + help: 'b' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { { let a; } a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { { function a() {} } a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { function foo() { var a; } a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { var a; } static { a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { let a; } static { a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { function a(){} } static { a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { var a; } foo() { a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { let a; } foo() { a; } } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { var a; } [a]; } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { let a; } [a]; } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { function a() {} } [a]; } + · ─ + ╰──── + help: 'a' is not defined. + + ⚠ eslint(no-undef): Disallow the use of undeclared variables + ╭─[no_undef.tsx:1:1] + 1 │ class C { static { var a; } } a; + · ─ + ╰──── + help: 'a' is not defined. + +