From 0708b5a233bfb248ca80e43754307bf794b86b5f Mon Sep 17 00:00:00 2001 From: Mikhail Baev Date: Fri, 12 Jun 2026 14:05:03 +0500 Subject: [PATCH] feat(linter): add schema for `react/jsx-filename-extension` (#23315) issue #22955 --------- Co-authored-by: Cameron --- .../oxlint/src-js/package/config.generated.ts | 19 ++++- .../src/rules/react/jsx_filename_extension.rs | 18 ++--- crates/oxc_linter/src/utils/schemars.rs | 3 +- npm/oxlint/configuration_schema.json | 72 ++++++++++++++++++- .../src/snapshots/schema_json.snap | 72 ++++++++++++++++++- 5 files changed, 170 insertions(+), 14 deletions(-) diff --git a/apps/oxlint/src-js/package/config.generated.ts b/apps/oxlint/src-js/package/config.generated.ts index 37bca1df27..430c2bea0f 100644 --- a/apps/oxlint/src-js/package/config.generated.ts +++ b/apps/oxlint/src-js/package/config.generated.ts @@ -176,6 +176,7 @@ export type ForbidItem2 = message?: string; }; export type EnforceBooleanAttribute = "always" | "never"; +export type JsxFilenameExtensionAllowMode = "always" | "as-needed"; export type FragmentMode = "syntax" | "element"; export type IgnoreEnforceOption = "ignore" | "enforce"; export type NoDidMountSetStateConfig = "allowed" | "disallow-in-func"; @@ -1202,7 +1203,7 @@ export interface DummyRuleMap { | [AllowWarnDeny, EnforceBooleanAttribute] | [AllowWarnDeny, EnforceBooleanAttribute, JsxBooleanValueOptions]; "react/jsx-curly-brace-presence"?: DummyRule; - "react/jsx-filename-extension"?: DummyRule; + "react/jsx-filename-extension"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, JsxFilenameExtensionConfig]; "react/jsx-fragments"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, FragmentMode]; "react/jsx-handler-names"?: DummyRule; "react/jsx-key"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, JsxKeyConfig]; @@ -4152,6 +4153,22 @@ export interface JsxBooleanValueOptions { */ never?: string[]; } +export interface JsxFilenameExtensionConfig { + /** + * When to allow a JSX filename extension. By default all files may have a JSX extension. + * Set this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax. + */ + allow?: JsxFilenameExtensionAllowMode; + /** + * The set of allowed file extensions. + * Can include or exclude the leading dot (e.g., "jsx" and ".jsx" are both valid). + */ + extensions?: string[]; + /** + * If enabled, files that do not contain code (i.e. are empty, contain only whitespaces or comments) will not be rejected. + */ + ignoreFilesWithoutCode?: boolean; +} export interface JsxKeyConfig { /** * When true, check fragment shorthand `<>` for keys diff --git a/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs b/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs index d8b367ca29..7ac4ea42de 100644 --- a/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs +++ b/crates/oxc_linter/src/rules/react/jsx_filename_extension.rs @@ -34,15 +34,15 @@ fn extension_only_for_jsx_diagnostic(ext: &str, span: Span) -> OxcDiagnostic { #[derive(Debug, Default, Clone, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] -enum AllowType { - /// Always allow a `.jsx` file extension. +enum JsxFilenameExtensionAllowMode { + /// Always allow a JSX filename extension. #[default] Always, - /// Only allow `.jsx` file extension for files that contain JSX syntax. + /// Only allow JSX filename extension for files that contain JSX syntax. AsNeeded, } -impl AllowType { +impl JsxFilenameExtensionAllowMode { pub fn from(raw: &str) -> Self { match raw { "as-needed" => Self::AsNeeded, @@ -55,11 +55,11 @@ impl AllowType { pub struct JsxFilenameExtension(Box); #[derive(Debug, Clone, JsonSchema, Deserialize, Serialize)] -#[serde(rename_all = "camelCase", default)] +#[serde(rename_all = "camelCase", default, deny_unknown_fields)] pub struct JsxFilenameExtensionConfig { /// When to allow a JSX filename extension. By default all files may have a JSX extension. /// Set this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax. - allow: AllowType, + allow: JsxFilenameExtensionAllowMode, /// The set of allowed file extensions. /// Can include or exclude the leading dot (e.g., "jsx" and ".jsx" are both valid). extensions: Vec, @@ -70,7 +70,7 @@ pub struct JsxFilenameExtensionConfig { impl Default for JsxFilenameExtensionConfig { fn default() -> Self { Self { - allow: AllowType::Always, + allow: JsxFilenameExtensionAllowMode::Always, extensions: vec![CompactStr::from("jsx")], ignore_files_without_code: false, } @@ -132,7 +132,7 @@ impl Rule for JsxFilenameExtension { let allow = config .and_then(|config| config.get("allow")) .and_then(Value::as_str) - .map(AllowType::from) + .map(JsxFilenameExtensionAllowMode::from) .unwrap_or_default(); let extensions = config @@ -173,7 +173,7 @@ impl Rule for JsxFilenameExtension { return; } - if matches!(self.allow, AllowType::AsNeeded) { + if matches!(self.allow, JsxFilenameExtensionAllowMode::AsNeeded) { if self.ignore_files_without_code && ctx.nodes().len() == 1 { return; } diff --git a/crates/oxc_linter/src/utils/schemars.rs b/crates/oxc_linter/src/utils/schemars.rs index 3900d60d7a..f2633174d0 100644 --- a/crates/oxc_linter/src/utils/schemars.rs +++ b/crates/oxc_linter/src/utils/schemars.rs @@ -13,7 +13,7 @@ use crate::rules::RuleEnum; /// These should return the DummyRule struct/schema instead of the actual rule struct/schema, /// until we verify that the rule's schema is valid and can be generated by schemars. #[cfg(feature = "ruledocs")] -const NO_VERIFIED_VALID_SCHEMA: [&str; 40] = [ +const NO_VERIFIED_VALID_SCHEMA: [&str; 39] = [ "eslint/func-name-matching", "eslint/no-restricted-globals", "eslint/no-restricted-imports", @@ -36,7 +36,6 @@ const NO_VERIFIED_VALID_SCHEMA: [&str; 40] = [ "node/callback-return", "react/exhaustive-deps", "react/jsx-curly-brace-presence", - "react/jsx-filename-extension", "react/jsx-handler-names", "react/jsx-no-script-url", "react/jsx-no-target-blank", diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index d67e7ba5dd..48fa032c0e 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -5572,7 +5572,24 @@ "$ref": "#/definitions/DummyRule" }, "react/jsx-filename-extension": { - "$ref": "#/definitions/DummyRule" + "anyOf": [ + { + "$ref": "#/definitions/AllowWarnDeny" + }, + { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AllowWarnDeny" + }, + { + "$ref": "#/definitions/JsxFilenameExtensionConfig" + } + ], + "maxItems": 2, + "minItems": 1 + } + ] }, "react/jsx-fragments": { "anyOf": [ @@ -10301,6 +10318,59 @@ }, "additionalProperties": false }, + "JsxFilenameExtensionAllowMode": { + "oneOf": [ + { + "description": "Always allow a JSX filename extension.", + "type": "string", + "enum": [ + "always" + ], + "markdownDescription": "Always allow a JSX filename extension." + }, + { + "description": "Only allow JSX filename extension for files that contain JSX syntax.", + "type": "string", + "enum": [ + "as-needed" + ], + "markdownDescription": "Only allow JSX filename extension for files that contain JSX syntax." + } + ] + }, + "JsxFilenameExtensionConfig": { + "type": "object", + "properties": { + "allow": { + "description": "When to allow a JSX filename extension. By default all files may have a JSX extension.\nSet this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax.", + "default": "always", + "allOf": [ + { + "$ref": "#/definitions/JsxFilenameExtensionAllowMode" + } + ], + "markdownDescription": "When to allow a JSX filename extension. By default all files may have a JSX extension.\nSet this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax." + }, + "extensions": { + "description": "The set of allowed file extensions.\nCan include or exclude the leading dot (e.g., \"jsx\" and \".jsx\" are both valid).", + "default": [ + "jsx" + ], + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The set of allowed file extensions.\nCan include or exclude the leading dot (e.g., \"jsx\" and \".jsx\" are both valid)." + }, + "ignoreFilesWithoutCode": { + "description": "If enabled, files that do not contain code (i.e. are empty, contain only whitespaces or comments) will not be rejected.", + "default": false, + "type": "boolean", + "markdownDescription": "If enabled, files that do not contain code (i.e. are empty, contain only whitespaces or comments) will not be rejected." + } + }, + "additionalProperties": false + }, "JsxKeyConfig": { "type": "object", "properties": { diff --git a/tasks/website_linter/src/snapshots/schema_json.snap b/tasks/website_linter/src/snapshots/schema_json.snap index c17ca2fd3f..f3d573435d 100644 --- a/tasks/website_linter/src/snapshots/schema_json.snap +++ b/tasks/website_linter/src/snapshots/schema_json.snap @@ -5576,7 +5576,24 @@ expression: json "$ref": "#/definitions/DummyRule" }, "react/jsx-filename-extension": { - "$ref": "#/definitions/DummyRule" + "anyOf": [ + { + "$ref": "#/definitions/AllowWarnDeny" + }, + { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AllowWarnDeny" + }, + { + "$ref": "#/definitions/JsxFilenameExtensionConfig" + } + ], + "maxItems": 2, + "minItems": 1 + } + ] }, "react/jsx-fragments": { "anyOf": [ @@ -10305,6 +10322,59 @@ expression: json }, "additionalProperties": false }, + "JsxFilenameExtensionAllowMode": { + "oneOf": [ + { + "description": "Always allow a JSX filename extension.", + "type": "string", + "enum": [ + "always" + ], + "markdownDescription": "Always allow a JSX filename extension." + }, + { + "description": "Only allow JSX filename extension for files that contain JSX syntax.", + "type": "string", + "enum": [ + "as-needed" + ], + "markdownDescription": "Only allow JSX filename extension for files that contain JSX syntax." + } + ] + }, + "JsxFilenameExtensionConfig": { + "type": "object", + "properties": { + "allow": { + "description": "When to allow a JSX filename extension. By default all files may have a JSX extension.\nSet this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax.", + "default": "always", + "allOf": [ + { + "$ref": "#/definitions/JsxFilenameExtensionAllowMode" + } + ], + "markdownDescription": "When to allow a JSX filename extension. By default all files may have a JSX extension.\nSet this to `as-needed` to only allow JSX file extensions in files that contain JSX syntax." + }, + "extensions": { + "description": "The set of allowed file extensions.\nCan include or exclude the leading dot (e.g., \"jsx\" and \".jsx\" are both valid).", + "default": [ + "jsx" + ], + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "The set of allowed file extensions.\nCan include or exclude the leading dot (e.g., \"jsx\" and \".jsx\" are both valid)." + }, + "ignoreFilesWithoutCode": { + "description": "If enabled, files that do not contain code (i.e. are empty, contain only whitespaces or comments) will not be rejected.", + "default": false, + "type": "boolean", + "markdownDescription": "If enabled, files that do not contain code (i.e. are empty, contain only whitespaces or comments) will not be rejected." + } + }, + "additionalProperties": false + }, "JsxKeyConfig": { "type": "object", "properties": {