Skip to content

Commit 2dbc91b

Browse files
committed
feat: implement noEmptyDocumentation
1 parent 8d19e7f commit 2dbc91b

33 files changed

Lines changed: 1326 additions & 0 deletions

File tree

.changeset/wise-hoops-dream.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added the new nursery rule [`noEmptyDocumentation`](https://biomejs.dev/linter/rules/no-empty-documentation), which disallows empty documentation (comments & descriptions) in HTML, CSS, JavaScript, JSON & GraphQL.
6+
7+
```html
8+
<!-- -->
9+
<input/>
10+
```
11+
12+
```css
13+
/* */
14+
.invalid {}
15+
```
16+
17+
```js
18+
/* */
19+
let invalid = 1;
20+
```
21+
22+
```jsonc
23+
{
24+
/* */
25+
"name": "John Doe"
26+
}
27+
```
28+
29+
```graphql
30+
" "
31+
query {}
32+
```

crates/biome_configuration/src/analyzer/linter/rules.rs

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/generated/linter_options_check.rs

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_css_syntax::{AnyCssRoot, CssLanguage};
6+
use biome_rowan::{AstNode, SyntaxNode, SyntaxTriviaPiece, TextRange};
7+
use biome_rule_options::no_empty_documentation::NoEmptyDocumentationOptions;
8+
9+
declare_lint_rule! {
10+
/// Disallow empty documentation.
11+
///
12+
/// Enforces that comments are not empty. This helps maintain code quality by preventing meaningless
13+
/// or placeholder comments that don't provide any documentation value.
14+
///
15+
/// Empty comments clutter the codebase and should be removed. This rule catches comments (`/* */`) that contain no meaningful content.
16+
///
17+
/// ## Examples
18+
///
19+
/// ### Invalid
20+
///
21+
/// ```css,expect_diagnostic
22+
/// /* */
23+
/// p {}
24+
/// ```
25+
///
26+
/// ### Valid
27+
///
28+
/// ```css
29+
/// /* Valid */
30+
/// p {}
31+
/// ```
32+
///
33+
pub NoEmptyDocumentation {
34+
version: "next",
35+
name: "noEmptyDocumentation",
36+
language: "css",
37+
recommended: false,
38+
sources: &[RuleSource::Stylelint("comment-no-empty").same()],
39+
}
40+
}
41+
42+
impl Rule for NoEmptyDocumentation {
43+
type Query = Ast<AnyCssRoot>;
44+
type State = Vec<TextRange>;
45+
type Signals = Option<Self::State>;
46+
type Options = NoEmptyDocumentationOptions;
47+
48+
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
49+
let node = ctx.query();
50+
let syntax_node = node.syntax();
51+
52+
let mut found = Vec::new();
53+
found.append(&mut empty_comments(syntax_node));
54+
55+
for descendant in syntax_node.descendants() {
56+
found.append(&mut empty_comments(&descendant));
57+
}
58+
59+
found.sort_unstable_by_key(|range| (range.start(), range.end()));
60+
found.dedup();
61+
62+
if found.is_empty() {
63+
return None;
64+
}
65+
66+
Some(found)
67+
}
68+
69+
fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
70+
let mut diagnostic = RuleDiagnostic::new(
71+
rule_category!(),
72+
state.first()?,
73+
markup! {
74+
"Unexpected empty documentation."
75+
},
76+
);
77+
78+
for range in &state[1..] {
79+
diagnostic = diagnostic.detail(
80+
range,
81+
markup! {
82+
"More empty documentation."
83+
},
84+
);
85+
}
86+
87+
Some(diagnostic.note(markup! {
88+
"Empty documentation provides no value. Remove them or add meaningful content."
89+
}))
90+
}
91+
}
92+
93+
fn leading_comments(syntax_node: &SyntaxNode<CssLanguage>) -> Vec<SyntaxTriviaPiece<CssLanguage>> {
94+
if let Some(token) = syntax_node.first_token() {
95+
token
96+
.leading_trivia()
97+
.pieces()
98+
.filter(|piece| piece.is_comments())
99+
.collect()
100+
} else {
101+
Vec::new()
102+
}
103+
}
104+
105+
fn trailing_comments(syntax_node: &SyntaxNode<CssLanguage>) -> Vec<SyntaxTriviaPiece<CssLanguage>> {
106+
if let Some(token) = syntax_node.last_token() {
107+
token
108+
.leading_trivia()
109+
.pieces()
110+
.filter(|piece| piece.is_comments())
111+
.collect()
112+
} else {
113+
Vec::new()
114+
}
115+
}
116+
117+
fn is_empty_comment(trivia_piece: &SyntaxTriviaPiece<CssLanguage>) -> bool {
118+
let text = trivia_piece.text().trim();
119+
120+
// Remove comment prefixes and check if anything remains
121+
let content = if let Some(stripped) = text.strip_prefix("/*") {
122+
if let Some(stripped) = stripped.strip_suffix("*/") {
123+
stripped.trim()
124+
} else {
125+
stripped.trim()
126+
}
127+
} else {
128+
text
129+
};
130+
131+
content.is_empty()
132+
}
133+
134+
fn empty_comments(syntax_node: &SyntaxNode<CssLanguage>) -> Vec<TextRange> {
135+
let mut comments = Vec::new();
136+
137+
comments.append(&mut leading_comments(syntax_node));
138+
comments.append(&mut trailing_comments(syntax_node));
139+
140+
comments
141+
.iter()
142+
.filter(|comment| is_empty_comment(comment))
143+
.map(|comment| comment.text_range())
144+
.collect()
145+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* should generate diagnostics */
2+
3+
/* */
4+
.invalid {
5+
display: flex;
6+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
source: crates/biome_css_analyze/tests/spec_tests.rs
3+
expression: invalid.css
4+
---
5+
# Input
6+
```css
7+
/* should generate diagnostics */
8+
9+
/* */
10+
.invalid {
11+
display: flex;
12+
}
13+
14+
```
15+
16+
# Diagnostics
17+
```
18+
invalid.css:3:1 lint/nursery/noEmptyDocumentation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19+
20+
i Unexpected empty documentation.
21+
22+
1 │ /* should generate diagnostics */
23+
2 │
24+
> 3 │ /* */
25+
│ ^^^^^
26+
4 │ .invalid {
27+
5display: flex;
28+
29+
i Empty documentation provides no value. Remove them or add meaningful content.
30+
31+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
32+
33+
34+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* should not generate diagnostics */
2+
3+
/* Valid */
4+
.valid {
5+
display: flex;
6+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
source: crates/biome_css_analyze/tests/spec_tests.rs
3+
expression: valid.css
4+
---
5+
# Input
6+
```css
7+
/* should not generate diagnostics */
8+
9+
/* Valid */
10+
.valid {
11+
display: flex;
12+
}
13+
14+
```

crates/biome_diagnostics_categories/src/categories.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)