mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-10-11 04:47:58 +00:00
fix(parser): exclude function-local var/const from symbols
extractGoSymbols/extractJSSymbols matched var/const/let at any tree depth, so every function-local var, const (Go) and let/const/var (JS/TS) was emitted as a top-level symbol — flooding include_ast/ast_query symbol lists with locals (inaccurate + token-wasteful). Skip declarations nested inside a function/block body (isNestedInBody); module-level and 'export const' declarations are still kept. Adds TestExtractSymbolsExcludesLocals.
This commit is contained in:
@@ -35,6 +35,28 @@ func ExtractSymbols(tree *sitter.Tree, content []byte, lang protocol.Language, f
|
||||
}
|
||||
|
||||
// extractGoSymbols extracts symbols from Go code.
|
||||
// goBodyTypes are the node types whose descendants are function-local (not
|
||||
// package-level) declarations in Go.
|
||||
var goBodyTypes = map[string]bool{"block": true, "func_literal": true}
|
||||
|
||||
// jsBodyTypes are the node types whose descendants are function-local (not
|
||||
// module/class-member) declarations in JS/TS.
|
||||
var jsBodyTypes = map[string]bool{
|
||||
"statement_block": true, "function_declaration": true, "method_definition": true,
|
||||
"arrow_function": true, "function_expression": true, "generator_function_declaration": true,
|
||||
}
|
||||
|
||||
// isNestedInBody reports whether n has an ancestor in bodyTypes, i.e. it is a
|
||||
// function-local declaration rather than a top-level/module one.
|
||||
func isNestedInBody(n *sitter.Node, bodyTypes map[string]bool) bool {
|
||||
for p := n.Parent(); p != nil; p = p.Parent() {
|
||||
if bodyTypes[p.Type()] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func extractGoSymbols(root *sitter.Node, content []byte, filename string) []protocol.Symbol {
|
||||
var symbols []protocol.Symbol
|
||||
|
||||
@@ -49,6 +71,10 @@ func extractGoSymbols(root *sitter.Node, content []byte, filename string) []prot
|
||||
case "type_declaration":
|
||||
symbol = extractGoType(n, content, filename)
|
||||
case "const_declaration", "var_declaration":
|
||||
// Skip function-local var/const; only package-level ones are symbols.
|
||||
if isNestedInBody(n, goBodyTypes) {
|
||||
return true
|
||||
}
|
||||
syms := extractGoVarConst(n, content, filename)
|
||||
symbols = append(symbols, syms...)
|
||||
return true
|
||||
@@ -190,6 +216,11 @@ func extractJSSymbols(root *sitter.Node, content []byte, filename string) []prot
|
||||
case "method_definition":
|
||||
symbol = extractJSMethod(n, content, filename)
|
||||
case "lexical_declaration", "variable_declaration":
|
||||
// Skip function-local let/const/var; only module/top-level ones
|
||||
// (including `export const`) are symbols.
|
||||
if isNestedInBody(n, jsBodyTypes) {
|
||||
return true
|
||||
}
|
||||
syms := extractJSVariable(n, content, filename)
|
||||
symbols = append(symbols, syms...)
|
||||
return true
|
||||
|
||||
@@ -125,6 +125,52 @@ var globalVar = "test"
|
||||
|
||||
// Grouped var/const specs declare multiple names on one spec (var a, b = 1, 2);
|
||||
// every name must surface as its own symbol, not just the first.
|
||||
// Function-local var/const declarations must NOT appear as top-level symbols
|
||||
// (they flood the list and aren't package/module symbols).
|
||||
func TestExtractSymbolsExcludesLocals(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
defer r.Close()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("go", func(t *testing.T) {
|
||||
content := "package main\n\nvar TopLevel = 1\n\nfunc f() {\n\tvar localVar = 2\n\tconst localConst = 3\n\t_ = localVar\n\t_ = localConst\n}\n"
|
||||
result, err := r.Parse(ctx, "m.go", []byte(content))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
names := symbolNameSet(ExtractSymbols(result.Tree, []byte(content), protocol.LangGo, "m.go"))
|
||||
if !names["TopLevel"] {
|
||||
t.Errorf("expected TopLevel symbol, got %v", names)
|
||||
}
|
||||
if names["localVar"] || names["localConst"] {
|
||||
t.Errorf("function-local var/const leaked into symbols: %v", names)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("js exported and module consts kept, locals dropped", func(t *testing.T) {
|
||||
content := "export const Exported = 1;\nconst ModuleConst = 2;\nfunction f() {\n const localOne = 3;\n return localOne;\n}\n"
|
||||
result, err := r.Parse(ctx, "m.js", []byte(content))
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
names := symbolNameSet(ExtractSymbols(result.Tree, []byte(content), protocol.LangJavaScript, "m.js"))
|
||||
if !names["Exported"] || !names["ModuleConst"] {
|
||||
t.Errorf("expected Exported and ModuleConst symbols, got %v", names)
|
||||
}
|
||||
if names["localOne"] {
|
||||
t.Errorf("function-local const leaked into symbols: %v", names)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func symbolNameSet(syms []protocol.Symbol) map[string]bool {
|
||||
m := make(map[string]bool, len(syms))
|
||||
for _, s := range syms {
|
||||
m[s.Name] = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// An Elixir defstruct must be named after its enclosing module (%Mod{}),
|
||||
// not the useless literal "defstruct".
|
||||
func TestExtractElixirStructNamedByModule(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user