Skip to content

Commit d90c728

Browse files
refactor(extgen): share signature and parameter parsing helpers (#2376)
1 parent 68573a9 commit d90c728

5 files changed

Lines changed: 88 additions & 130 deletions

File tree

internal/extgen/classparser.go

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import (
1414

1515
var phpClassRegex = regexp.MustCompile(`//\s*export_php:class\s+(\w+)`)
1616
var phpMethodRegex = regexp.MustCompile(`//\s*export_php:method\s+(\w+)::([^{}\n]+)(?:\s*{\s*})?`)
17-
var methodSignatureRegex = regexp.MustCompile(`(\w+)\s*\(([^)]*)\)\s*:\s*(\??[\w|]+)`)
18-
var methodParamTypeNameRegex = regexp.MustCompile(`(\??[\w|]+)\s+\$?(\w+)`)
1917

2018
type exportDirective struct {
2119
line int
@@ -290,79 +288,22 @@ func (cp *classParser) parseMethods(filename string) (methods []phpClassMethod,
290288
}
291289

292290
func (cp *classParser) parseMethodSignature(className, signature string) (*phpClassMethod, error) {
293-
matches := methodSignatureRegex.FindStringSubmatch(signature)
294-
295-
if len(matches) != 4 {
296-
return nil, fmt.Errorf("invalid method signature format")
297-
}
298-
299-
methodName := matches[1]
300-
paramsStr := strings.TrimSpace(matches[2])
301-
returnTypeStr := strings.TrimSpace(matches[3])
302-
303-
isReturnNullable := strings.HasPrefix(returnTypeStr, "?")
304-
returnType := strings.TrimPrefix(returnTypeStr, "?")
305-
306-
var params []phpParameter
307-
if paramsStr != "" {
308-
paramParts := strings.SplitSeq(paramsStr, ",")
309-
for part := range paramParts {
310-
param, err := cp.parseMethodParameter(strings.TrimSpace(part))
311-
if err != nil {
312-
return nil, fmt.Errorf("parsing parameter '%s': %w", part, err)
313-
}
314-
315-
params = append(params, param)
316-
}
291+
name, params, returnType, nullable, err := parseSignatureParams(signature)
292+
if err != nil {
293+
return nil, err
317294
}
318295

319296
return &phpClassMethod{
320-
Name: methodName,
321-
PhpName: methodName,
297+
Name: name,
298+
PhpName: name,
322299
ClassName: className,
323300
Signature: signature,
324301
Params: params,
325302
ReturnType: phpType(returnType),
326-
isReturnNullable: isReturnNullable,
303+
isReturnNullable: nullable,
327304
}, nil
328305
}
329306

330-
func (cp *classParser) parseMethodParameter(paramStr string) (phpParameter, error) {
331-
parts := strings.Split(paramStr, "=")
332-
typePart := strings.TrimSpace(parts[0])
333-
334-
param := phpParameter{HasDefault: len(parts) > 1}
335-
336-
if param.HasDefault {
337-
param.DefaultValue = cp.sanitizeDefaultValue(strings.TrimSpace(parts[1]))
338-
}
339-
340-
matches := methodParamTypeNameRegex.FindStringSubmatch(typePart)
341-
342-
if len(matches) < 3 {
343-
return phpParameter{}, fmt.Errorf("invalid parameter format: %s", paramStr)
344-
}
345-
346-
typeStr := strings.TrimSpace(matches[1])
347-
param.Name = strings.TrimSpace(matches[2])
348-
param.IsNullable = strings.HasPrefix(typeStr, "?")
349-
param.PhpType = phpType(strings.TrimPrefix(typeStr, "?"))
350-
351-
return param, nil
352-
}
353-
354-
func (cp *classParser) sanitizeDefaultValue(value string) string {
355-
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
356-
return value
357-
}
358-
359-
if strings.ToLower(value) == "null" {
360-
return "null"
361-
}
362-
363-
return strings.Trim(value, `'"`)
364-
}
365-
366307
func (cp *classParser) extractGoMethodFunction(scanner *bufio.Scanner, firstLine string) (string, error) {
367308
goFunc := firstLine + "\n"
368309
braceCount := 1

internal/extgen/classparser_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,9 @@ func TestMethodParameterParsing(t *testing.T) {
244244
},
245245
}
246246

247-
parser := classParser{}
248247
for _, tt := range tests {
249248
t.Run(tt.name, func(t *testing.T) {
250-
param, err := parser.parseMethodParameter(tt.paramStr)
249+
param, err := parseParameter(tt.paramStr)
251250

252251
if tt.expectError {
253252
assert.Error(t, err, "Expected error for parameter '%s', but got none", tt.paramStr)

internal/extgen/funcparser.go

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
)
1111

1212
var phpFuncRegex = regexp.MustCompile(`//\s*export_php:function\s+([^{}\n]+)(?:\s*{\s*})?`)
13-
var signatureRegex = regexp.MustCompile(`(\w+)\s*\(([^)]*)\)\s*:\s*(\??[\w|]+)`)
14-
var typeNameRegex = regexp.MustCompile(`(\??[\w|]+)\s+\$?(\w+)`)
1513

1614
type FuncParser struct{}
1715

@@ -112,71 +110,16 @@ func (fp *FuncParser) extractGoFunction(scanner *bufio.Scanner, firstLine string
112110
}
113111

114112
func (fp *FuncParser) parseSignature(signature string) (*phpFunction, error) {
115-
matches := signatureRegex.FindStringSubmatch(signature)
116-
117-
if len(matches) != 4 {
118-
return nil, fmt.Errorf("invalid signature format")
119-
}
120-
121-
name := matches[1]
122-
paramsStr := strings.TrimSpace(matches[2])
123-
returnTypeStr := strings.TrimSpace(matches[3])
124-
125-
isReturnNullable := strings.HasPrefix(returnTypeStr, "?")
126-
returnType := strings.TrimPrefix(returnTypeStr, "?")
127-
128-
var params []phpParameter
129-
if paramsStr != "" {
130-
paramParts := strings.SplitSeq(paramsStr, ",")
131-
for part := range paramParts {
132-
param, err := fp.parseParameter(strings.TrimSpace(part))
133-
if err != nil {
134-
return nil, fmt.Errorf("parsing parameter '%s': %w", part, err)
135-
}
136-
params = append(params, param)
137-
}
113+
name, params, returnType, nullable, err := parseSignatureParams(signature)
114+
if err != nil {
115+
return nil, err
138116
}
139117

140118
return &phpFunction{
141119
Name: name,
142120
Signature: signature,
143121
Params: params,
144122
ReturnType: phpType(returnType),
145-
IsReturnNullable: isReturnNullable,
123+
IsReturnNullable: nullable,
146124
}, nil
147125
}
148-
149-
func (fp *FuncParser) parseParameter(paramStr string) (phpParameter, error) {
150-
parts := strings.Split(paramStr, "=")
151-
typePart := strings.TrimSpace(parts[0])
152-
153-
param := phpParameter{HasDefault: len(parts) > 1}
154-
155-
if param.HasDefault {
156-
param.DefaultValue = fp.sanitizeDefaultValue(strings.TrimSpace(parts[1]))
157-
}
158-
159-
matches := typeNameRegex.FindStringSubmatch(typePart)
160-
161-
if len(matches) < 3 {
162-
return phpParameter{}, fmt.Errorf("invalid parameter format: %s", paramStr)
163-
}
164-
165-
typeStr := strings.TrimSpace(matches[1])
166-
param.Name = strings.TrimSpace(matches[2])
167-
param.IsNullable = strings.HasPrefix(typeStr, "?")
168-
param.PhpType = phpType(strings.TrimPrefix(typeStr, "?"))
169-
170-
return param, nil
171-
}
172-
173-
func (fp *FuncParser) sanitizeDefaultValue(value string) string {
174-
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
175-
return value
176-
}
177-
if strings.ToLower(value) == "null" {
178-
return "null"
179-
}
180-
181-
return strings.Trim(value, `'"`)
182-
}

internal/extgen/funcparser_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,9 @@ func TestParameterParsing(t *testing.T) {
270270
},
271271
}
272272

273-
parser := &FuncParser{}
274273
for _, tt := range tests {
275274
t.Run(tt.name, func(t *testing.T) {
276-
param, err := parser.parseParameter(tt.paramStr)
275+
param, err := parseParameter(tt.paramStr)
277276

278277
if tt.expectError {
279278
assert.Error(t, err, "parseParameter() expected error but got none")

internal/extgen/signature.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package extgen
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strings"
7+
)
8+
9+
// Shared patterns for both function and method signatures.
10+
var (
11+
signaturePattern = regexp.MustCompile(`(\w+)\s*\(([^)]*)\)\s*:\s*(\??[\w|]+)`)
12+
paramPattern = regexp.MustCompile(`(\??[\w|]+)\s+\$?(\w+)`)
13+
)
14+
15+
// parseSignatureParams splits a "name(params): returnType" signature into its parts.
16+
// Returns name, slice of parameters, return type (without leading "?") and whether it was nullable.
17+
func parseSignatureParams(signature string) (name string, params []phpParameter, returnType string, nullable bool, err error) {
18+
matches := signaturePattern.FindStringSubmatch(signature)
19+
if len(matches) != 4 {
20+
return "", nil, "", false, fmt.Errorf("invalid signature format")
21+
}
22+
23+
name = matches[1]
24+
paramsStr := strings.TrimSpace(matches[2])
25+
returnTypeStr := strings.TrimSpace(matches[3])
26+
27+
nullable = strings.HasPrefix(returnTypeStr, "?")
28+
returnType = strings.TrimPrefix(returnTypeStr, "?")
29+
30+
if paramsStr != "" {
31+
for part := range strings.SplitSeq(paramsStr, ",") {
32+
param, perr := parseParameter(strings.TrimSpace(part))
33+
if perr != nil {
34+
return "", nil, "", false, fmt.Errorf("parsing parameter '%s': %w", part, perr)
35+
}
36+
params = append(params, param)
37+
}
38+
}
39+
40+
return name, params, returnType, nullable, nil
41+
}
42+
43+
// parseParameter parses a single PHP parameter declaration like "?int $name = 42".
44+
func parseParameter(paramStr string) (phpParameter, error) {
45+
parts := strings.SplitN(paramStr, "=", 2)
46+
typePart := strings.TrimSpace(parts[0])
47+
48+
param := phpParameter{HasDefault: len(parts) > 1}
49+
if param.HasDefault {
50+
param.DefaultValue = sanitizeDefaultValue(strings.TrimSpace(parts[1]))
51+
}
52+
53+
matches := paramPattern.FindStringSubmatch(typePart)
54+
if len(matches) < 3 {
55+
return phpParameter{}, fmt.Errorf("invalid parameter format: %s", paramStr)
56+
}
57+
58+
typeStr := strings.TrimSpace(matches[1])
59+
param.Name = strings.TrimSpace(matches[2])
60+
param.IsNullable = strings.HasPrefix(typeStr, "?")
61+
param.PhpType = phpType(strings.TrimPrefix(typeStr, "?"))
62+
63+
return param, nil
64+
}
65+
66+
// sanitizeDefaultValue normalizes a PHP default value literal: keeps array literals,
67+
// preserves "null", and strips surrounding quotes for scalar strings.
68+
func sanitizeDefaultValue(value string) string {
69+
if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") {
70+
return value
71+
}
72+
if strings.EqualFold(value, "null") {
73+
return "null"
74+
}
75+
return strings.Trim(value, `'"`)
76+
}

0 commit comments

Comments
 (0)