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
codegraph/indexer.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Orchestrates detection -> language plugins (+ framework sub-plugins) -> entry tagging -> SQLite."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from collections import defaultdict, deque
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .core.detect import detect
|
|
11
|
+
from .core.model import ENTRY_KINDS, PROPAGATING
|
|
12
|
+
from .core.plugin import GraphBuilder, Project, gc_paused
|
|
13
|
+
from .core.store import GraphStore
|
|
14
|
+
from .plugins.laravel.plugin import LaravelPlugin
|
|
15
|
+
from .plugins.php.plugin import PhpPlugin
|
|
16
|
+
from .plugins.stubs.plugins import SCIP_PLUGINS
|
|
17
|
+
from .plugins.ts.plugin import TypeScriptPlugin
|
|
18
|
+
from .plugins.nuxt.plugin import NuxtPlugin
|
|
19
|
+
from .plugins.rust.plugin import RustPlugin
|
|
20
|
+
from .plugins.cfamily.plugin import CFamilyPlugin
|
|
21
|
+
from .plugins.python.plugin import PythonPlugin
|
|
22
|
+
from .plugins.django.plugin import DjangoPlugin
|
|
23
|
+
from .plugins.pyweb.plugin import FastAPIPlugin, FlaskPlugin
|
|
24
|
+
from .plugins.dart.plugin import DartPlugin
|
|
25
|
+
from .plugins.flutter.plugin import FlutterPlugin
|
|
26
|
+
from .plugins.nest.plugin import NestPlugin
|
|
27
|
+
from .plugins.nextjs.plugin import NextPlugin
|
|
28
|
+
from .plugins.express.plugin import ExpressPlugin
|
|
29
|
+
from .plugins.kotlin.plugin import KotlinPlugin
|
|
30
|
+
from .plugins.swift.plugin import SwiftPlugin
|
|
31
|
+
|
|
32
|
+
LANGUAGE_PLUGINS = [PhpPlugin(), TypeScriptPlugin(), PythonPlugin(), DartPlugin(), RustPlugin(), CFamilyPlugin(), KotlinPlugin(), SwiftPlugin(),
|
|
33
|
+
*SCIP_PLUGINS]
|
|
34
|
+
FRAMEWORK_PLUGINS = [LaravelPlugin(), NuxtPlugin(), DjangoPlugin(), FastAPIPlugin(), FlaskPlugin(), FlutterPlugin(), NestPlugin(), NextPlugin(), ExpressPlugin()]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _crash_site(ex: BaseException) -> str | None:
|
|
38
|
+
"""Innermost codegraph frame of an exception (file:line function), for plugin crash reports."""
|
|
39
|
+
import traceback
|
|
40
|
+
frames = [f for f in traceback.extract_tb(ex.__traceback__) if "codegraph" in f.filename]
|
|
41
|
+
if not frames:
|
|
42
|
+
return None
|
|
43
|
+
f = frames[-1]
|
|
44
|
+
return f"{Path(f.filename).name}:{f.lineno} {f.name}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def tag_entries(builder: GraphBuilder, skip_gate: str | None = None) -> list[tuple]:
|
|
48
|
+
"""Forward closure from every entry node over propagating edges (optionally ignoring edges
|
|
49
|
+
that are dead under gate scenario `skip_gate`). Returns rows (node_id, entry_kind, entry_count, sample_entry)."""
|
|
50
|
+
with gc_paused():
|
|
51
|
+
return _tag_entries(builder, skip_gate)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _tag_entries(builder: GraphBuilder, skip_gate: str | None) -> list[tuple]:
|
|
55
|
+
prop = set(PROPAGATING)
|
|
56
|
+
fwd = defaultdict(list)
|
|
57
|
+
for e in builder.edges.values():
|
|
58
|
+
if e.kind in prop and not (skip_gate and e.gate == skip_gate):
|
|
59
|
+
fwd[e.src].append(e.dst)
|
|
60
|
+
counts = defaultdict(lambda: defaultdict(int))
|
|
61
|
+
sample = {}
|
|
62
|
+
for n in builder.nodes.values():
|
|
63
|
+
if not n.entry_kind:
|
|
64
|
+
continue
|
|
65
|
+
seen, dq = {n.id}, deque([n.id])
|
|
66
|
+
while dq:
|
|
67
|
+
x = dq.popleft()
|
|
68
|
+
for y in fwd.get(x, ()):
|
|
69
|
+
if y not in seen:
|
|
70
|
+
seen.add(y)
|
|
71
|
+
dq.append(y)
|
|
72
|
+
for x in seen:
|
|
73
|
+
counts[x][n.entry_kind] += 1
|
|
74
|
+
sample.setdefault((x, n.entry_kind), n.id)
|
|
75
|
+
return [(nid, k, c, sample[(nid, k)]) for nid, ks in counts.items() for k, c in ks.items()]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def setup(project: Project) -> dict:
|
|
79
|
+
"""Which language plugins, framework plugins and presets apply to `project` (detection, then .cg.yaml
|
|
80
|
+
frameworks.add / remove). Fills project.options["presets"] (the applied preset names)."""
|
|
81
|
+
from . import presets
|
|
82
|
+
from .config import FRAMEWORK_PLUGIN_NAMES
|
|
83
|
+
cfg = project.options.get("config") or {}
|
|
84
|
+
fwc = cfg.get("frameworks") or {}
|
|
85
|
+
add, remove = set(fwc.get("add") or []), set(fwc.get("remove") or [])
|
|
86
|
+
if not project.detected:
|
|
87
|
+
project.detected = detect(project.root)
|
|
88
|
+
# fresh plugin instances per run: state a plugin keeps on itself (caches, gate predicates, file lists) never
|
|
89
|
+
# carries over to the next project indexed in the same process (MCP `index`, test suites)
|
|
90
|
+
langs = [lp for lp in copy.deepcopy(LANGUAGE_PLUGINS) if lp.detect(project)]
|
|
91
|
+
all_fws = copy.deepcopy(FRAMEWORK_PLUGINS)
|
|
92
|
+
detected = [f for f in all_fws if f.detect(project)]
|
|
93
|
+
fws = [f for f in all_fws if (f in detected or f.name in add) and f.name not in remove]
|
|
94
|
+
found = {f.name: "detected" for f in detected}
|
|
95
|
+
for k in (project.detected.get("frameworks") or {}): # sub-frameworks with a preset (DRF, django-ninja)
|
|
96
|
+
k = presets.FRAMEWORK_ALIASES.get(k, k)
|
|
97
|
+
if k in presets.frameworks() and k not in FRAMEWORK_PLUGIN_NAMES:
|
|
98
|
+
found.setdefault(k, "detected")
|
|
99
|
+
names = {k: v for k, v in found.items() if k not in remove}
|
|
100
|
+
for k in sorted(add):
|
|
101
|
+
names.setdefault(k, ".cg.yaml")
|
|
102
|
+
applied = presets.select([lp.name for lp in langs], list(names))
|
|
103
|
+
project.options["presets"] = applied
|
|
104
|
+
return {"language_plugins": langs, "framework_plugins": fws, "languages": [lp.name for lp in langs],
|
|
105
|
+
"frameworks": names, "removed": sorted(remove & set(found)), "presets": applied}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def scip_warnings(plugins: dict) -> list[str]:
|
|
109
|
+
"""Unusable SCIP indexes (no readable positions, no definitions, none matched): the generic importer's
|
|
110
|
+
(`scip:<path>`) and the exact layers' (`<lang>.scip.warning`)."""
|
|
111
|
+
out = []
|
|
112
|
+
for k, v in plugins.items():
|
|
113
|
+
if not isinstance(v, dict):
|
|
114
|
+
continue
|
|
115
|
+
w = v.get("warning") if k.startswith("scip:") else (v.get("scip") or {}).get("warning") \
|
|
116
|
+
if isinstance(v.get("scip"), dict) else None
|
|
117
|
+
if w:
|
|
118
|
+
out.append(w if k.startswith("scip:") else f"{k}: {w}")
|
|
119
|
+
return out
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def index_project(root: str | Path, db_path: str | Path, name: str | None = None, scip: list[str] | None = None,
|
|
123
|
+
gates: str | None = None, python_roots: list[str] | None = None, include_generated: bool = False) -> dict:
|
|
124
|
+
"""Index `root` into a new graph DB. The project config file (.cg.yaml at the root) is read automatically;
|
|
125
|
+
python_roots (`cg index --python-root`) overrides its python.source_roots and `gates` its gates. Generated, copied
|
|
126
|
+
and vendored files (codegraph/core/generated.py) stay out of the graph unless include_generated
|
|
127
|
+
(`--include-generated`, .cg.yaml generated.include) indexes them with attrs.generated. Raises
|
|
128
|
+
config.ConfigError for an invalid config file or root."""
|
|
129
|
+
from .config import ConfigError, load as load_config, norm_root, resolve_path
|
|
130
|
+
t0 = time.time()
|
|
131
|
+
project = Project(root=Path(root).resolve(), name=name or Path(root).name)
|
|
132
|
+
project.options["config"] = cfg = load_config(project.root)
|
|
133
|
+
if scip:
|
|
134
|
+
project.options["scip"] = list(scip) # a language plugin may consume its own index (Kotlin: scip-java)
|
|
135
|
+
if python_roots:
|
|
136
|
+
project.options["python_roots"] = list(dict.fromkeys(norm_root(r, "--python-root") for r in python_roots))
|
|
137
|
+
gates_from = "flag" if gates else None
|
|
138
|
+
if not gates and cfg.get("gates"):
|
|
139
|
+
gates, gates_from = str(resolve_path(project.root, cfg["gates"])), cfg["file"]
|
|
140
|
+
if not Path(gates).is_file():
|
|
141
|
+
raise ConfigError(f"{cfg['file']}: gates: {cfg['gates']!r} does not exist")
|
|
142
|
+
if gates:
|
|
143
|
+
project.options["gates"] = json.loads(Path(gates).read_text())["scenarios"]
|
|
144
|
+
project.detected = detect(project.root)
|
|
145
|
+
# one file scan up front: coverage counts + the generated / copied / vendored classification every walk consults
|
|
146
|
+
from .core.generated import Classifier, apply as apply_generated
|
|
147
|
+
from .core.paths import rules as path_rules
|
|
148
|
+
from .coverage import scan_tree
|
|
149
|
+
t_scan = time.time()
|
|
150
|
+
clf = Classifier(project.root, cfg, include=include_generated)
|
|
151
|
+
scanned = scan_tree(project.root, path_rules(project, "common", "scan_skip_dirs", generated=False), classifier=clf)
|
|
152
|
+
project.options["generated"] = clf
|
|
153
|
+
if clf.include:
|
|
154
|
+
project.options["dart_keep_generated"] = True
|
|
155
|
+
scan_seconds = round(time.time() - t_scan, 2)
|
|
156
|
+
builder = GraphBuilder()
|
|
157
|
+
stats = {"detected": project.detected, "plugins": {}}
|
|
158
|
+
if cfg:
|
|
159
|
+
stats["config"] = {k: v for k, v in cfg.items()}
|
|
160
|
+
if python_roots:
|
|
161
|
+
stats["python_roots_flag"] = project.options["python_roots"]
|
|
162
|
+
if include_generated:
|
|
163
|
+
stats["include_generated_flag"] = True
|
|
164
|
+
if gates_from:
|
|
165
|
+
stats["gates_from"] = gates_from
|
|
166
|
+
plan = setup(project)
|
|
167
|
+
stats["presets"] = {"applied": plan["presets"], "languages": plan["languages"], "frameworks": plan["frameworks"]}
|
|
168
|
+
if plan["removed"]:
|
|
169
|
+
stats["presets"]["frameworks_removed"] = plan["removed"]
|
|
170
|
+
file_reports: dict = {} # language -> per-file outcome (coverage file completeness)
|
|
171
|
+
frameworks = plan["framework_plugins"]
|
|
172
|
+
for lp in plan["language_plugins"]:
|
|
173
|
+
fws = [f for f in frameworks if f.language == lp.name]
|
|
174
|
+
lp.program = None
|
|
175
|
+
# one missing indexer / toolchain never fails the whole index: that language is skipped with a note
|
|
176
|
+
missing = lp.prerequisite_problem(project) if hasattr(lp, "prerequisite_problem") else None
|
|
177
|
+
if missing:
|
|
178
|
+
stats["plugins"][lp.name] = {"status": "skipped", "reason": missing}
|
|
179
|
+
continue
|
|
180
|
+
n0, e0 = len(builder.nodes), len(builder.edges)
|
|
181
|
+
lp.file_report = None
|
|
182
|
+
try:
|
|
183
|
+
st = lp.index(project, builder, fws)
|
|
184
|
+
except Exception as ex: # noqa: BLE001
|
|
185
|
+
stats["plugins"][lp.name] = {"status": "skipped", "reason": f"{type(ex).__name__}: {str(ex)[:300]}",
|
|
186
|
+
"partial_nodes": len(builder.nodes) - n0, "partial_edges": len(builder.edges) - e0}
|
|
187
|
+
continue
|
|
188
|
+
stats["plugins"][lp.name] = st
|
|
189
|
+
if isinstance(st, dict) and st.get("status") in ("skipped", "error", "stub"):
|
|
190
|
+
continue
|
|
191
|
+
if getattr(lp, "file_report", None) is not None:
|
|
192
|
+
file_reports[lp.name] = lp.file_report
|
|
193
|
+
lp.file_report = None
|
|
194
|
+
ctx = getattr(lp, "program", None)
|
|
195
|
+
for fw in fws:
|
|
196
|
+
try:
|
|
197
|
+
stats["plugins"][f"{lp.name}/{fw.name}"] = fw.contribute(project, builder, ctx)
|
|
198
|
+
except Exception as ex: # noqa: BLE001
|
|
199
|
+
stats["plugins"][f"{lp.name}/{fw.name}"] = {"status": "skipped", "reason": f"{type(ex).__name__}: {str(ex)[:300]}",
|
|
200
|
+
"at": _crash_site(ex)}
|
|
201
|
+
if fws and hasattr(lp, "after_frameworks"):
|
|
202
|
+
lp.after_frameworks(builder, st)
|
|
203
|
+
if hasattr(lp, "link_test_requests"):
|
|
204
|
+
lp.link_test_requests(builder, st)
|
|
205
|
+
for fw in frameworks:
|
|
206
|
+
if f"{fw.language}/{fw.name}" not in stats["plugins"]:
|
|
207
|
+
stats["plugins"][f"{fw.language}/{fw.name}"] = {"status": "detected; language plugin not active"}
|
|
208
|
+
if scip:
|
|
209
|
+
from .plugins.scip.importer import import_scip
|
|
210
|
+
for s in scip:
|
|
211
|
+
if s in (project.options.get("scip_consumed") or []):
|
|
212
|
+
stats["plugins"][f"scip:{s}"] = {"status": "imported by the Kotlin plugin (exact layer)"}
|
|
213
|
+
continue
|
|
214
|
+
stats["plugins"][f"scip:{s}"] = import_scip(s, builder)
|
|
215
|
+
# generated / copied / vendored files: out of the graph (default) or labelled (attrs.generated)
|
|
216
|
+
stats["generated"] = apply_generated(builder, clf)
|
|
217
|
+
# web / native bridges: native receivers of Capacitor / React Native / Flutter calls (platform marks on their files)
|
|
218
|
+
from . import bridges as bridges_mod
|
|
219
|
+
t_br = time.time()
|
|
220
|
+
br = bridges_mod.apply(project, builder, scanned)
|
|
221
|
+
br_seconds = time.time() - t_br
|
|
222
|
+
# platform-specific code: platform tags on nodes / edges, variant implementations linked, divergence
|
|
223
|
+
from .platforms import apply as apply_platforms
|
|
224
|
+
t_pl = time.time()
|
|
225
|
+
pl = apply_platforms(project, builder)
|
|
226
|
+
if pl:
|
|
227
|
+
pl["seconds"] = round(time.time() - t_pl, 2)
|
|
228
|
+
stats["platforms"] = pl
|
|
229
|
+
# external systems (#40): env address keys, Laravel connections, .env.example / docker-compose -> external:<protocol>:<target>
|
|
230
|
+
from .external import attach as attach_external
|
|
231
|
+
if (ex := attach_external(builder, project.root)):
|
|
232
|
+
stats["external"] = ex
|
|
233
|
+
# tests that run the project's programs in a subprocess, outside Python (Rust, Node, PHP artisan, Dart; #60)
|
|
234
|
+
from .process_runs import apply as apply_process_runs
|
|
235
|
+
if (pr := apply_process_runs(project, builder)):
|
|
236
|
+
stats["process_runs"] = pr
|
|
237
|
+
# test code (tests/, *.spec.ts ...) never feeds the application graph: its edges become TEST_* kinds
|
|
238
|
+
from .tests_index import isolate_tests
|
|
239
|
+
stats["tests"] = isolate_tests(builder)
|
|
240
|
+
t_br = time.time()
|
|
241
|
+
br = bridges_mod.finalize(project, builder, br, (pl or {}).get("targets") or bridges_mod.project_targets(project, builder))
|
|
242
|
+
if br:
|
|
243
|
+
br["seconds"] = round(br_seconds + time.time() - t_br, 2)
|
|
244
|
+
stats["bridges"] = br
|
|
245
|
+
from .tests_index import link_local_channels
|
|
246
|
+
if (ch := link_local_channels(builder)):
|
|
247
|
+
stats["channels_linked"] = ch
|
|
248
|
+
# protocol endpoints (#31): MATCHES_ENDPOINT between senders and receivers of one graph (wildcards, templates)
|
|
249
|
+
from .protocols import apply as apply_protocols
|
|
250
|
+
if (pr := apply_protocols(builder, ((cfg or {}).get("protocols") or {}).get("external") or [])):
|
|
251
|
+
stats["protocols"] = pr
|
|
252
|
+
# dangling edge targets -> placeholder nodes so every edge resolves
|
|
253
|
+
for e in list(builder.edges.values()):
|
|
254
|
+
for nid in (e.src, e.dst):
|
|
255
|
+
if nid not in builder.nodes:
|
|
256
|
+
kind, key = nid.split(":", 1)
|
|
257
|
+
builder.add_node(kind, key, attrs={"placeholder": True})
|
|
258
|
+
# files parsed with syntax errors (#73): their error spans and the declarations the graph lost there
|
|
259
|
+
from .core.syntax_errors import summarize as summarize_syntax_errors
|
|
260
|
+
raw_err = {lang: rep["syntax_errors"] for lang, rep in file_reports.items() if rep.get("syntax_errors")}
|
|
261
|
+
if raw_err:
|
|
262
|
+
want = {f for raw in raw_err.values() for f in raw}
|
|
263
|
+
names_by_file: dict = {}
|
|
264
|
+
for n in builder.nodes.values():
|
|
265
|
+
if n.file in want and n.name:
|
|
266
|
+
names_by_file.setdefault(n.file, set()).add(n.name.split(".")[-1].split("(")[0])
|
|
267
|
+
for lang, raw in raw_err.items():
|
|
268
|
+
file_reports[lang]["syntax_errors"] = summarize_syntax_errors(
|
|
269
|
+
lang, project.root, raw, names_by_file, file_reports[lang].get("parse_failed"))
|
|
270
|
+
# completeness: one file scan for coverage + the blind-spot detectors (patterns no plugin models)
|
|
271
|
+
from .coverage import compute
|
|
272
|
+
from .blindspots import detect as detect_blind_spots
|
|
273
|
+
t_cov = time.time()
|
|
274
|
+
progs = {lp.name: lp.program for lp in plan["language_plugins"] if getattr(lp, "program", None) is not None
|
|
275
|
+
and lp.name in stats["plugins"] and "status" not in (stats["plugins"][lp.name] or {})}
|
|
276
|
+
bspots = detect_blind_spots(project.root, scanned.paths, builder, progs)
|
|
277
|
+
t_tag = time.time()
|
|
278
|
+
rows = tag_entries(builder)
|
|
279
|
+
stats["entry_tagging_seconds"] = round(time.time() - t_tag, 2)
|
|
280
|
+
store = GraphStore.create(db_path)
|
|
281
|
+
with gc_paused():
|
|
282
|
+
store.write(builder.nodes.values(), builder.edges.values())
|
|
283
|
+
store.db.executemany("INSERT INTO node_entry VALUES (?,?,?,?)", rows)
|
|
284
|
+
scen = [g["name"] for g in (project.options.get("gates") or [])[:1]]
|
|
285
|
+
for sc in scen:
|
|
286
|
+
live = tag_entries(builder, skip_gate=sc)
|
|
287
|
+
store.db.executemany("INSERT INTO node_entry_live VALUES (?,?,?,?,?)", [(sc, *r) for r in live])
|
|
288
|
+
stats["gated_edges"] = {sc: sum(1 for e in builder.edges.values() if e.gate == sc)}
|
|
289
|
+
for lp in plan["language_plugins"]:
|
|
290
|
+
store.db.executemany("INSERT OR REPLACE INTO gate_predicates VALUES (?,?,?,?)", getattr(lp, "gate_predicates", []) or [])
|
|
291
|
+
store.db.commit()
|
|
292
|
+
stats["coverage"] = compute(project.root, {k: v for k, v in stats["plugins"].items() if "/" not in k and not k.startswith("scip:")},
|
|
293
|
+
scip_imported=bool(scip), reports=file_reports, scanned=scanned, blind_spots=bspots,
|
|
294
|
+
warnings=scip_warnings(stats["plugins"]))
|
|
295
|
+
# detected.languages: the root marker files (detect), plus every language a plugin indexed source files of: an
|
|
296
|
+
# Xcode app has no root Package.swift, a monorepo no root tsconfig.json (#75)
|
|
297
|
+
dl = stats["detected"].setdefault("languages", {})
|
|
298
|
+
for e in stats["coverage"].get("languages") or []:
|
|
299
|
+
pst = stats["plugins"].get(e["language"])
|
|
300
|
+
if e["files"] and e["language"] not in dl and isinstance(pst, dict) and pst.get("status") not in ("skipped", "error", "stub"):
|
|
301
|
+
dl[e["language"]] = ["source files (" + ", ".join(f"{x} {n}" for x, n in sorted(e["by_ext"].items())) + ")"]
|
|
302
|
+
gsum = clf.summary()
|
|
303
|
+
if gsum["files"] or gsum.get("build_dirs"):
|
|
304
|
+
stats["coverage"]["generated"] = gsum
|
|
305
|
+
# starter queries for the visual view / MCP, derived from this graph (each resolves to existing nodes)
|
|
306
|
+
from .starters import generate as gen_starters
|
|
307
|
+
t_st = time.time()
|
|
308
|
+
store.set_meta(project=project.name, root=str(project.root), stats=stats) # presets / config for the route guards
|
|
309
|
+
gst = GraphStore(db_path)
|
|
310
|
+
try:
|
|
311
|
+
rep: dict = {}
|
|
312
|
+
stats["starters"] = gen_starters(gst, report=rep)
|
|
313
|
+
if rep.get("skipped"):
|
|
314
|
+
stats["starters_skipped"] = rep["skipped"] # over the time budget
|
|
315
|
+
except Exception as ex: # noqa: BLE001 (never fails an index)
|
|
316
|
+
stats["starters"], stats["starters_error"] = [], f"{type(ex).__name__}: {str(ex)[:200]}"
|
|
317
|
+
finally:
|
|
318
|
+
gst.db.close()
|
|
319
|
+
stats["starters_seconds"] = round(time.time() - t_st, 2)
|
|
320
|
+
stats["coverage"]["setup"] = {"frameworks": sorted(plan["frameworks"]), "presets": plan["presets"],
|
|
321
|
+
"config": cfg.get("file")}
|
|
322
|
+
if pl: # per-target coverage: what each declared target builds, conditions left unevaluated
|
|
323
|
+
stats["coverage"]["platforms"] = {k: pl[k] for k in ("targets", "per_target", "unevaluated_conditions",
|
|
324
|
+
"unevaluated_samples", "conditions") if k in pl}
|
|
325
|
+
from .coverage import value_counts
|
|
326
|
+
vc = value_counts(builder)
|
|
327
|
+
if vc: # enum cases / constants per language and the references resolved to them (#84)
|
|
328
|
+
stats["coverage"]["values"] = vc
|
|
329
|
+
stats["completeness_seconds"] = round(t_tag - t_cov, 2)
|
|
330
|
+
stats["scan_seconds"] = scan_seconds
|
|
331
|
+
stats["nodes"] = len(builder.nodes)
|
|
332
|
+
stats["edges"] = len(builder.edges)
|
|
333
|
+
stats["index_seconds"] = round(time.time() - t0, 2)
|
|
334
|
+
store.set_meta(project=project.name, root=str(project.root), stats=stats, indexed_at=time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()))
|
|
335
|
+
store.db.close()
|
|
336
|
+
return stats
|