codemap-go 0.1.0__py3-none-any.whl

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.
codemap_go/__init__.py ADDED
@@ -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"
codemap_go/indexer.py ADDED
@@ -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
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: codemap-go
3
+ Version: 0.1.0
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.0
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 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,6 @@
1
+ codemap_go/__init__.py,sha256=ut_Nn9ZIjGOfCtQD30YTAB3ZUz2idY1Pxxskf1_fO9c,162
2
+ codemap_go/indexer.py,sha256=YYgiN9joUmZJp_3C58v0zp7ReNCIiqg-jPTRxdoYIIY,10929
3
+ codemap_go-0.1.0.dist-info/METADATA,sha256=nYvI9Kooh0ZNP3dynTPMLj6e2UpSEqb4R-OSIxlUHEQ,2364
4
+ codemap_go-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ codemap_go-0.1.0.dist-info/entry_points.txt,sha256=WmXXQ_zcLF6Plaf_G-KTzEkLpLkC-vTviTf_4G-KMn8,45
6
+ codemap_go-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [codemap.indexers]
2
+ go = codemap_go:GoIndexer