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
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,,
|