feat(linter): support eslint globals (#3038)

closes #3019
This commit is contained in:
Boshen 2024-04-20 23:01:59 +08:00 committed by GitHub
parent 53c0ff5135
commit ae1f15ac1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 183 additions and 140 deletions

View file

@ -1,5 +1,8 @@
{
"rules": {
"no-undef": "warn"
},
"globals": {
"foo": "readonly"
}
}

View file

@ -1 +1 @@
console.log()
console.log(foo)

View file

@ -0,0 +1,21 @@
use serde::Deserialize;
use rustc_hash::FxHashMap;
/// <https://eslint.org/docs/v8.x/use/configure/language-options#using-configuration-files-1>
#[derive(Debug, Default, Deserialize)]
pub struct ESLintGlobals(FxHashMap<String, GlobalValue>);
#[derive(Debug, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GlobalValue {
Readonly,
Writeable,
Off,
}
impl ESLintGlobals {
pub fn is_enabled(&self, name: &str) -> bool {
self.0.get(name).is_some_and(|value| *value != GlobalValue::Off)
}
}

View file

@ -1,5 +1,6 @@
mod env;
pub mod errors;
mod globals;
mod rules;
mod settings;
@ -15,16 +16,19 @@ use self::errors::{
FailedToParseConfigError, FailedToParseConfigJsonError, FailedToParseConfigPropertyError,
FailedToParseJsonc,
};
pub use self::{env::ESLintEnv, rules::ESLintRules, settings::ESLintSettings};
pub use self::{
env::ESLintEnv, globals::ESLintGlobals, rules::ESLintRules, settings::ESLintSettings,
};
/// ESLint Config
/// <https://eslint.org/docs/latest/use/configure/configuration-files-new#configuration-objects>
/// <https://eslint.org/docs/v8.x/use/configure/configuration-files>
#[derive(Debug, Default, Deserialize)]
#[serde(default)]
pub struct ESLintConfig {
pub(crate) rules: ESLintRules,
pub(crate) settings: ESLintSettings,
pub(crate) env: ESLintEnv,
pub(crate) globals: ESLintGlobals,
}
impl ESLintConfig {
@ -174,13 +178,15 @@ mod test {
}
},
},
"env": { "browser": true, }
"env": { "browser": true, },
"globals": { "foo": "readonly", }
}));
assert!(config.is_ok());
let ESLintConfig { rules, settings, env } = config.unwrap();
let ESLintConfig { rules, settings, env, globals } = config.unwrap();
assert!(!rules.is_empty());
assert_eq!(settings.jsx_a11y.polymorphic_prop_name, Some("role".to_string()));
assert_eq!(env.iter().count(), 1);
assert!(globals.is_enabled("foo"));
}
}

View file

@ -9,7 +9,7 @@ use crate::{
disable_directives::{DisableDirectives, DisableDirectivesBuilder},
fixer::{Fix, Message},
javascript_globals::GLOBALS,
ESLintConfig, ESLintEnv, ESLintSettings,
ESLintConfig, ESLintEnv, ESLintGlobals, ESLintSettings,
};
pub struct LintContext<'a> {
@ -26,7 +26,7 @@ pub struct LintContext<'a> {
file_path: Box<Path>,
pub eslint_config: Arc<ESLintConfig>,
eslint_config: Arc<ESLintConfig>,
}
impl<'a> LintContext<'a> {
@ -76,14 +76,18 @@ impl<'a> LintContext<'a> {
&self.file_path
}
pub fn env(&self) -> &ESLintEnv {
&self.eslint_config.env
}
pub fn settings(&self) -> &ESLintSettings {
&self.eslint_config.settings
}
pub fn globals(&self) -> &ESLintGlobals {
&self.eslint_config.globals
}
pub fn env(&self) -> &ESLintEnv {
&self.eslint_config.env
}
pub fn env_contains_var(&self, var: &str) -> bool {
for env in self.env().iter() {
let env = GLOBALS.get(env).unwrap_or(&GLOBALS["builtin"]);

View file

@ -30,7 +30,7 @@ pub use crate::{
service::{LintService, LintServiceOptions},
};
use crate::{
config::{ESLintEnv, ESLintSettings},
config::{ESLintEnv, ESLintGlobals, ESLintSettings},
fixer::Fix,
fixer::{Fixer, Message},
rule::RuleCategory,

View file

@ -10,7 +10,7 @@ use oxc_syntax::operator::UnaryOperator;
use crate::{context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint(no-undef): Disallow the use of undeclared variables")]
#[error("eslint(no-undef): Disallow the use of undeclared variables.")]
#[diagnostic(severity(warning), help("'{0}' is not defined."))]
struct NoUndefDiagnostic(CompactStr, #[label] pub Span);
@ -47,22 +47,29 @@ impl Rule for NoUndef {
.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 ctx.env_contains_var(reference.name().as_str()) {
return;
let name = reference.name();
if ctx.env_contains_var(name) {
continue;
}
if ctx.globals().is_enabled(name.as_str()) {
continue;
}
let node = ctx.nodes().get_node(reference.node_id());
if !self.type_of && has_typeof_operator(node, ctx) {
return;
continue;
}
ctx.diagnostic(NoUndefDiagnostic(reference.name().clone(), reference.span()));
ctx.diagnostic(NoUndefDiagnostic(name.clone(), reference.span()));
}
}
}
@ -81,53 +88,53 @@ fn test() {
use crate::tester::Tester;
let pass = vec![
("var a = 1, b = 2; a;", None),
// ("/*global b*/ function f() { b; }", None),
"var a = 1, b = 2; a;",
// "/*global b*/ function f() { b; }",
// { 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(<App attr={a} />);", 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),
// "/*global b a:false*/ a; function f() { b; a; }",
"function a(){} a();",
"function f(b) { b; }",
"var a; a = 1; a++;",
"var a; function f() { a = 1; }",
// "/*global b:true*/ b++;",
// "/*eslint-env browser*/ window;",
// "/*eslint-env node*/ require(\"a\");",
"Object; isNaN();",
"toString()",
"hasOwnProperty()",
"function evilEval(stuffToEval) { var ultimateAnswer; ultimateAnswer = 42; eval(stuffToEval); }",
"typeof a",
"typeof (a)",
"var b = typeof a",
"typeof a === 'undefined'",
"if (typeof a === 'undefined') {}",
"function foo() { var [a, b=4] = [1, 2]; return {a, b}; }",
"var toString = 1;",
"function myFunc(...foo) { return foo;}",
"var React, App, a=1; React.render(<App attr={a} />);",
"var console; [1,2,3].forEach(obj => {\n console.log(obj);\n});",
"var Foo; class Bar extends Foo { constructor() { super(); }}",
"import Warning from '../lib/warning'; var warn = new Warning('text');",
"import * as Warning from '../lib/warning'; var warn = new Warning('text');",
"var a; [a] = [0];",
"var a; ({a} = {});",
"var a; ({b: a} = {});",
"var obj; [obj.a, obj.b] = [0, 1];",
// "URLSearchParams;",
// "Intl;",
// "IntersectionObserver;",
// "Credential;",
// "requestIdleCallback;",
// "customElements;",
// "PromiseRejectionEvent;",
"(foo, bar) => { foo ||= WeakRef; bar ??= FinalizationRegistry; }",
// "/*global b:false*/ function f() { b = 1; }",
// { 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),
// "/*global b:false*/ function f() { b++; }",
// "/*global b*/ b = 1;",
// "/*global b:false*/ var b = 1;",
"Array = 1;",
"class A { constructor() { new.target; } }",
// {
// code: "var {bacon, ...others} = stuff; foo(others)",
// parserOptions: {
@ -135,55 +142,64 @@ fn test() {
// },
// 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)
"export * as ns from \"source\"",
"import.meta",
"let a; class C { static {} } a;",
"var a; class C { static {} } a;",
"a; class C { static {} } var a;",
"class C { static { C; } }",
"const C = class { static { C; } }",
"class C { static { a; } } var a;",
"class C { static { a; } } let a;",
"class C { static { var a; a; } }",
"class C { static { a; var a; } }",
"class C { static { a; { var a; } } }",
"class C { static { let a; a; } }",
"class C { static { a; let a; } }",
"class C { static { function a() {} a; } }",
"class C { static { a; function a() {} } }"
];
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(<img attr={a} />);", None),
("var React, App; React.render(<App attr={a} />);", 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),
"a = 1;",
"var a = b;",
"function f() { b; }",
"window;",
"require(\"a\");",
"var React; React.render(<img attr={a} />);",
"var React, App; React.render(<App attr={a} />);",
"[a] = [0];",
"({a} = {});",
"({b: a} = {});",
"[obj.a, obj.b] = [0, 1];",
"const c = 0; const a = {...b, c};",
"class C { static { a; } }",
"class C { static { { let a; } a; } }",
"class C { static { { function a() {} } a; } }",
"class C { static { function foo() { var a; } a; } }",
"class C { static { var a; } static { a; } }",
"class C { static { let a; } static { a; } }",
"class C { static { function a(){} } static { a; } }",
"class C { static { var a; } foo() { a; } }",
"class C { static { let a; } foo() { a; } }",
"class C { static { var a; } [a]; }",
"class C { static { let a; } [a]; }",
"class C { static { function a() {} } [a]; }",
"class C { static { var a; } } a;",
];
Tester::new(NoUndef::NAME, pass, fail).test_and_snapshot();
let pass = vec![];
let fail = vec![(
"if (typeof anUndefinedVar === 'string') {}",
Some(serde_json::json!([{ "typeof": true }])),
)];
Tester::new(NoUndef::NAME, pass, fail).test();
let pass = vec![("foo", None, Some(serde_json::json!({ "globals": { "foo": "readonly" } })))];
let fail = vec![("foo", None, Some(serde_json::json!({ "globals": { "foo": "off" } })))];
Tester::new(NoUndef::NAME, pass, fail).test();
}

View file

@ -2,189 +2,182 @@
source: crates/oxc_linter/src/tester.rs
expression: no_undef
---
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ 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:12]
1 │ if (typeof anUndefinedVar === 'string') {}
· ──────────────
╰────
help: 'anUndefinedVar' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:9]
1 │ var a = b;
· ─
╰────
help: 'b' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:16]
1 │ function f() { b; }
· ─
╰────
help: 'b' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ 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
⚠ 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
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:36]
1 │ var React; React.render(<img attr={a} />);
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:41]
1 │ var React, App; React.render(<App attr={a} />);
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:2]
1 │ [a] = [0];
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:3]
1 │ ({a} = {});
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:6]
1 │ ({b: a} = {});
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:2]
1 │ [obj.a, obj.b] = [0, 1];
· ───
╰────
help: 'obj' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:9]
1 │ [obj.a, obj.b] = [0, 1];
· ───
╰────
help: 'obj' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:28]
1 │ const c = 0; const a = {...b, c};
· ─
╰────
help: 'b' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:20]
1 │ class C { static { a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:31]
1 │ class C { static { { let a; } a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:40]
1 │ class C { static { { function a() {} } a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:47]
1 │ class C { static { function foo() { var a; } a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:38]
1 │ class C { static { var a; } static { a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:38]
1 │ class C { static { let a; } static { a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:46]
1 │ class C { static { function a(){} } static { a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:37]
1 │ class C { static { var a; } foo() { a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:37]
1 │ class C { static { let a; } foo() { a; } }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:30]
1 │ class C { static { var a; } [a]; }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:30]
1 │ class C { static { let a; } [a]; }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:39]
1 │ class C { static { function a() {} } [a]; }
· ─
╰────
help: 'a' is not defined.
⚠ eslint(no-undef): Disallow the use of undeclared variables
⚠ eslint(no-undef): Disallow the use of undeclared variables.
╭─[no_undef.tsx:1:31]
1 │ class C { static { var a; } } a;
· ─