codemap-swift 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.
- codemap_swift-0.1.0a1/.gitignore +43 -0
- codemap_swift-0.1.0a1/PKG-INFO +60 -0
- codemap_swift-0.1.0a1/README.md +41 -0
- codemap_swift-0.1.0a1/pyproject.toml +35 -0
- codemap_swift-0.1.0a1/src/codemap_swift/__init__.py +8 -0
- codemap_swift-0.1.0a1/src/codemap_swift/indexer.py +261 -0
- codemap_swift-0.1.0a1/tests/__init__.py +0 -0
- codemap_swift-0.1.0a1/tests/test_indexer.py +149 -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,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codemap-swift
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Swift indexer plugin for CodeMap
|
|
5
|
+
Project-URL: Homepage, https://github.com/qxbyte/codemap
|
|
6
|
+
Author: CodeMap Contributors
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: codemap,indexer,swift,tree-sitter
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Software Development
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: codemap-core<0.2,>=0.1.0a1
|
|
14
|
+
Requires-Dist: tree-sitter-swift>=0.7
|
|
15
|
+
Requires-Dist: tree-sitter>=0.25
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# codemap-swift
|
|
21
|
+
|
|
22
|
+
> A Swift indexer for [CodeMap](https://github.com/qxbyte/codemap),
|
|
23
|
+
> shipped as an independent PyPI package.
|
|
24
|
+
|
|
25
|
+
## What it captures
|
|
26
|
+
|
|
27
|
+
Backed by `tree-sitter-swift`:
|
|
28
|
+
|
|
29
|
+
| AST node | Symbol kind |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `class_declaration` (keyword `class`) | `class` (with `extra.swift_kind=class`) |
|
|
32
|
+
| `class_declaration` (keyword `struct`) | `class` (with `extra.swift_kind=struct`) |
|
|
33
|
+
| `class_declaration` (keyword `enum`) | `class` (with `extra.swift_kind=enum`) |
|
|
34
|
+
| `protocol_declaration` | `class` (with `extra.swift_kind=protocol`) |
|
|
35
|
+
| `function_declaration` | `function` (free) or `method` (inside type) |
|
|
36
|
+
| `init_declaration` | `method` (signature prefixed with `init`) |
|
|
37
|
+
| `property_declaration` (top-level) | `variable` |
|
|
38
|
+
| `property_declaration` (inside type) | `field` |
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install "git+https://github.com/qxbyte/codemap.git#subdirectory=plugins/codemap-swift"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## SymbolID encoding
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
scip-swift . . . src/User.swift/User#hello().
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Limits
|
|
53
|
+
|
|
54
|
+
* Extensions (`extension User { ... }`) are not yet tracked.
|
|
55
|
+
* Generic-parameter descriptors are dropped.
|
|
56
|
+
* Property wrappers are ignored.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# codemap-swift
|
|
2
|
+
|
|
3
|
+
> A Swift indexer for [CodeMap](https://github.com/qxbyte/codemap),
|
|
4
|
+
> shipped as an independent PyPI package.
|
|
5
|
+
|
|
6
|
+
## What it captures
|
|
7
|
+
|
|
8
|
+
Backed by `tree-sitter-swift`:
|
|
9
|
+
|
|
10
|
+
| AST node | Symbol kind |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `class_declaration` (keyword `class`) | `class` (with `extra.swift_kind=class`) |
|
|
13
|
+
| `class_declaration` (keyword `struct`) | `class` (with `extra.swift_kind=struct`) |
|
|
14
|
+
| `class_declaration` (keyword `enum`) | `class` (with `extra.swift_kind=enum`) |
|
|
15
|
+
| `protocol_declaration` | `class` (with `extra.swift_kind=protocol`) |
|
|
16
|
+
| `function_declaration` | `function` (free) or `method` (inside type) |
|
|
17
|
+
| `init_declaration` | `method` (signature prefixed with `init`) |
|
|
18
|
+
| `property_declaration` (top-level) | `variable` |
|
|
19
|
+
| `property_declaration` (inside type) | `field` |
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install "git+https://github.com/qxbyte/codemap.git#subdirectory=plugins/codemap-swift"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## SymbolID encoding
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
scip-swift . . . src/User.swift/User#hello().
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Limits
|
|
34
|
+
|
|
35
|
+
* Extensions (`extension User { ... }`) are not yet tracked.
|
|
36
|
+
* Generic-parameter descriptors are dropped.
|
|
37
|
+
* Property wrappers are ignored.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codemap-swift"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "Swift 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", "swift", "indexer", "tree-sitter"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Topic :: Software Development",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"codemap-core>=0.1.0a1,<0.2",
|
|
21
|
+
"tree-sitter>=0.25",
|
|
22
|
+
"tree-sitter-swift>=0.7",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest>=8.0"]
|
|
27
|
+
|
|
28
|
+
[project.entry-points."codemap.indexers"]
|
|
29
|
+
swift = "codemap_swift:SwiftIndexer"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/qxbyte/codemap"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["src/codemap_swift"]
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Swift indexer built on tree-sitter-swift.
|
|
2
|
+
|
|
3
|
+
Swift's tree-sitter grammar uses a single ``class_declaration`` node to
|
|
4
|
+
cover ``class``, ``struct``, and ``enum`` declarations. The keyword
|
|
5
|
+
itself is the first child token of the node, so we read it to fill in
|
|
6
|
+
``extra.swift_kind``. ``protocol_declaration`` is a separate node type.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path, PurePosixPath
|
|
12
|
+
from typing import ClassVar
|
|
13
|
+
|
|
14
|
+
import tree_sitter
|
|
15
|
+
import tree_sitter_swift
|
|
16
|
+
|
|
17
|
+
from codemap.core.models import Diagnostic, Edge, IndexResult, Range, Symbol
|
|
18
|
+
from codemap.core.symbol import Descriptor, DescriptorKind, SymbolID
|
|
19
|
+
from codemap.indexers.base import IndexContext
|
|
20
|
+
|
|
21
|
+
SCHEME = "scip-swift"
|
|
22
|
+
LANG = "swift"
|
|
23
|
+
|
|
24
|
+
_SWIFT_LANG = tree_sitter.Language(tree_sitter_swift.language())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SwiftIndexer:
|
|
28
|
+
name: ClassVar[str] = "swift"
|
|
29
|
+
version: ClassVar[str] = "0.1.0"
|
|
30
|
+
file_patterns: ClassVar[list[str]] = ["*.swift"]
|
|
31
|
+
languages: ClassVar[list[str]] = [LANG]
|
|
32
|
+
|
|
33
|
+
def supports(self, path: Path) -> bool:
|
|
34
|
+
return path.suffix == ".swift"
|
|
35
|
+
|
|
36
|
+
def index_file(
|
|
37
|
+
self,
|
|
38
|
+
path: Path,
|
|
39
|
+
source: bytes,
|
|
40
|
+
ctx: IndexContext,
|
|
41
|
+
) -> IndexResult:
|
|
42
|
+
try:
|
|
43
|
+
source.decode("utf-8")
|
|
44
|
+
except UnicodeDecodeError as exc:
|
|
45
|
+
return IndexResult(
|
|
46
|
+
diagnostics=[
|
|
47
|
+
Diagnostic(
|
|
48
|
+
severity="error",
|
|
49
|
+
file=ctx.relative_path,
|
|
50
|
+
code="SW002",
|
|
51
|
+
message=f"not valid UTF-8: {exc}",
|
|
52
|
+
producer=self.name,
|
|
53
|
+
)
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
parser = tree_sitter.Parser(_SWIFT_LANG)
|
|
57
|
+
tree = parser.parse(source)
|
|
58
|
+
visitor = _Visitor(ctx.relative_path)
|
|
59
|
+
visitor.visit(tree.root_node)
|
|
60
|
+
diagnostics = list(visitor.diagnostics)
|
|
61
|
+
if tree.root_node.has_error:
|
|
62
|
+
diagnostics.append(
|
|
63
|
+
Diagnostic(
|
|
64
|
+
severity="warning",
|
|
65
|
+
file=ctx.relative_path,
|
|
66
|
+
range=Range(start_line=1, end_line=1),
|
|
67
|
+
code="SW001",
|
|
68
|
+
message="tree-sitter reported parse errors; symbols may be incomplete",
|
|
69
|
+
producer=self.name,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return IndexResult(
|
|
73
|
+
symbols=visitor.symbols,
|
|
74
|
+
edges=visitor.edges,
|
|
75
|
+
diagnostics=diagnostics,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class _Visitor:
|
|
80
|
+
def __init__(self, relative_path: PurePosixPath) -> None:
|
|
81
|
+
self.relative_path = relative_path
|
|
82
|
+
self.symbols: list[Symbol] = []
|
|
83
|
+
self.edges: list[Edge] = []
|
|
84
|
+
self.diagnostics: list[Diagnostic] = []
|
|
85
|
+
self._type_stack: list[str] = []
|
|
86
|
+
|
|
87
|
+
def visit(self, node: tree_sitter.Node) -> None:
|
|
88
|
+
kind = node.type
|
|
89
|
+
if kind == "class_declaration":
|
|
90
|
+
self._visit_class_like(node)
|
|
91
|
+
return
|
|
92
|
+
if kind == "protocol_declaration":
|
|
93
|
+
self._visit_type(node, swift_kind="protocol", body_field="body")
|
|
94
|
+
return
|
|
95
|
+
if kind == "function_declaration":
|
|
96
|
+
self._visit_function(node)
|
|
97
|
+
return
|
|
98
|
+
if kind == "init_declaration":
|
|
99
|
+
self._visit_init(node)
|
|
100
|
+
return
|
|
101
|
+
if kind == "property_declaration":
|
|
102
|
+
self._visit_property(node)
|
|
103
|
+
return
|
|
104
|
+
for child in node.children:
|
|
105
|
+
self.visit(child)
|
|
106
|
+
|
|
107
|
+
# ----------------------------------------------------- types
|
|
108
|
+
|
|
109
|
+
def _visit_class_like(self, node: tree_sitter.Node) -> None:
|
|
110
|
+
swift_kind = _first_keyword(node) or "class"
|
|
111
|
+
self._visit_type(node, swift_kind=swift_kind, body_field=None)
|
|
112
|
+
|
|
113
|
+
def _visit_type(
|
|
114
|
+
self,
|
|
115
|
+
node: tree_sitter.Node,
|
|
116
|
+
*,
|
|
117
|
+
swift_kind: str,
|
|
118
|
+
body_field: str | None,
|
|
119
|
+
) -> None:
|
|
120
|
+
name = _first_type_identifier(node)
|
|
121
|
+
if name is None:
|
|
122
|
+
return
|
|
123
|
+
sid = self._make_id(name, kind=DescriptorKind.TYPE)
|
|
124
|
+
self.symbols.append(
|
|
125
|
+
Symbol(
|
|
126
|
+
id=sid,
|
|
127
|
+
kind="class",
|
|
128
|
+
language=LANG,
|
|
129
|
+
file=self.relative_path,
|
|
130
|
+
range=_node_range(node),
|
|
131
|
+
extra={"swift_kind": swift_kind},
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
body = node.child_by_field_name(body_field) if body_field else _find_body(node)
|
|
135
|
+
if body is None:
|
|
136
|
+
return
|
|
137
|
+
self._type_stack.append(name)
|
|
138
|
+
try:
|
|
139
|
+
for child in body.children:
|
|
140
|
+
self.visit(child)
|
|
141
|
+
finally:
|
|
142
|
+
self._type_stack.pop()
|
|
143
|
+
|
|
144
|
+
# --------------------------------------------------- functions
|
|
145
|
+
|
|
146
|
+
def _visit_function(self, node: tree_sitter.Node) -> None:
|
|
147
|
+
name = _simple_identifier(node)
|
|
148
|
+
if name is None:
|
|
149
|
+
return
|
|
150
|
+
kind: str = "method" if self._type_stack else "function"
|
|
151
|
+
sid = self._make_id(name, kind=DescriptorKind.METHOD)
|
|
152
|
+
self.symbols.append(
|
|
153
|
+
Symbol(
|
|
154
|
+
id=sid,
|
|
155
|
+
kind=kind, # type: ignore[arg-type]
|
|
156
|
+
language=LANG,
|
|
157
|
+
file=self.relative_path,
|
|
158
|
+
range=_node_range(node),
|
|
159
|
+
signature=f"func {name}()",
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _visit_init(self, node: tree_sitter.Node) -> None:
|
|
164
|
+
if not self._type_stack:
|
|
165
|
+
return
|
|
166
|
+
sid = self._make_id("init", kind=DescriptorKind.METHOD)
|
|
167
|
+
self.symbols.append(
|
|
168
|
+
Symbol(
|
|
169
|
+
id=sid,
|
|
170
|
+
kind="method",
|
|
171
|
+
language=LANG,
|
|
172
|
+
file=self.relative_path,
|
|
173
|
+
range=_node_range(node),
|
|
174
|
+
signature="init()",
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------- properties
|
|
179
|
+
|
|
180
|
+
def _visit_property(self, node: tree_sitter.Node) -> None:
|
|
181
|
+
# property_declaration > pattern > simple_identifier (name)
|
|
182
|
+
for child in node.children:
|
|
183
|
+
if child.type == "pattern":
|
|
184
|
+
for grand in child.children:
|
|
185
|
+
if grand.type == "simple_identifier":
|
|
186
|
+
name = _node_text(grand)
|
|
187
|
+
if not name:
|
|
188
|
+
return
|
|
189
|
+
sym_kind: str = "field" if self._type_stack else "variable"
|
|
190
|
+
sid = self._make_id(name, kind=DescriptorKind.TERM)
|
|
191
|
+
self.symbols.append(
|
|
192
|
+
Symbol(
|
|
193
|
+
id=sid,
|
|
194
|
+
kind=sym_kind, # type: ignore[arg-type]
|
|
195
|
+
language=LANG,
|
|
196
|
+
file=self.relative_path,
|
|
197
|
+
range=_node_range(node),
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# -------------------------------------------------------- helpers
|
|
203
|
+
|
|
204
|
+
def _make_id(self, name: str, *, kind: DescriptorKind) -> SymbolID:
|
|
205
|
+
descriptors = list(_path_namespaces(self.relative_path))
|
|
206
|
+
descriptors.extend(Descriptor(name=t, kind=DescriptorKind.TYPE) for t in self._type_stack)
|
|
207
|
+
descriptors.append(Descriptor(name=name, kind=kind))
|
|
208
|
+
return SymbolID(scheme=SCHEME, descriptors=tuple(descriptors))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# Pure helpers
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _path_namespaces(path: PurePosixPath) -> list[Descriptor]:
|
|
217
|
+
return [Descriptor(name=part, kind=DescriptorKind.NAMESPACE) for part in path.parts]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _node_range(node: tree_sitter.Node) -> Range:
|
|
221
|
+
sr, sc = node.start_point
|
|
222
|
+
er, ec = node.end_point
|
|
223
|
+
return Range(
|
|
224
|
+
start_line=sr + 1,
|
|
225
|
+
start_col=sc,
|
|
226
|
+
end_line=max(er + 1, sr + 1),
|
|
227
|
+
end_col=ec,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _node_text(node: tree_sitter.Node) -> str:
|
|
232
|
+
return node.text.decode("utf-8") if node.text is not None else ""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _first_keyword(node: tree_sitter.Node) -> str | None:
|
|
236
|
+
"""Return the first keyword token (class/struct/enum) for class_declaration."""
|
|
237
|
+
for child in node.children:
|
|
238
|
+
if child.type in {"class", "struct", "enum", "actor"}:
|
|
239
|
+
return child.type
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _first_type_identifier(node: tree_sitter.Node) -> str | None:
|
|
244
|
+
for child in node.children:
|
|
245
|
+
if child.type == "type_identifier":
|
|
246
|
+
return _node_text(child)
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _simple_identifier(node: tree_sitter.Node) -> str | None:
|
|
251
|
+
for child in node.children:
|
|
252
|
+
if child.type == "simple_identifier":
|
|
253
|
+
return _node_text(child)
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _find_body(node: tree_sitter.Node) -> tree_sitter.Node | None:
|
|
258
|
+
for child in node.children:
|
|
259
|
+
if child.type in {"class_body", "enum_class_body", "protocol_body"}:
|
|
260
|
+
return child
|
|
261
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Unit tests for the Swift indexer plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import textwrap
|
|
6
|
+
from pathlib import Path, PurePosixPath
|
|
7
|
+
|
|
8
|
+
from codemap_swift import SwiftIndexer
|
|
9
|
+
from codemap_swift.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 = "src/Main.swift") -> IndexResult:
|
|
16
|
+
code = textwrap.dedent(source).lstrip("\n")
|
|
17
|
+
return SwiftIndexer().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="swift",
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_indexer_metadata() -> None:
|
|
29
|
+
ix = SwiftIndexer()
|
|
30
|
+
assert ix.name == "swift"
|
|
31
|
+
assert ix.languages == ["swift"]
|
|
32
|
+
assert ix.supports(Path("a.swift"))
|
|
33
|
+
assert not ix.supports(Path("a.kt"))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_scheme_is_consistent() -> None:
|
|
37
|
+
r = _index("class A {}\nfunc f() {}\nlet X = 1\n")
|
|
38
|
+
for s in r.symbols:
|
|
39
|
+
assert str(s.id).startswith(f"{SCHEME} ")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_class_declaration() -> None:
|
|
43
|
+
r = _index(
|
|
44
|
+
"""
|
|
45
|
+
class User {
|
|
46
|
+
var name: String = ""
|
|
47
|
+
func hello() -> String { return name }
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
)
|
|
51
|
+
kinds = sorted(s.kind for s in r.symbols)
|
|
52
|
+
assert kinds == ["class", "field", "method"]
|
|
53
|
+
cls = next(s for s in r.symbols if s.kind == "class")
|
|
54
|
+
assert cls.extra.get("swift_kind") == "class"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_struct_kept_as_class_with_extra() -> None:
|
|
58
|
+
r = _index("struct Point { let x: Int; let y: Int }")
|
|
59
|
+
cls = next(s for s in r.symbols if s.kind == "class")
|
|
60
|
+
assert cls.extra.get("swift_kind") == "struct"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_enum_kept_as_class_with_extra() -> None:
|
|
64
|
+
r = _index("enum Color { case red, green, blue }")
|
|
65
|
+
cls = next(s for s in r.symbols if s.kind == "class")
|
|
66
|
+
assert cls.extra.get("swift_kind") == "enum"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_protocol_kept_as_class_with_extra() -> None:
|
|
70
|
+
r = _index(
|
|
71
|
+
"""
|
|
72
|
+
protocol Greeter {
|
|
73
|
+
func hello() -> String
|
|
74
|
+
}
|
|
75
|
+
"""
|
|
76
|
+
)
|
|
77
|
+
cls = next(s for s in r.symbols if s.kind == "class")
|
|
78
|
+
assert cls.extra.get("swift_kind") == "protocol"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_init_inside_class() -> None:
|
|
82
|
+
r = _index(
|
|
83
|
+
"""
|
|
84
|
+
class User {
|
|
85
|
+
var name: String
|
|
86
|
+
init(name: String) { self.name = name }
|
|
87
|
+
}
|
|
88
|
+
"""
|
|
89
|
+
)
|
|
90
|
+
methods = [s for s in r.symbols if s.kind == "method"]
|
|
91
|
+
assert any("init" in str(m.id) for m in methods)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_free_function() -> None:
|
|
95
|
+
r = _index("func helper(x: Int) -> Int { return x + 1 }")
|
|
96
|
+
funcs = [s for s in r.symbols if s.kind == "function"]
|
|
97
|
+
assert len(funcs) == 1
|
|
98
|
+
assert "helper" in str(funcs[0].id)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_top_level_let_is_variable() -> None:
|
|
102
|
+
r = _index("let MAX = 10")
|
|
103
|
+
vars_ = [s for s in r.symbols if s.kind == "variable"]
|
|
104
|
+
assert len(vars_) == 1
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_top_level_var_is_variable() -> None:
|
|
108
|
+
r = _index("var counter = 0")
|
|
109
|
+
vars_ = [s for s in r.symbols if s.kind == "variable"]
|
|
110
|
+
assert len(vars_) == 1
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_method_inside_class_attaches_to_type() -> None:
|
|
114
|
+
r = _index(
|
|
115
|
+
"""
|
|
116
|
+
class User {
|
|
117
|
+
func hello() -> String { return "" }
|
|
118
|
+
}
|
|
119
|
+
"""
|
|
120
|
+
)
|
|
121
|
+
method = next(s for s in r.symbols if s.kind == "method")
|
|
122
|
+
assert "User#hello()." in str(method.id)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_symbol_id_uses_path_namespaces() -> None:
|
|
126
|
+
r = _index("class A {}", path="src/Foo/Bar.swift")
|
|
127
|
+
assert str(next(s for s in r.symbols if s.kind == "class").id) == (
|
|
128
|
+
"scip-swift . . . src/Foo/Bar.swift/A#"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_empty_file_yields_no_symbols() -> None:
|
|
133
|
+
r = _index("")
|
|
134
|
+
assert r.symbols == []
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_invalid_utf8_yields_diagnostic() -> None:
|
|
138
|
+
ix = SwiftIndexer()
|
|
139
|
+
r = ix.index_file(
|
|
140
|
+
Path("bad.swift"),
|
|
141
|
+
b"\xff\xfe class",
|
|
142
|
+
IndexContext(
|
|
143
|
+
project_root=Path("/tmp/proj"),
|
|
144
|
+
relative_path=PurePosixPath("bad.swift"),
|
|
145
|
+
language="swift",
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
assert r.symbols == []
|
|
149
|
+
assert r.diagnostics[0].code == "SW002"
|