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,391 @@
1
+ """Adaptador Python via tree-sitter-python — extração estrutural real.
2
+
3
+ Node types cobertos: module, class_definition, function_definition
4
+ (async incluído), decorated_definition, import_statement,
5
+ import_from_statement, call. Escopo léxico via pilha de nomes — funções
6
+ aninhadas e métodos recebem qualified names distintos.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+
13
+ import tree_sitter_python
14
+ from tree_sitter import Node
15
+
16
+ from code_atlas.parsers import tree_sitter_runtime as rt
17
+ from code_atlas.parsers.contracts import (
18
+ ExtractedReference,
19
+ ExtractedRelation,
20
+ ExtractedSymbol,
21
+ ParseDiagnostic,
22
+ ParserCapability,
23
+ ParseRequest,
24
+ ParseResult,
25
+ )
26
+
27
+ _DEFS = ("function_definition", "class_definition")
28
+
29
+
30
+ def module_name(path: str) -> str:
31
+ """`pkg/mod.py` → ``pkg.mod``; `pkg/__init__.py` → ``pkg``."""
32
+ parts = path[:-3].split("/") # remove .py
33
+ if parts and parts[-1] == "__init__":
34
+ parts = parts[:-1]
35
+ return ".".join(p for p in parts if p) or path
36
+
37
+
38
+ class _PyExtractor:
39
+ """Visitor sobre a árvore; produz símbolos, referências e relações."""
40
+
41
+ def __init__(self, src: bytes, module: str) -> None:
42
+ self.src = src
43
+ self.module = module
44
+ self.symbols: list[ExtractedSymbol] = []
45
+ self.refs: list[ExtractedReference] = []
46
+ self.relations: list[ExtractedRelation] = []
47
+
48
+ def text(self, node: Node) -> str:
49
+ return self.src[node.start_byte : node.end_byte].decode("utf-8", "replace")
50
+
51
+ # -- símbolos -----------------------------------------------------
52
+
53
+ def _emit(self, kind: str, name: str, name_node: Node, node: Node,
54
+ scope: tuple[str, ...], **kw: object) -> str:
55
+ qname = ".".join((*scope, name)) if scope else name
56
+ qname = f"{self.module}.{qname}" if self.module else qname
57
+ self.symbols.append(
58
+ ExtractedSymbol(
59
+ language="python",
60
+ kind=kind,
61
+ name=name,
62
+ qualified_name=qname,
63
+ span=rt.span_of(node),
64
+ name_span=rt.span_of(name_node),
65
+ scope_path=scope,
66
+ **kw, # type: ignore[arg-type]
67
+ )
68
+ )
69
+ return qname
70
+
71
+ def _signature(self, node: Node, body: Node | None) -> str:
72
+ end = body.start_byte if body is not None else node.end_byte
73
+ return self.src[node.start_byte : end].decode("utf-8", "replace").rstrip(" :\n")
74
+
75
+ def _docstring(self, body: Node | None) -> str | None:
76
+ if body is None or body.named_child_count == 0:
77
+ return None
78
+ first = body.named_children[0]
79
+ if first.type != "expression_statement" or first.named_child_count == 0:
80
+ return None
81
+ s = first.named_children[0]
82
+ if s.type not in ("string", "concatenated_string"):
83
+ return None
84
+ return self.text(s).strip("\"' \n")[:500]
85
+
86
+ def _handle_class(self, node: Node, scope: tuple[str, ...],
87
+ decorators: tuple[str, ...]) -> None:
88
+ name_node = node.child_by_field_name("name")
89
+ if name_node is None:
90
+ return
91
+ name = self.text(name_node)
92
+ bases: list[str] = []
93
+ sup = node.child_by_field_name("superclasses")
94
+ if sup is not None:
95
+ for c in sup.named_children:
96
+ bases.append(self.text(c).strip())
97
+ self.refs.append(
98
+ ExtractedReference(
99
+ name=self.text(c).strip(),
100
+ context="extends",
101
+ span=rt.span_of(c),
102
+ scope_path=scope,
103
+ )
104
+ )
105
+ body = node.child_by_field_name("body")
106
+ qname = self._emit(
107
+ "class", name, name_node, node, scope,
108
+ signature=self._signature(node, body),
109
+ docstring=self._docstring(body),
110
+ decorators=decorators,
111
+ bases=tuple(bases),
112
+ )
113
+ for base in bases:
114
+ self.relations.append(
115
+ ExtractedRelation(
116
+ type="EXTENDS",
117
+ from_name=qname,
118
+ to_name=base.split("(")[0].strip(),
119
+ span=rt.span_of(node),
120
+ )
121
+ )
122
+ self._walk_children(body, (*scope, name), in_class=True)
123
+
124
+ def _param_names(self, parameters: Node | None) -> tuple[str, ...]:
125
+ """Nomes de parâmetros (typed/default/splat inclusos)."""
126
+ if parameters is None:
127
+ return ()
128
+ names: list[str] = []
129
+ for p in parameters.named_children:
130
+ name_node = p.child_by_field_name("name")
131
+ if name_node is not None:
132
+ names.append(self.text(name_node))
133
+ elif p.type == "identifier":
134
+ names.append(self.text(p))
135
+ elif p.type in ("list_splat_pattern", "dictionary_splat_pattern"):
136
+ inner = next(
137
+ (c for c in p.named_children if c.type == "identifier"),
138
+ None,
139
+ )
140
+ if inner is not None:
141
+ names.append(self.text(inner))
142
+ return tuple(names)
143
+
144
+ def _handle_function(self, node: Node, scope: tuple[str, ...],
145
+ decorators: tuple[str, ...], in_class: bool) -> None:
146
+ name_node = node.child_by_field_name("name")
147
+ if name_node is None:
148
+ return
149
+ name = self.text(name_node)
150
+ mods = list(decorators_to_mods(decorators))
151
+ if any(c.type == "async" for c in node.children):
152
+ mods.append("async")
153
+ body = node.child_by_field_name("body")
154
+ params = node.child_by_field_name("parameters")
155
+ kind = "method" if in_class else "function"
156
+ self._emit(
157
+ kind, name, name_node, node, scope,
158
+ signature=self._signature(node, body),
159
+ params=self._param_names(params),
160
+ docstring=self._docstring(body),
161
+ decorators=decorators,
162
+ modifiers=tuple(mods),
163
+ )
164
+ self._walk_children(body, (*scope, name), in_class=False)
165
+
166
+ # -- imports / calls ----------------------------------------------
167
+
168
+ def _handle_import(self, node: Node, scope: tuple[str, ...]) -> None:
169
+ if node.type == "import_statement":
170
+ for c in node.named_children:
171
+ target, alias = self._import_pair(c)
172
+ # `import a.b` liga o nome 'a'; `import a.b as c` liga 'c'
173
+ binding = alias or target.split(".")[0]
174
+ self._emit_import(self.module, target, binding, target, c, scope)
175
+ else: # import_from_statement
176
+ mod = node.child_by_field_name("module_name")
177
+ base = self.text(mod) if mod is not None else ""
178
+ for c in node.named_children:
179
+ # `is not` NÃO funciona entre wrappers Node — comparar .id
180
+ if c.type in ("dotted_name", "aliased_import") and (
181
+ mod is None or c.id != mod.id
182
+ ):
183
+ target, alias = self._import_pair(c)
184
+ # base relativo termina em "." — sem separador extra
185
+ # (`from . import u` → `.u`, não `..u`)
186
+ full = (
187
+ f"{base}{target}" if base.endswith(".")
188
+ else f"{base}.{target}" if base else target
189
+ )
190
+ binding = alias or target.split(".")[-1]
191
+ self._emit_import(self.module, full, binding, full, c, scope)
192
+ elif c.type == "wildcard_import":
193
+ # `from base import *` — aresta de dependência real
194
+ # no módulo, sem binding de membro (lf-0024).
195
+ self._emit_import(
196
+ self.module, base or "*", "*", base or "*", c, scope
197
+ )
198
+
199
+ def _import_pair(self, node: Node) -> tuple[str, str | None]:
200
+ if node.type == "aliased_import":
201
+ name = node.child_by_field_name("name")
202
+ alias = node.child_by_field_name("alias")
203
+ return (
204
+ self.text(name) if name else self.text(node),
205
+ self.text(alias) if alias else None,
206
+ )
207
+ return self.text(node), None
208
+
209
+ def _emit_import(self, src_qname: str, target: str, binding: str,
210
+ member_target: str, node: Node, scope: tuple[str, ...]) -> None:
211
+ self.refs.append(
212
+ ExtractedReference(
213
+ name=target, context="import",
214
+ span=rt.span_of(node), scope_path=scope,
215
+ )
216
+ )
217
+ # binding local → alvo completo: alimenta a resolução do grafo
218
+ self.refs.append(
219
+ ExtractedReference(
220
+ name=binding, context="import-member",
221
+ span=rt.span_of(node), scope_path=scope, target=member_target,
222
+ )
223
+ )
224
+ self.relations.append(
225
+ ExtractedRelation(
226
+ type="IMPORTS", from_name=src_qname, to_name=target,
227
+ span=rt.span_of(node),
228
+ )
229
+ )
230
+
231
+ def _handle_call(self, node: Node, scope: tuple[str, ...]) -> None:
232
+ fn = node.child_by_field_name("function")
233
+ if fn is None:
234
+ return
235
+ self.refs.append(
236
+ ExtractedReference(
237
+ name=self.text(fn), context="call",
238
+ span=rt.span_of(fn), scope_path=scope,
239
+ )
240
+ )
241
+ caller = ".".join(scope)
242
+ caller = f"{self.module}.{caller}" if self.module and caller else caller or self.module
243
+ self.relations.append(
244
+ ExtractedRelation(
245
+ type="CALLS", from_name=caller, to_name=self.text(fn),
246
+ span=rt.span_of(fn),
247
+ )
248
+ )
249
+
250
+ # -- walk ----------------------------------------------------------
251
+
252
+ def _walk_children(self, node: Node | None, scope: tuple[str, ...],
253
+ in_class: bool) -> None:
254
+ if node is None:
255
+ return
256
+ for child in node.named_children:
257
+ self._walk(child, scope, in_class)
258
+
259
+ def _walk(self, node: Node, scope: tuple[str, ...], in_class: bool) -> None:
260
+ t = node.type
261
+ if t == "decorated_definition":
262
+ decs = tuple(
263
+ self.text(c).lstrip("@").strip()
264
+ for c in node.children
265
+ if c.type == "decorator"
266
+ )
267
+ inner = next((c for c in node.children if c.type in _DEFS), None)
268
+ if inner is not None:
269
+ if inner.type == "class_definition":
270
+ self._handle_class(inner, scope, decs)
271
+ else:
272
+ self._handle_function(inner, scope, decs, in_class)
273
+ return
274
+ if t == "class_definition":
275
+ self._handle_class(node, scope, ())
276
+ return
277
+ if t == "function_definition":
278
+ self._handle_function(node, scope, (), in_class)
279
+ return
280
+ if t in ("import_statement", "import_from_statement"):
281
+ self._handle_import(node, scope)
282
+ return
283
+ if t == "call":
284
+ self._handle_call(node, scope)
285
+ # continua descendo — chamadas aninhadas nos argumentos
286
+ for child in node.named_children:
287
+ self._walk(child, scope, in_class)
288
+
289
+ def extract(self, root: Node) -> None:
290
+ module_span = rt.span_of(root)
291
+ self.symbols.append(
292
+ ExtractedSymbol(
293
+ language="python",
294
+ kind="module",
295
+ name=self.module,
296
+ qualified_name=self.module,
297
+ span=module_span,
298
+ docstring=self._docstring(
299
+ root if root.type == "module" else None
300
+ ) or self._module_doc(root),
301
+ )
302
+ )
303
+ self._walk_children(root, (), in_class=False)
304
+ self.symbols.sort(key=lambda s: (s.span.start_byte, s.qualified_name))
305
+
306
+ def _module_doc(self, root: Node) -> str | None:
307
+ return self._docstring(root)
308
+
309
+
310
+ def decorators_to_mods(decorators: tuple[str, ...]) -> tuple[str, ...]:
311
+ """Decorators conhecidos viram modifiers (staticmethod etc.)."""
312
+ known = {"staticmethod", "classmethod", "abstractmethod", "property", "overload"}
313
+ return tuple(d for d in decorators if d.split("(")[0].split(".")[-1] in known)
314
+
315
+
316
+ class PythonAdapter:
317
+ """Adaptador Python — tree-sitter-python pinned."""
318
+
319
+ language: str = "python"
320
+ extensions: tuple[str, ...] = (".py", ".pyi")
321
+ capabilities: ParserCapability = ParserCapability(decorators=True)
322
+ _spec: rt.LanguageSpec | None = None
323
+
324
+ @property
325
+ def adapter_id(self) -> str:
326
+ return "tree-sitter-python"
327
+
328
+ @property
329
+ def parser_version(self) -> str:
330
+ return rt.parser_version()
331
+
332
+ @property
333
+ def grammar_version(self) -> str:
334
+ return self._load().grammar_version
335
+
336
+ def _load(self) -> rt.LanguageSpec:
337
+ if self._spec is None:
338
+ self._spec = rt.load_language(
339
+ tree_sitter_python.language, "tree-sitter-python"
340
+ )
341
+ return self._spec
342
+
343
+ def parse(self, request: ParseRequest) -> ParseResult:
344
+ spec = self._load()
345
+ tree = rt.parse_bytes(spec, request.content, request.timeout_us)
346
+ if tree is None:
347
+ return ParseResult(
348
+ path=request.path,
349
+ language="python",
350
+ parser=self.adapter_id,
351
+ parser_version=self.parser_version,
352
+ grammar_version=spec.grammar_version,
353
+ content_hash=hashlib.sha256(request.content).hexdigest(),
354
+ has_error=True,
355
+ diagnostics=(
356
+ ParseDiagnostic(
357
+ code="parse-timeout",
358
+ severity="error",
359
+ message=f"parse exceeded {request.timeout_us}us",
360
+ ),
361
+ ),
362
+ )
363
+ diagnostics = [
364
+ ParseDiagnostic(
365
+ code="missing-node" if is_missing else "error-node",
366
+ severity="warning" if is_missing else "error",
367
+ message=f"{n.type} at line {n.start_point.row + 1}",
368
+ span=rt.span_of(n),
369
+ )
370
+ for n, is_missing in rt.iter_problem_nodes(tree.root_node)
371
+ ]
372
+ ex = _PyExtractor(request.content, module_name(request.path))
373
+ ex.extract(tree.root_node)
374
+ err = sum(1 for n, m in rt.iter_problem_nodes(tree.root_node) if not m)
375
+ miss = sum(1 for _, m in rt.iter_problem_nodes(tree.root_node) if m)
376
+ return ParseResult(
377
+ path=request.path,
378
+ language="python",
379
+ parser=self.adapter_id,
380
+ parser_version=self.parser_version,
381
+ grammar_version=spec.grammar_version,
382
+ content_hash=hashlib.sha256(request.content).hexdigest(),
383
+ has_error=tree.root_node.has_error,
384
+ error_node_count=err,
385
+ missing_node_count=miss,
386
+ node_count=rt.node_count(tree.root_node),
387
+ diagnostics=tuple(diagnostics),
388
+ symbols=tuple(ex.symbols),
389
+ references=tuple(ex.refs),
390
+ relations=tuple(ex.relations),
391
+ )
@@ -0,0 +1,28 @@
1
+ """Adaptador TSX — mesma extração do TypeScript, gramática JSX-enabled.
2
+
3
+ ``tree_sitter_typescript.language_tsx()`` habilita constructs JSX; o
4
+ extractor compartilhado já registra referências ``jsx``. Linguagem
5
+ reportada como ``tsx`` para distinguir capacidade e gramática.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import tree_sitter_typescript
13
+
14
+ from code_atlas.parsers.languages.typescript import TypeScriptAdapter
15
+
16
+
17
+ class TsxAdapter(TypeScriptAdapter):
18
+ """TSX (.tsx) — gramática tsx do pacote tree-sitter-typescript."""
19
+
20
+ language: str = "tsx"
21
+ extensions: tuple[str, ...] = (".tsx",)
22
+
23
+ @property
24
+ def adapter_id(self) -> str:
25
+ return "tree-sitter-tsx"
26
+
27
+ def _factory(self) -> Any:
28
+ return tree_sitter_typescript.language_tsx