Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
12 changes: 10 additions & 2 deletions tasks/website/src/linter/json_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl Renderer {
}
if let Some(subschemas) = &schema.subschemas {
let key = parent_key.unwrap_or("");
return self.render_sub_schema(depth, key, subschemas);
self.render_sub_schema(depth, key, subschemas);
}
vec![]
}
Expand All @@ -171,7 +171,9 @@ impl Renderer {
.map(|schema| {
let schema = Self::get_schema_object(schema);
let schema = self.get_referenced_schema(schema);
self.render_schema(depth + 1, key, schema)
let mut section = self.render_schema(depth + 1, key, schema);
section.sanitize();
section
})
.collect::<Vec<Section>>();
}
Expand Down Expand Up @@ -203,6 +205,12 @@ impl Root {
}
}

impl Section {
fn sanitize(&mut self) {
sanitize(&mut self.description);
}
}

fn sanitize(s: &mut String) {
let Some(start) = s.find("```json") else { return };
let start = start + 7;
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