This repository has been archived by the owner on Aug 31, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 664
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rome_js_analyze): useSelfClosingElements (#2707)
* feat: 🎸 basic implement selfclosing element * fix: 🐛 clippy * test: 💍 snapshto * chore: 🤖 self closing element * refactor: 💡 do some refactor * test: 💍 add some trivia snapshot * chore: 🤖 clippy * chore: 🤖 tweak the case when open element has multiline * test: 💍 snpashot * fix: 🐛 clippy * chore: 🤖 merge main resolve * test: 💍 add another test case * fix: 🐛 extra test case * fix: 🐛 don't not edit generated code * fix: 🐛 address cr
- Loading branch information
1 parent
7be2c4e
commit a7fe396
Showing
6 changed files
with
292 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
94 changes: 94 additions & 0 deletions
94
crates/rome_js_analyze/src/analyzers/use_self_closing_elements.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
use rome_analyze::{ActionCategory, Rule, RuleCategory, RuleDiagnostic}; | ||
use rome_console::markup; | ||
use rome_diagnostics::Applicability; | ||
use rome_js_factory::make; | ||
use rome_js_syntax::{JsAnyRoot, JsSyntaxToken, JsxAnyTag, JsxElement, JsxOpeningElementFields, T}; | ||
use rome_rowan::{AstNode, AstNodeExt, AstNodeList, TriviaPiece}; | ||
|
||
use crate::JsRuleAction; | ||
|
||
pub(crate) enum UseSelfClosingElements {} | ||
|
||
impl Rule for UseSelfClosingElements { | ||
const NAME: &'static str = "useSelfClosingElements"; | ||
const CATEGORY: RuleCategory = RuleCategory::Lint; | ||
|
||
type Query = JsxElement; | ||
type State = (); | ||
|
||
fn run(n: &Self::Query) -> Option<Self::State> { | ||
if n.children().is_empty() { | ||
Some(()) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn diagnostic(node: &Self::Query, _: &Self::State) -> Option<RuleDiagnostic> { | ||
Some(RuleDiagnostic::warning( | ||
node.range(), | ||
markup! { | ||
"JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing." | ||
}, | ||
)) | ||
} | ||
|
||
fn action(root: JsAnyRoot, node: &Self::Query, _: &Self::State) -> Option<JsRuleAction> { | ||
let open_element = node.opening_element().ok()?; | ||
let JsxOpeningElementFields { | ||
l_angle_token, | ||
name, | ||
type_arguments, | ||
attributes, | ||
r_angle_token, | ||
} = open_element.as_fields(); | ||
let mut r_angle_token = r_angle_token.ok()?; | ||
let mut leading_trivia = vec![]; | ||
let mut slash_token = String::new(); | ||
|
||
for trivia in r_angle_token.leading_trivia().pieces() { | ||
leading_trivia.push(TriviaPiece::new(trivia.kind(), trivia.text_len())); | ||
slash_token.push_str(trivia.text()); | ||
} | ||
// check if previous `open_element` have a whitespace before `>` | ||
// this step make sure we could convert <div></div> -> <div /> | ||
// <div test="some""></div> -> <div test="some" /> | ||
let prev_token = r_angle_token.prev_token(); | ||
let need_extra_whitespace = prev_token | ||
.as_ref() | ||
.map_or(true, |token| !token.trailing_trivia().text().ends_with(' ')); | ||
|
||
// drop the leading trivia of `r_angle_token` | ||
r_angle_token = r_angle_token.with_leading_trivia(std::iter::empty()); | ||
|
||
if leading_trivia.is_empty() && need_extra_whitespace { | ||
slash_token.push(' '); | ||
leading_trivia.push(TriviaPiece::whitespace(1)); | ||
} | ||
|
||
slash_token += "/"; | ||
|
||
let mut self_closing_element_builder = make::jsx_self_closing_element( | ||
l_angle_token.ok()?, | ||
name.ok()?, | ||
attributes, | ||
JsSyntaxToken::new_detached(T![/], &slash_token, leading_trivia, []), | ||
r_angle_token, | ||
); | ||
if let Some(type_arguments) = type_arguments { | ||
self_closing_element_builder = | ||
self_closing_element_builder.with_type_arguments(type_arguments); | ||
} | ||
let self_closing_element = self_closing_element_builder.build(); | ||
let root = root.replace_node( | ||
JsxAnyTag::JsxElement(node.clone()), | ||
JsxAnyTag::JsxSelfClosingElement(self_closing_element), | ||
)?; | ||
Some(JsRuleAction { | ||
category: ActionCategory::QuickFix, | ||
applicability: Applicability::MaybeIncorrect, | ||
message: markup! { "Use a SelfClosingElement instead" }.to_owned(), | ||
root, | ||
}) | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
crates/rome_js_analyze/tests/specs/useSelfClosingElements.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// valid | ||
<div />; | ||
<div>child</div>; | ||
<Component />; | ||
<Component>child</Component>; | ||
<Foo.bar />; | ||
<Foo.bar>child</Foo.bar>; | ||
|
||
// invalid | ||
<div ></div>; | ||
<Component></Component>; | ||
<Foo.bar></Foo.bar>; | ||
<div | ||
|
||
></div>; | ||
|
||
<div ></div> /* comment */; | ||
/* comment */ <div ></div>; | ||
<Generic<true>></Generic>; |
173 changes: 173 additions & 0 deletions
173
crates/rome_js_analyze/tests/specs/useSelfClosingElements.tsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
--- | ||
source: crates/rome_js_analyze/tests/spec_tests.rs | ||
assertion_line: 98 | ||
expression: useSelfClosingElements.tsx | ||
--- | ||
# Input | ||
```js | ||
// valid | ||
<div />; | ||
<div>child</div>; | ||
<Component />; | ||
<Component>child</Component>; | ||
<Foo.bar />; | ||
<Foo.bar>child</Foo.bar>; | ||
// invalid | ||
<div ></div>; | ||
<Component></Component>; | ||
<Foo.bar></Foo.bar>; | ||
<div | ||
></div>; | ||
<div ></div> /* comment */; | ||
/* comment */ <div ></div>; | ||
<Generic<true>></Generic>; | ||
``` | ||
|
||
# Diagnostics | ||
``` | ||
warning[useSelfClosingElements]: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing. | ||
┌─ useSelfClosingElements.tsx:10:1 | ||
│ | ||
10 │ <div ></div>; | ||
│ -------------------------- | ||
Suggested fix: Use a SelfClosingElement instead | ||
| @@ -7,7 +7,7 @@ | ||
6 6 | <Foo.bar>child</Foo.bar>; | ||
7 7 | | ||
8 8 | // invalid | ||
9 | - <div ></div>; | ||
9 | + <div />; | ||
10 10 | <Component></Component>; | ||
11 11 | <Foo.bar></Foo.bar>; | ||
12 12 | <div | ||
``` | ||
``` | ||
warning[useSelfClosingElements]: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing. | ||
┌─ useSelfClosingElements.tsx:11:1 | ||
│ | ||
11 │ <Component></Component>; | ||
│ ----------------------- | ||
Suggested fix: Use a SelfClosingElement instead | ||
| @@ -8,7 +8,7 @@ | ||
7 7 | | ||
8 8 | // invalid | ||
9 9 | <div ></div>; | ||
10 | - <Component></Component>; | ||
10 | + <Component />; | ||
11 11 | <Foo.bar></Foo.bar>; | ||
12 12 | <div | ||
13 13 | | ||
``` | ||
``` | ||
warning[useSelfClosingElements]: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing. | ||
┌─ useSelfClosingElements.tsx:12:1 | ||
│ | ||
12 │ <Foo.bar></Foo.bar>; | ||
│ ------------------- | ||
Suggested fix: Use a SelfClosingElement instead | ||
| @@ -9,7 +9,7 @@ | ||
8 8 | // invalid | ||
9 9 | <div ></div>; | ||
10 10 | <Component></Component>; | ||
11 | - <Foo.bar></Foo.bar>; | ||
11 | + <Foo.bar />; | ||
12 12 | <div | ||
13 13 | | ||
14 14 | ></div>; | ||
``` | ||
|
||
``` | ||
warning[useSelfClosingElements]: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing. | ||
┌─ useSelfClosingElements.tsx:13:1 | ||
│ | ||
13 │ ┌ <div | ||
14 │ │ | ||
15 │ │ ></div>; | ||
│ └───────' | ||
Suggested fix: Use a SelfClosingElement instead | ||
| @@ -12,7 +12,7 @@ | ||
11 11 | <Foo.bar></Foo.bar>; | ||
12 12 | <div | ||
13 13 | | ||
14 | - ></div>; | ||
14 | + />; | ||
15 15 | | ||
16 16 | <div ></div> /* comment */; | ||
17 17 | /* comment */ <div ></div>; | ||
``` | ||
|
||
``` | ||
warning[useSelfClosingElements]: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing. | ||
┌─ useSelfClosingElements.tsx:17:1 | ||
│ | ||
17 │ <div ></div> /* comment */; | ||
│ ------------ | ||
Suggested fix: Use a SelfClosingElement instead | ||
| @@ -14,6 +14,6 @@ | ||
13 13 | | ||
14 14 | ></div>; | ||
15 15 | | ||
16 | - <div ></div> /* comment */; | ||
16 | + <div /> /* comment */; | ||
17 17 | /* comment */ <div ></div>; | ||
18 18 | <Generic<true>></Generic>; | ||
``` | ||
|
||
``` | ||
warning[useSelfClosingElements]: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing. | ||
┌─ useSelfClosingElements.tsx:18:15 | ||
│ | ||
18 │ /* comment */ <div ></div>; | ||
│ ------------ | ||
Suggested fix: Use a SelfClosingElement instead | ||
| @@ -15,5 +15,5 @@ | ||
14 14 | ></div>; | ||
15 15 | | ||
16 16 | <div ></div> /* comment */; | ||
17 | - /* comment */ <div ></div>; | ||
17 | + /* comment */ <div />; | ||
18 18 | <Generic<true>></Generic>; | ||
``` | ||
|
||
``` | ||
warning[useSelfClosingElements]: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing. | ||
┌─ useSelfClosingElements.tsx:19:1 | ||
│ | ||
19 │ <Generic<true>></Generic>; | ||
│ ------------------------- | ||
Suggested fix: Use a SelfClosingElement instead | ||
| @@ -16,4 +16,4 @@ | ||
15 15 | | ||
16 16 | <div ></div> /* comment */; | ||
17 17 | /* comment */ <div ></div>; | ||
18 | - <Generic<true>></Generic>; | ||
18 | + <Generic<true> />; | ||
``` | ||