Skip to content

Commit 0b1e194

Browse files
ematipicosiketyan
andauthored
feat(json/analyze): rule noQuickfixBiome (#6992)
Co-authored-by: siketyan <[email protected]>
1 parent 29cb6da commit 0b1e194

File tree

35 files changed

+966
-79
lines changed

35 files changed

+966
-79
lines changed

.changeset/dark-states-speak.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added a new JSON rule called `noQuickfixBiome`, which disallow the use of code action `quickfix.biome` inside code editor settings.

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

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

crates/biome_diagnostics_categories/src/categories.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ define_categories! {
170170
"lint/nursery/noNestedComponentDefinitions": "https://biomejs.dev/linter/rules/no-nested-component-definitions",
171171
"lint/nursery/noNoninteractiveElementInteractions": "https://biomejs.dev/linter/rules/no-noninteractive-element-interactions",
172172
"lint/nursery/noProcessGlobal": "https://biomejs.dev/linter/rules/no-process-global",
173+
"lint/nursery/noQuickfixBiome": "https://biomejs.dev/linter/rules/no-quickfix-biome",
173174
"lint/nursery/noReactPropAssign": "https://biomejs.dev/linter/rules/no-react-prop-assign",
174175
"lint/nursery/noReactSpecificProps": "https://biomejs.dev/linter/rules/no-react-specific-props",
175176
"lint/nursery/noRestrictedElements": "https://biomejs.dev/linter/rules/no-restricted-elements",

crates/biome_json_analyze/src/lint.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
33
//! Generated file, do not edit by hand, see `xtask/codegen`
44
5+
pub mod nursery;
56
pub mod suspicious;
6-
::biome_analyze::declare_category! { pub Lint { kind : Lint , groups : [self :: suspicious :: Suspicious ,] } }
7+
::biome_analyze::declare_category! { pub Lint { kind : Lint , groups : [self :: nursery :: Nursery , self :: suspicious :: Suspicious ,] } }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Generated file, do not edit by hand, see `xtask/codegen`
2+
3+
//! Generated file, do not edit by hand, see `xtask/codegen`
4+
5+
use biome_analyze::declare_lint_group;
6+
pub mod no_quickfix_biome;
7+
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_quickfix_biome :: NoQuickfixBiome ,] } }
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
use crate::JsonRuleAction;
2+
use biome_analyze::{Ast, FixKind, Rule, RuleDiagnostic, context::RuleContext, declare_lint_rule};
3+
use biome_console::markup;
4+
use biome_json_factory::make::{
5+
json_boolean_value, json_member, json_member_list, json_member_name, json_string_literal,
6+
json_string_value, token,
7+
};
8+
use biome_json_syntax::{AnyJsonValue, JsonMember, JsonObjectValue, T, inner_string_text};
9+
use biome_rowan::{AstNode, AstSeparatedList, BatchMutationExt, TextRange, TriviaPieceKind};
10+
use biome_rule_options::no_quickfix_biome::NoQuickfixBiomeOptions;
11+
12+
declare_lint_rule! {
13+
/// Disallow the use if `quickfix.biome` inside editor settings file.
14+
///
15+
/// The code action `quickfix.biome` can be harmful because it instructs the editors
16+
/// to apply the code fix of lint rules and code actions atomically. If multiple rules or
17+
/// actions apply a code fix to the same code span, the editor will emit invalid code.
18+
///
19+
/// The rule targets specifically VSCode settings and Zed settings. Specifically, paths that end with:
20+
/// - `.vscode/settings.json`
21+
/// - `Code/User/settings.json`
22+
/// - `.zed/settings.json`
23+
/// - `zed/settings.json`
24+
///
25+
/// ## Examples
26+
///
27+
/// ### Invalid
28+
///
29+
/// ```json,ignore
30+
/// {
31+
/// "quickfix.biome": "explicit"
32+
/// }
33+
/// ```
34+
///
35+
/// ### Valid
36+
///
37+
/// ```json,ignore
38+
/// {
39+
/// "source.fixAll.biome": "explicit"
40+
/// }
41+
/// ```
42+
///
43+
/// ## Options
44+
///
45+
/// The following options are available
46+
///
47+
/// ### `additionalPaths`
48+
///
49+
/// It's possible to specify a list of JSON paths, if your editor uses a JSON file setting that isn't supported natively by the rule.
50+
///
51+
/// If your editor uses, for example, a file called `.myEditor/file.json`, you can add `".myEditor/file.json"` to the list.
52+
/// **The rule checks if the file ends with the given paths**.
53+
///
54+
/// ```json,options
55+
/// {
56+
/// "options": {
57+
/// "additionalPaths": [".myEditor/file.json"]
58+
/// }
59+
/// }
60+
/// ```
61+
///
62+
pub NoQuickfixBiome {
63+
version: "next",
64+
name: "noQuickfixBiome",
65+
language: "json",
66+
recommended: true,
67+
fix_kind: FixKind::Safe,
68+
}
69+
}
70+
71+
const DEFAULT_PATHS: &[&str] = &[
72+
".vscode/settings.json",
73+
"Code/User/settings.json",
74+
".zed/settings.json",
75+
"zed/settings.json",
76+
];
77+
78+
impl Rule for NoQuickfixBiome {
79+
type Query = Ast<JsonMember>;
80+
type State = TextRange;
81+
type Signals = Option<Self::State>;
82+
type Options = NoQuickfixBiomeOptions;
83+
84+
fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
85+
let node = ctx.query();
86+
let path = ctx.file_path();
87+
let options = ctx.options();
88+
for default_path in DEFAULT_PATHS {
89+
if path.ends_with(default_path) {
90+
let name = node.name().ok()?;
91+
let value = name.value_token().ok()?;
92+
if inner_string_text(&value) == "quickfix.biome" {
93+
return Some(name.range());
94+
}
95+
}
96+
}
97+
98+
for default_path in options.additional_paths.iter() {
99+
if path.ends_with(default_path) {
100+
let name = node.name().ok()?;
101+
let value = name.value_token().ok()?;
102+
if inner_string_text(&value) == "quickfix.biome" {
103+
return Some(name.range());
104+
}
105+
}
106+
}
107+
108+
None
109+
}
110+
111+
fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
112+
Some(
113+
RuleDiagnostic::new(
114+
rule_category!(),
115+
state,
116+
markup! {
117+
"The use of "<Emphasis>"quickfix.biome"</Emphasis>" is deprecated."
118+
},
119+
)
120+
.note(markup! {
121+
"The code action "<Emphasis>"quickfix.biome"</Emphasis>" applies the code fix of rules and actions without being aware of each other. This might cause the emission of malformed code, especially if the code fixes are applied to the same code."
122+
}),
123+
)
124+
}
125+
126+
fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsonRuleAction> {
127+
let quick_fix_node = ctx.query();
128+
let path = ctx.file_path();
129+
let mut mutation = ctx.root().begin();
130+
let parent = quick_fix_node
131+
.syntax()
132+
.ancestors()
133+
.find_map(JsonObjectValue::cast)?;
134+
135+
let parent_list = parent.json_member_list();
136+
let has_fix_all = parent_list.iter().flatten().any(|member| {
137+
member
138+
.name()
139+
.map(|name| {
140+
name.value_token()
141+
.map(|token| inner_string_text(&token) == "source.fixAll.biome")
142+
.unwrap_or(false)
143+
})
144+
.unwrap_or(false)
145+
});
146+
147+
let new_list = parent_list
148+
.iter()
149+
.flatten()
150+
.filter(|node| node != quick_fix_node)
151+
.collect::<Vec<_>>();
152+
if has_fix_all {
153+
let mut separators = vec![];
154+
155+
for _ in 0..(new_list.len() - 1) {
156+
separators.push(token(T![,]));
157+
}
158+
159+
let new_list = json_member_list(new_list, separators);
160+
mutation.replace_node(parent_list, new_list);
161+
Some(JsonRuleAction::new(
162+
ctx.metadata().action_category(ctx.category(), ctx.group()),
163+
ctx.metadata().applicability(),
164+
markup! {
165+
"Remove the code action."
166+
},
167+
mutation,
168+
))
169+
} else {
170+
let mut new_list = vec![];
171+
new_list.push(json_member(
172+
json_member_name(json_string_literal("source.fixAll.biome")),
173+
token(T![:]).with_trailing_trivia(vec![(TriviaPieceKind::Whitespace, " ")]),
174+
if path.as_str().contains("zed") || path.as_str().contains(".zed") {
175+
AnyJsonValue::JsonBooleanValue(json_boolean_value(token(T![true])))
176+
} else {
177+
AnyJsonValue::JsonStringValue(json_string_value(json_string_literal(
178+
"explicit",
179+
)))
180+
},
181+
));
182+
let mut separators = vec![];
183+
184+
for _ in 0..(new_list.len() - 1) {
185+
separators.push(token(T![,]));
186+
}
187+
188+
let new_list = json_member_list(new_list, separators);
189+
mutation.replace_node(parent_list, new_list);
190+
Some(JsonRuleAction::new(
191+
ctx.metadata().action_category(ctx.category(), ctx.group()),
192+
ctx.metadata().applicability(),
193+
markup! {
194+
"Remove the code action."
195+
},
196+
mutation,
197+
))
198+
}
199+
}
200+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"editor.codeActionsOnSave": {
3+
"source.organizeImports.biome": "explicit",
4+
"source.fixAll.biome": "explicit",
5+
"quickfix.biome": "explicit"
6+
}
7+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
source: crates/biome_json_analyze/tests/spec_tests.rs
3+
expression: settings.json
4+
---
5+
# Input
6+
```json
7+
{
8+
"editor.codeActionsOnSave": {
9+
"source.organizeImports.biome": "explicit",
10+
"source.fixAll.biome": "explicit",
11+
"quickfix.biome": "explicit"
12+
}
13+
}
14+
15+
```
16+
17+
# Diagnostics
18+
```
19+
settings.json:5:5 lint/nursery/noQuickfixBiome FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20+
21+
i The use of quickfix.biome is deprecated.
22+
23+
3 │ "source.organizeImports.biome": "explicit",
24+
4 │ "source.fixAll.biome": "explicit",
25+
> 5 │ "quickfix.biome": "explicit"
26+
│ ^^^^^^^^^^^^^^^^
27+
6 │ }
28+
7 │ }
29+
30+
i The code action quickfix.biome applies the code fix of rules and actions without being aware of each other. This might cause the emission of malformed code, especially if the code fixes are applied to the same code.
31+
32+
i Safe fix: Remove the code action.
33+
34+
2 2 │ "editor.codeActionsOnSave": {
35+
3 3"source.organizeImports.biome": "explicit",
36+
4- ····"source.fixAll.biome""explicit",
37+
5- ····"quickfix.biome""explicit"
38+
4+ ····"source.fixAll.biome""explicit"
39+
6 5}
40+
7 6 │ }
41+
42+
43+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"editor.code_action_on_format": {
3+
"quickfix.biome": true,
4+
"source.organizeImports.biome": true,
5+
"source.fixAll.biome": true
6+
}
7+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
source: crates/biome_json_analyze/tests/spec_tests.rs
3+
expression: settings.json
4+
---
5+
# Input
6+
```json
7+
{
8+
"editor.code_action_on_format": {
9+
"quickfix.biome": true,
10+
"source.organizeImports.biome": true,
11+
"source.fixAll.biome": true
12+
}
13+
}
14+
15+
```
16+
17+
# Diagnostics
18+
```
19+
settings.json:3:5 lint/nursery/noQuickfixBiome FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20+
21+
i The use of quickfix.biome is deprecated.
22+
23+
1 │ {
24+
2"editor.code_action_on_format": {
25+
> 3 │ "quickfix.biome": true,
26+
│ ^^^^^^^^^^^^^^^^
27+
4 │ "source.organizeImports.biome": true,
28+
5 │ "source.fixAll.biome": true
29+
30+
i The code action quickfix.biome applies the code fix of rules and actions without being aware of each other. This might cause the emission of malformed code, especially if the code fixes are applied to the same code.
31+
32+
i Safe fix: Remove the code action.
33+
34+
1 1 │ {
35+
2 2 │ "editor.code_action_on_format": {
36+
3 │ - ····"quickfix.biome"true,
37+
4 │ - ····"source.organizeImports.biome"true,
38+
3 │ + ····"source.organizeImports.biome"true,
39+
5 4 │ "source.fixAll.biome": true
40+
6 5 │ }
41+
42+
43+
```

0 commit comments

Comments
 (0)