mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(linter): add schema for react/jsx-props-no-spreading (#23276)
part of #22955 also replaced `get_tag_name` in rule with shared react helper `get_jsx_element_name`
This commit is contained in:
1 parent
08d68a5e20
commit
44806094fc
5 files changed
+174
-43
No files matched your search
@@ -176,6 +176,7 @@ export type ForbidItem2 =
|
||||
};
|
||||
export type EnforceBooleanAttribute = "always" | "never";
|
||||
export type FragmentMode = "syntax" | "element";
|
||||
export type IgnoreEnforceOption = "ignore" | "enforce";
|
||||
export type NoDidMountSetStateConfig = "allowed" | "disallow-in-func";
|
||||
export type NoWillUpdateSetStateConfig = "allowed" | "disallow-in-func";
|
||||
export type RequireFlag = "u" | "v";
|
||||
@@ -1179,7 +1180,7 @@ export interface DummyRuleMap {
|
||||
"react/jsx-no-useless-fragment"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, JsxNoUselessFragment];
|
||||
"react/jsx-pascal-case"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, JsxPascalCaseConfig];
|
||||
"react/jsx-props-no-spread-multi"?: RuleNoConfig;
|
||||
"react/jsx-props-no-spreading"?: DummyRule;
|
||||
"react/jsx-props-no-spreading"?: AllowWarnDeny | [AllowWarnDeny] | [AllowWarnDeny, JsxPropsNoSpreadingConfig];
|
||||
"react/no-array-index-key"?: RuleNoConfig;
|
||||
"react/no-children-prop"?: RuleNoConfig;
|
||||
"react/no-clone-element"?: RuleNoConfig;
|
||||
@@ -4166,6 +4167,29 @@ export interface JsxPascalCaseConfig {
|
||||
*/
|
||||
ignore?: string[];
|
||||
}
|
||||
export interface JsxPropsNoSpreadingConfig {
|
||||
/**
|
||||
* `custom` set to `ignore` will ignore all custom jsx tags like `App`, `MyCustomComponent` etc. Default is set to `enforce`.
|
||||
*/
|
||||
custom?: IgnoreEnforceOption;
|
||||
/**
|
||||
* Exceptions flip the enforcement behavior for specific components.
|
||||
* For example:
|
||||
* - If `html` is set to `ignore`, an exception for `div` will enforce the rule on `<div>` elements.
|
||||
* - If `custom` is set to `enforce`, an exception for `Foo` will ignore the rule on `<Foo>` components.
|
||||
*
|
||||
* This allows you to override the general setting for individual components.
|
||||
*/
|
||||
exceptions?: string[];
|
||||
/**
|
||||
* `explicitSpread` set to `ignore` will ignore spread operators that are explicitly listing all object properties within that spread. Default is set to `enforce`.
|
||||
*/
|
||||
explicitSpread?: IgnoreEnforceOption;
|
||||
/**
|
||||
* `html` set to `ignore` will ignore all html jsx tags like `div`, `img` etc. Default is set to `enforce`.
|
||||
*/
|
||||
html?: IgnoreEnforceOption;
|
||||
}
|
||||
export interface NoMultiCompConfig {
|
||||
/**
|
||||
* When `true`, the rule will ignore stateless components and will allow you to have multiple
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use oxc_ast::{
|
||||
AstKind,
|
||||
ast::{Expression, JSXElementName, JSXMemberExpression, JSXMemberExpressionObject},
|
||||
};
|
||||
use oxc_ast::{AstKind, ast::Expression};
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::Span;
|
||||
@@ -14,7 +11,7 @@ use crate::{
|
||||
AstNode,
|
||||
context::LintContext,
|
||||
rule::{DefaultRuleConfig, Rule},
|
||||
utils::is_react_component_name,
|
||||
utils::{get_jsx_element_name, is_react_component_name},
|
||||
};
|
||||
|
||||
fn jsx_props_no_spreading_diagnostic(span: Span) -> OxcDiagnostic {
|
||||
@@ -107,14 +104,15 @@ impl Rule for JsxPropsNoSpreading {
|
||||
return;
|
||||
};
|
||||
|
||||
let tag_name = get_tag_name(&jsx_opening_element.name);
|
||||
let tag_name = get_jsx_element_name(&jsx_opening_element.name);
|
||||
let tag_name = tag_name.as_ref();
|
||||
|
||||
// Check if first character is lowercase (HTML tag convention)
|
||||
let is_html_tag = !is_react_component_name(&tag_name);
|
||||
let is_html_tag = !is_react_component_name(tag_name);
|
||||
// Custom tags: uppercase first char OR contains '.' (member expressions like Nav.Item)
|
||||
let is_custom_tag = !is_html_tag || tag_name.contains('.');
|
||||
|
||||
let is_exception = is_exception(&tag_name, &self.exceptions);
|
||||
let is_exception = is_exception(tag_name, &self.exceptions);
|
||||
let ignore_html_tags = self.html == IgnoreEnforceOption::Ignore;
|
||||
let ignore_custom_tags = self.custom == IgnoreEnforceOption::Ignore;
|
||||
|
||||
@@ -140,36 +138,8 @@ impl Rule for JsxPropsNoSpreading {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_exception(tag: &CompactStr, exceptions: &[CompactStr]) -> bool {
|
||||
exceptions.contains(tag)
|
||||
}
|
||||
|
||||
fn get_tag_name(name: &JSXElementName<'_>) -> CompactStr {
|
||||
match name {
|
||||
JSXElementName::Identifier(ident) => ident.name.as_str().into(),
|
||||
JSXElementName::IdentifierReference(ident) => ident.name.as_str().into(),
|
||||
JSXElementName::MemberExpression(member_expr) => get_member_expr_tag_name(member_expr),
|
||||
JSXElementName::NamespacedName(namespaced_name) => format!(
|
||||
"{}:{}",
|
||||
namespaced_name.namespace.name.as_str(),
|
||||
namespaced_name.name.name.as_str()
|
||||
)
|
||||
.into(),
|
||||
JSXElementName::ThisExpression(_) => "this".into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// gets full component name, e.g. "components.Group" in <components.Group />
|
||||
fn get_member_expr_tag_name(member_expr: &JSXMemberExpression) -> CompactStr {
|
||||
let object_name = match &member_expr.object {
|
||||
JSXMemberExpressionObject::IdentifierReference(ident) => ident.name.as_str(),
|
||||
JSXMemberExpressionObject::ThisExpression(_) => "this",
|
||||
JSXMemberExpressionObject::MemberExpression(next_expr) => {
|
||||
&get_member_expr_tag_name(next_expr)
|
||||
}
|
||||
};
|
||||
|
||||
format!("{}.{}", object_name, member_expr.property.name.as_str()).into()
|
||||
fn is_exception(tag: &str, exceptions: &[CompactStr]) -> bool {
|
||||
exceptions.iter().any(|exception| exception.as_str() == tag)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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; 59] = [
|
||||
const NO_VERIFIED_VALID_SCHEMA: [&str; 58] = [
|
||||
"eslint/func-name-matching",
|
||||
"eslint/no-restricted-globals",
|
||||
"eslint/no-restricted-imports",
|
||||
@@ -43,7 +43,6 @@ const NO_VERIFIED_VALID_SCHEMA: [&str; 59] = [
|
||||
"react/jsx-handler-names",
|
||||
"react/jsx-no-script-url",
|
||||
"react/jsx-no-target-blank",
|
||||
"react/jsx-props-no-spreading",
|
||||
"react/no-did-update-set-state",
|
||||
"react/no-object-type-as-default-prop",
|
||||
"react/no-unstable-nested-components",
|
||||
|
||||
@@ -5388,7 +5388,24 @@
|
||||
"$ref": "#/definitions/RuleNoConfig"
|
||||
},
|
||||
"react/jsx-props-no-spreading": {
|
||||
"$ref": "#/definitions/DummyRule"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AllowWarnDeny"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"$ref": "#/definitions/AllowWarnDeny"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/JsxPropsNoSpreadingConfig"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"react/no-array-index-key": {
|
||||
"$ref": "#/definitions/RuleNoConfig"
|
||||
@@ -9398,6 +9415,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"IgnoreEnforceOption": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"enforce"
|
||||
]
|
||||
},
|
||||
"IgnorePattern_for_String": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -9788,6 +9812,51 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"JsxPropsNoSpreadingConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"custom": {
|
||||
"description": "`custom` set to `ignore` will ignore all custom jsx tags like `App`, `MyCustomComponent` etc. Default is set to `enforce`.",
|
||||
"default": "enforce",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IgnoreEnforceOption"
|
||||
}
|
||||
],
|
||||
"markdownDescription": "`custom` set to `ignore` will ignore all custom jsx tags like `App`, `MyCustomComponent` etc. Default is set to `enforce`."
|
||||
},
|
||||
"exceptions": {
|
||||
"description": "Exceptions flip the enforcement behavior for specific components.\nFor example:\n- If `html` is set to `ignore`, an exception for `div` will enforce the rule on `<div>` elements.\n- If `custom` is set to `enforce`, an exception for `Foo` will ignore the rule on `<Foo>` components.\n\nThis allows you to override the general setting for individual components.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"markdownDescription": "Exceptions flip the enforcement behavior for specific components.\nFor example:\n- If `html` is set to `ignore`, an exception for `div` will enforce the rule on `<div>` elements.\n- If `custom` is set to `enforce`, an exception for `Foo` will ignore the rule on `<Foo>` components.\n\nThis allows you to override the general setting for individual components."
|
||||
},
|
||||
"explicitSpread": {
|
||||
"description": "`explicitSpread` set to `ignore` will ignore spread operators that are explicitly listing all object properties within that spread. Default is set to `enforce`.",
|
||||
"default": "enforce",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IgnoreEnforceOption"
|
||||
}
|
||||
],
|
||||
"markdownDescription": "`explicitSpread` set to `ignore` will ignore spread operators that are explicitly listing all object properties within that spread. Default is set to `enforce`."
|
||||
},
|
||||
"html": {
|
||||
"description": "`html` set to `ignore` will ignore all html jsx tags like `div`, `img` etc. Default is set to `enforce`.",
|
||||
"default": "enforce",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IgnoreEnforceOption"
|
||||
}
|
||||
],
|
||||
"markdownDescription": "`html` set to `ignore` will ignore all html jsx tags like `div`, `img` etc. Default is set to `enforce`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"LibFrom": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
@@ -5392,7 +5392,24 @@ expression: json
|
||||
"$ref": "#/definitions/RuleNoConfig"
|
||||
},
|
||||
"react/jsx-props-no-spreading": {
|
||||
"$ref": "#/definitions/DummyRule"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AllowWarnDeny"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"$ref": "#/definitions/AllowWarnDeny"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/JsxPropsNoSpreadingConfig"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"react/no-array-index-key": {
|
||||
"$ref": "#/definitions/RuleNoConfig"
|
||||
@@ -9402,6 +9419,13 @@ expression: json
|
||||
}
|
||||
]
|
||||
},
|
||||
"IgnoreEnforceOption": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"enforce"
|
||||
]
|
||||
},
|
||||
"IgnorePattern_for_String": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -9792,6 +9816,51 @@ expression: json
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"JsxPropsNoSpreadingConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"custom": {
|
||||
"description": "`custom` set to `ignore` will ignore all custom jsx tags like `App`, `MyCustomComponent` etc. Default is set to `enforce`.",
|
||||
"default": "enforce",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IgnoreEnforceOption"
|
||||
}
|
||||
],
|
||||
"markdownDescription": "`custom` set to `ignore` will ignore all custom jsx tags like `App`, `MyCustomComponent` etc. Default is set to `enforce`."
|
||||
},
|
||||
"exceptions": {
|
||||
"description": "Exceptions flip the enforcement behavior for specific components.\nFor example:\n- If `html` is set to `ignore`, an exception for `div` will enforce the rule on `<div>` elements.\n- If `custom` is set to `enforce`, an exception for `Foo` will ignore the rule on `<Foo>` components.\n\nThis allows you to override the general setting for individual components.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"markdownDescription": "Exceptions flip the enforcement behavior for specific components.\nFor example:\n- If `html` is set to `ignore`, an exception for `div` will enforce the rule on `<div>` elements.\n- If `custom` is set to `enforce`, an exception for `Foo` will ignore the rule on `<Foo>` components.\n\nThis allows you to override the general setting for individual components."
|
||||
},
|
||||
"explicitSpread": {
|
||||
"description": "`explicitSpread` set to `ignore` will ignore spread operators that are explicitly listing all object properties within that spread. Default is set to `enforce`.",
|
||||
"default": "enforce",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IgnoreEnforceOption"
|
||||
}
|
||||
],
|
||||
"markdownDescription": "`explicitSpread` set to `ignore` will ignore spread operators that are explicitly listing all object properties within that spread. Default is set to `enforce`."
|
||||
},
|
||||
"html": {
|
||||
"description": "`html` set to `ignore` will ignore all html jsx tags like `div`, `img` etc. Default is set to `enforce`.",
|
||||
"default": "enforce",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IgnoreEnforceOption"
|
||||
}
|
||||
],
|
||||
"markdownDescription": "`html` set to `ignore` will ignore all html jsx tags like `div`, `img` etc. Default is set to `enforce`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"LibFrom": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
Reference in new issue
Block a user