codemap-go 0.1.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Build artifacts
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+ .eggs/
12
+
13
+ # Test / coverage
14
+ .pytest_cache/
15
+ .coverage
16
+ .coverage.*
17
+ htmlcov/
18
+ coverage.xml
19
+ .tox/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ .benchmarks/
23
+
24
+ # Virtualenv
25
+ .venv/
26
+ venv/
27
+ env/
28
+
29
+ # uv / pdm lockfiles (commit uv.lock once we settle)
30
+ # uv.lock
31
+
32
+ # IDE
33
+ .idea/
34
+ .vscode/
35
+ *.swp
36
+ *.swo
37
+
38
+ # OS
39
+ .DS_Store
40
+ Thumbs.db
41
+
42
+ # CodeMap own index when dogfooding
43
+ .codemap/
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: codemap-go
3
+ Version: 0.1.0a1
4
+ Summary: Go indexer plugin for CodeMap
5
+ Project-URL: Homepage, https://github.com/qxbyte/codemap
6
+ Author: CodeMap Contributors
7
+ License: MIT
8
+ Keywords: codemap,go,indexer,tree-sitter
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: Go
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: codemap-core<0.2,>=0.1.0a1
15
+ Requires-Dist: tree-sitter-go>=0.23
16
+ Requires-Dist: tree-sitter>=0.25
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # codemap-go
22
+
23
+ > A Go indexer for [CodeMap](https://github.com/qxbyte/codemap), shipped
24
+ > as an independent PyPI package.
25
+
26
+ ## What it captures
27
+
28
+ Backed by `tree-sitter-go`:
29
+
30
+ | AST node | Symbol kind |
31
+ |---|---|
32
+ | `function_declaration` | `function` |
33
+ | `method_declaration` | `method` (attached to its receiver type) |
34
+ | `type_declaration` containing `struct_type` | `class` (with `extra.go_kind=struct`) |
35
+ | `type_declaration` containing `interface_type` | `class` (with `extra.go_kind=interface`) |
36
+ | `type_declaration` (other) | `class` (with `extra.go_kind=type`) |
37
+ | `const_declaration` (top-level) | `variable` (with `extra.go_kind=const`) |
38
+ | `var_declaration` (top-level) | `variable` |
39
+
40
+ `package_clause` is captured as `extra.package` on every symbol-producing
41
+ type so a downstream bridge can use it for cross-file resolution.
42
+
43
+ Method receivers — both `func (u User) M()` and `func (u *User) M()` —
44
+ attach the method to its `User` type, producing
45
+ `scip-go . . . main.go/User#Login().`.
46
+
47
+ ## SymbolID encoding
48
+
49
+ ```
50
+ scip-go . . . pkg/user/user.go/User#Login().
51
+ └──────┘ └─────────────────────┘ └────┘ └─────┘
52
+ scheme file path type method
53
+ ```
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install "git+https://github.com/qxbyte/codemap.git#subdirectory=plugins/codemap-go"
59
+ ```
60
+
61
+ After install, `codemap doctor` lists `go` alongside the other indexers
62
+ on identical terms (ADR-004 + ADR-L001).
63
+
64
+ ## Limits
65
+
66
+ * No `imports` edges. Top-level imports are recorded for the package
67
+ table but not turned into edges.
68
+ * No generic constraints / type-parameter descriptors.
69
+ * Method bodies are not traversed for call edges yet.
70
+ * Anonymous structs and function-typed fields are skipped.
71
+
72
+ ## License
73
+
74
+ MIT.
@@ -0,0 +1,54 @@
1
+ # codemap-go
2
+
3
+ > A Go indexer for [CodeMap](https://github.com/qxbyte/codemap), shipped
4
+ > as an independent PyPI package.
5
+
6
+ ## What it captures
7
+
8
+ Backed by `tree-sitter-go`:
9
+
10
+ | AST node | Symbol kind |
11
+ |---|---|
12
+ | `function_declaration` | `function` |
13
+ | `method_declaration` | `method` (attached to its receiver type) |
14
+ | `type_declaration` containing `struct_type` | `class` (with `extra.go_kind=struct`) |
15
+ | `type_declaration` containing `interface_type` | `class` (with `extra.go_kind=interface`) |
16
+ | `type_declaration` (other) | `class` (with `extra.go_kind=type`) |
17
+ | `const_declaration` (top-level) | `variable` (with `extra.go_kind=const`) |
18
+ | `var_declaration` (top-level) | `variable` |
19
+
20
+ `package_clause` is captured as `extra.package` on every symbol-producing
21
+ type so a downstream bridge can use it for cross-file resolution.
22
+
23
+ Method receivers — both `func (u User) M()` and `func (u *User) M()` —
24
+ attach the method to its `User` type, producing
25
+ `scip-go . . . main.go/User#Login().`.
26
+
27
+ ## SymbolID encoding
28
+
29
+ ```
30
+ scip-go . . . pkg/user/user.go/User#Login().
31
+ └──────┘ └─────────────────────┘ └────┘ └─────┘
32
+ scheme file path type method
33
+ ```
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install "git+https://github.com/qxbyte/codemap.git#subdirectory=plugins/codemap-go"
39
+ ```
40
+
41
+ After install, `codemap doctor` lists `go` alongside the other indexers
42
+ on identical terms (ADR-004 + ADR-L001).
43
+
44
+ ## Limits
45
+
46
+ * No `imports` edges. Top-level imports are recorded for the package
47
+ table but not turned into edges.
48
+ * No generic constraints / type-parameter descriptors.
49
+ * Method bodies are not traversed for call edges yet.
50
+ * Anonymous structs and function-typed fields are skipped.
51
+
52
+ ## License
53
+
54
+ MIT.
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "codemap-go"
7
+ version = "0.1.0a1"
8
+ description = "Go indexer plugin for CodeMap"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "CodeMap Contributors" }]
13
+ keywords = ["codemap", "go", "indexer", "tree-sitter"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Go",
18
+ "Topic :: Software Development",
19
+ ]
20
+ dependencies = [
21
+ "codemap-core>=0.1.0a1,<0.2",
22
+ "tree-sitter>=0.25",
23
+ "tree-sitter-go>=0.23",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pytest>=8.0"]
28
+
29
+ [project.entry-points."codemap.indexers"]
30
+ go = "codemap_go:GoIndexer"
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/qxbyte/codemap"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/codemap_go"]
@@ -0,0 +1,8 @@
1
+ """Go indexer plugin for CodeMap."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from codemap_go.indexer import GoIndexer
6
+
7
+ __all__ = ["GoIndexer"]
8
+ __version__ = "0.1.0"
@@ -0,0 +1,315 @@
1
+ """Go indexer built on tree-sitter-go.
2
+
3
+ Notable Go-specific behaviour: ``method_declaration`` carries a receiver
4
+ parameter list before the method name. The indexer pulls the receiver's
5
+ type out and attaches the method as ``<TypeName>#methodName()`` so a
6
+ later query like ``callers 'scip-go . . . main.go/User#Login().'`` can
7
+ find every cross-receiver caller via the cross-module bridge.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path, PurePosixPath
13
+ from typing import ClassVar
14
+
15
+ import tree_sitter
16
+ import tree_sitter_go
17
+
18
+ from codemap.core.models import Diagnostic, Edge, IndexResult, Range, Symbol
19
+ from codemap.core.symbol import Descriptor, DescriptorKind, SymbolID
20
+ from codemap.indexers.base import IndexContext
21
+
22
+ SCHEME = "scip-go"
23
+ LANG = "go"
24
+
25
+ _GO_LANG = tree_sitter.Language(tree_sitter_go.language())
26
+
27
+
28
+ class GoIndexer:
29
+ name: ClassVar[str] = "go"
30
+ version: ClassVar[str] = "0.1.0"
31
+ file_patterns: ClassVar[list[str]] = ["*.go"]
32
+ languages: ClassVar[list[str]] = [LANG]
33
+
34
+ def supports(self, path: Path) -> bool:
35
+ return path.suffix == ".go"
36
+
37
+ def index_file(
38
+ self,
39
+ path: Path,
40
+ source: bytes,
41
+ ctx: IndexContext,
42
+ ) -> IndexResult:
43
+ try:
44
+ source.decode("utf-8")
45
+ except UnicodeDecodeError as exc:
46
+ return IndexResult(
47
+ diagnostics=[
48
+ Diagnostic(
49
+ severity="error",
50
+ file=ctx.relative_path,
51
+ code="GO002",
52
+ message=f"not valid UTF-8: {exc}",
53
+ producer=self.name,
54
+ )
55
+ ]
56
+ )
57
+ parser = tree_sitter.Parser(_GO_LANG)
58
+ tree = parser.parse(source)
59
+ visitor = _Visitor(ctx.relative_path)
60
+ visitor.visit(tree.root_node)
61
+ diagnostics = list(visitor.diagnostics)
62
+ if tree.root_node.has_error:
63
+ diagnostics.append(
64
+ Diagnostic(
65
+ severity="warning",
66
+ file=ctx.relative_path,
67
+ range=Range(start_line=1, end_line=1),
68
+ code="GO001",
69
+ message="tree-sitter reported parse errors; symbols may be incomplete",
70
+ producer=self.name,
71
+ )
72
+ )
73
+ return IndexResult(
74
+ symbols=visitor.symbols,
75
+ edges=visitor.edges,
76
+ diagnostics=diagnostics,
77
+ )
78
+
79
+
80
+ class _Visitor:
81
+ def __init__(self, relative_path: PurePosixPath) -> None:
82
+ self.relative_path = relative_path
83
+ self.symbols: list[Symbol] = []
84
+ self.edges: list[Edge] = []
85
+ self.diagnostics: list[Diagnostic] = []
86
+ self._package: str = ""
87
+
88
+ def visit(self, node: tree_sitter.Node) -> None:
89
+ kind = node.type
90
+ if kind == "package_clause":
91
+ self._set_package(node)
92
+ return
93
+ if kind == "function_declaration":
94
+ self._visit_function(node)
95
+ return
96
+ if kind == "method_declaration":
97
+ self._visit_method(node)
98
+ return
99
+ if kind == "type_declaration":
100
+ for spec in node.children:
101
+ if spec.type == "type_spec":
102
+ self._visit_type_spec(spec)
103
+ return
104
+ if kind == "const_declaration":
105
+ self._visit_const_or_var(node, go_kind="const")
106
+ return
107
+ if kind == "var_declaration":
108
+ self._visit_const_or_var(node, go_kind="var")
109
+ return
110
+ for child in node.children:
111
+ self.visit(child)
112
+
113
+ # ------------------------------------------------------------ pkg
114
+
115
+ def _set_package(self, node: tree_sitter.Node) -> None:
116
+ for child in node.children:
117
+ if child.type == "package_identifier":
118
+ self._package = _node_text(child)
119
+ return
120
+
121
+ # ------------------------------------------------------- functions
122
+
123
+ def _visit_function(self, node: tree_sitter.Node) -> None:
124
+ name_node = node.child_by_field_name("name")
125
+ if name_node is None:
126
+ return
127
+ name = _node_text(name_node)
128
+ if not name:
129
+ return
130
+ sid = self._make_id([], name, kind=DescriptorKind.METHOD)
131
+ self.symbols.append(
132
+ Symbol(
133
+ id=sid,
134
+ kind="function",
135
+ language=LANG,
136
+ file=self.relative_path,
137
+ range=_node_range(node),
138
+ signature=_function_signature(node, name),
139
+ extra={"package": self._package} if self._package else {},
140
+ )
141
+ )
142
+
143
+ def _visit_method(self, node: tree_sitter.Node) -> None:
144
+ name_node = node.child_by_field_name("name")
145
+ if name_node is None:
146
+ return
147
+ name = _node_text(name_node)
148
+ if not name:
149
+ return
150
+ receiver_type = _extract_receiver_type(node)
151
+ if receiver_type is None:
152
+ # Methods without resolvable receivers are dropped — they would
153
+ # otherwise alias against unrelated top-level functions.
154
+ return
155
+ sid = self._make_id([receiver_type], name, kind=DescriptorKind.METHOD)
156
+ self.symbols.append(
157
+ Symbol(
158
+ id=sid,
159
+ kind="method",
160
+ language=LANG,
161
+ file=self.relative_path,
162
+ range=_node_range(node),
163
+ signature=_function_signature(node, name),
164
+ extra={
165
+ "package": self._package,
166
+ "receiver_type": receiver_type,
167
+ },
168
+ )
169
+ )
170
+
171
+ # ----------------------------------------------------------- types
172
+
173
+ def _visit_type_spec(self, node: tree_sitter.Node) -> None:
174
+ name_node = node.child_by_field_name("name")
175
+ if name_node is None:
176
+ return
177
+ name = _node_text(name_node)
178
+ if not name:
179
+ return
180
+ body = node.child_by_field_name("type")
181
+ if body is not None and body.type == "struct_type":
182
+ go_kind = "struct"
183
+ elif body is not None and body.type == "interface_type":
184
+ go_kind = "interface"
185
+ else:
186
+ go_kind = "type"
187
+ sid = self._make_id([], name, kind=DescriptorKind.TYPE)
188
+ self.symbols.append(
189
+ Symbol(
190
+ id=sid,
191
+ kind="class",
192
+ language=LANG,
193
+ file=self.relative_path,
194
+ range=_node_range(node),
195
+ extra={"package": self._package, "go_kind": go_kind},
196
+ )
197
+ )
198
+
199
+ # ---------------------------------------------------- vars / consts
200
+
201
+ def _visit_const_or_var(self, node: tree_sitter.Node, *, go_kind: str) -> None:
202
+ for spec in node.children:
203
+ if spec.type not in {"const_spec", "var_spec"}:
204
+ continue
205
+ for child in spec.children:
206
+ if child.type != "identifier":
207
+ continue
208
+ name = _node_text(child)
209
+ if not name:
210
+ continue
211
+ sid = self._make_id([], name, kind=DescriptorKind.TERM)
212
+ self.symbols.append(
213
+ Symbol(
214
+ id=sid,
215
+ kind="variable",
216
+ language=LANG,
217
+ file=self.relative_path,
218
+ range=_node_range(spec),
219
+ extra={"package": self._package, "go_kind": go_kind}
220
+ if self._package
221
+ else {"go_kind": go_kind},
222
+ )
223
+ )
224
+
225
+ # --------------------------------------------------------- helpers
226
+
227
+ def _make_id(
228
+ self,
229
+ type_chain: list[str],
230
+ name: str,
231
+ *,
232
+ kind: DescriptorKind,
233
+ ) -> SymbolID:
234
+ descriptors = list(_path_namespaces(self.relative_path))
235
+ descriptors.extend(Descriptor(name=t, kind=DescriptorKind.TYPE) for t in type_chain)
236
+ descriptors.append(Descriptor(name=name, kind=kind))
237
+ return SymbolID(scheme=SCHEME, descriptors=tuple(descriptors))
238
+
239
+
240
+ # ---------------------------------------------------------------------------
241
+ # Pure helpers
242
+ # ---------------------------------------------------------------------------
243
+
244
+
245
+ def _path_namespaces(path: PurePosixPath) -> list[Descriptor]:
246
+ return [Descriptor(name=part, kind=DescriptorKind.NAMESPACE) for part in path.parts]
247
+
248
+
249
+ def _node_range(node: tree_sitter.Node) -> Range:
250
+ sr, sc = node.start_point
251
+ er, ec = node.end_point
252
+ return Range(
253
+ start_line=sr + 1,
254
+ start_col=sc,
255
+ end_line=max(er + 1, sr + 1),
256
+ end_col=ec,
257
+ )
258
+
259
+
260
+ def _node_text(node: tree_sitter.Node) -> str:
261
+ return node.text.decode("utf-8") if node.text is not None else ""
262
+
263
+
264
+ def _function_signature(node: tree_sitter.Node, name: str) -> str:
265
+ params = node.child_by_field_name("parameters")
266
+ params_text = _node_text(params) if params is not None else "()"
267
+ result = node.child_by_field_name("result")
268
+ result_text = (" " + _node_text(result)) if result is not None else ""
269
+ return f"func {name}{params_text}{result_text}"
270
+
271
+
272
+ def _extract_receiver_type(method_node: tree_sitter.Node) -> str | None:
273
+ """For ``func (u User) M()`` and ``func (u *User) M()`` return ``User``."""
274
+ receiver = method_node.child_by_field_name("receiver")
275
+ if receiver is None:
276
+ # Some versions of the grammar don't expose `receiver` by name —
277
+ # fall back to the first parameter_list before the method name.
278
+ for child in method_node.children:
279
+ if child.type == "parameter_list":
280
+ receiver = child
281
+ break
282
+ if receiver is None:
283
+ return None
284
+ for param in receiver.children:
285
+ if param.type != "parameter_declaration":
286
+ continue
287
+ type_node = param.child_by_field_name("type")
288
+ if type_node is None:
289
+ # Walk children to find the type
290
+ for c in param.children:
291
+ if c.type in {"type_identifier", "pointer_type", "qualified_type"}:
292
+ type_node = c
293
+ break
294
+ if type_node is None:
295
+ continue
296
+ return _unwrap_type(type_node)
297
+ return None
298
+
299
+
300
+ def _unwrap_type(node: tree_sitter.Node) -> str | None:
301
+ if node.type == "type_identifier":
302
+ return _node_text(node)
303
+ if node.type == "pointer_type":
304
+ for c in node.children:
305
+ if c.type == "type_identifier":
306
+ return _node_text(c)
307
+ if c.type == "qualified_type":
308
+ return _unwrap_type(c)
309
+ return None
310
+ if node.type == "qualified_type":
311
+ for c in node.children:
312
+ if c.type == "type_identifier":
313
+ return _node_text(c)
314
+ return None
315
+ return None
File without changes
@@ -0,0 +1,181 @@
1
+ """Unit tests for the Go indexer plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import textwrap
6
+ from pathlib import Path, PurePosixPath
7
+
8
+ from codemap_go import GoIndexer
9
+ from codemap_go.indexer import SCHEME
10
+
11
+ from codemap.core.models import IndexResult
12
+ from codemap.indexers.base import IndexContext
13
+
14
+
15
+ def _index(source: str, *, path: str = "main.go") -> IndexResult:
16
+ code = textwrap.dedent(source).lstrip("\n")
17
+ return GoIndexer().index_file(
18
+ Path(path),
19
+ code.encode("utf-8"),
20
+ IndexContext(
21
+ project_root=Path("/tmp/proj"),
22
+ relative_path=PurePosixPath(path),
23
+ language="go",
24
+ ),
25
+ )
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Metadata
30
+ # ---------------------------------------------------------------------------
31
+
32
+
33
+ def test_indexer_metadata() -> None:
34
+ ix = GoIndexer()
35
+ assert ix.name == "go"
36
+ assert ix.languages == ["go"]
37
+ assert "*.go" in ix.file_patterns
38
+ assert ix.supports(Path("a.go"))
39
+ assert not ix.supports(Path("a.py"))
40
+
41
+
42
+ def test_scheme_is_consistent() -> None:
43
+ r = _index(
44
+ """
45
+ package main
46
+ func f() {}
47
+ type T struct {}
48
+ const C = 1
49
+ """
50
+ )
51
+ for s in r.symbols:
52
+ assert str(s.id).startswith(f"{SCHEME} ")
53
+
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Functions / methods
57
+ # ---------------------------------------------------------------------------
58
+
59
+
60
+ def test_function_declaration() -> None:
61
+ r = _index(
62
+ """
63
+ package main
64
+ func helper(x int) int { return x + 1 }
65
+ """
66
+ )
67
+ funcs = [s for s in r.symbols if s.kind == "function"]
68
+ assert len(funcs) == 1
69
+ assert "helper" in str(funcs[0].id)
70
+ assert funcs[0].extra.get("package") == "main"
71
+
72
+
73
+ def test_method_attached_to_receiver_value() -> None:
74
+ r = _index(
75
+ """
76
+ package main
77
+ type User struct { Name string }
78
+ func (u User) Hello() string { return u.Name }
79
+ """
80
+ )
81
+ methods = [s for s in r.symbols if s.kind == "method"]
82
+ assert len(methods) == 1
83
+ assert "User#Hello()." in str(methods[0].id)
84
+ assert methods[0].extra.get("receiver_type") == "User"
85
+
86
+
87
+ def test_method_attached_to_pointer_receiver() -> None:
88
+ r = _index(
89
+ """
90
+ package main
91
+ type User struct {}
92
+ func (u *User) Login(pw string) bool { return false }
93
+ """
94
+ )
95
+ methods = [s for s in r.symbols if s.kind == "method"]
96
+ assert "User#Login()." in str(methods[0].id)
97
+
98
+
99
+ # ---------------------------------------------------------------------------
100
+ # Types
101
+ # ---------------------------------------------------------------------------
102
+
103
+
104
+ def test_struct_type() -> None:
105
+ r = _index("package main\ntype User struct { ID int }")
106
+ types = [s for s in r.symbols if s.kind == "class"]
107
+ assert len(types) == 1
108
+ assert types[0].extra.get("go_kind") == "struct"
109
+
110
+
111
+ def test_interface_type() -> None:
112
+ r = _index(
113
+ """
114
+ package main
115
+ type Greeter interface { Hello() string }
116
+ """
117
+ )
118
+ types = [s for s in r.symbols if s.kind == "class"]
119
+ assert types[0].extra.get("go_kind") == "interface"
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # Constants / variables
124
+ # ---------------------------------------------------------------------------
125
+
126
+
127
+ def test_const_declaration() -> None:
128
+ r = _index("package main\nconst MAX = 10")
129
+ vars_ = [s for s in r.symbols if s.kind == "variable"]
130
+ assert len(vars_) == 1
131
+ assert vars_[0].extra.get("go_kind") == "const"
132
+
133
+
134
+ def test_var_declaration() -> None:
135
+ r = _index("package main\nvar counter int")
136
+ vars_ = [s for s in r.symbols if s.kind == "variable"]
137
+ assert vars_[0].extra.get("go_kind") == "var"
138
+
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # SymbolID + edge cases
142
+ # ---------------------------------------------------------------------------
143
+
144
+
145
+ def test_symbol_id_uses_path_namespaces() -> None:
146
+ r = _index(
147
+ "package user\nfunc helper() {}",
148
+ path="pkg/user/u.go",
149
+ )
150
+ assert str(r.symbols[0].id) == "scip-go . . . pkg/user/u.go/helper()."
151
+
152
+
153
+ def test_empty_file_yields_no_symbols() -> None:
154
+ r = _index("")
155
+ assert r.symbols == []
156
+
157
+
158
+ def test_invalid_utf8_yields_diagnostic() -> None:
159
+ ix = GoIndexer()
160
+ r = ix.index_file(
161
+ Path("bad.go"),
162
+ b"\xff\xfe package",
163
+ IndexContext(
164
+ project_root=Path("/tmp/proj"),
165
+ relative_path=PurePosixPath("bad.go"),
166
+ language="go",
167
+ ),
168
+ )
169
+ assert r.symbols == []
170
+ assert r.diagnostics[0].code == "GO002"
171
+
172
+
173
+ def test_package_captured_on_symbols() -> None:
174
+ r = _index(
175
+ """
176
+ package user
177
+ type User struct {}
178
+ """
179
+ )
180
+ cls = next(s for s in r.symbols if s.kind == "class")
181
+ assert cls.extra.get("package") == "user"