cg-code-graph 0.10.1__py3-none-any.whl

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