2424 unanchoredPatternNodeTypes = []string {"pair" , "keyword_parameter" }
2525 allowedPatternQueryTypes = []string {"identifier" , "constant" , "_" , "call" , "simple_symbol" }
2626
27- classPatternErrorRegex = regexp .MustCompile (`\Aclass\s*\z` )
27+ classPatternErrorRegex = regexp .MustCompile (`\Aclass\s*\z` )
28+ superclassPatternErrorRegex = regexp .MustCompile (`<\s*\z` )
2829)
2930
3031type Pattern struct {
@@ -101,7 +102,30 @@ func (*Pattern) AnonymousParentTypes() []string {
101102}
102103
103104func (* Pattern ) IsContainer (node * tree.Node ) bool {
104- return slices .Contains (patternMatchNodeContainerTypes , node .Type ())
105+ if slices .Contains (patternMatchNodeContainerTypes , node .Type ()) {
106+ return true
107+ }
108+ // Treat body_statement as a container so pattern variables skip over it
109+ // and capture the actual content inside. In the new tree-sitter grammar,
110+ // class/module/block bodies are wrapped in body_statement.
111+ if node .Type () == "body_statement" {
112+ return true
113+ }
114+ return false
115+ }
116+
117+ func (* Pattern ) IsLeaf (node * tree.Node ) bool {
118+ // Treat bare method calls (no receiver, no arguments) as leaf nodes.
119+ // In the new tree-sitter grammar, `find_by!` is parsed as:
120+ // call(method: identifier)
121+ // We want patterns like `find_by!` to match the identifier directly,
122+ // not require a full call node structure.
123+ if node .Type () == "call" {
124+ if node .ChildByFieldName ("receiver" ) == nil && node .ChildByFieldName ("arguments" ) == nil {
125+ return true
126+ }
127+ }
128+ return false
105129}
106130
107131func (* Pattern ) IsAnchored (node * tree.Node ) (bool , bool ) {
@@ -211,7 +235,29 @@ func (*Pattern) TranslateContent(fromNodeType, toNodeType, content string) strin
211235}
212236
213237func (* Pattern ) IsRoot (node * tree.Node ) bool {
214- return ! slices .Contains ([]string {"program" }, node .Type ())
238+ // Skip program node
239+ if node .Type () == "program" {
240+ return false
241+ }
242+
243+ // Skip bare method calls (no receiver, no arguments) and use their
244+ // method identifier as the root instead. In the new tree-sitter grammar,
245+ // `find_by!` is parsed as call(method: identifier). We want auxiliary
246+ // patterns like `find_by!` to compile to an identifier query, not a call query.
247+ if node .Type () == "call" {
248+ if node .ChildByFieldName ("receiver" ) == nil && node .ChildByFieldName ("arguments" ) == nil {
249+ return false
250+ }
251+ }
252+
253+ // Skip body_statement nodes and use the actual content inside.
254+ // In the new tree-sitter grammar, class/module/block bodies are wrapped
255+ // in body_statement. Patterns should match the contents, not the wrapper.
256+ if node .Type () == "body_statement" {
257+ return false
258+ }
259+
260+ return true
215261}
216262
217263func (* Pattern ) FixupVariableDummyValue (input []byte , node * tree.Node , dummyValue string ) string {
@@ -221,9 +267,14 @@ func (*Pattern) FixupVariableDummyValue(input []byte, node *tree.Node, dummyValu
221267 }
222268
223269 errorPrefix := input [ancestor .ContentStart .Byte :node .ContentStart .Byte ]
270+ // Capitalize class name: class $<NAME>
224271 if classPatternErrorRegex .Match (errorPrefix ) {
225272 return strings .ToUpper (string (dummyValue [0 ])) + dummyValue [1 :]
226273 }
274+ // Capitalize superclass: class Foo < $<PARENT>
275+ if superclassPatternErrorRegex .Match (errorPrefix ) {
276+ return strings .ToUpper (string (dummyValue [0 ])) + dummyValue [1 :]
277+ }
227278 }
228279
229280 return dummyValue
0 commit comments