Skip to content

Commit 051e22e

Browse files
authored
refactor: diagnostics code cleanup, test coverage (ribru17#134)
1 parent 55773eb commit 051e22e

2 files changed

Lines changed: 183 additions & 59 deletions

File tree

queries/query/diagnostics.scm

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
(string
77
(string_content) @node.anon))
88

9+
(program
10+
(definition) @pattern)
11+
912
(named_node
1013
.
1114
name: (identifier) @node.named)
@@ -40,3 +43,21 @@
4043
(#eq? @_type "!"))
4144

4245
(escape_sequence) @escape
46+
47+
(parameters
48+
(capture) @capture.reference)
49+
50+
(named_node
51+
(capture) @capture.definition)
52+
53+
(list
54+
(capture) @capture.definition)
55+
56+
(anonymous_node
57+
(capture) @capture.definition)
58+
59+
(grouping
60+
(capture) @capture.definition)
61+
62+
(missing_node
63+
(capture) @capture.definition)

src/handlers/diagnostic.rs

Lines changed: 162 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,28 @@ static DIAGNOSTICS_QUERY: LazyLock<Query> = LazyLock::new(|| {
3838
});
3939
static DEFINITIONS_QUERY: LazyLock<Query> =
4040
LazyLock::new(|| Query::new(&QUERY_LANGUAGE, "(program (definition) @def)").unwrap());
41+
static CAPTURE_DEFINITIONS_QUERY: LazyLock<Query> = LazyLock::new(|| {
42+
Query::new(
43+
&QUERY_LANGUAGE,
44+
"
45+
(named_node
46+
(capture) @capture.definition)
47+
48+
(list
49+
(capture) @capture.definition)
50+
51+
(anonymous_node
52+
(capture) @capture.definition)
53+
54+
(grouping
55+
(capture) @capture.definition)
56+
57+
(missing_node
58+
(capture) @capture.definition)
59+
",
60+
)
61+
.unwrap()
62+
});
4163

4264
pub async fn diagnostic(
4365
backend: &Backend,
@@ -170,35 +192,13 @@ pub async fn get_diagnostics(
170192
let rope = &document.rope;
171193
let tree = &document.tree;
172194

173-
let provider = TextProviderRope(rope);
174-
let mut cursor = QueryCursor::new();
175-
let mut matches = cursor.matches(&DEFINITIONS_QUERY, tree.root_node(), &provider);
176-
while let Some(match_) = matches.next() {
177-
for capture in match_.captures {
178-
let mut cursor = QueryCursor::new();
179-
let query = &CAPTURES_QUERY;
180-
let mut matches = cursor.matches(query, capture.node, &provider);
181-
if matches.next().is_none() {
182-
diagnostics.push(Diagnostic {
183-
message: String::from(
184-
"This pattern has no captures, and will not be processed",
185-
),
186-
range: capture.node.lsp_range(rope),
187-
severity: WARNING_SEVERITY,
188-
tags: Some(vec![DiagnosticTag::UNNECESSARY]),
189-
data: serde_json::to_value(CodeActions::Remove).ok(),
190-
..Default::default()
191-
});
192-
}
193-
}
194-
}
195-
196195
let valid_predicates = &options.valid_predicates;
197196
let valid_directives = &options.valid_directives;
198197
let symbols = language_data.as_deref().map(|ld| &ld.symbols_set);
199198
let fields = language_data.as_deref().map(|ld| &ld.fields_set);
200199
let supertypes = language_data.as_deref().map(|ld| &ld.supertype_map);
201200
let mut cursor = QueryCursor::new();
201+
let mut helper_cursor = QueryCursor::new();
202202
let mut tree_cursor = tree.root_node().walk();
203203
let provider = &TextProviderRope(rope);
204204
let mut matches = cursor.matches(&DIAGNOSTICS_QUERY, tree.root_node(), provider);
@@ -307,44 +307,34 @@ pub async fn get_diagnostics(
307307
range,
308308
..Default::default()
309309
}),
310-
"capture" => {
311-
if capture
312-
.node
313-
.parent()
314-
.is_some_and(|p| p.kind() == "parameters")
315-
{
316-
let mut cursor = QueryCursor::new();
317-
let query = &CAPTURES_QUERY;
318-
let mut matches = cursor.matches(
319-
query,
320-
tree.root_node()
321-
.child_with_descendant(capture.node)
322-
.unwrap(),
323-
provider,
324-
);
325-
let mut valid = false;
326-
// NOTE: Find a simpler way to do this?
327-
'outer: while let Some(m) = matches.next() {
328-
for cap in m.captures {
329-
if let Some(parent) = cap.node.parent() {
330-
if parent.kind() != "parameters"
331-
&& cap.node.text(rope) == capture_text
332-
{
333-
valid = true;
334-
break 'outer;
335-
}
336-
}
310+
"capture.reference" => {
311+
let mut matches = helper_cursor.matches(
312+
&CAPTURE_DEFINITIONS_QUERY,
313+
tree.root_node()
314+
.child_with_descendant(capture.node)
315+
.unwrap(),
316+
provider,
317+
);
318+
let mut valid = false;
319+
'outer: while let Some(m) = matches.next() {
320+
for cap in m.captures {
321+
if cap.node.text(rope) == capture_text {
322+
valid = true;
323+
break 'outer;
337324
}
338325
}
339-
if !valid {
340-
diagnostics.push(Diagnostic {
341-
message: format!("Undeclared capture: \"{capture_text}\""),
342-
severity: ERROR_SEVERITY,
343-
range,
344-
..Default::default()
345-
});
346-
}
347-
} else if let Some(suffix) = capture_text.strip_prefix("@") {
326+
}
327+
if !valid {
328+
diagnostics.push(Diagnostic {
329+
message: format!("Undeclared capture: \"{capture_text}\""),
330+
severity: ERROR_SEVERITY,
331+
range,
332+
..Default::default()
333+
});
334+
}
335+
}
336+
"capture.definition" => {
337+
if let Some(suffix) = capture_text.strip_prefix("@") {
348338
if !suffix.starts_with('_')
349339
&& valid_captures
350340
.is_some_and(|c| !c.contains_key(&String::from(suffix)))
@@ -399,6 +389,22 @@ pub async fn get_diagnostics(
399389
});
400390
}
401391
},
392+
"pattern" => {
393+
let mut matches =
394+
helper_cursor.matches(&CAPTURES_QUERY, capture.node, provider);
395+
if matches.next().is_none() {
396+
diagnostics.push(Diagnostic {
397+
message: String::from(
398+
"This pattern has no captures, and will not be processed",
399+
),
400+
range,
401+
severity: WARNING_SEVERITY,
402+
tags: Some(vec![DiagnosticTag::UNNECESSARY]),
403+
data: serde_json::to_value(CodeActions::Remove).ok(),
404+
..Default::default()
405+
});
406+
}
407+
}
402408
_ => {}
403409
}
404410
}
@@ -573,6 +579,103 @@ mod test {
573579
..Default::default()
574580
}],
575581
)]
582+
#[case(
583+
r#"("*" @constant
584+
(#match? @constant "^[A-Z][A-Z\\d_]*$"))"#,
585+
&[SymbolInfo { label: String::from("*"), named: false }],
586+
&["operator"],
587+
&["supertype"],
588+
Options {
589+
valid_captures: HashMap::from([(String::from("test"),
590+
BTreeMap::from([
591+
(String::from("variable"), String::default()),
592+
(String::from("variable.parameter"), String::default()),
593+
]))]),
594+
..Default::default()
595+
},
596+
&[Diagnostic {
597+
range: Range {
598+
start: Position::new(0, 5),
599+
end: Position::new(0, 14),
600+
},
601+
severity: WARNING_SEVERITY,
602+
message: String::from("Unsupported capture name \"@constant\" (fix available)"),
603+
data: Some(serde_json::to_value(CodeActions::PrefixUnderscore).unwrap()),
604+
..Default::default()
605+
}],
606+
)]
607+
#[case(
608+
r#"(MISSING "*") @keyword"#,
609+
&[SymbolInfo { label: String::from("*"), named: false }],
610+
&["operator"],
611+
&["supertype"],
612+
Options {
613+
valid_captures: HashMap::from([(String::from("test"),
614+
BTreeMap::from([
615+
(String::from("variable"), String::default()),
616+
(String::from("variable.parameter"), String::default()),
617+
]))]),
618+
..Default::default()
619+
},
620+
&[Diagnostic {
621+
range: Range {
622+
start: Position::new(0, 14),
623+
end: Position::new(0, 22),
624+
},
625+
severity: WARNING_SEVERITY,
626+
message: String::from("Unsupported capture name \"@keyword\" (fix available)"),
627+
data: Some(serde_json::to_value(CodeActions::PrefixUnderscore).unwrap()),
628+
..Default::default()
629+
}],
630+
)]
631+
#[case(
632+
r#"[ "*" ] @keyword"#,
633+
&[SymbolInfo { label: String::from("*"), named: false }],
634+
&["operator"],
635+
&["supertype"],
636+
Options {
637+
valid_captures: HashMap::from([(String::from("test"),
638+
BTreeMap::from([
639+
(String::from("variable"), String::default()),
640+
(String::from("variable.parameter"), String::default()),
641+
]))]),
642+
..Default::default()
643+
},
644+
&[Diagnostic {
645+
range: Range {
646+
start: Position::new(0, 8),
647+
end: Position::new(0, 16),
648+
},
649+
severity: WARNING_SEVERITY,
650+
message: String::from("Unsupported capture name \"@keyword\" (fix available)"),
651+
data: Some(serde_json::to_value(CodeActions::PrefixUnderscore).unwrap()),
652+
..Default::default()
653+
}],
654+
)]
655+
#[case(
656+
r#"("*") @keyword"#,
657+
&[SymbolInfo { label: String::from("*"), named: false }],
658+
&["operator"],
659+
&["supertype"],
660+
Options {
661+
valid_captures: HashMap::from([(String::from("test"),
662+
BTreeMap::from([
663+
(String::from("variable"), String::default()),
664+
(String::from("variable.parameter"), String::default()),
665+
]))]),
666+
..Default::default()
667+
},
668+
&[Diagnostic {
669+
range: Range {
670+
start: Position::new(0, 6),
671+
end: Position::new(0, 14),
672+
},
673+
severity: WARNING_SEVERITY,
674+
message: String::from("Unsupported capture name \"@keyword\" (fix available)"),
675+
data: Some(serde_json::to_value(CodeActions::PrefixUnderscore).unwrap()),
676+
..Default::default()
677+
}],
678+
)]
576679
#[case(
577680
r#"((identifierr) @_constant
578681
(#match? @_constant "^[A-Z][A-Z\\d_]*$"))

0 commit comments

Comments
 (0)