cg-code-graph 0.10.1__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.
- cg_code_graph-0.10.1.dist-info/METADATA +678 -0
- cg_code_graph-0.10.1.dist-info/RECORD +174 -0
- cg_code_graph-0.10.1.dist-info/WHEEL +5 -0
- cg_code_graph-0.10.1.dist-info/entry_points.txt +3 -0
- cg_code_graph-0.10.1.dist-info/licenses/LICENSE +21 -0
- cg_code_graph-0.10.1.dist-info/top_level.txt +1 -0
- codegraph/__init__.py +2 -0
- codegraph/aitools.py +129 -0
- codegraph/apps.py +76 -0
- codegraph/blindspots.py +428 -0
- codegraph/bridges.py +1701 -0
- codegraph/cli.py +725 -0
- codegraph/concepts.py +362 -0
- codegraph/config.py +559 -0
- codegraph/core/__init__.py +0 -0
- codegraph/core/cache.py +375 -0
- codegraph/core/detect.py +80 -0
- codegraph/core/extractors.py +187 -0
- codegraph/core/fsutil.py +61 -0
- codegraph/core/generated.py +575 -0
- codegraph/core/model.py +174 -0
- codegraph/core/paths.py +175 -0
- codegraph/core/plugin.py +160 -0
- codegraph/core/store.py +80 -0
- codegraph/core/syntax_errors.py +132 -0
- codegraph/coverage.py +928 -0
- codegraph/doctor.py +453 -0
- codegraph/external.py +613 -0
- codegraph/indexer.py +336 -0
- codegraph/link.py +434 -0
- codegraph/lint_async.py +524 -0
- codegraph/mcp_server.py +1303 -0
- codegraph/parity.py +473 -0
- codegraph/parity_structure.py +307 -0
- codegraph/payload.py +321 -0
- codegraph/plans.py +1285 -0
- codegraph/platform_scan.py +643 -0
- codegraph/platforms.py +1369 -0
- codegraph/plugins/__init__.py +0 -0
- codegraph/plugins/cfamily/__init__.py +0 -0
- codegraph/plugins/cfamily/plugin.py +930 -0
- codegraph/plugins/cfamily/syntax.py +881 -0
- codegraph/plugins/dart/__init__.py +0 -0
- codegraph/plugins/dart/bridges.py +345 -0
- codegraph/plugins/dart/extractor/bin/extract.dart +717 -0
- codegraph/plugins/dart/extractor/pubspec.lock +149 -0
- codegraph/plugins/dart/extractor/pubspec.yaml +7 -0
- codegraph/plugins/dart/http.py +904 -0
- codegraph/plugins/dart/models.py +308 -0
- codegraph/plugins/dart/plugin.py +625 -0
- codegraph/plugins/dart/program.py +907 -0
- codegraph/plugins/django/__init__.py +0 -0
- codegraph/plugins/django/extras.py +378 -0
- codegraph/plugins/django/models.py +508 -0
- codegraph/plugins/django/plugin.py +728 -0
- codegraph/plugins/django/schemas.py +339 -0
- codegraph/plugins/django/shapes.py +216 -0
- codegraph/plugins/django/urls.py +603 -0
- codegraph/plugins/express/__init__.py +0 -0
- codegraph/plugins/express/plugin.py +428 -0
- codegraph/plugins/flutter/__init__.py +0 -0
- codegraph/plugins/flutter/plugin.py +538 -0
- codegraph/plugins/kotlin/__init__.py +0 -0
- codegraph/plugins/kotlin/exact.py +457 -0
- codegraph/plugins/kotlin/plugin.py +1961 -0
- codegraph/plugins/kotlin/reparse.py +234 -0
- codegraph/plugins/laravel/__init__.py +0 -0
- codegraph/plugins/laravel/broadcast.py +351 -0
- codegraph/plugins/laravel/plugin.py +863 -0
- codegraph/plugins/laravel/tests.py +262 -0
- codegraph/plugins/laravel/values.py +728 -0
- codegraph/plugins/native/__init__.py +0 -0
- codegraph/plugins/native/gates.py +286 -0
- codegraph/plugins/native/runner.py +183 -0
- codegraph/plugins/native/scipread.py +194 -0
- codegraph/plugins/native/ts.py +54 -0
- codegraph/plugins/nest/__init__.py +0 -0
- codegraph/plugins/nest/plugin.py +654 -0
- codegraph/plugins/nextjs/__init__.py +0 -0
- codegraph/plugins/nextjs/plugin.py +336 -0
- codegraph/plugins/nuxt/__init__.py +0 -0
- codegraph/plugins/nuxt/plugin.py +308 -0
- codegraph/plugins/php/__init__.py +0 -0
- codegraph/plugins/php/extractor/composer.json +5 -0
- codegraph/plugins/php/extractor/composer.lock +76 -0
- codegraph/plugins/php/extractor/extract.php +743 -0
- codegraph/plugins/php/gating.py +573 -0
- codegraph/plugins/php/plugin.py +668 -0
- codegraph/plugins/php/strings.py +197 -0
- codegraph/plugins/python/__init__.py +0 -0
- codegraph/plugins/python/aitools.py +664 -0
- codegraph/plugins/python/external.py +245 -0
- codegraph/plugins/python/fields.py +107 -0
- codegraph/plugins/python/plugin.py +1733 -0
- codegraph/plugins/python/refs.py +485 -0
- codegraph/plugins/python/roots.py +412 -0
- codegraph/plugins/python/socketio.py +210 -0
- codegraph/plugins/python/subproc.py +864 -0
- codegraph/plugins/python/tests.py +1040 -0
- codegraph/plugins/python/values.py +179 -0
- codegraph/plugins/pyweb/__init__.py +0 -0
- codegraph/plugins/pyweb/plugin.py +1334 -0
- codegraph/plugins/pyweb/values.py +68 -0
- codegraph/plugins/rust/__init__.py +0 -0
- codegraph/plugins/rust/cargo.py +226 -0
- codegraph/plugins/rust/plugin.py +980 -0
- codegraph/plugins/rust/syntax.py +678 -0
- codegraph/plugins/scip/__init__.py +0 -0
- codegraph/plugins/scip/importer.py +129 -0
- codegraph/plugins/scip/scip.proto +962 -0
- codegraph/plugins/scip/scip_pb2.py +97 -0
- codegraph/plugins/stubs/__init__.py +0 -0
- codegraph/plugins/stubs/plugins.py +38 -0
- codegraph/plugins/swift/__init__.py +0 -0
- codegraph/plugins/swift/baseurl.py +109 -0
- codegraph/plugins/swift/exact.py +415 -0
- codegraph/plugins/swift/indexstore.py +209 -0
- codegraph/plugins/swift/packages.py +174 -0
- codegraph/plugins/swift/plugin.py +2890 -0
- codegraph/plugins/ts/__init__.py +0 -0
- codegraph/plugins/ts/baseurl.py +185 -0
- codegraph/plugins/ts/extractor/extract.mjs +2652 -0
- codegraph/plugins/ts/extractor/fw.mjs +685 -0
- codegraph/plugins/ts/extractor/package-lock.json +205 -0
- codegraph/plugins/ts/extractor/package.json +9 -0
- codegraph/plugins/ts/plugin.py +480 -0
- codegraph/plugins/tsweb/__init__.py +0 -0
- codegraph/plugins/tsweb/common.py +290 -0
- codegraph/plugins/tsweb/data.py +276 -0
- codegraph/presets/__init__.py +146 -0
- codegraph/presets/c_cpp.yaml +9 -0
- codegraph/presets/common.yaml +66 -0
- codegraph/presets/dart.yaml +9 -0
- codegraph/presets/django-ninja.yaml +15 -0
- codegraph/presets/django.yaml +25 -0
- codegraph/presets/djangorestframework.yaml +17 -0
- codegraph/presets/express.yaml +17 -0
- codegraph/presets/kotlin.yaml +11 -0
- codegraph/presets/laravel.yaml +40 -0
- codegraph/presets/nest.yaml +11 -0
- codegraph/presets/nextjs.yaml +15 -0
- codegraph/presets/nuxt.yaml +9 -0
- codegraph/presets/php.yaml +5 -0
- codegraph/presets/python.yaml +10 -0
- codegraph/presets/rust.yaml +5 -0
- codegraph/presets/swift.yaml +10 -0
- codegraph/presets/typescript.yaml +13 -0
- codegraph/process_runs.py +328 -0
- codegraph/protocols/__init__.py +299 -0
- codegraph/protocols/builtin.py +67 -0
- codegraph/protocols/matchers.py +144 -0
- codegraph/protocols/view.py +334 -0
- codegraph/query.py +2089 -0
- codegraph/realtime.py +260 -0
- codegraph/roundtrip.py +346 -0
- codegraph/routes.py +442 -0
- codegraph/starters.py +218 -0
- codegraph/tests_index.py +117 -0
- codegraph/viz/__init__.py +0 -0
- codegraph/viz/graph.py +369 -0
- codegraph/viz/server.py +198 -0
- codegraph/viz/static/app.css +148 -0
- codegraph/viz/static/app.js +1082 -0
- codegraph/viz/static/index.html +81 -0
- codegraph/viz/static/layered.js +237 -0
- codegraph/viz/static/vendor/VERSIONS.txt +4 -0
- codegraph/viz/static/vendor/cose-base.js +3214 -0
- codegraph/viz/static/vendor/cytoscape-fcose.js +1549 -0
- codegraph/viz/static/vendor/cytoscape.min.js +31 -0
- codegraph/viz/static/vendor/layout-base.js +5230 -0
- codegraph/viz/tools/package-lock.json +303 -0
- codegraph/viz/tools/package.json +7 -0
- codegraph/viz/tools/shoot.mjs +165 -0
- codegraph/xcode.py +251 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
"""Syntactic layer for Rust (tree-sitter-rust): items with stable keys and ranges, attributes, cfg gates,
|
|
2
|
+
visibility, impls, unsafe blocks, FFI, env keys, async entry points, axum/actix-style routes, and (for the
|
|
3
|
+
heuristic mode without rust-analyzer) call sites, type references and `use` maps.
|
|
4
|
+
|
|
5
|
+
Keys follow Rust paths so the SCIP layer can be matched onto them by (file, line, name):
|
|
6
|
+
function kv_core::util::checksum method kv_core::store::MemoryStore::len
|
|
7
|
+
trait-impl method kv_core::store::<MemoryStore as Store>::get
|
|
8
|
+
trait method kv_core::store::Store::get type kv_core::store::MemoryStore (struct/enum/union/trait/type_alias)
|
|
9
|
+
field / variant kv_core::store::MemoryStore::map
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
|
|
16
|
+
from ..native.ts import parser, string_value, text
|
|
17
|
+
|
|
18
|
+
ITEM_TYPES = {"function_item", "function_signature_item", "struct_item", "enum_item", "union_item", "trait_item",
|
|
19
|
+
"type_item", "const_item", "static_item", "mod_item", "impl_item", "foreign_mod_item", "macro_definition"}
|
|
20
|
+
HTTP_VERBS = {"get", "post", "put", "delete", "patch", "head", "options", "trace", "any"}
|
|
21
|
+
ENTRY_ATTRS = { # attribute path -> (entry kind, runtime)
|
|
22
|
+
"tokio::main": ("main", "tokio"), "async_std::main": ("main", "async-std"), "actix_web::main": ("main", "actix"),
|
|
23
|
+
"actix_rt::main": ("main", "actix"), "smol_potat::main": ("main", "smol"), "rocket::main": ("main", "rocket"),
|
|
24
|
+
"rocket::launch": ("main", "rocket"), "launch": ("main", "rocket"), "main": ("main", None),
|
|
25
|
+
"test": ("test", None), "tokio::test": ("test", "tokio"), "async_std::test": ("test", "async-std"),
|
|
26
|
+
"actix_rt::test": ("test", "actix"), "actix_web::test": ("test", "actix"), "rstest": ("test", None),
|
|
27
|
+
"test_case": ("test", None), "quickcheck": ("test", None), "proptest": ("test", None), "bench": ("bench", None),
|
|
28
|
+
"wasm_bindgen_test": ("test", None), "sqlx::test": ("test", None),
|
|
29
|
+
}
|
|
30
|
+
_BLANK = re.compile(rb"[^\n]")
|
|
31
|
+
ATTR_FN_REF_ATTRS = {"serde", "arg", "clap", "validate", "builder"}
|
|
32
|
+
ATTR_FN_REF = re.compile(r'\b(default|with|serialize_with|deserialize_with|skip_serializing_if|value_parser|getter|custom)\s*=\s*(?:"([A-Za-z_][\w:]*)"|([A-Za-z_][\w:]*)\b(?!\s*[(!]))')
|
|
33
|
+
ROUTE_ATTRS = {"get", "post", "put", "delete", "patch", "head", "options", "route"} # actix-web / rocket
|
|
34
|
+
ENV_CALLS = re.compile(r"(^|::)(env::var|env::var_os|dotenvy?::var|env::var_os)$")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class RItem:
|
|
39
|
+
kind: str
|
|
40
|
+
name: str
|
|
41
|
+
key: str
|
|
42
|
+
file: str
|
|
43
|
+
line: int # 1-based line of the name
|
|
44
|
+
col: int # 0-based column of the name
|
|
45
|
+
start: int
|
|
46
|
+
end: int
|
|
47
|
+
module: str
|
|
48
|
+
vis: str | None = None
|
|
49
|
+
attrs: list = field(default_factory=list)
|
|
50
|
+
cfgs: list = field(default_factory=list) # [(predicate text, line)] incl. inherited
|
|
51
|
+
in_test: bool = False
|
|
52
|
+
unsafe: bool = False
|
|
53
|
+
is_async: bool = False
|
|
54
|
+
abi: str | None = None
|
|
55
|
+
parent: str | None = None # owning type / trait / impl prefix key
|
|
56
|
+
impl_self: str | None = None
|
|
57
|
+
impl_trait: str | None = None # trait text, e.g. From<u8>
|
|
58
|
+
impl_trait_pos: tuple | None = None # (line, col) of the trait name (for SCIP lookup)
|
|
59
|
+
impl_self_pos: tuple | None = None
|
|
60
|
+
doc: str | None = None
|
|
61
|
+
pub_chain: bool = False # item and every enclosing module are `pub`
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ModDecl:
|
|
66
|
+
name: str
|
|
67
|
+
line: int
|
|
68
|
+
vis: str | None
|
|
69
|
+
path_attr: str | None
|
|
70
|
+
cfgs: list
|
|
71
|
+
in_test: bool
|
|
72
|
+
module: str # module key of the declared child
|
|
73
|
+
pub_chain: bool
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class RFile:
|
|
78
|
+
path: str
|
|
79
|
+
crate: str
|
|
80
|
+
module: str
|
|
81
|
+
items: list = field(default_factory=list)
|
|
82
|
+
mods: list = field(default_factory=list)
|
|
83
|
+
unsafe_blocks: list = field(default_factory=list) # (line, owner key)
|
|
84
|
+
macro_bodies: int = 0 # item-level macro bodies indexed as items
|
|
85
|
+
attr_refs: list = field(default_factory=list) # (owner key, fn/module path, line, attribute key)
|
|
86
|
+
env: list = field(default_factory=list) # (key, line, owner, how)
|
|
87
|
+
routes: list = field(default_factory=list) # (method, path, handler text, line, col, owner, framework)
|
|
88
|
+
cfg_regions: list = field(default_factory=list) # (start, end, predicate, attr line, owner)
|
|
89
|
+
use_ranges: list = field(default_factory=list) # (start, end)
|
|
90
|
+
use_map: dict = field(default_factory=dict) # (module key, alias) -> path
|
|
91
|
+
pub_uses: list = field(default_factory=list) # (module key, path, line)
|
|
92
|
+
calls: list = field(default_factory=list) # (owner, form, path text, name, line, col)
|
|
93
|
+
type_refs: list = field(default_factory=list) # (owner, name, line, col)
|
|
94
|
+
inner_cfgs: list = field(default_factory=list) # #![cfg(..)] at file top
|
|
95
|
+
error_spans: list = field(default_factory=list) # [first, last] lines tree-sitter could not parse (#73)
|
|
96
|
+
file_doc: str | None = None
|
|
97
|
+
lines: list = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _vis(src, n) -> str | None:
|
|
101
|
+
for c in n.children:
|
|
102
|
+
if c.type == "visibility_modifier":
|
|
103
|
+
return text(src, c)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _attr_path(src, a) -> tuple[str, str]:
|
|
108
|
+
"""attribute_item -> (path text, args text)."""
|
|
109
|
+
at = next((c for c in a.children if c.type == "attribute"), None)
|
|
110
|
+
if at is None:
|
|
111
|
+
return "", ""
|
|
112
|
+
path = at.children[0] if at.children else None
|
|
113
|
+
args = at.child_by_field_name("arguments")
|
|
114
|
+
val = at.child_by_field_name("value")
|
|
115
|
+
return (text(src, path) if path is not None else ""), (text(src, args) if args is not None else (text(src, val) if val is not None else ""))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _type_name(src, t) -> str:
|
|
119
|
+
"""Self type of an impl -> short name (generic args and path prefix dropped)."""
|
|
120
|
+
if t is None:
|
|
121
|
+
return "?"
|
|
122
|
+
while t.type in ("reference_type", "pointer_type") and t.child_by_field_name("type") is not None:
|
|
123
|
+
t = t.child_by_field_name("type")
|
|
124
|
+
if t.type == "generic_type":
|
|
125
|
+
t = t.child_by_field_name("type") or t
|
|
126
|
+
s = text(src, t)
|
|
127
|
+
s = re.sub(r"\s+", " ", s)
|
|
128
|
+
return s.split("::")[-1] if "<" not in s else s
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _trait_text(src, t) -> str:
|
|
132
|
+
s = re.sub(r"\s+", "", text(src, t))
|
|
133
|
+
if s.startswith("::"):
|
|
134
|
+
s = s[2:]
|
|
135
|
+
# drop the path prefix outside generic arguments: fmt::Display -> Display, a::From<b::C> -> From<b::C>
|
|
136
|
+
head, lt, rest = s.partition("<")
|
|
137
|
+
return head.split("::")[-1] + lt + rest
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _name_pos(n):
|
|
141
|
+
nm = n.child_by_field_name("name")
|
|
142
|
+
if nm is None:
|
|
143
|
+
return None, n.start_point[0] + 1, n.start_point[1]
|
|
144
|
+
return nm, nm.start_point[0] + 1, nm.start_point[1]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def parse_use(src, n, prefix: str = "") -> list[tuple[str, str]]:
|
|
148
|
+
"""use tree -> [(alias, full path)]; glob imports give ('*', path)."""
|
|
149
|
+
out = []
|
|
150
|
+
t = n.type
|
|
151
|
+
if t in ("identifier", "self", "crate", "super", "metavariable"):
|
|
152
|
+
p = f"{prefix}::{text(src, n)}" if prefix else text(src, n)
|
|
153
|
+
alias = text(src, n)
|
|
154
|
+
if alias == "self" and prefix:
|
|
155
|
+
alias, p = prefix.split("::")[-1], prefix
|
|
156
|
+
out.append((alias, p))
|
|
157
|
+
elif t == "scoped_identifier":
|
|
158
|
+
p = re.sub(r"\s+", "", text(src, n))
|
|
159
|
+
out.append((p.split("::")[-1], f"{prefix}::{p}" if prefix else p))
|
|
160
|
+
elif t == "use_as_clause":
|
|
161
|
+
path = n.child_by_field_name("path")
|
|
162
|
+
alias = n.child_by_field_name("alias")
|
|
163
|
+
p = re.sub(r"\s+", "", text(src, path))
|
|
164
|
+
out.append((text(src, alias), f"{prefix}::{p}" if prefix else p))
|
|
165
|
+
elif t == "use_wildcard":
|
|
166
|
+
p = re.sub(r"\s+", "", text(src, n))[:-3].rstrip(":")
|
|
167
|
+
out.append(("*", f"{prefix}::{p}" if prefix and p else (p or prefix)))
|
|
168
|
+
elif t == "scoped_use_list":
|
|
169
|
+
path = n.child_by_field_name("path")
|
|
170
|
+
lst = n.child_by_field_name("list")
|
|
171
|
+
p = re.sub(r"\s+", "", text(src, path)) if path is not None else ""
|
|
172
|
+
np = f"{prefix}::{p}" if prefix and p else (p or prefix)
|
|
173
|
+
if lst is not None:
|
|
174
|
+
for c in lst.named_children:
|
|
175
|
+
out += parse_use(src, c, np)
|
|
176
|
+
elif t == "use_list":
|
|
177
|
+
for c in n.named_children:
|
|
178
|
+
out += parse_use(src, c, prefix)
|
|
179
|
+
return out
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Extractor:
|
|
183
|
+
def __init__(self, path: str, src: bytes, crate: str, module: str, cfgs: list, in_test: bool, pub_chain: bool):
|
|
184
|
+
self.src = src
|
|
185
|
+
self.f = RFile(path, crate, module)
|
|
186
|
+
self.base_cfgs = list(cfgs)
|
|
187
|
+
self.base_test = in_test
|
|
188
|
+
self.base_pub = pub_chain
|
|
189
|
+
self.consts: dict[str, str] = {}
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------- entry
|
|
192
|
+
def run(self) -> RFile:
|
|
193
|
+
tree = parser("rust").parse(self.src)
|
|
194
|
+
root = tree.root_node
|
|
195
|
+
if root.has_error:
|
|
196
|
+
from ...core.syntax_errors import tree_spans
|
|
197
|
+
self.f.error_spans = tree_spans(root)
|
|
198
|
+
self.f.lines = self.src.decode("utf-8", "replace").split("\n")
|
|
199
|
+
# pass 1: string consts (for env::var(CONST))
|
|
200
|
+
for n in root.children:
|
|
201
|
+
if n.type in ("const_item", "static_item"):
|
|
202
|
+
v = n.child_by_field_name("value")
|
|
203
|
+
nm = n.child_by_field_name("name")
|
|
204
|
+
sv = string_value(self.src, v) if v is not None else None
|
|
205
|
+
if nm is not None and sv is not None:
|
|
206
|
+
self.consts[text(self.src, nm)] = sv
|
|
207
|
+
cfgs, test = list(self.base_cfgs), self.base_test
|
|
208
|
+
docs = []
|
|
209
|
+
for c in root.children:
|
|
210
|
+
if c.type == "inner_attribute_item":
|
|
211
|
+
p, a = _attr_path(self.src, c)
|
|
212
|
+
if p == "cfg":
|
|
213
|
+
pred = a[1:-1] if a.startswith("(") else a
|
|
214
|
+
if pred.strip() == "test":
|
|
215
|
+
test = True
|
|
216
|
+
else:
|
|
217
|
+
cfgs.append((pred, c.start_point[0] + 1))
|
|
218
|
+
self.f.inner_cfgs.append((pred, c.start_point[0] + 1))
|
|
219
|
+
elif c.type == "line_comment" and text(self.src, c).startswith("//!"):
|
|
220
|
+
docs.append(text(self.src, c)[3:].strip())
|
|
221
|
+
self.f.file_doc = "\n".join(docs) or None
|
|
222
|
+
self.container(root, self.f.module, cfgs, test, self.base_pub, None)
|
|
223
|
+
return self.f
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------- items
|
|
226
|
+
def container(self, node, module: str, cfgs: list, in_test: bool, pub_chain: bool, ctx: dict | None):
|
|
227
|
+
"""Walk a source_file / declaration_list: attach preceding attributes + doc comments to each item."""
|
|
228
|
+
attrs, docs = [], []
|
|
229
|
+
for c in node.children:
|
|
230
|
+
t = c.type
|
|
231
|
+
if t == "attribute_item":
|
|
232
|
+
attrs.append(c)
|
|
233
|
+
continue
|
|
234
|
+
if t == "line_comment" or t == "block_comment":
|
|
235
|
+
s = text(self.src, c)
|
|
236
|
+
if s.startswith("///") or s.startswith("/**"):
|
|
237
|
+
docs.append(s.lstrip("/*! ").rstrip("*/ "))
|
|
238
|
+
continue
|
|
239
|
+
if t in ITEM_TYPES or t == "use_declaration" or t in ("field_declaration", "enum_variant"):
|
|
240
|
+
self.item(c, attrs, docs, module, cfgs, in_test, pub_chain, ctx)
|
|
241
|
+
elif t == "macro_invocation" and (ctx is None or ctx.get("kind") in ("impl", "trait_impl", "trait")):
|
|
242
|
+
self.macro_item(c, module, cfgs, in_test, pub_chain, ctx)
|
|
243
|
+
attrs, docs = [], []
|
|
244
|
+
|
|
245
|
+
def _attr_info(self, attrs):
|
|
246
|
+
out_attrs, cfg_add, test = [], [], False
|
|
247
|
+
self._pending_refs = []
|
|
248
|
+
for a in attrs:
|
|
249
|
+
p, args = _attr_path(self.src, a)
|
|
250
|
+
out_attrs.append((p, args))
|
|
251
|
+
if p in ATTR_FN_REF_ATTRS and args:
|
|
252
|
+
# #[serde(default = "path", with = "module", ...)] / #[arg(value_parser = path)]: fn named in an attribute
|
|
253
|
+
for m in ATTR_FN_REF.finditer(args):
|
|
254
|
+
self._pending_refs.append((m.group(2) or m.group(3), a.start_point[0] + 1, m.group(1)))
|
|
255
|
+
if p == "cfg":
|
|
256
|
+
pred = args[1:-1] if args.startswith("(") else args
|
|
257
|
+
if pred.strip() == "test":
|
|
258
|
+
test = True
|
|
259
|
+
else:
|
|
260
|
+
cfg_add.append((pred, a.start_point[0] + 1))
|
|
261
|
+
return out_attrs, cfg_add, test
|
|
262
|
+
|
|
263
|
+
def _add(self, kind, name, key, n, module, vis, attrs, cfgs, in_test, docs, **kw) -> RItem:
|
|
264
|
+
nm, line, col = _name_pos(n)
|
|
265
|
+
if kw.get("pos"):
|
|
266
|
+
line, col = kw.pop("pos")
|
|
267
|
+
it = RItem(kind, name, key, self.f.path, line, col, n.start_point[0] + 1, n.end_point[0] + 1, module, vis,
|
|
268
|
+
[p for p, _ in attrs] if attrs and isinstance(attrs[0], tuple) else list(attrs or []), list(cfgs),
|
|
269
|
+
in_test, doc="\n".join(docs) or None, **kw)
|
|
270
|
+
self.f.items.append(it)
|
|
271
|
+
if getattr(self, "_pending_refs", None):
|
|
272
|
+
for path, line, how in self._pending_refs:
|
|
273
|
+
self.f.attr_refs.append((it.key, path, line, how))
|
|
274
|
+
self._pending_refs = []
|
|
275
|
+
return it
|
|
276
|
+
|
|
277
|
+
def item(self, n, attr_nodes, docs, module, cfgs, in_test, pub_chain, ctx):
|
|
278
|
+
src = self.src
|
|
279
|
+
attrs, cfg_add, test = self._attr_info(attr_nodes)
|
|
280
|
+
cfgs = cfgs + cfg_add
|
|
281
|
+
in_test = in_test or test
|
|
282
|
+
t = n.type
|
|
283
|
+
vis = _vis(src, n)
|
|
284
|
+
is_pub = bool(vis) and vis.strip() == "pub"
|
|
285
|
+
# inside a trait every method is as public as the trait; impl items carry their own visibility
|
|
286
|
+
chain = pub_chain and (is_pub or (ctx is not None and ctx.get("kind") in ("trait", "trait_impl")))
|
|
287
|
+
if t == "use_declaration":
|
|
288
|
+
self.f.use_ranges.append((n.start_point[0] + 1, n.end_point[0] + 1))
|
|
289
|
+
arg = n.child_by_field_name("argument")
|
|
290
|
+
if arg is not None:
|
|
291
|
+
for alias, path in parse_use(src, arg):
|
|
292
|
+
self.f.use_map.setdefault((module, alias), path) if alias != "*" else self.f.use_map.setdefault((module, "*" + path), path)
|
|
293
|
+
if is_pub:
|
|
294
|
+
self.f.pub_uses.append((module, path if alias != "*" else path + "::*", n.start_point[0] + 1))
|
|
295
|
+
return
|
|
296
|
+
if t in ("function_item", "function_signature_item"):
|
|
297
|
+
nm = n.child_by_field_name("name")
|
|
298
|
+
if nm is None:
|
|
299
|
+
return
|
|
300
|
+
name = text(src, nm)
|
|
301
|
+
mods = next((c for c in n.children if c.type == "function_modifiers"), None)
|
|
302
|
+
mtxt = text(src, mods) if mods is not None else ""
|
|
303
|
+
abi = None
|
|
304
|
+
if mods is not None:
|
|
305
|
+
for c in mods.children:
|
|
306
|
+
if c.type == "extern_modifier":
|
|
307
|
+
sv = next((string_value(src, x) for x in c.children if x.type == "string_literal"), None)
|
|
308
|
+
abi = sv or "C"
|
|
309
|
+
if ctx and ctx.get("kind") == "foreign":
|
|
310
|
+
kind, key = "ffi", f"{module}::{name}"
|
|
311
|
+
abi = ctx.get("abi")
|
|
312
|
+
elif ctx and ctx.get("kind") in ("impl", "trait_impl", "trait"):
|
|
313
|
+
kind, key = "method", f"{ctx['prefix']}::{name}"
|
|
314
|
+
elif ctx and ctx.get("kind") == "fn":
|
|
315
|
+
kind, key = "function", f"{ctx['prefix']}::{name}"
|
|
316
|
+
else:
|
|
317
|
+
kind, key = "function", f"{module}::{name}"
|
|
318
|
+
it = self._add(kind, name, key, n, module, vis, attrs, cfgs, in_test, docs, unsafe="unsafe" in mtxt.split(),
|
|
319
|
+
is_async="async" in mtxt.split(), abi=abi, parent=(ctx or {}).get("prefix"),
|
|
320
|
+
impl_self=(ctx or {}).get("self"), impl_trait=(ctx or {}).get("trait"),
|
|
321
|
+
impl_trait_pos=(ctx or {}).get("trait_pos"), impl_self_pos=(ctx or {}).get("self_pos"))
|
|
322
|
+
it.pub_chain = chain if not (ctx and ctx.get("kind") == "fn") else False
|
|
323
|
+
body = n.child_by_field_name("body")
|
|
324
|
+
if body is not None:
|
|
325
|
+
self.body(body, it, module, cfgs, in_test)
|
|
326
|
+
return
|
|
327
|
+
if t in ("struct_item", "enum_item", "union_item", "trait_item", "type_item"):
|
|
328
|
+
nm = n.child_by_field_name("name")
|
|
329
|
+
if nm is None:
|
|
330
|
+
return
|
|
331
|
+
name = text(src, nm)
|
|
332
|
+
kind = {"struct_item": "struct", "enum_item": "enum", "union_item": "union", "trait_item": "trait", "type_item": "type_alias"}[t]
|
|
333
|
+
prefix = ctx["prefix"] if ctx and ctx.get("kind") == "fn" else module
|
|
334
|
+
if ctx and ctx.get("kind") in ("impl", "trait_impl", "trait"): # associated type
|
|
335
|
+
prefix = ctx["prefix"]
|
|
336
|
+
key = f"{prefix}::{name}"
|
|
337
|
+
it = self._add(kind, name, key, n, module, vis, attrs, cfgs, in_test, docs, parent=(ctx or {}).get("prefix"))
|
|
338
|
+
it.pub_chain = chain
|
|
339
|
+
if "unsafe" in [c.type for c in n.children]:
|
|
340
|
+
it.unsafe = True
|
|
341
|
+
body = n.child_by_field_name("body")
|
|
342
|
+
if body is not None:
|
|
343
|
+
if kind == "trait":
|
|
344
|
+
self.container(body, module, cfgs, in_test, chain, {"kind": "trait", "prefix": key, "trait": name})
|
|
345
|
+
elif kind in ("struct", "union"):
|
|
346
|
+
self.fields(body, key, module, cfgs, in_test, chain)
|
|
347
|
+
elif kind == "enum":
|
|
348
|
+
self.variants(body, key, module, cfgs, in_test, chain)
|
|
349
|
+
return
|
|
350
|
+
if t in ("field_declaration", "enum_variant"):
|
|
351
|
+
return # handled by fields()/variants()
|
|
352
|
+
if t in ("const_item", "static_item"):
|
|
353
|
+
nm = n.child_by_field_name("name")
|
|
354
|
+
if nm is None:
|
|
355
|
+
return
|
|
356
|
+
name = text(src, nm)
|
|
357
|
+
if ctx and ctx.get("kind") == "foreign":
|
|
358
|
+
kind, key = "ffi", f"{module}::{name}"
|
|
359
|
+
else:
|
|
360
|
+
kind = "const" if t == "const_item" else "static"
|
|
361
|
+
prefix = ctx["prefix"] if ctx else module
|
|
362
|
+
key = f"{prefix}::{name}"
|
|
363
|
+
it = self._add(kind, name, key, n, module, vis, attrs, cfgs, in_test, docs, parent=(ctx or {}).get("prefix"))
|
|
364
|
+
it.pub_chain = chain
|
|
365
|
+
if "mutable_specifier" in [c.type for c in n.children]:
|
|
366
|
+
it.attrs.append("mut")
|
|
367
|
+
v = n.child_by_field_name("value")
|
|
368
|
+
if v is not None:
|
|
369
|
+
self.body(v, it, module, cfgs, in_test)
|
|
370
|
+
return
|
|
371
|
+
if t == "macro_definition":
|
|
372
|
+
nm = n.child_by_field_name("name")
|
|
373
|
+
if nm is None:
|
|
374
|
+
return
|
|
375
|
+
name = text(src, nm)
|
|
376
|
+
it = self._add("macro", name, f"{self.f.crate}::{name}" if any(p == "macro_export" for p, _ in attrs) else f"{module}::{name}",
|
|
377
|
+
n, module, vis, attrs, cfgs, in_test, docs)
|
|
378
|
+
it.pub_chain = any(p == "macro_export" for p, _ in attrs)
|
|
379
|
+
return
|
|
380
|
+
if t == "mod_item":
|
|
381
|
+
nm = n.child_by_field_name("name")
|
|
382
|
+
if nm is None:
|
|
383
|
+
return
|
|
384
|
+
name = text(src, nm)
|
|
385
|
+
mkey = f"{module}::{name}"
|
|
386
|
+
body = n.child_by_field_name("body")
|
|
387
|
+
path_attr = next((a.strip().lstrip("=").strip().strip('"') for p, a in attrs if p == "path"), None)
|
|
388
|
+
it = self._add("mod", name, mkey, n, module, vis, attrs, cfgs, in_test, docs)
|
|
389
|
+
it.pub_chain = pub_chain and is_pub
|
|
390
|
+
if body is None:
|
|
391
|
+
self.f.mods.append(ModDecl(name, n.start_point[0] + 1, vis, path_attr, cfgs, in_test, mkey, pub_chain and is_pub))
|
|
392
|
+
else:
|
|
393
|
+
self.f.mods.append(ModDecl(name, n.start_point[0] + 1, vis, path_attr, cfgs, in_test, mkey, pub_chain and is_pub))
|
|
394
|
+
self.f.mods[-1].inline = True
|
|
395
|
+
self.container(body, mkey, cfgs, in_test, pub_chain and is_pub, None)
|
|
396
|
+
return
|
|
397
|
+
if t == "impl_item":
|
|
398
|
+
ty = n.child_by_field_name("type")
|
|
399
|
+
tr = n.child_by_field_name("trait")
|
|
400
|
+
self_name = _type_name(src, ty)
|
|
401
|
+
base = ctx["prefix"] if ctx and ctx.get("kind") == "fn" else module
|
|
402
|
+
unsafe_impl = any(c.type == "unsafe" for c in n.children)
|
|
403
|
+
if tr is not None:
|
|
404
|
+
trait = _trait_text(src, tr)
|
|
405
|
+
tnode = tr
|
|
406
|
+
if tnode.type == "generic_type":
|
|
407
|
+
tnode = tnode.child_by_field_name("type") or tnode
|
|
408
|
+
if tnode.type == "scoped_type_identifier":
|
|
409
|
+
tnode = tnode.child_by_field_name("name") or tnode
|
|
410
|
+
prefix = f"{base}::<{self_name} as {trait}>"
|
|
411
|
+
c2 = {"kind": "trait_impl", "prefix": prefix, "self": self_name, "trait": trait,
|
|
412
|
+
"trait_pos": (tnode.start_point[0] + 1, tnode.start_point[1]),
|
|
413
|
+
"self_pos": self._type_pos(ty), "unsafe_impl": unsafe_impl}
|
|
414
|
+
else:
|
|
415
|
+
prefix = f"{base}::{self_name}"
|
|
416
|
+
c2 = {"kind": "impl", "prefix": prefix, "self": self_name, "self_pos": self._type_pos(ty)}
|
|
417
|
+
body = n.child_by_field_name("body")
|
|
418
|
+
# impl blocks are not nodes; record them as pseudo-items for trait -> impl edges
|
|
419
|
+
self.f.items.append(RItem("impl", self_name, prefix, self.f.path, n.start_point[0] + 1, n.start_point[1],
|
|
420
|
+
n.start_point[0] + 1, n.end_point[0] + 1, module, None, [p for p, _ in attrs], list(cfgs),
|
|
421
|
+
in_test, unsafe=unsafe_impl, impl_self=self_name, impl_trait=c2.get("trait"),
|
|
422
|
+
impl_trait_pos=c2.get("trait_pos"), impl_self_pos=c2.get("self_pos"), pub_chain=pub_chain))
|
|
423
|
+
if body is not None:
|
|
424
|
+
self.container(body, module, cfgs, in_test, pub_chain, c2)
|
|
425
|
+
return
|
|
426
|
+
if t == "foreign_mod_item":
|
|
427
|
+
em = next((c for c in n.children if c.type == "extern_modifier"), None)
|
|
428
|
+
abi = next((string_value(src, x) for x in em.children if x.type == "string_literal"), None) if em is not None else None
|
|
429
|
+
body = n.child_by_field_name("body")
|
|
430
|
+
if body is not None:
|
|
431
|
+
self.container(body, module, cfgs, in_test, False, {"kind": "foreign", "abi": abi or "C", "prefix": module})
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
def _type_pos(self, ty):
|
|
435
|
+
if ty is None:
|
|
436
|
+
return None
|
|
437
|
+
t = ty
|
|
438
|
+
while t.type in ("reference_type", "pointer_type", "generic_type") and (t.child_by_field_name("type") is not None):
|
|
439
|
+
t = t.child_by_field_name("type")
|
|
440
|
+
if t.type == "scoped_type_identifier":
|
|
441
|
+
t = t.child_by_field_name("name") or t
|
|
442
|
+
return (t.start_point[0] + 1, t.start_point[1])
|
|
443
|
+
|
|
444
|
+
def fields(self, body, owner_key, module, cfgs, in_test, chain):
|
|
445
|
+
attrs = []
|
|
446
|
+
for c in body.children:
|
|
447
|
+
if c.type == "attribute_item":
|
|
448
|
+
attrs.append(c)
|
|
449
|
+
continue
|
|
450
|
+
if c.type == "field_declaration":
|
|
451
|
+
nm = c.child_by_field_name("name")
|
|
452
|
+
if nm is not None:
|
|
453
|
+
a, cadd, _ = self._attr_info(attrs)
|
|
454
|
+
it = self._add("field", text(self.src, nm), f"{owner_key}::{text(self.src, nm)}", c, module, _vis(self.src, c), a, cfgs + cadd, in_test, [],
|
|
455
|
+
parent=owner_key)
|
|
456
|
+
it.pub_chain = chain and bool(it.vis)
|
|
457
|
+
ty = c.child_by_field_name("type")
|
|
458
|
+
if ty is not None:
|
|
459
|
+
self._type_refs(ty, it)
|
|
460
|
+
if c.type not in ("line_comment", "block_comment"):
|
|
461
|
+
attrs = []
|
|
462
|
+
|
|
463
|
+
def variants(self, body, owner_key, module, cfgs, in_test, chain):
|
|
464
|
+
attrs = []
|
|
465
|
+
for c in body.children:
|
|
466
|
+
if c.type == "attribute_item":
|
|
467
|
+
attrs.append(c)
|
|
468
|
+
continue
|
|
469
|
+
if c.type == "enum_variant":
|
|
470
|
+
nm = c.child_by_field_name("name")
|
|
471
|
+
if nm is not None:
|
|
472
|
+
a, cadd, _ = self._attr_info(attrs)
|
|
473
|
+
it = self._add("variant", text(self.src, nm), f"{owner_key}::{text(self.src, nm)}", c, module, None, a, cfgs + cadd, in_test, [],
|
|
474
|
+
parent=owner_key)
|
|
475
|
+
it.pub_chain = chain
|
|
476
|
+
b = c.child_by_field_name("body")
|
|
477
|
+
if b is not None and b.type == "field_declaration_list":
|
|
478
|
+
# struct-like variant `V { a: T }`: fields keyed under the variant
|
|
479
|
+
self.fields(b, it.key, module, cfgs + cadd, in_test, chain)
|
|
480
|
+
elif b is not None:
|
|
481
|
+
self._type_refs(b, it)
|
|
482
|
+
if c.type not in ("line_comment", "block_comment"):
|
|
483
|
+
attrs = []
|
|
484
|
+
|
|
485
|
+
def macro_item(self, n, module, cfgs, in_test, pub_chain=True, ctx=None):
|
|
486
|
+
"""Item-level macro invocations are not expanded, but a very common pattern wraps ordinary items in a
|
|
487
|
+
macro (`cfg_rt! { pub mod runtime; pub fn spawn() {} }`, `cfg_if!`-like wrappers, `impl X { cfg_io! { fn f() } }`).
|
|
488
|
+
When the macro body parses cleanly as Rust items, index those items in place (same byte offsets, so lines and
|
|
489
|
+
SCIP positions match). Bodies that are not plain items (`lazy_static!`, `bitflags!`, DSLs) are skipped."""
|
|
490
|
+
tt = next((c for c in n.children if c.type == "token_tree"), None)
|
|
491
|
+
if tt is None or tt.end_byte - tt.start_byte < 4 or getattr(self, "_macro_depth", 0) > 3:
|
|
492
|
+
return
|
|
493
|
+
a, b = tt.start_byte + 1, tt.end_byte - 1
|
|
494
|
+
src = self.src
|
|
495
|
+
if len(src) > 4_000_000:
|
|
496
|
+
return
|
|
497
|
+
masked = _BLANK.sub(b" ", src[:a]) + src[a:b] + _BLANK.sub(b" ", src[b:])
|
|
498
|
+
root = parser("rust").parse(masked).root_node
|
|
499
|
+
if root.has_error or not any(c.type in ITEM_TYPES for c in root.children):
|
|
500
|
+
return
|
|
501
|
+
self.f.macro_bodies += 1
|
|
502
|
+
self._macro_depth = getattr(self, "_macro_depth", 0) + 1
|
|
503
|
+
try:
|
|
504
|
+
self.container(root, module, cfgs, in_test, pub_chain, ctx)
|
|
505
|
+
finally:
|
|
506
|
+
self._macro_depth -= 1
|
|
507
|
+
|
|
508
|
+
def _type_refs(self, node, owner: RItem):
|
|
509
|
+
for x in _iter(node):
|
|
510
|
+
if x.type == "type_identifier":
|
|
511
|
+
self.f.type_refs.append((owner.key, text(self.src, x), x.start_point[0] + 1, x.start_point[1]))
|
|
512
|
+
|
|
513
|
+
# ---------------------------------------------------------------- bodies
|
|
514
|
+
def body(self, body, owner: RItem, module, cfgs, in_test):
|
|
515
|
+
src = self.src
|
|
516
|
+
stack = [body]
|
|
517
|
+
fn_ctx = {"kind": "fn", "prefix": owner.key}
|
|
518
|
+
while stack:
|
|
519
|
+
x = stack.pop()
|
|
520
|
+
t = x.type
|
|
521
|
+
if t in ITEM_TYPES and x is not body:
|
|
522
|
+
attrs = []
|
|
523
|
+
p = x.prev_sibling
|
|
524
|
+
while p is not None and p.type in ("attribute_item", "line_comment", "block_comment"):
|
|
525
|
+
if p.type == "attribute_item":
|
|
526
|
+
attrs.insert(0, p)
|
|
527
|
+
p = p.prev_sibling
|
|
528
|
+
self.item(x, attrs, [], module, cfgs, in_test, False, fn_ctx)
|
|
529
|
+
continue
|
|
530
|
+
if t == "attribute_item":
|
|
531
|
+
p, a = _attr_path(src, x)
|
|
532
|
+
if p == "cfg":
|
|
533
|
+
nxt = x.next_sibling
|
|
534
|
+
while nxt is not None and nxt.type in ("attribute_item", "line_comment", "block_comment"):
|
|
535
|
+
nxt = nxt.next_sibling
|
|
536
|
+
if nxt is not None:
|
|
537
|
+
pred = a[1:-1] if a.startswith("(") else a
|
|
538
|
+
# `#[cfg(unix)] Pattern => { ... }`: the attribute is the arm's first child and gates the whole arm
|
|
539
|
+
end = x.parent if x.parent is not None and x.parent.type == "match_arm" else nxt
|
|
540
|
+
self.f.cfg_regions.append((nxt.start_point[0] + 1, end.end_point[0] + 1, pred, x.start_point[0] + 1, owner.key))
|
|
541
|
+
continue
|
|
542
|
+
if t == "unsafe_block":
|
|
543
|
+
self.f.unsafe_blocks.append((x.start_point[0] + 1, owner.key))
|
|
544
|
+
elif t == "macro_invocation":
|
|
545
|
+
mname = text(src, x.children[0]) if x.children else ""
|
|
546
|
+
short = mname.split("::")[-1]
|
|
547
|
+
if short in ("env", "option_env"):
|
|
548
|
+
tt = next((c for c in x.children if c.type == "token_tree"), None)
|
|
549
|
+
sl = next((c for c in tt.children if c.type == "string_literal"), None) if tt is not None else None
|
|
550
|
+
k = string_value(src, sl)
|
|
551
|
+
if k:
|
|
552
|
+
self.f.env.append((k, x.start_point[0] + 1, owner.key, f"{short}!"))
|
|
553
|
+
self.f.calls.append((owner.key, "macro", mname, short, x.start_point[0] + 1, x.start_point[1]))
|
|
554
|
+
if short not in ("env", "option_env", "include_str", "include_bytes", "concat", "stringify", "cfg"):
|
|
555
|
+
tt = next((c for c in x.children if c.type == "token_tree"), None)
|
|
556
|
+
if tt is not None:
|
|
557
|
+
self._tt_calls(tt, owner)
|
|
558
|
+
elif t == "call_expression":
|
|
559
|
+
self.call(x, owner)
|
|
560
|
+
elif t == "type_identifier":
|
|
561
|
+
self.f.type_refs.append((owner.key, text(src, x), x.start_point[0] + 1, x.start_point[1]))
|
|
562
|
+
elif t == "struct_expression":
|
|
563
|
+
nm = x.child_by_field_name("name")
|
|
564
|
+
if nm is not None:
|
|
565
|
+
s = text(src, nm)
|
|
566
|
+
self.f.type_refs.append((owner.key, s.split("::")[-1].split("<")[0], nm.end_point[0] + 1, max(0, nm.end_point[1] - len(s.split("::")[-1]))))
|
|
567
|
+
stack.extend(reversed(x.children))
|
|
568
|
+
|
|
569
|
+
def _tt_calls(self, tt, owner: RItem):
|
|
570
|
+
"""Macro arguments are unparsed token trees (`assert_eq!(f(x), 1)`, `println!("{}", a.b())`): recover calls
|
|
571
|
+
as `ident (` / `a::b (` / `.m (` token sequences. Heuristic by nature; SCIP mode resolves these exactly."""
|
|
572
|
+
src = self.src
|
|
573
|
+
stack = [tt]
|
|
574
|
+
while stack:
|
|
575
|
+
node = stack.pop()
|
|
576
|
+
ch = node.children
|
|
577
|
+
for i, c in enumerate(ch):
|
|
578
|
+
if c.type == "token_tree":
|
|
579
|
+
stack.append(c)
|
|
580
|
+
continue
|
|
581
|
+
if c.type != "identifier" or i + 1 >= len(ch) or ch[i + 1].type != "token_tree" or not text(src, ch[i + 1]).startswith("("):
|
|
582
|
+
continue
|
|
583
|
+
name = text(src, c)
|
|
584
|
+
prev = ch[i - 1] if i > 0 else None
|
|
585
|
+
if prev is not None and text(src, prev) == ".":
|
|
586
|
+
self.f.calls.append((owner.key, "method", name, name, c.start_point[0] + 1, c.start_point[1]))
|
|
587
|
+
continue
|
|
588
|
+
parts, j = [name], i - 1
|
|
589
|
+
while j >= 1 and text(src, ch[j]) == "::" and ch[j - 1].type in ("identifier", "self", "super", "crate"):
|
|
590
|
+
parts.insert(0, text(src, ch[j - 1]))
|
|
591
|
+
j -= 2
|
|
592
|
+
path = "::".join(parts)
|
|
593
|
+
self.f.calls.append((owner.key, "path", path, name, c.start_point[0] + 1, c.start_point[1]))
|
|
594
|
+
if ENV_CALLS.search(path):
|
|
595
|
+
sl = next((y for y in ch[i + 1].children if y.type == "string_literal"), None)
|
|
596
|
+
k = string_value(src, sl) if sl is not None else None
|
|
597
|
+
if k:
|
|
598
|
+
self.f.env.append((k, c.start_point[0] + 1, owner.key, path))
|
|
599
|
+
|
|
600
|
+
def call(self, x, owner: RItem):
|
|
601
|
+
src = self.src
|
|
602
|
+
fn = x.child_by_field_name("function")
|
|
603
|
+
args = x.child_by_field_name("arguments")
|
|
604
|
+
if fn is None:
|
|
605
|
+
return
|
|
606
|
+
if fn.type == "generic_function":
|
|
607
|
+
fn = fn.child_by_field_name("function") or fn
|
|
608
|
+
ft = text(src, fn)
|
|
609
|
+
if fn.type in ("identifier", "scoped_identifier"):
|
|
610
|
+
name = ft.split("::")[-1]
|
|
611
|
+
nm = fn.child_by_field_name("name") if fn.type == "scoped_identifier" else fn
|
|
612
|
+
self.f.calls.append((owner.key, "path", re.sub(r"\s+", "", ft), name, nm.start_point[0] + 1, nm.start_point[1]))
|
|
613
|
+
if ENV_CALLS.search(re.sub(r"\s+", "", ft)) and args is not None and args.named_children:
|
|
614
|
+
a0 = args.named_children[0]
|
|
615
|
+
k = string_value(src, a0)
|
|
616
|
+
if k is None and a0.type == "identifier":
|
|
617
|
+
k = self.consts.get(text(src, a0))
|
|
618
|
+
if k is None and a0.type == "scoped_identifier":
|
|
619
|
+
k = self.consts.get(text(src, a0).split("::")[-1])
|
|
620
|
+
if k:
|
|
621
|
+
self.f.env.append((k, x.start_point[0] + 1, owner.key, re.sub(r"\s+", "", ft)))
|
|
622
|
+
elif fn.type == "field_expression":
|
|
623
|
+
fld = fn.child_by_field_name("field")
|
|
624
|
+
if fld is not None:
|
|
625
|
+
name = text(src, fld)
|
|
626
|
+
self.f.calls.append((owner.key, "method", name, name, fld.start_point[0] + 1, fld.start_point[1]))
|
|
627
|
+
if name == "route" and args is not None and len(args.named_children) >= 2:
|
|
628
|
+
self.route(args, owner)
|
|
629
|
+
|
|
630
|
+
def route(self, args, owner: RItem):
|
|
631
|
+
"""axum `.route("/p", get(h).post(h2))`, actix `.route("/p", web::get().to(h))`."""
|
|
632
|
+
src = self.src
|
|
633
|
+
p = string_value(src, args.named_children[0])
|
|
634
|
+
if p is None:
|
|
635
|
+
return
|
|
636
|
+
found = []
|
|
637
|
+
for x in _iter(args.named_children[1]):
|
|
638
|
+
if x.type != "call_expression":
|
|
639
|
+
continue
|
|
640
|
+
fn = x.child_by_field_name("function")
|
|
641
|
+
a = x.child_by_field_name("arguments")
|
|
642
|
+
if fn is None:
|
|
643
|
+
continue
|
|
644
|
+
verb = text(src, fn.child_by_field_name("field")) if fn.type == "field_expression" and fn.child_by_field_name("field") is not None else text(src, fn).split("::")[-1]
|
|
645
|
+
if verb not in HTTP_VERBS and verb != "to":
|
|
646
|
+
# `use axum::routing::get as aget;` -> aget(h)
|
|
647
|
+
alias = self.f.use_map.get((owner.module, verb))
|
|
648
|
+
if alias and alias.split("::")[-1] in HTTP_VERBS:
|
|
649
|
+
verb = alias.split("::")[-1]
|
|
650
|
+
if verb in HTTP_VERBS and a is not None and a.named_children:
|
|
651
|
+
h = a.named_children[0]
|
|
652
|
+
if h.type in ("identifier", "scoped_identifier"):
|
|
653
|
+
hn = h.child_by_field_name("name") if h.type == "scoped_identifier" else h
|
|
654
|
+
found.append((verb.upper(), text(src, h), hn.start_point[0] + 1, hn.start_point[1], "axum"))
|
|
655
|
+
elif verb == "to" and a is not None and a.named_children and fn.type == "field_expression":
|
|
656
|
+
h = a.named_children[0]
|
|
657
|
+
inner = fn.child_by_field_name("value")
|
|
658
|
+
v = None
|
|
659
|
+
for y in _iter(inner) if inner is not None else []:
|
|
660
|
+
if y.type in ("identifier", "field_identifier") and text(src, y) in HTTP_VERBS:
|
|
661
|
+
v = text(src, y)
|
|
662
|
+
if h.type in ("identifier", "scoped_identifier"):
|
|
663
|
+
hn = h.child_by_field_name("name") if h.type == "scoped_identifier" else h
|
|
664
|
+
found.append(((v or "any").upper(), text(src, h), hn.start_point[0] + 1, hn.start_point[1], "actix"))
|
|
665
|
+
for verb, h, line, col, fw in found:
|
|
666
|
+
self.f.routes.append((verb, p, h, line, col, owner.key, fw))
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def _iter(n):
|
|
670
|
+
stack = [n]
|
|
671
|
+
while stack:
|
|
672
|
+
x = stack.pop()
|
|
673
|
+
yield x
|
|
674
|
+
stack.extend(reversed(x.children))
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def extract(path: str, src: bytes, crate: str, module: str, cfgs: list, in_test: bool, pub_chain: bool) -> RFile:
|
|
678
|
+
return Extractor(path, src, crate, module, cfgs, in_test, pub_chain).run()
|