codemap-go 0.1.0__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.
- codemap_go-0.1.0/.gitignore +43 -0
- codemap_go-0.1.0/PKG-INFO +74 -0
- codemap_go-0.1.0/README.md +54 -0
- codemap_go-0.1.0/pyproject.toml +36 -0
- codemap_go-0.1.0/src/codemap_go/__init__.py +8 -0
- codemap_go-0.1.0/src/codemap_go/indexer.py +315 -0
- codemap_go-0.1.0/tests/__init__.py +0 -0
- codemap_go-0.1.0/tests/test_indexer.py +181 -0
|
@@ -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.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,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 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.0"
|
|
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.0,<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,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"
|