codemap-swift 0.1.0a1__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.
@@ -0,0 +1,8 @@
1
+ """Swift indexer plugin for CodeMap."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from codemap_swift.indexer import SwiftIndexer
6
+
7
+ __all__ = ["SwiftIndexer"]
8
+ __version__ = "0.1.0"
@@ -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
@@ -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,6 @@
1
+ codemap_swift/__init__.py,sha256=wwetgvtw-HRD_q9qXMMh5-H6os54F89R0-jYtJ-qr7k,174
2
+ codemap_swift/indexer.py,sha256=9U04J1tGJU27VtMoxLkSaMrG0BpLUFeDiRvb-ftjO10,8805
3
+ codemap_swift-0.1.0a1.dist-info/METADATA,sha256=g25bXI7IZJ7Di89gzSKuUfC1AzduWIS1MZTQZ7gW5so,1766
4
+ codemap_swift-0.1.0a1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ codemap_swift-0.1.0a1.dist-info/entry_points.txt,sha256=sSOIgqPfd91Bj2866Aa3APp39g6jvvWhzQDWkJRggag,54
6
+ codemap_swift-0.1.0a1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [codemap.indexers]
2
+ swift = codemap_swift:SwiftIndexer