From df35502bb71d30ebffe04f2853b99eb1ee3f49ad Mon Sep 17 00:00:00 2001 From: kaioduarte Date: Sun, 20 Nov 2022 17:25:41 +0000 Subject: [PATCH 1/2] feat(rome_js_analyze): add noHeaderScope rule --- .../src/categories.rs | 1 + .../rome_js_analyze/src/analyzers/nursery.rs | 3 +- .../src/analyzers/nursery/no_header_scope.rs | 101 ++++++++++++++++++ .../specs/nursery/noHeaderScope/invalid.jsx | 6 ++ .../nursery/noHeaderScope/invalid.jsx.snap | 93 ++++++++++++++++ .../specs/nursery/noHeaderScope/valid.jsx | 4 + .../nursery/noHeaderScope/valid.jsx.snap | 15 +++ .../src/configuration/linter/rules.rs | 5 +- editors/vscode/configuration_schema.json | 11 ++ npm/backend-jsonrpc/src/workspace.ts | 5 + npm/rome/configuration_schema.json | 11 ++ website/src/pages/lint/rules/index.mdx | 6 ++ website/src/pages/lint/rules/noHeaderScope.md | 61 +++++++++++ 13 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx.snap create mode 100644 website/src/pages/lint/rules/noHeaderScope.md diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 9ca0a5755b9..8500e8ba204 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -81,6 +81,7 @@ define_dategories! { "lint/nursery/noDupeKeys":"https://docs.rome.tools/lint/rules/noDupeKeys", "lint/nursery/noEmptyInterface": "https://docs.rome.tools/lint/rules/noEmptyInterface", "lint/nursery/noExplicitAny": "https://docs.rome.tools/lint/rules/noExplicitAny", + "lint/nursery/noHeaderScope": "https://docs.rome.tools/lint/rules/noHeaderScope", "lint/nursery/noInvalidConstructorSuper": "https://docs.rome.tools/lint/rules/noInvalidConstructorSuper", "lint/nursery/noPrecisionLoss": "https://docs.rome.tools/lint/rules/noPrecisionLoss", "lint/nursery/noUnsafeFinally": "https://docs.rome.tools/lint/rules/noUnsafeFinally", diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index bebcc783c9b..a872c2bccb5 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -6,10 +6,11 @@ mod no_conditional_assignment; mod no_dupe_keys; mod no_empty_interface; mod no_explicit_any; +mod no_header_scope; mod no_invalid_constructor_super; mod no_precision_loss; mod no_unsafe_finally; mod use_flat_map; mod use_numeric_literals; mod use_valid_for_direction; -declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_banned_types :: NoBannedTypes , self :: no_conditional_assignment :: NoConditionalAssignment , self :: no_dupe_keys :: NoDupeKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_explicit_any :: NoExplicitAny , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_precision_loss :: NoPrecisionLoss , self :: no_unsafe_finally :: NoUnsafeFinally , self :: use_flat_map :: UseFlatMap , self :: use_numeric_literals :: UseNumericLiterals , self :: use_valid_for_direction :: UseValidForDirection ,] } } +declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_banned_types :: NoBannedTypes , self :: no_conditional_assignment :: NoConditionalAssignment , self :: no_dupe_keys :: NoDupeKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_explicit_any :: NoExplicitAny , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_precision_loss :: NoPrecisionLoss , self :: no_unsafe_finally :: NoUnsafeFinally , self :: use_flat_map :: UseFlatMap , self :: use_numeric_literals :: UseNumericLiterals , self :: use_valid_for_direction :: UseValidForDirection ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs new file mode 100644 index 00000000000..a5d77ec9ebe --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs @@ -0,0 +1,101 @@ +use rome_analyze::context::RuleContext; +use rome_analyze::{declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_diagnostics::Applicability; +use rome_js_syntax::{JsxAttribute, JsxOpeningElement, JsxSelfClosingElement}; +use rome_rowan::{declare_node_union, AstNode, BatchMutationExt}; + +use crate::JsRuleAction; + +declare_rule! { + /// Check that the scope attribute is only used on `th` elements. + /// + /// ESLint Equivalent: [scope](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/scope.md) + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```jsx,expect_diagnostic + ///
+ /// ``` + /// + /// ```jsx,expect_diagnostic + ///
+ /// ``` + /// + /// ### Valid + /// + /// ```jsx + /// + /// ``` + /// + /// ```jsx + /// + /// ``` + pub(crate) NoHeaderScope { + version: "11.0.0", + name: "noHeaderScope", + recommended: false, + } +} + +declare_node_union! { + pub(crate) JsxAnyElement = JsxOpeningElement | JsxSelfClosingElement +} + +impl Rule for NoHeaderScope { + type Query = Ast; + type State = JsxAttribute; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + + match node { + JsxAnyElement::JsxOpeningElement(element) => { + let binding = element.name().ok()?; + let jsx_name = binding.as_jsx_name()?; + + if jsx_name.text() != "th" { + return element.find_attribute_by_name("scope").ok()?; + } + } + JsxAnyElement::JsxSelfClosingElement(element) => { + let binding = element.name().ok()?; + let jsx_name = binding.as_jsx_name()?; + + if jsx_name.text() != "th" { + return element.find_attribute_by_name("scope").ok()?; + } + } + } + + None + } + + fn diagnostic(_: &RuleContext, jsx_attr: &Self::State) -> Option { + let diagnostic = RuleDiagnostic::new( + rule_category!(), + jsx_attr.range(), + markup! {"Avoid using the ""scope"" attribute on elements other than ""th"" elements."} + .to_owned(), + ); + + Some(diagnostic) + } + + fn action(ctx: &RuleContext, jsx_attr: &Self::State) -> Option { + let mut mutation = ctx.root().begin(); + + mutation.remove_node(jsx_attr.clone()); + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::MaybeIncorrect, + message: markup! { "Remove the ""scope"" attribute." }.to_owned(), + mutation, + }) + } +} diff --git a/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx new file mode 100644 index 00000000000..c1869c6c294 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx @@ -0,0 +1,6 @@ +<> +
+
+
+
+ diff --git a/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx.snap b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx.snap new file mode 100644 index 00000000000..ad8ad952f39 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/invalid.jsx.snap @@ -0,0 +1,93 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 73 +expression: invalid.jsx +--- +# Input +```js +<> +
+
+
+
+ + +``` + +# Diagnostics +``` +invalid.jsx:2:7 lint/nursery/noHeaderScope FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the scope attribute on elements other than th elements. + + 1 │ <> + > 2 │
+ │ ^^^^^^^^^^^^^ + 3 │
+ 4 │
+ + i Suggested fix: Remove the scope attribute. + + 2 │ → + │ -------------- + +``` + +``` +invalid.jsx:3:7 lint/nursery/noHeaderScope FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the scope attribute on elements other than th elements. + + 1 │ <> + 2 │
+ > 3 │
+ │ ^^^^^^^^^^^^^ + 4 │
+ 5 │
+ + i Suggested fix: Remove the scope attribute. + + 3 │ →
+ │ ------------- + +``` + +``` +invalid.jsx:4:7 lint/nursery/noHeaderScope FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the scope attribute on elements other than th elements. + + 2 │
+ 3 │
+ > 4 │
+ │ ^^^^^^^^^^^ + 5 │
+ 6 │ + + i Suggested fix: Remove the scope attribute. + + 4 │ → + │ ------------ + +``` + +``` +invalid.jsx:5:7 lint/nursery/noHeaderScope FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid using the scope attribute on elements other than th elements. + + 3 │
+ 4 │
+ > 5 │
+ │ ^^^^^^^^^^^ + 6 │ + 7 │ + + i Suggested fix: Remove the scope attribute. + + 5 │ →
+ │ ----------- + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx new file mode 100644 index 00000000000..53e6e92dac8 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx @@ -0,0 +1,4 @@ +<> + + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx.snap b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx.snap new file mode 100644 index 00000000000..6650ca4e75d --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noHeaderScope/valid.jsx.snap @@ -0,0 +1,15 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 73 +expression: valid.jsx +--- +# Input +```js +<> + + + + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index eeb7b88fce3..a7f30f313fe 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -741,6 +741,8 @@ struct NurserySchema { no_empty_interface: Option, #[doc = "Disallow the any type usage"] no_explicit_any: Option, + #[doc = "Check that the scope attribute is only used on th elements."] + no_header_scope: Option, #[doc = "Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors."] no_invalid_constructor_super: Option, #[doc = "Disallow literal numbers that lose precision"] @@ -762,13 +764,14 @@ struct NurserySchema { } impl Nursery { const CATEGORY_NAME: &'static str = "nursery"; - pub(crate) const CATEGORY_RULES: [&'static str; 15] = [ + pub(crate) const CATEGORY_RULES: [&'static str; 16] = [ "noBannedTypes", "noConditionalAssignment", "noConstAssign", "noDupeKeys", "noEmptyInterface", "noExplicitAny", + "noHeaderScope", "noInvalidConstructorSuper", "noPrecisionLoss", "noUnsafeFinally", diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 86d77a9d128..20319d63c55 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -819,6 +819,17 @@ } ] }, + "noHeaderScope": { + "description": "Check that the scope attribute is only used on th elements.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noInvalidConstructorSuper": { "description": "Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 4d2ce1360d9..926d357c338 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -371,6 +371,10 @@ export interface Nursery { * Disallow the any type usage */ noExplicitAny?: RuleConfiguration; + /** + * Check that the scope attribute is only used on th elements. + */ + noHeaderScope?: RuleConfiguration; /** * Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors. */ @@ -621,6 +625,7 @@ export type Category = | "lint/nursery/noDupeKeys" | "lint/nursery/noEmptyInterface" | "lint/nursery/noExplicitAny" + | "lint/nursery/noHeaderScope" | "lint/nursery/noInvalidConstructorSuper" | "lint/nursery/noPrecisionLoss" | "lint/nursery/noUnsafeFinally" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 86d77a9d128..20319d63c55 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -819,6 +819,17 @@ } ] }, + "noHeaderScope": { + "description": "Check that the scope attribute is only used on th elements.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noInvalidConstructorSuper": { "description": "Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index a086610bb9e..94e5ce8d1a1 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -479,6 +479,12 @@ Disallow the declaration of empty interfaces. Disallow the any type usage
+

+ noHeaderScope +

+Check that the scope attribute is only used on th elements. +
+

noInvalidConstructorSuper

diff --git a/website/src/pages/lint/rules/noHeaderScope.md b/website/src/pages/lint/rules/noHeaderScope.md new file mode 100644 index 00000000000..bf412936dcd --- /dev/null +++ b/website/src/pages/lint/rules/noHeaderScope.md @@ -0,0 +1,61 @@ +--- +title: Lint Rule noHeaderScope +parent: lint/rules/index +--- + +# noHeaderScope (since v11.0.0) + +Check that the scope attribute is only used on `th` elements. + +ESLint Equivalent: [scope](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/scope.md) + +## Examples + +### Invalid + +```jsx +
+``` + +
nursery/noHeaderScope.js:1:6 lint/nursery/noHeaderScope  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Avoid using the scope attribute on elements other than th elements.
+  
+  > 1 │ <div scope={scope} />
+        ^^^^^^^^^^^^^
+    2 │ 
+  
+   Suggested fix: Remove the scope attribute.
+  
+    1 │ <div·scope={scope}·/>
+       --------------  
+
+ +```jsx +
+``` + +
nursery/noHeaderScope.js:1:6 lint/nursery/noHeaderScope  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Avoid using the scope attribute on elements other than th elements.
+  
+  > 1 │ <div scope="col" />
+        ^^^^^^^^^^^
+    2 │ 
+  
+   Suggested fix: Remove the scope attribute.
+  
+    1 │ <div·scope="col"·/>
+       ------------  
+
+ +### Valid + +```jsx + +``` + +```jsx + +``` + From 8c655f2b8e9d8430d584645c49361bdd66a57021 Mon Sep 17 00:00:00 2001 From: kaioduarte Date: Mon, 21 Nov 2022 13:42:33 +0000 Subject: [PATCH 2/2] chore: address PR feedback --- .../src/analyzers/nursery/no_header_scope.rs | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs index a5d77ec9ebe..9bb011da811 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_header_scope.rs @@ -2,7 +2,9 @@ use rome_analyze::context::RuleContext; use rome_analyze::{declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic}; use rome_console::markup; use rome_diagnostics::Applicability; -use rome_js_syntax::{JsxAttribute, JsxOpeningElement, JsxSelfClosingElement}; +use rome_js_syntax::{ + JsxAnyElementName, JsxAttribute, JsxAttributeList, JsxOpeningElement, JsxSelfClosingElement, +}; use rome_rowan::{declare_node_union, AstNode, BatchMutationExt}; use crate::JsRuleAction; @@ -44,41 +46,43 @@ declare_node_union! { pub(crate) JsxAnyElement = JsxOpeningElement | JsxSelfClosingElement } +impl JsxAnyElement { + fn name(&self) -> Option { + match self { + JsxAnyElement::JsxOpeningElement(element) => element.name().ok(), + JsxAnyElement::JsxSelfClosingElement(element) => element.name().ok(), + } + } +} + impl Rule for NoHeaderScope { - type Query = Ast; - type State = JsxAttribute; + type Query = Ast; + type State = (); type Signals = Option; type Options = (); fn run(ctx: &RuleContext) -> Self::Signals { - let node = ctx.query(); + let attr = ctx.query(); - match node { - JsxAnyElement::JsxOpeningElement(element) => { - let binding = element.name().ok()?; - let jsx_name = binding.as_jsx_name()?; + if attr.name().ok()?.syntax().text_trimmed() != "scope" { + return None; + } - if jsx_name.text() != "th" { - return element.find_attribute_by_name("scope").ok()?; - } - } - JsxAnyElement::JsxSelfClosingElement(element) => { - let binding = element.name().ok()?; - let jsx_name = binding.as_jsx_name()?; + let jsx_element = attr + .parent::()? + .parent::()?; - if jsx_name.text() != "th" { - return element.find_attribute_by_name("scope").ok()?; - } - } + if jsx_element.name()?.as_jsx_name()?.syntax().text_trimmed() != "th" { + return Some(()); } None } - fn diagnostic(_: &RuleContext, jsx_attr: &Self::State) -> Option { + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { let diagnostic = RuleDiagnostic::new( rule_category!(), - jsx_attr.range(), + ctx.query().range(), markup! {"Avoid using the ""scope"" attribute on elements other than ""th"" elements."} .to_owned(), ); @@ -86,10 +90,10 @@ impl Rule for NoHeaderScope { Some(diagnostic) } - fn action(ctx: &RuleContext, jsx_attr: &Self::State) -> Option { + fn action(ctx: &RuleContext, _: &Self::State) -> Option { let mut mutation = ctx.root().begin(); - mutation.remove_node(jsx_attr.clone()); + mutation.remove_node(ctx.query().clone()); Some(JsRuleAction { category: ActionCategory::QuickFix,