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,415 @@
|
|
|
1
|
+
"""Swift exact layer: compiler-resolved call edges from the Swift index store.
|
|
2
|
+
|
|
3
|
+
The index store is what `swift build --enable-index-store` (SwiftPM) or Xcode (DerivedData `Index.noindex/DataStore`)
|
|
4
|
+
writes; it is read through the toolchain's libIndexStore (indexstore.py). Index-store definitions are matched to the
|
|
5
|
+
syntax layer's declarations by file, line and name, so node ids stay those of the heuristic layer; the heuristic
|
|
6
|
+
CALLS / INSTANTIATES edges of the covered files are replaced by the compiler's call occurrences (the caller comes from
|
|
7
|
+
the `calledBy` / `containedBy` relation).
|
|
8
|
+
|
|
9
|
+
Where the store comes from, in order:
|
|
10
|
+
CODEGRAPH_SWIFT_INDEX_STORE=/path/to/store an existing store (CI, Xcode DerivedData, a previous build)
|
|
11
|
+
CODEGRAPH_SWIFT_INDEX=1 run `swift build --enable-index-store` for a SwiftPM package (the build
|
|
12
|
+
runs the package manifest and plugins, hence opt-in), with the build
|
|
13
|
+
directory under the codegraph cache and a source fingerprint so an
|
|
14
|
+
unchanged package is not rebuilt
|
|
15
|
+
Without one of those (or without a toolchain / libIndexStore) the heuristic layer stays and coverage says why.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import glob
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import time
|
|
26
|
+
from collections import defaultdict
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from ...core.model import EXACT, HEURISTIC
|
|
30
|
+
from ...core import cache, fsutil
|
|
31
|
+
from ..native import runner
|
|
32
|
+
from . import indexstore as ix
|
|
33
|
+
|
|
34
|
+
# index-store names of accessor definitions (`getter:label`, `didSet:items`): owned by their property
|
|
35
|
+
ACCESSORS = ("getter:", "setter:", "_modify:", "modify:", "read:", "_read:", "willSet:", "didSet:")
|
|
36
|
+
ACCESSOR_ATTR = {"getter": "get", "setter": "set", "willSet": "willSet", "didSet": "didSet", "modify": "modify",
|
|
37
|
+
"_modify": "modify"}
|
|
38
|
+
|
|
39
|
+
CALL_KINDS = ("CALLS", "INSTANTIATES")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def find_swift() -> str | None:
|
|
43
|
+
env = os.environ.get("CODEGRAPH_SWIFT")
|
|
44
|
+
if env:
|
|
45
|
+
return env if (Path(env).exists() or shutil.which(env)) else None
|
|
46
|
+
w = shutil.which("swift")
|
|
47
|
+
if w:
|
|
48
|
+
return w
|
|
49
|
+
for pat in (str(Path.home() / "tools" / "swift-*" / "usr" / "bin" / "swift"), "/usr/share/swift/usr/bin/swift",
|
|
50
|
+
"/opt/swift/usr/bin/swift"):
|
|
51
|
+
hits = sorted(glob.glob(pat))
|
|
52
|
+
if hits:
|
|
53
|
+
return hits[-1]
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _store_dirs(build: Path) -> list[Path]:
|
|
58
|
+
return [Path(p) for p in sorted(glob.glob(str(build / "*" / "debug" / "index" / "store")))
|
|
59
|
+
+ sorted(glob.glob(str(build / "debug" / "index" / "store")))]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def find_store(project, files: list[str]) -> tuple[Path | None, dict]:
|
|
63
|
+
"""(index store directory or None, info); info["status"] explains a missing store."""
|
|
64
|
+
pre = os.environ.get("CODEGRAPH_SWIFT_INDEX_STORE")
|
|
65
|
+
swift = find_swift()
|
|
66
|
+
lib = ix.find_lib(swift)
|
|
67
|
+
if pre:
|
|
68
|
+
if not Path(pre).is_dir():
|
|
69
|
+
return None, {"status": f"CODEGRAPH_SWIFT_INDEX_STORE={pre} is not a directory"}
|
|
70
|
+
if not lib:
|
|
71
|
+
return None, {"status": "libIndexStore not found (a Swift toolchain or CODEGRAPH_LIBINDEXSTORE is needed "
|
|
72
|
+
"to read the index store)"}
|
|
73
|
+
return Path(pre), {"source": "CODEGRAPH_SWIFT_INDEX_STORE", "path": pre, "lib": lib}
|
|
74
|
+
root = Path(project.root)
|
|
75
|
+
if not (root / "Package.swift").is_file():
|
|
76
|
+
return None, {"status": "no Package.swift at the project root (Xcode projects: set CODEGRAPH_SWIFT_INDEX_STORE "
|
|
77
|
+
"to DerivedData/<app>/Index.noindex/DataStore)"}
|
|
78
|
+
if not swift:
|
|
79
|
+
return None, {"status": "no Swift toolchain (swift not on PATH; CODEGRAPH_SWIFT)"}
|
|
80
|
+
if not lib:
|
|
81
|
+
return None, {"status": f"libIndexStore not found next to {swift} (CODEGRAPH_LIBINDEXSTORE)"}
|
|
82
|
+
if os.environ.get("CODEGRAPH_SWIFT_INDEX") != "1":
|
|
83
|
+
return None, {"status": "Swift toolchain found but the package was not built: `swift build` runs the package "
|
|
84
|
+
"manifest and plugins; set CODEGRAPH_SWIFT_INDEX=1, or CODEGRAPH_SWIFT_INDEX_STORE to an "
|
|
85
|
+
"existing store (Xcode DerivedData)",
|
|
86
|
+
"toolchain": swift}
|
|
87
|
+
return _build(root, files, swift, lib)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _build(root: Path, files: list[str], swift: str, lib: str) -> tuple[Path | None, dict]:
|
|
91
|
+
"""`swift build --enable-index-store` into a per-project build directory under the cache; skipped when the sources
|
|
92
|
+
and manifests are unchanged since the last successful build."""
|
|
93
|
+
srcs = list(files) + [f for f in ("Package.swift", "Package.resolved") if (root / f).exists()]
|
|
94
|
+
srcs += [p.name for p in root.glob("Package@swift-*.swift")]
|
|
95
|
+
key = runner.fingerprint(root, srcs, f"swift-index|{runner.tool_version(swift, ('--version',))}")
|
|
96
|
+
build = cache.subdir("swift-build") / cache.swift_build_key(root)
|
|
97
|
+
stamp = build / "codegraph-stamp.json"
|
|
98
|
+
info = {"source": "swift build", "toolchain": swift, "lib": lib, "build_path": str(build)}
|
|
99
|
+
timeout = int(os.environ.get("CODEGRAPH_INDEXER_TIMEOUT", "3600"))
|
|
100
|
+
use_cache = os.environ.get("CODEGRAPH_NO_CACHE") != "1"
|
|
101
|
+
with runner.key_lock("swift", build.name, timeout) as waited: # one build per build directory at a time
|
|
102
|
+
if waited:
|
|
103
|
+
info["lock_wait_seconds"] = waited
|
|
104
|
+
try:
|
|
105
|
+
done = json.loads(stamp.read_text()) if stamp.exists() else {}
|
|
106
|
+
except (OSError, ValueError):
|
|
107
|
+
done = {}
|
|
108
|
+
stores = _store_dirs(build)
|
|
109
|
+
if use_cache and done.get("key") == key and stores:
|
|
110
|
+
info["cache"] = "hit"
|
|
111
|
+
return stores[0], info
|
|
112
|
+
info["cache"] = "miss"
|
|
113
|
+
build.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
cache.note_project(root)
|
|
115
|
+
cmd = [swift, "build", "--enable-index-store", "--build-path", str(build)]
|
|
116
|
+
info["command"] = " ".join(cmd[1:])
|
|
117
|
+
t0 = time.time()
|
|
118
|
+
try:
|
|
119
|
+
r = subprocess.run(cmd, cwd=root, capture_output=True, text=True, timeout=timeout)
|
|
120
|
+
except subprocess.TimeoutExpired:
|
|
121
|
+
info["error"] = f"timeout after {timeout}s"
|
|
122
|
+
info["status"] = f"swift build failed ({info['error']})"
|
|
123
|
+
return None, info
|
|
124
|
+
except OSError as e:
|
|
125
|
+
info["error"] = str(e)
|
|
126
|
+
info["status"] = f"swift build failed ({e})"
|
|
127
|
+
return None, info
|
|
128
|
+
info["seconds"] = round(time.time() - t0, 2)
|
|
129
|
+
out = (r.stdout or "") + "\n" + (r.stderr or "")
|
|
130
|
+
info["stderr_tail"] = [x for x in out.strip().splitlines()[-8:]]
|
|
131
|
+
stores = _store_dirs(build)
|
|
132
|
+
if r.returncode != 0:
|
|
133
|
+
errs = [x.strip() for x in out.splitlines() if re.search(r"\berror:", x)]
|
|
134
|
+
info["error"] = f"exit {r.returncode}"
|
|
135
|
+
info["notes"] = list(dict.fromkeys(errs))[:6]
|
|
136
|
+
first = re.sub(r"^/\S*/", "", errs[0].replace(str(root) + os.sep, ""))[:160] if errs else ""
|
|
137
|
+
if not stores:
|
|
138
|
+
info["status"] = "swift build failed (" + info["error"] + (f": {first}" if first else "") + ")"
|
|
139
|
+
return None, info
|
|
140
|
+
# a partial build still indexed the modules that compiled
|
|
141
|
+
info["partial"] = True
|
|
142
|
+
info["build_error"] = first
|
|
143
|
+
else:
|
|
144
|
+
stamp.write_text(json.dumps({"key": key, "cache_version": fsutil.CACHE_VERSION}))
|
|
145
|
+
return (stores[0] if stores else None), info
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ExactLayer:
|
|
149
|
+
def __init__(self, plugin):
|
|
150
|
+
self.p = plugin
|
|
151
|
+
self.b = plugin.b
|
|
152
|
+
|
|
153
|
+
def _all_decls(self):
|
|
154
|
+
return list(self.p.decls.values())
|
|
155
|
+
|
|
156
|
+
def apply(self, store: Path, lib: str, root: Path, files: list[str], st: dict) -> bool:
|
|
157
|
+
occ_by_path = ix.load(store, None, lib)
|
|
158
|
+
rels = self._map_paths(occ_by_path, root, files)
|
|
159
|
+
st["index_files"] = len(rels)
|
|
160
|
+
if not rels:
|
|
161
|
+
st["index_units_outside_project"] = len(occ_by_path)
|
|
162
|
+
return False
|
|
163
|
+
by_line = defaultdict(list)
|
|
164
|
+
for d in self._all_decls():
|
|
165
|
+
by_line[(d.file, d.line)].append(d)
|
|
166
|
+
usr_node: dict[str, str] = {}
|
|
167
|
+
ctor_type: dict[str, str] = {}
|
|
168
|
+
matched = unmatched = 0
|
|
169
|
+
accessor_of: dict[str, str] = {}
|
|
170
|
+
accessor_kind: dict[str, str] = {} # accessor usr -> "getter" | "setter" | "didSet" | ...
|
|
171
|
+
prop_def: dict[str, str] = {}
|
|
172
|
+
prop_how: dict[str, str] = {} # property usr -> "computed" | "observed" | "lazy" (property nodes)
|
|
173
|
+
parent_of: dict[str, str] = {} # definition usr -> enclosing type / extension usr (childOf)
|
|
174
|
+
ext_of: dict[str, str] = {} # extension usr -> extended type usr
|
|
175
|
+
for rel, occs in rels.items():
|
|
176
|
+
for o in occs:
|
|
177
|
+
if not o.roles & ix.DEFINITION:
|
|
178
|
+
for rr, ru, _rn, _rk in o.rels:
|
|
179
|
+
if rr & ix.REL_EXTENDEDBY and o.kind in ix.TYPES: # `extension DispatchQueue { }`
|
|
180
|
+
ext_of[ru] = o.usr
|
|
181
|
+
if o.usr not in usr_node:
|
|
182
|
+
nid = self._decl(rel, o.line, o.name, ("class",), by_line) or \
|
|
183
|
+
(f"class:{o.name}" if f"class:{o.name}" in self.b.nodes else None)
|
|
184
|
+
if nid:
|
|
185
|
+
usr_node[o.usr] = nid
|
|
186
|
+
continue
|
|
187
|
+
for rr, ru, _rn, _rk in o.rels:
|
|
188
|
+
if rr & ix.REL_CHILDOF:
|
|
189
|
+
parent_of.setdefault(o.usr, ru)
|
|
190
|
+
if o.kind in ("instancemethod", "function", "staticmethod", "classmethod") and \
|
|
191
|
+
o.name.startswith(ACCESSORS):
|
|
192
|
+
for rr, ru, _rn, _rk in o.rels:
|
|
193
|
+
if rr & ix.REL_ACCESSOROF:
|
|
194
|
+
accessor_of[o.usr] = ru
|
|
195
|
+
accessor_kind[o.usr] = o.name.split(":", 1)[0]
|
|
196
|
+
continue
|
|
197
|
+
if o.kind in ("instanceproperty", "staticproperty", "classproperty", "variable"):
|
|
198
|
+
if o.name == "body":
|
|
199
|
+
prop_def[o.usr] = self._decl(rel, o.line, "body", ("method",), by_line) or ""
|
|
200
|
+
else:
|
|
201
|
+
d = self._prop_decl(rel, o.line, o.name, by_line)
|
|
202
|
+
if d is not None: # a computed / observed / lazy property node (#72)
|
|
203
|
+
prop_def[o.usr] = d.id
|
|
204
|
+
prop_how[o.usr] = next(m for m in ("computed", "observed", "lazy") if m in d.modifiers)
|
|
205
|
+
continue
|
|
206
|
+
if o.kind not in ix.CALLABLE and o.kind not in ix.TYPES:
|
|
207
|
+
continue
|
|
208
|
+
base = "init" if o.kind == "constructor" else o.name.split("(")[0]
|
|
209
|
+
kinds = ("class",) if o.kind in ix.TYPES else ("function", "method")
|
|
210
|
+
nid = self._decl(rel, o.line, base, kinds, by_line)
|
|
211
|
+
if nid is None and o.kind != "constructor":
|
|
212
|
+
nid = self._decl_by_name(rel, base, kinds, o)
|
|
213
|
+
if o.kind == "constructor":
|
|
214
|
+
parent = next((ru for rr, ru, _rn, rk in o.rels if rr & ix.REL_CHILDOF), None)
|
|
215
|
+
if parent:
|
|
216
|
+
ctor_type[o.usr] = parent
|
|
217
|
+
if nid is None:
|
|
218
|
+
if o.kind != "constructor":
|
|
219
|
+
unmatched += 1
|
|
220
|
+
continue
|
|
221
|
+
usr_node[o.usr] = nid
|
|
222
|
+
matched += 1
|
|
223
|
+
for acc, prop in accessor_of.items(): # SwiftUI `body` getter -> method:<View>.body
|
|
224
|
+
if prop_def.get(prop):
|
|
225
|
+
usr_node[acc] = prop_def[prop]
|
|
226
|
+
st["index_defs_matched"] = matched
|
|
227
|
+
st["index_defs_unmatched"] = unmatched
|
|
228
|
+
def owner(usr: str, depth: int = 0) -> str | None:
|
|
229
|
+
"""Node of a caller symbol: itself, the `body` property of an accessor, else its enclosing type
|
|
230
|
+
(accessors -> property -> type; extension -> extended type)."""
|
|
231
|
+
if not usr or depth > 6:
|
|
232
|
+
return None
|
|
233
|
+
if usr in usr_node:
|
|
234
|
+
return usr_node[usr]
|
|
235
|
+
prop = accessor_of.get(usr)
|
|
236
|
+
if prop:
|
|
237
|
+
if prop_def.get(prop):
|
|
238
|
+
return prop_def[prop]
|
|
239
|
+
return owner(prop, depth + 1)
|
|
240
|
+
if usr in ext_of:
|
|
241
|
+
return owner(ext_of[usr], depth + 1)
|
|
242
|
+
return owner(parent_of.get(usr, ""), depth + 1)
|
|
243
|
+
|
|
244
|
+
covered = set(rels)
|
|
245
|
+
lambdas = defaultdict(list) # Vapor route handlers / BGTask closures own the calls inside them
|
|
246
|
+
for nid, n in self.b.nodes.items():
|
|
247
|
+
if n.lang == "swift" and n.kind == "function" and n.attrs.get("lambda") and n.file in covered and n.line:
|
|
248
|
+
lambdas[n.file].append((n.line, n.end_line or n.line, nid))
|
|
249
|
+
seen_lines = {(rel, o.line) for rel, occs in rels.items() for o in occs}
|
|
250
|
+
heur, kept = self._remove_heuristic(covered, seen_lines)
|
|
251
|
+
st["heuristic_kept_not_compiled"] = kept
|
|
252
|
+
exact = set()
|
|
253
|
+
refs = ext = 0
|
|
254
|
+
for rel, occs in rels.items():
|
|
255
|
+
fid = f"file:swift:{rel}"
|
|
256
|
+
for o in occs:
|
|
257
|
+
if o.roles & ix.DEFINITION or not (o.roles & ix.CALL or o.kind == "constructor"):
|
|
258
|
+
continue
|
|
259
|
+
if o.kind not in ix.CALLABLE:
|
|
260
|
+
continue
|
|
261
|
+
prop = accessor_of.get(o.usr) if o.name.startswith(ACCESSORS) else None
|
|
262
|
+
if o.name.startswith(ACCESSORS) and not (prop in prop_how and self._runs(accessor_kind.get(o.usr, ""),
|
|
263
|
+
prop_how[prop])):
|
|
264
|
+
continue
|
|
265
|
+
if prop is not None:
|
|
266
|
+
# a read (getter) or write (setter / modify) of a property node: a call of it (#72)
|
|
267
|
+
targets = [(prop_def[prop], "CALLS")]
|
|
268
|
+
elif o.kind == "constructor":
|
|
269
|
+
tt = ctor_type.get(o.usr, "")
|
|
270
|
+
# an initializer declared in an extension: its parent is the extension, whose node is the type's
|
|
271
|
+
tnode = usr_node.get(tt) or usr_node.get(ext_of.get(tt, ""))
|
|
272
|
+
if tnode is None:
|
|
273
|
+
ext += 1
|
|
274
|
+
continue
|
|
275
|
+
targets = [(tnode, "INSTANTIATES")]
|
|
276
|
+
if o.usr in usr_node:
|
|
277
|
+
targets.append((usr_node[o.usr], "CALLS"))
|
|
278
|
+
else:
|
|
279
|
+
t = usr_node.get(o.usr)
|
|
280
|
+
if t is None:
|
|
281
|
+
ext += 1
|
|
282
|
+
continue
|
|
283
|
+
targets = [(t, "CALLS")]
|
|
284
|
+
src = None
|
|
285
|
+
acc = {}
|
|
286
|
+
for rr, ru, _rn, _rk in o.rels:
|
|
287
|
+
if rr & (ix.REL_CALLEDBY | ix.REL_CONTAINEDBY):
|
|
288
|
+
src = owner(ru)
|
|
289
|
+
if src is not None:
|
|
290
|
+
acc = self._accessor_attr(src, accessor_kind.get(ru))
|
|
291
|
+
break
|
|
292
|
+
if src is None:
|
|
293
|
+
src = self._owner_type(rel, o.line) or fid
|
|
294
|
+
if src.startswith("class:"):
|
|
295
|
+
# the initializer of a `lazy var` belongs to the type in the store; here to the property node
|
|
296
|
+
pd = next((d for d in self.p.props_in.get(rel, ()) if d.line <= o.line <= d.end
|
|
297
|
+
and f"class:{d.cls}" == src), None)
|
|
298
|
+
src = pd.id if pd is not None else src
|
|
299
|
+
lam = min((x for x in lambdas.get(rel, ()) if x[0] <= o.line <= x[1]),
|
|
300
|
+
key=lambda x: x[1] - x[0], default=None)
|
|
301
|
+
if lam is not None:
|
|
302
|
+
src = lam[2]
|
|
303
|
+
for dst, kind in targets:
|
|
304
|
+
if (src == dst and kind == "CALLS") or src not in self.b.nodes or dst not in self.b.nodes:
|
|
305
|
+
continue
|
|
306
|
+
if prop is not None:
|
|
307
|
+
acc = {**acc, "property": "read" if accessor_kind.get(o.usr) == "getter" else "write"}
|
|
308
|
+
br = self.p.branch_at.get((rel, o.line, self.b.nodes[dst].name), {}) \
|
|
309
|
+
if kind == "INSTANTIATES" else {}
|
|
310
|
+
self.b.add_edge(src, dst, kind, rel, o.line, EXACT, source="indexstore",
|
|
311
|
+
**({"dynamic": True} if o.roles & ix.DYNAMIC else {}), **acc, **br)
|
|
312
|
+
exact.add((src, dst, kind))
|
|
313
|
+
refs += 1
|
|
314
|
+
st["index_references"] = refs
|
|
315
|
+
st["index_refs_external"] = ext
|
|
316
|
+
agree = len(heur & exact)
|
|
317
|
+
st["exact_vs_heuristic"] = {
|
|
318
|
+
"heuristic_edges": len(heur), "exact_edges": len(exact), "agree": agree,
|
|
319
|
+
"precision": round(agree / len(heur), 3) if heur else None,
|
|
320
|
+
"recall": round(agree / len(exact), 3) if exact else None,
|
|
321
|
+
# candidate edges (receiver type unknown, one per same-name method, #83) are compared on their own
|
|
322
|
+
"candidate_edges": len(self.candidates), "candidate_agree": len(self.candidates & exact)}
|
|
323
|
+
return True
|
|
324
|
+
|
|
325
|
+
def _map_paths(self, occ_by_path: dict, root: Path, files: list[str]) -> dict:
|
|
326
|
+
"""Index-store source paths -> project-relative paths: under the project root, else by the longest
|
|
327
|
+
project path suffix (a store built in another checkout or on another machine)."""
|
|
328
|
+
rootr = str(Path(root).resolve()) + os.sep
|
|
329
|
+
fset = set(files)
|
|
330
|
+
by_name = defaultdict(list)
|
|
331
|
+
for f in files:
|
|
332
|
+
by_name[f.rsplit("/", 1)[-1]].append(f)
|
|
333
|
+
out = {}
|
|
334
|
+
for p, occs in occ_by_path.items():
|
|
335
|
+
rel = None
|
|
336
|
+
if p.startswith(rootr) and p[len(rootr):] in fset:
|
|
337
|
+
rel = p[len(rootr):]
|
|
338
|
+
else:
|
|
339
|
+
cands = [f for f in by_name.get(p.rsplit("/", 1)[-1], []) if p.endswith("/" + f)]
|
|
340
|
+
rel = max(cands, key=len) if cands else None
|
|
341
|
+
if rel is not None:
|
|
342
|
+
out[rel] = occs
|
|
343
|
+
return out
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _runs(accessor: str, how: str) -> bool:
|
|
347
|
+
"""Does a use of this accessor run code of a property node? A read of a computed or lazy property, a write of
|
|
348
|
+
a computed or observed one (the getter of a stored property with observers is the plain storage)."""
|
|
349
|
+
if accessor in ("getter", "read", "_read"):
|
|
350
|
+
return how in ("computed", "lazy")
|
|
351
|
+
if accessor in ("setter", "modify", "_modify"):
|
|
352
|
+
return how in ("computed", "observed")
|
|
353
|
+
return False
|
|
354
|
+
|
|
355
|
+
def _accessor_attr(self, src: str, accessor: str | None) -> dict:
|
|
356
|
+
"""{"accessor": "didSet"} for a call inside an explicit accessor of a property node (as the heuristic pass)."""
|
|
357
|
+
name = ACCESSOR_ATTR.get(accessor or "")
|
|
358
|
+
n = self.b.nodes.get(src)
|
|
359
|
+
return {"accessor": name} if name and n is not None and name in (n.attrs.get("accessors") or ()) else {}
|
|
360
|
+
|
|
361
|
+
def _prop_decl(self, rel, line, name, by_line):
|
|
362
|
+
"""The property node of a property definition: by its line, else (attributes on the lines above the name)
|
|
363
|
+
the one of that name whose range holds the line."""
|
|
364
|
+
for d in by_line.get((rel, line), ()):
|
|
365
|
+
if d.name == name and "property" in d.modifiers:
|
|
366
|
+
return d
|
|
367
|
+
for d in self.p.props_in.get(rel, ()):
|
|
368
|
+
if d.name == name and d.line <= line <= d.end:
|
|
369
|
+
return d
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
def _decl(self, rel, line, name, kinds, by_line):
|
|
373
|
+
for d in by_line.get((rel, line), ()):
|
|
374
|
+
if d.name == name and d.kind in kinds:
|
|
375
|
+
return d.id
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
def _decl_by_name(self, rel, name, kinds, o):
|
|
379
|
+
"""An overload (one node per name) or a declaration whose name line differs from the node's line."""
|
|
380
|
+
parent = next((rn for rr, _ru, rn, _rk in o.rels if rr & ix.REL_CHILDOF), None)
|
|
381
|
+
for d in self._all_decls():
|
|
382
|
+
if d.file == rel and d.name == name and d.kind in kinds and "property" not in d.modifiers and \
|
|
383
|
+
(parent is None or (d.cls or "").split(".")[-1] == parent or d.kind == "class"):
|
|
384
|
+
if d.line <= o.line <= d.end or d.kind != "class":
|
|
385
|
+
return d.id
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
def _owner_type(self, rel, line):
|
|
389
|
+
best = None
|
|
390
|
+
for d in self._all_decls():
|
|
391
|
+
if d.file == rel and d.kind == "class" and d.line <= line <= d.end:
|
|
392
|
+
if best is None or d.end - d.line < best.end - best.line:
|
|
393
|
+
best = d
|
|
394
|
+
return best.id if best else None
|
|
395
|
+
|
|
396
|
+
def _remove_heuristic(self, covered: set, seen_lines: set) -> tuple[set, int]:
|
|
397
|
+
"""Drop the heuristic call edges of the covered files; returns their (src, dst, kind) set. An edge on a line
|
|
398
|
+
without any index occurrence is code the compiler did not see (an inactive `#if` branch on this platform):
|
|
399
|
+
it is kept, marked `via: not-compiled`, and left out of the comparison. Candidate edges (#83) go to
|
|
400
|
+
self.candidates, compared on their own."""
|
|
401
|
+
out, cand, kept = set(), set(), 0
|
|
402
|
+
for key, e in list(self.b.edges.items()):
|
|
403
|
+
if e.confidence != HEURISTIC or e.file not in covered or e.kind not in CALL_KINDS:
|
|
404
|
+
continue
|
|
405
|
+
n = self.b.nodes.get(e.src)
|
|
406
|
+
if n is None or n.lang != "swift":
|
|
407
|
+
continue
|
|
408
|
+
if (e.file, e.line) not in seen_lines:
|
|
409
|
+
e.attrs["via"] = "not-compiled"
|
|
410
|
+
kept += 1
|
|
411
|
+
continue
|
|
412
|
+
(cand if e.attrs.get("binding") == "candidate" else out).add((e.src, e.dst, e.kind))
|
|
413
|
+
del self.b.edges[key]
|
|
414
|
+
self.candidates = cand
|
|
415
|
+
return out, kept
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Read a Swift compiler index store (`swift build --enable-index-store`, Xcode DerivedData `Index.noindex/DataStore`)
|
|
2
|
+
through the toolchain's libIndexStore C API (ctypes, the function-pointer `_apply_f` variants).
|
|
3
|
+
|
|
4
|
+
load(store, roots) -> {source path: [Occurrence]} for the units whose main file lies under one of `roots`.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ctypes
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
DECLARATION, DEFINITION, REFERENCE, READ, WRITE, CALL, DYNAMIC, ADDRESSOF, IMPLICIT = (1 << i for i in range(9))
|
|
15
|
+
REL_CHILDOF, REL_BASEOF, REL_OVERRIDEOF, REL_RECEIVEDBY, REL_CALLEDBY, REL_EXTENDEDBY, REL_ACCESSOROF, \
|
|
16
|
+
REL_CONTAINEDBY = (1 << i for i in range(9, 17))
|
|
17
|
+
|
|
18
|
+
KIND = {1: "module", 5: "enum", 6: "struct", 7: "class", 8: "protocol", 9: "extension", 11: "typealias",
|
|
19
|
+
12: "function", 13: "variable", 14: "field", 15: "enumconstant", 16: "instancemethod", 17: "classmethod",
|
|
20
|
+
18: "staticmethod", 19: "instanceproperty", 20: "classproperty", 21: "staticproperty", 22: "constructor",
|
|
21
|
+
23: "destructor", 25: "parameter"}
|
|
22
|
+
CALLABLE = {"function", "instancemethod", "classmethod", "staticmethod", "constructor"}
|
|
23
|
+
TYPES = {"enum", "struct", "class", "protocol"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StrRef(ctypes.Structure):
|
|
27
|
+
_fields_ = [("data", ctypes.c_char_p), ("length", ctypes.c_size_t)]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _s(r: StrRef) -> str:
|
|
31
|
+
if not r.data or not r.length:
|
|
32
|
+
return ""
|
|
33
|
+
return ctypes.string_at(r.data, r.length).decode("utf-8", "replace")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Occurrence:
|
|
38
|
+
line: int
|
|
39
|
+
col: int
|
|
40
|
+
roles: int
|
|
41
|
+
usr: str
|
|
42
|
+
name: str
|
|
43
|
+
kind: str
|
|
44
|
+
subkind: int
|
|
45
|
+
rels: list = field(default_factory=list) # (roles, usr, name, kind)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def find_lib(swift: str | None = None) -> str | None:
|
|
49
|
+
"""libIndexStore.so: CODEGRAPH_LIBINDEXSTORE, else next to the `swift` toolchain (usr/lib)."""
|
|
50
|
+
env = os.environ.get("CODEGRAPH_LIBINDEXSTORE")
|
|
51
|
+
if env:
|
|
52
|
+
return env if Path(env).exists() else None
|
|
53
|
+
sw = swift or shutil.which("swift")
|
|
54
|
+
if sw:
|
|
55
|
+
base = Path(sw).resolve().parent.parent
|
|
56
|
+
for name in ("libIndexStore.so", "libIndexStore.dylib"):
|
|
57
|
+
for d in (base / "lib", base / "lib" / "swift" / "linux"):
|
|
58
|
+
if (d / name).exists():
|
|
59
|
+
return str(d / name)
|
|
60
|
+
# macOS: /usr/bin/swift is a shim; the library lives in the selected Xcode's default toolchain
|
|
61
|
+
for dev in (os.environ.get("DEVELOPER_DIR"), "/Applications/Xcode.app/Contents/Developer"):
|
|
62
|
+
if dev:
|
|
63
|
+
p = Path(dev) / "Toolchains" / "XcodeDefault.xctoolchain" / "usr" / "lib" / "libIndexStore.dylib"
|
|
64
|
+
if p.exists():
|
|
65
|
+
return str(p)
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_LIB = {}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _lib(path: str):
|
|
73
|
+
if path in _LIB:
|
|
74
|
+
return _LIB[path]
|
|
75
|
+
L = ctypes.CDLL(path)
|
|
76
|
+
vp, u64, cb = ctypes.c_void_p, ctypes.c_uint64, ctypes.CFUNCTYPE
|
|
77
|
+
L.indexstore_store_create.restype = vp
|
|
78
|
+
L.indexstore_store_create.argtypes = [ctypes.c_char_p, ctypes.POINTER(vp)]
|
|
79
|
+
L.indexstore_error_get_description.restype = ctypes.c_char_p
|
|
80
|
+
L.indexstore_error_get_description.argtypes = [vp]
|
|
81
|
+
L.indexstore_store_dispose.argtypes = [vp]
|
|
82
|
+
L.UNIT_CB = cb(ctypes.c_bool, vp, StrRef)
|
|
83
|
+
L.indexstore_store_units_apply_f.argtypes = [vp, ctypes.c_uint, vp, L.UNIT_CB]
|
|
84
|
+
L.indexstore_unit_reader_create.restype = vp
|
|
85
|
+
L.indexstore_unit_reader_create.argtypes = [vp, ctypes.c_char_p, ctypes.POINTER(vp)]
|
|
86
|
+
L.indexstore_unit_reader_dispose.argtypes = [vp]
|
|
87
|
+
for f in ("main_file", "module_name", "working_dir"):
|
|
88
|
+
fn = getattr(L, f"indexstore_unit_reader_get_{f}")
|
|
89
|
+
fn.restype, fn.argtypes = StrRef, [vp]
|
|
90
|
+
L.indexstore_unit_reader_is_system_unit.restype = ctypes.c_bool
|
|
91
|
+
L.indexstore_unit_reader_is_system_unit.argtypes = [vp]
|
|
92
|
+
L.DEP_CB = cb(ctypes.c_bool, vp, vp)
|
|
93
|
+
L.indexstore_unit_reader_dependencies_apply_f.argtypes = [vp, vp, L.DEP_CB]
|
|
94
|
+
L.indexstore_unit_dependency_get_kind.restype = ctypes.c_int
|
|
95
|
+
L.indexstore_unit_dependency_get_kind.argtypes = [vp]
|
|
96
|
+
for f in ("filepath", "name"):
|
|
97
|
+
fn = getattr(L, f"indexstore_unit_dependency_get_{f}")
|
|
98
|
+
fn.restype, fn.argtypes = StrRef, [vp]
|
|
99
|
+
L.indexstore_record_reader_create.restype = vp
|
|
100
|
+
L.indexstore_record_reader_create.argtypes = [vp, ctypes.c_char_p, ctypes.POINTER(vp)]
|
|
101
|
+
L.indexstore_record_reader_dispose.argtypes = [vp]
|
|
102
|
+
L.OCC_CB = cb(ctypes.c_bool, vp, vp)
|
|
103
|
+
L.indexstore_record_reader_occurrences_apply_f.argtypes = [vp, vp, L.OCC_CB]
|
|
104
|
+
L.indexstore_occurrence_get_symbol.restype = vp
|
|
105
|
+
L.indexstore_occurrence_get_symbol.argtypes = [vp]
|
|
106
|
+
L.indexstore_occurrence_get_roles.restype = u64
|
|
107
|
+
L.indexstore_occurrence_get_roles.argtypes = [vp]
|
|
108
|
+
L.indexstore_occurrence_get_line_col.argtypes = [vp, ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)]
|
|
109
|
+
L.REL_CB = cb(ctypes.c_bool, vp, vp)
|
|
110
|
+
L.indexstore_occurrence_relations_apply_f.argtypes = [vp, vp, L.REL_CB]
|
|
111
|
+
L.indexstore_symbol_relation_get_roles.restype = u64
|
|
112
|
+
L.indexstore_symbol_relation_get_roles.argtypes = [vp]
|
|
113
|
+
L.indexstore_symbol_relation_get_symbol.restype = vp
|
|
114
|
+
L.indexstore_symbol_relation_get_symbol.argtypes = [vp]
|
|
115
|
+
for f in ("name", "usr"):
|
|
116
|
+
fn = getattr(L, f"indexstore_symbol_get_{f}")
|
|
117
|
+
fn.restype, fn.argtypes = StrRef, [vp]
|
|
118
|
+
for f in ("kind", "subkind", "language"):
|
|
119
|
+
fn = getattr(L, f"indexstore_symbol_get_{f}")
|
|
120
|
+
fn.restype, fn.argtypes = ctypes.c_int, [vp]
|
|
121
|
+
_LIB[path] = L
|
|
122
|
+
return L
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class IndexStoreError(RuntimeError):
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def load(store: str | Path, roots: list[str | Path] | None, lib_path: str) -> dict[str, list[Occurrence]]:
|
|
130
|
+
"""Occurrences per absolute source path, for non-system units whose main file is under `roots`."""
|
|
131
|
+
L = _lib(lib_path)
|
|
132
|
+
err = ctypes.c_void_p()
|
|
133
|
+
st = L.indexstore_store_create(str(store).encode(), ctypes.byref(err))
|
|
134
|
+
if not st:
|
|
135
|
+
msg = L.indexstore_error_get_description(err) if err else b"?"
|
|
136
|
+
raise IndexStoreError(f"cannot open index store {store}: {msg.decode(errors='replace')}")
|
|
137
|
+
roots_s = [str(Path(r).resolve()) + os.sep for r in roots] if roots else None
|
|
138
|
+
units: list[str] = []
|
|
139
|
+
|
|
140
|
+
@L.UNIT_CB
|
|
141
|
+
def on_unit(_ctx, name):
|
|
142
|
+
units.append(_s(name))
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
L.indexstore_store_units_apply_f(st, 0, None, on_unit)
|
|
146
|
+
records: dict[str, str] = {} # source path -> record name (the newest unit wins)
|
|
147
|
+
for u in units:
|
|
148
|
+
ur = L.indexstore_unit_reader_create(st, u.encode(), ctypes.byref(err))
|
|
149
|
+
if not ur:
|
|
150
|
+
continue
|
|
151
|
+
try:
|
|
152
|
+
if L.indexstore_unit_reader_is_system_unit(ur):
|
|
153
|
+
continue
|
|
154
|
+
main = _s(L.indexstore_unit_reader_get_main_file(ur))
|
|
155
|
+
if not main or (roots_s is not None and not any(main.startswith(r) for r in roots_s)):
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
@L.DEP_CB
|
|
159
|
+
def on_dep(_ctx, dep):
|
|
160
|
+
if L.indexstore_unit_dependency_get_kind(dep) == 2: # record
|
|
161
|
+
fp = _s(L.indexstore_unit_dependency_get_filepath(dep))
|
|
162
|
+
if fp == main:
|
|
163
|
+
records[fp] = _s(L.indexstore_unit_dependency_get_name(dep))
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
L.indexstore_unit_reader_dependencies_apply_f(ur, None, on_dep)
|
|
167
|
+
finally:
|
|
168
|
+
L.indexstore_unit_reader_dispose(ur)
|
|
169
|
+
out: dict[str, list[Occurrence]] = {}
|
|
170
|
+
sym_cache: dict[int, tuple] = {}
|
|
171
|
+
|
|
172
|
+
def sym(p):
|
|
173
|
+
r = sym_cache.get(p)
|
|
174
|
+
if r is None:
|
|
175
|
+
r = (_s(L.indexstore_symbol_get_usr(p)), _s(L.indexstore_symbol_get_name(p)),
|
|
176
|
+
KIND.get(L.indexstore_symbol_get_kind(p), "other"), L.indexstore_symbol_get_subkind(p))
|
|
177
|
+
sym_cache[p] = r
|
|
178
|
+
return r
|
|
179
|
+
|
|
180
|
+
for path, rec in records.items():
|
|
181
|
+
rr = L.indexstore_record_reader_create(st, rec.encode(), ctypes.byref(err))
|
|
182
|
+
if not rr:
|
|
183
|
+
continue
|
|
184
|
+
occs: list[Occurrence] = []
|
|
185
|
+
sym_cache.clear()
|
|
186
|
+
try:
|
|
187
|
+
@L.OCC_CB
|
|
188
|
+
def on_occ(_ctx, o):
|
|
189
|
+
line, col = ctypes.c_uint(), ctypes.c_uint()
|
|
190
|
+
L.indexstore_occurrence_get_line_col(o, ctypes.byref(line), ctypes.byref(col))
|
|
191
|
+
usr, name, kind, sub = sym(L.indexstore_occurrence_get_symbol(o))
|
|
192
|
+
oc = Occurrence(line.value, col.value, L.indexstore_occurrence_get_roles(o), usr, name, kind, sub)
|
|
193
|
+
|
|
194
|
+
@L.REL_CB
|
|
195
|
+
def on_rel(_c, rel):
|
|
196
|
+
rs = sym(L.indexstore_symbol_relation_get_symbol(rel))
|
|
197
|
+
oc.rels.append((L.indexstore_symbol_relation_get_roles(rel), rs[0], rs[1], rs[2]))
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
L.indexstore_occurrence_relations_apply_f(o, None, on_rel)
|
|
201
|
+
occs.append(oc)
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
L.indexstore_record_reader_occurrences_apply_f(rr, None, on_occ)
|
|
205
|
+
finally:
|
|
206
|
+
L.indexstore_record_reader_dispose(rr)
|
|
207
|
+
out[path] = occs
|
|
208
|
+
L.indexstore_store_dispose(st)
|
|
209
|
+
return out
|