diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index e22e359dc4a72..87b118f42b92e 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -68,6 +68,9 @@ use super::{ #[serde(default, deny_unknown_fields)] #[non_exhaustive] pub struct Oxlintrc { + /// Schema URI for editor tooling. + #[serde(rename = "$schema", default, skip_serializing_if = "Option::is_none")] + pub schema: Option, /// Enabled built-in plugins for Oxlint. /// You can view the list of available plugins on /// [the website](https://oxc.rs/docs/guide/usage/linter/plugins.html#supported-plugins). @@ -221,23 +224,6 @@ impl Oxlintrc { let mut json = serde_json::to_value(&schema).unwrap(); - // inject "$schema" at the root for editor support without changing the struct - if let serde_json::Value::Object(map) = &mut json { - let props = map - .entry("properties") - .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new())); - if let serde_json::Value::Object(props) = props { - props.insert( - "$schema".to_string(), - serde_json::json!({ - "type": "string", - "description": "Schema URI for editor tooling", - "markdownDescription": "Schema URI for editor tooling" - }), - ); - } - } - // Inject markdown descriptions for better editor support Self::inject_markdown_descriptions(&mut json); @@ -327,7 +313,10 @@ impl Oxlintrc { (None, None) => None, }; + let schema = self.schema.clone().or(other.schema); + Oxlintrc { + schema, plugins, external_plugins, categories, @@ -481,4 +470,42 @@ mod test { let merged = config1.merge(config2); assert_eq!(merged.external_plugins.unwrap().len(), 2); } + + #[test] + fn test_oxlintrc_schema_field() { + // Test that $schema field is accepted and deserialized correctly + let config: Oxlintrc = serde_json::from_str( + r#"{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "rules": { + "no-console": "warn" + } + }"#, + ) + .unwrap(); + assert_eq!( + config.schema, + Some("./node_modules/oxlint/configuration_schema.json".to_string()) + ); + + // Test that config without $schema still works + let config_without_schema: Oxlintrc = serde_json::from_str(r#"{"rules": {}}"#).unwrap(); + assert_eq!(config_without_schema.schema, None); + + // Test serialization - $schema should be skipped when None + let serialized = serde_json::to_string(&config_without_schema).unwrap(); + assert!(!serialized.contains("$schema")); + + // Test merge - self takes priority over other + let config1: Oxlintrc = serde_json::from_str(r#"{"$schema": "schema1.json"}"#).unwrap(); + let config2: Oxlintrc = serde_json::from_str(r#"{"$schema": "schema2.json"}"#).unwrap(); + let merged = config1.merge(config2); + assert_eq!(merged.schema, Some("schema1.json".to_string())); + + // Test merge - when self has no schema, use other's schema + let config1: Oxlintrc = serde_json::from_str(r"{}").unwrap(); + let config2: Oxlintrc = serde_json::from_str(r#"{"$schema": "schema2.json"}"#).unwrap(); + let merged = config1.merge(config2); + assert_eq!(merged.schema, Some("schema2.json".to_string())); + } } diff --git a/crates/oxc_linter/src/snapshots/schema_json.snap b/crates/oxc_linter/src/snapshots/schema_json.snap index 758f6bdc81906..b91c5412e4ec7 100644 --- a/crates/oxc_linter/src/snapshots/schema_json.snap +++ b/crates/oxc_linter/src/snapshots/schema_json.snap @@ -8,6 +8,14 @@ expression: json "description": "Oxlint Configuration File\n\nThis configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`).\n\nUsage: `oxlint -c oxlintrc.json --import-plugin`\n\n::: danger NOTE\n\nOnly the `.json` format is supported. You can use comments in configuration files.\n\n:::\n\nExample\n\n`.oxlintrc.json`\n\n```json\n{\n\"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n\"plugins\": [\"import\", \"typescript\", \"unicorn\"],\n\"env\": {\n\"browser\": true\n},\n\"globals\": {\n\"foo\": \"readonly\"\n},\n\"settings\": {\n},\n\"rules\": {\n\"eqeqeq\": \"warn\",\n\"import/no-cycle\": \"error\",\n\"react/self-closing-comp\": [\"error\", { \"html\": false }]\n},\n\"overrides\": [\n{\n\"files\": [\"*.test.ts\", \"*.spec.ts\"],\n\"rules\": {\n\"@typescript-eslint/no-explicit-any\": \"off\"\n}\n}\n]\n}\n```", "type": "object", "properties": { + "$schema": { + "description": "Schema URI for editor tooling.", + "type": [ + "string", + "null" + ], + "markdownDescription": "Schema URI for editor tooling." + }, "categories": { "default": {}, "allOf": [ @@ -136,11 +144,6 @@ expression: json "$ref": "#/definitions/OxlintSettings" } ] - }, - "$schema": { - "type": "string", - "description": "Schema URI for editor tooling", - "markdownDescription": "Schema URI for editor tooling" } }, "additionalProperties": false, diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index d6a43b36e5d86..632a273f3e1e6 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -4,6 +4,14 @@ "description": "Oxlint Configuration File\n\nThis configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`).\n\nUsage: `oxlint -c oxlintrc.json --import-plugin`\n\n::: danger NOTE\n\nOnly the `.json` format is supported. You can use comments in configuration files.\n\n:::\n\nExample\n\n`.oxlintrc.json`\n\n```json\n{\n\"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n\"plugins\": [\"import\", \"typescript\", \"unicorn\"],\n\"env\": {\n\"browser\": true\n},\n\"globals\": {\n\"foo\": \"readonly\"\n},\n\"settings\": {\n},\n\"rules\": {\n\"eqeqeq\": \"warn\",\n\"import/no-cycle\": \"error\",\n\"react/self-closing-comp\": [\"error\", { \"html\": false }]\n},\n\"overrides\": [\n{\n\"files\": [\"*.test.ts\", \"*.spec.ts\"],\n\"rules\": {\n\"@typescript-eslint/no-explicit-any\": \"off\"\n}\n}\n]\n}\n```", "type": "object", "properties": { + "$schema": { + "description": "Schema URI for editor tooling.", + "type": [ + "string", + "null" + ], + "markdownDescription": "Schema URI for editor tooling." + }, "categories": { "default": {}, "allOf": [ @@ -132,11 +140,6 @@ "$ref": "#/definitions/OxlintSettings" } ] - }, - "$schema": { - "type": "string", - "description": "Schema URI for editor tooling", - "markdownDescription": "Schema URI for editor tooling" } }, "additionalProperties": false, diff --git a/tasks/website_linter/src/snapshots/schema_markdown.snap b/tasks/website_linter/src/snapshots/schema_markdown.snap index c8f5671fc84a6..d181086790b76 100644 --- a/tasks/website_linter/src/snapshots/schema_markdown.snap +++ b/tasks/website_linter/src/snapshots/schema_markdown.snap @@ -62,6 +62,14 @@ Example ``` +## $schema + +type: `string | null` + + +Schema URI for editor tooling. + + ## categories type: `object`