codemap-scala 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
+ """Scala indexer plugin for CodeMap."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from codemap_scala.indexer import ScalaIndexer
6
+
7
+ __all__ = ["ScalaIndexer"]
8
+ __version__ = "0.1.0"
@@ -0,0 +1,287 @@
1
+ """Scala indexer built on tree-sitter-scala.
2
+
3
+ A leading ``package_clause`` (or multiple, for nested-package syntax)
4
+ prefixes every symbol with namespace descriptors. Classes / objects /
5
+ traits expose their template body so nested ``def`` / ``val`` / ``var``
6
+ members become methods/fields of the enclosing 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_scala
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-scala"
22
+ LANG = "scala"
23
+
24
+ _SCALA_LANG = tree_sitter.Language(tree_sitter_scala.language())
25
+
26
+
27
+ class ScalaIndexer:
28
+ name: ClassVar[str] = "scala"
29
+ version: ClassVar[str] = "0.1.0"
30
+ file_patterns: ClassVar[list[str]] = ["*.scala", "*.sc"]
31
+ languages: ClassVar[list[str]] = [LANG]
32
+
33
+ def supports(self, path: Path) -> bool:
34
+ return path.suffix in {".scala", ".sc"}
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="SCALA002",
51
+ message=f"not valid UTF-8: {exc}",
52
+ producer=self.name,
53
+ )
54
+ ]
55
+ )
56
+ parser = tree_sitter.Parser(_SCALA_LANG)
57
+ tree = parser.parse(source)
58
+ visitor = _Visitor(ctx.relative_path)
59
+ visitor.visit_root(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="SCALA001",
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
+
86
+ def visit_root(self, root: tree_sitter.Node) -> None:
87
+ scope: list[Descriptor] = []
88
+ for child in root.children:
89
+ kind = child.type
90
+ if kind == "package_clause":
91
+ scope.extend(
92
+ Descriptor(name=part, kind=DescriptorKind.NAMESPACE)
93
+ for part in _package_parts(child)
94
+ )
95
+ else:
96
+ self._visit(child, scope=scope)
97
+
98
+ def _visit(
99
+ self,
100
+ node: tree_sitter.Node,
101
+ *,
102
+ scope: list[Descriptor],
103
+ ) -> None:
104
+ kind = node.type
105
+ if kind == "class_definition":
106
+ scala_kind = "case_class" if _is_case(node) else "class"
107
+ self._emit_type(node, scope=scope, scala_kind=scala_kind)
108
+ elif kind == "object_definition":
109
+ scala_kind = "case_object" if _is_case(node) else "object"
110
+ self._emit_type(node, scope=scope, scala_kind=scala_kind)
111
+ elif kind == "trait_definition":
112
+ self._emit_type(node, scope=scope, scala_kind="trait")
113
+ elif kind == "type_definition":
114
+ self._emit_type_alias(node, scope=scope)
115
+ elif kind in {"function_definition", "function_declaration"}:
116
+ self._emit_method(node, scope=scope)
117
+ elif kind == "val_definition":
118
+ self._emit_value(node, scope=scope, scala_kind="val")
119
+ elif kind == "var_definition":
120
+ self._emit_value(node, scope=scope, scala_kind="var")
121
+
122
+ def _emit_type(
123
+ self,
124
+ node: tree_sitter.Node,
125
+ *,
126
+ scope: list[Descriptor],
127
+ scala_kind: str,
128
+ ) -> None:
129
+ name = _first_child_text(node, "identifier")
130
+ if name is None:
131
+ return
132
+ type_desc = Descriptor(name=name, kind=DescriptorKind.TYPE)
133
+ descriptors = [*list(scope), type_desc]
134
+ self.symbols.append(
135
+ Symbol(
136
+ id=self._make_id(descriptors),
137
+ kind="class",
138
+ language=LANG,
139
+ file=self.relative_path,
140
+ range=_node_range(node),
141
+ extra={"scala_kind": scala_kind},
142
+ )
143
+ )
144
+ # Case-class constructor parameters become fields.
145
+ if scala_kind == "case_class":
146
+ params = _first_child(node, "class_parameters")
147
+ if params is not None:
148
+ for child in params.children:
149
+ if child.type == "class_parameter":
150
+ pname = _first_child_text(child, "identifier")
151
+ if pname is None:
152
+ continue
153
+ self.symbols.append(
154
+ Symbol(
155
+ id=self._make_id(
156
+ [
157
+ *list(descriptors),
158
+ Descriptor(name=pname, kind=DescriptorKind.TERM),
159
+ ]
160
+ ),
161
+ kind="field",
162
+ language=LANG,
163
+ file=self.relative_path,
164
+ range=_node_range(child),
165
+ extra={"scala_kind": "case_field"},
166
+ )
167
+ )
168
+ # Walk the template body for nested members.
169
+ body = _first_child(node, "template_body")
170
+ if body is None:
171
+ return
172
+ for child in body.children:
173
+ self._visit(child, scope=descriptors)
174
+
175
+ def _emit_type_alias(
176
+ self,
177
+ node: tree_sitter.Node,
178
+ *,
179
+ scope: list[Descriptor],
180
+ ) -> None:
181
+ name = _first_child_text(node, "type_identifier")
182
+ if name is None:
183
+ return
184
+ self.symbols.append(
185
+ Symbol(
186
+ id=self._make_id([*list(scope), Descriptor(name=name, kind=DescriptorKind.TYPE)]),
187
+ kind="class",
188
+ language=LANG,
189
+ file=self.relative_path,
190
+ range=_node_range(node),
191
+ extra={"scala_kind": "type"},
192
+ )
193
+ )
194
+
195
+ def _emit_method(
196
+ self,
197
+ node: tree_sitter.Node,
198
+ *,
199
+ scope: list[Descriptor],
200
+ ) -> None:
201
+ name = _first_child_text(node, "identifier")
202
+ if name is None:
203
+ return
204
+ self.symbols.append(
205
+ Symbol(
206
+ id=self._make_id([*list(scope), Descriptor(name=name, kind=DescriptorKind.METHOD)]),
207
+ kind="method",
208
+ language=LANG,
209
+ file=self.relative_path,
210
+ range=_node_range(node),
211
+ signature=f"{name}()",
212
+ )
213
+ )
214
+
215
+ def _emit_value(
216
+ self,
217
+ node: tree_sitter.Node,
218
+ *,
219
+ scope: list[Descriptor],
220
+ scala_kind: str,
221
+ ) -> None:
222
+ name = _first_child_text(node, "identifier")
223
+ if name is None:
224
+ return
225
+ self.symbols.append(
226
+ Symbol(
227
+ id=self._make_id([*list(scope), Descriptor(name=name, kind=DescriptorKind.TERM)]),
228
+ kind="field",
229
+ language=LANG,
230
+ file=self.relative_path,
231
+ range=_node_range(node),
232
+ extra={"scala_kind": scala_kind},
233
+ )
234
+ )
235
+
236
+ def _make_id(self, descriptors: list[Descriptor]) -> SymbolID:
237
+ full = list(_path_namespaces(self.relative_path))
238
+ full.extend(descriptors)
239
+ return SymbolID(scheme=SCHEME, descriptors=tuple(full))
240
+
241
+
242
+ # ---------------------------------------------------------------------------
243
+ # Pure helpers
244
+ # ---------------------------------------------------------------------------
245
+
246
+
247
+ def _path_namespaces(path: PurePosixPath) -> list[Descriptor]:
248
+ return [Descriptor(name=part, kind=DescriptorKind.NAMESPACE) for part in path.parts]
249
+
250
+
251
+ def _node_range(node: tree_sitter.Node) -> Range:
252
+ sr, sc = node.start_point
253
+ er, ec = node.end_point
254
+ return Range(
255
+ start_line=sr + 1,
256
+ start_col=sc,
257
+ end_line=max(er + 1, sr + 1),
258
+ end_col=ec,
259
+ )
260
+
261
+
262
+ def _node_text(node: tree_sitter.Node) -> str:
263
+ return node.text.decode("utf-8") if node.text is not None else ""
264
+
265
+
266
+ def _first_child(node: tree_sitter.Node, kind: str) -> tree_sitter.Node | None:
267
+ for child in node.children:
268
+ if child.type == kind:
269
+ return child
270
+ return None
271
+
272
+
273
+ def _first_child_text(node: tree_sitter.Node, kind: str) -> str | None:
274
+ found = _first_child(node, kind)
275
+ return _node_text(found) if found is not None else None
276
+
277
+
278
+ def _is_case(node: tree_sitter.Node) -> bool:
279
+ return any(child.type == "case" for child in node.children)
280
+
281
+
282
+ def _package_parts(node: tree_sitter.Node) -> list[str]:
283
+ ident_node = _first_child(node, "package_identifier")
284
+ if ident_node is None:
285
+ # Fallback: single identifier under package_clause.
286
+ return [_node_text(c) for c in node.children if c.type == "identifier"]
287
+ return [_node_text(c) for c in ident_node.children if c.type == "identifier"]
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.4
2
+ Name: codemap-scala
3
+ Version: 0.1.0a1
4
+ Summary: Scala language indexer plugin for CodeMap
5
+ Project-URL: Homepage, https://github.com/qxbyte/codemap
6
+ Author: CodeMap Contributors
7
+ License: MIT
8
+ Keywords: codemap,indexer,scala,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-scala>=0.23
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-scala
21
+
22
+ > A Scala language 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-scala`:
28
+
29
+ | AST node | Symbol kind |
30
+ |---|---|
31
+ | `package_clause` (incl. `package_identifier`) | namespace prefix |
32
+ | `class_definition` (incl. `case class`) | `class` (`extra.scala_kind=class` / `case_class`) |
33
+ | `object_definition` (incl. `case object`) | `class` (`extra.scala_kind=object`) |
34
+ | `trait_definition` | `class` (`extra.scala_kind=trait`) |
35
+ | `type_definition` (top-level alias) | `class` (`extra.scala_kind=type`) |
36
+ | `function_definition` / `function_declaration` | `method` |
37
+ | `val_definition` (top-level or in template body) | `field` (`extra.scala_kind=val`) |
38
+ | `var_definition` (top-level or in template body) | `field` (`extra.scala_kind=var`) |
39
+ | `class_parameter` (in `case class`) | `field` (`extra.scala_kind=case_field`) |
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install "git+https://github.com/qxbyte/codemap.git#subdirectory=plugins/codemap-scala"
45
+ ```
46
+
47
+ ## File patterns
48
+
49
+ * `*.scala`, `*.sc`
50
+
51
+ ## Limits
52
+
53
+ * Implicits and Scala 3 `given` declarations parse but are not specifically
54
+ tagged.
55
+ * Anonymous classes (`new T { ... }`) are not surfaced.
56
+ * Block-scoped `def` inside a method body is not emitted — it is private
57
+ to that method.
58
+
59
+ ## License
60
+
61
+ MIT.
@@ -0,0 +1,6 @@
1
+ codemap_scala/__init__.py,sha256=ch-EJxrOJh7cknEErFgjqNrcM50VL30h8--xEkkUaCQ,174
2
+ codemap_scala/indexer.py,sha256=1sV7AfaYEOKV3COYyZTV_gAYnKY-rz6VnwDVhwI45KM,9806
3
+ codemap_scala-0.1.0a1.dist-info/METADATA,sha256=PryUPRvSkBAQ6Y1Vmh_QcLeNOSulSOCdTlsxqWKJ0pk,1981
4
+ codemap_scala-0.1.0a1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ codemap_scala-0.1.0a1.dist-info/entry_points.txt,sha256=rkUKL5rkDgkwk3sFmuk7cXdxk5FhF0jNUMt8bSLAjOo,54
6
+ codemap_scala-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
+ scala = codemap_scala:ScalaIndexer