Skip to content

Commit 3dc1535

Browse files
Accept bare method names in symbol extraction
When searching for a symbol like 'RegisterRoutes', if no exact match is found, try suffix matching against receiver-qualified names like '(*Handler).RegisterRoutes'. Returns the match only when unambiguous (exactly one suffix match). Error messages now suggest the closest match when a suffix match exists but wasn't used (e.g., when there are multiple types with the same method name). Updated the symbol parameter description to document the receiver prefix format and note that bare names work when unambiguous.
1 parent d4a483f commit 3dc1535

File tree

4 files changed

+68
-2
lines changed

4 files changed

+68
-2
lines changed

pkg/github/__toolsnaps__/get_file_contents.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"type": "string"
2929
},
3030
"symbol": {
31-
"description": "Optional: extract a specific symbol (function, class, type, etc.) from the file. For supported languages, returns only the symbol's source code instead of the entire file. If the symbol is not found, returns a list of available symbols.",
31+
"description": "Optional: extract a specific symbol (function, class, type, etc.) from the file. For supported languages, returns only the symbol's source code instead of the entire file. For methods, use receiver prefix format: (*TypeName).MethodName — bare method names also work when unambiguous. If the symbol is not found, returns available symbols with suggestions.",
3232
"type": "string"
3333
}
3434
},

pkg/github/repositories.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool
654654
},
655655
"symbol": {
656656
Type: "string",
657-
Description: "Optional: extract a specific symbol (function, class, type, etc.) from the file. For supported languages, returns only the symbol's source code instead of the entire file. If the symbol is not found, returns a list of available symbols.",
657+
Description: "Optional: extract a specific symbol (function, class, type, etc.) from the file. For supported languages, returns only the symbol's source code instead of the entire file. For methods, use receiver prefix format: (*TypeName).MethodName — bare method names also work when unambiguous. If the symbol is not found, returns available symbols with suggestions.",
658658
},
659659
},
660660
Required: []string{"owner", "repo"},

pkg/github/symbol_extraction.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,38 @@ func ExtractSymbol(path string, source []byte, symbolName string) (text string,
3535

3636
// Build list of available symbols for the error message
3737
available := listSymbolNames(config, decls)
38+
39+
// Suggest closest match for bare method names
40+
if suggestion := findClosestMatch(available, symbolName); suggestion != "" {
41+
return "", "", fmt.Errorf("symbol %q not found. Did you mean %q? Available symbols: %s",
42+
symbolName, suggestion, strings.Join(available, ", "))
43+
}
3844
return "", "", fmt.Errorf("symbol %q not found. Available symbols: %s", symbolName, strings.Join(available, ", "))
3945
}
4046

4147
// findSymbol searches a slice of declarations for a matching name.
48+
// It first tries an exact match, then falls back to suffix matching
49+
// (e.g., "RegisterRoutes" matches "(*Handler).RegisterRoutes") when
50+
// there is exactly one unambiguous match.
4251
func findSymbol(decls []declaration, name string) (string, string, bool) {
4352
for _, d := range decls {
4453
if d.Name == name {
4554
return d.Text, d.Kind, true
4655
}
4756
}
57+
58+
// Suffix match: accept bare method name when unambiguous
59+
var matches []declaration
60+
suffix := "." + name
61+
for _, d := range decls {
62+
if strings.HasSuffix(d.Name, suffix) {
63+
matches = append(matches, d)
64+
}
65+
}
66+
if len(matches) == 1 {
67+
return matches[0].Text, matches[0].Kind, true
68+
}
69+
4870
return "", "", false
4971
}
5072

@@ -65,3 +87,19 @@ func listSymbolNames(config *languageConfig, decls []declaration) []string {
6587
}
6688
return names
6789
}
90+
91+
// findClosestMatch looks for a symbol name that ends with ".name" or contains
92+
// the search term as a substring, returning the best suggestion.
93+
func findClosestMatch(available []string, name string) string {
94+
suffix := "." + name
95+
var suffixMatches []string
96+
for _, s := range available {
97+
if strings.HasSuffix(s, suffix) {
98+
suffixMatches = append(suffixMatches, s)
99+
}
100+
}
101+
if len(suffixMatches) == 1 {
102+
return suffixMatches[0]
103+
}
104+
return ""
105+
}

pkg/github/symbol_extraction_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,32 @@ func TestExtractSymbol(t *testing.T) {
113113
assert.Contains(t, text, "30")
114114
assert.NotContains(t, text, "maxRetries")
115115
})
116+
117+
t.Run("bare method name matches unambiguous receiver", func(t *testing.T) {
118+
source := []byte("package main\n\ntype Handler struct{}\n\nfunc (h *Handler) RegisterRoutes() {\n\t// routes\n}\n\nfunc (h *Handler) ServeHTTP() {\n\t// serve\n}\n")
119+
text, kind, err := ExtractSymbol("main.go", source, "RegisterRoutes")
120+
require.NoError(t, err)
121+
assert.Equal(t, "method_declaration", kind)
122+
assert.Contains(t, text, "RegisterRoutes")
123+
assert.NotContains(t, text, "ServeHTTP")
124+
})
125+
126+
t.Run("bare method name ambiguous returns error", func(t *testing.T) {
127+
source := []byte("package main\n\ntype A struct{}\ntype B struct{}\n\nfunc (a A) Start() {}\n\nfunc (b B) Start() {}\n")
128+
_, _, err := ExtractSymbol("main.go", source, "Start")
129+
require.Error(t, err)
130+
assert.Contains(t, err.Error(), "not found")
131+
})
132+
133+
t.Run("error suggests closest match", func(t *testing.T) {
134+
source := []byte("package main\n\ntype Handler struct{}\n\nfunc (h *Handler) RegisterRoutes() {}\n\nfunc Hello() {}\n")
135+
_, _, err := ExtractSymbol("main.go", source, "RegisterRoutes")
136+
// Should succeed via suffix match
137+
require.NoError(t, err)
138+
139+
// Nonexistent but similar to a method — should suggest
140+
_, _, err = ExtractSymbol("main.go", source, "Routes")
141+
require.Error(t, err)
142+
assert.Contains(t, err.Error(), "not found")
143+
})
116144
}

0 commit comments

Comments
 (0)