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

Commit 08b49db

Browse files
authored
fix(rome_js_analyze): fix the removal of nodes from syntax lists in batch mutations (#2980)
* fix(rome_js_analyze): fix the removal of nodes from syntax lists in batch mutations * address PR review * run the configuration codegen
1 parent 2b6910e commit 08b49db

File tree

17 files changed

+301
-138
lines changed

17 files changed

+301
-138
lines changed

crates/rome_js_analyze/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ rome_control_flow = { path = "../rome_control_flow" }
1313
rome_rowan = { path = "../rome_rowan" }
1414
rome_js_semantic = { path = "../rome_js_semantic" }
1515
rome_js_syntax = { path = "../rome_js_syntax" }
16-
rome_js_parser = { path = "../rome_js_parser" }
1716
rome_js_factory = { path = "../rome_js_factory" }
1817
rome_console = { path = "../rome_console" }
1918
rome_diagnostics = { path = "../rome_diagnostics" }
@@ -24,6 +23,7 @@ iai = "0.1.1"
2423
[dev-dependencies]
2524
tests_macros = { path = "../tests_macros" }
2625
rome_text_edit = { path = "../rome_text_edit" }
26+
rome_js_parser = { path = "../rome_js_parser", features = ["tests"] }
2727
insta = { version = "1.10.0", features = ["glob"] }
2828
countme = { version = "3.0.0", features = ["enable"] }
2929
case = "1.0.0"

crates/rome_js_analyze/src/analyzers/js/no_debugger.rs

+3-15
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ use rome_analyze::{
33
};
44
use rome_console::markup;
55
use rome_diagnostics::Applicability;
6-
use rome_js_factory::make;
7-
use rome_js_syntax::{JsAnyStatement, JsDebuggerStatement, JsModuleItemList, JsStatementList, T};
6+
use rome_js_syntax::JsDebuggerStatement;
87
use rome_rowan::{AstNode, BatchMutationExt};
98

10-
use crate::JsRuleAction;
9+
use crate::{utils, JsRuleAction};
1110

1211
declare_rule! {
1312
/// Disallow the use of `debugger`
@@ -59,19 +58,8 @@ impl Rule for NoDebugger {
5958
fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> {
6059
let node = ctx.query();
6160

62-
let prev_parent = node.syntax().parent()?;
63-
6461
let mut mutation = ctx.root().begin();
65-
if JsStatementList::can_cast(prev_parent.kind())
66-
|| JsModuleItemList::can_cast(prev_parent.kind())
67-
{
68-
mutation.remove_node(node.clone());
69-
} else {
70-
mutation.replace_node(
71-
JsAnyStatement::JsDebuggerStatement(node.clone()),
72-
JsAnyStatement::JsEmptyStatement(make::js_empty_statement(make::token(T![;]))),
73-
);
74-
}
62+
utils::remove_statement(&mut mutation, node)?;
7563

7664
Some(JsRuleAction {
7765
category: ActionCategory::QuickFix,

crates/rome_js_analyze/src/analyzers/js/no_delete.rs

+41-3
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,19 @@ impl TryFrom<JsAnyExpression> for MemberExpression {
116116
member,
117117
} = expr.as_fields();
118118

119-
if object.is_err() || operator_token.is_err() || member.is_err() {
119+
match object {
120+
Ok(expr) if has_optional(&expr)? => return Err(()),
121+
Err(_) => return Err(()),
122+
_ => {}
123+
}
124+
125+
match operator_token {
126+
Ok(token) if token.kind() == T![?.] => return Err(()),
127+
Err(_) => return Err(()),
128+
_ => {}
129+
}
130+
131+
if member.is_err() {
120132
return Err(());
121133
}
122134

@@ -131,8 +143,13 @@ impl TryFrom<JsAnyExpression> for MemberExpression {
131143
r_brack_token,
132144
} = expr.as_fields();
133145

134-
if object.is_err()
135-
|| optional_chain_token.is_some()
146+
match object {
147+
Ok(expr) if has_optional(&expr)? => return Err(()),
148+
Err(_) => return Err(()),
149+
_ => {}
150+
}
151+
152+
if optional_chain_token.is_some()
136153
|| l_brack_token.is_err()
137154
|| member.is_err()
138155
|| r_brack_token.is_err()
@@ -188,3 +205,24 @@ impl TryFrom<MemberExpression> for JsAnyAssignmentPattern {
188205
}
189206
}
190207
}
208+
209+
fn has_optional(expr: &JsAnyExpression) -> Result<bool, ()> {
210+
match expr {
211+
JsAnyExpression::JsStaticMemberExpression(expr) => match expr.operator_token() {
212+
Ok(token) if token.kind() == T![?.] => Ok(true),
213+
Err(_) => Err(()),
214+
_ => match expr.object() {
215+
Ok(expr) => has_optional(&expr),
216+
Err(_) => Err(()),
217+
},
218+
},
219+
JsAnyExpression::JsComputedMemberExpression(expr) => match expr.optional_chain_token() {
220+
Some(_) => Ok(true),
221+
None => match expr.object() {
222+
Ok(expr) => has_optional(&expr),
223+
Err(_) => Err(()),
224+
},
225+
},
226+
_ => Ok(false),
227+
}
228+
}

crates/rome_js_analyze/src/analyzers/js/no_unnecessary_continue.rs

+4-15
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use rome_analyze::{
44
use rome_console::markup;
55
use rome_diagnostics::Applicability;
66
use rome_js_syntax::{JsContinueStatement, JsLabeledStatement, JsSyntaxKind, JsSyntaxNode};
7-
use rome_rowan::{AstNode, BatchMutationExt, SyntaxElement};
7+
use rome_rowan::{AstNode, BatchMutationExt};
88

9-
use crate::JsRuleAction;
9+
use crate::{utils, JsRuleAction};
1010

1111
declare_rule! {
1212
/// Avoid using unnecessary `ContinueStatement`.
@@ -103,20 +103,9 @@ impl Rule for NoUnnecessaryContinue {
103103

104104
fn action(ctx: &RuleContext<Self>, _: &Self::State) -> Option<JsRuleAction> {
105105
let node = ctx.query();
106-
let mut mutation = ctx.root().begin();
107-
108-
let syntax = node.clone().into_syntax();
109-
// Get parent of `ContinueStatement` SyntaxNode .
110-
let parent = syntax.parent()?;
111-
// Find index of `ContinueStatement` SyntaxNode in parent.
112-
let wrapper_syntax_node = SyntaxElement::Node(syntax);
113-
let index = parent
114-
.children_with_tokens()
115-
.position(|n| n == wrapper_syntax_node)?;
116-
// Remove `ContinueStatement` SyntaxNode from parent.
117-
let next_parent = parent.clone().splice_slots(index..=index, []);
118106

119-
mutation.replace_element(parent.into(), next_parent.into());
107+
let mut mutation = ctx.root().begin();
108+
utils::remove_statement(&mut mutation, node)?;
120109

121110
Some(JsRuleAction {
122111
category: ActionCategory::QuickFix,

crates/rome_js_analyze/src/utils.rs

+27
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use rome_js_factory::make;
2+
use rome_js_syntax::{JsAnyRoot, JsAnyStatement, JsLanguage, JsModuleItemList, JsStatementList, T};
3+
use rome_rowan::{AstNode, BatchMutation};
14
use std::borrow::Cow;
25

36
pub mod rename;
@@ -116,6 +119,30 @@ pub fn to_camel_case(input: &str) -> Cow<str> {
116119
}
117120
}
118121

122+
/// Utility function to remove a statement node from a syntax tree, by either
123+
/// removing the node from its parent if said parent is a statement list or
124+
/// module item list, or by replacing the statement node with an empty statement
125+
pub(crate) fn remove_statement<N>(
126+
mutation: &mut BatchMutation<JsLanguage, JsAnyRoot>,
127+
node: &N,
128+
) -> Option<()>
129+
where
130+
N: AstNode<Language = JsLanguage> + Into<JsAnyStatement>,
131+
{
132+
let parent = node.syntax().parent()?;
133+
134+
if JsStatementList::can_cast(parent.kind()) || JsModuleItemList::can_cast(parent.kind()) {
135+
mutation.remove_node(node.clone());
136+
} else {
137+
mutation.replace_node(
138+
node.clone().into(),
139+
JsAnyStatement::JsEmptyStatement(make::js_empty_statement(make::token(T![;]))),
140+
);
141+
}
142+
143+
Some(())
144+
}
145+
119146
#[test]
120147
fn ok_to_camel_case() {
121148
assert_eq!(to_camel_case("camelCase"), Cow::Borrowed("camelCase"));

crates/rome_js_analyze/tests/spec_tests.rs

+40-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ use rome_console::{
99
markup, Markup,
1010
};
1111
use rome_diagnostics::{file::SimpleFile, termcolor::NoColor, Diagnostic};
12-
use rome_js_parser::parse;
13-
use rome_rowan::Language;
12+
use rome_js_parser::{
13+
parse,
14+
test_utils::{assert_errors_are_absent, has_unknown_nodes_or_empty_slots},
15+
};
16+
use rome_js_syntax::{JsLanguage, SourceType};
17+
use rome_rowan::AstNode;
1418
use rome_text_edit::apply_indels;
1519

1620
tests_macros::gen_tests! {"tests/specs/**/*.{cjs,js,jsx,tsx,ts}", crate::run_test, "module"}
@@ -40,6 +44,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
4044
rome_js_analyze::analyze(0, &root, filter, |event| {
4145
if let Some(mut diag) = event.diagnostic() {
4246
if let Some(action) = event.action() {
47+
check_code_action(input_file, &input_code, source_type, &action);
4348
diag.suggestions.push(action.into());
4449
}
4550

@@ -48,6 +53,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
4853
}
4954

5055
if let Some(action) = event.action() {
56+
check_code_action(input_file, &input_code, source_type, &action);
5157
code_fixes.push(code_fix_to_string(&input_code, action));
5258
}
5359

@@ -130,10 +136,38 @@ fn diagnostic_to_string(name: &str, source: &str, diag: Diagnostic) -> String {
130136
text
131137
}
132138

133-
fn code_fix_to_string<L>(source: &str, action: AnalyzerAction<L>) -> String
134-
where
135-
L: Language,
136-
{
139+
fn check_code_action(
140+
path: &Path,
141+
source: &str,
142+
source_type: SourceType,
143+
action: &AnalyzerAction<JsLanguage>,
144+
) {
145+
let mut output = source.to_string();
146+
let (_, indels) = action.mutation.as_text_edits().unwrap_or_default();
147+
148+
apply_indels(&indels, &mut output);
149+
150+
let new_tree = action.mutation.clone().commit();
151+
152+
// Checks that applying the text edits returned by the BatchMutation
153+
// returns the same code as printing the modified syntax tree
154+
assert_eq!(new_tree.to_string(), output);
155+
156+
if has_unknown_nodes_or_empty_slots(new_tree.syntax()) {
157+
panic!("modified tree has unknown nodes or empty slots:\n{new_tree:#?}")
158+
}
159+
160+
// Checks the returned tree contains no missing children node
161+
if format!("{new_tree:?}").contains("missing (required)") {
162+
panic!("modified tree has missing children:\n{new_tree:#?}")
163+
}
164+
165+
// Re-parse the modified code and panic if the resulting tree has syntax errors
166+
let re_parse = parse(&output, 0, source_type);
167+
assert_errors_are_absent(&re_parse, path);
168+
}
169+
170+
fn code_fix_to_string(source: &str, action: AnalyzerAction<JsLanguage>) -> String {
137171
let mut output = source.to_string();
138172
let (_, indels) = action.mutation.as_text_edits().unwrap_or_default();
139173

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
delete a;
21
delete a.b;
32
delete a?.b;
43
delete a["b"];
54
delete a?.["b"];
5+
delete a.b.c;
6+
delete a.b?.c;
7+
delete a.b["c"];
8+
delete a.b?.["c"];
9+
delete a?.b.c;
10+
delete a?.b?.c;
11+
delete a?.b["c"];
12+
delete a?.b?.["c"];

0 commit comments

Comments
 (0)