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,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
|