@@ -38,6 +38,28 @@ static DIAGNOSTICS_QUERY: LazyLock<Query> = LazyLock::new(|| {
3838} ) ;
3939static 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
4264pub 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