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/doctor.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"""`cg doctor`: what this installation can index, and how.
|
|
2
|
+
|
|
3
|
+
Versions of cg and the tools it uses, whether the Node / PHP / Dart extractor dependencies are installed (and where:
|
|
4
|
+
codegraph/core/extractors.py), and per language whether `cg index` runs in exact or heuristic mode, why, and the
|
|
5
|
+
command that installs what is missing. Every module of the package is imported as well: `cg index` loads every
|
|
6
|
+
language plugin, so a module that does not import on this Python (syntax or an API newer than the running
|
|
7
|
+
interpreter) breaks indexing for every language; doctor names it and exits non-zero. With a project root it also checks project-level conditions (a
|
|
8
|
+
compile_commands.json for C / C++, a Gradle / Maven build for Kotlin, `.cg.yaml` rust.targets) and lists only the
|
|
9
|
+
languages the project has."""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import importlib.util
|
|
13
|
+
import os
|
|
14
|
+
import platform
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from . import __version__
|
|
21
|
+
from .core import cache, extractors
|
|
22
|
+
|
|
23
|
+
INSTALL_DOC = "docs/install.md"
|
|
24
|
+
REPO = "https://github.com/cyberchronos00/code-graph"
|
|
25
|
+
GRAMMARS = {"rust": "tree_sitter_rust", "c_cpp": "tree_sitter_c", "kotlin": "tree_sitter_kotlin", "swift": "tree_sitter_swift"}
|
|
26
|
+
PIP_NAMES = {"tree_sitter": "tree-sitter", "tree_sitter_rust": "tree-sitter-rust", "tree_sitter_c": "tree-sitter-c",
|
|
27
|
+
"tree_sitter_cpp": "tree-sitter-cpp", "tree_sitter_kotlin": "tree-sitter-kotlin", "tree_sitter_swift": "tree-sitter-swift"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# language -> the plugin packages its `cg index` run loads (besides the indexer, which loads all of them)
|
|
31
|
+
PLUGIN_PACKAGES = {"python": ("python", "pyweb", "django"), "typescript": ("ts", "tsweb", "nuxt", "nest", "nextjs", "express"),
|
|
32
|
+
"php": ("php", "laravel"), "dart": ("dart", "flutter"), "rust": ("rust", "native", "scip"),
|
|
33
|
+
"c_cpp": ("cfamily", "native", "scip"), "kotlin": ("kotlin", "scip"), "swift": ("swift",)}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _run(tool: str | None, args=("--version",)) -> tuple[str | None, bool]:
|
|
37
|
+
"""First output line of `tool args` and whether it exited 0 (a rustup proxy without its component prints an
|
|
38
|
+
error and exits non-zero)."""
|
|
39
|
+
if not tool:
|
|
40
|
+
return None, False
|
|
41
|
+
try:
|
|
42
|
+
r = subprocess.run([tool, *args], capture_output=True, text=True, timeout=15)
|
|
43
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
44
|
+
return None, False
|
|
45
|
+
out = (r.stdout or r.stderr).strip().splitlines()
|
|
46
|
+
return (out[0].strip()[:120] if out else "?"), r.returncode == 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _version(tool: str | None, args=("--version",)) -> str | None:
|
|
50
|
+
return _run(tool, args)[0]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _describe(e: BaseException) -> str:
|
|
54
|
+
"""`SyntaxError: <msg> (codegraph/plugins/x/plugin.py:12)`: the error and where in the package it is."""
|
|
55
|
+
pkg = Path(__file__).resolve().parent
|
|
56
|
+
where = None
|
|
57
|
+
if isinstance(e, SyntaxError) and e.filename:
|
|
58
|
+
where = (e.filename, e.lineno)
|
|
59
|
+
else:
|
|
60
|
+
tb = e.__traceback__
|
|
61
|
+
while tb is not None:
|
|
62
|
+
if str(Path(tb.tb_frame.f_code.co_filename).resolve()).startswith(str(pkg)):
|
|
63
|
+
where = (tb.tb_frame.f_code.co_filename, tb.tb_lineno)
|
|
64
|
+
tb = tb.tb_next
|
|
65
|
+
msg = e.msg if isinstance(e, SyntaxError) else str(e)
|
|
66
|
+
if where:
|
|
67
|
+
try:
|
|
68
|
+
rel = Path(where[0]).resolve().relative_to(pkg.parent).as_posix()
|
|
69
|
+
except ValueError:
|
|
70
|
+
rel = where[0]
|
|
71
|
+
return f"{type(e).__name__}: {msg} ({rel}:{where[1]})"
|
|
72
|
+
return f"{type(e).__name__}: {msg}"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def module_imports() -> dict:
|
|
76
|
+
"""Import every module of the package. `cg index` imports every language plugin, so one module that fails to
|
|
77
|
+
import on this interpreter makes indexing fail for every language. Returns the module count and the failures
|
|
78
|
+
(module -> error)."""
|
|
79
|
+
import importlib
|
|
80
|
+
import pkgutil
|
|
81
|
+
|
|
82
|
+
import codegraph
|
|
83
|
+
failed: dict[str, str] = {}
|
|
84
|
+
|
|
85
|
+
def onerror(name):
|
|
86
|
+
e = sys.exc_info()[1]
|
|
87
|
+
if e is not None and name not in failed:
|
|
88
|
+
failed[name] = _describe(e)
|
|
89
|
+
|
|
90
|
+
n = 0
|
|
91
|
+
for m in pkgutil.walk_packages(codegraph.__path__, "codegraph.", onerror=onerror):
|
|
92
|
+
n += 1
|
|
93
|
+
try:
|
|
94
|
+
importlib.import_module(m.name)
|
|
95
|
+
except Exception as e: # noqa: BLE001 (SyntaxError included: a module this Python cannot compile)
|
|
96
|
+
failed.setdefault(m.name, _describe(e))
|
|
97
|
+
return {"modules": n, "failed": failed}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _tools() -> dict:
|
|
101
|
+
from .plugins.dart.plugin import find_dart
|
|
102
|
+
from .plugins.kotlin.exact import find_java
|
|
103
|
+
from .plugins.native.runner import find_tool
|
|
104
|
+
from .plugins.swift.exact import find_swift
|
|
105
|
+
home = Path.home()
|
|
106
|
+
found = {
|
|
107
|
+
"node": shutil.which("node"), "npm": shutil.which("npm"), "php": shutil.which("php"),
|
|
108
|
+
"composer": shutil.which("composer"), "dart": find_dart(),
|
|
109
|
+
"rust-analyzer": find_tool("CODEGRAPH_RUST_ANALYZER", ["rust-analyzer"], [home / ".cargo" / "bin"]),
|
|
110
|
+
"cargo": os.environ.get("CODEGRAPH_CARGO") or shutil.which("cargo") or
|
|
111
|
+
(str(home / ".cargo/bin/cargo") if (home / ".cargo/bin/cargo").exists() else None),
|
|
112
|
+
"scip-clang": find_tool("CODEGRAPH_SCIP_CLANG", ["scip-clang"], [home / ".local" / "bin"]),
|
|
113
|
+
"scip-java": find_tool("CODEGRAPH_SCIP_JAVA", ["scip-java"], [home / "tools", home / ".local" / "bin",
|
|
114
|
+
home / ".local" / "share" / "coursier" / "bin"]),
|
|
115
|
+
"java": find_java(), "swift": find_swift(), "git": shutil.which("git"),
|
|
116
|
+
}
|
|
117
|
+
vargs = {"java": ("-version",), "dart": ("--version",)}
|
|
118
|
+
out = {}
|
|
119
|
+
for k, v in found.items():
|
|
120
|
+
ver, ok = _run(v, vargs.get(k, ("--version",)))
|
|
121
|
+
out[k] = {"path": v, "version": ver, **({"runs": False} if v and not ok else {})}
|
|
122
|
+
return out
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _module(name: str) -> bool:
|
|
126
|
+
try:
|
|
127
|
+
return importlib.util.find_spec(name) is not None
|
|
128
|
+
except (ImportError, ValueError):
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _pip(*mods: str) -> str:
|
|
133
|
+
return f"{Path(sys.executable).name} -m pip install " + " ".join(PIP_NAMES[m] for m in mods) + \
|
|
134
|
+
" (or reinstall cg: the package depends on it)"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _languages(tools: dict, root: Path | None, cfg: dict) -> list[dict]:
|
|
138
|
+
has = lambda t: bool(tools[t]["path"])
|
|
139
|
+
out = []
|
|
140
|
+
|
|
141
|
+
def add(lang, mode, why, fix=None, **extra):
|
|
142
|
+
out.append({"language": lang, "mode": mode, "why": why, **({"fix": fix} if fix else {}), **extra})
|
|
143
|
+
|
|
144
|
+
add("python", "exact", "built-in parser (Python ast)")
|
|
145
|
+
st = extractors.status("typescript")
|
|
146
|
+
if not has("node"):
|
|
147
|
+
add("typescript", "unavailable", "node not installed (Node.js 20+ runs the TypeScript extractor)",
|
|
148
|
+
"install Node.js 20+ (https://nodejs.org or your package manager), then `cg setup typescript`")
|
|
149
|
+
elif not st["installed"] and not has("npm"):
|
|
150
|
+
add("typescript", "unavailable", "extractor dependencies missing and npm not installed", "install npm, then `cg setup typescript`")
|
|
151
|
+
else:
|
|
152
|
+
add("typescript", "exact", "TypeScript compiler API" + ("" if st["installed"] else
|
|
153
|
+
"; its npm dependencies install on the first index (or now: `cg setup typescript`)"))
|
|
154
|
+
st = extractors.status("php")
|
|
155
|
+
if not has("php"):
|
|
156
|
+
add("php", "unavailable", "php not installed (PHP 8.2+ runs the PHP extractor)", "install PHP 8.2+ and Composer, then `cg setup php`")
|
|
157
|
+
elif not st["installed"] and not has("composer"):
|
|
158
|
+
add("php", "unavailable", "extractor dependencies missing and Composer not installed", "install Composer (https://getcomposer.org), then `cg setup php`")
|
|
159
|
+
else:
|
|
160
|
+
add("php", "exact", "nikic/php-parser" + ("" if st["installed"] else "; Composer dependencies install on the first index (or `cg setup php`)"))
|
|
161
|
+
st = extractors.status("dart")
|
|
162
|
+
if not has("dart"):
|
|
163
|
+
add("dart", "unavailable", "Dart SDK not found (`dart` on PATH or $DART)", "install the Dart SDK 3.x (https://dart.dev/get-dart), then `cg setup dart`")
|
|
164
|
+
else:
|
|
165
|
+
add("dart", "exact", "Dart analyzer" + ("" if st["installed"] else "; `dart pub get` + compile run on the first index (or `cg setup dart`)"))
|
|
166
|
+
ts_ok = _module("tree_sitter")
|
|
167
|
+
# Rust
|
|
168
|
+
if not ts_ok or not _module("tree_sitter_rust"):
|
|
169
|
+
add("rust", "unavailable", "tree-sitter grammar missing", _pip("tree_sitter", "tree_sitter_rust"))
|
|
170
|
+
elif os.environ.get("CODEGRAPH_RUST_SCIP", "1") == "0":
|
|
171
|
+
add("rust", "heuristic", "CODEGRAPH_RUST_SCIP=0")
|
|
172
|
+
elif not has("rust-analyzer") or not has("cargo"):
|
|
173
|
+
miss = " and ".join(t for t in ("rust-analyzer", "cargo") if not has(t))
|
|
174
|
+
add("rust", "heuristic", f"{miss} not found: tree-sitter layer with name-based resolution",
|
|
175
|
+
"rustup component add rust-analyzer (or install.sh --with rust)")
|
|
176
|
+
elif tools["rust-analyzer"].get("runs") is False:
|
|
177
|
+
add("rust", "heuristic", f"rust-analyzer at {tools['rust-analyzer']['path']} does not run "
|
|
178
|
+
f"({tools['rust-analyzer']['version'] or 'no output'}): tree-sitter layer with name-based resolution",
|
|
179
|
+
"rustup component add rust-analyzer (an empty rustup proxy is on PATH)")
|
|
180
|
+
else:
|
|
181
|
+
from .plugins.rust.plugin import rust_targets_setting
|
|
182
|
+
class _P: # rust_targets_setting reads project.options["config"]
|
|
183
|
+
options = {"config": cfg}
|
|
184
|
+
val, src = rust_targets_setting(_P)
|
|
185
|
+
off = val in ("0", "", "off", "none")
|
|
186
|
+
note = ("per-target runs off" if off else
|
|
187
|
+
f"plus one run per other target the cfg conditions name ({val}, from {src}); a cold index takes about "
|
|
188
|
+
"twice as long, later runs use rust-analyzer's cache. Turn off with `rust: {targets: off}` in .cg.yaml "
|
|
189
|
+
"or CODEGRAPH_RUST_TARGETS=0")
|
|
190
|
+
add("rust", "exact", f"rust-analyzer ({tools['rust-analyzer']['version']}); {note}", targets=val, targets_source=src)
|
|
191
|
+
# C / C++
|
|
192
|
+
if not ts_ok or not _module("tree_sitter_c") or not _module("tree_sitter_cpp"):
|
|
193
|
+
add("c_cpp", "unavailable", "tree-sitter grammars missing", _pip("tree_sitter", "tree_sitter_c", "tree_sitter_cpp"))
|
|
194
|
+
elif not has("scip-clang"):
|
|
195
|
+
add("c_cpp", "heuristic", "scip-clang not found: tree-sitter layer with name-based resolution",
|
|
196
|
+
"install.sh --with c (scip-clang release binary into ~/.local/bin; docs/native.md)")
|
|
197
|
+
else:
|
|
198
|
+
compdb = None
|
|
199
|
+
if root is not None:
|
|
200
|
+
from .plugins.cfamily.plugin import find_compdb
|
|
201
|
+
compdb = find_compdb(root)
|
|
202
|
+
if root is not None and compdb is None:
|
|
203
|
+
add("c_cpp", "heuristic", "scip-clang found, but no compile_commands.json in the project",
|
|
204
|
+
"generate one (`cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`, `bear -- make`) or set CODEGRAPH_COMPDB")
|
|
205
|
+
else:
|
|
206
|
+
add("c_cpp", "exact", "scip-clang" + (f" with {compdb}" if compdb else " (needs a compile_commands.json in the project)"))
|
|
207
|
+
# Kotlin
|
|
208
|
+
if not ts_ok or not _module("tree_sitter_kotlin"):
|
|
209
|
+
add("kotlin", "unavailable", "tree-sitter grammar missing", _pip("tree_sitter", "tree_sitter_kotlin"))
|
|
210
|
+
elif os.environ.get("CODEGRAPH_KOTLIN_SCIP_FILE"):
|
|
211
|
+
add("kotlin", "exact", "CODEGRAPH_KOTLIN_SCIP_FILE")
|
|
212
|
+
elif not has("scip-java") or not has("java"):
|
|
213
|
+
miss = " and ".join(t for t in ("scip-java", "java") if not has(t))
|
|
214
|
+
add("kotlin", "heuristic", f"{miss} not found: tree-sitter layer with name-based resolution",
|
|
215
|
+
"install.sh --with kotlin (JDK 17+ and scip-java via coursier), then CODEGRAPH_KOTLIN_SCIP=1")
|
|
216
|
+
elif os.environ.get("CODEGRAPH_KOTLIN_SCIP") != "1":
|
|
217
|
+
add("kotlin", "heuristic", "scip-java found but not run: it runs the Gradle / Maven build (the project's build scripts)",
|
|
218
|
+
"set CODEGRAPH_KOTLIN_SCIP=1 (or pass --scip index.scip)")
|
|
219
|
+
else:
|
|
220
|
+
add("kotlin", "exact", "scip-java on the Gradle / Maven build")
|
|
221
|
+
# Swift
|
|
222
|
+
if not ts_ok or not _module("tree_sitter_swift"):
|
|
223
|
+
add("swift", "unavailable", "tree-sitter grammar missing", _pip("tree_sitter", "tree_sitter_swift"))
|
|
224
|
+
elif os.environ.get("CODEGRAPH_SWIFT_INDEX_STORE"):
|
|
225
|
+
add("swift", "exact", "CODEGRAPH_SWIFT_INDEX_STORE")
|
|
226
|
+
elif not has("swift"):
|
|
227
|
+
add("swift", "heuristic", "no Swift toolchain: tree-sitter layer with name-based resolution",
|
|
228
|
+
"install.sh --with swift (prints the swift.org toolchain install), then CODEGRAPH_SWIFT_INDEX=1")
|
|
229
|
+
elif os.environ.get("CODEGRAPH_SWIFT_INDEX") != "1":
|
|
230
|
+
add("swift", "heuristic", "Swift toolchain found but not run: the index store needs a `swift build`",
|
|
231
|
+
"set CODEGRAPH_SWIFT_INDEX=1 (SwiftPM) or CODEGRAPH_SWIFT_INDEX_STORE to an existing index store")
|
|
232
|
+
else:
|
|
233
|
+
add("swift", "exact", "swift build --enable-index-store")
|
|
234
|
+
return out
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def scip_health(path: str | Path) -> dict:
|
|
238
|
+
"""What cg can read from a SCIP index: documents, occurrences with a usable position, definitions, and the
|
|
239
|
+
warning `cg coverage` would show."""
|
|
240
|
+
from .plugins.native import scipread
|
|
241
|
+
p = Path(path)
|
|
242
|
+
out = {"path": str(p)}
|
|
243
|
+
try:
|
|
244
|
+
idx = scipread.load(p)
|
|
245
|
+
except Exception as e: # noqa: BLE001
|
|
246
|
+
out["warning"] = f"SCIP index {p.name}: cannot be read ({type(e).__name__}: {str(e)[:200]})"
|
|
247
|
+
return out
|
|
248
|
+
ndefs = sum(len(v) for v in idx.defs.values())
|
|
249
|
+
out.update(tool=f"{idx.tool} {idx.version}".strip(), documents=len(idx.docs), occurrences=idx.occurrences,
|
|
250
|
+
positioned=idx.positioned, definitions=ndefs)
|
|
251
|
+
w = scipread.health_warning(p.name, idx.occurrences, idx.positioned, ndefs)
|
|
252
|
+
if w:
|
|
253
|
+
out["warning"] = w
|
|
254
|
+
return out
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _mark_broken(langs: list[dict], imports: dict) -> None:
|
|
258
|
+
"""A language whose plugin (or the indexer itself) does not import cannot be indexed, whatever its tools."""
|
|
259
|
+
failed = imports["failed"]
|
|
260
|
+
if not failed:
|
|
261
|
+
return
|
|
262
|
+
index_err = failed.get("codegraph.indexer")
|
|
263
|
+
for x in langs:
|
|
264
|
+
own = [m for m in failed for p in PLUGIN_PACKAGES.get(x["language"], ())
|
|
265
|
+
if m == f"codegraph.plugins.{p}" or m.startswith(f"codegraph.plugins.{p}.")]
|
|
266
|
+
if not own and not index_err:
|
|
267
|
+
continue
|
|
268
|
+
x["mode"] = "broken"
|
|
269
|
+
x["why"] = (f"{own[0]} does not import: {failed[own[0]]}" if own else
|
|
270
|
+
f"cg index cannot load (codegraph.indexer: {index_err})")
|
|
271
|
+
x["fix"] = "see `cg modules` above"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
UPGRADE_HINT = ("upgrade cg (`uv tool upgrade cg-code-graph` / `install.sh --update`); if it persists, report it with the "
|
|
275
|
+
"`cg doctor` output, and meanwhile reinstall cg under a newer Python (`uv tool install --python 3.12 ...`)")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
SCIP_JAVA_RANGES = {12: "0.12: Kotlin <= 2.1", 13: "0.13: Kotlin 2.2.0 - 2.2.10"}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def project_checks(root: Path, present: set) -> list[dict]:
|
|
282
|
+
"""`cg doctor <root>`: what the project gives the exact layers and the TypeScript program (#65)."""
|
|
283
|
+
out = []
|
|
284
|
+
|
|
285
|
+
def add(lang, ok, what, fix=None):
|
|
286
|
+
out.append({"language": lang, "ok": ok, "what": what, **({"fix": fix} if fix else {})})
|
|
287
|
+
if "typescript" in present:
|
|
288
|
+
from .plugins.ts.plugin import sub_tsconfigs
|
|
289
|
+
cfgs = [c for c in ("tsconfig.json", "jsconfig.json") if (root / c).is_file()]
|
|
290
|
+
subs = sub_tsconfigs(root) if not cfgs else []
|
|
291
|
+
if cfgs:
|
|
292
|
+
add("typescript", True, f"{cfgs[0]} at the root: one program from it")
|
|
293
|
+
elif subs:
|
|
294
|
+
add("typescript", True, f"no root tsconfig; {len(subs)} package tsconfig(s) indexed as one program: "
|
|
295
|
+
+ ", ".join(subs[:5]) + (" …" if len(subs) > 5 else ""))
|
|
296
|
+
else:
|
|
297
|
+
add("typescript", False, "no tsconfig.json / jsconfig.json: plain-JS defaults over src/ and app/",
|
|
298
|
+
"add a tsconfig.json (or a .cg.yaml `include:`) when the code lives elsewhere")
|
|
299
|
+
if "kotlin" in present:
|
|
300
|
+
from .plugins.kotlin.exact import BUILD_FILES, android_modules, kotlin_version
|
|
301
|
+
builds = [f for f in BUILD_FILES if (root / f).is_file()]
|
|
302
|
+
if not builds:
|
|
303
|
+
add("kotlin", False, "no Gradle / Maven build file at the root: scip-java cannot run (heuristic mode)",
|
|
304
|
+
"run cg on the directory holding settings.gradle(.kts) / build.gradle(.kts) / pom.xml")
|
|
305
|
+
else:
|
|
306
|
+
kv = kotlin_version(root)
|
|
307
|
+
andr = android_modules(root)
|
|
308
|
+
add("kotlin", True, f"{builds[0]} at the root" + (f"; Kotlin {'.'.join(map(str, kv))}" if kv else
|
|
309
|
+
"; Kotlin version not declared in the root build files")
|
|
310
|
+
+ (f"; Android modules (no scip-java variant, heuristic there): {', '.join(andr[:4])}" if andr else ""))
|
|
311
|
+
from .plugins.kotlin.exact import _generation, _supports, scip_java_candidates
|
|
312
|
+
tools = scip_java_candidates()
|
|
313
|
+
if tools: # #67: each scip-java release's Kotlin plugin loads into a narrow range
|
|
314
|
+
gens = [(t, _generation(t)) for t in tools]
|
|
315
|
+
desc = "; ".join(f"{t} ({SCIP_JAVA_RANGES.get(g, '?')})" for t, g in gens)
|
|
316
|
+
fits = [t for t, g in gens if _supports(g, kv)]
|
|
317
|
+
kvs = ".".join(map(str, kv)) if kv else "undeclared"
|
|
318
|
+
if fits:
|
|
319
|
+
add("kotlin", True, f"scip-java for Kotlin {kvs}: {fits[0]} (installed: {desc})")
|
|
320
|
+
else:
|
|
321
|
+
add("kotlin", False, f"no installed scip-java loads into Kotlin {kvs} (installed: {desc})",
|
|
322
|
+
"Kotlin 2.2.20+ has no released scip-java yet (docs/kotlin.md#exact-mode); "
|
|
323
|
+
"pass --scip index.scip from another indexer" if kv and kv >= (2, 2, 20) else
|
|
324
|
+
"install the scip-java release for this Kotlin version next to the other one "
|
|
325
|
+
"(scip-java-<version>/scip-java in ~/tools), docs/kotlin.md#exact-mode")
|
|
326
|
+
if "swift" in present:
|
|
327
|
+
from .xcode import _manifests, _projects
|
|
328
|
+
pk, xp = _manifests(root), _projects(root)
|
|
329
|
+
if (root / "Package.swift").is_file():
|
|
330
|
+
add("swift", True, "Package.swift at the root: `swift build` can produce the index store (CODEGRAPH_SWIFT_INDEX=1)"
|
|
331
|
+
+ (f"; also {len(xp)} Xcode project(s)" if xp else ""))
|
|
332
|
+
elif xp or pk:
|
|
333
|
+
where = ", ".join([p.parent.parent.relative_to(root).as_posix() or "." for p in xp][:3]
|
|
334
|
+
+ [p.parent.relative_to(root).as_posix() for p in pk][:3])
|
|
335
|
+
add("swift", False, f"no Package.swift at the root ({len(pk)} manifest(s), {len(xp)} Xcode project(s): {where}): "
|
|
336
|
+
"the exact layer builds a root package only and runs no Xcode build (heuristic mode)",
|
|
337
|
+
"point CODEGRAPH_SWIFT_INDEX_STORE at the index store of an Xcode build (DerivedData/<app>/Index.noindex/DataStore)")
|
|
338
|
+
else:
|
|
339
|
+
add("swift", False, "no Package.swift or .xcodeproj found (heuristic mode; platforms from file conditions only)")
|
|
340
|
+
return out
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def report(root: str | Path | None = None, scip: list | None = None) -> dict:
|
|
344
|
+
rootp = Path(root).resolve() if root else None
|
|
345
|
+
cfg, cfg_error, present = {}, None, None
|
|
346
|
+
if rootp is not None:
|
|
347
|
+
from .config import ConfigError, load
|
|
348
|
+
from .coverage import SUPPORTED, scan
|
|
349
|
+
try:
|
|
350
|
+
cfg = load(rootp)
|
|
351
|
+
except ConfigError as e:
|
|
352
|
+
cfg_error = str(e)
|
|
353
|
+
counts = scan(rootp)
|
|
354
|
+
present = {lang for lang, exts in SUPPORTED.items() if any(counts.get(e) for e in exts)}
|
|
355
|
+
tools = _tools()
|
|
356
|
+
langs = _languages(tools, rootp, cfg)
|
|
357
|
+
imports = module_imports()
|
|
358
|
+
_mark_broken(langs, imports)
|
|
359
|
+
if present is not None:
|
|
360
|
+
langs = [x for x in langs if x["language"] in present]
|
|
361
|
+
pkg = Path(__file__).resolve().parent
|
|
362
|
+
return {
|
|
363
|
+
"cg": __version__, "python": platform.python_version(), "python_executable": sys.executable,
|
|
364
|
+
"package": str(pkg), "checkout": (pkg.parent / ".git").exists(),
|
|
365
|
+
"os": f"{platform.system()} {platform.machine()}",
|
|
366
|
+
"extractors": {k: extractors.status(k) for k in extractors.SPECS}, "cache": str(extractors.cache_root()),
|
|
367
|
+
"cache_usage": cache.usage(),
|
|
368
|
+
"tools": tools, "python_modules": {m: _module(m) for m in (*PIP_NAMES, "yaml", "mcp")},
|
|
369
|
+
"modules": imports, "root": str(rootp) if rootp else None, "config_error": cfg_error, "languages": langs,
|
|
370
|
+
**({"project": project_checks(rootp, present)} if rootp is not None else {}),
|
|
371
|
+
"update": "uv tool upgrade cg-code-graph | pipx upgrade cg-code-graph | install.sh --update",
|
|
372
|
+
**({"scip": [scip_health(x) for x in scip]} if scip else {}),
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def render(r: dict) -> str:
|
|
377
|
+
out = [f"cg {r['cg']} (Python {r['python']}, {r['os']}; {r['package']}{', checkout' if r['checkout'] else ''})"]
|
|
378
|
+
out.append("tools:")
|
|
379
|
+
for k, v in r["tools"].items():
|
|
380
|
+
out.append(f" {k:<14} {v['version'] or 'not found'}" + (f" ({v['path']})" if v["path"] else "")
|
|
381
|
+
+ (" [does not run]" if v.get("runs") is False else ""))
|
|
382
|
+
out.append(f"extractor dependencies (cache {r['cache']}):")
|
|
383
|
+
for k, v in r["extractors"].items():
|
|
384
|
+
out.append(f" {k:<14} {'installed' if v['installed'] else 'not installed'} ({v['where']}: {v['dir']})")
|
|
385
|
+
cu = r.get("cache_usage")
|
|
386
|
+
if cu:
|
|
387
|
+
kinds = ", ".join(f"{k} {cache.human(v['bytes'])}" for k, v in cu["kinds"].items())
|
|
388
|
+
out.append(f"cache: {cache.human(cu['bytes'])} in {cu['root']}" + (f" ({kinds})" if kinds else "")
|
|
389
|
+
+ (f"; {cache.human(cu['stale_bytes'])} stale (`cg clean --stale`)" if cu["stale_bytes"] else ""))
|
|
390
|
+
mods = r["python_modules"]
|
|
391
|
+
missing = sorted(m for m, ok in mods.items() if not ok)
|
|
392
|
+
out.append("python modules: " + ("all present" if not missing else "missing " + ", ".join(missing)))
|
|
393
|
+
imp = r.get("modules") or {"modules": 0, "failed": {}}
|
|
394
|
+
if not imp["failed"]:
|
|
395
|
+
out.append(f"cg modules: all {imp['modules']} import on Python {r['python']}")
|
|
396
|
+
else:
|
|
397
|
+
out.append(f"cg modules: {len(imp['failed'])} of {imp['modules']} do not import on Python {r['python']}; "
|
|
398
|
+
"`cg index` fails for every language that loads them:")
|
|
399
|
+
for m, err in sorted(imp["failed"].items()):
|
|
400
|
+
out.append(f" {m}: {err}")
|
|
401
|
+
out.append(f" fix: {UPGRADE_HINT}")
|
|
402
|
+
if r.get("config_error"):
|
|
403
|
+
out.append(f"config: {r['config_error']}")
|
|
404
|
+
out.append("languages" + (f" in {r['root']}:" if r["root"] else ":"))
|
|
405
|
+
if not r["languages"]:
|
|
406
|
+
out.append(" (no source files of a supported language found)")
|
|
407
|
+
for x in r["languages"]:
|
|
408
|
+
out.append(f" {x['language']:<11} {x['mode']:<11} {x['why']}")
|
|
409
|
+
if x.get("fix"):
|
|
410
|
+
out.append(f" {'':<11} {'':<11} fix: {x['fix']}")
|
|
411
|
+
if r.get("project"):
|
|
412
|
+
out.append("project:")
|
|
413
|
+
for x in r["project"]:
|
|
414
|
+
out.append(f" {x['language']:<11} {'ok' if x['ok'] else 'check':<11} {x['what']}")
|
|
415
|
+
if x.get("fix"):
|
|
416
|
+
out.append(f" {'':<11} {'':<11} fix: {x['fix']}")
|
|
417
|
+
for x in r.get("scip") or ():
|
|
418
|
+
if "documents" in x:
|
|
419
|
+
out.append(f"scip {x['path']}: {x['tool']}, {x['documents']} documents, {x['occurrences']} occurrences "
|
|
420
|
+
f"({x['positioned']} with a usable position), {x['definitions']} definitions")
|
|
421
|
+
if x.get("warning"):
|
|
422
|
+
out.append(f" warning: {x['warning']}")
|
|
423
|
+
out.append(f"update: {r['update']} ({INSTALL_DOC})")
|
|
424
|
+
return "\n".join(out)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def setup(langs: list[str] | None = None, quiet: bool = False) -> int:
|
|
428
|
+
"""`cg setup [typescript php dart]`: install the extractor dependencies for the languages whose toolchain is
|
|
429
|
+
present (all three by default); returns 1 if an explicitly named one failed."""
|
|
430
|
+
from .plugins.dart.plugin import DartPlugin, find_dart
|
|
431
|
+
want = langs or list(extractors.SPECS)
|
|
432
|
+
rc = 0
|
|
433
|
+
for lang in want:
|
|
434
|
+
tool = {"typescript": "npm", "php": "composer", "dart": None}[lang]
|
|
435
|
+
need = {"typescript": "node", "php": "php", "dart": None}[lang]
|
|
436
|
+
dart = find_dart() if lang == "dart" else None
|
|
437
|
+
if (need and not shutil.which(need)) or (tool and not shutil.which(tool) and not extractors.status(lang)["installed"]) \
|
|
438
|
+
or (lang == "dart" and not dart):
|
|
439
|
+
msg = f"{lang}: skipped ({'dart' if lang == 'dart' else need if need and not shutil.which(need) else tool} not installed)"
|
|
440
|
+
print(msg, file=sys.stderr)
|
|
441
|
+
rc = rc or (1 if langs else 0)
|
|
442
|
+
continue
|
|
443
|
+
try:
|
|
444
|
+
if lang == "dart":
|
|
445
|
+
d = Path(DartPlugin().ensure_extractor(dart)).parent.parent
|
|
446
|
+
else:
|
|
447
|
+
d = extractors.ensure(lang)
|
|
448
|
+
if not quiet:
|
|
449
|
+
print(f"{lang}: ready ({d})")
|
|
450
|
+
except (RuntimeError, OSError) as e:
|
|
451
|
+
print(f"{lang}: failed: {e}", file=sys.stderr)
|
|
452
|
+
rc = 1
|
|
453
|
+
return rc
|