diff --git a/internal/languages/php/.snapshots/TestPattern-parameter_names_are_unanchored b/internal/languages/php/.snapshots/TestPattern-parameter_names_are_unanchored new file mode 100644 index 000000000..9748880a9 --- /dev/null +++ b/internal/languages/php/.snapshots/TestPattern-parameter_names_are_unanchored @@ -0,0 +1,12 @@ +(*builder.Result)({ + Query: (string) (len=210) "([(class_declaration . (_) . [(declaration_list [(method_declaration [ (visibility_modifier )] (_) . [(formal_parameters . [(simple_parameter . (_) (_) @match)] . )] [ (compound_statement )])] )] .)] @root)", + VariableNames: ([]string) (len=1) { + (string) (len=1) "_" + }, + ParamToVariable: (map[string]string) { + }, + EqualParams: ([][]string) , + ParamToContent: (map[string]map[string]string) { + }, + RootVariable: (*language.PatternVariable)() +}) diff --git a/internal/languages/php/pattern/pattern.go b/internal/languages/php/pattern/pattern.go index 608d699ff..de4b854b5 100644 --- a/internal/languages/php/pattern/pattern.go +++ b/internal/languages/php/pattern/pattern.go @@ -20,6 +20,9 @@ var ( patternMatchNodeContainerTypes = []string{"formal_parameters", "simple_parameter", "argument"} allowedPatternQueryTypes = []string{"_"} + + functionRegex = regexp.MustCompile(`\bfunction\b`) + parameterTypeRegex = regexp.MustCompile(`[,(]\s*(public|private|protected|var)?\s*\z`) ) type Pattern struct { @@ -39,13 +42,26 @@ func (*Pattern) FixupMissing(node *tree.Node) string { } func (*Pattern) FixupVariableDummyValue(input []byte, node *tree.Node, dummyValue string) string { + addDollar := false + if parent := node.Parent(); parent != nil { - if parent.Type() == "named_type" || - (parent.Type() == "ERROR" && parent.Parent() != nil && parent.Parent().Type() == "declaration_list") { - return "$" + dummyValue + if parent.Type() == "named_type" { + addDollar = true + } + + if parent.Type() == "ERROR" && parent.Parent() != nil && parent.Parent().Type() == "declaration_list" { + parentContent := []byte(parent.Content()) + parentPrefix := string(parentContent[:node.ContentStart.Byte-parent.ContentStart.Byte]) + + isFunctionName := functionRegex.MatchString(parentPrefix) && !strings.Contains(parentPrefix, "(") + addDollar = !isFunctionName && !parameterTypeRegex.MatchString(parentPrefix) } } + if addDollar { + return "$" + dummyValue + } + return dummyValue } @@ -143,6 +159,12 @@ func (*Pattern) IsAnchored(node *tree.Node) (bool, bool) { return true, true } + // optional type on parameters + if slices.Contains([]string{"property_promotion_parameter", "simple_parameter"}, parent.Type()) && + node == parent.ChildByFieldName("name") { + return false, true + } + if parent.Type() == "method_declaration" { // visibility if node == parent.ChildByFieldName("name") { diff --git a/internal/languages/php/php_test.go b/internal/languages/php/php_test.go index 832639599..12abf34bd 100644 --- a/internal/languages/php/php_test.go +++ b/internal/languages/php/php_test.go @@ -38,6 +38,11 @@ func TestPattern(t *testing.T) { public $$<_>; } `}, + {"parameter names are unanchored", ` + class $<_> { + public function $<_>($<_> $$<_>) {} + } + `}, } { t.Run(test.name, func(tt *testing.T) { result, err := patternquerybuilder.Build(php.Get(), test.pattern, "")