Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
feat(rome_js_analyze): useSelfClosingElements (#2707)
Browse files Browse the repository at this point in the history
* 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
IWANABETHATGUY committed Jun 17, 2022
1 parent 7be2c4e commit a7fe396
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 1 deletion.
2 changes: 2 additions & 0 deletions crates/rome_js_analyze/src/analyzers.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 94 additions & 0 deletions crates/rome_js_analyze/src/analyzers/use_self_closing_elements.rs
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,
})
}
}
3 changes: 3 additions & 0 deletions crates/rome_js_analyze/src/registry.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/rome_js_analyze/tests/spec_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use rome_diagnostics::{file::SimpleFile, termcolor::NoColor, Diagnostic};
use rome_js_parser::parse;
use rome_rowan::{AstNode, Language};

tests_macros::gen_tests! {"tests/specs/**/*.{js,jsx}", crate::run_test, "module"}
tests_macros::gen_tests! {"tests/specs/**/*.{js,jsx,tsx}", crate::run_test, "module"}

fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
register_leak_checker();
Expand Down
19 changes: 19 additions & 0 deletions crates/rome_js_analyze/tests/specs/useSelfClosingElements.tsx
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 crates/rome_js_analyze/tests/specs/useSelfClosingElements.tsx.snap
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> />;
```

0 comments on commit a7fe396

Please sign in to comment.