diff --git a/crates/oxc_linter/src/config/env.rs b/crates/oxc_linter/src/config/env.rs index 20b516d364857..ee1bd9e6c871a 100644 --- a/crates/oxc_linter/src/config/env.rs +++ b/crates/oxc_linter/src/config/env.rs @@ -4,8 +4,11 @@ use serde::Deserialize; use std::{borrow::Borrow, hash::Hash}; /// Predefine global variables. -// TODO: list the keys we support -// +/// +/// Environments specify what global variables are predefined. See [ESLint's +/// list of +/// environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) +/// for what environments are available and what each one provides. #[derive(Debug, Clone, Deserialize, JsonSchema)] pub struct OxlintEnv(FxHashMap); diff --git a/crates/oxc_linter/src/config/globals.rs b/crates/oxc_linter/src/config/globals.rs index df1b22084503d..0b7440ab2e0ae 100644 --- a/crates/oxc_linter/src/config/globals.rs +++ b/crates/oxc_linter/src/config/globals.rs @@ -1,14 +1,46 @@ use rustc_hash::FxHashMap; use schemars::JsonSchema; -use serde::Deserialize; +use serde::{de::Visitor, Deserialize}; +use std::{borrow, fmt, hash}; /// Add or remove global variables. +/// +/// For each global variable, set the corresponding value equal to `"writable"` +/// to allow the variable to be overwritten or `"readonly"` to disallow overwriting. +/// +/// Globals can be disabled by setting their value to `"off"`. For example, in +/// an environment where most Es2015 globals are available but `Promise` is unavailable, +/// you might use this config: +/// +/// ```json +/// +/// { +/// "env": { +/// "es6": true +/// }, +/// "globals": { +/// "Promise": "off" +/// } +/// } +/// +/// ``` +/// +/// You may also use `"readable"` or `false` to represent `"readonly"`, and +/// `"writeable"` or `true` to represent `"writable"`. // #[derive(Debug, Default, Deserialize, JsonSchema)] pub struct OxlintGlobals(FxHashMap); +impl OxlintGlobals { + pub fn is_enabled(&self, name: &Q) -> bool + where + String: borrow::Borrow, + Q: ?Sized + Eq + hash::Hash, + { + self.0.get(name).is_some_and(|value| *value != GlobalValue::Off) + } +} -// TODO: support deprecated `false` -#[derive(Debug, Eq, PartialEq, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum GlobalValue { Readonly, @@ -16,8 +48,118 @@ pub enum GlobalValue { Off, } -impl OxlintGlobals { - pub fn is_enabled(&self, name: &str) -> bool { - self.0.get(name).is_some_and(|value| *value != GlobalValue::Off) +impl GlobalValue { + pub const fn as_str(self) -> &'static str { + match self { + Self::Readonly => "readonly", + Self::Writeable => "writeable", + Self::Off => "off", + } + } +} + +impl<'de> Deserialize<'de> for GlobalValue { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(GlobalValueVisitor) + } +} + +impl From for GlobalValue { + #[inline] + fn from(value: bool) -> Self { + if value { + GlobalValue::Writeable + } else { + GlobalValue::Readonly + } + } +} + +impl TryFrom<&str> for GlobalValue { + type Error = &'static str; + + fn try_from(value: &str) -> Result { + match value { + "readonly" | "readable" => Ok(GlobalValue::Readonly), + "writable" | "writeable" => Ok(GlobalValue::Writeable), + "off" => Ok(GlobalValue::Off), + _ => Err("Invalid global value"), + } + } +} + +impl fmt::Display for GlobalValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_str().fmt(f) + } +} + +struct GlobalValueVisitor; +impl<'de> Visitor<'de> for GlobalValueVisitor { + type Value = GlobalValue; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "'readonly', 'writable', 'off', or a boolean") + } + + fn visit_bool(self, v: bool) -> Result + where + E: serde::de::Error, + { + Ok(v.into()) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + v.try_into().map_err(E::custom) + } +} + +#[cfg(test)] +mod test { + use super::*; + use serde_json::json; + + macro_rules! globals { + ($($json:tt)+) => { + OxlintGlobals::deserialize(&json!($($json)+)).unwrap() + }; + } + + #[test] + fn test_deserialize_normal() { + let globals = globals!({ + "foo": "readonly", + "bar": "writable", + "baz": "off", + }); + assert!(globals.is_enabled("foo")); + assert!(globals.is_enabled("bar")); + assert!(!globals.is_enabled("baz")); + } + + #[test] + fn test_deserialize_legacy_spelling() { + let globals = globals!({ + "foo": "readable", + "bar": "writeable", + }); + assert!(globals.is_enabled("foo")); + assert!(globals.is_enabled("bar")); + } + + #[test] + fn test_deserialize_bool() { + let globals = globals!({ + "foo": true, + "bar": false, + }); + assert!(globals.is_enabled("foo")); + assert!(globals.is_enabled("bar")); } } diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 1618b6351ef9b..d38734591d720 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -54,10 +54,12 @@ use crate::{ #[derive(Debug, Default, Deserialize, JsonSchema)] #[serde(default)] pub struct OxlintConfig { - /// See [Oxlint Rules](./rules) + /// See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html). pub(crate) rules: OxlintRules, pub(crate) settings: OxlintSettings, + /// Environments enable and disable collections of global variables. pub(crate) env: OxlintEnv, + /// Enabled or disabled specific global variables. pub(crate) globals: OxlintGlobals, } diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index 0e78d9a5a1702..a46201c4345f5 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -49,7 +49,9 @@ impl JsonSchema for OxlintRules { #[allow(unused)] #[derive(Debug, JsonSchema)] - #[schemars(description = "See [Oxlint Rules](./rules)")] + #[schemars( + description = "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html)" + )] struct DummyRuleMap(pub FxHashMap); gen.subschema_for::() diff --git a/crates/oxc_linter/src/snapshots/schema_json.snap b/crates/oxc_linter/src/snapshots/schema_json.snap index 929e03c8408c2..65195d87165a6 100644 --- a/crates/oxc_linter/src/snapshots/schema_json.snap +++ b/crates/oxc_linter/src/snapshots/schema_json.snap @@ -9,13 +9,23 @@ expression: json "type": "object", "properties": { "env": { - "$ref": "#/definitions/OxlintEnv" + "description": "Environments enable and disable collections of global variables.", + "allOf": [ + { + "$ref": "#/definitions/OxlintEnv" + } + ] }, "globals": { - "$ref": "#/definitions/OxlintGlobals" + "description": "Enabled or disabled specific global variables.", + "allOf": [ + { + "$ref": "#/definitions/OxlintGlobals" + } + ] }, "rules": { - "description": "See [Oxlint Rules](./rules)", + "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).", "allOf": [ { "$ref": "#/definitions/OxlintRules" @@ -101,7 +111,7 @@ expression: json ] }, "DummyRuleMap": { - "description": "See [Oxlint Rules](./rules)", + "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html)", "type": "object", "additionalProperties": { "$ref": "#/definitions/DummyRule" @@ -201,14 +211,14 @@ expression: json ] }, "OxlintEnv": { - "description": "Predefine global variables.", + "description": "Predefine global variables.\n\nEnvironments specify what global variables are predefined. See [ESLint's list of environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) for what environments are available and what each one provides.", "type": "object", "additionalProperties": { "type": "boolean" } }, "OxlintGlobals": { - "description": "Add or remove global variables.", + "description": "Add or remove global variables.\n\nFor each global variable, set the corresponding value equal to `\"writable\"` to allow the variable to be overwritten or `\"readonly\"` to disallow overwriting.\n\nGlobals can be disabled by setting their value to `\"off\"`. For example, in an environment where most Es2015 globals are available but `Promise` is unavailable, you might use this config:\n\n```json\n\n{ \"env\": { \"es6\": true }, \"globals\": { \"Promise\": \"off\" } }\n\n```\n\nYou may also use `\"readable\"` or `false` to represent `\"readonly\"`, and `\"writeable\"` or `true` to represent `\"writable\"`.", "type": "object", "additionalProperties": { "$ref": "#/definitions/GlobalValue" diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index c3d4ea510056a..9f06b0720e7ec 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -5,13 +5,23 @@ "type": "object", "properties": { "env": { - "$ref": "#/definitions/OxlintEnv" + "description": "Environments enable and disable collections of global variables.", + "allOf": [ + { + "$ref": "#/definitions/OxlintEnv" + } + ] }, "globals": { - "$ref": "#/definitions/OxlintGlobals" + "description": "Enabled or disabled specific global variables.", + "allOf": [ + { + "$ref": "#/definitions/OxlintGlobals" + } + ] }, "rules": { - "description": "See [Oxlint Rules](./rules)", + "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).", "allOf": [ { "$ref": "#/definitions/OxlintRules" @@ -97,7 +107,7 @@ ] }, "DummyRuleMap": { - "description": "See [Oxlint Rules](./rules)", + "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html)", "type": "object", "additionalProperties": { "$ref": "#/definitions/DummyRule" @@ -197,14 +207,14 @@ ] }, "OxlintEnv": { - "description": "Predefine global variables.", + "description": "Predefine global variables.\n\nEnvironments specify what global variables are predefined. See [ESLint's list of environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) for what environments are available and what each one provides.", "type": "object", "additionalProperties": { "type": "boolean" } }, "OxlintGlobals": { - "description": "Add or remove global variables.", + "description": "Add or remove global variables.\n\nFor each global variable, set the corresponding value equal to `\"writable\"` to allow the variable to be overwritten or `\"readonly\"` to disallow overwriting.\n\nGlobals can be disabled by setting their value to `\"off\"`. For example, in an environment where most Es2015 globals are available but `Promise` is unavailable, you might use this config:\n\n```json\n\n{ \"env\": { \"es6\": true }, \"globals\": { \"Promise\": \"off\" } }\n\n```\n\nYou may also use `\"readable\"` or `false` to represent `\"readonly\"`, and `\"writeable\"` or `true` to represent `\"writable\"`.", "type": "object", "additionalProperties": { "$ref": "#/definitions/GlobalValue" diff --git a/tasks/website/src/linter/snapshots/schema_markdown.snap b/tasks/website/src/linter/snapshots/schema_markdown.snap index ae6f0c29ab6a0..6a69945a99a77 100644 --- a/tasks/website/src/linter/snapshots/schema_markdown.snap +++ b/tasks/website/src/linter/snapshots/schema_markdown.snap @@ -40,6 +40,8 @@ type: `object` Predefine global variables. +Environments specify what global variables are predefined. See [ESLint's list of environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) for what environments are available and what each one provides. + ## globals @@ -48,13 +50,30 @@ type: `object` Add or remove global variables. +For each global variable, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. + +Globals can be disabled by setting their value to `"off"`. For example, in an environment where most Es2015 globals are available but `Promise` is unavailable, you might use this config: + +```json +{ + "env": { + "es6": true + }, + "globals": { + "Promise": "off" + } +} +``` + +You may also use `"readable"` or `false` to represent `"readonly"`, and `"writeable"` or `true` to represent `"writable"`. + ## rules type: `object` -See [Oxlint Rules](./rules) +See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html)