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.
Files changed (174) hide show
  1. cg_code_graph-0.10.1.dist-info/METADATA +678 -0
  2. cg_code_graph-0.10.1.dist-info/RECORD +174 -0
  3. cg_code_graph-0.10.1.dist-info/WHEEL +5 -0
  4. cg_code_graph-0.10.1.dist-info/entry_points.txt +3 -0
  5. cg_code_graph-0.10.1.dist-info/licenses/LICENSE +21 -0
  6. cg_code_graph-0.10.1.dist-info/top_level.txt +1 -0
  7. codegraph/__init__.py +2 -0
  8. codegraph/aitools.py +129 -0
  9. codegraph/apps.py +76 -0
  10. codegraph/blindspots.py +428 -0
  11. codegraph/bridges.py +1701 -0
  12. codegraph/cli.py +725 -0
  13. codegraph/concepts.py +362 -0
  14. codegraph/config.py +559 -0
  15. codegraph/core/__init__.py +0 -0
  16. codegraph/core/cache.py +375 -0
  17. codegraph/core/detect.py +80 -0
  18. codegraph/core/extractors.py +187 -0
  19. codegraph/core/fsutil.py +61 -0
  20. codegraph/core/generated.py +575 -0
  21. codegraph/core/model.py +174 -0
  22. codegraph/core/paths.py +175 -0
  23. codegraph/core/plugin.py +160 -0
  24. codegraph/core/store.py +80 -0
  25. codegraph/core/syntax_errors.py +132 -0
  26. codegraph/coverage.py +928 -0
  27. codegraph/doctor.py +453 -0
  28. codegraph/external.py +613 -0
  29. codegraph/indexer.py +336 -0
  30. codegraph/link.py +434 -0
  31. codegraph/lint_async.py +524 -0
  32. codegraph/mcp_server.py +1303 -0
  33. codegraph/parity.py +473 -0
  34. codegraph/parity_structure.py +307 -0
  35. codegraph/payload.py +321 -0
  36. codegraph/plans.py +1285 -0
  37. codegraph/platform_scan.py +643 -0
  38. codegraph/platforms.py +1369 -0
  39. codegraph/plugins/__init__.py +0 -0
  40. codegraph/plugins/cfamily/__init__.py +0 -0
  41. codegraph/plugins/cfamily/plugin.py +930 -0
  42. codegraph/plugins/cfamily/syntax.py +881 -0
  43. codegraph/plugins/dart/__init__.py +0 -0
  44. codegraph/plugins/dart/bridges.py +345 -0
  45. codegraph/plugins/dart/extractor/bin/extract.dart +717 -0
  46. codegraph/plugins/dart/extractor/pubspec.lock +149 -0
  47. codegraph/plugins/dart/extractor/pubspec.yaml +7 -0
  48. codegraph/plugins/dart/http.py +904 -0
  49. codegraph/plugins/dart/models.py +308 -0
  50. codegraph/plugins/dart/plugin.py +625 -0
  51. codegraph/plugins/dart/program.py +907 -0
  52. codegraph/plugins/django/__init__.py +0 -0
  53. codegraph/plugins/django/extras.py +378 -0
  54. codegraph/plugins/django/models.py +508 -0
  55. codegraph/plugins/django/plugin.py +728 -0
  56. codegraph/plugins/django/schemas.py +339 -0
  57. codegraph/plugins/django/shapes.py +216 -0
  58. codegraph/plugins/django/urls.py +603 -0
  59. codegraph/plugins/express/__init__.py +0 -0
  60. codegraph/plugins/express/plugin.py +428 -0
  61. codegraph/plugins/flutter/__init__.py +0 -0
  62. codegraph/plugins/flutter/plugin.py +538 -0
  63. codegraph/plugins/kotlin/__init__.py +0 -0
  64. codegraph/plugins/kotlin/exact.py +457 -0
  65. codegraph/plugins/kotlin/plugin.py +1961 -0
  66. codegraph/plugins/kotlin/reparse.py +234 -0
  67. codegraph/plugins/laravel/__init__.py +0 -0
  68. codegraph/plugins/laravel/broadcast.py +351 -0
  69. codegraph/plugins/laravel/plugin.py +863 -0
  70. codegraph/plugins/laravel/tests.py +262 -0
  71. codegraph/plugins/laravel/values.py +728 -0
  72. codegraph/plugins/native/__init__.py +0 -0
  73. codegraph/plugins/native/gates.py +286 -0
  74. codegraph/plugins/native/runner.py +183 -0
  75. codegraph/plugins/native/scipread.py +194 -0
  76. codegraph/plugins/native/ts.py +54 -0
  77. codegraph/plugins/nest/__init__.py +0 -0
  78. codegraph/plugins/nest/plugin.py +654 -0
  79. codegraph/plugins/nextjs/__init__.py +0 -0
  80. codegraph/plugins/nextjs/plugin.py +336 -0
  81. codegraph/plugins/nuxt/__init__.py +0 -0
  82. codegraph/plugins/nuxt/plugin.py +308 -0
  83. codegraph/plugins/php/__init__.py +0 -0
  84. codegraph/plugins/php/extractor/composer.json +5 -0
  85. codegraph/plugins/php/extractor/composer.lock +76 -0
  86. codegraph/plugins/php/extractor/extract.php +743 -0
  87. codegraph/plugins/php/gating.py +573 -0
  88. codegraph/plugins/php/plugin.py +668 -0
  89. codegraph/plugins/php/strings.py +197 -0
  90. codegraph/plugins/python/__init__.py +0 -0
  91. codegraph/plugins/python/aitools.py +664 -0
  92. codegraph/plugins/python/external.py +245 -0
  93. codegraph/plugins/python/fields.py +107 -0
  94. codegraph/plugins/python/plugin.py +1733 -0
  95. codegraph/plugins/python/refs.py +485 -0
  96. codegraph/plugins/python/roots.py +412 -0
  97. codegraph/plugins/python/socketio.py +210 -0
  98. codegraph/plugins/python/subproc.py +864 -0
  99. codegraph/plugins/python/tests.py +1040 -0
  100. codegraph/plugins/python/values.py +179 -0
  101. codegraph/plugins/pyweb/__init__.py +0 -0
  102. codegraph/plugins/pyweb/plugin.py +1334 -0
  103. codegraph/plugins/pyweb/values.py +68 -0
  104. codegraph/plugins/rust/__init__.py +0 -0
  105. codegraph/plugins/rust/cargo.py +226 -0
  106. codegraph/plugins/rust/plugin.py +980 -0
  107. codegraph/plugins/rust/syntax.py +678 -0
  108. codegraph/plugins/scip/__init__.py +0 -0
  109. codegraph/plugins/scip/importer.py +129 -0
  110. codegraph/plugins/scip/scip.proto +962 -0
  111. codegraph/plugins/scip/scip_pb2.py +97 -0
  112. codegraph/plugins/stubs/__init__.py +0 -0
  113. codegraph/plugins/stubs/plugins.py +38 -0
  114. codegraph/plugins/swift/__init__.py +0 -0
  115. codegraph/plugins/swift/baseurl.py +109 -0
  116. codegraph/plugins/swift/exact.py +415 -0
  117. codegraph/plugins/swift/indexstore.py +209 -0
  118. codegraph/plugins/swift/packages.py +174 -0
  119. codegraph/plugins/swift/plugin.py +2890 -0
  120. codegraph/plugins/ts/__init__.py +0 -0
  121. codegraph/plugins/ts/baseurl.py +185 -0
  122. codegraph/plugins/ts/extractor/extract.mjs +2652 -0
  123. codegraph/plugins/ts/extractor/fw.mjs +685 -0
  124. codegraph/plugins/ts/extractor/package-lock.json +205 -0
  125. codegraph/plugins/ts/extractor/package.json +9 -0
  126. codegraph/plugins/ts/plugin.py +480 -0
  127. codegraph/plugins/tsweb/__init__.py +0 -0
  128. codegraph/plugins/tsweb/common.py +290 -0
  129. codegraph/plugins/tsweb/data.py +276 -0
  130. codegraph/presets/__init__.py +146 -0
  131. codegraph/presets/c_cpp.yaml +9 -0
  132. codegraph/presets/common.yaml +66 -0
  133. codegraph/presets/dart.yaml +9 -0
  134. codegraph/presets/django-ninja.yaml +15 -0
  135. codegraph/presets/django.yaml +25 -0
  136. codegraph/presets/djangorestframework.yaml +17 -0
  137. codegraph/presets/express.yaml +17 -0
  138. codegraph/presets/kotlin.yaml +11 -0
  139. codegraph/presets/laravel.yaml +40 -0
  140. codegraph/presets/nest.yaml +11 -0
  141. codegraph/presets/nextjs.yaml +15 -0
  142. codegraph/presets/nuxt.yaml +9 -0
  143. codegraph/presets/php.yaml +5 -0
  144. codegraph/presets/python.yaml +10 -0
  145. codegraph/presets/rust.yaml +5 -0
  146. codegraph/presets/swift.yaml +10 -0
  147. codegraph/presets/typescript.yaml +13 -0
  148. codegraph/process_runs.py +328 -0
  149. codegraph/protocols/__init__.py +299 -0
  150. codegraph/protocols/builtin.py +67 -0
  151. codegraph/protocols/matchers.py +144 -0
  152. codegraph/protocols/view.py +334 -0
  153. codegraph/query.py +2089 -0
  154. codegraph/realtime.py +260 -0
  155. codegraph/roundtrip.py +346 -0
  156. codegraph/routes.py +442 -0
  157. codegraph/starters.py +218 -0
  158. codegraph/tests_index.py +117 -0
  159. codegraph/viz/__init__.py +0 -0
  160. codegraph/viz/graph.py +369 -0
  161. codegraph/viz/server.py +198 -0
  162. codegraph/viz/static/app.css +148 -0
  163. codegraph/viz/static/app.js +1082 -0
  164. codegraph/viz/static/index.html +81 -0
  165. codegraph/viz/static/layered.js +237 -0
  166. codegraph/viz/static/vendor/VERSIONS.txt +4 -0
  167. codegraph/viz/static/vendor/cose-base.js +3214 -0
  168. codegraph/viz/static/vendor/cytoscape-fcose.js +1549 -0
  169. codegraph/viz/static/vendor/cytoscape.min.js +31 -0
  170. codegraph/viz/static/vendor/layout-base.js +5230 -0
  171. codegraph/viz/tools/package-lock.json +303 -0
  172. codegraph/viz/tools/package.json +7 -0
  173. codegraph/viz/tools/shoot.mjs +165 -0
  174. 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()