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.
- code_atlas/__init__.py +6 -0
- code_atlas/__main__.py +6 -0
- code_atlas/api/__init__.py +5 -0
- code_atlas/api/handlers.py +1514 -0
- code_atlas/api/server.py +394 -0
- code_atlas/cli.py +2084 -0
- code_atlas/config.py +149 -0
- code_atlas/context/__init__.py +66 -0
- code_atlas/context/contracts.py +277 -0
- code_atlas/context/domain.py +760 -0
- code_atlas/context/engine.py +442 -0
- code_atlas/context/expand.py +133 -0
- code_atlas/context/renderers.py +71 -0
- code_atlas/context/tokenizer.py +37 -0
- code_atlas/contracts/__init__.py +38 -0
- code_atlas/contracts/enums.py +67 -0
- code_atlas/contracts/models.py +175 -0
- code_atlas/contracts/ports.py +70 -0
- code_atlas/discovery/__init__.py +25 -0
- code_atlas/discovery/gitignore.py +119 -0
- code_atlas/discovery/identity.py +52 -0
- code_atlas/discovery/models.py +204 -0
- code_atlas/discovery/scanner.py +495 -0
- code_atlas/domains/__init__.py +107 -0
- code_atlas/domains/api/__init__.py +28 -0
- code_atlas/domains/api/adapter.py +405 -0
- code_atlas/domains/api/common.py +308 -0
- code_atlas/domains/api/consumers.py +224 -0
- code_atlas/domains/api/emit.py +231 -0
- code_atlas/domains/api/openapi.py +398 -0
- code_atlas/domains/api/routes.py +582 -0
- code_atlas/domains/api/yaml_subset.py +683 -0
- code_atlas/domains/aws/__init__.py +28 -0
- code_atlas/domains/aws/adapter.py +382 -0
- code_atlas/domains/aws/common.py +261 -0
- code_atlas/domains/aws/configs.py +1164 -0
- code_atlas/domains/aws/emit.py +411 -0
- code_atlas/domains/aws/pyfiles.py +2220 -0
- code_atlas/domains/aws/sqlbridge.py +448 -0
- code_atlas/domains/cicd/__init__.py +50 -0
- code_atlas/domains/cicd/adapter.py +762 -0
- code_atlas/domains/cicd/classify.py +136 -0
- code_atlas/domains/cicd/workflow.py +504 -0
- code_atlas/domains/cicd/yamlmini.py +946 -0
- code_atlas/domains/cli_surface.py +739 -0
- code_atlas/domains/contracts.py +274 -0
- code_atlas/domains/crossresolve.py +1208 -0
- code_atlas/domains/evidence.py +57 -0
- code_atlas/domains/identity.py +149 -0
- code_atlas/domains/model.py +202 -0
- code_atlas/domains/registry.py +250 -0
- code_atlas/domains/resolution.py +65 -0
- code_atlas/domains/spark/__init__.py +31 -0
- code_atlas/domains/spark/adapter.py +146 -0
- code_atlas/domains/spark/emit.py +344 -0
- code_atlas/domains/spark/extract.py +108 -0
- code_atlas/domains/spark/sqllit.py +192 -0
- code_atlas/domains/spark/walk.py +1943 -0
- code_atlas/domains/sql/__init__.py +30 -0
- code_atlas/domains/sql/adapter.py +382 -0
- code_atlas/domains/sql/extract.py +1271 -0
- code_atlas/domains/sql/nodes.py +264 -0
- code_atlas/domains/sql/parser.py +1510 -0
- code_atlas/domains/sql/tokens.py +318 -0
- code_atlas/domains/summarize.py +126 -0
- code_atlas/domains/terraform/__init__.py +20 -0
- code_atlas/domains/terraform/adapter.py +127 -0
- code_atlas/domains/terraform/catalog.py +43 -0
- code_atlas/domains/terraform/extract.py +1277 -0
- code_atlas/domains/terraform/hcl.py +538 -0
- code_atlas/domains/terraform/refs.py +181 -0
- code_atlas/domains/terraform/tfjson.py +163 -0
- code_atlas/domains/terraform/tfvars.py +98 -0
- code_atlas/domains/validation.py +120 -0
- code_atlas/env_override.py +27 -0
- code_atlas/eval/__init__.py +26 -0
- code_atlas/eval/domain_eval.py +681 -0
- code_atlas/eval/ground_truth.py +688 -0
- code_atlas/eval/impact_truth.py +758 -0
- code_atlas/eval/metrics.py +178 -0
- code_atlas/eval/pack_value.py +610 -0
- code_atlas/federation/__init__.py +72 -0
- code_atlas/federation/federation.py +676 -0
- code_atlas/federation/model.py +355 -0
- code_atlas/federation/store.py +194 -0
- code_atlas/gitbin.py +48 -0
- code_atlas/graph/__init__.py +20 -0
- code_atlas/graph/builder.py +378 -0
- code_atlas/graph/identity.py +54 -0
- code_atlas/graph/queries.py +47 -0
- code_atlas/graph/validation.py +92 -0
- code_atlas/impact/__init__.py +37 -0
- code_atlas/impact/blast_radius.py +171 -0
- code_atlas/impact/contract_analyzer.py +104 -0
- code_atlas/impact/contracts.py +315 -0
- code_atlas/impact/diff_reader.py +142 -0
- code_atlas/impact/domain_traversal.py +768 -0
- code_atlas/impact/engine.py +392 -0
- code_atlas/impact/packs.py +128 -0
- code_atlas/impact/report.py +136 -0
- code_atlas/impact/revision_binding.py +220 -0
- code_atlas/impact/risk_classifier.py +83 -0
- code_atlas/impact/snapshot_resolver.py +136 -0
- code_atlas/impact/symbol_diff.py +210 -0
- code_atlas/impact/test_selector.py +166 -0
- code_atlas/indexing/__init__.py +5 -0
- code_atlas/indexing/cache.py +396 -0
- code_atlas/indexing/change_detector.py +278 -0
- code_atlas/indexing/coordinator.py +1032 -0
- code_atlas/indexing/dependency_planner.py +268 -0
- code_atlas/indexing/persist.py +436 -0
- code_atlas/indexing/reuse.py +85 -0
- code_atlas/install/__init__.py +7 -0
- code_atlas/install/cli.py +168 -0
- code_atlas/install/lifecycle.py +286 -0
- code_atlas/mcp.py +674 -0
- code_atlas/observability/__init__.py +19 -0
- code_atlas/observability/logging.py +199 -0
- code_atlas/parsers/__init__.py +43 -0
- code_atlas/parsers/contracts.py +209 -0
- code_atlas/parsers/engine.py +125 -0
- code_atlas/parsers/languages/__init__.py +1 -0
- code_atlas/parsers/languages/python.py +391 -0
- code_atlas/parsers/languages/tsx.py +28 -0
- code_atlas/parsers/languages/typescript.py +909 -0
- code_atlas/parsers/registry.py +51 -0
- code_atlas/parsers/tree_sitter_runtime.py +118 -0
- code_atlas/resolvers/__init__.py +33 -0
- code_atlas/resolvers/bootstrap.py +246 -0
- code_atlas/resolvers/capabilities.py +33 -0
- code_atlas/resolvers/contracts.py +133 -0
- code_atlas/resolvers/coordinator.py +96 -0
- code_atlas/resolvers/enrichment.py +318 -0
- code_atlas/resolvers/lsp/__init__.py +26 -0
- code_atlas/resolvers/lsp/adapter.py +256 -0
- code_atlas/resolvers/lsp/lifecycle.py +251 -0
- code_atlas/resolvers/lsp/spawn.py +92 -0
- code_atlas/resolvers/lsp/transport.py +240 -0
- code_atlas/resolvers/registry.py +33 -0
- code_atlas/resolvers/scip/__init__.py +49 -0
- code_atlas/resolvers/scip/decoder.py +216 -0
- code_atlas/resolvers/scip/importer.py +248 -0
- code_atlas/resolvers/scip/loader.py +90 -0
- code_atlas/resolvers/scip/mapper.py +271 -0
- code_atlas/resolvers/scip/provenance.py +211 -0
- code_atlas/resolvers/scip/resolver.py +289 -0
- code_atlas/resolvers/scip/validation.py +101 -0
- code_atlas/resolvers/syntactic/__init__.py +8 -0
- code_atlas/resolvers/syntactic/resolver.py +254 -0
- code_atlas/search/__init__.py +32 -0
- code_atlas/search/contracts.py +176 -0
- code_atlas/search/domain.py +561 -0
- code_atlas/search/engine.py +530 -0
- code_atlas/search/federated.py +276 -0
- code_atlas/search/graph.py +282 -0
- code_atlas/search/hybrid.py +151 -0
- code_atlas/search/intent.py +48 -0
- code_atlas/search/lexical.py +176 -0
- code_atlas/search/refs.py +359 -0
- code_atlas/search/structural.py +505 -0
- code_atlas/search/tokenizer.py +43 -0
- code_atlas/search/vector/__init__.py +35 -0
- code_atlas/search/vector/contracts.py +76 -0
- code_atlas/search/vector/embeddings.py +96 -0
- code_atlas/search/vector/index.py +169 -0
- code_atlas/search/vector/provider.py +50 -0
- code_atlas/storage/__init__.py +14 -0
- code_atlas/storage/db.py +47 -0
- code_atlas/storage/migrations.py +552 -0
- code_atlas/storage/store.py +1045 -0
- code_atlas/ui/__init__.py +29 -0
- code_atlas/ui/i18n.py +143 -0
- code_atlas/ui/kit.py +689 -0
- code_atlas/ui/wizard.py +317 -0
- code_atlas_forge-1.1.0.dist-info/METADATA +246 -0
- code_atlas_forge-1.1.0.dist-info/RECORD +181 -0
- code_atlas_forge-1.1.0.dist-info/WHEEL +5 -0
- code_atlas_forge-1.1.0.dist-info/entry_points.txt +2 -0
- code_atlas_forge-1.1.0.dist-info/licenses/LICENSE +202 -0
- code_atlas_forge-1.1.0.dist-info/licenses/NOTICE +17 -0
- 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
|
+
)
|