mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter): support read env from eslintrc (#2130)
## target resolve #732 ## contexts - globals definition from https://github.com/sindresorhus/globals/blob/main/globals.json - port https://github.com/eslint/eslintrc/blob/main/conf/environments.js to derive environments
This commit is contained in:
parent
e5719e9b4d
commit
ee5b9682ae
21 changed files with 2237 additions and 94 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -915,6 +915,16 @@ version = "1.0.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "javascript_globals"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"handlebars",
|
||||
"lazy_static",
|
||||
"oxc_tasks_common",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemalloc-sys"
|
||||
version = "0.5.4+5.3.0-patched"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"no-undef": "error"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"rules": {
|
||||
"no-undef": "error"
|
||||
}
|
||||
}
|
||||
1
crates/oxc_cli/fixtures/eslintrc_env/test.js
Normal file
1
crates/oxc_cli/fixtures/eslintrc_env/test.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
console.log('')
|
||||
|
|
@ -324,6 +324,29 @@ mod test {
|
|||
assert_eq!(result.number_of_errors, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eslintrc_no_env() {
|
||||
let args =
|
||||
&["-c", "fixtures/eslintrc_env/eslintrc_no_env.json", "fixtures/eslintrc_env/test.js"];
|
||||
let result = test(args);
|
||||
assert_eq!(result.number_of_files, 1);
|
||||
assert_eq!(result.number_of_warnings, 1);
|
||||
assert_eq!(result.number_of_errors, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eslintrc_with_env() {
|
||||
let args = &[
|
||||
"-c",
|
||||
"fixtures/eslintrc_env/eslintrc_env_browser.json",
|
||||
"fixtures/eslintrc_env/test.js",
|
||||
];
|
||||
let result = test(args);
|
||||
assert_eq!(result.number_of_files, 1);
|
||||
assert_eq!(result.number_of_warnings, 0);
|
||||
assert_eq!(result.number_of_errors, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_empty_allow_empty_catch() {
|
||||
let args = &[
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use oxc_diagnostics::{Error, FailedToOpenFileError, Report};
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{rules::RuleEnum, settings::Nextjs, AllowWarnDeny, JsxA11y, LintSettings};
|
||||
use crate::{rules::RuleEnum, settings::Nextjs, AllowWarnDeny, Env, JsxA11y, LintSettings};
|
||||
|
||||
use self::errors::{
|
||||
FailedToParseConfigError, FailedToParseConfigJsonError, FailedToParseJsonc,
|
||||
|
|
@ -15,6 +15,7 @@ use self::errors::{
|
|||
pub struct ESLintConfig {
|
||||
rules: Vec<ESLintRuleConfig>,
|
||||
settings: LintSettings,
|
||||
env: Env,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -30,11 +31,12 @@ impl ESLintConfig {
|
|||
let json = Self::read_json(path)?;
|
||||
let rules = parse_rules(&json)?;
|
||||
let settings = parse_settings_from_root(&json);
|
||||
Ok(Self { rules, settings })
|
||||
let env = parse_env_from_root(&json);
|
||||
Ok(Self { rules, settings, env })
|
||||
}
|
||||
|
||||
pub fn settings(self) -> LintSettings {
|
||||
self.settings
|
||||
pub fn properties(self) -> (LintSettings, Env) {
|
||||
(self.settings, self.env)
|
||||
}
|
||||
|
||||
fn read_json(path: &Path) -> Result<serde_json::Value, Error> {
|
||||
|
|
@ -201,6 +203,28 @@ pub fn parse_settings(setting_value: &Value) -> LintSettings {
|
|||
LintSettings::default()
|
||||
}
|
||||
|
||||
fn parse_env_from_root(root_json: &Value) -> Env {
|
||||
let Value::Object(root_object) = root_json else { return Env::default() };
|
||||
|
||||
let Some(env_value) = root_object.get("env") else { return Env::default() };
|
||||
|
||||
let env_object = match env_value {
|
||||
Value::Object(env_object) => env_object,
|
||||
_ => return Env::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![];
|
||||
for (k, v) in env_object {
|
||||
if let Value::Bool(v) = v {
|
||||
if *v {
|
||||
result.push(String::from(k));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Env::new(result)
|
||||
}
|
||||
|
||||
fn parse_rule_name(name: &str) -> (&str, &str) {
|
||||
if let Some((category, name)) = name.split_once('/') {
|
||||
let category = category.trim_start_matches('@');
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use oxc_span::SourceType;
|
|||
use crate::{
|
||||
disable_directives::{DisableDirectives, DisableDirectivesBuilder},
|
||||
fixer::{Fix, Message},
|
||||
AstNode, LintSettings,
|
||||
javascript_globals::GLOBALS,
|
||||
AstNode, Env, LintSettings,
|
||||
};
|
||||
|
||||
pub struct LintContext<'a> {
|
||||
|
|
@ -26,6 +27,8 @@ pub struct LintContext<'a> {
|
|||
file_path: Box<Path>,
|
||||
|
||||
settings: Arc<LintSettings>,
|
||||
|
||||
env: Arc<Env>,
|
||||
}
|
||||
|
||||
impl<'a> LintContext<'a> {
|
||||
|
|
@ -40,6 +43,7 @@ impl<'a> LintContext<'a> {
|
|||
current_rule_name: "",
|
||||
file_path,
|
||||
settings: Arc::new(LintSettings::default()),
|
||||
env: Arc::new(Env::default()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +59,12 @@ impl<'a> LintContext<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_env(mut self, env: &Arc<Env>) -> Self {
|
||||
self.env = Arc::clone(env);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn semantic(&self) -> &Rc<Semantic<'a>> {
|
||||
&self.semantic
|
||||
}
|
||||
|
|
@ -79,6 +89,21 @@ impl<'a> LintContext<'a> {
|
|||
&self.file_path
|
||||
}
|
||||
|
||||
pub fn envs(&self) -> &Env {
|
||||
&self.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"]);
|
||||
if env.get(var).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_rule_name(&mut self, name: &'static str) {
|
||||
self.current_rule_name = name;
|
||||
|
|
|
|||
25
crates/oxc_linter/src/env.rs
Normal file
25
crates/oxc_linter/src/env.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use std::{self, ops::Deref};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Env(Vec<String>);
|
||||
|
||||
impl Env {
|
||||
pub fn new(env: Vec<String>) -> Self {
|
||||
Self(env)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `env` field from ESLint config
|
||||
impl Default for Env {
|
||||
fn default() -> Self {
|
||||
Self(vec!["builtin".to_string()])
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Env {
|
||||
type Target = Vec<String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +1,5 @@
|
|||
//! [Globals](https://github.com/sindresorhus/globals/blob/main/globals.json)
|
||||
//! Each global is given a value of true or false.
|
||||
//! A value of true indicates that the variable may be overwritten.
|
||||
//! A value of false indicates that the variable should be considered read-only.
|
||||
|
||||
use phf::{phf_map, phf_set, Map};
|
||||
|
||||
pub const BUILTINS: Map<&'static str, bool> = phf_map! {
|
||||
"AggregateError" => false,
|
||||
"Array" => false,
|
||||
"ArrayBuffer" => false,
|
||||
"Atomics" => false,
|
||||
"BigInt" => false,
|
||||
"BigInt64Array" => false,
|
||||
"BigUint64Array" => false,
|
||||
"Boolean" => false,
|
||||
"constructor" => false,
|
||||
"DataView" => false,
|
||||
"Date" => false,
|
||||
"decodeURI" => false,
|
||||
"decodeURIComponent" => false,
|
||||
"encodeURI" => false,
|
||||
"encodeURIComponent" => false,
|
||||
"Error" => false,
|
||||
"escape" => false,
|
||||
"eval" => false,
|
||||
"EvalError" => false,
|
||||
"FinalizationRegistry" => false,
|
||||
"Float32Array" => false,
|
||||
"Float64Array" => false,
|
||||
"Function" => false,
|
||||
"globalThis" => false,
|
||||
"hasOwnProperty" => false,
|
||||
"Infinity" => false,
|
||||
"Int16Array" => false,
|
||||
"Int32Array" => false,
|
||||
"Int8Array" => false,
|
||||
"isFinite" => false,
|
||||
"isNaN" => false,
|
||||
"isPrototypeOf" => false,
|
||||
"JSON" => false,
|
||||
"Map" => false,
|
||||
"Math" => false,
|
||||
"NaN" => false,
|
||||
"Number" => false,
|
||||
"Object" => false,
|
||||
"parseFloat" => false,
|
||||
"parseInt" => false,
|
||||
"Promise" => false,
|
||||
"propertyIsEnumerable" => false,
|
||||
"Proxy" => false,
|
||||
"RangeError" => false,
|
||||
"ReferenceError" => false,
|
||||
"Reflect" => false,
|
||||
"RegExp" => false,
|
||||
"Set" => false,
|
||||
"SharedArrayBuffer" => false,
|
||||
"String" => false,
|
||||
"Symbol" => false,
|
||||
"Diagnostic" => false,
|
||||
"toLocaleString" => false,
|
||||
"toString" => false,
|
||||
"TypeError" => false,
|
||||
"Uint16Array" => false,
|
||||
"Uint32Array" => false,
|
||||
"Uint8Array" => false,
|
||||
"Uint8ClampedArray" => false,
|
||||
"undefined" => false,
|
||||
"unescape" => false,
|
||||
"URIError" => false,
|
||||
"valueOf" => false,
|
||||
"WeakMap" => false,
|
||||
"WeakRef" => false,
|
||||
"WeakSet" => false
|
||||
};
|
||||
|
||||
pub const PRE_DEFINE_VAR: Map<&'static str, bool> = phf_map! {
|
||||
"undefined" => false,
|
||||
"Infinity" => false,
|
||||
|
|
|
|||
1832
crates/oxc_linter/src/javascript_globals.rs
Normal file
1832
crates/oxc_linter/src/javascript_globals.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -8,8 +8,10 @@ mod ast_util;
|
|||
mod config;
|
||||
mod context;
|
||||
mod disable_directives;
|
||||
mod env;
|
||||
mod fixer;
|
||||
mod globals;
|
||||
mod javascript_globals;
|
||||
mod options;
|
||||
pub mod partial_loader;
|
||||
pub mod rule;
|
||||
|
|
@ -25,6 +27,7 @@ use oxc_diagnostics::Report;
|
|||
|
||||
pub use crate::{
|
||||
context::LintContext,
|
||||
env::Env,
|
||||
fixer::Fix,
|
||||
fixer::{FixResult, Fixer, Message},
|
||||
options::{AllowWarnDeny, LintOptions},
|
||||
|
|
@ -52,6 +55,7 @@ pub struct Linter {
|
|||
rules: Vec<(/* rule name */ &'static str, RuleEnum)>,
|
||||
options: LintOptions,
|
||||
settings: Arc<LintSettings>,
|
||||
env: Arc<Env>,
|
||||
}
|
||||
|
||||
impl Default for Linter {
|
||||
|
|
@ -65,9 +69,9 @@ impl Linter {
|
|||
///
|
||||
/// Returns `Err` if there are any errors parsing the configuration file.
|
||||
pub fn from_options(options: LintOptions) -> Result<Self, Report> {
|
||||
let (rules, settings) = options.derive_rules_and_settings()?;
|
||||
let (rules, settings, env) = options.derive_rules_and_settings_and_env()?;
|
||||
let rules = rules.into_iter().map(|rule| (rule.name(), rule)).collect();
|
||||
Ok(Self { rules, options, settings: Arc::new(settings) })
|
||||
Ok(Self { rules, options, settings: Arc::new(settings), env: Arc::new(env) })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -82,6 +86,12 @@ impl Linter {
|
|||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_envs(mut self, env: Env) -> Self {
|
||||
self.env = Arc::new(env);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn options(&self) -> &LintOptions {
|
||||
&self.options
|
||||
}
|
||||
|
|
@ -98,7 +108,8 @@ impl Linter {
|
|||
|
||||
pub fn run<'a>(&self, ctx: LintContext<'a>) -> Vec<Message<'a>> {
|
||||
let semantic = Rc::clone(ctx.semantic());
|
||||
let mut ctx = ctx.with_fix(self.options.fix).with_settings(&self.settings);
|
||||
let mut ctx =
|
||||
ctx.with_fix(self.options.fix).with_settings(&self.settings).with_env(&self.env);
|
||||
|
||||
for (rule_name, rule) in &self.rules {
|
||||
ctx.with_rule_name(rule_name);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
ESLintConfig,
|
||||
},
|
||||
rules::RULES,
|
||||
LintSettings, RuleCategory, RuleEnum,
|
||||
Env, LintSettings, RuleCategory, RuleEnum,
|
||||
};
|
||||
use oxc_diagnostics::Error;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
|
@ -28,6 +28,7 @@ pub struct LintOptions {
|
|||
pub jsx_a11y_plugin: bool,
|
||||
pub nextjs_plugin: bool,
|
||||
pub react_perf_plugin: bool,
|
||||
pub env: Env,
|
||||
}
|
||||
|
||||
impl Default for LintOptions {
|
||||
|
|
@ -42,6 +43,7 @@ impl Default for LintOptions {
|
|||
jsx_a11y_plugin: false,
|
||||
nextjs_plugin: false,
|
||||
react_perf_plugin: false,
|
||||
env: Env::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,6 +104,12 @@ impl LintOptions {
|
|||
self.react_perf_plugin = yes;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_env(mut self, env: Vec<String>) -> Self {
|
||||
self.env = Env::new(env);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
|
|
@ -168,7 +176,9 @@ impl LintOptions {
|
|||
/// # Errors
|
||||
///
|
||||
/// * Returns `Err` if there are any errors parsing the configuration file.
|
||||
pub fn derive_rules_and_settings(&self) -> Result<(Vec<RuleEnum>, LintSettings), Error> {
|
||||
pub fn derive_rules_and_settings_and_env(
|
||||
&self,
|
||||
) -> Result<(Vec<RuleEnum>, LintSettings, Env), Error> {
|
||||
let config = self.config_path.as_ref().map(|path| ESLintConfig::new(path)).transpose()?;
|
||||
|
||||
let mut rules: FxHashSet<RuleEnum> = FxHashSet::default();
|
||||
|
|
@ -216,10 +226,13 @@ impl LintOptions {
|
|||
}
|
||||
|
||||
let mut rules = rules.into_iter().collect::<Vec<_>>();
|
||||
|
||||
let (settings, env) = config.map(ESLintConfig::properties).unwrap_or_default();
|
||||
|
||||
// for stable diagnostics output ordering
|
||||
rules.sort_unstable_by_key(RuleEnum::name);
|
||||
|
||||
Ok((rules, config.map(ESLintConfig::settings).unwrap_or_default()))
|
||||
Ok((rules, settings, env))
|
||||
}
|
||||
|
||||
// get final filtered rules by reading `self.jest_plugin` and `self.jsx_a11y_plugin`
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator,
|
|||
use crate::{
|
||||
ast_util::{self, IsConstant},
|
||||
context::LintContext,
|
||||
globals::BUILTINS,
|
||||
rule::Rule,
|
||||
AstNode,
|
||||
};
|
||||
|
|
@ -349,7 +348,7 @@ impl NoConstantBinaryExpression {
|
|||
| Expression::RegExpLiteral(_) => true,
|
||||
Expression::NewExpression(call_expr) => {
|
||||
if let Expression::Identifier(ident) = &call_expr.callee {
|
||||
return BUILTINS.contains_key(ident.name.as_str())
|
||||
return ctx.env_contains_var(ident.name.as_str())
|
||||
&& ctx.semantic().is_reference_to_global_variable(ident);
|
||||
}
|
||||
false
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use oxc_diagnostics::{
|
|||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::{Atom, Span};
|
||||
|
||||
use crate::{context::LintContext, globals::BUILTINS, rule::Rule};
|
||||
use crate::{context::LintContext, rule::Rule};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint(no-global-assign): Read-only global '{0}' should not be modified.")]
|
||||
|
|
@ -71,7 +71,7 @@ impl Rule for NoGlobalAssign {
|
|||
if reference.is_write() && symbol_table.is_global_reference(reference_id) {
|
||||
let name = reference.name();
|
||||
|
||||
if !self.excludes.contains(name) && BUILTINS.contains_key(name) {
|
||||
if !self.excludes.contains(name) && ctx.env_contains_var(name) {
|
||||
ctx.diagnostic(NoGlobalAssignDiagnostic(name.clone(), reference.span()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use oxc_macros::declare_oxc_lint;
|
|||
use oxc_semantic::VariableInfo;
|
||||
use oxc_span::{Atom, Span};
|
||||
|
||||
use crate::{context::LintContext, globals::BUILTINS, rule::Rule};
|
||||
use crate::{context::LintContext, rule::Rule};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint(no-redeclare): '{0}' is already defined.")]
|
||||
|
|
@ -106,7 +106,7 @@ impl NoRedeclare {
|
|||
variable: &VariableInfo,
|
||||
ident: &BindingIdentifier,
|
||||
) {
|
||||
if self.built_in_globals && BUILTINS.get(&ident.name).is_some() {
|
||||
if self.built_in_globals && ctx.env_contains_var(&ident.name) {
|
||||
ctx.diagnostic(NoRedeclareAsBuiltiInDiagnostic(ident.name.clone(), ident.span));
|
||||
} else if variable.span != ident.span {
|
||||
ctx.diagnostic(NoRedeclareDiagnostic(ident.name.clone(), ident.span, variable.span));
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ 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};
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint(no-undef): Disallow the use of undeclared variables")]
|
||||
|
|
@ -35,7 +35,7 @@ declare_oxc_lint!(
|
|||
/// var bar = a + 1;
|
||||
/// ```
|
||||
NoUndef,
|
||||
nursery // https://github.com/oxc-project/oxc/issues/732
|
||||
nursery
|
||||
);
|
||||
|
||||
impl Rule for NoUndef {
|
||||
|
|
@ -53,7 +53,7 @@ impl Rule for NoUndef {
|
|||
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()) {
|
||||
if ctx.env_contains_var(reference.name().as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
4
justfile
4
justfile
|
|
@ -74,6 +74,10 @@ codecov:
|
|||
benchmark:
|
||||
cargo benchmark
|
||||
|
||||
# Generate the JavaScript global variables. See `tasks/javascript_globals`
|
||||
javascript-globals:
|
||||
cargo run -p javascript_globals
|
||||
|
||||
# Create a new lint rule by providing the ESLint name. See `tasks/rulegen`
|
||||
new-rule name:
|
||||
cargo run -p rulegen {{name}}
|
||||
|
|
|
|||
21
tasks/javascript_globals/Cargo.toml
Normal file
21
tasks/javascript_globals/Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "javascript_globals"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "javascript_globals"
|
||||
test = false
|
||||
|
||||
[dependencies]
|
||||
oxc_tasks_common = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
lazy_static = { workspace = true }
|
||||
|
||||
handlebars = "5.0.0"
|
||||
167
tasks/javascript_globals/src/main.rs
Normal file
167
tasks/javascript_globals/src/main.rs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
use lazy_static::lazy_static;
|
||||
use oxc_tasks_common::agent;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
mod template;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct EnvVar<'a> {
|
||||
pub name: &'a str,
|
||||
pub writeable: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct Env<'a> {
|
||||
pub name: &'a str,
|
||||
pub vars: Vec<EnvVar<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Context<'a> {
|
||||
envs: Vec<Env<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
fn new(envs: Vec<Env<'a>>) -> Self {
|
||||
Self { envs }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
current: &HashMap<String, bool>,
|
||||
prev: &HashMap<String, bool>,
|
||||
) -> HashMap<String, bool> {
|
||||
let mut retv: HashMap<String, bool> = HashMap::new();
|
||||
|
||||
for (key, value) in current {
|
||||
if !prev.contains_key(key) {
|
||||
retv.insert(key.clone(), *value);
|
||||
}
|
||||
}
|
||||
|
||||
retv
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref NEW_GLOBALS_2017: HashMap<String, bool> = {
|
||||
return HashMap::from([
|
||||
(String::from("Atomics"), false),
|
||||
(String::from("SharedArrayBuffer"), false),
|
||||
]);
|
||||
};
|
||||
static ref NEW_GLOBALS_2020: HashMap<String, bool> = {
|
||||
return HashMap::from([
|
||||
(String::from("BigInt"), false),
|
||||
(String::from("BigInt64Array"), false),
|
||||
(String::from("BigUint64Array"), false),
|
||||
(String::from("globalThis"), false),
|
||||
]);
|
||||
};
|
||||
static ref NEW_GLOBALS_2021: HashMap<String, bool> = {
|
||||
return HashMap::from([
|
||||
(String::from("AggregateError"), false),
|
||||
(String::from("FinalizationRegistry"), false),
|
||||
(String::from("WeakRef"), false),
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Each global is given a value of true or false.
|
||||
// A value of true indicates that the variable may be overwritten.
|
||||
// A value of false indicates that the variable should be considered read-only.
|
||||
// open globals.json file relative to current file
|
||||
// let globals: HashMap<String, HashMap<String, bool>>;
|
||||
let globals: HashMap<String, HashMap<String, bool>> = match agent()
|
||||
.get("https://raw.githubusercontent.com/sindresorhus/globals/main/globals.json")
|
||||
.call()
|
||||
{
|
||||
Ok(response) => response.into_json().unwrap(),
|
||||
Err(e) => {
|
||||
panic!("Failed to fetch globals.json: {e}");
|
||||
}
|
||||
};
|
||||
|
||||
// 19 variables such as Promise, Map, ...
|
||||
let new_globals_2015 = get_diff(&globals["es2015"], &globals["es5"]);
|
||||
|
||||
let new_globals_2015_2017 = {
|
||||
let mut map = HashMap::new();
|
||||
map.extend(new_globals_2015.clone());
|
||||
map.extend(NEW_GLOBALS_2017.clone());
|
||||
map
|
||||
};
|
||||
|
||||
let new_globals_2015_2017_2020 = {
|
||||
let mut map = new_globals_2015_2017.clone();
|
||||
map.extend(NEW_GLOBALS_2020.clone());
|
||||
map
|
||||
};
|
||||
|
||||
let new_globals_2015_2017_2020_2021 = {
|
||||
let mut map = new_globals_2015_2017_2020.clone();
|
||||
map.extend(NEW_GLOBALS_2021.clone());
|
||||
map
|
||||
};
|
||||
|
||||
let envs_preset: Vec<Env> = [
|
||||
// Language
|
||||
("builtin", &globals["builtin"]), // oxc uses builtin instead of es5 of ESLint
|
||||
("es6", &new_globals_2015),
|
||||
("es2015", &new_globals_2015),
|
||||
("es2016", &new_globals_2015),
|
||||
("es2017", &new_globals_2015_2017),
|
||||
("es2018", &new_globals_2015_2017),
|
||||
("es2019", &new_globals_2015_2017),
|
||||
("es2020", &new_globals_2015_2017_2020),
|
||||
("es2021", &new_globals_2015_2017_2020_2021),
|
||||
("es2022", &new_globals_2015_2017_2020_2021),
|
||||
("es2023", &new_globals_2015_2017_2020_2021),
|
||||
("es2024", &new_globals_2015_2017_2020_2021),
|
||||
// Platforms
|
||||
("browser", &globals["browser"]),
|
||||
("node", &globals["node"]),
|
||||
("shared-node-browser", &globals["shared-node-browser"]),
|
||||
("worker", &globals["worker"]),
|
||||
("serviceworker", &globals["serviceworker"]),
|
||||
// Frameworks
|
||||
("commonjs", &globals["commonjs"]),
|
||||
("amd", &globals["amd"]),
|
||||
("mocha", &globals["mocha"]),
|
||||
("jasmine", &globals["jasmine"]),
|
||||
("jest", &globals["jest"]),
|
||||
("phantomjs", &globals["phantomjs"]),
|
||||
("jquery", &globals["jquery"]),
|
||||
("qunit", &globals["qunit"]),
|
||||
("prototypejs", &globals["prototypejs"]),
|
||||
("shelljs", &globals["shelljs"]),
|
||||
("meteor", &globals["meteor"]),
|
||||
("mongo", &globals["mongo"]),
|
||||
("protractor", &globals["protractor"]),
|
||||
("applescript", &globals["applescript"]),
|
||||
("nashorn", &globals["nashorn"]),
|
||||
("atomtest", &globals["atomtest"]),
|
||||
("embertest", &globals["embertest"]),
|
||||
("webextensions", &globals["webextensions"]),
|
||||
("greasemonkey", &globals["greasemonkey"]),
|
||||
]
|
||||
.iter()
|
||||
.map(|(name, vars)| Env { name, vars: to_env_vars(vars) })
|
||||
.collect();
|
||||
|
||||
let context = Context::new(envs_preset);
|
||||
let template = template::Template::with_context(&context);
|
||||
if let Err(err) = template.render() {
|
||||
eprintln!("failed to render environments template: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
fn to_env_vars(env_var_map: &HashMap<String, bool>) -> Vec<EnvVar> {
|
||||
let mut result: Vec<EnvVar> = vec![];
|
||||
for (key, value) in env_var_map {
|
||||
result.push(EnvVar { name: key, writeable: *value });
|
||||
}
|
||||
|
||||
result.sort_by(|a, b| a.name.cmp(b.name));
|
||||
result
|
||||
}
|
||||
38
tasks/javascript_globals/src/template.rs
Normal file
38
tasks/javascript_globals/src/template.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{Error, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use handlebars::Handlebars;
|
||||
|
||||
use crate::Context;
|
||||
|
||||
const ENV_TEMPLATE: &str = include_str!("../template.hbs");
|
||||
|
||||
pub struct Template<'a> {
|
||||
context: &'a Context<'a>,
|
||||
registry: Handlebars<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Template<'a> {
|
||||
pub fn with_context(context: &'a Context) -> Self {
|
||||
let mut registry = handlebars::Handlebars::new();
|
||||
registry.register_escape_fn(handlebars::no_escape);
|
||||
Self { context, registry }
|
||||
}
|
||||
|
||||
pub fn render(&self) -> Result<(), Error> {
|
||||
let rendered = self
|
||||
.registry
|
||||
.render_template(ENV_TEMPLATE, &handlebars::to_json(self.context))
|
||||
.unwrap();
|
||||
|
||||
let out_path = Path::new("crates/oxc_linter/src/javascript_globals.rs");
|
||||
File::create(out_path)?.write_all(rendered.as_bytes())?;
|
||||
|
||||
println!("Saved env file to {out_path:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
11
tasks/javascript_globals/template.hbs
Normal file
11
tasks/javascript_globals/template.hbs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use phf::{phf_map, Map};
|
||||
|
||||
pub static ENVIRONMENTS: Map<&'static str, Map<&'static str, bool>> = phf_map! {
|
||||
{{#each envs }}
|
||||
"{{name}}" => phf_map! {
|
||||
{{#each vars}}
|
||||
"{{name}}" => {{writeable}},
|
||||
{{/each}}
|
||||
},
|
||||
{{/each}}
|
||||
};
|
||||
Loading…
Reference in a new issue