@@ -7,20 +7,15 @@ import (
77 "strings"
88
99 "golang.org/x/tools/go/analysis"
10- )
1110
12- // FuncSpec holds parsed components of a derive function specification.
13- type FuncSpec struct {
14- PkgPath string
15- TypeName string // empty for package-level functions
16- FuncName string
17- }
11+ "github.com/mpyw/goroutinectx/internal/funcspec"
12+ )
1813
1914// Matcher provides OR/AND matching for derive function specifications.
2015// The check passes if ANY group is fully satisfied (OR semantics).
2116// A group is satisfied if ALL functions in that group are called (AND semantics).
2217type Matcher struct {
23- OrGroups [][]FuncSpec
18+ OrGroups [][]funcspec. Spec
2419 Original string
2520}
2621
@@ -40,15 +35,15 @@ func NewMatcher(deriveFuncsStr string) *Matcher {
4035 }
4136
4237 // Split by plus (AND within group)
43- var andGroup []FuncSpec
38+ var andGroup []funcspec. Spec
4439
4540 for andPart := range strings .SplitSeq (orPart , "+" ) {
4641 andPart = strings .TrimSpace (andPart )
4742 if andPart == "" {
4843 continue
4944 }
5045
51- spec := parseFunc (andPart )
46+ spec := funcspec . Parse (andPart )
5247 andGroup = append (andGroup , spec )
5348 }
5449
@@ -60,41 +55,6 @@ func NewMatcher(deriveFuncsStr string) *Matcher {
6055 return m
6156}
6257
63- // parseFunc parses a single derive function string into components.
64- // Format: "pkg/path.Func" or "pkg/path.Type.Method".
65- func parseFunc (s string ) FuncSpec {
66- spec := FuncSpec {}
67-
68- lastDot := strings .LastIndex (s , "." )
69- if lastDot == - 1 {
70- spec .FuncName = s
71-
72- return spec
73- }
74-
75- spec .FuncName = s [lastDot + 1 :]
76- prefix := s [:lastDot ]
77-
78- // Check if there's another dot (indicating Type.Method)
79- // Type names start with uppercase in Go.
80- secondLastDot := strings .LastIndex (prefix , "." )
81- if secondLastDot != - 1 {
82- potentialTypeName := prefix [secondLastDot + 1 :]
83- if len (potentialTypeName ) > 0 && potentialTypeName [0 ] >= 'A' && potentialTypeName [0 ] <= 'Z' {
84- spec .PkgPath = prefix [:secondLastDot ]
85- spec .TypeName = potentialTypeName
86- } else {
87- spec .PkgPath = prefix
88- spec .TypeName = ""
89- }
90- } else {
91- spec .PkgPath = prefix
92- spec .TypeName = ""
93- }
94-
95- return spec
96- }
97-
9858// SatisfiesAnyGroup checks if the AST node satisfies ANY of the OR groups.
9959func (m * Matcher ) SatisfiesAnyGroup (pass * analysis.Pass , node ast.Node ) bool {
10060 calledFuncs := collectCalledFuncs (pass , node )
@@ -118,7 +78,7 @@ func (m *Matcher) IsEmpty() bool {
11878func (m * Matcher ) MatchesFunc (fn * types.Func ) bool {
11979 for _ , andGroup := range m .OrGroups {
12080 for _ , spec := range andGroup {
121- if matchesSpec (fn , spec ) {
81+ if spec . Matches (fn ) {
12282 return true
12383 }
12484 }
@@ -142,7 +102,7 @@ func collectCalledFuncs(pass *analysis.Pass, node ast.Node) []*types.Func {
142102 return true
143103 }
144104
145- if fn := extractFunc (pass , call ); fn != nil {
105+ if fn := funcspec . ExtractFunc (pass , call ); fn != nil {
146106 funcs = append (funcs , fn )
147107 }
148108
@@ -152,34 +112,8 @@ func collectCalledFuncs(pass *analysis.Pass, node ast.Node) []*types.Func {
152112 return funcs
153113}
154114
155- // extractFunc extracts the types.Func from a call expression.
156- func extractFunc (pass * analysis.Pass , call * ast.CallExpr ) * types.Func {
157- switch fun := call .Fun .(type ) {
158- case * ast.Ident :
159- obj := pass .TypesInfo .ObjectOf (fun )
160- if f , ok := obj .(* types.Func ); ok {
161- return f
162- }
163-
164- case * ast.SelectorExpr :
165- sel := pass .TypesInfo .Selections [fun ]
166- if sel != nil {
167- if f , ok := sel .Obj ().(* types.Func ); ok {
168- return f
169- }
170- } else {
171- obj := pass .TypesInfo .ObjectOf (fun .Sel )
172- if f , ok := obj .(* types.Func ); ok {
173- return f
174- }
175- }
176- }
177-
178- return nil
179- }
180-
181115// groupSatisfied checks if ALL specs in the AND group are satisfied.
182- func groupSatisfied (calledFuncs []* types.Func , andGroup []FuncSpec ) bool {
116+ func groupSatisfied (calledFuncs []* types.Func , andGroup []funcspec. Spec ) bool {
183117 for _ , spec := range andGroup {
184118 if ! specSatisfied (calledFuncs , spec ) {
185119 return false
@@ -190,54 +124,12 @@ func groupSatisfied(calledFuncs []*types.Func, andGroup []FuncSpec) bool {
190124}
191125
192126// specSatisfied checks if the spec is satisfied by any of the called functions.
193- func specSatisfied (calledFuncs []* types.Func , spec FuncSpec ) bool {
127+ func specSatisfied (calledFuncs []* types.Func , spec funcspec. Spec ) bool {
194128 for _ , fn := range calledFuncs {
195- if matchesSpec (fn , spec ) {
129+ if spec . Matches (fn ) {
196130 return true
197131 }
198132 }
199133
200134 return false
201135}
202-
203- // matchesSpec checks if a types.Func matches the given derive function spec.
204- func matchesSpec (fn * types.Func , spec FuncSpec ) bool {
205- if fn .Name () != spec .FuncName {
206- return false
207- }
208-
209- if fn .Pkg () == nil || fn .Pkg ().Path () != spec .PkgPath {
210- return false
211- }
212-
213- if spec .TypeName != "" {
214- return matchesMethod (fn , spec .TypeName )
215- }
216-
217- return true
218- }
219-
220- // matchesMethod checks if a types.Func is a method on the expected type.
221- func matchesMethod (fn * types.Func , typeName string ) bool {
222- sig , ok := fn .Type ().(* types.Signature )
223- if ! ok {
224- return false
225- }
226-
227- recv := sig .Recv ()
228- if recv == nil {
229- return false
230- }
231-
232- recvType := recv .Type ()
233- if ptr , ok := recvType .(* types.Pointer ); ok {
234- recvType = ptr .Elem ()
235- }
236-
237- named , ok := recvType .(* types.Named )
238- if ! ok {
239- return false
240- }
241-
242- return named .Obj ().Name () == typeName
243- }
0 commit comments