codemap-csharp 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.
@@ -0,0 +1,8 @@
1
+ """C# indexer plugin for CodeMap."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from codemap_csharp.indexer import CSharpIndexer
6
+
7
+ __all__ = ["CSharpIndexer"]
8
+ __version__ = "0.1.0"
@@ -0,0 +1,315 @@
1
+ """C# indexer built on tree-sitter-c-sharp.
2
+
3
+ ``namespace_declaration`` chains (including dotted ``A.B.C`` qualifiers)
4
+ are decomposed into a stack of namespace descriptors that prefix each
5
+ contained type's SymbolID.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path, PurePosixPath
11
+ from typing import ClassVar
12
+
13
+ import tree_sitter
14
+ import tree_sitter_c_sharp
15
+
16
+ from codemap.core.models import Diagnostic, Edge, IndexResult, Range, Symbol
17
+ from codemap.core.symbol import Descriptor, DescriptorKind, SymbolID
18
+ from codemap.indexers.base import IndexContext
19
+
20
+ SCHEME = "scip-csharp"
21
+ LANG = "csharp"
22
+
23
+ _CS_LANG = tree_sitter.Language(tree_sitter_c_sharp.language())
24
+
25
+ _TYPE_DECL_KINDS = {
26
+ "class_declaration": (None, "class"),
27
+ "interface_declaration": ("interface", "class"),
28
+ "struct_declaration": ("struct", "class"),
29
+ "record_declaration": ("record", "class"),
30
+ "enum_declaration": ("enum", "class"),
31
+ "delegate_declaration": ("delegate", "class"),
32
+ }
33
+
34
+
35
+ class CSharpIndexer:
36
+ name: ClassVar[str] = "csharp"
37
+ version: ClassVar[str] = "0.1.0"
38
+ file_patterns: ClassVar[list[str]] = ["*.cs", "*.csx"]
39
+ languages: ClassVar[list[str]] = [LANG]
40
+
41
+ def supports(self, path: Path) -> bool:
42
+ return path.suffix in {".cs", ".csx"}
43
+
44
+ def index_file(
45
+ self,
46
+ path: Path,
47
+ source: bytes,
48
+ ctx: IndexContext,
49
+ ) -> IndexResult:
50
+ try:
51
+ source.decode("utf-8")
52
+ except UnicodeDecodeError as exc:
53
+ return IndexResult(
54
+ diagnostics=[
55
+ Diagnostic(
56
+ severity="error",
57
+ file=ctx.relative_path,
58
+ code="CS002",
59
+ message=f"not valid UTF-8: {exc}",
60
+ producer=self.name,
61
+ )
62
+ ]
63
+ )
64
+ parser = tree_sitter.Parser(_CS_LANG)
65
+ tree = parser.parse(source)
66
+ visitor = _Visitor(ctx.relative_path)
67
+ visitor.visit_root(tree.root_node)
68
+ diagnostics = list(visitor.diagnostics)
69
+ if tree.root_node.has_error:
70
+ diagnostics.append(
71
+ Diagnostic(
72
+ severity="warning",
73
+ file=ctx.relative_path,
74
+ range=Range(start_line=1, end_line=1),
75
+ code="CS001",
76
+ message="tree-sitter reported parse errors; symbols may be incomplete",
77
+ producer=self.name,
78
+ )
79
+ )
80
+ return IndexResult(
81
+ symbols=visitor.symbols,
82
+ edges=visitor.edges,
83
+ diagnostics=diagnostics,
84
+ )
85
+
86
+
87
+ class _Visitor:
88
+ def __init__(self, relative_path: PurePosixPath) -> None:
89
+ self.relative_path = relative_path
90
+ self.symbols: list[Symbol] = []
91
+ self.edges: list[Edge] = []
92
+ self.diagnostics: list[Diagnostic] = []
93
+
94
+ def visit_root(self, root: tree_sitter.Node) -> None:
95
+ for child in root.children:
96
+ self._visit(child, 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 == "namespace_declaration":
106
+ self._visit_namespace(node, scope=scope)
107
+ elif kind in _TYPE_DECL_KINDS:
108
+ csharp_kind, symbol_kind = _TYPE_DECL_KINDS[kind]
109
+ self._emit_type(
110
+ node,
111
+ scope=scope,
112
+ csharp_kind=csharp_kind,
113
+ symbol_kind=symbol_kind,
114
+ )
115
+ elif kind == "method_declaration":
116
+ self._emit_method(node, scope=scope)
117
+ elif kind == "property_declaration":
118
+ self._emit_property(node, scope=scope)
119
+ elif kind == "field_declaration":
120
+ self._emit_field_declaration(node, scope=scope)
121
+ elif kind == "enum_member_declaration":
122
+ self._emit_enum_member(node, scope=scope)
123
+
124
+ def _visit_namespace(
125
+ self,
126
+ node: tree_sitter.Node,
127
+ *,
128
+ scope: list[Descriptor],
129
+ ) -> None:
130
+ new_scope = list(scope)
131
+ for child in node.children:
132
+ if child.type == "identifier":
133
+ new_scope.append(Descriptor(name=_node_text(child), kind=DescriptorKind.NAMESPACE))
134
+ elif child.type == "qualified_name":
135
+ new_scope.extend(
136
+ Descriptor(name=part, kind=DescriptorKind.NAMESPACE)
137
+ for part in _qualified_name_parts(child)
138
+ )
139
+ body = _first_child(node, "declaration_list")
140
+ if body is None:
141
+ return
142
+ for child in body.children:
143
+ self._visit(child, scope=new_scope)
144
+
145
+ def _emit_type(
146
+ self,
147
+ node: tree_sitter.Node,
148
+ *,
149
+ scope: list[Descriptor],
150
+ csharp_kind: str | None,
151
+ symbol_kind: str,
152
+ ) -> None:
153
+ name = _first_child_text(node, "identifier")
154
+ if name is None:
155
+ return
156
+ type_desc = Descriptor(name=name, kind=DescriptorKind.TYPE)
157
+ descriptors = [*list(scope), type_desc]
158
+ extra: dict[str, str] = {}
159
+ if csharp_kind is not None:
160
+ extra["csharp_kind"] = csharp_kind
161
+ self.symbols.append(
162
+ Symbol(
163
+ id=self._make_id(descriptors),
164
+ kind=symbol_kind,
165
+ language=LANG,
166
+ file=self.relative_path,
167
+ range=_node_range(node),
168
+ extra=extra,
169
+ )
170
+ )
171
+ # Recurse into nested members.
172
+ body = _first_child(node, "declaration_list") or _first_child(
173
+ node, "enum_member_declaration_list"
174
+ )
175
+ if body is None:
176
+ return
177
+ for child in body.children:
178
+ self._visit(child, scope=descriptors)
179
+
180
+ def _emit_method(
181
+ self,
182
+ node: tree_sitter.Node,
183
+ *,
184
+ scope: list[Descriptor],
185
+ ) -> None:
186
+ name = _first_child_text(node, "identifier")
187
+ if name is None:
188
+ return
189
+ self.symbols.append(
190
+ Symbol(
191
+ id=self._make_id([*list(scope), Descriptor(name=name, kind=DescriptorKind.METHOD)]),
192
+ kind="method",
193
+ language=LANG,
194
+ file=self.relative_path,
195
+ range=_node_range(node),
196
+ signature=f"{name}()",
197
+ )
198
+ )
199
+
200
+ def _emit_property(
201
+ self,
202
+ node: tree_sitter.Node,
203
+ *,
204
+ scope: list[Descriptor],
205
+ ) -> None:
206
+ name = _first_child_text(node, "identifier")
207
+ if name is None:
208
+ return
209
+ self.symbols.append(
210
+ Symbol(
211
+ id=self._make_id([*list(scope), Descriptor(name=name, kind=DescriptorKind.TERM)]),
212
+ kind="field",
213
+ language=LANG,
214
+ file=self.relative_path,
215
+ range=_node_range(node),
216
+ extra={"csharp_kind": "property"},
217
+ )
218
+ )
219
+
220
+ def _emit_field_declaration(
221
+ self,
222
+ node: tree_sitter.Node,
223
+ *,
224
+ scope: list[Descriptor],
225
+ ) -> None:
226
+ var_decl = _first_child(node, "variable_declaration")
227
+ if var_decl is None:
228
+ return
229
+ for child in var_decl.children:
230
+ if child.type == "variable_declarator":
231
+ name = _first_child_text(child, "identifier")
232
+ if name is None:
233
+ continue
234
+ self.symbols.append(
235
+ Symbol(
236
+ id=self._make_id(
237
+ [*list(scope), Descriptor(name=name, kind=DescriptorKind.TERM)]
238
+ ),
239
+ kind="field",
240
+ language=LANG,
241
+ file=self.relative_path,
242
+ range=_node_range(node),
243
+ )
244
+ )
245
+
246
+ def _emit_enum_member(
247
+ self,
248
+ node: tree_sitter.Node,
249
+ *,
250
+ scope: list[Descriptor],
251
+ ) -> None:
252
+ name = _first_child_text(node, "identifier")
253
+ if name is None:
254
+ return
255
+ self.symbols.append(
256
+ Symbol(
257
+ id=self._make_id([*list(scope), Descriptor(name=name, kind=DescriptorKind.TERM)]),
258
+ kind="field",
259
+ language=LANG,
260
+ file=self.relative_path,
261
+ range=_node_range(node),
262
+ extra={"csharp_kind": "enum_member"},
263
+ )
264
+ )
265
+
266
+ def _make_id(self, descriptors: list[Descriptor]) -> SymbolID:
267
+ full = list(_path_namespaces(self.relative_path))
268
+ full.extend(descriptors)
269
+ return SymbolID(scheme=SCHEME, descriptors=tuple(full))
270
+
271
+
272
+ # ---------------------------------------------------------------------------
273
+ # Pure helpers
274
+ # ---------------------------------------------------------------------------
275
+
276
+
277
+ def _path_namespaces(path: PurePosixPath) -> list[Descriptor]:
278
+ return [Descriptor(name=part, kind=DescriptorKind.NAMESPACE) for part in path.parts]
279
+
280
+
281
+ def _node_range(node: tree_sitter.Node) -> Range:
282
+ sr, sc = node.start_point
283
+ er, ec = node.end_point
284
+ return Range(
285
+ start_line=sr + 1,
286
+ start_col=sc,
287
+ end_line=max(er + 1, sr + 1),
288
+ end_col=ec,
289
+ )
290
+
291
+
292
+ def _node_text(node: tree_sitter.Node) -> str:
293
+ return node.text.decode("utf-8") if node.text is not None else ""
294
+
295
+
296
+ def _first_child(node: tree_sitter.Node, kind: str) -> tree_sitter.Node | None:
297
+ for child in node.children:
298
+ if child.type == kind:
299
+ return child
300
+ return None
301
+
302
+
303
+ def _first_child_text(node: tree_sitter.Node, kind: str) -> str | None:
304
+ found = _first_child(node, kind)
305
+ return _node_text(found) if found is not None else None
306
+
307
+
308
+ def _qualified_name_parts(node: tree_sitter.Node) -> list[str]:
309
+ parts: list[str] = []
310
+ for child in node.children:
311
+ if child.type == "identifier":
312
+ parts.append(_node_text(child))
313
+ elif child.type == "qualified_name":
314
+ parts.extend(_qualified_name_parts(child))
315
+ return parts
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: codemap-csharp
3
+ Version: 0.1.0
4
+ Summary: C# language indexer plugin for CodeMap
5
+ Project-URL: Homepage, https://github.com/qxbyte/codemap
6
+ Author: CodeMap Contributors
7
+ License: MIT
8
+ Keywords: c-sharp,codemap,csharp,dotnet,indexer,tree-sitter
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: C#
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-c-sharp>=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-csharp
22
+
23
+ > A C# language indexer for [CodeMap](https://github.com/qxbyte/codemap),
24
+ > shipped as an independent PyPI package.
25
+
26
+ ## What it captures
27
+
28
+ Backed by `tree-sitter-c-sharp`:
29
+
30
+ | AST node | Symbol kind |
31
+ |---|---|
32
+ | `namespace_declaration` (incl. `qualified_name`) | namespace prefix (recursed into) |
33
+ | `class_declaration` | `class` |
34
+ | `interface_declaration` | `class` (`extra.csharp_kind=interface`) |
35
+ | `struct_declaration` | `class` (`extra.csharp_kind=struct`) |
36
+ | `record_declaration` | `class` (`extra.csharp_kind=record`) |
37
+ | `enum_declaration` | `class` (`extra.csharp_kind=enum`) |
38
+ | `delegate_declaration` | `class` (`extra.csharp_kind=delegate`) |
39
+ | `method_declaration` | `method` |
40
+ | `property_declaration` | `field` (`extra.csharp_kind=property`) |
41
+ | `field_declaration` → `variable_declarator` | `field` |
42
+ | `enum_member_declaration` | `field` |
43
+
44
+ Top-level statements (C# 9+) are not surfaced as named symbols.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install codemap-csharp
50
+ ```
51
+
52
+ ## File patterns
53
+
54
+ * `*.cs`, `*.csx`
55
+
56
+ ## Limits
57
+
58
+ * File-scoped namespaces (``namespace App;``) and block-scoped namespaces
59
+ are both handled, but global usings are not emitted as symbols.
60
+ * Partial classes are emitted as separate symbols per file — the index
61
+ layer does not currently merge them.
62
+
63
+ ## License
64
+
65
+ MIT.
@@ -0,0 +1,6 @@
1
+ codemap_csharp/__init__.py,sha256=j6a8eG3Pcfek-_06NlubDRazKvy4QAlXJMPdzx-hOGU,174
2
+ codemap_csharp/indexer.py,sha256=MGGq9EMcTKUz3kgutXF0IDMwk43Rl7TVbOP86wonwYc,10102
3
+ codemap_csharp-0.1.0.dist-info/METADATA,sha256=mam2xOJbZwmtgJXMn6z9q0h4rXlJyJB35bcsqcP7aAM,1995
4
+ codemap_csharp-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ codemap_csharp-0.1.0.dist-info/entry_points.txt,sha256=RiPsPCnMU_G-2fwmDQwWmBipwQE7sMpv4Ir5dtMYJ1E,57
6
+ codemap_csharp-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [codemap.indexers]
2
+ csharp = codemap_csharp:CSharpIndexer