-
Notifications
You must be signed in to change notification settings - Fork 111
Expand file tree
/
Copy pathqueries_examples.go
More file actions
331 lines (289 loc) · 9.11 KB
/
Copy pathqueries_examples.go
File metadata and controls
331 lines (289 loc) · 9.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
package graph
import (
"fmt"
"sort"
"strings"
)
// UsageTarget describes what we're looking for usage examples of.
type UsageTarget struct {
ObjectType string // FUNC, CLAS, INTF, PROG
ObjectName string // Z_CALCULATE_TAX, ZCL_TRAVEL, ZREPORT
Method string // GET_DATA (for class/interface methods, empty otherwise)
Form string // BUILD_OUTPUT (for FORM in program, empty otherwise)
}
// CanonicalPattern returns the search pattern used to find call sites in source.
func (t *UsageTarget) CanonicalPattern() string {
switch strings.ToUpper(t.ObjectType) {
case "FUNC":
return t.ObjectName // CALL FUNCTION 'Z_CALCULATE_TAX'
case "CLAS":
if t.Method != "" {
return t.Method // ->get_data( or =>get_data(
}
return t.ObjectName // NEW zcl_travel or TYPE REF TO zcl_travel
case "INTF":
if t.Method != "" {
return t.Method
}
return t.ObjectName
case "SUBMIT", "PROG":
if t.Form != "" {
return t.Form // PERFORM form IN PROGRAM
}
return t.ObjectName // SUBMIT prog
default:
return t.ObjectName
}
}
// UsageExample represents a single usage example found in caller source code.
type UsageExample struct {
CallerID string `json:"caller_id"` // CLAS:ZCL_ORDER_SERVICE
CallerName string `json:"caller_name"` // ZCL_ORDER_SERVICE
CallerType string `json:"caller_type"` // CLAS
Package string `json:"package,omitempty"` // $ZDEV
IsTest bool `json:"is_test"` // true if test class/include
Snippet string `json:"snippet"` // source lines with line numbers
LineNumber int `json:"line_number"` // line of the call site
MatchType string `json:"match_type"` // CALL_FUNCTION, METHOD_CALL, SUBMIT, PERFORM, GREP
Confidence string `json:"confidence"` // HIGH (parser/exact), MEDIUM (grep)
}
// UsageExamplesResult is the result of FindUsageExamples.
type UsageExamplesResult struct {
Target UsageTarget `json:"target"`
TotalCallers int `json:"total_callers"` // total before cap
Examples []UsageExample `json:"examples"`
}
// CallerSource pairs a caller's identity with its source code for snippet extraction.
type CallerSource struct {
NodeID string // CLAS:ZCL_ORDER_SERVICE
Name string // ZCL_ORDER_SERVICE
Type string // CLAS
Package string // $ZDEV
IsTest bool // test class/include
Source string // full source code
}
// FindUsageExamples extracts usage snippets from caller source code.
// This is the pure core: it takes pre-fetched caller sources and extracts
// call sites matching the target. No I/O — caller discovery and source fetch
// are the responsibility of CLI/MCP handlers.
func FindUsageExamples(target UsageTarget, callers []CallerSource, maxExamples int) *UsageExamplesResult {
if maxExamples <= 0 {
maxExamples = 10
}
result := &UsageExamplesResult{
Target: target,
TotalCallers: len(callers),
}
var examples []UsageExample
for _, caller := range callers {
found := extractCallSites(target, caller)
examples = append(examples, found...)
}
// Rank examples
sort.Slice(examples, func(i, j int) bool {
return exampleRank(examples[i]) < exampleRank(examples[j])
})
// Cap
if len(examples) > maxExamples {
examples = examples[:maxExamples]
}
result.Examples = examples
return result
}
// extractCallSites finds call sites in a single caller's source that match the target.
func extractCallSites(target UsageTarget, caller CallerSource) []UsageExample {
lines := strings.Split(caller.Source, "\n")
targetType := strings.ToUpper(target.ObjectType)
var examples []UsageExample
for i, line := range lines {
lineUpper := strings.ToUpper(line)
lineNum := i + 1
matchType := ""
// Skip comment lines before any pattern matching
if isCommentLine(line) {
continue
}
switch targetType {
case "FUNC":
// CALL FUNCTION 'FM_NAME'
if matchCallFunction(lineUpper, target.ObjectName) {
matchType = "CALL_FUNCTION"
}
case "CLAS", "INTF":
if target.Method != "" {
// zcl_class=>method( or zcl_class->method( or zif~method(
if matchMethodCall(lineUpper, target.ObjectName, target.Method) {
matchType = "METHOD_CALL"
}
} else {
// NEW zcl_class, TYPE REF TO zcl_class, CREATE OBJECT TYPE zcl_class
if matchClassReference(lineUpper, target.ObjectName) {
matchType = "CLASS_REFERENCE"
}
}
case "SUBMIT":
// SUBMIT prog_name
if matchSubmit(lineUpper, target.ObjectName) {
matchType = "SUBMIT"
}
case "PROG":
if target.Form != "" {
// PERFORM form IN PROGRAM prog
if matchPerformInProgram(lineUpper, target.ObjectName, target.Form) {
matchType = "PERFORM"
}
} else {
// SUBMIT prog
if matchSubmit(lineUpper, target.ObjectName) {
matchType = "SUBMIT"
}
}
}
if matchType == "" {
// Grep fallback: literal name match (case-insensitive)
pattern := strings.ToUpper(target.CanonicalPattern())
if strings.Contains(lineUpper, pattern) {
matchType = "GREP"
}
}
if matchType != "" {
snippet := extractSnippet(lines, i, 3)
confidence := "HIGH"
if matchType == "GREP" {
confidence = "MEDIUM"
}
examples = append(examples, UsageExample{
CallerID: caller.NodeID,
CallerName: caller.Name,
CallerType: caller.Type,
Package: caller.Package,
IsTest: caller.IsTest,
Snippet: snippet,
LineNumber: lineNum,
MatchType: matchType,
Confidence: confidence,
})
}
}
return examples
}
// --- Pattern matchers ---
// matchCallFunction checks for CALL FUNCTION 'FM_NAME'
func matchCallFunction(lineUpper, fmName string) bool {
fmUpper := strings.ToUpper(fmName)
// CALL FUNCTION 'FM_NAME'
return strings.Contains(lineUpper, "CALL FUNCTION") &&
strings.Contains(lineUpper, "'"+fmUpper+"'")
}
// matchMethodCall checks for class=>method( or class->method( or intf~method(
func matchMethodCall(lineUpper, className, methodName string) bool {
classUpper := strings.ToUpper(className)
methodUpper := strings.ToUpper(methodName)
// Static call: ZCL_FOO=>GET_DATA
if strings.Contains(lineUpper, classUpper+"=>"+methodUpper) {
return true
}
// Instance call with known class: ...->METHOD_NAME (less precise, but common)
// We check if class name appears nearby AND method name is on this line
if strings.Contains(lineUpper, "->"+methodUpper) {
return true
}
// Interface method call: ZIF_API~METHOD_NAME
if strings.Contains(lineUpper, classUpper+"~"+methodUpper) {
return true
}
return false
}
// matchClassReference checks for NEW class, TYPE REF TO class, CREATE OBJECT TYPE class
func matchClassReference(lineUpper, className string) bool {
classUpper := strings.ToUpper(className)
if strings.Contains(lineUpper, "NEW "+classUpper) {
return true
}
if strings.Contains(lineUpper, "TYPE REF TO "+classUpper) {
return true
}
if strings.Contains(lineUpper, "TYPE "+classUpper) && strings.Contains(lineUpper, "CREATE OBJECT") {
return true
}
return false
}
// matchSubmit checks for SUBMIT prog_name
func matchSubmit(lineUpper, progName string) bool {
progUpper := strings.ToUpper(progName)
return strings.Contains(lineUpper, "SUBMIT") && strings.Contains(lineUpper, progUpper)
}
// matchPerformInProgram checks for PERFORM form IN PROGRAM prog
func matchPerformInProgram(lineUpper, progName, formName string) bool {
progUpper := strings.ToUpper(progName)
formUpper := strings.ToUpper(formName)
return strings.Contains(lineUpper, "PERFORM") &&
strings.Contains(lineUpper, formUpper) &&
strings.Contains(lineUpper, "PROGRAM") &&
strings.Contains(lineUpper, progUpper)
}
// --- Helpers ---
// extractSnippet extracts N lines of context around a target line.
func extractSnippet(lines []string, targetIdx, contextLines int) string {
start := targetIdx - contextLines
if start < 0 {
start = 0
}
end := targetIdx + contextLines + 1
if end > len(lines) {
end = len(lines)
}
var sb strings.Builder
for i := start; i < end; i++ {
sb.WriteString(fmt.Sprintf(" %4d | %s\n", i+1, lines[i]))
}
return sb.String()
}
// isCommentLine checks if a line is an ABAP comment.
func isCommentLine(line string) bool {
trimmed := strings.TrimSpace(line)
return strings.HasPrefix(trimmed, "*") || strings.HasPrefix(trimmed, "\"")
}
// IsTestCaller detects if a caller is a test class/include by naming convention.
func IsTestCaller(name, includeType string) bool {
upper := strings.ToUpper(name)
if strings.Contains(upper, "TEST") {
return true
}
if strings.HasPrefix(upper, "LCL_TEST") || strings.HasPrefix(upper, "LTH_") {
return true
}
incUpper := strings.ToUpper(includeType)
if incUpper == "CCAU" || incUpper == "TESTCLASSES" {
return true
}
return false
}
// exampleRank returns a sort key (lower = better).
func exampleRank(e UsageExample) int {
rank := 0
// Test classes first (cleanest examples)
if !e.IsTest {
rank += 100
}
// HIGH confidence before MEDIUM
if e.Confidence == "MEDIUM" {
rank += 50
}
// Specific match types before grep
switch e.MatchType {
case "CALL_FUNCTION":
rank += 0
case "METHOD_CALL":
rank += 1
case "SUBMIT":
rank += 2
case "PERFORM":
rank += 3
case "CLASS_REFERENCE":
rank += 10
case "GREP":
rank += 20
}
return rank
}