code-atlas-forge 1.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.
Files changed (181) hide show
  1. code_atlas/__init__.py +6 -0
  2. code_atlas/__main__.py +6 -0
  3. code_atlas/api/__init__.py +5 -0
  4. code_atlas/api/handlers.py +1514 -0
  5. code_atlas/api/server.py +394 -0
  6. code_atlas/cli.py +2084 -0
  7. code_atlas/config.py +149 -0
  8. code_atlas/context/__init__.py +66 -0
  9. code_atlas/context/contracts.py +277 -0
  10. code_atlas/context/domain.py +760 -0
  11. code_atlas/context/engine.py +442 -0
  12. code_atlas/context/expand.py +133 -0
  13. code_atlas/context/renderers.py +71 -0
  14. code_atlas/context/tokenizer.py +37 -0
  15. code_atlas/contracts/__init__.py +38 -0
  16. code_atlas/contracts/enums.py +67 -0
  17. code_atlas/contracts/models.py +175 -0
  18. code_atlas/contracts/ports.py +70 -0
  19. code_atlas/discovery/__init__.py +25 -0
  20. code_atlas/discovery/gitignore.py +119 -0
  21. code_atlas/discovery/identity.py +52 -0
  22. code_atlas/discovery/models.py +204 -0
  23. code_atlas/discovery/scanner.py +495 -0
  24. code_atlas/domains/__init__.py +107 -0
  25. code_atlas/domains/api/__init__.py +28 -0
  26. code_atlas/domains/api/adapter.py +405 -0
  27. code_atlas/domains/api/common.py +308 -0
  28. code_atlas/domains/api/consumers.py +224 -0
  29. code_atlas/domains/api/emit.py +231 -0
  30. code_atlas/domains/api/openapi.py +398 -0
  31. code_atlas/domains/api/routes.py +582 -0
  32. code_atlas/domains/api/yaml_subset.py +683 -0
  33. code_atlas/domains/aws/__init__.py +28 -0
  34. code_atlas/domains/aws/adapter.py +382 -0
  35. code_atlas/domains/aws/common.py +261 -0
  36. code_atlas/domains/aws/configs.py +1164 -0
  37. code_atlas/domains/aws/emit.py +411 -0
  38. code_atlas/domains/aws/pyfiles.py +2220 -0
  39. code_atlas/domains/aws/sqlbridge.py +448 -0
  40. code_atlas/domains/cicd/__init__.py +50 -0
  41. code_atlas/domains/cicd/adapter.py +762 -0
  42. code_atlas/domains/cicd/classify.py +136 -0
  43. code_atlas/domains/cicd/workflow.py +504 -0
  44. code_atlas/domains/cicd/yamlmini.py +946 -0
  45. code_atlas/domains/cli_surface.py +739 -0
  46. code_atlas/domains/contracts.py +274 -0
  47. code_atlas/domains/crossresolve.py +1208 -0
  48. code_atlas/domains/evidence.py +57 -0
  49. code_atlas/domains/identity.py +149 -0
  50. code_atlas/domains/model.py +202 -0
  51. code_atlas/domains/registry.py +250 -0
  52. code_atlas/domains/resolution.py +65 -0
  53. code_atlas/domains/spark/__init__.py +31 -0
  54. code_atlas/domains/spark/adapter.py +146 -0
  55. code_atlas/domains/spark/emit.py +344 -0
  56. code_atlas/domains/spark/extract.py +108 -0
  57. code_atlas/domains/spark/sqllit.py +192 -0
  58. code_atlas/domains/spark/walk.py +1943 -0
  59. code_atlas/domains/sql/__init__.py +30 -0
  60. code_atlas/domains/sql/adapter.py +382 -0
  61. code_atlas/domains/sql/extract.py +1271 -0
  62. code_atlas/domains/sql/nodes.py +264 -0
  63. code_atlas/domains/sql/parser.py +1510 -0
  64. code_atlas/domains/sql/tokens.py +318 -0
  65. code_atlas/domains/summarize.py +126 -0
  66. code_atlas/domains/terraform/__init__.py +20 -0
  67. code_atlas/domains/terraform/adapter.py +127 -0
  68. code_atlas/domains/terraform/catalog.py +43 -0
  69. code_atlas/domains/terraform/extract.py +1277 -0
  70. code_atlas/domains/terraform/hcl.py +538 -0
  71. code_atlas/domains/terraform/refs.py +181 -0
  72. code_atlas/domains/terraform/tfjson.py +163 -0
  73. code_atlas/domains/terraform/tfvars.py +98 -0
  74. code_atlas/domains/validation.py +120 -0
  75. code_atlas/env_override.py +27 -0
  76. code_atlas/eval/__init__.py +26 -0
  77. code_atlas/eval/domain_eval.py +681 -0
  78. code_atlas/eval/ground_truth.py +688 -0
  79. code_atlas/eval/impact_truth.py +758 -0
  80. code_atlas/eval/metrics.py +178 -0
  81. code_atlas/eval/pack_value.py +610 -0
  82. code_atlas/federation/__init__.py +72 -0
  83. code_atlas/federation/federation.py +676 -0
  84. code_atlas/federation/model.py +355 -0
  85. code_atlas/federation/store.py +194 -0
  86. code_atlas/gitbin.py +48 -0
  87. code_atlas/graph/__init__.py +20 -0
  88. code_atlas/graph/builder.py +378 -0
  89. code_atlas/graph/identity.py +54 -0
  90. code_atlas/graph/queries.py +47 -0
  91. code_atlas/graph/validation.py +92 -0
  92. code_atlas/impact/__init__.py +37 -0
  93. code_atlas/impact/blast_radius.py +171 -0
  94. code_atlas/impact/contract_analyzer.py +104 -0
  95. code_atlas/impact/contracts.py +315 -0
  96. code_atlas/impact/diff_reader.py +142 -0
  97. code_atlas/impact/domain_traversal.py +768 -0
  98. code_atlas/impact/engine.py +392 -0
  99. code_atlas/impact/packs.py +128 -0
  100. code_atlas/impact/report.py +136 -0
  101. code_atlas/impact/revision_binding.py +220 -0
  102. code_atlas/impact/risk_classifier.py +83 -0
  103. code_atlas/impact/snapshot_resolver.py +136 -0
  104. code_atlas/impact/symbol_diff.py +210 -0
  105. code_atlas/impact/test_selector.py +166 -0
  106. code_atlas/indexing/__init__.py +5 -0
  107. code_atlas/indexing/cache.py +396 -0
  108. code_atlas/indexing/change_detector.py +278 -0
  109. code_atlas/indexing/coordinator.py +1032 -0
  110. code_atlas/indexing/dependency_planner.py +268 -0
  111. code_atlas/indexing/persist.py +436 -0
  112. code_atlas/indexing/reuse.py +85 -0
  113. code_atlas/install/__init__.py +7 -0
  114. code_atlas/install/cli.py +168 -0
  115. code_atlas/install/lifecycle.py +286 -0
  116. code_atlas/mcp.py +674 -0
  117. code_atlas/observability/__init__.py +19 -0
  118. code_atlas/observability/logging.py +199 -0
  119. code_atlas/parsers/__init__.py +43 -0
  120. code_atlas/parsers/contracts.py +209 -0
  121. code_atlas/parsers/engine.py +125 -0
  122. code_atlas/parsers/languages/__init__.py +1 -0
  123. code_atlas/parsers/languages/python.py +391 -0
  124. code_atlas/parsers/languages/tsx.py +28 -0
  125. code_atlas/parsers/languages/typescript.py +909 -0
  126. code_atlas/parsers/registry.py +51 -0
  127. code_atlas/parsers/tree_sitter_runtime.py +118 -0
  128. code_atlas/resolvers/__init__.py +33 -0
  129. code_atlas/resolvers/bootstrap.py +246 -0
  130. code_atlas/resolvers/capabilities.py +33 -0
  131. code_atlas/resolvers/contracts.py +133 -0
  132. code_atlas/resolvers/coordinator.py +96 -0
  133. code_atlas/resolvers/enrichment.py +318 -0
  134. code_atlas/resolvers/lsp/__init__.py +26 -0
  135. code_atlas/resolvers/lsp/adapter.py +256 -0
  136. code_atlas/resolvers/lsp/lifecycle.py +251 -0
  137. code_atlas/resolvers/lsp/spawn.py +92 -0
  138. code_atlas/resolvers/lsp/transport.py +240 -0
  139. code_atlas/resolvers/registry.py +33 -0
  140. code_atlas/resolvers/scip/__init__.py +49 -0
  141. code_atlas/resolvers/scip/decoder.py +216 -0
  142. code_atlas/resolvers/scip/importer.py +248 -0
  143. code_atlas/resolvers/scip/loader.py +90 -0
  144. code_atlas/resolvers/scip/mapper.py +271 -0
  145. code_atlas/resolvers/scip/provenance.py +211 -0
  146. code_atlas/resolvers/scip/resolver.py +289 -0
  147. code_atlas/resolvers/scip/validation.py +101 -0
  148. code_atlas/resolvers/syntactic/__init__.py +8 -0
  149. code_atlas/resolvers/syntactic/resolver.py +254 -0
  150. code_atlas/search/__init__.py +32 -0
  151. code_atlas/search/contracts.py +176 -0
  152. code_atlas/search/domain.py +561 -0
  153. code_atlas/search/engine.py +530 -0
  154. code_atlas/search/federated.py +276 -0
  155. code_atlas/search/graph.py +282 -0
  156. code_atlas/search/hybrid.py +151 -0
  157. code_atlas/search/intent.py +48 -0
  158. code_atlas/search/lexical.py +176 -0
  159. code_atlas/search/refs.py +359 -0
  160. code_atlas/search/structural.py +505 -0
  161. code_atlas/search/tokenizer.py +43 -0
  162. code_atlas/search/vector/__init__.py +35 -0
  163. code_atlas/search/vector/contracts.py +76 -0
  164. code_atlas/search/vector/embeddings.py +96 -0
  165. code_atlas/search/vector/index.py +169 -0
  166. code_atlas/search/vector/provider.py +50 -0
  167. code_atlas/storage/__init__.py +14 -0
  168. code_atlas/storage/db.py +47 -0
  169. code_atlas/storage/migrations.py +552 -0
  170. code_atlas/storage/store.py +1045 -0
  171. code_atlas/ui/__init__.py +29 -0
  172. code_atlas/ui/i18n.py +143 -0
  173. code_atlas/ui/kit.py +689 -0
  174. code_atlas/ui/wizard.py +317 -0
  175. code_atlas_forge-1.1.0.dist-info/METADATA +246 -0
  176. code_atlas_forge-1.1.0.dist-info/RECORD +181 -0
  177. code_atlas_forge-1.1.0.dist-info/WHEEL +5 -0
  178. code_atlas_forge-1.1.0.dist-info/entry_points.txt +2 -0
  179. code_atlas_forge-1.1.0.dist-info/licenses/LICENSE +202 -0
  180. code_atlas_forge-1.1.0.dist-info/licenses/NOTICE +17 -0
  181. code_atlas_forge-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,909 @@
1
+ """Adaptador TypeScript via tree-sitter-typescript — extração real.
2
+
3
+ Cobre class/interface/function/method (incl. signature-only), arrow
4
+ functions nomeáveis (bound via variable_declarator), imports/exports com
5
+ aliases, extends/implements declarados, type/enum declarations e
6
+ modificadores. A política para funções anônimas: só ganham identidade
7
+ quando ligadas a um nome (declarator/property) — documentado no spec.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ from typing import Any
14
+
15
+ import tree_sitter_typescript
16
+ from tree_sitter import Node
17
+
18
+ from code_atlas.parsers import tree_sitter_runtime as rt
19
+ from code_atlas.parsers.contracts import (
20
+ ExtractedReference,
21
+ ExtractedRelation,
22
+ ExtractedSymbol,
23
+ ParseDiagnostic,
24
+ ParserCapability,
25
+ ParseRequest,
26
+ ParseResult,
27
+ )
28
+
29
+ _MODIFIER_TYPES = {
30
+ "async",
31
+ "static",
32
+ "readonly",
33
+ "abstract",
34
+ "declare",
35
+ "override_modifier",
36
+ "accessibility_modifier",
37
+ "default",
38
+ }
39
+
40
+
41
+ def module_name(path: str) -> str:
42
+ """`src/a.ts` → ``src.a``; `src/b.d.ts` → ``src.b``; `i.d.cts` → ``i``."""
43
+ for suffix in (".d.cts", ".d.mts", ".d.ts", ".tsx",
44
+ ".cts", ".mts", ".ts", ".jsx"):
45
+ if path.endswith(suffix):
46
+ path = path[: -len(suffix)]
47
+ break
48
+ return ".".join(p for p in path.split("/") if p)
49
+
50
+
51
+ def _children_of_type(node: Node, types: tuple[str, ...]) -> list[Node]:
52
+ return [c for c in node.named_children if c.type in types]
53
+
54
+
55
+ def _modifiers(node: Node, src: bytes) -> tuple[str, ...]:
56
+ mods = []
57
+ for c in node.children:
58
+ if c.type in _MODIFIER_TYPES:
59
+ mods.append(src[c.start_byte : c.end_byte].decode("utf-8", "replace"))
60
+ return tuple(dict.fromkeys(mods))
61
+
62
+
63
+ class _TsExtractor:
64
+ """Visitor compartilhado TypeScript/TSX (mesma gramática-base)."""
65
+
66
+ def __init__(self, src: bytes, module: str, language: str) -> None:
67
+ self.src = src
68
+ self.module = module
69
+ self.language = language
70
+ self.symbols: list[ExtractedSymbol] = []
71
+ self.refs: list[ExtractedReference] = []
72
+ self.relations: list[ExtractedRelation] = []
73
+ self.diagnostics: list[ParseDiagnostic] = []
74
+ # decorators que a gramática anexa como siblings em class_body
75
+ # (pendentes até a declaração seguinte consumi-los — lf-0130)
76
+ self._pending_decs: list[Node] = []
77
+
78
+ def text(self, node: Node) -> str:
79
+ return self.src[node.start_byte : node.end_byte].decode("utf-8", "replace")
80
+
81
+ def _diag(self, code: str, severity: str, message: str,
82
+ node: Node) -> None:
83
+ self.diagnostics.append(
84
+ ParseDiagnostic(
85
+ code=code, severity=severity, message=message,
86
+ span=rt.span_of(node),
87
+ )
88
+ )
89
+
90
+ def _emit(self, kind: str, name: str, name_node: Node, node: Node,
91
+ scope: tuple[str, ...], **kw: Any) -> str:
92
+ qname = ".".join((*scope, name)) if scope else name
93
+ qname = f"{self.module}.{qname}" if self.module else qname
94
+ self.symbols.append(
95
+ ExtractedSymbol(
96
+ language=self.language,
97
+ kind=kind,
98
+ name=name,
99
+ qualified_name=qname,
100
+ span=rt.span_of(node),
101
+ name_span=rt.span_of(name_node),
102
+ scope_path=scope,
103
+ **kw,
104
+ )
105
+ )
106
+ return qname
107
+
108
+ def _signature(self, node: Node, body: Node | None) -> str:
109
+ end = body.start_byte if body is not None else node.end_byte
110
+ return self.src[node.start_byte : end].decode("utf-8", "replace").rstrip(" {\n")
111
+
112
+ def _name_node(self, node: Node) -> Node | None:
113
+ n = node.child_by_field_name("name")
114
+ if n is not None:
115
+ return n
116
+ return next(
117
+ (c for c in node.named_children if c.type.endswith("identifier")), None
118
+ )
119
+
120
+ # -- decorators ----------------------------------------------------
121
+ # `@Injectable()` em classe é field `decorator` do próprio nó; em
122
+ # métodos a gramática anexa o `decorator` como filho de `class_body`
123
+ # (sibling anterior ao method_definition) — os dois caminhos cobrem.
124
+
125
+ def _decorator_text(self, node: Node) -> str:
126
+ return self.text(node).lstrip("@").strip()
127
+
128
+ def _take_decorators(
129
+ self, node: Node, scope: tuple[str, ...], name: str
130
+ ) -> tuple[str, ...]:
131
+ """Coleta decorators aplicados ao símbolo e emite refs
132
+ ``decorator`` (o builder transforma em arestas REFERENCES)."""
133
+ decs = [c for c in node.children if c.type == "decorator"]
134
+ decs += self._pending_decs
135
+ self._pending_decs = []
136
+ out: list[str] = []
137
+ for d in decs:
138
+ text = self._decorator_text(d)
139
+ if not text:
140
+ continue
141
+ out.append(text)
142
+ ref_name = text.split("(")[0].strip()
143
+ if ref_name:
144
+ self.refs.append(
145
+ ExtractedReference(
146
+ name=ref_name, context="decorator",
147
+ span=rt.span_of(d), scope_path=(*scope, name),
148
+ )
149
+ )
150
+ return tuple(dict.fromkeys(out))
151
+
152
+ # -- declarações --------------------------------------------------
153
+
154
+ def _handle_class(self, node: Node, scope: tuple[str, ...],
155
+ exported: bool,
156
+ fallback_name: str | None = None) -> None:
157
+ name_node = self._name_node(node)
158
+ name = (
159
+ self.text(name_node) if name_node is not None else fallback_name
160
+ )
161
+ if name is None:
162
+ return
163
+ name_node = name_node or node
164
+ bases: list[str] = []
165
+ implements: list[str] = []
166
+ heritage = next(
167
+ (c for c in node.named_children if c.type == "class_heritage"), None
168
+ )
169
+ if heritage is not None:
170
+ for clause in heritage.named_children:
171
+ targets = [
172
+ c
173
+ for c in clause.named_children
174
+ if c.type.endswith("identifier")
175
+ or c.type in ("generic_type", "member_expression")
176
+ ]
177
+ if clause.type == "extends_clause":
178
+ bases.extend(self.text(t).strip() for t in targets)
179
+ elif clause.type == "implements_clause":
180
+ implements.extend(self.text(t).strip() for t in targets)
181
+ for t in targets:
182
+ self.refs.append(
183
+ ExtractedReference(
184
+ name=self.text(t).strip(),
185
+ context="extends" if clause.type == "extends_clause" else "implements",
186
+ span=rt.span_of(t),
187
+ scope_path=scope,
188
+ )
189
+ )
190
+ body = next(
191
+ (c for c in node.named_children if c.type == "class_body"), None
192
+ )
193
+ mods = _modifiers(node, self.src) + (("export",) if exported else ())
194
+ decs = self._take_decorators(node, scope, name)
195
+ qname = self._emit(
196
+ "class", name, name_node, node, scope,
197
+ signature=self._signature(node, body),
198
+ bases=tuple(bases), implements=tuple(implements),
199
+ modifiers=mods, decorators=decs,
200
+ )
201
+ for b in bases:
202
+ self.relations.append(
203
+ ExtractedRelation("EXTENDS", qname, b, rt.span_of(node))
204
+ )
205
+ for i in implements:
206
+ self.relations.append(
207
+ ExtractedRelation("IMPLEMENTS", qname, i, rt.span_of(node))
208
+ )
209
+ self._walk_children(body, (*scope, name), in_class=True)
210
+
211
+ def _handle_interface(self, node: Node, scope: tuple[str, ...],
212
+ exported: bool) -> None:
213
+ name_node = self._name_node(node)
214
+ if name_node is None:
215
+ return
216
+ name = self.text(name_node)
217
+ ext = next(
218
+ (c for c in node.named_children if c.type == "extends_type_clause"), None
219
+ )
220
+ bases = [
221
+ self.text(c).strip()
222
+ for c in (ext.named_children if ext is not None else [])
223
+ if c.type.endswith("identifier") or c.type == "generic_type"
224
+ ]
225
+ body = next(
226
+ (c for c in node.named_children
227
+ if c.type in ("interface_body", "object_type")),
228
+ None,
229
+ )
230
+ mods = _modifiers(node, self.src) + (("export",) if exported else ())
231
+ qname = self._emit(
232
+ "interface", name, name_node, node, scope,
233
+ signature=self._signature(node, body),
234
+ bases=tuple(bases), modifiers=mods,
235
+ decorators=self._take_decorators(node, scope, name),
236
+ )
237
+ for b in bases:
238
+ self.relations.append(
239
+ ExtractedRelation("EXTENDS", qname, b, rt.span_of(node))
240
+ )
241
+ self._walk_children(body, (*scope, name), in_class=True)
242
+
243
+ def _param_names(self, node: Node) -> tuple[str, ...]:
244
+ """Nomes de parâmetros TS (required/optional/rest/destructure)."""
245
+ params = node.child_by_field_name("parameters")
246
+ if params is None:
247
+ params = next(
248
+ (c for c in node.named_children
249
+ if c.type in ("formal_parameters", "parameters")),
250
+ None,
251
+ )
252
+ if params is None:
253
+ return ()
254
+ names: list[str] = []
255
+ for p in params.named_children:
256
+ pattern = p.child_by_field_name("pattern")
257
+ target = pattern if pattern is not None else p
258
+ names.append(self.text(target).strip())
259
+ return tuple(n for n in names if n)
260
+
261
+ def _handle_function(self, node: Node, scope: tuple[str, ...],
262
+ in_class: bool, exported: bool,
263
+ name_override: str | None = None,
264
+ name_node: Node | None = None,
265
+ fallback_name: str | None = None) -> None:
266
+ nn = name_node or self._name_node(node)
267
+ name = name_override or (
268
+ self.text(nn) if nn is not None else fallback_name
269
+ )
270
+ if name is None:
271
+ return # anônima sem binding — política: sem identidade
272
+ body = next(
273
+ (c for c in node.named_children
274
+ if c.type in ("statement_block", "class_body")),
275
+ None,
276
+ )
277
+ mods = list(_modifiers(node, self.src))
278
+ if exported:
279
+ mods.append("export")
280
+ if body is None:
281
+ mods.append("signature-only")
282
+ kind = "method" if in_class else "function"
283
+ self._emit(
284
+ kind, name, nn or node, node, scope,
285
+ signature=self._signature(node, body),
286
+ params=self._param_names(node),
287
+ modifiers=tuple(dict.fromkeys(mods)),
288
+ decorators=self._take_decorators(node, scope, name),
289
+ )
290
+ self._walk_children(body, (*scope, name), in_class=False)
291
+
292
+ def _handle_declarator(self, node: Node, scope: tuple[str, ...],
293
+ in_class: bool, exported: bool) -> None:
294
+ name_n = node.child_by_field_name("name")
295
+ value = node.child_by_field_name("value")
296
+ if name_n is None:
297
+ self._walk_children(node, scope, in_class)
298
+ return
299
+ if value is None:
300
+ # `declare const x: T` / `let x` — binding declarado sem
301
+ # inicializador (comum em .d.ts/ambient): símbolo real.
302
+ if name_n.type == "identifier":
303
+ mods = _modifiers(node, self.src) + (
304
+ ("export",) if exported else ())
305
+ self._emit(
306
+ "symbol", self.text(name_n), name_n, node, scope,
307
+ signature=self._signature(node, None)[:200],
308
+ modifiers=mods,
309
+ )
310
+ return
311
+ if value.type in ("arrow_function", "function_expression", "function"):
312
+ self._handle_function(
313
+ value, scope, in_class, exported,
314
+ name_override=self.text(name_n), name_node=name_n,
315
+ )
316
+ return
317
+ # `const x = require('m')` / `require('m').default` — binding CJS
318
+ # ao módulo (a aresta IMPORTS sai do _handle_call no walk abaixo).
319
+ req_target = self._require_declarator_target(value)
320
+ if req_target is not None:
321
+ self.refs.append(
322
+ ExtractedReference(
323
+ name=self.text(name_n), context="import-member",
324
+ span=rt.span_of(name_n), scope_path=scope,
325
+ target=req_target,
326
+ )
327
+ )
328
+ self._walk(value, scope, in_class) # valor pode ser jsx_element inteiro
329
+
330
+ def _require_declarator_target(self, value: Node) -> str | None:
331
+ """`require('m')` → ``m``; `require('m').prop` → ``m.prop``."""
332
+ call: Node | None = None
333
+ prop: Node | None = None
334
+ if value.type == "call_expression":
335
+ call = value
336
+ elif value.type == "member_expression":
337
+ obj = value.child_by_field_name("object")
338
+ if obj is not None and obj.type == "call_expression":
339
+ call = obj
340
+ prop = value.child_by_field_name("property")
341
+ if call is None:
342
+ return None
343
+ mod = self._require_module_arg(call)
344
+ if mod is None:
345
+ return None
346
+ if prop is not None:
347
+ return f"{mod}.{self.text(prop)}"
348
+ return mod
349
+
350
+ def _require_module_arg(self, call: Node) -> str | None:
351
+ """call_expression `require('<literal>')` → path ou None."""
352
+ fn = call.child_by_field_name("function")
353
+ if fn is None or fn.type != "identifier" or self.text(fn) != "require":
354
+ return None
355
+ return self._first_string(call.child_by_field_name("arguments"))
356
+
357
+ def _first_string(self, args: Node | None) -> str | None:
358
+ if args is None:
359
+ return None
360
+ s = next(
361
+ (c for c in args.named_children if c.type == "string"), None
362
+ )
363
+ return self.text(s).strip("\"'") if s is not None else None
364
+
365
+ def _handle_alias_enum(self, node: Node, scope: tuple[str, ...],
366
+ exported: bool) -> None:
367
+ name_node = self._name_node(node)
368
+ if name_node is None:
369
+ return
370
+ mods = _modifiers(node, self.src) + (("export",) if exported else ())
371
+ self._emit(
372
+ "symbol", self.text(name_node), name_node, node, scope,
373
+ signature=self._signature(node, None)[:200],
374
+ modifiers=mods,
375
+ decorators=self._take_decorators(
376
+ node, scope, self.text(name_node)),
377
+ )
378
+
379
+ # -- imports / exports / calls ------------------------------------
380
+
381
+ def _handle_import(self, node: Node, scope: tuple[str, ...]) -> None:
382
+ # `import x = require('m')`: o field `source` mora no
383
+ # `import_require_clause`, **não** no statement (lf-0130 — ler do
384
+ # statement produzia module_path vazio → IMPORTS sem alvo →
385
+ # snapshot FAILED inteiro).
386
+ req = next(
387
+ (c for c in node.named_children
388
+ if c.type == "import_require_clause"), None,
389
+ )
390
+ if req is not None:
391
+ src_n = req.child_by_field_name("source")
392
+ module_path = self._module_path(src_n)
393
+ binding = next(
394
+ (c for c in req.named_children if c.type == "identifier"),
395
+ None,
396
+ )
397
+ # `x` liga ao módulo inteiro (module.exports) — não a membro
398
+ self._emit_module_import(
399
+ node, module_path, scope,
400
+ bindings=[(self.text(binding), module_path)]
401
+ if binding is not None else [],
402
+ )
403
+ return
404
+ src_n = node.child_by_field_name("source")
405
+ module_path = self._module_path(src_n)
406
+ clause = next(
407
+ (c for c in node.named_children if c.type == "import_clause"), None
408
+ )
409
+ names = self._import_names(clause) if clause is not None else []
410
+ bindings: list[tuple[str, str]] = []
411
+ for imported, alias in names:
412
+ local = alias or imported
413
+ member = imported if imported != "*" else "*"
414
+ bindings.append((local, f"{module_path}.{member}"))
415
+ self._emit_module_import(node, module_path, scope, bindings=bindings)
416
+
417
+ def _handle_import_alias(self, node: Node, scope: tuple[str, ...]) -> None:
418
+ """`import foo = Bar.Baz` (import_alias) — alias de entidade, não
419
+ de módulo: binding registrado, sem aresta IMPORTS (não há source)."""
420
+ names = node.named_children
421
+ if len(names) < 2:
422
+ return
423
+ binding, target = names[0], names[-1]
424
+ self.refs.append(
425
+ ExtractedReference(
426
+ name=self.text(target), context="import-alias",
427
+ span=rt.span_of(node), scope_path=scope,
428
+ )
429
+ )
430
+ self.refs.append(
431
+ ExtractedReference(
432
+ name=self.text(binding), context="import-member",
433
+ span=rt.span_of(node), scope_path=scope,
434
+ target=self.text(target),
435
+ )
436
+ )
437
+
438
+ def _module_path(self, src_n: Node | None) -> str:
439
+ return self.text(src_n).strip("\"'") if src_n is not None else ""
440
+
441
+ def _emit_module_import(
442
+ self,
443
+ node: Node,
444
+ module_path: str,
445
+ scope: tuple[str, ...],
446
+ bindings: list[tuple[str, str]],
447
+ ) -> None:
448
+ """IMPORTS + ref de módulo + bindings de membros.
449
+
450
+ lf-0130: caminho de módulo não-resolvível NUNCA emite aresta
451
+ IMPORTS vazia — diagnostica e sai; uma aresta sem ``to_text``
452
+ derruba o snapshot inteiro no validate_graph.
453
+ """
454
+ if not module_path:
455
+ self._diag(
456
+ "import-source-missing", "warning",
457
+ "import without resolvable module path — IMPORTS edge "
458
+ "dropped instead of emitted empty",
459
+ node,
460
+ )
461
+ return
462
+ self.refs.append(
463
+ ExtractedReference(
464
+ name=module_path, context="import",
465
+ span=rt.span_of(node), scope_path=scope,
466
+ )
467
+ )
468
+ self.relations.append(
469
+ ExtractedRelation("IMPORTS", self.module, module_path, rt.span_of(node))
470
+ )
471
+ for binding, target in bindings:
472
+ self.refs.append(
473
+ ExtractedReference(
474
+ name=binding, context="import-member",
475
+ span=rt.span_of(node), scope_path=scope, target=target,
476
+ )
477
+ )
478
+
479
+ def _import_names(self, node: Node) -> list[tuple[str, str | None]]:
480
+ out: list[tuple[str, str | None]] = []
481
+ if node.type == "import_specifier":
482
+ name = node.child_by_field_name("name")
483
+ alias = node.child_by_field_name("alias")
484
+ out.append((self.text(name) if name else self.text(node),
485
+ self.text(alias) if alias else None))
486
+ elif node.type == "namespace_import":
487
+ alias = next((c for c in node.named_children
488
+ if c.type == "identifier"), None)
489
+ if alias is not None:
490
+ out.append(("*", self.text(alias)))
491
+ elif node.type == "identifier":
492
+ out.append((self.text(node), None))
493
+ else:
494
+ for c in node.named_children:
495
+ out.extend(self._import_names(c))
496
+ return out
497
+
498
+ def _handle_export(self, node: Node, scope: tuple[str, ...],
499
+ in_class: bool) -> None:
500
+ children = node.named_children
501
+ inner = next(
502
+ (c for c in children
503
+ if c.type in self._DECLS
504
+ or c.type in ("internal_module", "module",
505
+ "ambient_declaration")),
506
+ None,
507
+ )
508
+ if inner is not None:
509
+ if inner.type in ("internal_module", "module"):
510
+ self._handle_namespace(inner, scope, exported=True)
511
+ return
512
+ default = any(c.type == "default" for c in node.children)
513
+ self._walk_decl(
514
+ inner, scope, in_class, exported=True,
515
+ fallback_name="default" if default else None,
516
+ )
517
+ return
518
+ # `export default <expr>` — function/class anônimas viram
519
+ # `default`; demais expressões viram referência `export`.
520
+ value = node.child_by_field_name("value")
521
+ if value is not None:
522
+ if value.type in ("arrow_function", "function_expression",
523
+ "function"):
524
+ self._handle_function(
525
+ value, scope, in_class, True, fallback_name="default",
526
+ )
527
+ elif value.type in ("class", "class_declaration",
528
+ "abstract_class_declaration"):
529
+ self._handle_class(
530
+ value, scope, True, fallback_name="default",
531
+ )
532
+ elif self.text(value):
533
+ self.refs.append(
534
+ ExtractedReference(
535
+ name=self.text(value), context="export",
536
+ span=rt.span_of(value), scope_path=scope,
537
+ )
538
+ )
539
+ return
540
+ src_n = node.child_by_field_name("source")
541
+ module_path = self._module_path(src_n)
542
+ if module_path:
543
+ # export {x} from 'm' / export * from 'm' / export * as ns
544
+ # — dependência real de módulo (re-export): IMPORTS + binding.
545
+ self._emit_module_import(
546
+ node, module_path, scope,
547
+ bindings=self._export_bindings(node, module_path),
548
+ )
549
+ return
550
+ # `export = expr` (CJS/UMD) — a expressão é o module.exports
551
+ # inteiro: referência `export-assignment` (sem aresta própria).
552
+ if any(c.type == "=" for c in node.children) and children:
553
+ self.refs.append(
554
+ ExtractedReference(
555
+ name=self.text(children[-1]),
556
+ context="export-assignment",
557
+ span=rt.span_of(children[-1]), scope_path=scope,
558
+ )
559
+ )
560
+ return
561
+ # `export {a, b}` local — referência ao símbolo exportado.
562
+ for name in self._export_specifier_names(node):
563
+ self.refs.append(
564
+ ExtractedReference(
565
+ name=name, context="export",
566
+ span=rt.span_of(node), scope_path=scope,
567
+ )
568
+ )
569
+
570
+ def _export_bindings(
571
+ self, node: Node, module_path: str
572
+ ) -> list[tuple[str, str]]:
573
+ """Bindings de re-export: `{a as b} from 'm'` → ``b → m.a``;
574
+ `* as ns from 'm'` → ``ns → m.*``; `* from 'm'` → ``* → m.*``."""
575
+ out: list[tuple[str, str]] = []
576
+ # `*` de `export * from` é filho anônimo — iterar children.
577
+ for c in node.children:
578
+ if c.type == "export_clause":
579
+ for spec in c.named_children:
580
+ if spec.type != "export_specifier":
581
+ continue
582
+ n = spec.child_by_field_name("name")
583
+ a = spec.child_by_field_name("alias")
584
+ if n is None:
585
+ continue
586
+ out.append(
587
+ (self.text(a) if a is not None else self.text(n),
588
+ f"{module_path}.{self.text(n)}")
589
+ )
590
+ elif c.type == "namespace_export":
591
+ ident = next(
592
+ (x for x in c.named_children if x.type == "identifier"),
593
+ None,
594
+ )
595
+ if ident is not None:
596
+ out.append((self.text(ident), f"{module_path}.*"))
597
+ elif c.type == "*":
598
+ out.append(("*", f"{module_path}.*"))
599
+ return out
600
+
601
+ def _export_specifier_names(self, node: Node) -> list[str]:
602
+ out: list[str] = []
603
+ for c in node.named_children:
604
+ if c.type != "export_clause":
605
+ continue
606
+ for spec in c.named_children:
607
+ if spec.type != "export_specifier":
608
+ continue
609
+ n = spec.child_by_field_name("name")
610
+ if n is not None:
611
+ out.append(self.text(n))
612
+ return out
613
+
614
+ def _handle_namespace(self, node: Node, scope: tuple[str, ...],
615
+ exported: bool) -> None:
616
+ """`namespace X {}` / `declare module 'x' {}` (internal_module /
617
+ module) — escopo nomeado real: filhos ganham qname `X.*`."""
618
+ name_n = node.child_by_field_name("name")
619
+ if name_n is None:
620
+ name_n = next(
621
+ (c for c in node.named_children
622
+ if c.type in ("identifier", "nested_identifier", "string")),
623
+ None,
624
+ )
625
+ if name_n is None:
626
+ self._walk_children(node, scope, in_class=False)
627
+ return
628
+ name = self.text(name_n).strip("\"'")
629
+ body = next(
630
+ (c for c in node.named_children
631
+ if c.type == "statement_block"),
632
+ None,
633
+ )
634
+ mods = _modifiers(node, self.src) + (("export",) if exported else ())
635
+ self._emit(
636
+ "symbol", name, name_n, node, scope,
637
+ signature=self._signature(node, body)[:200],
638
+ modifiers=tuple(dict.fromkeys((*mods, "namespace"))),
639
+ )
640
+ self._walk_children(body, (*scope, name), in_class=False)
641
+
642
+ def _handle_call(self, node: Node, scope: tuple[str, ...]) -> None:
643
+ callee = node.child_by_field_name("function")
644
+ if callee is None:
645
+ callee = node.child_by_field_name("constructor")
646
+ if callee is None:
647
+ return
648
+ # `import('m')` / `require('m')` — dependência de módulo, não call.
649
+ if callee.type == "import":
650
+ lit = self._first_string(
651
+ node.child_by_field_name("arguments"))
652
+ if lit:
653
+ self._emit_module_import(node, lit, scope, bindings=[])
654
+ else:
655
+ self._diag(
656
+ "dynamic-import-nonliteral", "info",
657
+ "import() with non-literal argument — no IMPORTS edge",
658
+ node,
659
+ )
660
+ return
661
+ if callee.type == "identifier" and self.text(callee) == "require":
662
+ lit = self._first_string(
663
+ node.child_by_field_name("arguments"))
664
+ if lit:
665
+ self._emit_module_import(node, lit, scope, bindings=[])
666
+ return
667
+ # require(expr) não-literal: cai para CALLS (honesto — não
668
+ # comprovável como dependência estática).
669
+ caller = ".".join(scope)
670
+ caller = f"{self.module}.{caller}" if self.module and caller else (caller or self.module)
671
+ self.refs.append(
672
+ ExtractedReference(
673
+ name=self.text(callee), context="call",
674
+ span=rt.span_of(callee), scope_path=scope,
675
+ )
676
+ )
677
+ self.relations.append(
678
+ ExtractedRelation("CALLS", caller, self.text(callee), rt.span_of(callee))
679
+ )
680
+
681
+ def _handle_jsx(self, node: Node, scope: tuple[str, ...]) -> None:
682
+ name_n = node.child_by_field_name("name") or next(
683
+ (c for c in node.named_children
684
+ if c.type in ("identifier", "member_expression", "nested_identifier")),
685
+ None,
686
+ )
687
+ if name_n is not None:
688
+ self.refs.append(
689
+ ExtractedReference(
690
+ name=self.text(name_n), context="jsx",
691
+ span=rt.span_of(name_n), scope_path=scope,
692
+ )
693
+ )
694
+
695
+ # -- walk ----------------------------------------------------------
696
+
697
+ _DECLS = (
698
+ "class_declaration", "abstract_class_declaration",
699
+ "interface_declaration", "function_declaration",
700
+ "function_signature", "method_definition", "method_signature",
701
+ "type_alias_declaration", "enum_declaration",
702
+ "lexical_declaration", "variable_declaration",
703
+ )
704
+
705
+ def _walk_decl(self, node: Node, scope: tuple[str, ...],
706
+ in_class: bool, exported: bool = False,
707
+ fallback_name: str | None = None) -> None:
708
+ t = node.type
709
+ if t in ("class_declaration", "abstract_class_declaration"):
710
+ self._handle_class(node, scope, exported,
711
+ fallback_name=fallback_name)
712
+ elif t == "interface_declaration":
713
+ self._handle_interface(node, scope, exported)
714
+ elif t in ("function_declaration", "function_signature"):
715
+ self._handle_function(node, scope, in_class, exported,
716
+ fallback_name=fallback_name)
717
+ elif t in ("method_definition", "method_signature"):
718
+ self._handle_function(node, scope, True, exported,
719
+ fallback_name=fallback_name)
720
+ elif t in ("type_alias_declaration", "enum_declaration"):
721
+ self._handle_alias_enum(node, scope, exported)
722
+ elif t == "ambient_declaration":
723
+ self._walk_ambient(node, scope, in_class, exported,
724
+ fallback_name)
725
+ elif t in ("lexical_declaration", "variable_declaration"):
726
+ self._walk_vardecl(node, scope, in_class, exported)
727
+
728
+ def _walk_ambient(self, node: Node, scope: tuple[str, ...],
729
+ in_class: bool, exported: bool,
730
+ fallback_name: str | None) -> None:
731
+ """`declare X` / `export declare X` — unwrap para a decl real
732
+ preservando o flag export (declare global → statement_block)."""
733
+ for c in node.named_children:
734
+ if c.type in ("internal_module", "module"):
735
+ self._handle_namespace(c, scope, exported)
736
+ elif c.type in self._DECLS:
737
+ self._walk_decl(c, scope, in_class, exported,
738
+ fallback_name)
739
+ else:
740
+ self._walk(c, scope, in_class)
741
+
742
+ def _walk_vardecl(self, node: Node, scope: tuple[str, ...],
743
+ in_class: bool, exported: bool) -> None:
744
+ for c in node.named_children:
745
+ if c.type == "variable_declarator":
746
+ self._handle_declarator(c, scope, in_class, exported)
747
+ else:
748
+ self._walk(c, scope, in_class)
749
+
750
+ def _emit_decorator_ref(self, node: Node,
751
+ scope: tuple[str, ...]) -> None:
752
+ ref = self._decorator_text(node).split("(")[0].strip()
753
+ if ref:
754
+ self.refs.append(
755
+ ExtractedReference(
756
+ name=ref, context="decorator",
757
+ span=rt.span_of(node), scope_path=scope,
758
+ )
759
+ )
760
+
761
+ def _walk(self, node: Node, scope: tuple[str, ...], in_class: bool) -> None:
762
+ t = node.type
763
+ if t in self._DECLS:
764
+ self._walk_decl(node, scope, in_class)
765
+ elif t == "export_statement":
766
+ self._handle_export(node, scope, in_class)
767
+ elif t == "import_statement":
768
+ self._handle_import(node, scope)
769
+ elif t == "import_alias":
770
+ self._handle_import_alias(node, scope)
771
+ elif t in ("internal_module", "module"):
772
+ self._handle_namespace(node, scope, exported=False)
773
+ elif t == "decorator":
774
+ # decorator anexado dentro de um nó não-declaração (ex.:
775
+ # public_field_definition) — vira ref no escopo atual; os de
776
+ # class_body são capturados por _walk_children antes da decl.
777
+ self._emit_decorator_ref(node, scope)
778
+ else:
779
+ self._walk_leaf(node, scope, in_class)
780
+
781
+ def _walk_leaf(self, node: Node, scope: tuple[str, ...],
782
+ in_class: bool) -> None:
783
+ """Nós não-declaração: calls, JSX e descida genérica."""
784
+ t = node.type
785
+ if t in ("call_expression", "new_expression"):
786
+ self._handle_call(node, scope)
787
+ elif t in ("jsx_opening_element", "jsx_self_closing_element"):
788
+ self._handle_jsx(node, scope)
789
+ for child in node.named_children:
790
+ self._walk(child, scope, in_class)
791
+
792
+ def _walk_children(self, node: Node | None, scope: tuple[str, ...],
793
+ in_class: bool) -> None:
794
+ if node is None:
795
+ return
796
+ for child in node.named_children:
797
+ if child.type == "decorator":
798
+ # decorator de método vem como filho de class_body —
799
+ # fica pendente até a decl seguinte consumi-lo.
800
+ self._pending_decs.append(child)
801
+ continue
802
+ self._walk(child, scope, in_class)
803
+ if self._pending_decs:
804
+ # decorator solto antes de não-declaração (campo etc.):
805
+ # emite a ref no escopo e limpa — nunca vaza para a
806
+ # declaração seguinte errada.
807
+ for d in self._pending_decs:
808
+ ref = self._decorator_text(d).split("(")[0].strip()
809
+ if ref:
810
+ self.refs.append(
811
+ ExtractedReference(
812
+ name=ref, context="decorator",
813
+ span=rt.span_of(d), scope_path=scope,
814
+ )
815
+ )
816
+ self._pending_decs = []
817
+
818
+ def extract(self, root: Node) -> None:
819
+ self.symbols.append(
820
+ ExtractedSymbol(
821
+ language=self.language,
822
+ kind="module",
823
+ name=self.module,
824
+ qualified_name=self.module,
825
+ span=rt.span_of(root),
826
+ )
827
+ )
828
+ self._walk_children(root, (), in_class=False)
829
+ self.symbols.sort(key=lambda s: (s.span.start_byte, s.qualified_name))
830
+
831
+
832
+ class TypeScriptAdapter:
833
+ """Adaptador TypeScript (.ts/.d.ts) — tree-sitter-typescript."""
834
+
835
+ language: str = "typescript"
836
+ extensions: tuple[str, ...] = (".ts", ".d.ts", ".mts", ".cts")
837
+ capabilities: ParserCapability = ParserCapability(
838
+ decorators=True, docstrings=False, interfaces=True
839
+ )
840
+ _spec: rt.LanguageSpec | None = None
841
+
842
+ @property
843
+ def adapter_id(self) -> str:
844
+ return "tree-sitter-typescript"
845
+
846
+ @property
847
+ def parser_version(self) -> str:
848
+ return rt.parser_version()
849
+
850
+ @property
851
+ def grammar_version(self) -> str:
852
+ return self._load().grammar_version
853
+
854
+ def _factory(self) -> Any:
855
+ return tree_sitter_typescript.language_typescript
856
+
857
+ def _load(self) -> rt.LanguageSpec:
858
+ if self._spec is None:
859
+ self._spec = rt.load_language(self._factory(), "tree-sitter-typescript")
860
+ return self._spec
861
+
862
+ def parse(self, request: ParseRequest) -> ParseResult:
863
+ spec = self._load()
864
+ tree = rt.parse_bytes(spec, request.content, request.timeout_us)
865
+ if tree is None:
866
+ return ParseResult(
867
+ path=request.path,
868
+ language=self.language,
869
+ parser=self.adapter_id,
870
+ parser_version=self.parser_version,
871
+ grammar_version=spec.grammar_version,
872
+ content_hash=hashlib.sha256(request.content).hexdigest(),
873
+ has_error=True,
874
+ diagnostics=(
875
+ ParseDiagnostic(
876
+ code="parse-timeout",
877
+ severity="error",
878
+ message=f"parse exceeded {request.timeout_us}us",
879
+ ),
880
+ ),
881
+ )
882
+ problems = list(rt.iter_problem_nodes(tree.root_node))
883
+ diagnostics = [
884
+ ParseDiagnostic(
885
+ code="missing-node" if m else "error-node",
886
+ severity="warning" if m else "error",
887
+ message=f"{n.type} at line {n.start_point.row + 1}",
888
+ span=rt.span_of(n),
889
+ )
890
+ for n, m in problems
891
+ ]
892
+ ex = _TsExtractor(request.content, module_name(request.path), self.language)
893
+ ex.extract(tree.root_node)
894
+ return ParseResult(
895
+ path=request.path,
896
+ language=self.language,
897
+ parser=self.adapter_id,
898
+ parser_version=self.parser_version,
899
+ grammar_version=spec.grammar_version,
900
+ content_hash=hashlib.sha256(request.content).hexdigest(),
901
+ has_error=tree.root_node.has_error,
902
+ error_node_count=sum(1 for _, m in problems if not m),
903
+ missing_node_count=sum(1 for _, m in problems if m),
904
+ node_count=rt.node_count(tree.root_node),
905
+ diagnostics=tuple(diagnostics) + tuple(ex.diagnostics),
906
+ symbols=tuple(ex.symbols),
907
+ references=tuple(ex.refs),
908
+ relations=tuple(ex.relations),
909
+ )