Skip to content

Commit

Permalink
feat(linter): support more flexible config.globals values (#4990)
Browse files Browse the repository at this point in the history
Support `"readable", `"writable"`, and boolean values for `GlobalValue`.

I also enhanced the documentation for `OxcGlobals`

## Screenshot
<img width="797" alt="image"
src="https://github.com/user-attachments/assets/8f76de4c-4ae8-44d1-9be1-720fc3c7e0ec">
  • Loading branch information
DonIsaac authored Aug 19, 2024
1 parent 4436774 commit a0effab
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 23 deletions.
7 changes: 5 additions & 2 deletions crates/oxc_linter/src/config/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use serde::Deserialize;
use std::{borrow::Borrow, hash::Hash};

/// Predefine global variables.
// TODO: list the keys we support
// <https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments>
///
/// 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<String, bool>);

Expand Down
154 changes: 148 additions & 6 deletions crates/oxc_linter/src/config/globals.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,165 @@
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"`.
// <https://eslint.org/docs/v8.x/use/configure/language-options#using-configuration-files-1>
#[derive(Debug, Default, Deserialize, JsonSchema)]
pub struct OxlintGlobals(FxHashMap<String, GlobalValue>);
impl OxlintGlobals {
pub fn is_enabled<Q>(&self, name: &Q) -> bool
where
String: borrow::Borrow<Q>,
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,
Writeable,
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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(GlobalValueVisitor)
}
}

impl From<bool> 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<Self, Self::Error> {
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<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
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"));
}
}
4 changes: 3 additions & 1 deletion crates/oxc_linter/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down
4 changes: 3 additions & 1 deletion crates/oxc_linter/src/config/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, DummyRule>);

gen.subschema_for::<DummyRuleMap>()
Expand Down
22 changes: 16 additions & 6 deletions crates/oxc_linter/src/snapshots/schema_json.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
22 changes: 16 additions & 6 deletions npm/oxlint/configuration_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
21 changes: 20 additions & 1 deletion tasks/website/src/linter/snapshots/schema_markdown.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)



Expand Down

0 comments on commit a0effab

Please sign in to comment.