scriptonia 0.8.0 → 0.9.0-rc.2
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.
- package/LICENSE +21 -0
- package/README.md +43 -13
- package/assets/grammar-manifest.json +32 -0
- package/assets/grammars/tree-sitter-bash.wasm +0 -0
- package/assets/grammars/tree-sitter-c.wasm +0 -0
- package/assets/grammars/tree-sitter-cpp.wasm +0 -0
- package/assets/grammars/tree-sitter-dart.wasm +0 -0
- package/assets/grammars/tree-sitter-go.wasm +0 -0
- package/assets/grammars/tree-sitter-java.wasm +0 -0
- package/assets/grammars/tree-sitter-javascript.wasm +0 -0
- package/assets/grammars/tree-sitter-kotlin.wasm +0 -0
- package/assets/grammars/tree-sitter-objc.wasm +0 -0
- package/assets/grammars/tree-sitter-python.wasm +0 -0
- package/assets/grammars/tree-sitter-rust.wasm +0 -0
- package/assets/grammars/tree-sitter-swift.wasm +0 -0
- package/assets/grammars/tree-sitter-tsx.wasm +0 -0
- package/assets/grammars/tree-sitter-typescript.wasm +0 -0
- package/assets/licenses/tree-sitter-wasms-LICENSE +24 -0
- package/assets/licenses/web-tree-sitter-LICENSE +21 -0
- package/assets/queries/manifest.json +12 -0
- package/assets/queries/v1/dart.scm +7 -0
- package/assets/queries/v1/go.scm +5 -0
- package/assets/queries/v1/javascript.scm +15 -0
- package/assets/queries/v1/python.scm +5 -0
- package/assets/queries/v1/typescript.scm +17 -0
- package/assets/tree-sitter.wasm +0 -0
- package/bin/scriptonia.mjs +243 -76
- package/bin/verify.mjs +40 -6
- package/dist/brain-ast-worker.js +62 -0
- package/dist/brain-ast-worker.js.map +1 -0
- package/dist/chunk-6TLQP3LV.js +4048 -0
- package/dist/chunk-6TLQP3LV.js.map +1 -0
- package/dist/chunk-ZLKAW77A.js +16613 -0
- package/dist/chunk-ZLKAW77A.js.map +1 -0
- package/dist/index.js +2431 -0
- package/dist/index.js.map +1 -0
- package/dist/legacy-brain-bridge.js +156 -0
- package/dist/legacy-brain-bridge.js.map +1 -0
- package/package.json +78 -5
|
@@ -0,0 +1,4048 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../packages/core/src/index.ts
|
|
4
|
+
import { createHash, randomBytes } from "crypto";
|
|
5
|
+
import path from "path";
|
|
6
|
+
function canonicalize(value) {
|
|
7
|
+
return JSON.stringify(normalize(value));
|
|
8
|
+
}
|
|
9
|
+
function normalize(value) {
|
|
10
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return typeof value === "string" ? value.replace(/\r\n?/g, "\n") : value;
|
|
11
|
+
if (typeof value === "number") {
|
|
12
|
+
if (!Number.isFinite(value)) throw new TypeError("canonical JSON rejects non-finite numbers");
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(value)) return value.map(normalize);
|
|
16
|
+
if (typeof value === "object") {
|
|
17
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, normalize(item)]));
|
|
18
|
+
}
|
|
19
|
+
throw new TypeError(`canonical JSON rejects ${typeof value}`);
|
|
20
|
+
}
|
|
21
|
+
function sha256(value) {
|
|
22
|
+
return createHash("sha256").update(value).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
function hashObject(value) {
|
|
25
|
+
return sha256(canonicalize(value));
|
|
26
|
+
}
|
|
27
|
+
function createId(prefix, at = Date.now()) {
|
|
28
|
+
return `${prefix}_${at.toString(36).padStart(9, "0")}${randomBytes(8).toString("hex")}`;
|
|
29
|
+
}
|
|
30
|
+
function normalizeRepoPath(input) {
|
|
31
|
+
if (input.includes("\0")) throw new Error("repository path contains a null byte");
|
|
32
|
+
const normalized = input.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
33
|
+
if (path.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
34
|
+
throw new Error(`path escapes repository: ${input}`);
|
|
35
|
+
}
|
|
36
|
+
return path.posix.normalize(normalized);
|
|
37
|
+
}
|
|
38
|
+
function assertInside(root, candidate) {
|
|
39
|
+
const resolvedRoot = path.resolve(root);
|
|
40
|
+
const resolved = path.resolve(candidate);
|
|
41
|
+
if (resolved !== resolvedRoot && !resolved.startsWith(`${resolvedRoot}${path.sep}`)) throw new Error(`path escapes repository: ${candidate}`);
|
|
42
|
+
return resolved;
|
|
43
|
+
}
|
|
44
|
+
function aggregateGate(checks, incompleteBlocks = true) {
|
|
45
|
+
if (checks.some((item) => item.state === "FAIL" && item.enforcement === "block")) return "BLOCKED";
|
|
46
|
+
if (checks.some((item) => item.state === "FAIL" && item.enforcement === "action_required")) return "ACTION_REQUIRED";
|
|
47
|
+
if (checks.some((item) => item.state === "ERROR" || incompleteBlocks && item.state === "INCONCLUSIVE" && item.enforcement === "block")) return "INCOMPLETE";
|
|
48
|
+
if (checks.some((item) => item.state === "FAIL" || item.state === "INCONCLUSIVE")) return "PASS_WITH_WARNINGS";
|
|
49
|
+
return "PASS";
|
|
50
|
+
}
|
|
51
|
+
function exitCodeForGate(state) {
|
|
52
|
+
return state === "PASS" || state === "PASS_WITH_WARNINGS" ? 0 : state === "ACTION_REQUIRED" ? 2 : state === "BLOCKED" ? 3 : 4;
|
|
53
|
+
}
|
|
54
|
+
function nowIso() {
|
|
55
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ../packages/brain-census/src/walk.ts
|
|
59
|
+
import fs from "fs";
|
|
60
|
+
import path3 from "path";
|
|
61
|
+
import { createHash as createHash2 } from "crypto";
|
|
62
|
+
import { execFileSync } from "child_process";
|
|
63
|
+
import ignore from "ignore";
|
|
64
|
+
|
|
65
|
+
// ../packages/brain-census/src/languages.ts
|
|
66
|
+
import path2 from "path";
|
|
67
|
+
var EXTENSIONS = /* @__PURE__ */ new Map([
|
|
68
|
+
[".c", "c"],
|
|
69
|
+
[".h", "cpp"],
|
|
70
|
+
[".cc", "cpp"],
|
|
71
|
+
[".cp", "cpp"],
|
|
72
|
+
[".cpp", "cpp"],
|
|
73
|
+
[".cxx", "cpp"],
|
|
74
|
+
[".hh", "cpp"],
|
|
75
|
+
[".hpp", "cpp"],
|
|
76
|
+
[".hxx", "cpp"],
|
|
77
|
+
[".inc", "cpp"],
|
|
78
|
+
[".m", "objective-c"],
|
|
79
|
+
[".mm", "objective-c"],
|
|
80
|
+
[".cs", "csharp"],
|
|
81
|
+
[".css", "css"],
|
|
82
|
+
[".scss", "css"],
|
|
83
|
+
[".sass", "css"],
|
|
84
|
+
[".less", "css"],
|
|
85
|
+
[".dart", "dart"],
|
|
86
|
+
[".ex", "elixir"],
|
|
87
|
+
[".exs", "elixir"],
|
|
88
|
+
[".go", "go"],
|
|
89
|
+
[".graphql", "graphql"],
|
|
90
|
+
[".gql", "graphql"],
|
|
91
|
+
[".html", "html"],
|
|
92
|
+
[".htm", "html"],
|
|
93
|
+
[".java", "java"],
|
|
94
|
+
[".js", "javascript"],
|
|
95
|
+
[".jsx", "javascript"],
|
|
96
|
+
[".mjs", "javascript"],
|
|
97
|
+
[".cjs", "javascript"],
|
|
98
|
+
[".json", "json"],
|
|
99
|
+
[".jsonc", "json"],
|
|
100
|
+
[".kt", "kotlin"],
|
|
101
|
+
[".kts", "kotlin"],
|
|
102
|
+
[".lua", "lua"],
|
|
103
|
+
[".pl", "perl"],
|
|
104
|
+
[".pm", "perl"],
|
|
105
|
+
[".php", "php"],
|
|
106
|
+
[".proto", "proto"],
|
|
107
|
+
[".py", "python"],
|
|
108
|
+
[".pyi", "python"],
|
|
109
|
+
[".rb", "ruby"],
|
|
110
|
+
[".rs", "rust"],
|
|
111
|
+
[".scala", "scala"],
|
|
112
|
+
[".sh", "shell"],
|
|
113
|
+
[".bash", "shell"],
|
|
114
|
+
[".zsh", "shell"],
|
|
115
|
+
[".fish", "shell"],
|
|
116
|
+
[".sql", "sql"],
|
|
117
|
+
[".bzl", "starlark"],
|
|
118
|
+
[".bazel", "starlark"],
|
|
119
|
+
[".star", "starlark"],
|
|
120
|
+
[".swift", "swift"],
|
|
121
|
+
[".toml", "toml"],
|
|
122
|
+
[".ts", "typescript"],
|
|
123
|
+
[".tsx", "typescript"],
|
|
124
|
+
[".mts", "typescript"],
|
|
125
|
+
[".cts", "typescript"],
|
|
126
|
+
[".vue", "vue"],
|
|
127
|
+
[".xml", "xml"],
|
|
128
|
+
[".plist", "xml"],
|
|
129
|
+
[".yaml", "yaml"],
|
|
130
|
+
[".yml", "yaml"]
|
|
131
|
+
]);
|
|
132
|
+
var STARLARK_BASENAMES = /* @__PURE__ */ new Set(["BUILD", "WORKSPACE", "WORKSPACE.bzlmod", "MODULE.bazel"]);
|
|
133
|
+
var CONFIG_BASENAMES = /* @__PURE__ */ new Set([
|
|
134
|
+
".bazelignore",
|
|
135
|
+
".bazelrc",
|
|
136
|
+
".bazelversion",
|
|
137
|
+
".clang-format",
|
|
138
|
+
".clang-tidy",
|
|
139
|
+
".dockerignore",
|
|
140
|
+
".editorconfig",
|
|
141
|
+
".env.example",
|
|
142
|
+
".flake8",
|
|
143
|
+
".gitignore",
|
|
144
|
+
".npmrc",
|
|
145
|
+
".nvmrc",
|
|
146
|
+
".prettierrc",
|
|
147
|
+
".yamllint",
|
|
148
|
+
"CMakeLists.txt",
|
|
149
|
+
"CODEOWNERS",
|
|
150
|
+
"Cargo.toml",
|
|
151
|
+
"Dockerfile",
|
|
152
|
+
"Gemfile",
|
|
153
|
+
"Gemfile.lock",
|
|
154
|
+
"Makefile",
|
|
155
|
+
"Podfile",
|
|
156
|
+
"analysis_options.yaml",
|
|
157
|
+
"build.gradle",
|
|
158
|
+
"build.gradle.kts",
|
|
159
|
+
"composer.json",
|
|
160
|
+
"composer.lock",
|
|
161
|
+
"deps.yaml",
|
|
162
|
+
"extensions_metadata.yaml",
|
|
163
|
+
"go.mod",
|
|
164
|
+
"go.sum",
|
|
165
|
+
"gradle.properties",
|
|
166
|
+
"MODULE.bazel.lock",
|
|
167
|
+
"package-lock.json",
|
|
168
|
+
"package.json",
|
|
169
|
+
"pnpm-lock.yaml",
|
|
170
|
+
"repository_locations.bzl",
|
|
171
|
+
"pnpm-workspace.yaml",
|
|
172
|
+
"pom.xml",
|
|
173
|
+
"pubspec.lock",
|
|
174
|
+
"pubspec.yaml",
|
|
175
|
+
"pyproject.toml",
|
|
176
|
+
"requirements.txt",
|
|
177
|
+
"settings.gradle",
|
|
178
|
+
"settings.gradle.kts",
|
|
179
|
+
"tsconfig.json",
|
|
180
|
+
"yarn.lock"
|
|
181
|
+
]);
|
|
182
|
+
var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
183
|
+
".7z",
|
|
184
|
+
".a",
|
|
185
|
+
".avi",
|
|
186
|
+
".bin",
|
|
187
|
+
".bmp",
|
|
188
|
+
".cer",
|
|
189
|
+
".class",
|
|
190
|
+
".crt",
|
|
191
|
+
".der",
|
|
192
|
+
".dylib",
|
|
193
|
+
".eot",
|
|
194
|
+
".exe",
|
|
195
|
+
".fuzz",
|
|
196
|
+
".gif",
|
|
197
|
+
".gold",
|
|
198
|
+
".gz",
|
|
199
|
+
".ico",
|
|
200
|
+
".jar",
|
|
201
|
+
".jpeg",
|
|
202
|
+
".jpg",
|
|
203
|
+
".key",
|
|
204
|
+
".lockb",
|
|
205
|
+
".mmdb",
|
|
206
|
+
".mov",
|
|
207
|
+
".mp3",
|
|
208
|
+
".mp4",
|
|
209
|
+
".o",
|
|
210
|
+
".otf",
|
|
211
|
+
".p12",
|
|
212
|
+
".pdf",
|
|
213
|
+
".pem",
|
|
214
|
+
".pfx",
|
|
215
|
+
".png",
|
|
216
|
+
".so",
|
|
217
|
+
".tar",
|
|
218
|
+
".tgz",
|
|
219
|
+
".ttf",
|
|
220
|
+
".wav",
|
|
221
|
+
".webm",
|
|
222
|
+
".webp",
|
|
223
|
+
".woff",
|
|
224
|
+
".woff2",
|
|
225
|
+
".xz",
|
|
226
|
+
".zip"
|
|
227
|
+
]);
|
|
228
|
+
var CERTIFICATE_EXTENSIONS = /* @__PURE__ */ new Set([".cer", ".crt", ".der", ".key", ".p12", ".pem", ".pfx"]);
|
|
229
|
+
var CORPUS_EXTENSIONS = /* @__PURE__ */ new Set([".fuzz", ".gold"]);
|
|
230
|
+
function detectLanguage(filePath, sample = "") {
|
|
231
|
+
const base = path2.posix.basename(filePath);
|
|
232
|
+
if (STARLARK_BASENAMES.has(base) || base === "BUILD.bazel") return "starlark";
|
|
233
|
+
const extension = path2.posix.extname(base).toLowerCase();
|
|
234
|
+
const known = EXTENSIONS.get(extension);
|
|
235
|
+
if (known) return known;
|
|
236
|
+
const firstLine = sample.split(/\r?\n/, 1)[0] ?? "";
|
|
237
|
+
if (/^#!.*\bpython(?:3)?\b/.test(firstLine)) return "python";
|
|
238
|
+
if (/^#!.*\b(?:bash|sh|zsh|fish)\b/.test(firstLine)) return "shell";
|
|
239
|
+
if (/^#!.*\bnode\b/.test(firstLine)) return "javascript";
|
|
240
|
+
if (/^#!.*\bruby\b/.test(firstLine)) return "ruby";
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
function classifyFile(filePath, sample, binary) {
|
|
244
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
245
|
+
const lower = normalized.toLowerCase();
|
|
246
|
+
const base = path2.posix.basename(normalized);
|
|
247
|
+
const extension = path2.posix.extname(base).toLowerCase();
|
|
248
|
+
const language = detectLanguage(normalized, sample);
|
|
249
|
+
if (binary || ASSET_EXTENSIONS.has(extension)) return "asset";
|
|
250
|
+
if (isManifestConfigPath(normalized) || CONFIG_BASENAMES.has(base) || STARLARK_BASENAMES.has(base) || /\.(?:lock|config|conf|ini|properties)$/i.test(base)) return "config";
|
|
251
|
+
if (/(?:^|\/)(?:third_party|vendor|external)\//i.test(normalized)) return "vendor";
|
|
252
|
+
if (/(?:^|\/)generated(?:\/|$)/i.test(normalized) || /\.(?:g|freezed)\.dart$|\.pb\.go$|\.generated\.[^.]+$|\.min\.(?:js|css)$/i.test(base) || /generated|do not edit/i.test(sample.split(/\r?\n/).slice(0, 3).join("\n"))) return "generated";
|
|
253
|
+
if (/\.(?:md|mdx|rst|adoc|txt)$/i.test(extension) || /(?:^|\/)(?:docs?|adr|changelogs?)(?:\/|$)/i.test(normalized)) return "docs";
|
|
254
|
+
if (language && (/(?:^|\/)(?:test|tests|__tests__|spec|integration_test)(?:\/|$)/i.test(normalized) || /(?:_test\.dart|\.(?:test|spec)\.[cm]?[jt]sx?|test_[^/]+\.py)$/i.test(base))) return "test";
|
|
255
|
+
if (language) return "source";
|
|
256
|
+
if (lower.includes("/assets/") || lower.includes("/images/")) return "asset";
|
|
257
|
+
return "asset";
|
|
258
|
+
}
|
|
259
|
+
function analysisPolicy(filePath, fileClass, binary, oversized = false) {
|
|
260
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
261
|
+
const extension = path2.posix.extname(path2.posix.basename(normalized)).toLowerCase();
|
|
262
|
+
if (/(?:^|\/)(?:[^/]*_corpus|corpus)(?:\/|$)/i.test(normalized) || CORPUS_EXTENSIONS.has(extension)) {
|
|
263
|
+
return { analysisMode: "metadata_only", metadataOnlyReason: "corpus" };
|
|
264
|
+
}
|
|
265
|
+
if (CERTIFICATE_EXTENSIONS.has(extension)) {
|
|
266
|
+
return { analysisMode: "metadata_only", metadataOnlyReason: "certificate" };
|
|
267
|
+
}
|
|
268
|
+
if (fileClass === "generated" || /(?:^|\/)generated(?:\/|$)/i.test(normalized)) {
|
|
269
|
+
return { analysisMode: "metadata_only", metadataOnlyReason: "generated" };
|
|
270
|
+
}
|
|
271
|
+
if (fileClass === "config") return { analysisMode: "full" };
|
|
272
|
+
if (oversized) return { analysisMode: "metadata_only", metadataOnlyReason: "oversized" };
|
|
273
|
+
if (binary || fileClass === "asset") return { analysisMode: "metadata_only", metadataOnlyReason: "binary_asset" };
|
|
274
|
+
return { analysisMode: "full" };
|
|
275
|
+
}
|
|
276
|
+
function isKnownManifest(filePath) {
|
|
277
|
+
const base = path2.posix.basename(filePath);
|
|
278
|
+
return CONFIG_BASENAMES.has(base) || STARLARK_BASENAMES.has(base) || isManifestConfigPath(filePath);
|
|
279
|
+
}
|
|
280
|
+
function isManifestConfigPath(filePath) {
|
|
281
|
+
const base = path2.posix.basename(filePath);
|
|
282
|
+
return /(?:^|\/)BUILD(?:\.bazel)?$/i.test(filePath) || /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(filePath) || /^(?:Dockerfile(?:\..+)?|requirements(?:[-_.][^/]*)?\.txt|tsconfig(?:\.[^/]+)?\.json)$/i.test(base) || /^(?:docker-)?compose(?:\.[^/]+)?\.ya?ml$/i.test(base);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ../packages/brain-census/src/walk.ts
|
|
286
|
+
var NEVER_DESCEND = /* @__PURE__ */ new Set([
|
|
287
|
+
".git",
|
|
288
|
+
".scriptonia",
|
|
289
|
+
".dart_tool",
|
|
290
|
+
".next",
|
|
291
|
+
".turbo",
|
|
292
|
+
".venv",
|
|
293
|
+
"__pycache__",
|
|
294
|
+
"DerivedData",
|
|
295
|
+
"Microsoft VS Code",
|
|
296
|
+
"Pods",
|
|
297
|
+
"bazel-bin",
|
|
298
|
+
"bazel-out",
|
|
299
|
+
"bazel-testlogs",
|
|
300
|
+
"node_modules"
|
|
301
|
+
]);
|
|
302
|
+
var BUILTIN_IGNORE = [
|
|
303
|
+
".git/",
|
|
304
|
+
".scriptonia/",
|
|
305
|
+
".dart_tool/",
|
|
306
|
+
".next/",
|
|
307
|
+
".turbo/",
|
|
308
|
+
".venv/",
|
|
309
|
+
"**/__pycache__/",
|
|
310
|
+
"**/node_modules/",
|
|
311
|
+
"**/DerivedData/",
|
|
312
|
+
"**/Microsoft VS Code/",
|
|
313
|
+
"**/Pods/",
|
|
314
|
+
"**/.cache/",
|
|
315
|
+
"**/build/",
|
|
316
|
+
"**/coverage/",
|
|
317
|
+
"**/dist/",
|
|
318
|
+
"bazel-bin/",
|
|
319
|
+
"bazel-out/",
|
|
320
|
+
"bazel-testlogs/"
|
|
321
|
+
];
|
|
322
|
+
var DEFAULT_MAX_ASSET_BYTES = 2 * 1024 * 1024;
|
|
323
|
+
function walkRepo(inputRoot, options = {}) {
|
|
324
|
+
const started = performance.now();
|
|
325
|
+
const root = fs.realpathSync(inputRoot);
|
|
326
|
+
const matcher = ignore().add(BUILTIN_IGNORE).add(options.extraIgnore ?? []);
|
|
327
|
+
const forcedMatcher = ignore().add(options.extraIgnore ?? []);
|
|
328
|
+
const tracked = gitTrackedPaths(root);
|
|
329
|
+
const trackedDirectories = /* @__PURE__ */ new Set();
|
|
330
|
+
for (const filename of tracked) {
|
|
331
|
+
let directory = path3.posix.dirname(filename);
|
|
332
|
+
while (directory !== ".") {
|
|
333
|
+
trackedDirectories.add(directory);
|
|
334
|
+
directory = path3.posix.dirname(directory);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const files = [];
|
|
338
|
+
const warnings = [];
|
|
339
|
+
let ignored = 0;
|
|
340
|
+
const maxAssetBytes = options.maxAssetBytes ?? DEFAULT_MAX_ASSET_BYTES;
|
|
341
|
+
const maxFileBytes = options.maxFileBytes ?? 128 * 1024 * 1024;
|
|
342
|
+
const maxTextBytes = options.maxTextBytes ?? 8 * 1024 * 1024;
|
|
343
|
+
const visit = (absoluteDir, relativeDir) => {
|
|
344
|
+
loadIgnoreFile(matcher, absoluteDir, relativeDir, warnings);
|
|
345
|
+
let entries;
|
|
346
|
+
try {
|
|
347
|
+
entries = fs.readdirSync(absoluteDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
348
|
+
} catch (error) {
|
|
349
|
+
warnings.push({ path: relativeDir || ".", kind: "unreadable", message: errorMessage(error) });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
for (const entry of entries) {
|
|
353
|
+
const relativeRaw = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
354
|
+
let relative;
|
|
355
|
+
try {
|
|
356
|
+
relative = normalizeRepoPath(relativeRaw);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
warnings.push({ path: relativeRaw, kind: "invalid_path", message: errorMessage(error) });
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const absolute = path3.join(root, ...relative.split("/"));
|
|
362
|
+
try {
|
|
363
|
+
assertInside(root, absolute);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
warnings.push({ path: relative, kind: "invalid_path", message: errorMessage(error) });
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
let stat;
|
|
369
|
+
try {
|
|
370
|
+
stat = fs.lstatSync(absolute);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
warnings.push({ path: relative, kind: "unreadable", message: errorMessage(error) });
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (stat.isSymbolicLink()) {
|
|
376
|
+
warnings.push({ path: relative, kind: "symlink", message: "symbolic link recorded as metadata and never followed" });
|
|
377
|
+
try {
|
|
378
|
+
const target = fs.readlinkSync(absolute);
|
|
379
|
+
const language = detectLanguage(relative);
|
|
380
|
+
const fileClass = classifyFile(relative, "", false);
|
|
381
|
+
const file = {
|
|
382
|
+
path: relative,
|
|
383
|
+
...language ? { language } : {},
|
|
384
|
+
class: fileClass,
|
|
385
|
+
analysisMode: "metadata_only",
|
|
386
|
+
metadataOnlyReason: "symlink",
|
|
387
|
+
size: Buffer.byteLength(target),
|
|
388
|
+
loc: 0,
|
|
389
|
+
sha256: createHash2("sha256").update("symlink\0").update(target).digest("hex"),
|
|
390
|
+
binary: false,
|
|
391
|
+
mtimeMs: stat.mtimeMs
|
|
392
|
+
};
|
|
393
|
+
files.push(file);
|
|
394
|
+
options.onFile?.(file);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
warnings.push({ path: relative, kind: "unreadable", message: errorMessage(error) });
|
|
397
|
+
}
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (entry.isDirectory()) {
|
|
401
|
+
if (NEVER_DESCEND.has(entry.name) || forcedMatcher.ignores(`${relative}/`) || matcher.ignores(`${relative}/`) && !trackedDirectories.has(relative)) {
|
|
402
|
+
ignored++;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
visit(absolute, relative);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (!entry.isFile()) continue;
|
|
409
|
+
if (forcedMatcher.ignores(relative) || matcher.ignores(relative) && entry.name !== ".gitignore" && !tracked.has(relative)) {
|
|
410
|
+
ignored++;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const manifest = isKnownManifest(relative);
|
|
414
|
+
const aboveAssetLimit = stat.size > maxAssetBytes && !manifest;
|
|
415
|
+
const oversized = stat.size > maxFileBytes && !manifest;
|
|
416
|
+
if (oversized) {
|
|
417
|
+
warnings.push({ path: relative, kind: "oversized", message: `file exceeds ${maxFileBytes} byte census limit` });
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
const inspectText = stat.size <= maxTextBytes || manifest;
|
|
421
|
+
const hashed = hashFile(absolute, inspectText);
|
|
422
|
+
const fileClass = classifyFile(relative, hashed.sample, hashed.binary);
|
|
423
|
+
if (aboveAssetLimit && fileClass === "asset") {
|
|
424
|
+
warnings.push({ path: relative, kind: "oversized", message: `asset or binary exceeds ${maxAssetBytes} byte full-analysis limit; retained as metadata` });
|
|
425
|
+
}
|
|
426
|
+
const file = {
|
|
427
|
+
path: relative,
|
|
428
|
+
language: detectLanguage(relative, hashed.sample),
|
|
429
|
+
class: fileClass,
|
|
430
|
+
...analysisPolicy(relative, fileClass, hashed.binary, oversized || !inspectText || aboveAssetLimit && fileClass === "asset"),
|
|
431
|
+
size: stat.size,
|
|
432
|
+
loc: hashed.loc,
|
|
433
|
+
sha256: hashed.sha256,
|
|
434
|
+
binary: hashed.binary,
|
|
435
|
+
mtimeMs: stat.mtimeMs
|
|
436
|
+
};
|
|
437
|
+
files.push(file);
|
|
438
|
+
options.onFile?.(file);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
warnings.push({ path: relative, kind: "unreadable", message: errorMessage(error) });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
visit(root, "");
|
|
445
|
+
return { root, files, warnings, ignored, elapsedMs: performance.now() - started };
|
|
446
|
+
}
|
|
447
|
+
function gitTrackedPaths(root) {
|
|
448
|
+
try {
|
|
449
|
+
const output = execFileSync("git", ["-C", root, "ls-files", "--cached", "-z"], {
|
|
450
|
+
encoding: "utf8",
|
|
451
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
452
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
453
|
+
});
|
|
454
|
+
return new Set(output.split("\0").filter(Boolean).map((filename) => normalizeRepoPath(filename)));
|
|
455
|
+
} catch {
|
|
456
|
+
return /* @__PURE__ */ new Set();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function hashFile(filename, inspectText) {
|
|
460
|
+
const hash = createHash2("sha256");
|
|
461
|
+
const fd = fs.openSync(filename, "r");
|
|
462
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
463
|
+
let position = 0;
|
|
464
|
+
let sample = Buffer.alloc(0);
|
|
465
|
+
let binary = false;
|
|
466
|
+
let lineFeeds = 0;
|
|
467
|
+
try {
|
|
468
|
+
while (true) {
|
|
469
|
+
const bytes = fs.readSync(fd, buffer, 0, buffer.length, position);
|
|
470
|
+
if (!bytes) break;
|
|
471
|
+
position += bytes;
|
|
472
|
+
const chunk = buffer.subarray(0, bytes);
|
|
473
|
+
hash.update(chunk);
|
|
474
|
+
if (sample.length < 8192) sample = Buffer.concat([sample, chunk.subarray(0, 8192 - sample.length)]);
|
|
475
|
+
if (!binary && chunk.includes(0)) binary = true;
|
|
476
|
+
if (inspectText && !binary) {
|
|
477
|
+
for (const byte of chunk) if (byte === 10) lineFeeds++;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
} finally {
|
|
481
|
+
fs.closeSync(fd);
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
sha256: hash.digest("hex"),
|
|
485
|
+
loc: binary || !inspectText || position === 0 ? 0 : lineFeeds + 1,
|
|
486
|
+
binary,
|
|
487
|
+
sample: binary ? "" : sample.toString("utf8")
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function loadIgnoreFile(matcher, absoluteDir, relativeDir, warnings) {
|
|
491
|
+
const filename = path3.join(absoluteDir, ".gitignore");
|
|
492
|
+
if (!fs.existsSync(filename)) return;
|
|
493
|
+
try {
|
|
494
|
+
const lines = fs.readFileSync(filename, "utf8").split(/\r?\n/);
|
|
495
|
+
for (const line of lines) matcher.add(scopeIgnorePattern(line, relativeDir));
|
|
496
|
+
} catch (error) {
|
|
497
|
+
warnings.push({ path: relativeDir ? `${relativeDir}/.gitignore` : ".gitignore", kind: "unreadable", message: errorMessage(error) });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function scopeIgnorePattern(line, base) {
|
|
501
|
+
if (!base || !line || /^\s*#/.test(line)) return line;
|
|
502
|
+
const negated = line.startsWith("!");
|
|
503
|
+
const prefix = negated ? "!" : "";
|
|
504
|
+
const body = negated ? line.slice(1) : line;
|
|
505
|
+
if (!body) return line;
|
|
506
|
+
if (body.startsWith("/")) return `${prefix}${base}/${body.slice(1)}`;
|
|
507
|
+
if (body.includes("/")) return `${prefix}${base}/${body}`;
|
|
508
|
+
return [`${prefix}${base}/${body}`, `${prefix}${base}/**/${body}`];
|
|
509
|
+
}
|
|
510
|
+
function errorMessage(error) {
|
|
511
|
+
return error instanceof Error ? error.message : String(error);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ../packages/brain-census/src/manifests.ts
|
|
515
|
+
import fs2 from "fs";
|
|
516
|
+
import path4 from "path";
|
|
517
|
+
import { XMLParser } from "fast-xml-parser";
|
|
518
|
+
import YAML from "yaml";
|
|
519
|
+
import { z } from "zod";
|
|
520
|
+
var DEFAULT_MANIFEST_PARSE_LIMITS = Object.freeze({
|
|
521
|
+
maxManifestFiles: 2e4,
|
|
522
|
+
maxManifestBytes: 16 * 1024 * 1024,
|
|
523
|
+
maxTotalBytes: 256 * 1024 * 1024,
|
|
524
|
+
maxExtractedEntries: 1e5,
|
|
525
|
+
maxNestingDepth: 128,
|
|
526
|
+
maxYamlAliases: 100,
|
|
527
|
+
maxTsconfigExtendsDepth: 5,
|
|
528
|
+
maxLineBytes: 1024 * 1024
|
|
529
|
+
});
|
|
530
|
+
var manifestDependencySchema = z.object({
|
|
531
|
+
name: z.string().min(1),
|
|
532
|
+
declared: z.string().optional(),
|
|
533
|
+
resolved: z.string().optional(),
|
|
534
|
+
dev: z.boolean().default(false)
|
|
535
|
+
});
|
|
536
|
+
var manifestFrameworkSchema = z.object({
|
|
537
|
+
name: z.string().min(1),
|
|
538
|
+
version: z.string().optional(),
|
|
539
|
+
kind: z.enum(["api", "build", "database", "library", "mobile", "runtime", "test", "web"])
|
|
540
|
+
});
|
|
541
|
+
var manifestFactsSchema = z.object({
|
|
542
|
+
id: z.string().min(1),
|
|
543
|
+
path: z.string().min(1),
|
|
544
|
+
kind: z.string().min(1),
|
|
545
|
+
product: z.object({ name: z.string().optional(), version: z.string().optional() }).optional(),
|
|
546
|
+
dependencies: z.array(manifestDependencySchema),
|
|
547
|
+
frameworks: z.array(manifestFrameworkSchema),
|
|
548
|
+
runtimes: z.array(z.object({ name: z.string(), version: z.string().optional() })),
|
|
549
|
+
scripts: z.record(z.string(), z.string()).optional(),
|
|
550
|
+
workspaces: z.array(z.string()).optional(),
|
|
551
|
+
aliases: z.array(z.object({ prefix: z.string(), targets: z.array(z.string()) })).optional(),
|
|
552
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
553
|
+
errors: z.array(z.string())
|
|
554
|
+
});
|
|
555
|
+
var FRAMEWORKS = {
|
|
556
|
+
"@nestjs/core": { name: "NestJS", kind: "api" },
|
|
557
|
+
"@playwright/test": { name: "Playwright", kind: "test" },
|
|
558
|
+
"@sveltejs/kit": { name: "SvelteKit", kind: "web" },
|
|
559
|
+
"@vitejs/plugin-react": { name: "Vite", kind: "build" },
|
|
560
|
+
"com.android.tools.build:gradle": { name: "Android Gradle Plugin", kind: "build" },
|
|
561
|
+
django: { name: "Django", kind: "web" },
|
|
562
|
+
drizzle: { name: "Drizzle", kind: "database" },
|
|
563
|
+
express: { name: "Express", kind: "api" },
|
|
564
|
+
fastapi: { name: "FastAPI", kind: "api" },
|
|
565
|
+
fastify: { name: "Fastify", kind: "api" },
|
|
566
|
+
flutter: { name: "Flutter", kind: "mobile" },
|
|
567
|
+
gin: { name: "Gin", kind: "api" },
|
|
568
|
+
"junit:junit": { name: "JUnit", kind: "test" },
|
|
569
|
+
"org.junit.jupiter:junit-jupiter": { name: "JUnit Jupiter", kind: "test" },
|
|
570
|
+
"laravel/framework": { name: "Laravel", kind: "web" },
|
|
571
|
+
next: { name: "Next.js", kind: "web" },
|
|
572
|
+
prisma: { name: "Prisma", kind: "database" },
|
|
573
|
+
rails: { name: "Ruby on Rails", kind: "web" },
|
|
574
|
+
react: { name: "React", kind: "web" },
|
|
575
|
+
supabase_flutter: { name: "Supabase", kind: "database" },
|
|
576
|
+
"symfony/framework-bundle": { name: "Symfony", kind: "web" },
|
|
577
|
+
svelte: { name: "Svelte", kind: "web" },
|
|
578
|
+
vite: { name: "Vite", kind: "build" },
|
|
579
|
+
vitest: { name: "Vitest", kind: "test" },
|
|
580
|
+
vue: { name: "Vue", kind: "web" }
|
|
581
|
+
};
|
|
582
|
+
function parseManifests(rootInput, files, options = {}) {
|
|
583
|
+
const root = fs2.realpathSync(rootInput);
|
|
584
|
+
const limits = resolveLimits(options);
|
|
585
|
+
const detected = [...new Map(files.filter((file) => isParseableManifest(file.path)).map((file) => [file.path, file])).values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
586
|
+
const context = {
|
|
587
|
+
root,
|
|
588
|
+
limits,
|
|
589
|
+
cache: /* @__PURE__ */ new Map(),
|
|
590
|
+
totalBytes: 0,
|
|
591
|
+
mavenModels: /* @__PURE__ */ new Map(),
|
|
592
|
+
manifestPaths: new Set(detected.map((file) => file.path))
|
|
593
|
+
};
|
|
594
|
+
const facts = new Array(detected.length);
|
|
595
|
+
const byPath = new Map(detected.map((file, index) => [file.path, { file, index }]));
|
|
596
|
+
const limitedFact = (file, index) => {
|
|
597
|
+
if (index >= limits.maxManifestFiles) {
|
|
598
|
+
return finalize(baseFacts(file.path, manifestKind(path4.posix.basename(file.path)), [
|
|
599
|
+
`resource limit exceeded: detected manifest ${index + 1} of ${detected.length}; maxManifestFiles=${limits.maxManifestFiles}`
|
|
600
|
+
]));
|
|
601
|
+
}
|
|
602
|
+
return void 0;
|
|
603
|
+
};
|
|
604
|
+
detected.forEach((file, index) => {
|
|
605
|
+
const limited = limitedFact(file, index);
|
|
606
|
+
if (limited) facts[index] = limited;
|
|
607
|
+
else if (file.metadataOnlyReason !== "symlink") facts[index] = parseOne(context, file.path);
|
|
608
|
+
});
|
|
609
|
+
detected.forEach((file, index) => {
|
|
610
|
+
if (!facts[index]) facts[index] = parseManifestSymlink(context, file.path, byPath, facts);
|
|
611
|
+
});
|
|
612
|
+
return linkInternalMavenManifests(
|
|
613
|
+
facts.map((fact, index) => fact ?? finalize(baseFacts(detected[index].path, "manifest", ["manifest parser produced no fact"]))),
|
|
614
|
+
limits
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
function parseManifestSymlink(context, relative, detected, facts) {
|
|
618
|
+
try {
|
|
619
|
+
const absolute = assertInside(context.root, path4.join(context.root, ...relative.split("/")));
|
|
620
|
+
const rawTarget = fs2.readlinkSync(absolute);
|
|
621
|
+
if (path4.isAbsolute(rawTarget)) throw new Error("absolute symbolic link target");
|
|
622
|
+
const target = normalizeRepoPath(path4.posix.normalize(path4.posix.join(path4.posix.dirname(relative), rawTarget.replaceAll("\\", "/"))));
|
|
623
|
+
const targetEntry = detected.get(target);
|
|
624
|
+
if (!targetEntry) throw new Error(`target ${target} is not a detected supported manifest`);
|
|
625
|
+
if (targetEntry.file.metadataOnlyReason === "symlink") throw new Error(`target ${target} is another symbolic link`);
|
|
626
|
+
const source = facts[targetEntry.index];
|
|
627
|
+
if (!source) throw new Error(`target ${target} has no parsed fact`);
|
|
628
|
+
const alias = {
|
|
629
|
+
...source,
|
|
630
|
+
id: "pending",
|
|
631
|
+
path: relative,
|
|
632
|
+
product: source.product ? { ...source.product } : void 0,
|
|
633
|
+
dependencies: source.dependencies.map((dependency) => ({ ...dependency })),
|
|
634
|
+
frameworks: source.frameworks.map((framework) => ({ ...framework })),
|
|
635
|
+
runtimes: source.runtimes.map((runtime) => ({ ...runtime })),
|
|
636
|
+
scripts: source.scripts ? { ...source.scripts } : void 0,
|
|
637
|
+
workspaces: source.workspaces ? [...source.workspaces] : void 0,
|
|
638
|
+
aliases: source.aliases?.map((entry) => ({ ...entry, targets: [...entry.targets] })),
|
|
639
|
+
metadata: {
|
|
640
|
+
...source.metadata,
|
|
641
|
+
manifest_alias_target: target,
|
|
642
|
+
manifest_alias_target_sha256: targetEntry.file.sha256
|
|
643
|
+
},
|
|
644
|
+
errors: [...source.errors]
|
|
645
|
+
};
|
|
646
|
+
return finalize(alias);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
return finalize(baseFacts(relative, manifestKind(path4.posix.basename(relative)), [`safe-read rejected symbolic link manifest: ${message(error)}`]));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function isParseableManifest(relative) {
|
|
652
|
+
const base = path4.posix.basename(relative);
|
|
653
|
+
if ([
|
|
654
|
+
".bazelrc",
|
|
655
|
+
".bazelversion",
|
|
656
|
+
"BUILD",
|
|
657
|
+
"BUILD.bazel",
|
|
658
|
+
"CMakeLists.txt",
|
|
659
|
+
"Dockerfile",
|
|
660
|
+
"MODULE.bazel",
|
|
661
|
+
"MODULE.bazel.lock",
|
|
662
|
+
"WORKSPACE",
|
|
663
|
+
"WORKSPACE.bzlmod",
|
|
664
|
+
"deps.yaml",
|
|
665
|
+
"extensions_metadata.yaml",
|
|
666
|
+
"repository_locations.bzl",
|
|
667
|
+
".env.example",
|
|
668
|
+
"Cargo.toml",
|
|
669
|
+
"Gemfile",
|
|
670
|
+
"Gemfile.lock",
|
|
671
|
+
"analysis_options.yaml",
|
|
672
|
+
"composer.json",
|
|
673
|
+
"composer.lock",
|
|
674
|
+
"build.gradle",
|
|
675
|
+
"build.gradle.kts",
|
|
676
|
+
"gradle-wrapper.properties",
|
|
677
|
+
"gradle.properties",
|
|
678
|
+
"pom.xml",
|
|
679
|
+
"settings.gradle",
|
|
680
|
+
"settings.gradle.kts",
|
|
681
|
+
"package-lock.json",
|
|
682
|
+
"package.json",
|
|
683
|
+
"pnpm-lock.yaml",
|
|
684
|
+
"pnpm-workspace.yaml",
|
|
685
|
+
"pubspec.lock",
|
|
686
|
+
"pubspec.yaml",
|
|
687
|
+
"go.mod",
|
|
688
|
+
"pyproject.toml",
|
|
689
|
+
"yarn.lock"
|
|
690
|
+
].includes(base)) return true;
|
|
691
|
+
return /^(?:Dockerfile(?:\..+)?|requirements(?:[-_.][^/]*)?\.txt|tsconfig(?:\.[^/]+)?\.json)$/i.test(base) || /^(?:docker-)?compose(?:\.[^/]+)?\.ya?ml$/i.test(base) || /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relative);
|
|
692
|
+
}
|
|
693
|
+
function parseOne(context, relative) {
|
|
694
|
+
let text = "";
|
|
695
|
+
try {
|
|
696
|
+
text = readRepoText(context, relative);
|
|
697
|
+
validateManifestText(text, context.limits);
|
|
698
|
+
} catch (error) {
|
|
699
|
+
return finalize(baseFacts(relative, manifestKind(path4.posix.basename(relative)), [message(error)]));
|
|
700
|
+
}
|
|
701
|
+
const base = path4.posix.basename(relative);
|
|
702
|
+
try {
|
|
703
|
+
let result;
|
|
704
|
+
if (base === "package.json") result = parsePackageJson(context, relative, text);
|
|
705
|
+
else if (base === "package-lock.json") result = parsePackageLock(context, relative, text);
|
|
706
|
+
else if (base === "pnpm-lock.yaml") result = parsePnpmLock(context, relative, text);
|
|
707
|
+
else if (base === "yarn.lock") result = parseYarnLock(context, relative, text);
|
|
708
|
+
else if (base === "pnpm-workspace.yaml") result = parsePnpmWorkspace(context, relative, text);
|
|
709
|
+
else if (base === "pubspec.yaml") result = parsePubspec(context, relative, text);
|
|
710
|
+
else if (base === "pubspec.lock") result = parsePubspecLock(context, relative, text);
|
|
711
|
+
else if (base === "go.mod") result = parseGoMod(relative, text);
|
|
712
|
+
else if (base === "Cargo.toml") result = parseCargo(relative, text);
|
|
713
|
+
else if (base === "pom.xml") result = parseMaven(context, relative, text);
|
|
714
|
+
else if (base === "build.gradle" || base === "build.gradle.kts") result = parseGradleBuild(context, relative, text);
|
|
715
|
+
else if (base === "settings.gradle" || base === "settings.gradle.kts") result = parseGradleSettings(context, relative, text);
|
|
716
|
+
else if (base === "gradle-wrapper.properties") result = parseGradleWrapper(relative, text);
|
|
717
|
+
else if (base === "gradle.properties") result = parseGradleProperties(context, relative, text);
|
|
718
|
+
else if (base === "Gemfile") result = parseGemfile(context, relative, text);
|
|
719
|
+
else if (base === "Gemfile.lock") result = parseGemfileLock(relative, text);
|
|
720
|
+
else if (base === "composer.json") result = parseComposerJson(context, relative, text);
|
|
721
|
+
else if (base === "composer.lock") result = parseComposerLock(context, relative, text);
|
|
722
|
+
else if (base === "pyproject.toml") result = parsePyproject(relative, text);
|
|
723
|
+
else if (/^requirements(?:[-_.][^/]*)?\.txt$/i.test(base)) result = parseRequirements(relative, text);
|
|
724
|
+
else if (/^tsconfig(?:\.[^/]+)?\.json$/i.test(base)) result = parseTsconfig(context, relative, text);
|
|
725
|
+
else if (base === "analysis_options.yaml") result = parseAnalysisOptions(context, relative, text);
|
|
726
|
+
else if (base === ".env.example") result = parseEnvExample(relative, text);
|
|
727
|
+
else if (/^(?:docker-)?compose(?:\.[^/]+)?\.ya?ml$/i.test(base)) result = parseDockerCompose(context, relative, text);
|
|
728
|
+
else if (base === "repository_locations.bzl") result = parseRepositoryLocations(context, relative, text);
|
|
729
|
+
else if (base === "deps.yaml") result = parseDependencyMetadata(context, relative, text);
|
|
730
|
+
else if (base === "extensions_metadata.yaml") result = parseExtensionsMetadata(context, relative, text);
|
|
731
|
+
else if (base === "MODULE.bazel.lock") result = parseModuleBazelLock(context, relative, text);
|
|
732
|
+
else if (base === ".bazelrc") result = parseBazelRc(context, relative, text);
|
|
733
|
+
else if (base === ".bazelversion") result = parseBazelVersion(relative, text);
|
|
734
|
+
else if (["MODULE.bazel", "WORKSPACE", "WORKSPACE.bzlmod", "BUILD", "BUILD.bazel"].includes(base)) result = parseBazel(context, relative, text);
|
|
735
|
+
else if (base === "CMakeLists.txt") result = parseCmake(relative, text);
|
|
736
|
+
else if (/^Dockerfile(?:\..+)?$/i.test(base)) result = parseDockerfile(relative, text);
|
|
737
|
+
else if (/(?:^|\/)\.github\/workflows\//.test(relative)) result = parseWorkflow(context, relative, text);
|
|
738
|
+
else result = finalize(baseFacts(relative, "config", ["detected supported manifest has no registered parser"]));
|
|
739
|
+
return enforceFactBounds(result, context.limits);
|
|
740
|
+
} catch (error) {
|
|
741
|
+
return finalize(baseFacts(relative, manifestKind(base), [message(error)]));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function parsePackageJson(context, relative, text) {
|
|
745
|
+
const value = parseJson(context, text);
|
|
746
|
+
const declared = { ...record(value.dependencies), ...record(value.optionalDependencies), ...record(value.peerDependencies) };
|
|
747
|
+
const dev = record(value.devDependencies);
|
|
748
|
+
const exact = npmResolvedVersions(context, path4.posix.dirname(relative));
|
|
749
|
+
const dependencies = [
|
|
750
|
+
...Object.entries(declared).map(([name, version]) => ({ name, declared: String(version), resolved: exact.get(name), dev: false })),
|
|
751
|
+
...Object.entries(dev).map(([name, version]) => ({ name, declared: String(version), resolved: exact.get(name), dev: true }))
|
|
752
|
+
].sort((a, b) => a.name.localeCompare(b.name));
|
|
753
|
+
const facts = baseFacts(relative, "npm", []);
|
|
754
|
+
facts.product = { name: string(value.name), version: string(value.version) };
|
|
755
|
+
facts.dependencies = dependencies;
|
|
756
|
+
facts.frameworks = inferFrameworks(dependencies);
|
|
757
|
+
facts.runtimes = [{ name: "Node.js", version: string(record(value.engines).node) }];
|
|
758
|
+
facts.scripts = stringRecord(value.scripts);
|
|
759
|
+
facts.workspaces = (Array.isArray(value.workspaces) ? value.workspaces : array(record(value.workspaces).packages)).map(String);
|
|
760
|
+
if (!facts.workspaces.length) delete facts.workspaces;
|
|
761
|
+
return finalize(facts);
|
|
762
|
+
}
|
|
763
|
+
function parsePackageLock(context, relative, text) {
|
|
764
|
+
const value = parseJson(context, text);
|
|
765
|
+
const packages = record(value.packages);
|
|
766
|
+
const dependencies = Object.entries(packages).flatMap(([key, raw]) => {
|
|
767
|
+
const marker = key.lastIndexOf("node_modules/");
|
|
768
|
+
if (marker < 0) return [];
|
|
769
|
+
const row = record(raw);
|
|
770
|
+
return [{ name: key.slice(marker + "node_modules/".length), resolved: string(row.version), dev: Boolean(row.dev) }];
|
|
771
|
+
});
|
|
772
|
+
for (const [name, raw] of Object.entries(record(value.dependencies))) {
|
|
773
|
+
const row = record(raw);
|
|
774
|
+
dependencies.push({ name, resolved: string(row.version), dev: Boolean(row.dev) });
|
|
775
|
+
}
|
|
776
|
+
const facts = baseFacts(relative, "npm-lock", []);
|
|
777
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
778
|
+
return finalize(facts);
|
|
779
|
+
}
|
|
780
|
+
function parsePnpmLock(context, relative, text) {
|
|
781
|
+
const dependencies = pnpmResolvedDependencies(context, text);
|
|
782
|
+
const facts = baseFacts(relative, "pnpm-lock", []);
|
|
783
|
+
facts.dependencies = dependencies;
|
|
784
|
+
return finalize(facts);
|
|
785
|
+
}
|
|
786
|
+
function parseYarnLock(context, relative, text) {
|
|
787
|
+
const dependencies = yarnResolvedDependencies(context, text);
|
|
788
|
+
const facts = baseFacts(relative, "yarn-lock", []);
|
|
789
|
+
facts.dependencies = dependencies;
|
|
790
|
+
facts.metadata = { format: /^__metadata\s*:/m.test(text) ? "berry" : "classic", entries: dependencies.length };
|
|
791
|
+
if (text.trim() && dependencies.length === 0) facts.errors.push("yarn.lock contained no parseable package entries");
|
|
792
|
+
return finalize(facts);
|
|
793
|
+
}
|
|
794
|
+
function parsePnpmWorkspace(context, relative, text) {
|
|
795
|
+
const value = parseYamlRecord(context, text, "pnpm-workspace.yaml");
|
|
796
|
+
const packages = array(value.packages).map(String);
|
|
797
|
+
const catalogs = record(value.catalogs);
|
|
798
|
+
const dependencies = Object.values(catalogs).flatMap((catalog) => Object.entries(record(catalog)).map(([name, version]) => ({
|
|
799
|
+
name,
|
|
800
|
+
declared: string(version),
|
|
801
|
+
dev: false
|
|
802
|
+
})));
|
|
803
|
+
const facts = baseFacts(relative, "pnpm-workspace", []);
|
|
804
|
+
facts.workspaces = packages;
|
|
805
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
806
|
+
facts.metadata = { package_patterns: packages, catalog_count: Object.keys(catalogs).length };
|
|
807
|
+
return finalize(facts);
|
|
808
|
+
}
|
|
809
|
+
function parsePubspec(context, relative, text) {
|
|
810
|
+
const value = parseYamlRecord(context, text, "pubspec.yaml");
|
|
811
|
+
const dependenciesRaw = record(value.dependencies);
|
|
812
|
+
const devRaw = record(value.dev_dependencies);
|
|
813
|
+
const lock = pubspecResolvedVersions(context, path4.posix.dirname(relative));
|
|
814
|
+
const dependencies = [
|
|
815
|
+
...Object.entries(dependenciesRaw).map(([name, spec]) => ({ name, declared: yamlVersion(spec), resolved: lock.get(name), dev: false })),
|
|
816
|
+
...Object.entries(devRaw).map(([name, spec]) => ({ name, declared: yamlVersion(spec), resolved: lock.get(name), dev: true }))
|
|
817
|
+
].sort((a, b) => a.name.localeCompare(b.name));
|
|
818
|
+
const environment = record(value.environment);
|
|
819
|
+
const facts = baseFacts(relative, "pubspec", []);
|
|
820
|
+
facts.product = { name: string(value.name), version: string(value.version) };
|
|
821
|
+
facts.dependencies = dependencies;
|
|
822
|
+
facts.frameworks = inferFrameworks(dependencies);
|
|
823
|
+
if ("flutter" in value || "flutter" in dependenciesRaw || "flutter" in devRaw) facts.frameworks.unshift({ name: "Flutter", version: string(environment.flutter) ?? string(environment.sdk), kind: "mobile" });
|
|
824
|
+
facts.runtimes = [{ name: "Dart", version: string(environment.sdk) }];
|
|
825
|
+
return finalize(facts);
|
|
826
|
+
}
|
|
827
|
+
function parsePubspecLock(context, relative, text) {
|
|
828
|
+
const value = parseYamlRecord(context, text, "pubspec.lock");
|
|
829
|
+
const dependencies = Object.entries(record(value.packages)).map(([name, raw]) => ({ name, resolved: string(record(raw).version), dev: false }));
|
|
830
|
+
const facts = baseFacts(relative, "pubspec-lock", []);
|
|
831
|
+
facts.dependencies = dependencies.sort((a, b) => a.name.localeCompare(b.name));
|
|
832
|
+
return finalize(facts);
|
|
833
|
+
}
|
|
834
|
+
function parseGoMod(relative, text) {
|
|
835
|
+
const moduleName = text.match(/^module\s+(\S+)/m)?.[1];
|
|
836
|
+
const goVersion = text.match(/^go\s+(\S+)/m)?.[1];
|
|
837
|
+
const dependencies = [...text.matchAll(/^\s*([^\s/][^\s]*)\s+(v[^\s]+)(?:\s+\/\/\s+indirect)?$/gm)].map((match) => ({ name: match[1], resolved: match[2], dev: false }));
|
|
838
|
+
const emptyPlaceholder = text.trim().length === 0;
|
|
839
|
+
const facts = baseFacts(relative, "go", moduleName || emptyPlaceholder ? [] : ["go.mod is missing its required module directive"]);
|
|
840
|
+
facts.product = { name: moduleName };
|
|
841
|
+
facts.dependencies = dependencies;
|
|
842
|
+
facts.runtimes = [{ name: "Go", version: goVersion }];
|
|
843
|
+
facts.metadata = emptyPlaceholder ? { empty_placeholder: true } : {};
|
|
844
|
+
return finalize(facts);
|
|
845
|
+
}
|
|
846
|
+
function parseCargo(relative, text) {
|
|
847
|
+
const packageBlock = section(text, "package");
|
|
848
|
+
const dependencies = [
|
|
849
|
+
...parseTomlDependencies(section(text, "dependencies"), false),
|
|
850
|
+
...parseTomlDependencies(section(text, "dev-dependencies"), true)
|
|
851
|
+
];
|
|
852
|
+
const facts = baseFacts(relative, "cargo", /^\s*\[(?:package|workspace)\]\s*$/m.test(text) ? [] : ["Cargo.toml has neither a [package] nor [workspace] table"]);
|
|
853
|
+
facts.product = { name: quoted(packageBlock, "name"), version: quoted(packageBlock, "version") };
|
|
854
|
+
facts.dependencies = dependencies;
|
|
855
|
+
facts.runtimes = [{ name: "Rust", version: quoted(packageBlock, "rust-version") }];
|
|
856
|
+
return finalize(facts);
|
|
857
|
+
}
|
|
858
|
+
var MAVEN_XML = new XMLParser({
|
|
859
|
+
ignoreAttributes: false,
|
|
860
|
+
attributeNamePrefix: "@_",
|
|
861
|
+
allowBooleanAttributes: true,
|
|
862
|
+
parseTagValue: false,
|
|
863
|
+
parseAttributeValue: false,
|
|
864
|
+
processEntities: false,
|
|
865
|
+
trimValues: true
|
|
866
|
+
});
|
|
867
|
+
function parseMaven(context, relative, text) {
|
|
868
|
+
const model = readMavenModel(context, relative, text, /* @__PURE__ */ new Set(), 0);
|
|
869
|
+
const managedVersions = mavenManagedVersions(model);
|
|
870
|
+
const direct = mavenDependencyRows(record(model.project.dependencies).dependency, model.properties, false, managedVersions);
|
|
871
|
+
const managed = mavenDependencyRows(record(record(model.project.dependencyManagement).dependencies).dependency, model.properties, true);
|
|
872
|
+
const dependencies = dedupeDependencies(direct.map(({ name, declared, dev }) => ({ name, ...declared ? { declared } : {}, dev })));
|
|
873
|
+
const modules = valueArray(record(model.project.modules).module).map((value) => resolveMavenValue(string(value), model.properties)).filter((value) => Boolean(value)).map((value) => mavenModulePomPath(context, relative, value));
|
|
874
|
+
const javaVersion = mavenJavaVersion(model);
|
|
875
|
+
const mavenVersion = mavenInheritedRequiredVersion(model, "requireMavenVersion");
|
|
876
|
+
const coordinate = model.groupId && model.artifactId ? `${model.groupId}:${model.artifactId}` : model.artifactId;
|
|
877
|
+
const platform = mavenPlatform(model, relative);
|
|
878
|
+
const facts = baseFacts(relative, "maven", model.artifactId ? [] : ["pom.xml is missing project.artifactId"]);
|
|
879
|
+
facts.product = { name: coordinate, version: model.version };
|
|
880
|
+
facts.dependencies = dependencies;
|
|
881
|
+
facts.frameworks = [
|
|
882
|
+
{ name: "Maven", version: mavenVersion, kind: "build" },
|
|
883
|
+
...inferFrameworks(dependencies)
|
|
884
|
+
];
|
|
885
|
+
facts.runtimes = [{ name: "Java", version: javaVersion }];
|
|
886
|
+
if (modules.length) facts.workspaces = modules;
|
|
887
|
+
facts.metadata = {
|
|
888
|
+
coordinate,
|
|
889
|
+
group_id: model.groupId,
|
|
890
|
+
artifact_id: model.artifactId,
|
|
891
|
+
packaging: resolveMavenValue(string(model.project.packaging), model.properties) ?? "jar",
|
|
892
|
+
platform,
|
|
893
|
+
parent_coordinate: model.parentCoordinate,
|
|
894
|
+
parent_path: model.parentPath,
|
|
895
|
+
modules,
|
|
896
|
+
source_directory: model.sourceDirectory,
|
|
897
|
+
test_source_directory: model.testSourceDirectory,
|
|
898
|
+
direct_dependencies: direct,
|
|
899
|
+
direct_dependency_count: direct.length,
|
|
900
|
+
managed_dependencies: managed,
|
|
901
|
+
managed_dependency_count: managed.length
|
|
902
|
+
};
|
|
903
|
+
return finalize(facts);
|
|
904
|
+
}
|
|
905
|
+
function mavenModulePomPath(context, manifestPath, moduleSpecifier) {
|
|
906
|
+
const joined = normalizeRepoPath(path4.posix.join(path4.posix.dirname(manifestPath), moduleSpecifier));
|
|
907
|
+
if (context.manifestPaths.has(joined)) return joined;
|
|
908
|
+
const nestedPom = normalizeRepoPath(path4.posix.join(joined, "pom.xml"));
|
|
909
|
+
if (context.manifestPaths.has(nestedPom)) return nestedPom;
|
|
910
|
+
return joined.toLowerCase().endsWith(".xml") ? joined : nestedPom;
|
|
911
|
+
}
|
|
912
|
+
function readMavenModel(context, relative, text, seen, depth) {
|
|
913
|
+
if (depth > 16) throw new Error("Maven parent chain exceeds 16 local pom.xml files");
|
|
914
|
+
if (seen.has(relative)) throw new Error(`Maven parent cycle detected at ${relative}`);
|
|
915
|
+
const cached = context.mavenModels.get(relative);
|
|
916
|
+
if (cached) return cached;
|
|
917
|
+
const nextSeen = new Set(seen).add(relative);
|
|
918
|
+
const parsed = MAVEN_XML.parse(text);
|
|
919
|
+
const project = record(record(parsed).project);
|
|
920
|
+
if (!Object.keys(project).length) throw new Error("pom.xml root must be a project element");
|
|
921
|
+
const parent = record(project.parent);
|
|
922
|
+
let inherited;
|
|
923
|
+
let parentPath;
|
|
924
|
+
if (Object.keys(parent).length) {
|
|
925
|
+
const hasExplicitRelativePath = Object.prototype.hasOwnProperty.call(parent, "relativePath");
|
|
926
|
+
const configured = string(parent.relativePath);
|
|
927
|
+
const localSpecifier = hasExplicitRelativePath ? configured : "../pom.xml";
|
|
928
|
+
if (localSpecifier) {
|
|
929
|
+
try {
|
|
930
|
+
parentPath = normalizeRepoPath(path4.posix.join(path4.posix.dirname(relative), localSpecifier));
|
|
931
|
+
} catch {
|
|
932
|
+
parentPath = void 0;
|
|
933
|
+
}
|
|
934
|
+
if (parentPath && context.manifestPaths.has(parentPath)) {
|
|
935
|
+
const parentText = readRepoText(context, parentPath);
|
|
936
|
+
inherited = readMavenModel(context, parentPath, parentText, nextSeen, depth + 1);
|
|
937
|
+
} else {
|
|
938
|
+
parentPath = void 0;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
const rawProperties = Object.fromEntries(Object.entries(record(project.properties)).flatMap(([key, value]) => {
|
|
943
|
+
const scalar = string(value);
|
|
944
|
+
return scalar === void 0 ? [] : [[key, scalar]];
|
|
945
|
+
}));
|
|
946
|
+
let properties = { ...inherited?.properties ?? {}, ...rawProperties };
|
|
947
|
+
const parentGroup = inherited?.groupId ?? resolveMavenValue(string(parent.groupId), properties);
|
|
948
|
+
const parentArtifact = inherited?.artifactId ?? resolveMavenValue(string(parent.artifactId), properties);
|
|
949
|
+
const parentVersion = inherited?.version ?? resolveMavenValue(string(parent.version), properties);
|
|
950
|
+
let groupId = resolveMavenValue(string(project.groupId), properties) ?? parentGroup;
|
|
951
|
+
let artifactId = resolveMavenValue(string(project.artifactId), properties);
|
|
952
|
+
let version = resolveMavenValue(string(project.version), properties) ?? parentVersion;
|
|
953
|
+
const builtins = {
|
|
954
|
+
"project.groupId": groupId,
|
|
955
|
+
"project.artifactId": artifactId,
|
|
956
|
+
"project.version": version,
|
|
957
|
+
"pom.groupId": groupId,
|
|
958
|
+
"pom.artifactId": artifactId,
|
|
959
|
+
"pom.version": version,
|
|
960
|
+
"project.parent.groupId": parentGroup,
|
|
961
|
+
"project.parent.artifactId": parentArtifact,
|
|
962
|
+
"project.parent.version": parentVersion
|
|
963
|
+
};
|
|
964
|
+
for (const [key, value] of Object.entries(builtins)) if (value !== void 0) properties[key] = value;
|
|
965
|
+
properties = resolveMavenProperties(properties);
|
|
966
|
+
groupId = resolveMavenValue(groupId, properties);
|
|
967
|
+
artifactId = resolveMavenValue(artifactId, properties);
|
|
968
|
+
version = resolveMavenValue(version, properties);
|
|
969
|
+
const build = record(project.build);
|
|
970
|
+
const sourceDirectory = resolveMavenValue(string(build.sourceDirectory), properties) ?? inherited?.sourceDirectory ?? "src/main/java";
|
|
971
|
+
const testSourceDirectory = resolveMavenValue(string(build.testSourceDirectory), properties) ?? inherited?.testSourceDirectory ?? "src/test/java";
|
|
972
|
+
const model = {
|
|
973
|
+
project,
|
|
974
|
+
groupId,
|
|
975
|
+
artifactId,
|
|
976
|
+
version,
|
|
977
|
+
properties,
|
|
978
|
+
...parentPath ? { parentPath } : {},
|
|
979
|
+
...parentGroup && parentArtifact ? { parentCoordinate: `${parentGroup}:${parentArtifact}${parentVersion ? `:${parentVersion}` : ""}` } : {},
|
|
980
|
+
sourceDirectory,
|
|
981
|
+
testSourceDirectory,
|
|
982
|
+
...inherited ? { parent: inherited } : {}
|
|
983
|
+
};
|
|
984
|
+
context.mavenModels.set(relative, model);
|
|
985
|
+
return model;
|
|
986
|
+
}
|
|
987
|
+
function mavenDependencyRows(value, properties, managed, managedVersions = /* @__PURE__ */ new Map()) {
|
|
988
|
+
return valueArray(value).flatMap((raw) => {
|
|
989
|
+
const dependency = record(raw);
|
|
990
|
+
const groupId = resolveMavenValue(string(dependency.groupId), properties);
|
|
991
|
+
const artifactId = resolveMavenValue(string(dependency.artifactId), properties);
|
|
992
|
+
if (!groupId || !artifactId) return [];
|
|
993
|
+
const name = `${groupId}:${artifactId}`;
|
|
994
|
+
const version = resolveMavenValue(string(dependency.version), properties) ?? managedVersions.get(name);
|
|
995
|
+
const scope = resolveMavenValue(string(dependency.scope), properties)?.toLowerCase();
|
|
996
|
+
const classifier = resolveMavenValue(string(dependency.classifier), properties);
|
|
997
|
+
const dependencyType = resolveMavenValue(string(dependency.type), properties);
|
|
998
|
+
const optional = resolveMavenValue(string(dependency.optional), properties)?.toLowerCase();
|
|
999
|
+
const exclusions = valueArray(record(dependency.exclusions).exclusion).flatMap((rawExclusion) => {
|
|
1000
|
+
const exclusion = record(rawExclusion);
|
|
1001
|
+
const exclusionGroup = resolveMavenValue(string(exclusion.groupId), properties);
|
|
1002
|
+
const exclusionArtifact = resolveMavenValue(string(exclusion.artifactId), properties);
|
|
1003
|
+
return exclusionGroup && exclusionArtifact ? [`${exclusionGroup}:${exclusionArtifact}`] : [];
|
|
1004
|
+
});
|
|
1005
|
+
return [{
|
|
1006
|
+
name,
|
|
1007
|
+
...version ? { declared: version } : {},
|
|
1008
|
+
dev: scope === "test" || managed && scope === "test",
|
|
1009
|
+
...scope ? { scope } : {},
|
|
1010
|
+
...classifier ? { classifier } : {},
|
|
1011
|
+
...dependencyType ? { type: dependencyType } : {},
|
|
1012
|
+
...optional === "true" ? { optional: true } : optional === "false" ? { optional: false } : {},
|
|
1013
|
+
...exclusions.length ? { exclusions: [...new Set(exclusions)].sort() } : {}
|
|
1014
|
+
}];
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
function mavenManagedVersions(model) {
|
|
1018
|
+
const chain = [];
|
|
1019
|
+
for (let current = model; current; current = current.parent) chain.unshift(current);
|
|
1020
|
+
const versions = /* @__PURE__ */ new Map();
|
|
1021
|
+
for (const current of chain) {
|
|
1022
|
+
for (const dependency of mavenDependencyRows(
|
|
1023
|
+
record(record(current.project.dependencyManagement).dependencies).dependency,
|
|
1024
|
+
model.properties,
|
|
1025
|
+
true
|
|
1026
|
+
)) {
|
|
1027
|
+
if (dependency.declared) versions.set(dependency.name, dependency.declared);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return versions;
|
|
1031
|
+
}
|
|
1032
|
+
function mavenPlatform(model, relative) {
|
|
1033
|
+
const explicit = resolveMavenValue(
|
|
1034
|
+
model.properties["variant.jvmEnvironmentVariantName"] ?? model.properties["variant.jvmEnvironment"],
|
|
1035
|
+
model.properties
|
|
1036
|
+
)?.trim().toLowerCase();
|
|
1037
|
+
if (explicit) return explicit === "standard-jvm" ? "jre" : explicit;
|
|
1038
|
+
const versionPlatform = model.version?.match(/-(android|jre)(?:$|[.-])/i)?.[1]?.toLowerCase();
|
|
1039
|
+
if (versionPlatform) return versionPlatform;
|
|
1040
|
+
return relative.startsWith("android/") ? "android" : void 0;
|
|
1041
|
+
}
|
|
1042
|
+
function resolveMavenProperties(input) {
|
|
1043
|
+
const result = { ...input };
|
|
1044
|
+
for (let pass = 0; pass < 16; pass += 1) {
|
|
1045
|
+
let changed = false;
|
|
1046
|
+
for (const [key, value] of Object.entries(result)) {
|
|
1047
|
+
const resolved = resolveMavenValue(value, result);
|
|
1048
|
+
if (resolved !== void 0 && resolved !== value) {
|
|
1049
|
+
result[key] = resolved;
|
|
1050
|
+
changed = true;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (!changed) break;
|
|
1054
|
+
}
|
|
1055
|
+
return result;
|
|
1056
|
+
}
|
|
1057
|
+
function resolveMavenValue(value, properties) {
|
|
1058
|
+
if (value === void 0) return void 0;
|
|
1059
|
+
let output = value.trim();
|
|
1060
|
+
for (let pass = 0; pass < 16; pass += 1) {
|
|
1061
|
+
const next = output.replace(/\$\{([^}]+)\}/g, (whole, key) => properties[key] ?? whole);
|
|
1062
|
+
if (next === output) break;
|
|
1063
|
+
output = next;
|
|
1064
|
+
}
|
|
1065
|
+
return output || void 0;
|
|
1066
|
+
}
|
|
1067
|
+
function mavenJavaVersion(model) {
|
|
1068
|
+
for (const key of ["maven.compiler.release", "maven.compiler.source", "java.version", "jdk.version"]) {
|
|
1069
|
+
const value = resolveMavenValue(model.properties[key], model.properties);
|
|
1070
|
+
if (value) return value;
|
|
1071
|
+
}
|
|
1072
|
+
for (const plugin of mavenPlugins(model.project)) {
|
|
1073
|
+
if (string(plugin.artifactId) !== "maven-compiler-plugin") continue;
|
|
1074
|
+
const configuration = record(plugin.configuration);
|
|
1075
|
+
const configured = string(configuration.release) ?? string(configuration.source) ?? string(configuration.target);
|
|
1076
|
+
const value = resolveMavenValue(configured, model.properties);
|
|
1077
|
+
if (value) return value;
|
|
1078
|
+
}
|
|
1079
|
+
return mavenRequiredVersion(model.project, "requireJavaVersion", model.properties) ?? (model.parent ? mavenJavaVersion(model.parent) : void 0);
|
|
1080
|
+
}
|
|
1081
|
+
function mavenInheritedRequiredVersion(model, rule) {
|
|
1082
|
+
return mavenRequiredVersion(model.project, rule, model.properties) ?? (model.parent ? mavenInheritedRequiredVersion(model.parent, rule) : void 0);
|
|
1083
|
+
}
|
|
1084
|
+
function mavenPlugins(project) {
|
|
1085
|
+
const build = record(project.build);
|
|
1086
|
+
return [
|
|
1087
|
+
...valueArray(record(build.plugins).plugin),
|
|
1088
|
+
...valueArray(record(record(build.pluginManagement).plugins).plugin)
|
|
1089
|
+
].map(record);
|
|
1090
|
+
}
|
|
1091
|
+
function mavenRequiredVersion(project, rule, properties) {
|
|
1092
|
+
const stack = [project];
|
|
1093
|
+
let visited = 0;
|
|
1094
|
+
while (stack.length && visited < 1e5) {
|
|
1095
|
+
const current = stack.pop();
|
|
1096
|
+
visited += 1;
|
|
1097
|
+
if (Array.isArray(current)) {
|
|
1098
|
+
stack.push(...current);
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
const row = record(current);
|
|
1102
|
+
if (!Object.keys(row).length) continue;
|
|
1103
|
+
if (rule in row) {
|
|
1104
|
+
const value = row[rule];
|
|
1105
|
+
const configured = string(value) ?? string(record(value).version);
|
|
1106
|
+
const resolved = resolveMavenValue(configured, properties);
|
|
1107
|
+
if (resolved) return resolved;
|
|
1108
|
+
}
|
|
1109
|
+
stack.push(...Object.values(row));
|
|
1110
|
+
}
|
|
1111
|
+
return void 0;
|
|
1112
|
+
}
|
|
1113
|
+
function parseGradleBuild(context, relative, text) {
|
|
1114
|
+
const dependencies = gradleDependencies(text);
|
|
1115
|
+
const plugins = gradlePlugins(text);
|
|
1116
|
+
const version = gradleWrapperVersion(context, path4.posix.dirname(relative));
|
|
1117
|
+
const facts = baseFacts(relative, "gradle", []);
|
|
1118
|
+
facts.dependencies = dependencies;
|
|
1119
|
+
facts.frameworks = [
|
|
1120
|
+
{ name: "Gradle", version, kind: "build" },
|
|
1121
|
+
...inferFrameworks(dependencies)
|
|
1122
|
+
];
|
|
1123
|
+
const javaVersion = gradleJavaVersion(text);
|
|
1124
|
+
facts.runtimes = [{ name: "Java", version: javaVersion }];
|
|
1125
|
+
facts.scripts = Object.fromEntries([...text.matchAll(/\b(?:tasks\.)?(?:register|create|named)\s*\(\s*["']([^"']+)["']/g)].map((match) => [match[1], "Gradle task"]));
|
|
1126
|
+
if (!Object.keys(facts.scripts).length) delete facts.scripts;
|
|
1127
|
+
facts.metadata = { plugins, dependency_count: dependencies.length };
|
|
1128
|
+
return finalize(facts);
|
|
1129
|
+
}
|
|
1130
|
+
function parseGradleSettings(context, relative, text) {
|
|
1131
|
+
const version = gradleWrapperVersion(context, path4.posix.dirname(relative));
|
|
1132
|
+
const name = text.match(/\brootProject\.name\s*=\s*["']([^"']+)["']/)?.[1];
|
|
1133
|
+
const modules = [...text.matchAll(/\binclude\s*\(([^)]*)\)|\binclude\s+([^\n]+)/g)].flatMap(
|
|
1134
|
+
(match) => [...(match[1] ?? match[2] ?? "").matchAll(/["']([^"']+)["']/g)].map((value) => value[1].replace(/^:/, "").replaceAll(":", "/"))
|
|
1135
|
+
);
|
|
1136
|
+
const facts = baseFacts(relative, "gradle-settings", []);
|
|
1137
|
+
facts.product = { name };
|
|
1138
|
+
facts.frameworks = [{ name: "Gradle", version, kind: "build" }];
|
|
1139
|
+
facts.workspaces = [...new Set(modules)].sort();
|
|
1140
|
+
if (!facts.workspaces.length) delete facts.workspaces;
|
|
1141
|
+
facts.metadata = { root_project_name: name, modules: facts.workspaces ?? [] };
|
|
1142
|
+
return finalize(facts);
|
|
1143
|
+
}
|
|
1144
|
+
function parseGradleWrapper(relative, text) {
|
|
1145
|
+
const version = gradleDistributionVersion(text);
|
|
1146
|
+
const facts = baseFacts(relative, "gradle-wrapper", version ? [] : ["Gradle wrapper properties contain no versioned distributionUrl"]);
|
|
1147
|
+
facts.frameworks = [{ name: "Gradle", version, kind: "build" }];
|
|
1148
|
+
facts.metadata = { distribution_version: version };
|
|
1149
|
+
return finalize(facts);
|
|
1150
|
+
}
|
|
1151
|
+
function parseGradleProperties(context, relative, text) {
|
|
1152
|
+
const properties = propertyFile(text);
|
|
1153
|
+
const facts = baseFacts(relative, "gradle-properties", []);
|
|
1154
|
+
facts.frameworks = [{ name: "Gradle", version: gradleWrapperVersion(context, path4.posix.dirname(relative)), kind: "build" }];
|
|
1155
|
+
facts.metadata = { keys: [...properties.keys()].sort(), key_count: properties.size };
|
|
1156
|
+
return finalize(facts);
|
|
1157
|
+
}
|
|
1158
|
+
function gradleDependencies(text) {
|
|
1159
|
+
const rows = [];
|
|
1160
|
+
const coordinate = /["']([A-Za-z0-9_.-]+):([A-Za-z0-9_.-]+)(?::([^"']+))?["']/g;
|
|
1161
|
+
for (const match of text.matchAll(coordinate)) {
|
|
1162
|
+
const prefix = text.slice(Math.max(0, (match.index ?? 0) - 120), match.index).toLowerCase();
|
|
1163
|
+
const configuration = prefix.match(/["']?([a-z][a-z0-9_]*)["']?\s*(?:\(|\s)\s*$/)?.[1] ?? "";
|
|
1164
|
+
if (!/(?:api|implementation|compile|runtime|classpath|constraint|processor)/.test(configuration)) continue;
|
|
1165
|
+
rows.push({
|
|
1166
|
+
name: `${match[1]}:${match[2]}`,
|
|
1167
|
+
...match[3] ? { declared: match[3] } : {},
|
|
1168
|
+
dev: /(?:test|benchmark)/.test(configuration)
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
return dedupeDependencies(rows);
|
|
1172
|
+
}
|
|
1173
|
+
function gradlePlugins(text) {
|
|
1174
|
+
const plugins = [
|
|
1175
|
+
...[...text.matchAll(/\bid\s*\(\s*["']([^"']+)["']\s*\)\s*(?:version\s*["']([^"']+)["'])?/g)].map((match) => ({ id: match[1], ...match[2] ? { version: match[2] } : {} })),
|
|
1176
|
+
...[...text.matchAll(/\bapply\s*\(\s*plugin\s*=\s*["']([^"']+)["']/g)].map((match) => ({ id: match[1] })),
|
|
1177
|
+
...[...text.matchAll(/\bid\s+["']([^"']+)["']\s+version\s+["']([^"']+)["']/g)].map((match) => ({ id: match[1], version: match[2] }))
|
|
1178
|
+
];
|
|
1179
|
+
return dedupeBy(plugins.sort((a, b) => a.id.localeCompare(b.id)), (plugin) => `${plugin.id}:${plugin.version ?? ""}`);
|
|
1180
|
+
}
|
|
1181
|
+
function gradleJavaVersion(text) {
|
|
1182
|
+
const enumVersion = text.match(/JavaVersion\.VERSION_([0-9_]+)/)?.[1]?.replaceAll("_", ".");
|
|
1183
|
+
if (enumVersion) return enumVersion;
|
|
1184
|
+
return text.match(/JavaLanguageVersion\.of\s*\(\s*([0-9]+)\s*\)/)?.[1] ?? text.match(/(?:sourceCompatibility|targetCompatibility)\s*=\s*["']([^"']+)["']/)?.[1];
|
|
1185
|
+
}
|
|
1186
|
+
function gradleWrapperVersion(context, startDirectory) {
|
|
1187
|
+
let directory = startDirectory === "." ? "" : startDirectory;
|
|
1188
|
+
for (let depth = 0; depth < 16; depth += 1) {
|
|
1189
|
+
const candidate = joinRepoPath(directory, "gradle/wrapper/gradle-wrapper.properties");
|
|
1190
|
+
const text = tryReadRepoText(context, candidate);
|
|
1191
|
+
const version = text ? gradleDistributionVersion(text) : void 0;
|
|
1192
|
+
if (version) return version;
|
|
1193
|
+
if (!directory) break;
|
|
1194
|
+
const parent = path4.posix.dirname(directory);
|
|
1195
|
+
directory = parent === "." ? "" : parent;
|
|
1196
|
+
}
|
|
1197
|
+
return void 0;
|
|
1198
|
+
}
|
|
1199
|
+
function gradleDistributionVersion(text) {
|
|
1200
|
+
const url = propertyFile(text).get("distributionUrl")?.replaceAll("\\:", ":");
|
|
1201
|
+
return url?.match(/gradle-([0-9][0-9A-Za-z.+-]*)-(?:all|bin)\.zip(?:$|[?#])/)?.[1];
|
|
1202
|
+
}
|
|
1203
|
+
function propertyFile(text) {
|
|
1204
|
+
const result = /* @__PURE__ */ new Map();
|
|
1205
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
1206
|
+
const line = raw.trim();
|
|
1207
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) continue;
|
|
1208
|
+
const match = /^([^:=\s]+)\s*[:=]\s*(.*)$/.exec(line);
|
|
1209
|
+
if (match?.[1]) result.set(match[1], match[2] ?? "");
|
|
1210
|
+
}
|
|
1211
|
+
return result;
|
|
1212
|
+
}
|
|
1213
|
+
function valueArray(value) {
|
|
1214
|
+
return value === void 0 || value === null ? [] : Array.isArray(value) ? value : [value];
|
|
1215
|
+
}
|
|
1216
|
+
function parseGemfile(context, relative, text) {
|
|
1217
|
+
const exact = gemResolvedVersions(context, path4.posix.dirname(relative));
|
|
1218
|
+
const dependencies = [];
|
|
1219
|
+
let developmentDepth = 0;
|
|
1220
|
+
const invalidLines = [];
|
|
1221
|
+
text.split(/\r?\n/).forEach((rawLine, index) => {
|
|
1222
|
+
const line = rawLine.replace(/\s+#.*$/, "").trim();
|
|
1223
|
+
if (line && /^gem\b/.test(line) && !/^gem\s+["'][^"']+["']/.test(line)) invalidLines.push(index + 1);
|
|
1224
|
+
if (/^group\s+.*\b(?:development|test)\b.*\bdo\b/.test(line)) developmentDepth += 1;
|
|
1225
|
+
const match = line.match(/^gem\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/);
|
|
1226
|
+
if (match?.[1]) dependencies.push({
|
|
1227
|
+
name: match[1],
|
|
1228
|
+
...match[2] ? { declared: match[2] } : {},
|
|
1229
|
+
...exact.get(match[1]) ? { resolved: exact.get(match[1]) } : {},
|
|
1230
|
+
dev: developmentDepth > 0
|
|
1231
|
+
});
|
|
1232
|
+
if (developmentDepth > 0 && /^end\b/.test(line)) developmentDepth -= 1;
|
|
1233
|
+
});
|
|
1234
|
+
const ruby = text.match(/^ruby\s+["']([^"']+)["']/m)?.[1];
|
|
1235
|
+
const facts = baseFacts(relative, "bundler", invalidLines.length ? [`invalid Gemfile gem declaration at lines ${invalidLines.join(", ")}`] : []);
|
|
1236
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
1237
|
+
facts.runtimes = [{ name: "Ruby", version: ruby }];
|
|
1238
|
+
facts.frameworks = inferFrameworks(dependencies);
|
|
1239
|
+
return finalize(facts);
|
|
1240
|
+
}
|
|
1241
|
+
function parseGemfileLock(relative, text) {
|
|
1242
|
+
const dependencies = [];
|
|
1243
|
+
let inSpecs = false;
|
|
1244
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1245
|
+
if (/^(?:GEM|PATH|GIT)$/.test(line)) {
|
|
1246
|
+
inSpecs = false;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
if (/^ specs:$/.test(line)) {
|
|
1250
|
+
inSpecs = true;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (/^[A-Z][A-Z ]+$/.test(line)) {
|
|
1254
|
+
inSpecs = false;
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (!inSpecs) continue;
|
|
1258
|
+
const match = line.match(/^ ([A-Za-z0-9_.-]+) \(([^)]+)\)\s*$/);
|
|
1259
|
+
if (match?.[1]) dependencies.push({ name: match[1], resolved: match[2], dev: false });
|
|
1260
|
+
}
|
|
1261
|
+
const facts = baseFacts(relative, "bundler-lock", []);
|
|
1262
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
1263
|
+
facts.metadata = {
|
|
1264
|
+
bundler_version: text.match(/^BUNDLED WITH\s*\n\s+([^\s]+)$/m)?.[1],
|
|
1265
|
+
ruby_version: text.match(/^RUBY VERSION\s*\n\s+ruby\s+([^\s]+)$/m)?.[1]
|
|
1266
|
+
};
|
|
1267
|
+
if (text.trim() && !/^(?:GEM|PATH|GIT)\s*$/m.test(text)) facts.errors.push("Gemfile.lock contained no GEM, PATH, or GIT section");
|
|
1268
|
+
return finalize(facts);
|
|
1269
|
+
}
|
|
1270
|
+
function parseComposerJson(context, relative, text) {
|
|
1271
|
+
const value = parseJson(context, text);
|
|
1272
|
+
const declared = record(value.require);
|
|
1273
|
+
const dev = record(value["require-dev"]);
|
|
1274
|
+
const exact = composerResolvedVersions(context, path4.posix.dirname(relative));
|
|
1275
|
+
const dependencies = [
|
|
1276
|
+
...Object.entries(declared).filter(([name]) => name !== "php").map(([name, version]) => ({
|
|
1277
|
+
name,
|
|
1278
|
+
declared: string(version),
|
|
1279
|
+
resolved: exact.get(name),
|
|
1280
|
+
dev: false
|
|
1281
|
+
})),
|
|
1282
|
+
...Object.entries(dev).map(([name, version]) => ({
|
|
1283
|
+
name,
|
|
1284
|
+
declared: string(version),
|
|
1285
|
+
resolved: exact.get(name),
|
|
1286
|
+
dev: true
|
|
1287
|
+
}))
|
|
1288
|
+
];
|
|
1289
|
+
const facts = baseFacts(relative, "composer", []);
|
|
1290
|
+
facts.product = { name: string(value.name), version: string(value.version) };
|
|
1291
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
1292
|
+
facts.frameworks = inferFrameworks(dependencies);
|
|
1293
|
+
facts.runtimes = [{ name: "PHP", version: string(declared.php) }];
|
|
1294
|
+
facts.scripts = composerScripts(value.scripts);
|
|
1295
|
+
return finalize(facts);
|
|
1296
|
+
}
|
|
1297
|
+
function parseComposerLock(context, relative, text) {
|
|
1298
|
+
const value = parseJson(context, text);
|
|
1299
|
+
const dependencies = [
|
|
1300
|
+
...array(value.packages).flatMap((raw) => {
|
|
1301
|
+
const row = record(raw);
|
|
1302
|
+
const name = string(row.name);
|
|
1303
|
+
return name ? [{ name, resolved: string(row.version), dev: false }] : [];
|
|
1304
|
+
}),
|
|
1305
|
+
...array(value["packages-dev"]).flatMap((raw) => {
|
|
1306
|
+
const row = record(raw);
|
|
1307
|
+
const name = string(row.name);
|
|
1308
|
+
return name ? [{ name, resolved: string(row.version), dev: true }] : [];
|
|
1309
|
+
})
|
|
1310
|
+
];
|
|
1311
|
+
const facts = baseFacts(relative, "composer-lock", []);
|
|
1312
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
1313
|
+
facts.metadata = { content_hash: string(value["content-hash"]), plugin_api_version: string(value["plugin-api-version"]) };
|
|
1314
|
+
return finalize(facts);
|
|
1315
|
+
}
|
|
1316
|
+
function parsePyproject(relative, text) {
|
|
1317
|
+
const project = section(text, "project");
|
|
1318
|
+
const poetry = section(text, "tool.poetry");
|
|
1319
|
+
const dependencyLines = section(text, "tool.poetry.dependencies");
|
|
1320
|
+
const dependencies = parseTomlDependencies(dependencyLines, false).filter((dependency) => dependency.name !== "python");
|
|
1321
|
+
dependencies.push(...parseTomlDependencies(section(text, "tool.poetry.dev-dependencies"), true));
|
|
1322
|
+
for (const group of sectionsMatching(text, /^tool\.poetry\.group\.[^.]+\.dependencies$/)) {
|
|
1323
|
+
dependencies.push(...parseTomlDependencies(group.body, true));
|
|
1324
|
+
}
|
|
1325
|
+
const pyList = project.match(/dependencies\s*=\s*\[([\s\S]*?)\]/)?.[1] ?? "";
|
|
1326
|
+
for (const match of pyList.matchAll(/["']([A-Za-z0-9_.-]+)([^"']*)["']/g)) dependencies.push({ name: match[1], declared: match[2]?.trim() || void 0, dev: false });
|
|
1327
|
+
for (const optional of sectionsMatching(text, /^project\.optional-dependencies$/)) {
|
|
1328
|
+
for (const match of optional.body.matchAll(/["']([A-Za-z0-9_.-]+)([^"']*)["']/g)) {
|
|
1329
|
+
dependencies.push({ name: match[1], declared: match[2]?.trim() || void 0, dev: true });
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const facts = baseFacts(relative, "pyproject", /^\s*\[[^\]]+\]\s*$/m.test(text) ? [] : ["pyproject.toml contains no TOML table"]);
|
|
1333
|
+
facts.product = { name: quoted(project, "name") ?? quoted(poetry, "name"), version: quoted(project, "version") ?? quoted(poetry, "version") };
|
|
1334
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
1335
|
+
facts.frameworks = inferFrameworks(facts.dependencies);
|
|
1336
|
+
facts.runtimes = [{ name: "Python", version: quoted(project, "requires-python") ?? quoted(dependencyLines, "python") }];
|
|
1337
|
+
return finalize(facts);
|
|
1338
|
+
}
|
|
1339
|
+
function parseRequirements(relative, text) {
|
|
1340
|
+
const includes = [];
|
|
1341
|
+
const constraints = [];
|
|
1342
|
+
const directives = [];
|
|
1343
|
+
const invalidLines = [];
|
|
1344
|
+
const dependencies = text.split(/\r?\n/).flatMap((line, index) => {
|
|
1345
|
+
const cleaned = line.replace(/^\s*#.*$/, "").replace(/\s+#.*$/, "").trim();
|
|
1346
|
+
const include = cleaned.match(/^(?:-r|--require)\s+(.+)$/);
|
|
1347
|
+
if (include?.[1]) {
|
|
1348
|
+
includes.push(include[1].trim());
|
|
1349
|
+
return [];
|
|
1350
|
+
}
|
|
1351
|
+
const constraint = cleaned.match(/^(?:-c|--constraint)\s+(.+)$/);
|
|
1352
|
+
if (constraint?.[1]) {
|
|
1353
|
+
constraints.push(constraint[1].trim());
|
|
1354
|
+
return [];
|
|
1355
|
+
}
|
|
1356
|
+
if (!cleaned) return [];
|
|
1357
|
+
if (cleaned.includes("git+")) {
|
|
1358
|
+
const egg = cleaned.match(/[#&]egg=([A-Za-z0-9_.-]+)/)?.[1];
|
|
1359
|
+
if (egg) return [{ name: egg, declared: cleaned, dev: /(?:dev|test)/i.test(path4.posix.basename(relative)) }];
|
|
1360
|
+
invalidLines.push(index + 1);
|
|
1361
|
+
return [];
|
|
1362
|
+
}
|
|
1363
|
+
if (cleaned.startsWith("-")) {
|
|
1364
|
+
const directive = cleaned.match(/^(--?[A-Za-z][A-Za-z-]*)\b/)?.[1];
|
|
1365
|
+
if (directive) directives.push(directive);
|
|
1366
|
+
else invalidLines.push(index + 1);
|
|
1367
|
+
return [];
|
|
1368
|
+
}
|
|
1369
|
+
const match = cleaned.match(/^([A-Za-z0-9_.-]+)\s*(.*)$/);
|
|
1370
|
+
if (!match?.[1]) {
|
|
1371
|
+
invalidLines.push(index + 1);
|
|
1372
|
+
return [];
|
|
1373
|
+
}
|
|
1374
|
+
return [{ name: match[1], declared: match[2] || void 0, dev: /(?:dev|test)/i.test(path4.posix.basename(relative)) }];
|
|
1375
|
+
});
|
|
1376
|
+
const facts = baseFacts(relative, "requirements", invalidLines.length ? [`unparsed requirements syntax at lines ${invalidLines.slice(0, 20).join(", ")}${invalidLines.length > 20 ? "\u2026" : ""}`] : []);
|
|
1377
|
+
facts.dependencies = dependencies;
|
|
1378
|
+
facts.frameworks = inferFrameworks(dependencies);
|
|
1379
|
+
facts.runtimes = [{ name: "Python" }];
|
|
1380
|
+
facts.metadata = { includes, constraints, directives: [...new Set(directives)].sort() };
|
|
1381
|
+
return finalize(facts);
|
|
1382
|
+
}
|
|
1383
|
+
function parseTsconfig(context, relative, text) {
|
|
1384
|
+
const errors = [];
|
|
1385
|
+
const layer = loadTsconfigLayer(context, relative, text, 0, /* @__PURE__ */ new Set(), errors);
|
|
1386
|
+
const compiler = layer.compiler;
|
|
1387
|
+
const aliases = Object.entries(record(compiler.paths)).map(([prefix, targets]) => ({ prefix, targets: array(targets).map(String) }));
|
|
1388
|
+
const declaringDirectory = path4.posix.dirname(relative);
|
|
1389
|
+
const resolutionOrigin = Object.prototype.hasOwnProperty.call(compiler, "baseUrl") ? layer.baseUrlOrigin : layer.pathsOrigin;
|
|
1390
|
+
const baseDirectory = path4.posix.dirname(resolutionOrigin || relative);
|
|
1391
|
+
const declaredBaseUrl = string(compiler.baseUrl) ?? "";
|
|
1392
|
+
const effectiveBaseUrl = normalizeRelativeJoin(declaringDirectory, baseDirectory, declaredBaseUrl);
|
|
1393
|
+
const facts = baseFacts(relative, "tsconfig", errors);
|
|
1394
|
+
facts.runtimes = [{ name: "TypeScript", version: string(compiler.target) }];
|
|
1395
|
+
facts.aliases = aliases;
|
|
1396
|
+
facts.metadata = {
|
|
1397
|
+
baseUrl: effectiveBaseUrl,
|
|
1398
|
+
extends_chain: layer.chain,
|
|
1399
|
+
external_extends: [...new Set(layer.externalExtends)].sort(),
|
|
1400
|
+
paths_origin: layer.pathsOrigin,
|
|
1401
|
+
base_url_origin: layer.baseUrlOrigin
|
|
1402
|
+
};
|
|
1403
|
+
return finalize(facts);
|
|
1404
|
+
}
|
|
1405
|
+
function loadTsconfigLayer(context, relative, providedText, depth, visiting, errors) {
|
|
1406
|
+
if (depth > context.limits.maxTsconfigExtendsDepth) {
|
|
1407
|
+
errors.push(`tsconfig extends depth exceeds ${context.limits.maxTsconfigExtendsDepth} at ${relative}`);
|
|
1408
|
+
return { compiler: {}, chain: [], externalExtends: [], pathsOrigin: relative, baseUrlOrigin: relative };
|
|
1409
|
+
}
|
|
1410
|
+
if (visiting.has(relative)) {
|
|
1411
|
+
errors.push(`tsconfig extends cycle detected at ${relative}`);
|
|
1412
|
+
return { compiler: {}, chain: [], externalExtends: [], pathsOrigin: relative, baseUrlOrigin: relative };
|
|
1413
|
+
}
|
|
1414
|
+
visiting.add(relative);
|
|
1415
|
+
let value;
|
|
1416
|
+
try {
|
|
1417
|
+
const text = providedText ?? readRepoText(context, relative);
|
|
1418
|
+
validateManifestText(text, context.limits);
|
|
1419
|
+
value = parseJson(context, text, true);
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
errors.push(`cannot parse extended tsconfig ${relative}: ${message(error)}`);
|
|
1422
|
+
visiting.delete(relative);
|
|
1423
|
+
return { compiler: {}, chain: [], externalExtends: [], pathsOrigin: relative, baseUrlOrigin: relative };
|
|
1424
|
+
}
|
|
1425
|
+
let effective = { compiler: {}, chain: [], externalExtends: [], pathsOrigin: relative, baseUrlOrigin: relative };
|
|
1426
|
+
for (const specifier of tsconfigExtends(value.extends)) {
|
|
1427
|
+
const target = resolveTsconfigExtends(relative, specifier);
|
|
1428
|
+
if (!target) {
|
|
1429
|
+
effective.externalExtends.push(specifier);
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
const parent = loadTsconfigLayer(context, target, void 0, depth + 1, visiting, errors);
|
|
1433
|
+
effective = mergeTsconfigLayers(effective, parent);
|
|
1434
|
+
}
|
|
1435
|
+
const current = record(value.compilerOptions);
|
|
1436
|
+
const mergedCompiler = { ...effective.compiler, ...current };
|
|
1437
|
+
if (Object.prototype.hasOwnProperty.call(current, "paths")) effective.pathsOrigin = relative;
|
|
1438
|
+
if (Object.prototype.hasOwnProperty.call(current, "baseUrl")) effective.baseUrlOrigin = relative;
|
|
1439
|
+
effective.compiler = mergedCompiler;
|
|
1440
|
+
effective.chain.push(relative);
|
|
1441
|
+
visiting.delete(relative);
|
|
1442
|
+
return effective;
|
|
1443
|
+
}
|
|
1444
|
+
function parseAnalysisOptions(context, relative, text) {
|
|
1445
|
+
const value = parseYamlRecord(context, text, "analysis_options.yaml");
|
|
1446
|
+
const analyzer = record(value.analyzer);
|
|
1447
|
+
const linter = record(value.linter);
|
|
1448
|
+
const plugins = array(analyzer.plugins).map(String);
|
|
1449
|
+
const excludes = array(analyzer.exclude).map(String);
|
|
1450
|
+
const rulesValue = linter.rules;
|
|
1451
|
+
const lintRules = Array.isArray(rulesValue) ? rulesValue.map(String) : Object.entries(record(rulesValue)).filter(([, enabled]) => enabled !== false).map(([name]) => name);
|
|
1452
|
+
const facts = baseFacts(relative, "dart-analysis-options", []);
|
|
1453
|
+
facts.runtimes = [{ name: "Dart analyzer" }];
|
|
1454
|
+
facts.metadata = { include: string(value.include), plugins, excludes, lint_rules: lintRules.sort() };
|
|
1455
|
+
return finalize(facts);
|
|
1456
|
+
}
|
|
1457
|
+
function parseEnvExample(relative, text) {
|
|
1458
|
+
const keys = [];
|
|
1459
|
+
const invalidLines = [];
|
|
1460
|
+
text.split(/\r?\n/).forEach((raw, index) => {
|
|
1461
|
+
const line = raw.trim();
|
|
1462
|
+
if (!line || line.startsWith("#")) return;
|
|
1463
|
+
const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
1464
|
+
if (match?.[1]) keys.push(match[1]);
|
|
1465
|
+
else invalidLines.push(index + 1);
|
|
1466
|
+
});
|
|
1467
|
+
const facts = baseFacts(relative, "environment-template", invalidLines.length ? [`invalid dotenv assignment syntax at lines ${invalidLines.slice(0, 20).join(", ")}${invalidLines.length > 20 ? "\u2026" : ""}`] : []);
|
|
1468
|
+
facts.metadata = { keys: [...new Set(keys)].sort(), key_count: new Set(keys).size };
|
|
1469
|
+
return finalize(facts);
|
|
1470
|
+
}
|
|
1471
|
+
function parseDockerCompose(context, relative, text) {
|
|
1472
|
+
const value = parseYamlRecord(context, text, "Docker Compose manifest");
|
|
1473
|
+
if (!("services" in value) || !value.services || typeof value.services !== "object" || Array.isArray(value.services)) {
|
|
1474
|
+
throw new Error("Docker Compose manifest must contain a services mapping");
|
|
1475
|
+
}
|
|
1476
|
+
const services = Object.entries(record(value.services)).map(([name, raw]) => {
|
|
1477
|
+
const service = record(raw);
|
|
1478
|
+
const environment = service.environment;
|
|
1479
|
+
const environmentKeys = Array.isArray(environment) ? environment.flatMap((entry) => {
|
|
1480
|
+
const key = String(entry).split("=", 1)[0]?.trim();
|
|
1481
|
+
return key ? [key] : [];
|
|
1482
|
+
}) : Object.keys(record(environment));
|
|
1483
|
+
const build = typeof service.build === "string" ? service.build : string(record(service.build).context);
|
|
1484
|
+
return {
|
|
1485
|
+
name,
|
|
1486
|
+
image: string(service.image),
|
|
1487
|
+
build,
|
|
1488
|
+
depends_on: Array.isArray(service.depends_on) ? service.depends_on.map(String) : Object.keys(record(service.depends_on)),
|
|
1489
|
+
environment_keys: [...new Set(environmentKeys)].sort(),
|
|
1490
|
+
env_files: array(service.env_file).map(String)
|
|
1491
|
+
};
|
|
1492
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
1493
|
+
const facts = baseFacts(relative, "docker-compose", []);
|
|
1494
|
+
facts.frameworks = [{ name: "Docker Compose", kind: "build" }];
|
|
1495
|
+
facts.metadata = { service_count: services.length, services };
|
|
1496
|
+
return finalize(facts);
|
|
1497
|
+
}
|
|
1498
|
+
function parseRepositoryLocations(context, relative, text) {
|
|
1499
|
+
const repositories = starlarkRepositoryEntries(text);
|
|
1500
|
+
const facts = baseFacts(relative, "bazel-repository-locations", []);
|
|
1501
|
+
facts.dependencies = dedupeDependencies(repositories.map((entry) => ({
|
|
1502
|
+
name: entry.name,
|
|
1503
|
+
...entry.version ? { declared: entry.version, resolved: entry.version } : {},
|
|
1504
|
+
dev: false
|
|
1505
|
+
})));
|
|
1506
|
+
facts.frameworks = bazelFramework(context);
|
|
1507
|
+
facts.metadata = {
|
|
1508
|
+
repository_count: repositories.length,
|
|
1509
|
+
repositories: repositories.slice(0, 500),
|
|
1510
|
+
truncated: repositories.length > 500
|
|
1511
|
+
};
|
|
1512
|
+
return finalize(facts);
|
|
1513
|
+
}
|
|
1514
|
+
function parseDependencyMetadata(context, relative, text) {
|
|
1515
|
+
const value = parseYaml(context, text);
|
|
1516
|
+
const dependencies = collectYamlDependencies(value, void 0, [], /* @__PURE__ */ new WeakSet(), 0, context.limits.maxNestingDepth);
|
|
1517
|
+
const facts = baseFacts(relative, "envoy-dependency-metadata", []);
|
|
1518
|
+
facts.dependencies = dedupeDependencies(dependencies);
|
|
1519
|
+
facts.frameworks = bazelFramework(context);
|
|
1520
|
+
facts.metadata = {
|
|
1521
|
+
dependency_count: facts.dependencies.length,
|
|
1522
|
+
dependency_names: facts.dependencies.map((dependency) => dependency.name).slice(0, 1e3),
|
|
1523
|
+
truncated: facts.dependencies.length > 1e3
|
|
1524
|
+
};
|
|
1525
|
+
return finalize(facts);
|
|
1526
|
+
}
|
|
1527
|
+
function parseExtensionsMetadata(context, relative, text) {
|
|
1528
|
+
const value = parseYamlRecord(context, text, "extensions metadata");
|
|
1529
|
+
const nested = record(value.extensions);
|
|
1530
|
+
const source = Object.keys(nested).length ? nested : value;
|
|
1531
|
+
const extensions = Object.entries(source).flatMap(([name, raw]) => {
|
|
1532
|
+
const row = record(raw);
|
|
1533
|
+
if (!Object.keys(row).length) return [];
|
|
1534
|
+
return [{
|
|
1535
|
+
name,
|
|
1536
|
+
status: string(row.status),
|
|
1537
|
+
security_posture: string(row.security_posture),
|
|
1538
|
+
categories: array(row.categories).map(String),
|
|
1539
|
+
type_urls: array(row.type_urls).map(String)
|
|
1540
|
+
}];
|
|
1541
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
1542
|
+
const facts = baseFacts(relative, "envoy-extensions-metadata", []);
|
|
1543
|
+
facts.metadata = {
|
|
1544
|
+
extension_count: extensions.length,
|
|
1545
|
+
extensions: extensions.slice(0, 1e3),
|
|
1546
|
+
truncated: extensions.length > 1e3
|
|
1547
|
+
};
|
|
1548
|
+
return finalize(facts);
|
|
1549
|
+
}
|
|
1550
|
+
function parseModuleBazelLock(context, relative, text) {
|
|
1551
|
+
const value = parseJson(context, text);
|
|
1552
|
+
const registryFiles = Object.keys(record(value.registryFileHashes));
|
|
1553
|
+
const modules = registryFiles.flatMap((filename) => {
|
|
1554
|
+
const match = filename.match(/\/modules\/([^/]+)\/([^/]+)\//);
|
|
1555
|
+
return match?.[1] && match[2] ? [{ name: decodeURIComponent(match[1]), resolved: decodeURIComponent(match[2]), dev: false }] : [];
|
|
1556
|
+
});
|
|
1557
|
+
const moduleExtensions = Object.keys(record(value.moduleExtensions)).sort();
|
|
1558
|
+
const facts = baseFacts(relative, "bazel-module-lock", []);
|
|
1559
|
+
facts.dependencies = dedupeDependencies(modules);
|
|
1560
|
+
facts.frameworks = bazelFramework(context);
|
|
1561
|
+
facts.metadata = {
|
|
1562
|
+
lock_file_version: typeof value.lockFileVersion === "number" ? value.lockFileVersion : string(value.lockFileVersion),
|
|
1563
|
+
registry_file_count: registryFiles.length,
|
|
1564
|
+
module_extension_count: moduleExtensions.length,
|
|
1565
|
+
module_extensions: moduleExtensions.slice(0, 500),
|
|
1566
|
+
generated_repository_count: countRecordEntriesNamed(value, "generatedRepoSpecs", context.limits.maxNestingDepth),
|
|
1567
|
+
truncated: moduleExtensions.length > 500
|
|
1568
|
+
};
|
|
1569
|
+
return finalize(facts);
|
|
1570
|
+
}
|
|
1571
|
+
function parseBazelRc(context, relative, text) {
|
|
1572
|
+
const commandCounts = /* @__PURE__ */ new Map();
|
|
1573
|
+
const configs = /* @__PURE__ */ new Set();
|
|
1574
|
+
const imports = [];
|
|
1575
|
+
let optionCount = 0;
|
|
1576
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
1577
|
+
const line = rawLine.replace(/\s+#.*$/, "").trim();
|
|
1578
|
+
if (!line || line.startsWith("#")) continue;
|
|
1579
|
+
const [selector = "", ...options] = line.split(/\s+/);
|
|
1580
|
+
if (selector === "import" || selector === "try-import") {
|
|
1581
|
+
if (options[0]) imports.push(options[0]);
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
const [command = selector, config] = selector.split(":", 2);
|
|
1585
|
+
commandCounts.set(command, (commandCounts.get(command) ?? 0) + 1);
|
|
1586
|
+
if (config) configs.add(config);
|
|
1587
|
+
optionCount += options.length;
|
|
1588
|
+
}
|
|
1589
|
+
const facts = baseFacts(relative, "bazel-config", []);
|
|
1590
|
+
facts.frameworks = bazelFramework(context);
|
|
1591
|
+
facts.metadata = {
|
|
1592
|
+
commands: [...commandCounts].sort(([a], [b]) => a.localeCompare(b)).map(([command, lines]) => ({ command, lines })),
|
|
1593
|
+
configs: [...configs].sort(),
|
|
1594
|
+
imports: [...new Set(imports)].sort(),
|
|
1595
|
+
option_count: optionCount
|
|
1596
|
+
};
|
|
1597
|
+
return finalize(facts);
|
|
1598
|
+
}
|
|
1599
|
+
function parseBazelVersion(relative, text) {
|
|
1600
|
+
const version = text.trim() || void 0;
|
|
1601
|
+
const facts = baseFacts(relative, "bazel-version", []);
|
|
1602
|
+
facts.frameworks = [{ name: "Bazel", version, kind: "build" }];
|
|
1603
|
+
facts.runtimes = version ? [{ name: "Bazel", version }] : [];
|
|
1604
|
+
facts.metadata = { version };
|
|
1605
|
+
return finalize(facts);
|
|
1606
|
+
}
|
|
1607
|
+
function parseBazel(context, relative, text) {
|
|
1608
|
+
const base = path4.posix.basename(relative);
|
|
1609
|
+
const kind = base.startsWith("BUILD") ? "bazel-build" : "bazel-workspace";
|
|
1610
|
+
const dependencies = [...text.matchAll(/bazel_dep\s*\(\s*name\s*=\s*["']([^"']+)["'][\s\S]*?version\s*=\s*["']([^"']+)["']/g)].map((match) => ({ name: match[1], resolved: match[2], dev: false }));
|
|
1611
|
+
const moduleCall = text.match(/module\s*\(([\s\S]*?)\)/)?.[1] ?? "";
|
|
1612
|
+
const facts = baseFacts(relative, kind, []);
|
|
1613
|
+
facts.product = { name: attribute(moduleCall, "name"), version: attribute(moduleCall, "version") };
|
|
1614
|
+
facts.dependencies = dependencies;
|
|
1615
|
+
facts.frameworks = bazelFramework(context);
|
|
1616
|
+
facts.metadata = {
|
|
1617
|
+
rule_count: [...text.matchAll(/(?:^|\n)\s*([a-zA-Z_][\w.]*)\s*\(/g)].length,
|
|
1618
|
+
targets: [...text.matchAll(/\bname\s*=\s*["']([^"']+)["']/g)].map((match) => match[1]).slice(0, 500)
|
|
1619
|
+
};
|
|
1620
|
+
return finalize(facts);
|
|
1621
|
+
}
|
|
1622
|
+
function parseCmake(relative, text) {
|
|
1623
|
+
const project = text.match(/project\s*\(\s*([^\s)]+)(?:\s+VERSION\s+([^\s)]+))?/i);
|
|
1624
|
+
const facts = baseFacts(relative, "cmake", []);
|
|
1625
|
+
facts.product = { name: project?.[1], version: project?.[2] };
|
|
1626
|
+
facts.frameworks = [{ name: "CMake", version: text.match(/cmake_minimum_required\s*\(\s*VERSION\s+([^\s)]+)/i)?.[1], kind: "build" }];
|
|
1627
|
+
return finalize(facts);
|
|
1628
|
+
}
|
|
1629
|
+
function parseDockerfile(relative, text) {
|
|
1630
|
+
const images = [...text.matchAll(/^FROM\s+([^\s]+)(?:\s+AS\s+([^\s]+))?/gim)].map((match) => ({ image: match[1], stage: match[2] }));
|
|
1631
|
+
const facts = baseFacts(relative, "docker", images.length ? [] : ["Dockerfile contains no FROM instruction"]);
|
|
1632
|
+
facts.frameworks = [{ name: "Docker", kind: "build" }];
|
|
1633
|
+
facts.metadata = { images };
|
|
1634
|
+
return finalize(facts);
|
|
1635
|
+
}
|
|
1636
|
+
function parseWorkflow(context, relative, text) {
|
|
1637
|
+
const value = parseYamlRecord(context, text, "GitHub Actions workflow");
|
|
1638
|
+
if (!("jobs" in value) || !value.jobs || typeof value.jobs !== "object" || Array.isArray(value.jobs)) {
|
|
1639
|
+
throw new Error("GitHub Actions workflow must contain a jobs mapping");
|
|
1640
|
+
}
|
|
1641
|
+
const jobs = Object.entries(record(value.jobs)).map(([id, row]) => ({ id, name: string(record(row).name) ?? id, uses: string(record(row).uses) }));
|
|
1642
|
+
const facts = baseFacts(relative, "github-actions", []);
|
|
1643
|
+
facts.metadata = { name: string(value.name), jobs };
|
|
1644
|
+
return finalize(facts);
|
|
1645
|
+
}
|
|
1646
|
+
function baseFacts(relative, kind, errors) {
|
|
1647
|
+
return { id: "pending", path: relative, kind, dependencies: [], frameworks: [], runtimes: [], metadata: {}, errors };
|
|
1648
|
+
}
|
|
1649
|
+
function linkInternalMavenManifests(facts, limits) {
|
|
1650
|
+
const byCoordinate = /* @__PURE__ */ new Map();
|
|
1651
|
+
for (const fact of facts) {
|
|
1652
|
+
if (fact.kind !== "maven" || typeof fact.metadata.manifest_alias_target === "string") continue;
|
|
1653
|
+
const coordinate = typeof fact.metadata.coordinate === "string" ? fact.metadata.coordinate : void 0;
|
|
1654
|
+
if (!coordinate) continue;
|
|
1655
|
+
const candidate = {
|
|
1656
|
+
coordinate,
|
|
1657
|
+
path: fact.path,
|
|
1658
|
+
...fact.product?.version ? { version: fact.product.version } : {},
|
|
1659
|
+
...typeof fact.metadata.platform === "string" ? { platform: fact.metadata.platform } : {}
|
|
1660
|
+
};
|
|
1661
|
+
const candidates = byCoordinate.get(coordinate) ?? [];
|
|
1662
|
+
candidates.push(candidate);
|
|
1663
|
+
byCoordinate.set(coordinate, candidates);
|
|
1664
|
+
}
|
|
1665
|
+
for (const candidates of byCoordinate.values()) candidates.sort((a, b) => a.path.localeCompare(b.path));
|
|
1666
|
+
return facts.map((fact) => {
|
|
1667
|
+
if (fact.kind !== "maven" || typeof fact.metadata.manifest_alias_target === "string") return fact;
|
|
1668
|
+
const sourcePlatform = typeof fact.metadata.platform === "string" ? fact.metadata.platform : void 0;
|
|
1669
|
+
const managed = valueArray(fact.metadata.managed_dependencies).flatMap((value) => {
|
|
1670
|
+
const row = record(value);
|
|
1671
|
+
const name = string(row.name);
|
|
1672
|
+
if (!name) return [];
|
|
1673
|
+
return [{ name, declared: string(row.declared), resolved: string(row.resolved), dev: Boolean(row.dev), managed: true }];
|
|
1674
|
+
});
|
|
1675
|
+
const dependencies = [
|
|
1676
|
+
...fact.dependencies.map((dependency) => ({ ...dependency, managed: false })),
|
|
1677
|
+
...managed
|
|
1678
|
+
];
|
|
1679
|
+
const linked = [];
|
|
1680
|
+
const ambiguities = [];
|
|
1681
|
+
for (const dependency of dependencies) {
|
|
1682
|
+
let candidates = (byCoordinate.get(dependency.name) ?? []).filter((candidate2) => candidate2.path !== fact.path);
|
|
1683
|
+
if (!candidates.length) continue;
|
|
1684
|
+
const version = dependency.resolved ?? dependency.declared;
|
|
1685
|
+
if (version) {
|
|
1686
|
+
if (version.includes("${") || /^[[(]/.test(version)) continue;
|
|
1687
|
+
candidates = candidates.filter((candidate2) => candidate2.version === version);
|
|
1688
|
+
if (!candidates.length) continue;
|
|
1689
|
+
}
|
|
1690
|
+
if (candidates.length > 1 && sourcePlatform) {
|
|
1691
|
+
const samePlatform = candidates.filter((candidate2) => candidate2.platform === sourcePlatform);
|
|
1692
|
+
if (samePlatform.length) candidates = samePlatform;
|
|
1693
|
+
}
|
|
1694
|
+
if (candidates.length !== 1) {
|
|
1695
|
+
ambiguities.push({
|
|
1696
|
+
coordinate: dependency.name,
|
|
1697
|
+
...version ? { version } : {},
|
|
1698
|
+
...sourcePlatform ? { platform: sourcePlatform } : {},
|
|
1699
|
+
candidate_count: candidates.length
|
|
1700
|
+
});
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
const candidate = candidates[0];
|
|
1704
|
+
linked.push({
|
|
1705
|
+
coordinate: dependency.name,
|
|
1706
|
+
path: candidate.path,
|
|
1707
|
+
...version ? { version } : {},
|
|
1708
|
+
...candidate.platform ? { platform: candidate.platform } : {},
|
|
1709
|
+
managed: dependency.managed
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
const uniqueLinks = dedupeBy(
|
|
1713
|
+
linked.sort((a, b) => a.coordinate.localeCompare(b.coordinate) || a.path.localeCompare(b.path) || Number(a.managed) - Number(b.managed)),
|
|
1714
|
+
(entry) => `${entry.coordinate}:${entry.path}:${entry.managed}`
|
|
1715
|
+
);
|
|
1716
|
+
const uniqueAmbiguities = dedupeBy(
|
|
1717
|
+
ambiguities.sort((a, b) => a.coordinate.localeCompare(b.coordinate) || (a.version ?? "").localeCompare(b.version ?? "")),
|
|
1718
|
+
(entry) => `${entry.coordinate}:${entry.version ?? ""}:${entry.platform ?? ""}:${entry.candidate_count}`
|
|
1719
|
+
);
|
|
1720
|
+
const metadata = { ...fact.metadata };
|
|
1721
|
+
if (uniqueLinks.length) {
|
|
1722
|
+
metadata.internal_dependency_paths = [...new Set(uniqueLinks.map((entry) => entry.path))].sort();
|
|
1723
|
+
metadata.internal_dependencies = uniqueLinks;
|
|
1724
|
+
}
|
|
1725
|
+
if (uniqueAmbiguities.length) metadata.ambiguous_internal_dependencies = uniqueAmbiguities;
|
|
1726
|
+
return enforceFactBounds({ ...fact, metadata, errors: [...fact.errors] }, limits);
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
function finalize(input) {
|
|
1730
|
+
input.id = `manifest_${hashObject({ path: input.path, kind: input.kind, product: input.product, dependencies: input.dependencies, frameworks: input.frameworks, runtimes: input.runtimes, scripts: input.scripts, workspaces: input.workspaces, aliases: input.aliases, metadata: input.metadata, errors: input.errors }).slice(0, 24)}`;
|
|
1731
|
+
input.frameworks = dedupeBy(input.frameworks, (item) => `${item.name}:${item.version ?? ""}:${item.kind}`);
|
|
1732
|
+
input.runtimes = dedupeBy(input.runtimes, (item) => `${item.name}:${item.version ?? ""}`);
|
|
1733
|
+
return manifestFactsSchema.parse(input);
|
|
1734
|
+
}
|
|
1735
|
+
function enforceFactBounds(input, limits) {
|
|
1736
|
+
const overflows = [];
|
|
1737
|
+
const truncate = (label, items) => {
|
|
1738
|
+
if (items.length <= limits.maxExtractedEntries) return items;
|
|
1739
|
+
overflows.push(`resource limit exceeded: ${label}=${items.length}; maxExtractedEntries=${limits.maxExtractedEntries}`);
|
|
1740
|
+
return items.slice(0, limits.maxExtractedEntries);
|
|
1741
|
+
};
|
|
1742
|
+
input.dependencies = truncate("dependencies", input.dependencies);
|
|
1743
|
+
input.frameworks = truncate("frameworks", input.frameworks);
|
|
1744
|
+
input.runtimes = truncate("runtimes", input.runtimes);
|
|
1745
|
+
if (input.workspaces) input.workspaces = truncate("workspaces", input.workspaces);
|
|
1746
|
+
if (input.aliases) {
|
|
1747
|
+
input.aliases = truncate("aliases", input.aliases).map((alias) => ({
|
|
1748
|
+
...alias,
|
|
1749
|
+
targets: truncate(`alias targets (${alias.prefix})`, alias.targets)
|
|
1750
|
+
}));
|
|
1751
|
+
}
|
|
1752
|
+
if (input.scripts) {
|
|
1753
|
+
const entries = Object.entries(input.scripts);
|
|
1754
|
+
if (entries.length > limits.maxExtractedEntries) {
|
|
1755
|
+
overflows.push(`resource limit exceeded: scripts=${entries.length}; maxExtractedEntries=${limits.maxExtractedEntries}`);
|
|
1756
|
+
input.scripts = Object.fromEntries(entries.slice(0, limits.maxExtractedEntries));
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
input.metadata = boundMetadata(input.metadata, "metadata", limits.maxExtractedEntries, overflows, 0, limits.maxNestingDepth);
|
|
1760
|
+
input.errors = [.../* @__PURE__ */ new Set([...input.errors, ...overflows])];
|
|
1761
|
+
return finalize(input);
|
|
1762
|
+
}
|
|
1763
|
+
function boundMetadata(value, label, maximum, errors, depth, maxDepth) {
|
|
1764
|
+
if (depth > maxDepth) {
|
|
1765
|
+
errors.push(`resource limit exceeded: ${label} depth exceeds maxNestingDepth=${maxDepth}`);
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
if (Array.isArray(value)) {
|
|
1769
|
+
if (value.length > maximum) errors.push(`resource limit exceeded: ${label}=${value.length}; maxExtractedEntries=${maximum}`);
|
|
1770
|
+
return value.slice(0, maximum).map((item, index) => boundMetadata(item, `${label}[${index}]`, maximum, errors, depth + 1, maxDepth));
|
|
1771
|
+
}
|
|
1772
|
+
if (!value || typeof value !== "object") return value;
|
|
1773
|
+
const entries = Object.entries(value);
|
|
1774
|
+
if (entries.length > maximum) errors.push(`resource limit exceeded: ${label} keys=${entries.length}; maxExtractedEntries=${maximum}`);
|
|
1775
|
+
return Object.fromEntries(entries.slice(0, maximum).map(([key, item]) => [
|
|
1776
|
+
key,
|
|
1777
|
+
boundMetadata(item, `${label}.${key}`, maximum, errors, depth + 1, maxDepth)
|
|
1778
|
+
]));
|
|
1779
|
+
}
|
|
1780
|
+
function resolveLimits(options) {
|
|
1781
|
+
const limits = { ...DEFAULT_MANIFEST_PARSE_LIMITS, ...options };
|
|
1782
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
1783
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive safe integer`);
|
|
1784
|
+
}
|
|
1785
|
+
if (limits.maxTsconfigExtendsDepth > 5) throw new Error("maxTsconfigExtendsDepth cannot exceed the execution contract limit of 5");
|
|
1786
|
+
if (limits.maxTotalBytes < limits.maxManifestBytes) throw new Error("maxTotalBytes cannot be lower than maxManifestBytes");
|
|
1787
|
+
return limits;
|
|
1788
|
+
}
|
|
1789
|
+
function readRepoText(context, relativeInput) {
|
|
1790
|
+
const relative = normalizeRepoPath(relativeInput);
|
|
1791
|
+
const cached = context.cache.get(relative);
|
|
1792
|
+
if (cached) {
|
|
1793
|
+
if (!cached.ok) throw new Error(cached.error);
|
|
1794
|
+
return cached.text;
|
|
1795
|
+
}
|
|
1796
|
+
const absolute = assertInside(context.root, path4.join(context.root, ...relative.split("/")));
|
|
1797
|
+
const flags = fs2.constants.O_RDONLY | (fs2.constants.O_NOFOLLOW ?? 0);
|
|
1798
|
+
let descriptor;
|
|
1799
|
+
const chunks = [];
|
|
1800
|
+
let bytes = 0;
|
|
1801
|
+
try {
|
|
1802
|
+
descriptor = fs2.openSync(absolute, flags);
|
|
1803
|
+
const stat = fs2.fstatSync(descriptor);
|
|
1804
|
+
if (!stat.isFile()) throw new Error("safe-read rejected non-regular manifest");
|
|
1805
|
+
if (stat.size > context.limits.maxManifestBytes) {
|
|
1806
|
+
throw new Error(`resource limit exceeded: file bytes=${stat.size}; maxManifestBytes=${context.limits.maxManifestBytes}`);
|
|
1807
|
+
}
|
|
1808
|
+
if (context.totalBytes + stat.size > context.limits.maxTotalBytes) {
|
|
1809
|
+
throw new Error(`resource limit exceeded: total unique bytes would exceed maxTotalBytes=${context.limits.maxTotalBytes}`);
|
|
1810
|
+
}
|
|
1811
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
1812
|
+
while (true) {
|
|
1813
|
+
const read = fs2.readSync(descriptor, buffer, 0, buffer.length, null);
|
|
1814
|
+
if (!read) break;
|
|
1815
|
+
bytes += read;
|
|
1816
|
+
if (bytes > context.limits.maxManifestBytes) {
|
|
1817
|
+
throw new Error(`resource limit exceeded: file grew beyond maxManifestBytes=${context.limits.maxManifestBytes}`);
|
|
1818
|
+
}
|
|
1819
|
+
if (context.totalBytes + bytes > context.limits.maxTotalBytes) {
|
|
1820
|
+
throw new Error(`resource limit exceeded: total unique bytes exceed maxTotalBytes=${context.limits.maxTotalBytes}`);
|
|
1821
|
+
}
|
|
1822
|
+
chunks.push(Buffer.from(buffer.subarray(0, read)));
|
|
1823
|
+
}
|
|
1824
|
+
context.totalBytes += bytes;
|
|
1825
|
+
const text = Buffer.concat(chunks, bytes).toString("utf8");
|
|
1826
|
+
context.cache.set(relative, { ok: true, text, bytes });
|
|
1827
|
+
return text;
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
const detail = message(error);
|
|
1830
|
+
context.cache.set(relative, { ok: false, error: detail });
|
|
1831
|
+
throw new Error(detail);
|
|
1832
|
+
} finally {
|
|
1833
|
+
if (descriptor !== void 0) fs2.closeSync(descriptor);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
function tryReadRepoText(context, relative) {
|
|
1837
|
+
try {
|
|
1838
|
+
return readRepoText(context, relative);
|
|
1839
|
+
} catch {
|
|
1840
|
+
return void 0;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
function validateManifestText(text, limits) {
|
|
1844
|
+
let lineBytes = 0;
|
|
1845
|
+
let depth = 0;
|
|
1846
|
+
let quote;
|
|
1847
|
+
let escaped = false;
|
|
1848
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1849
|
+
const character = text[index];
|
|
1850
|
+
lineBytes += Buffer.byteLength(character);
|
|
1851
|
+
if (lineBytes > limits.maxLineBytes) throw new Error(`resource limit exceeded: line bytes exceed maxLineBytes=${limits.maxLineBytes}`);
|
|
1852
|
+
if (character === "\n") {
|
|
1853
|
+
lineBytes = 0;
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
if (quote) {
|
|
1857
|
+
if (escaped) {
|
|
1858
|
+
escaped = false;
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
if (character === "\\") {
|
|
1862
|
+
escaped = true;
|
|
1863
|
+
continue;
|
|
1864
|
+
}
|
|
1865
|
+
if (character === quote) quote = void 0;
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
if (character === '"' || character === "'") {
|
|
1869
|
+
quote = character;
|
|
1870
|
+
continue;
|
|
1871
|
+
}
|
|
1872
|
+
if (character === "{" || character === "[") {
|
|
1873
|
+
depth += 1;
|
|
1874
|
+
if (depth > limits.maxNestingDepth) throw new Error(`resource limit exceeded: nesting depth exceeds maxNestingDepth=${limits.maxNestingDepth}`);
|
|
1875
|
+
} else if ((character === "}" || character === "]") && depth > 0) depth -= 1;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
function parseJson(context, text, jsonc = false) {
|
|
1879
|
+
const source = jsonc ? stripJsonComments(text).replace(/,\s*([}\]])/g, "$1") : text;
|
|
1880
|
+
validateManifestText(source, context.limits);
|
|
1881
|
+
const value = JSON.parse(source);
|
|
1882
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("JSON manifest root must be an object");
|
|
1883
|
+
return value;
|
|
1884
|
+
}
|
|
1885
|
+
function parseYaml(context, text) {
|
|
1886
|
+
return YAML.parse(text, { maxAliasCount: context.limits.maxYamlAliases });
|
|
1887
|
+
}
|
|
1888
|
+
function parseYamlRecord(context, text, label) {
|
|
1889
|
+
const value = parseYaml(context, text);
|
|
1890
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} root must be a mapping`);
|
|
1891
|
+
return value;
|
|
1892
|
+
}
|
|
1893
|
+
function inferFrameworks(dependencies) {
|
|
1894
|
+
return dependencies.flatMap((dependency) => {
|
|
1895
|
+
const mapping = FRAMEWORKS[dependency.name.toLowerCase()];
|
|
1896
|
+
return mapping ? [{ ...mapping, version: dependency.resolved ?? dependency.declared }] : [];
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
function npmResolvedVersions(context, relativeDir) {
|
|
1900
|
+
const result = /* @__PURE__ */ new Map();
|
|
1901
|
+
const directories = [.../* @__PURE__ */ new Set([relativeDir, ""])];
|
|
1902
|
+
for (const directory of directories) {
|
|
1903
|
+
const packageLock = joinRepoPath(directory, "package-lock.json");
|
|
1904
|
+
try {
|
|
1905
|
+
const text = tryReadRepoText(context, packageLock);
|
|
1906
|
+
if (text) {
|
|
1907
|
+
const value = parseJson(context, text);
|
|
1908
|
+
const packages = record(value.packages);
|
|
1909
|
+
for (const [key, raw] of Object.entries(packages)) if (key.startsWith("node_modules/")) {
|
|
1910
|
+
const version = string(record(raw).version);
|
|
1911
|
+
if (version) result.set(key.slice("node_modules/".length), version);
|
|
1912
|
+
}
|
|
1913
|
+
for (const [name, raw] of Object.entries(record(value.dependencies))) {
|
|
1914
|
+
const version = string(record(raw).version);
|
|
1915
|
+
if (version) result.set(name, version);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
} catch {
|
|
1919
|
+
}
|
|
1920
|
+
const pnpmText = tryReadRepoText(context, joinRepoPath(directory, "pnpm-lock.yaml"));
|
|
1921
|
+
if (pnpmText) for (const dependency of pnpmResolvedDependencies(context, pnpmText)) {
|
|
1922
|
+
if (dependency.resolved && !result.has(dependency.name)) result.set(dependency.name, dependency.resolved);
|
|
1923
|
+
}
|
|
1924
|
+
const yarnText = tryReadRepoText(context, joinRepoPath(directory, "yarn.lock"));
|
|
1925
|
+
if (yarnText) for (const dependency of yarnResolvedDependencies(context, yarnText)) {
|
|
1926
|
+
if (dependency.resolved && !result.has(dependency.name)) result.set(dependency.name, dependency.resolved);
|
|
1927
|
+
}
|
|
1928
|
+
if (result.size) return result;
|
|
1929
|
+
}
|
|
1930
|
+
return result;
|
|
1931
|
+
}
|
|
1932
|
+
function pubspecResolvedVersions(context, relativeDir) {
|
|
1933
|
+
const result = /* @__PURE__ */ new Map();
|
|
1934
|
+
try {
|
|
1935
|
+
const text = tryReadRepoText(context, joinRepoPath(relativeDir, "pubspec.lock"));
|
|
1936
|
+
if (!text) return result;
|
|
1937
|
+
const value = parseYamlRecord(context, text, "pubspec.lock");
|
|
1938
|
+
for (const [name, raw] of Object.entries(record(value.packages))) {
|
|
1939
|
+
const version = string(record(raw).version);
|
|
1940
|
+
if (version) result.set(name, version);
|
|
1941
|
+
}
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1944
|
+
return result;
|
|
1945
|
+
}
|
|
1946
|
+
function bazelFramework(context) {
|
|
1947
|
+
let version;
|
|
1948
|
+
try {
|
|
1949
|
+
version = tryReadRepoText(context, ".bazelversion")?.trim() || void 0;
|
|
1950
|
+
} catch {
|
|
1951
|
+
}
|
|
1952
|
+
return [{ name: "Bazel", version, kind: "build" }];
|
|
1953
|
+
}
|
|
1954
|
+
function pnpmResolvedDependencies(context, text) {
|
|
1955
|
+
const value = parseYamlRecord(context, text, "pnpm-lock.yaml");
|
|
1956
|
+
const snapshots = { ...record(value.packages), ...record(value.snapshots) };
|
|
1957
|
+
const dependencies = Object.keys(snapshots).flatMap((key) => {
|
|
1958
|
+
const cleaned = key.replace(/^\//, "").replace(/\(.+\)$/, "");
|
|
1959
|
+
const modern = cleaned.match(/^(@[^/]+\/[^@]+|[^@/]+)@(.+)$/);
|
|
1960
|
+
if (modern?.[1] && modern[2]) return [{ name: modern[1], resolved: modern[2], dev: false }];
|
|
1961
|
+
const legacy = cleaned.match(/^(@[^/]+\/[^/]+|[^/]+)\/(.+)$/);
|
|
1962
|
+
return legacy?.[1] && legacy[2] ? [{ name: legacy[1], resolved: legacy[2], dev: false }] : [];
|
|
1963
|
+
});
|
|
1964
|
+
return dedupeDependencies(dependencies);
|
|
1965
|
+
}
|
|
1966
|
+
function yarnResolvedDependencies(context, text) {
|
|
1967
|
+
if (/^__metadata\s*:/m.test(text)) {
|
|
1968
|
+
const value = parseYamlRecord(context, text, "yarn.lock");
|
|
1969
|
+
const dependencies2 = Object.entries(value).flatMap(([selector, raw]) => {
|
|
1970
|
+
if (selector === "__metadata") return [];
|
|
1971
|
+
const row = record(raw);
|
|
1972
|
+
const resolved = string(row.version);
|
|
1973
|
+
return yarnSelectorNames(selector).map((name) => ({ name, resolved, dev: false }));
|
|
1974
|
+
});
|
|
1975
|
+
return dedupeDependencies(dependencies2);
|
|
1976
|
+
}
|
|
1977
|
+
const dependencies = [];
|
|
1978
|
+
let selectors = [];
|
|
1979
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1980
|
+
if (line && !/^\s/.test(line) && line.endsWith(":")) {
|
|
1981
|
+
selectors = yarnSelectorNames(line.slice(0, -1));
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
const version = line.match(/^\s+version(?:\s+|:\s*)["']?([^"'\s]+)["']?\s*$/)?.[1];
|
|
1985
|
+
if (!version || !selectors.length) continue;
|
|
1986
|
+
dependencies.push(...selectors.map((name) => ({ name, resolved: version, dev: false })));
|
|
1987
|
+
selectors = [];
|
|
1988
|
+
}
|
|
1989
|
+
return dedupeDependencies(dependencies);
|
|
1990
|
+
}
|
|
1991
|
+
function yarnSelectorNames(raw) {
|
|
1992
|
+
return raw.split(/,\s*(?=["']?@?[^"']+@)/).flatMap((part) => {
|
|
1993
|
+
const selector = part.trim().replace(/^["']|["']$/g, "");
|
|
1994
|
+
const match = selector.match(/^(@[^/]+\/[^@]+|[^@]+)@/);
|
|
1995
|
+
return match?.[1] ? [match[1]] : [];
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
function gemResolvedVersions(context, relativeDir) {
|
|
1999
|
+
const result = /* @__PURE__ */ new Map();
|
|
2000
|
+
const text = tryReadRepoText(context, joinRepoPath(relativeDir, "Gemfile.lock"));
|
|
2001
|
+
if (!text) return result;
|
|
2002
|
+
const facts = parseGemfileLock(joinRepoPath(relativeDir, "Gemfile.lock"), text);
|
|
2003
|
+
for (const dependency of facts.dependencies) if (dependency.resolved) result.set(dependency.name, dependency.resolved);
|
|
2004
|
+
return result;
|
|
2005
|
+
}
|
|
2006
|
+
function composerResolvedVersions(context, relativeDir) {
|
|
2007
|
+
const result = /* @__PURE__ */ new Map();
|
|
2008
|
+
const text = tryReadRepoText(context, joinRepoPath(relativeDir, "composer.lock"));
|
|
2009
|
+
if (!text) return result;
|
|
2010
|
+
try {
|
|
2011
|
+
const value = parseJson(context, text);
|
|
2012
|
+
for (const raw of [...array(value.packages), ...array(value["packages-dev"])]) {
|
|
2013
|
+
const row = record(raw);
|
|
2014
|
+
const name = string(row.name);
|
|
2015
|
+
const version = string(row.version);
|
|
2016
|
+
if (name && version) result.set(name, version);
|
|
2017
|
+
}
|
|
2018
|
+
} catch {
|
|
2019
|
+
}
|
|
2020
|
+
return result;
|
|
2021
|
+
}
|
|
2022
|
+
function composerScripts(value) {
|
|
2023
|
+
const entries = Object.entries(record(value)).flatMap(([name, command]) => {
|
|
2024
|
+
if (typeof command === "string") return [[name, command]];
|
|
2025
|
+
if (Array.isArray(command) && command.every((item) => typeof item === "string")) return [[name, command.join(" && ")]];
|
|
2026
|
+
return [];
|
|
2027
|
+
});
|
|
2028
|
+
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
2029
|
+
}
|
|
2030
|
+
function tsconfigExtends(value) {
|
|
2031
|
+
if (typeof value === "string") return [value];
|
|
2032
|
+
return array(value).flatMap((entry) => typeof entry === "string" ? [entry] : []);
|
|
2033
|
+
}
|
|
2034
|
+
function resolveTsconfigExtends(from, specifier) {
|
|
2035
|
+
if (!specifier.startsWith(".")) return void 0;
|
|
2036
|
+
const base = path4.posix.normalize(path4.posix.join(path4.posix.dirname(from), specifier));
|
|
2037
|
+
const withExtension = /\.json$/i.test(base) ? base : `${base}.json`;
|
|
2038
|
+
return normalizeRepoPath(withExtension);
|
|
2039
|
+
}
|
|
2040
|
+
function mergeTsconfigLayers(left, right) {
|
|
2041
|
+
const rightHasPaths = Object.prototype.hasOwnProperty.call(right.compiler, "paths");
|
|
2042
|
+
const rightHasBaseUrl = Object.prototype.hasOwnProperty.call(right.compiler, "baseUrl");
|
|
2043
|
+
return {
|
|
2044
|
+
compiler: { ...left.compiler, ...right.compiler },
|
|
2045
|
+
chain: [...left.chain, ...right.chain],
|
|
2046
|
+
externalExtends: [...left.externalExtends, ...right.externalExtends],
|
|
2047
|
+
pathsOrigin: rightHasPaths ? right.pathsOrigin : left.pathsOrigin,
|
|
2048
|
+
baseUrlOrigin: rightHasBaseUrl ? right.baseUrlOrigin : left.baseUrlOrigin
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
function normalizeRelativeJoin(fromDirectory, originDirectory, baseUrl) {
|
|
2052
|
+
const originBase = path4.posix.normalize(path4.posix.join(originDirectory, baseUrl || "."));
|
|
2053
|
+
const result = path4.posix.relative(fromDirectory === "." ? "" : fromDirectory, originBase);
|
|
2054
|
+
return result || ".";
|
|
2055
|
+
}
|
|
2056
|
+
function joinRepoPath(directory, filename) {
|
|
2057
|
+
return directory && directory !== "." ? `${directory}/${filename}` : filename;
|
|
2058
|
+
}
|
|
2059
|
+
function starlarkRepositoryEntries(text) {
|
|
2060
|
+
const entries = [];
|
|
2061
|
+
const pattern = /(?:^|\n)[ \t]*(?:["']([^"']+)["']|([A-Za-z_][A-Za-z0-9_]*))[ \t]*(?:=|:)[ \t]*dict[ \t]*\(/g;
|
|
2062
|
+
for (const match of text.matchAll(pattern)) {
|
|
2063
|
+
const name = match[1] ?? match[2];
|
|
2064
|
+
if (!name || /^[A-Z][A-Z0-9_]*$/.test(name) || match.index === void 0) continue;
|
|
2065
|
+
const open = match.index + match[0].lastIndexOf("(");
|
|
2066
|
+
const body = balancedParenthesisBody(text, open);
|
|
2067
|
+
if (body === void 0) continue;
|
|
2068
|
+
const entry = {
|
|
2069
|
+
name,
|
|
2070
|
+
project_name: attribute(body, "project_name"),
|
|
2071
|
+
version: attribute(body, "version"),
|
|
2072
|
+
project_url: attribute(body, "project_url"),
|
|
2073
|
+
release_date: attribute(body, "release_date"),
|
|
2074
|
+
sha256: attribute(body, "sha256")
|
|
2075
|
+
};
|
|
2076
|
+
if (!entry.project_name && !entry.version && !entry.project_url && !entry.sha256) continue;
|
|
2077
|
+
entries.push(entry);
|
|
2078
|
+
}
|
|
2079
|
+
return dedupeBy(entries.sort((a, b) => a.name.localeCompare(b.name)), (entry) => entry.name);
|
|
2080
|
+
}
|
|
2081
|
+
function balancedParenthesisBody(text, open) {
|
|
2082
|
+
if (text[open] !== "(") return void 0;
|
|
2083
|
+
let depth = 0;
|
|
2084
|
+
let quote;
|
|
2085
|
+
for (let index = open; index < text.length; index++) {
|
|
2086
|
+
if (quote) {
|
|
2087
|
+
if (text[index] === "\\") {
|
|
2088
|
+
index++;
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
if (text.startsWith(quote, index)) {
|
|
2092
|
+
index += quote.length - 1;
|
|
2093
|
+
quote = void 0;
|
|
2094
|
+
}
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
if (text.startsWith('"""', index) || text.startsWith("'''", index)) {
|
|
2098
|
+
quote = text.startsWith('"""', index) ? '"""' : "'''";
|
|
2099
|
+
index += 2;
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
if (text[index] === '"' || text[index] === "'") {
|
|
2103
|
+
quote = text[index];
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (text[index] === "(") depth++;
|
|
2107
|
+
if (text[index] === ")" && --depth === 0) return text.slice(open + 1, index);
|
|
2108
|
+
}
|
|
2109
|
+
return void 0;
|
|
2110
|
+
}
|
|
2111
|
+
function collectYamlDependencies(value, nameHint, output = [], seen = /* @__PURE__ */ new WeakSet(), depth = 0, maxDepth = DEFAULT_MANIFEST_PARSE_LIMITS.maxNestingDepth) {
|
|
2112
|
+
if (depth > maxDepth) throw new Error(`resource limit exceeded: YAML value depth exceeds maxNestingDepth=${maxDepth}`);
|
|
2113
|
+
if (Array.isArray(value)) {
|
|
2114
|
+
if (seen.has(value)) return output;
|
|
2115
|
+
seen.add(value);
|
|
2116
|
+
for (const item of value) collectYamlDependencies(item, void 0, output, seen, depth + 1, maxDepth);
|
|
2117
|
+
return output;
|
|
2118
|
+
}
|
|
2119
|
+
const row = record(value);
|
|
2120
|
+
if (!Object.keys(row).length) return output;
|
|
2121
|
+
if (seen.has(row)) return output;
|
|
2122
|
+
seen.add(row);
|
|
2123
|
+
const explicitName = string(row.name) ?? string(row.repository) ?? string(row.repo) ?? string(row.canonical_name) ?? string(row.project_name);
|
|
2124
|
+
const resolved = string(row.version) ?? string(row.release) ?? string(row.tag) ?? string(row.commit) ?? string(row.revision);
|
|
2125
|
+
const locator = resolved ?? string(row.sha256) ?? string(row.project_url) ?? string(row.url);
|
|
2126
|
+
const name = explicitName ?? (nameHint && locator ? nameHint : void 0);
|
|
2127
|
+
if (name && locator) output.push({ name, ...resolved ? { declared: resolved, resolved } : {}, dev: Boolean(row.dev) });
|
|
2128
|
+
for (const [key, item] of Object.entries(row)) if (item && typeof item === "object") collectYamlDependencies(item, key, output, seen, depth + 1, maxDepth);
|
|
2129
|
+
return output;
|
|
2130
|
+
}
|
|
2131
|
+
function countRecordEntriesNamed(value, keyName, maxDepth) {
|
|
2132
|
+
let count = 0;
|
|
2133
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2134
|
+
const pending = [{ value, depth: 0 }];
|
|
2135
|
+
while (pending.length) {
|
|
2136
|
+
const current = pending.pop();
|
|
2137
|
+
if (current.depth > maxDepth) throw new Error(`resource limit exceeded: JSON value depth exceeds maxNestingDepth=${maxDepth}`);
|
|
2138
|
+
if (!current.value || typeof current.value !== "object") continue;
|
|
2139
|
+
if (seen.has(current.value)) continue;
|
|
2140
|
+
seen.add(current.value);
|
|
2141
|
+
if (Array.isArray(current.value)) {
|
|
2142
|
+
for (const item of current.value) pending.push({ value: item, depth: current.depth + 1 });
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
for (const [key, item] of Object.entries(current.value)) {
|
|
2146
|
+
if (key === keyName) count += Object.keys(record(item)).length;
|
|
2147
|
+
pending.push({ value: item, depth: current.depth + 1 });
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
return count;
|
|
2151
|
+
}
|
|
2152
|
+
function parseTomlDependencies(text, dev) {
|
|
2153
|
+
return text.split(/\r?\n/).flatMap((line) => {
|
|
2154
|
+
const match = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=\s*(.+?)\s*(?:#.*)?$/);
|
|
2155
|
+
if (!match?.[1] || !match[2]) return [];
|
|
2156
|
+
const quotedVersion = match[2].match(/["']([^"']+)["']/)?.[1];
|
|
2157
|
+
const inlineVersion = match[2].match(/\bversion\s*=\s*["']([^"']+)["']/)?.[1];
|
|
2158
|
+
return [{ name: match[1], declared: inlineVersion ?? quotedVersion, dev }];
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
function section(text, name) {
|
|
2162
|
+
const lines = text.split(/\r?\n/);
|
|
2163
|
+
const wanted = `[${name}]`;
|
|
2164
|
+
const body = [];
|
|
2165
|
+
let found = false;
|
|
2166
|
+
for (const line of lines) {
|
|
2167
|
+
const candidate = line.replace(/\s+#.*$/, "").trim();
|
|
2168
|
+
if (candidate === wanted) {
|
|
2169
|
+
found = true;
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
if (found && /^\[\[?.+\]\]?$/.test(candidate)) break;
|
|
2173
|
+
if (found) body.push(line);
|
|
2174
|
+
}
|
|
2175
|
+
return body.join("\n");
|
|
2176
|
+
}
|
|
2177
|
+
function sectionsMatching(text, pattern) {
|
|
2178
|
+
const output = [];
|
|
2179
|
+
let active;
|
|
2180
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2181
|
+
const header = line.replace(/\s+#.*$/, "").trim().match(/^\[([^\]]+)\]$/)?.[1];
|
|
2182
|
+
if (header) {
|
|
2183
|
+
if (active) output.push({ name: active.name, body: active.lines.join("\n") });
|
|
2184
|
+
active = pattern.test(header) ? { name: header, lines: [] } : void 0;
|
|
2185
|
+
pattern.lastIndex = 0;
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
active?.lines.push(line);
|
|
2189
|
+
}
|
|
2190
|
+
if (active) output.push({ name: active.name, body: active.lines.join("\n") });
|
|
2191
|
+
return output;
|
|
2192
|
+
}
|
|
2193
|
+
function quoted(text, key) {
|
|
2194
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2195
|
+
return text.match(new RegExp(`^\\s*${escaped}\\s*=\\s*["']([^"']+)["']`, "m"))?.[1];
|
|
2196
|
+
}
|
|
2197
|
+
function attribute(text, key) {
|
|
2198
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2199
|
+
return text.match(new RegExp(`\\b${escaped}\\s*=\\s*["']([^"']+)["']`))?.[1];
|
|
2200
|
+
}
|
|
2201
|
+
function yamlVersion(value) {
|
|
2202
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
2203
|
+
const row = record(value);
|
|
2204
|
+
return string(row.version) ?? string(row.path) ?? string(row.git);
|
|
2205
|
+
}
|
|
2206
|
+
function stripJsonComments(value) {
|
|
2207
|
+
let output = "";
|
|
2208
|
+
let state = "code";
|
|
2209
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2210
|
+
const character = value[index] ?? "";
|
|
2211
|
+
const next = value[index + 1] ?? "";
|
|
2212
|
+
if (state === "string") {
|
|
2213
|
+
output += character;
|
|
2214
|
+
if (character === "\\" && next) {
|
|
2215
|
+
output += next;
|
|
2216
|
+
index += 1;
|
|
2217
|
+
} else if (character === '"') state = "code";
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
if (state === "line") {
|
|
2221
|
+
if (character === "\n") {
|
|
2222
|
+
output += "\n";
|
|
2223
|
+
state = "code";
|
|
2224
|
+
} else output += " ";
|
|
2225
|
+
continue;
|
|
2226
|
+
}
|
|
2227
|
+
if (state === "block") {
|
|
2228
|
+
if (character === "*" && next === "/") {
|
|
2229
|
+
output += " ";
|
|
2230
|
+
index += 1;
|
|
2231
|
+
state = "code";
|
|
2232
|
+
} else output += character === "\n" ? "\n" : " ";
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
if (character === '"') {
|
|
2236
|
+
output += character;
|
|
2237
|
+
state = "string";
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
if (character === "/" && next === "/") {
|
|
2241
|
+
output += " ";
|
|
2242
|
+
index += 1;
|
|
2243
|
+
state = "line";
|
|
2244
|
+
continue;
|
|
2245
|
+
}
|
|
2246
|
+
if (character === "/" && next === "*") {
|
|
2247
|
+
output += " ";
|
|
2248
|
+
index += 1;
|
|
2249
|
+
state = "block";
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
output += character;
|
|
2253
|
+
}
|
|
2254
|
+
return output;
|
|
2255
|
+
}
|
|
2256
|
+
function manifestKind(base) {
|
|
2257
|
+
return base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "manifest";
|
|
2258
|
+
}
|
|
2259
|
+
function record(value) {
|
|
2260
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2261
|
+
}
|
|
2262
|
+
function string(value) {
|
|
2263
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : void 0;
|
|
2264
|
+
}
|
|
2265
|
+
function array(value) {
|
|
2266
|
+
return Array.isArray(value) ? value : [];
|
|
2267
|
+
}
|
|
2268
|
+
function stringRecord(value) {
|
|
2269
|
+
const entries = Object.entries(record(value)).flatMap(([key, item]) => typeof item === "string" ? [[key, item]] : []);
|
|
2270
|
+
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
2271
|
+
}
|
|
2272
|
+
function dedupeDependencies(items) {
|
|
2273
|
+
return dedupeBy(items.sort((a, b) => a.name.localeCompare(b.name)), (item) => `${item.name}:${item.resolved ?? item.declared ?? ""}:${item.dev}`);
|
|
2274
|
+
}
|
|
2275
|
+
function dedupeBy(items, key) {
|
|
2276
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2277
|
+
return items.filter((item) => {
|
|
2278
|
+
const value = key(item);
|
|
2279
|
+
if (seen.has(value)) return false;
|
|
2280
|
+
seen.add(value);
|
|
2281
|
+
return true;
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
function message(error) {
|
|
2285
|
+
return error instanceof Error ? error.message : String(error);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
// ../packages/brain-census/src/stack.ts
|
|
2289
|
+
var DISPLAY = {
|
|
2290
|
+
c: "C",
|
|
2291
|
+
cpp: "C++",
|
|
2292
|
+
csharp: "C#",
|
|
2293
|
+
css: "CSS",
|
|
2294
|
+
dart: "Dart",
|
|
2295
|
+
elixir: "Elixir",
|
|
2296
|
+
go: "Go",
|
|
2297
|
+
graphql: "GraphQL",
|
|
2298
|
+
html: "HTML",
|
|
2299
|
+
java: "Java",
|
|
2300
|
+
javascript: "JavaScript",
|
|
2301
|
+
json: "JSON",
|
|
2302
|
+
kotlin: "Kotlin",
|
|
2303
|
+
lua: "Lua",
|
|
2304
|
+
"objective-c": "Objective-C",
|
|
2305
|
+
perl: "Perl",
|
|
2306
|
+
php: "PHP",
|
|
2307
|
+
proto: "Protobuf",
|
|
2308
|
+
python: "Python",
|
|
2309
|
+
ruby: "Ruby",
|
|
2310
|
+
rust: "Rust",
|
|
2311
|
+
scala: "Scala",
|
|
2312
|
+
shell: "Shell",
|
|
2313
|
+
sql: "SQL",
|
|
2314
|
+
starlark: "Starlark",
|
|
2315
|
+
swift: "Swift",
|
|
2316
|
+
toml: "TOML",
|
|
2317
|
+
typescript: "TypeScript",
|
|
2318
|
+
vue: "Vue",
|
|
2319
|
+
xml: "XML",
|
|
2320
|
+
yaml: "YAML"
|
|
2321
|
+
};
|
|
2322
|
+
function synthesizeStack(files, manifests) {
|
|
2323
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2324
|
+
for (const file of files) {
|
|
2325
|
+
if (!file.language || !["source", "test", "generated"].includes(file.class)) continue;
|
|
2326
|
+
const current = counts.get(file.language) ?? { files: 0, loc: 0 };
|
|
2327
|
+
current.files++;
|
|
2328
|
+
current.loc += file.loc;
|
|
2329
|
+
counts.set(file.language, current);
|
|
2330
|
+
}
|
|
2331
|
+
const totalLoc = [...counts.values()].reduce((sum, item) => sum + item.loc, 0);
|
|
2332
|
+
const languages = [...counts.entries()].map(([language, value]) => ({ language, ...value, share: totalLoc ? value.loc / totalLoc : 0 })).sort((a, b) => b.loc - a.loc || a.language.localeCompare(b.language));
|
|
2333
|
+
const frameworks = dedupe(manifests.flatMap((manifest) => manifest.frameworks), (item) => `${item.name}:${item.version ?? ""}:${item.kind}`);
|
|
2334
|
+
const runtimes = dedupe(manifests.flatMap((manifest) => manifest.runtimes), (item) => `${item.name}:${item.version ?? ""}`);
|
|
2335
|
+
const failures = manifests.flatMap((manifest) => manifest.errors.map((error) => `${manifest.path}: ${error}`));
|
|
2336
|
+
if (!languages.length && !manifests.length) failures.push("no source language or supported manifest was detected");
|
|
2337
|
+
const primaryFrameworks = summaryFrameworks(frameworks.filter((framework) => !["build", "test"].includes(framework.kind)));
|
|
2338
|
+
const buildFrameworks = summaryFrameworks(frameworks.filter((framework) => framework.kind === "build"));
|
|
2339
|
+
const summaryParts = [
|
|
2340
|
+
...primaryFrameworks.slice(0, 4).map(formatVersioned),
|
|
2341
|
+
...languages.filter((language) => language.share >= 0.05 || language === languages[0]).slice(0, 5).map((language) => DISPLAY[language.language]),
|
|
2342
|
+
...buildFrameworks.slice(0, 2).map(formatVersioned)
|
|
2343
|
+
];
|
|
2344
|
+
const summary = [...new Set(summaryParts)].join(" \xB7 ") || "UNMAPPED";
|
|
2345
|
+
const body = { summary, languages, frameworks, runtimes, manifests: manifests.length, failures };
|
|
2346
|
+
return { id: `stack_${hashObject(body).slice(0, 24)}`, ...body };
|
|
2347
|
+
}
|
|
2348
|
+
function summaryFrameworks(items) {
|
|
2349
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2350
|
+
for (const item of items) {
|
|
2351
|
+
const current = byName.get(item.name);
|
|
2352
|
+
if (!current || !current.version && item.version) byName.set(item.name, item);
|
|
2353
|
+
}
|
|
2354
|
+
return [...byName.values()];
|
|
2355
|
+
}
|
|
2356
|
+
function formatVersioned(item) {
|
|
2357
|
+
return item.version ? `${item.name} ${item.version}` : item.name;
|
|
2358
|
+
}
|
|
2359
|
+
function dedupe(items, key) {
|
|
2360
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2361
|
+
return items.filter((item) => {
|
|
2362
|
+
const value = key(item);
|
|
2363
|
+
if (seen.has(value)) return false;
|
|
2364
|
+
seen.add(value);
|
|
2365
|
+
return true;
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// ../packages/brain-census/src/docs.ts
|
|
2370
|
+
function chunkMarkdown(path8, markdown, options = {}) {
|
|
2371
|
+
const targetChars = options.targetChars ?? 1200;
|
|
2372
|
+
const maxChars = options.maxChars ?? 2e3;
|
|
2373
|
+
const normalized = markdown.replace(/\r\n?/g, "\n");
|
|
2374
|
+
const lines = normalized.split("\n");
|
|
2375
|
+
const chunks = [];
|
|
2376
|
+
let breadcrumb = [];
|
|
2377
|
+
let buffer = [];
|
|
2378
|
+
let startLine = 1;
|
|
2379
|
+
let searchCharacterOffset = 0;
|
|
2380
|
+
let searchByteOffset = 0;
|
|
2381
|
+
const flush = () => {
|
|
2382
|
+
const content = buffer.join("\n").trim();
|
|
2383
|
+
if (!content) {
|
|
2384
|
+
buffer = [];
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
for (let offset = 0; offset < content.length; offset += maxChars) {
|
|
2388
|
+
const part = content.slice(offset, offset + maxChars).trim();
|
|
2389
|
+
if (!part) continue;
|
|
2390
|
+
const lineOffset = content.slice(0, offset).split("\n").length - 1;
|
|
2391
|
+
const partLines = part.split("\n").length;
|
|
2392
|
+
const heading = breadcrumb.join(" > ") || "Document";
|
|
2393
|
+
const contentHash = sha256(part);
|
|
2394
|
+
const partCharacterOffset = normalized.indexOf(part, searchCharacterOffset);
|
|
2395
|
+
if (partCharacterOffset < 0) throw new Error(`document chunk cannot be mapped back to ${path8}`);
|
|
2396
|
+
const startByte = searchByteOffset + Buffer.byteLength(normalized.slice(searchCharacterOffset, partCharacterOffset), "utf8");
|
|
2397
|
+
const endByte = startByte + Buffer.byteLength(part, "utf8");
|
|
2398
|
+
searchCharacterOffset = partCharacterOffset + part.length;
|
|
2399
|
+
searchByteOffset = endByte;
|
|
2400
|
+
chunks.push({
|
|
2401
|
+
id: `doc_${hashObject({ path: path8, heading, startLine: startLine + lineOffset, contentHash }).slice(0, 24)}`,
|
|
2402
|
+
path: path8,
|
|
2403
|
+
heading,
|
|
2404
|
+
content: part,
|
|
2405
|
+
contentHash,
|
|
2406
|
+
startLine: startLine + lineOffset,
|
|
2407
|
+
endLine: startLine + lineOffset + partLines - 1,
|
|
2408
|
+
estimatedTokens: Math.ceil(part.length / 4),
|
|
2409
|
+
startByte,
|
|
2410
|
+
endByte,
|
|
2411
|
+
trust: "untrusted_repository_text"
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
buffer = [];
|
|
2415
|
+
};
|
|
2416
|
+
for (let index = 0; index < lines.length; index++) {
|
|
2417
|
+
const line = lines[index];
|
|
2418
|
+
const heading = line.match(/^(#{1,3})\s+(.+?)\s*#*$/);
|
|
2419
|
+
if (heading?.[1] && heading[2]) {
|
|
2420
|
+
flush();
|
|
2421
|
+
const depth = heading[1].length;
|
|
2422
|
+
breadcrumb = [...breadcrumb.slice(0, depth - 1), heading[2].trim()];
|
|
2423
|
+
startLine = index + 2;
|
|
2424
|
+
continue;
|
|
2425
|
+
}
|
|
2426
|
+
if (!buffer.length) startLine = index + 1;
|
|
2427
|
+
buffer.push(line);
|
|
2428
|
+
if (buffer.join("\n").length >= targetChars && !line.trim()) flush();
|
|
2429
|
+
}
|
|
2430
|
+
flush();
|
|
2431
|
+
return chunks;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// ../packages/brain-census/src/test-map.ts
|
|
2435
|
+
import path5 from "path";
|
|
2436
|
+
var NATIVE_IMPLEMENTATION_EXTENSIONS = [".c", ".cc", ".cpp", ".cxx", ".m", ".mm"];
|
|
2437
|
+
var NATIVE_HEADER_EXTENSIONS = [".h", ".hh", ".hpp", ".hxx"];
|
|
2438
|
+
var NATIVE_TEST_EXTENSIONS = new Set(NATIVE_IMPLEMENTATION_EXTENSIONS);
|
|
2439
|
+
function genericConventionPaths(testPath, files) {
|
|
2440
|
+
const directory = path5.posix.dirname(testPath);
|
|
2441
|
+
const basename = path5.posix.basename(testPath);
|
|
2442
|
+
const transformed = basename.replace(/\.(?:test|spec)(?=\.[^.]+$)/i, "").replace(/_(?:test|spec)(?=\.[^.]+$)/i, "").replace(/^test_(?=[^.]+\.[^.]+$)/i, "");
|
|
2443
|
+
if (transformed === basename) return [];
|
|
2444
|
+
const directories = /* @__PURE__ */ new Set([directory]);
|
|
2445
|
+
const segments = directory === "." ? [] : directory.split("/");
|
|
2446
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
2447
|
+
if (!/^(?:__tests__|tests?|spec)$/.test(segments[index] ?? "")) continue;
|
|
2448
|
+
const without = [...segments.slice(0, index), ...segments.slice(index + 1)].join("/") || ".";
|
|
2449
|
+
directories.add(without);
|
|
2450
|
+
if (index === 0) for (const root of ["src", "source", "lib"]) directories.add([root, ...segments.slice(1)].join("/"));
|
|
2451
|
+
}
|
|
2452
|
+
const exactExtension = [...directories].map((candidate) => path5.posix.join(candidate, transformed)).filter((candidate) => files.has(candidate));
|
|
2453
|
+
if (exactExtension.length > 0) return exactExtension;
|
|
2454
|
+
const extension = path5.posix.extname(transformed).toLowerCase();
|
|
2455
|
+
if (!NATIVE_TEST_EXTENSIONS.has(extension)) return [];
|
|
2456
|
+
const stem = transformed.slice(0, -extension.length);
|
|
2457
|
+
const nativeCounterparts = [...directories].flatMap((directory2) => [...NATIVE_IMPLEMENTATION_EXTENSIONS, ...NATIVE_HEADER_EXTENSIONS].map((candidateExtension) => path5.posix.join(directory2, `${stem}${candidateExtension}`))).filter((candidate) => files.has(candidate));
|
|
2458
|
+
return nativeCounterparts.length === 1 ? nativeCounterparts : [];
|
|
2459
|
+
}
|
|
2460
|
+
function javaModuleRoot(testPath, markerIndex) {
|
|
2461
|
+
return testPath.split("/").slice(0, markerIndex).join("/");
|
|
2462
|
+
}
|
|
2463
|
+
function javaProductionModule(testModule) {
|
|
2464
|
+
const segments = testModule.split("/");
|
|
2465
|
+
const module = segments.at(-1) ?? "";
|
|
2466
|
+
if (module.endsWith("-tests")) segments[segments.length - 1] = module.slice(0, -"-tests".length);
|
|
2467
|
+
return segments.filter(Boolean).join("/");
|
|
2468
|
+
}
|
|
2469
|
+
function javaFlavor(filePath) {
|
|
2470
|
+
return filePath === "android" || filePath.startsWith("android/") ? "android" : "jre";
|
|
2471
|
+
}
|
|
2472
|
+
function javaConventionPath(testPath, files) {
|
|
2473
|
+
const basename = path5.posix.basename(testPath);
|
|
2474
|
+
const match = /^(.+?)(?:Test|Tests|Spec)\.java$/.exec(basename);
|
|
2475
|
+
if (!match?.[1]) return void 0;
|
|
2476
|
+
const targetBasename = `${match[1]}.java`;
|
|
2477
|
+
const segments = testPath.split("/");
|
|
2478
|
+
const markerIndex = segments.findLastIndex((segment) => /^(?:test|test-super|tests)$/.test(segment));
|
|
2479
|
+
if (markerIndex < 0) return void 0;
|
|
2480
|
+
const packageSegments = segments.slice(markerIndex + 1, -1);
|
|
2481
|
+
const packageSuffix = [...packageSegments, targetBasename].join("/");
|
|
2482
|
+
const testModule = javaModuleRoot(testPath, markerIndex);
|
|
2483
|
+
const expectedModule = javaProductionModule(testModule);
|
|
2484
|
+
const candidates = [...files].filter((candidate) => {
|
|
2485
|
+
if (candidate === testPath || path5.posix.basename(candidate) !== targetBasename) return false;
|
|
2486
|
+
if (!(candidate === packageSuffix || candidate.endsWith(`/${packageSuffix}`))) return false;
|
|
2487
|
+
return !/(?:^|\/)(?:test|test-super|tests)(?:\/|$)/.test(candidate);
|
|
2488
|
+
});
|
|
2489
|
+
return candidates.sort((left, right) => {
|
|
2490
|
+
const score = (candidate) => {
|
|
2491
|
+
let value = javaFlavor(candidate) === javaFlavor(testPath) ? 1e6 : 0;
|
|
2492
|
+
if (candidate.startsWith(`${expectedModule}/`)) value += 2e5;
|
|
2493
|
+
if (candidate.startsWith(`${testModule}/`)) value += 15e4;
|
|
2494
|
+
if (/(?:^|\/)src(?:\/|$)/.test(candidate)) value += 1e4;
|
|
2495
|
+
return value;
|
|
2496
|
+
};
|
|
2497
|
+
return score(right) - score(left) || left.localeCompare(right);
|
|
2498
|
+
})[0];
|
|
2499
|
+
}
|
|
2500
|
+
function findConventionalSourcePaths(testPath, files) {
|
|
2501
|
+
const results = genericConventionPaths(testPath, files);
|
|
2502
|
+
if (testPath.endsWith(".java")) {
|
|
2503
|
+
const java = javaConventionPath(testPath, files);
|
|
2504
|
+
if (java) results.push(java);
|
|
2505
|
+
}
|
|
2506
|
+
return [...new Set(results)].sort();
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// ../packages/brain-ast/src/query-assets.ts
|
|
2510
|
+
import fs4 from "fs";
|
|
2511
|
+
import path7 from "path";
|
|
2512
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2513
|
+
import { Query } from "web-tree-sitter";
|
|
2514
|
+
import { z as z3 } from "zod";
|
|
2515
|
+
|
|
2516
|
+
// ../packages/brain-ast/src/grammar-registry.ts
|
|
2517
|
+
import fs3 from "fs";
|
|
2518
|
+
import path6 from "path";
|
|
2519
|
+
import { createRequire } from "module";
|
|
2520
|
+
import { fileURLToPath } from "url";
|
|
2521
|
+
import { setFlagsFromString } from "v8";
|
|
2522
|
+
import { z as z2 } from "zod";
|
|
2523
|
+
import { LANGUAGE_VERSION, Language, MIN_COMPATIBLE_VERSION, Parser } from "web-tree-sitter";
|
|
2524
|
+
var grammarEntrySchema = z2.object({
|
|
2525
|
+
id: z2.string().min(1),
|
|
2526
|
+
languages: z2.array(z2.string().min(1)).min(1),
|
|
2527
|
+
extensions: z2.array(z2.string().startsWith(".")).optional(),
|
|
2528
|
+
file: z2.string().min(1),
|
|
2529
|
+
package_subpath: z2.string().min(1),
|
|
2530
|
+
sha256: z2.string().regex(/^[a-f0-9]{64}$/),
|
|
2531
|
+
abi: z2.number().int().positive()
|
|
2532
|
+
}).strict();
|
|
2533
|
+
var grammarManifestSchema = z2.object({
|
|
2534
|
+
schema_version: z2.literal(1),
|
|
2535
|
+
runtime: z2.object({
|
|
2536
|
+
package: z2.literal("web-tree-sitter"),
|
|
2537
|
+
version: z2.string(),
|
|
2538
|
+
license: z2.string(),
|
|
2539
|
+
abi_min: z2.number().int(),
|
|
2540
|
+
abi_max: z2.number().int()
|
|
2541
|
+
}).strict(),
|
|
2542
|
+
source_package: z2.object({
|
|
2543
|
+
name: z2.literal("tree-sitter-wasms"),
|
|
2544
|
+
version: z2.string(),
|
|
2545
|
+
license: z2.string(),
|
|
2546
|
+
repository: z2.string().url()
|
|
2547
|
+
}).strict(),
|
|
2548
|
+
grammars: z2.array(grammarEntrySchema).min(1)
|
|
2549
|
+
}).strict();
|
|
2550
|
+
var defaultAssetRoot = fileURLToPath(new URL("../assets", import.meta.url));
|
|
2551
|
+
var require2 = createRequire(import.meta.url);
|
|
2552
|
+
var manifestCache = /* @__PURE__ */ new Map();
|
|
2553
|
+
var languageCache = /* @__PURE__ */ new Map();
|
|
2554
|
+
var runtimeInitialization;
|
|
2555
|
+
var initializedCorePath;
|
|
2556
|
+
var boundedWasmCompilationConfigured = false;
|
|
2557
|
+
var grammarLoadTail = Promise.resolve();
|
|
2558
|
+
function readUnsignedLeb(bytes, offset) {
|
|
2559
|
+
let value = 0;
|
|
2560
|
+
let shift = 0;
|
|
2561
|
+
for (let cursor = offset; cursor < bytes.length && shift <= 28; cursor += 1, shift += 7) {
|
|
2562
|
+
const byte = bytes[cursor];
|
|
2563
|
+
if (byte === void 0) break;
|
|
2564
|
+
value += (byte & 127) * 2 ** shift;
|
|
2565
|
+
if ((byte & 128) === 0) return { value, nextOffset: cursor + 1 };
|
|
2566
|
+
}
|
|
2567
|
+
throw new GrammarLoadError("grammar_integrity", "invalid unsigned LEB128 value in Tree-sitter core WASM");
|
|
2568
|
+
}
|
|
2569
|
+
function encodeUnsignedLeb(value) {
|
|
2570
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 4294967295) {
|
|
2571
|
+
throw new GrammarLoadError("grammar_integrity", `cannot encode WASM unsigned integer ${value}`);
|
|
2572
|
+
}
|
|
2573
|
+
const encoded = [];
|
|
2574
|
+
let remaining = value;
|
|
2575
|
+
do {
|
|
2576
|
+
let byte = remaining % 128;
|
|
2577
|
+
remaining = Math.floor(remaining / 128);
|
|
2578
|
+
if (remaining > 0) byte |= 128;
|
|
2579
|
+
encoded.push(byte);
|
|
2580
|
+
} while (remaining > 0);
|
|
2581
|
+
return Uint8Array.from(encoded);
|
|
2582
|
+
}
|
|
2583
|
+
function addShellScannerCoreAlias(coreBytes) {
|
|
2584
|
+
if (coreBytes.length < 8 || Buffer.from(coreBytes.subarray(0, 4)).toString("hex") !== "0061736d") {
|
|
2585
|
+
throw new GrammarLoadError("grammar_integrity", "Tree-sitter core is not a valid WASM binary");
|
|
2586
|
+
}
|
|
2587
|
+
let sectionOffset = 8;
|
|
2588
|
+
while (sectionOffset < coreBytes.length) {
|
|
2589
|
+
const sectionId = coreBytes[sectionOffset];
|
|
2590
|
+
if (sectionId === void 0) break;
|
|
2591
|
+
const sectionSize = readUnsignedLeb(coreBytes, sectionOffset + 1);
|
|
2592
|
+
const payloadStart = sectionSize.nextOffset;
|
|
2593
|
+
const payloadEnd = payloadStart + sectionSize.value;
|
|
2594
|
+
if (payloadEnd > coreBytes.length) {
|
|
2595
|
+
throw new GrammarLoadError("grammar_integrity", "Tree-sitter core contains a truncated WASM section");
|
|
2596
|
+
}
|
|
2597
|
+
if (sectionId !== 7) {
|
|
2598
|
+
sectionOffset = payloadEnd;
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
const exportCount = readUnsignedLeb(coreBytes, payloadStart);
|
|
2602
|
+
let cursor = exportCount.nextOffset;
|
|
2603
|
+
let sourceFunctionIndex;
|
|
2604
|
+
let targetAlreadyPresent = false;
|
|
2605
|
+
for (let index = 0; index < exportCount.value; index += 1) {
|
|
2606
|
+
const nameLength = readUnsignedLeb(coreBytes, cursor);
|
|
2607
|
+
const nameEnd = nameLength.nextOffset + nameLength.value;
|
|
2608
|
+
if (nameEnd + 1 > payloadEnd) {
|
|
2609
|
+
throw new GrammarLoadError("grammar_integrity", "Tree-sitter core contains a truncated WASM export");
|
|
2610
|
+
}
|
|
2611
|
+
const name = Buffer.from(coreBytes.subarray(nameLength.nextOffset, nameEnd)).toString("utf8");
|
|
2612
|
+
const kind = coreBytes[nameEnd];
|
|
2613
|
+
const itemIndex = readUnsignedLeb(coreBytes, nameEnd + 1);
|
|
2614
|
+
if (name === "isalpha") targetAlreadyPresent = true;
|
|
2615
|
+
if (name === "iswalpha" && kind === 0) sourceFunctionIndex = itemIndex.value;
|
|
2616
|
+
cursor = itemIndex.nextOffset;
|
|
2617
|
+
}
|
|
2618
|
+
if (cursor !== payloadEnd) {
|
|
2619
|
+
throw new GrammarLoadError("grammar_integrity", "Tree-sitter core WASM export section has trailing bytes");
|
|
2620
|
+
}
|
|
2621
|
+
if (targetAlreadyPresent) return coreBytes;
|
|
2622
|
+
if (sourceFunctionIndex === void 0) {
|
|
2623
|
+
throw new GrammarLoadError("grammar_integrity", "Tree-sitter core does not export iswalpha for the Bash scanner compatibility alias");
|
|
2624
|
+
}
|
|
2625
|
+
const aliasName = Buffer.from("isalpha", "utf8");
|
|
2626
|
+
const alias = Buffer.concat([
|
|
2627
|
+
Buffer.from(encodeUnsignedLeb(aliasName.length)),
|
|
2628
|
+
aliasName,
|
|
2629
|
+
Buffer.from([0]),
|
|
2630
|
+
Buffer.from(encodeUnsignedLeb(sourceFunctionIndex))
|
|
2631
|
+
]);
|
|
2632
|
+
const newPayload = Buffer.concat([
|
|
2633
|
+
Buffer.from(encodeUnsignedLeb(exportCount.value + 1)),
|
|
2634
|
+
Buffer.from(coreBytes.subarray(exportCount.nextOffset, payloadEnd)),
|
|
2635
|
+
alias
|
|
2636
|
+
]);
|
|
2637
|
+
return Buffer.concat([
|
|
2638
|
+
Buffer.from(coreBytes.subarray(0, sectionOffset)),
|
|
2639
|
+
Buffer.from([sectionId]),
|
|
2640
|
+
Buffer.from(encodeUnsignedLeb(newPayload.length)),
|
|
2641
|
+
newPayload,
|
|
2642
|
+
Buffer.from(coreBytes.subarray(payloadEnd))
|
|
2643
|
+
]);
|
|
2644
|
+
}
|
|
2645
|
+
throw new GrammarLoadError("grammar_integrity", "Tree-sitter core WASM does not contain an export section");
|
|
2646
|
+
}
|
|
2647
|
+
var GrammarLoadError = class extends Error {
|
|
2648
|
+
constructor(code, message2) {
|
|
2649
|
+
super(message2);
|
|
2650
|
+
this.code = code;
|
|
2651
|
+
this.name = "GrammarLoadError";
|
|
2652
|
+
}
|
|
2653
|
+
code;
|
|
2654
|
+
};
|
|
2655
|
+
function configureBoundedWasmCompilation() {
|
|
2656
|
+
if (boundedWasmCompilationConfigured) return;
|
|
2657
|
+
boundedWasmCompilationConfigured = true;
|
|
2658
|
+
setFlagsFromString("--no-wasm-tier-up --no-wasm-dynamic-tiering");
|
|
2659
|
+
}
|
|
2660
|
+
function safeAssetPath(root, relative) {
|
|
2661
|
+
const candidate = path6.resolve(root, relative);
|
|
2662
|
+
const resolvedRoot = path6.resolve(root);
|
|
2663
|
+
if (candidate !== resolvedRoot && !candidate.startsWith(`${resolvedRoot}${path6.sep}`)) {
|
|
2664
|
+
throw new GrammarLoadError("grammar_integrity", `grammar asset escapes its root: ${relative}`);
|
|
2665
|
+
}
|
|
2666
|
+
return candidate;
|
|
2667
|
+
}
|
|
2668
|
+
function loadGrammarManifest(assetRoot = defaultAssetRoot) {
|
|
2669
|
+
const resolvedRoot = path6.resolve(assetRoot);
|
|
2670
|
+
const cached = manifestCache.get(resolvedRoot);
|
|
2671
|
+
if (cached) return cached;
|
|
2672
|
+
const manifestPath = safeAssetPath(resolvedRoot, "grammar-manifest.json");
|
|
2673
|
+
let parsed;
|
|
2674
|
+
try {
|
|
2675
|
+
parsed = JSON.parse(fs3.readFileSync(manifestPath, "utf8"));
|
|
2676
|
+
} catch (error) {
|
|
2677
|
+
throw new GrammarLoadError("grammar_unavailable", `cannot read grammar manifest at ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2678
|
+
}
|
|
2679
|
+
const manifest = grammarManifestSchema.parse(parsed);
|
|
2680
|
+
if (manifest.grammars.some((grammar) => grammar.abi < manifest.runtime.abi_min || grammar.abi > manifest.runtime.abi_max)) {
|
|
2681
|
+
throw new GrammarLoadError(
|
|
2682
|
+
"grammar_integrity",
|
|
2683
|
+
`grammar manifest contains an entry outside its declared ABI range ${manifest.runtime.abi_min}-${manifest.runtime.abi_max}`
|
|
2684
|
+
);
|
|
2685
|
+
}
|
|
2686
|
+
manifestCache.set(resolvedRoot, manifest);
|
|
2687
|
+
return manifest;
|
|
2688
|
+
}
|
|
2689
|
+
function grammarFor(language, filePath, assetRoot = defaultAssetRoot) {
|
|
2690
|
+
const extension = path6.posix.extname(filePath.replaceAll("\\", "/")).toLowerCase();
|
|
2691
|
+
const matches = loadGrammarManifest(assetRoot).grammars.filter((entry) => entry.languages.includes(language));
|
|
2692
|
+
return matches.find((entry) => entry.extensions?.includes(extension)) ?? matches.find((entry) => !entry.extensions);
|
|
2693
|
+
}
|
|
2694
|
+
function resolveGrammarAsset(entry, assetRoot) {
|
|
2695
|
+
const bundled = safeAssetPath(assetRoot, entry.file);
|
|
2696
|
+
if (fs3.existsSync(bundled)) return bundled;
|
|
2697
|
+
try {
|
|
2698
|
+
return require2.resolve(`${loadGrammarManifest(assetRoot).source_package.name}/${entry.package_subpath}`);
|
|
2699
|
+
} catch {
|
|
2700
|
+
throw new GrammarLoadError("grammar_unavailable", `missing grammar ${entry.id}; expected ${bundled} or dependency subpath ${entry.package_subpath}`);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
async function initializeRuntime(coreWasmPath, assetRoot) {
|
|
2704
|
+
if (runtimeInitialization) {
|
|
2705
|
+
if (coreWasmPath && initializedCorePath && path6.resolve(coreWasmPath) !== initializedCorePath) {
|
|
2706
|
+
throw new GrammarLoadError("grammar_integrity", "Tree-sitter runtime was already initialized with a different core WASM");
|
|
2707
|
+
}
|
|
2708
|
+
return runtimeInitialization;
|
|
2709
|
+
}
|
|
2710
|
+
const resolvedCorePath = coreWasmPath ? path6.resolve(coreWasmPath) : require2.resolve("web-tree-sitter/tree-sitter.wasm");
|
|
2711
|
+
initializedCorePath = resolvedCorePath;
|
|
2712
|
+
const compatibleCore = addShellScannerCoreAlias(fs3.readFileSync(resolvedCorePath));
|
|
2713
|
+
runtimeInitialization = Parser.init({ wasmBinary: compatibleCore }).then(() => {
|
|
2714
|
+
const manifest = loadGrammarManifest(assetRoot);
|
|
2715
|
+
if (manifest.runtime.abi_min !== MIN_COMPATIBLE_VERSION || manifest.runtime.abi_max !== LANGUAGE_VERSION) {
|
|
2716
|
+
throw new GrammarLoadError(
|
|
2717
|
+
"grammar_integrity",
|
|
2718
|
+
`grammar manifest ABI range ${manifest.runtime.abi_min}-${manifest.runtime.abi_max} does not match runtime ${MIN_COMPATIBLE_VERSION}-${LANGUAGE_VERSION}`
|
|
2719
|
+
);
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
return runtimeInitialization;
|
|
2723
|
+
}
|
|
2724
|
+
async function loadLanguageSerialized(bytes) {
|
|
2725
|
+
const previous = grammarLoadTail.catch(() => void 0);
|
|
2726
|
+
let release;
|
|
2727
|
+
grammarLoadTail = new Promise((resolve) => {
|
|
2728
|
+
release = resolve;
|
|
2729
|
+
});
|
|
2730
|
+
await previous;
|
|
2731
|
+
try {
|
|
2732
|
+
return await Language.load(bytes);
|
|
2733
|
+
} finally {
|
|
2734
|
+
release();
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
async function loadGrammar(language, filePath, options = {}) {
|
|
2738
|
+
const assetRoot = path6.resolve(options.assetRoot ?? defaultAssetRoot);
|
|
2739
|
+
const descriptor = grammarFor(language, filePath, assetRoot);
|
|
2740
|
+
if (!descriptor) return void 0;
|
|
2741
|
+
const assetPath = resolveGrammarAsset(descriptor, assetRoot);
|
|
2742
|
+
const packagedCore = safeAssetPath(assetRoot, "tree-sitter.wasm");
|
|
2743
|
+
const coreWasmPath = options.coreWasmPath ?? (fs3.existsSync(packagedCore) ? packagedCore : void 0);
|
|
2744
|
+
const key = `${assetPath}:${descriptor.sha256}`;
|
|
2745
|
+
let pending = languageCache.get(key);
|
|
2746
|
+
if (!pending) {
|
|
2747
|
+
pending = (async () => {
|
|
2748
|
+
configureBoundedWasmCompilation();
|
|
2749
|
+
await initializeRuntime(coreWasmPath, assetRoot);
|
|
2750
|
+
const bytes = fs3.readFileSync(assetPath);
|
|
2751
|
+
const actualHash = sha256(bytes);
|
|
2752
|
+
if (actualHash !== descriptor.sha256) {
|
|
2753
|
+
throw new GrammarLoadError("grammar_integrity", `grammar ${descriptor.id} hash mismatch: expected ${descriptor.sha256}, received ${actualHash}`);
|
|
2754
|
+
}
|
|
2755
|
+
const loaded = await loadLanguageSerialized(bytes);
|
|
2756
|
+
if (loaded.abiVersion !== descriptor.abi || loaded.abiVersion < MIN_COMPATIBLE_VERSION || loaded.abiVersion > LANGUAGE_VERSION) {
|
|
2757
|
+
throw new GrammarLoadError(
|
|
2758
|
+
"grammar_integrity",
|
|
2759
|
+
`grammar ${descriptor.id} ABI ${loaded.abiVersion} is incompatible with runtime ${MIN_COMPATIBLE_VERSION}-${LANGUAGE_VERSION}`
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
return {
|
|
2763
|
+
descriptor,
|
|
2764
|
+
language: loaded,
|
|
2765
|
+
provenance: {
|
|
2766
|
+
id: descriptor.id,
|
|
2767
|
+
abi: loaded.abiVersion,
|
|
2768
|
+
sha256: actualHash,
|
|
2769
|
+
runtime: "web-tree-sitter@0.25.10",
|
|
2770
|
+
source: `${loadGrammarManifest(assetRoot).source_package.repository}@${loadGrammarManifest(assetRoot).source_package.version}`,
|
|
2771
|
+
license: loadGrammarManifest(assetRoot).source_package.license
|
|
2772
|
+
},
|
|
2773
|
+
assetPath
|
|
2774
|
+
};
|
|
2775
|
+
})();
|
|
2776
|
+
languageCache.set(key, pending);
|
|
2777
|
+
}
|
|
2778
|
+
try {
|
|
2779
|
+
return await pending;
|
|
2780
|
+
} catch (error) {
|
|
2781
|
+
languageCache.delete(key);
|
|
2782
|
+
throw error;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
// ../packages/brain-ast/src/query-assets.ts
|
|
2787
|
+
var AST_QUERY_VERSION = "1";
|
|
2788
|
+
var queryEntrySchema = z3.object({
|
|
2789
|
+
id: z3.string().min(1),
|
|
2790
|
+
languages: z3.array(z3.string().min(1)).min(1),
|
|
2791
|
+
extensions: z3.array(z3.string().startsWith(".")).optional(),
|
|
2792
|
+
file: z3.string().min(1),
|
|
2793
|
+
sample_path: z3.string().min(1)
|
|
2794
|
+
}).strict();
|
|
2795
|
+
var queryManifestSchema = z3.object({
|
|
2796
|
+
schema_version: z3.literal(1),
|
|
2797
|
+
query_version: z3.literal(AST_QUERY_VERSION),
|
|
2798
|
+
queries: z3.array(queryEntrySchema).min(1)
|
|
2799
|
+
}).strict();
|
|
2800
|
+
var defaultAssetRoot2 = fileURLToPath2(new URL("../assets", import.meta.url));
|
|
2801
|
+
var manifestCache2 = /* @__PURE__ */ new Map();
|
|
2802
|
+
var sourceCache = /* @__PURE__ */ new Map();
|
|
2803
|
+
var compiledCompatibility = /* @__PURE__ */ new WeakMap();
|
|
2804
|
+
function safeAssetPath2(root, relative) {
|
|
2805
|
+
const resolvedRoot = path7.resolve(root);
|
|
2806
|
+
const candidate = path7.resolve(resolvedRoot, relative);
|
|
2807
|
+
if (candidate !== resolvedRoot && !candidate.startsWith(`${resolvedRoot}${path7.sep}`)) {
|
|
2808
|
+
throw new Error(`AST query asset escapes its root: ${relative}`);
|
|
2809
|
+
}
|
|
2810
|
+
return candidate;
|
|
2811
|
+
}
|
|
2812
|
+
function loadAstQueryManifest(assetRoot = defaultAssetRoot2) {
|
|
2813
|
+
const root = path7.resolve(assetRoot);
|
|
2814
|
+
const cached = manifestCache2.get(root);
|
|
2815
|
+
if (cached) return cached;
|
|
2816
|
+
const manifestPath = safeAssetPath2(root, "queries/manifest.json");
|
|
2817
|
+
const manifest = queryManifestSchema.parse(JSON.parse(fs4.readFileSync(manifestPath, "utf8")));
|
|
2818
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2819
|
+
for (const query of manifest.queries) {
|
|
2820
|
+
if (ids.has(query.id)) throw new Error(`duplicate AST query id: ${query.id}`);
|
|
2821
|
+
ids.add(query.id);
|
|
2822
|
+
}
|
|
2823
|
+
manifestCache2.set(root, manifest);
|
|
2824
|
+
return manifest;
|
|
2825
|
+
}
|
|
2826
|
+
function queryDescriptor(language, filePath, assetRoot) {
|
|
2827
|
+
const extension = path7.posix.extname(filePath.replaceAll("\\", "/")).toLowerCase();
|
|
2828
|
+
const candidates = loadAstQueryManifest(assetRoot).queries.filter((query) => query.languages.includes(language));
|
|
2829
|
+
return candidates.find((query) => query.extensions?.includes(extension)) ?? candidates.find((query) => !query.extensions);
|
|
2830
|
+
}
|
|
2831
|
+
function loadAstQueryAsset(language, filePath, assetRoot = defaultAssetRoot2) {
|
|
2832
|
+
const root = path7.resolve(assetRoot);
|
|
2833
|
+
const descriptor = queryDescriptor(language, filePath, root);
|
|
2834
|
+
if (!descriptor) return void 0;
|
|
2835
|
+
const assetPath = safeAssetPath2(path7.join(root, "queries"), descriptor.file);
|
|
2836
|
+
const cacheKey = `${descriptor.id}\0${assetPath}\0${AST_QUERY_VERSION}`;
|
|
2837
|
+
const cached = sourceCache.get(cacheKey);
|
|
2838
|
+
if (cached) return cached;
|
|
2839
|
+
const source = fs4.readFileSync(assetPath, "utf8");
|
|
2840
|
+
if (!source.trim()) throw new Error(`AST query asset is empty: ${descriptor.file}`);
|
|
2841
|
+
const loaded = { descriptor, version: AST_QUERY_VERSION, source, sha256: sha256(source), path: assetPath };
|
|
2842
|
+
sourceCache.set(cacheKey, loaded);
|
|
2843
|
+
return loaded;
|
|
2844
|
+
}
|
|
2845
|
+
function assertAstQueryCompatible(language, filePath, grammar, assetRoot = defaultAssetRoot2) {
|
|
2846
|
+
const asset = loadAstQueryAsset(language, filePath, assetRoot);
|
|
2847
|
+
if (!asset) return void 0;
|
|
2848
|
+
const cache = compiledCompatibility.get(grammar) ?? /* @__PURE__ */ new Set();
|
|
2849
|
+
const identity = `${asset.descriptor.id}\0${asset.sha256}`;
|
|
2850
|
+
if (!cache.has(identity)) {
|
|
2851
|
+
const query = new Query(grammar, asset.source);
|
|
2852
|
+
try {
|
|
2853
|
+
if (!query.captureNames.includes("symbol") && !query.captureNames.includes("import")) {
|
|
2854
|
+
throw new Error(`AST query ${asset.descriptor.id} has no symbol or import captures`);
|
|
2855
|
+
}
|
|
2856
|
+
} finally {
|
|
2857
|
+
query.delete();
|
|
2858
|
+
}
|
|
2859
|
+
cache.add(identity);
|
|
2860
|
+
compiledCompatibility.set(grammar, cache);
|
|
2861
|
+
}
|
|
2862
|
+
return asset;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
// ../packages/brain-ast/src/types.ts
|
|
2866
|
+
var DEFAULT_AST_FILE_TIMEOUT_MS = 2e3;
|
|
2867
|
+
var AST_TYPE_DECLARATION_KINDS = [
|
|
2868
|
+
"class",
|
|
2869
|
+
"enum",
|
|
2870
|
+
"interface",
|
|
2871
|
+
"message",
|
|
2872
|
+
"service",
|
|
2873
|
+
"struct",
|
|
2874
|
+
"trait",
|
|
2875
|
+
"type"
|
|
2876
|
+
];
|
|
2877
|
+
var AST_TYPE_DECLARATION_KIND_SET = new Set(AST_TYPE_DECLARATION_KINDS);
|
|
2878
|
+
function isAstTypeDeclarationKind(kind) {
|
|
2879
|
+
return AST_TYPE_DECLARATION_KIND_SET.has(kind);
|
|
2880
|
+
}
|
|
2881
|
+
function parserRuntimeForGrammar(grammar) {
|
|
2882
|
+
if (grammar.runtime === "web-tree-sitter@0.25.10") return "web-tree-sitter";
|
|
2883
|
+
if (grammar.runtime === "structural-fallback@1") return "structural-fallback";
|
|
2884
|
+
return "unavailable";
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
// ../packages/brain-ast/src/fallback-parsers.ts
|
|
2888
|
+
import { performance as performance2 } from "perf_hooks";
|
|
2889
|
+
function locate(source) {
|
|
2890
|
+
const starts = [0];
|
|
2891
|
+
for (let index = 0; index < source.length; index += 1) if (source.charCodeAt(index) === 10) starts.push(index + 1);
|
|
2892
|
+
function point(offset) {
|
|
2893
|
+
let low = 0;
|
|
2894
|
+
let high = starts.length - 1;
|
|
2895
|
+
while (low <= high) {
|
|
2896
|
+
const middle = Math.floor((low + high) / 2);
|
|
2897
|
+
const value = starts[middle] ?? 0;
|
|
2898
|
+
if (value <= offset) low = middle + 1;
|
|
2899
|
+
else high = middle - 1;
|
|
2900
|
+
}
|
|
2901
|
+
const lineIndex = Math.max(0, high);
|
|
2902
|
+
const start = starts[lineIndex] ?? 0;
|
|
2903
|
+
return {
|
|
2904
|
+
line: lineIndex + 1,
|
|
2905
|
+
column: Buffer.byteLength(source.slice(start, offset), "utf8"),
|
|
2906
|
+
byte: Buffer.byteLength(source.slice(0, start), "utf8") + Buffer.byteLength(source.slice(start, offset), "utf8")
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
return {
|
|
2910
|
+
span(start, end) {
|
|
2911
|
+
const first = point(Math.max(0, start));
|
|
2912
|
+
const last = point(Math.max(start, end));
|
|
2913
|
+
return {
|
|
2914
|
+
startByte: first.byte,
|
|
2915
|
+
endByte: last.byte,
|
|
2916
|
+
startLine: first.line,
|
|
2917
|
+
startColumn: first.column,
|
|
2918
|
+
endLine: last.line,
|
|
2919
|
+
endColumn: last.column
|
|
2920
|
+
};
|
|
2921
|
+
},
|
|
2922
|
+
lineStart(lineIndex) {
|
|
2923
|
+
return starts[lineIndex] ?? source.length;
|
|
2924
|
+
}
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
function fallbackGrammar(id) {
|
|
2928
|
+
return {
|
|
2929
|
+
id: `${id}-structural`,
|
|
2930
|
+
abi: 1,
|
|
2931
|
+
sha256: sha256(`scriptonia:${id}:structural-fallback:1`),
|
|
2932
|
+
runtime: "structural-fallback@1",
|
|
2933
|
+
source: "@scriptonia/brain-ast",
|
|
2934
|
+
license: "project"
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
function baseResult(input, language) {
|
|
2938
|
+
const sourceSha256 = input.sourceSha256 ?? sha256(input.source);
|
|
2939
|
+
return {
|
|
2940
|
+
schemaVersion: 1,
|
|
2941
|
+
path: normalizeRepoPath(input.path),
|
|
2942
|
+
language,
|
|
2943
|
+
sourceSha256,
|
|
2944
|
+
parserRuntime: "structural-fallback",
|
|
2945
|
+
grammar: fallbackGrammar(language),
|
|
2946
|
+
sourceBytes: Buffer.byteLength(input.source, "utf8")
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
function factSymbol(base, kind, name, qualifiedName, signature, nodeType, span) {
|
|
2950
|
+
return {
|
|
2951
|
+
factId: `symbol_${sha256(`${base.sourceSha256}\0${base.path}\0${kind}\0${qualifiedName}\0${span.startByte}`).slice(0, 24)}`,
|
|
2952
|
+
path: base.path,
|
|
2953
|
+
language: base.language,
|
|
2954
|
+
kind,
|
|
2955
|
+
name,
|
|
2956
|
+
qualifiedName,
|
|
2957
|
+
signature: signature.replace(/\s+/g, " ").trim().slice(0, 1e3),
|
|
2958
|
+
nodeType,
|
|
2959
|
+
span,
|
|
2960
|
+
confidence: "medium",
|
|
2961
|
+
provenance: "structural_fallback"
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
function factImport(base, kind, specifier, importedNames, span) {
|
|
2965
|
+
return {
|
|
2966
|
+
factId: `import_${sha256(`${base.sourceSha256}\0${base.path}\0${kind}\0${specifier}\0${span.startByte}`).slice(0, 24)}`,
|
|
2967
|
+
path: base.path,
|
|
2968
|
+
language: base.language,
|
|
2969
|
+
kind,
|
|
2970
|
+
specifier,
|
|
2971
|
+
importedNames,
|
|
2972
|
+
span,
|
|
2973
|
+
confidence: "medium",
|
|
2974
|
+
provenance: "structural_fallback"
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
function stripComments(source) {
|
|
2978
|
+
let output = "";
|
|
2979
|
+
let state = "code";
|
|
2980
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
2981
|
+
const char = source[index] ?? "";
|
|
2982
|
+
const next = source[index + 1] ?? "";
|
|
2983
|
+
if (state === "line") {
|
|
2984
|
+
if (char === "\n") {
|
|
2985
|
+
state = "code";
|
|
2986
|
+
output += "\n";
|
|
2987
|
+
} else output += " ";
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
if (state === "block") {
|
|
2991
|
+
if (char === "*" && next === "/") {
|
|
2992
|
+
output += " ";
|
|
2993
|
+
index += 1;
|
|
2994
|
+
state = "code";
|
|
2995
|
+
} else output += char === "\n" ? "\n" : " ";
|
|
2996
|
+
continue;
|
|
2997
|
+
}
|
|
2998
|
+
if (state === "single" || state === "double") {
|
|
2999
|
+
output += char;
|
|
3000
|
+
if (char === "\\" && next) {
|
|
3001
|
+
output += next;
|
|
3002
|
+
index += 1;
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
if (state === "single" && char === "'" || state === "double" && char === '"') state = "code";
|
|
3006
|
+
continue;
|
|
3007
|
+
}
|
|
3008
|
+
if (char === "/" && next === "/") {
|
|
3009
|
+
output += " ";
|
|
3010
|
+
index += 1;
|
|
3011
|
+
state = "line";
|
|
3012
|
+
continue;
|
|
3013
|
+
}
|
|
3014
|
+
if (char === "/" && next === "*") {
|
|
3015
|
+
output += " ";
|
|
3016
|
+
index += 1;
|
|
3017
|
+
state = "block";
|
|
3018
|
+
continue;
|
|
3019
|
+
}
|
|
3020
|
+
if (char === "#") {
|
|
3021
|
+
output += " ";
|
|
3022
|
+
state = "line";
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
if (char === "'") state = "single";
|
|
3026
|
+
else if (char === '"') state = "double";
|
|
3027
|
+
output += char;
|
|
3028
|
+
}
|
|
3029
|
+
return output;
|
|
3030
|
+
}
|
|
3031
|
+
function braceDelta(line) {
|
|
3032
|
+
let delta = 0;
|
|
3033
|
+
let quote;
|
|
3034
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
3035
|
+
const char = line[index];
|
|
3036
|
+
if (quote) {
|
|
3037
|
+
if (char === "\\") index += 1;
|
|
3038
|
+
else if (char === quote) quote = void 0;
|
|
3039
|
+
} else if (char === "'" || char === '"') quote = char;
|
|
3040
|
+
else if (char === "{") delta += 1;
|
|
3041
|
+
else if (char === "}") delta -= 1;
|
|
3042
|
+
}
|
|
3043
|
+
return delta;
|
|
3044
|
+
}
|
|
3045
|
+
function parseProtoFallback(input) {
|
|
3046
|
+
const startedAt = performance2.now();
|
|
3047
|
+
const base = baseResult(input, "proto");
|
|
3048
|
+
const locator = locate(input.source);
|
|
3049
|
+
const cleaned = stripComments(input.source);
|
|
3050
|
+
const lines = cleaned.split(/\n/);
|
|
3051
|
+
const originalLines = input.source.split(/\n/);
|
|
3052
|
+
const symbols = [];
|
|
3053
|
+
const imports = [];
|
|
3054
|
+
const diagnostics = [{ code: "grammar_unavailable", severity: "info", message: "Proto uses the deterministic structural parser; semantic type resolution is not claimed" }];
|
|
3055
|
+
const scope = [];
|
|
3056
|
+
let depth = 0;
|
|
3057
|
+
let packageName = "";
|
|
3058
|
+
let negativeDepth = false;
|
|
3059
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
3060
|
+
const line = lines[lineIndex] ?? "";
|
|
3061
|
+
const original = originalLines[lineIndex] ?? "";
|
|
3062
|
+
const start = locator.lineStart(lineIndex);
|
|
3063
|
+
const end = start + original.length;
|
|
3064
|
+
const packageMatch = /^\s*package\s+([A-Za-z_][\w.]*)\s*;/.exec(line);
|
|
3065
|
+
if (packageMatch?.[1]) {
|
|
3066
|
+
packageName = packageMatch[1];
|
|
3067
|
+
imports.push(factImport(base, "package", packageName, [], locator.span(start, end)));
|
|
3068
|
+
}
|
|
3069
|
+
const importMatch = /^\s*import\s+(?:public\s+|weak\s+)?["']([^"']+)["']\s*;/.exec(line);
|
|
3070
|
+
if (importMatch?.[1]) imports.push(factImport(base, "import", importMatch[1], [], locator.span(start, end)));
|
|
3071
|
+
const declaration = /^\s*(message|enum|service|oneof)\s+([A-Za-z_][\w]*)\b/.exec(line);
|
|
3072
|
+
if (declaration?.[1] && declaration[2]) {
|
|
3073
|
+
const kind = declaration[1] === "message" || declaration[1] === "oneof" ? "message" : declaration[1];
|
|
3074
|
+
const names = [packageName, ...scope.map((entry) => entry.name), declaration[2]].filter(Boolean);
|
|
3075
|
+
const symbol = factSymbol(base, kind, declaration[2], names.join("."), original, `proto_${declaration[1]}`, locator.span(start, end));
|
|
3076
|
+
symbols.push(symbol);
|
|
3077
|
+
if (line.includes("{")) scope.push({ name: declaration[2], depth, symbolIndex: symbols.length - 1 });
|
|
3078
|
+
}
|
|
3079
|
+
const rpc = /^\s*rpc\s+([A-Za-z_][\w]*)\s*\(/.exec(line);
|
|
3080
|
+
if (rpc?.[1]) {
|
|
3081
|
+
const names = [packageName, ...scope.map((entry) => entry.name), rpc[1]].filter(Boolean);
|
|
3082
|
+
symbols.push(factSymbol(base, "rpc", rpc[1], names.join("."), original, "proto_rpc", locator.span(start, end)));
|
|
3083
|
+
}
|
|
3084
|
+
depth += braceDelta(line);
|
|
3085
|
+
if (depth < 0) {
|
|
3086
|
+
negativeDepth = true;
|
|
3087
|
+
depth = 0;
|
|
3088
|
+
}
|
|
3089
|
+
while (scope.length > 0 && depth <= (scope.at(-1)?.depth ?? -1)) {
|
|
3090
|
+
const closed = scope.pop();
|
|
3091
|
+
if (!closed) continue;
|
|
3092
|
+
const existing = symbols[closed.symbolIndex];
|
|
3093
|
+
if (existing) existing.span = { ...existing.span, ...{ endByte: locator.span(start, end).endByte, endLine: lineIndex + 1, endColumn: Buffer.byteLength(original, "utf8") } };
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
const broken = negativeDepth || depth !== 0;
|
|
3097
|
+
if (broken) diagnostics.push({ code: "parse_error", severity: "warning", message: "unbalanced Proto braces; extracted facts are partial" });
|
|
3098
|
+
const parseMs = performance2.now() - startedAt;
|
|
3099
|
+
const errorBytes = broken ? Math.min(base.sourceBytes, Math.max(1, Math.abs(depth))) : 0;
|
|
3100
|
+
return {
|
|
3101
|
+
schemaVersion: base.schemaVersion,
|
|
3102
|
+
path: base.path,
|
|
3103
|
+
language: base.language,
|
|
3104
|
+
sourceSha256: base.sourceSha256,
|
|
3105
|
+
parserRuntime: base.parserRuntime,
|
|
3106
|
+
grammar: base.grammar,
|
|
3107
|
+
status: broken ? "partial" : "parsed",
|
|
3108
|
+
rootType: "proto_file",
|
|
3109
|
+
symbols,
|
|
3110
|
+
imports,
|
|
3111
|
+
calls: [],
|
|
3112
|
+
diagnostics,
|
|
3113
|
+
coverage: { sourceBytes: base.sourceBytes, parsedBytes: base.sourceBytes - errorBytes, errorBytes, errorByteRatio: base.sourceBytes === 0 ? 0 : errorBytes / base.sourceBytes, errorNodes: broken ? 1 : 0, missingNodes: 0, parseMs }
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
function balancedCallArgumentCount(text) {
|
|
3117
|
+
const open = text.indexOf("(");
|
|
3118
|
+
const close = text.lastIndexOf(")");
|
|
3119
|
+
if (open < 0 || close < open) return void 0;
|
|
3120
|
+
const body = text.slice(open + 1, close).trim();
|
|
3121
|
+
if (!body) return 0;
|
|
3122
|
+
let count = 1;
|
|
3123
|
+
let depth = 0;
|
|
3124
|
+
let quote;
|
|
3125
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
3126
|
+
const character = body[index];
|
|
3127
|
+
if (quote) {
|
|
3128
|
+
if (character === "\\") index += 1;
|
|
3129
|
+
else if (character === quote) quote = void 0;
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3132
|
+
if (character === "'" || character === '"') quote = character;
|
|
3133
|
+
else if ("([{<".includes(character ?? "")) depth += 1;
|
|
3134
|
+
else if (")]}>".includes(character ?? "")) depth = Math.max(0, depth - 1);
|
|
3135
|
+
else if (character === "," && depth === 0) count += 1;
|
|
3136
|
+
}
|
|
3137
|
+
return count;
|
|
3138
|
+
}
|
|
3139
|
+
function balancedCalls(source) {
|
|
3140
|
+
const cleaned = stripComments(source);
|
|
3141
|
+
const calls = [];
|
|
3142
|
+
const pattern = /\b([A-Za-z_][\w.]*)\s*\(/g;
|
|
3143
|
+
for (let match = pattern.exec(cleaned); match; match = pattern.exec(cleaned)) {
|
|
3144
|
+
const name = match[1];
|
|
3145
|
+
if (!name) continue;
|
|
3146
|
+
const open = cleaned.indexOf("(", match.index + name.length);
|
|
3147
|
+
let depth = 0;
|
|
3148
|
+
let quote;
|
|
3149
|
+
let end = open + 1;
|
|
3150
|
+
let closed = false;
|
|
3151
|
+
for (let index = open; index < cleaned.length; index += 1) {
|
|
3152
|
+
const char = cleaned[index];
|
|
3153
|
+
if (quote) {
|
|
3154
|
+
if (char === "\\") index += 1;
|
|
3155
|
+
else if (char === quote) quote = void 0;
|
|
3156
|
+
} else if (char === "'" || char === '"') quote = char;
|
|
3157
|
+
else if (char === "(") depth += 1;
|
|
3158
|
+
else if (char === ")") {
|
|
3159
|
+
depth -= 1;
|
|
3160
|
+
if (depth === 0) {
|
|
3161
|
+
end = index + 1;
|
|
3162
|
+
closed = true;
|
|
3163
|
+
break;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
calls.push({ name, start: match.index, end, text: source.slice(match.index, end), closed });
|
|
3168
|
+
if (closed) pattern.lastIndex = end;
|
|
3169
|
+
}
|
|
3170
|
+
return calls;
|
|
3171
|
+
}
|
|
3172
|
+
function parseStarlarkFallback(input) {
|
|
3173
|
+
const startedAt = performance2.now();
|
|
3174
|
+
const base = baseResult(input, "starlark");
|
|
3175
|
+
const locator = locate(input.source);
|
|
3176
|
+
const symbols = [];
|
|
3177
|
+
const imports = [];
|
|
3178
|
+
const calls = [];
|
|
3179
|
+
const diagnostics = [{ code: "grammar_unavailable", severity: "info", message: "Bazel/Starlark uses the deterministic structural parser; macro expansion is recorded as unresolved" }];
|
|
3180
|
+
const cleaned = stripComments(input.source);
|
|
3181
|
+
const definitionPattern = /^[\t ]*def[\t ]+([A-Za-z_][\w]*)[\t ]*\([^\n]*/gm;
|
|
3182
|
+
for (let match = definitionPattern.exec(cleaned); match; match = definitionPattern.exec(cleaned)) {
|
|
3183
|
+
const name = match[1];
|
|
3184
|
+
if (!name) continue;
|
|
3185
|
+
const end = cleaned.indexOf("\n", match.index);
|
|
3186
|
+
const finalEnd = end === -1 ? cleaned.length : end;
|
|
3187
|
+
symbols.push(factSymbol(base, "function", name, name, input.source.slice(match.index, finalEnd), "starlark_function", locator.span(match.index, finalEnd)));
|
|
3188
|
+
}
|
|
3189
|
+
let unclosed = 0;
|
|
3190
|
+
for (const call of balancedCalls(input.source)) {
|
|
3191
|
+
if (!call.closed) unclosed += 1;
|
|
3192
|
+
const callSpan = locator.span(call.start, call.end);
|
|
3193
|
+
const argumentCount = call.closed ? balancedCallArgumentCount(call.text) : void 0;
|
|
3194
|
+
calls.push({
|
|
3195
|
+
factId: `call_${sha256(`${base.sourceSha256}\0${base.path}\0${call.name}\0${callSpan.startByte}`).slice(0, 24)}`,
|
|
3196
|
+
path: base.path,
|
|
3197
|
+
language: "starlark",
|
|
3198
|
+
callee: call.name,
|
|
3199
|
+
...argumentCount === void 0 ? {} : { argumentCount },
|
|
3200
|
+
span: callSpan,
|
|
3201
|
+
confidence: "low",
|
|
3202
|
+
resolution: "unresolved",
|
|
3203
|
+
provenance: "structural_fallback"
|
|
3204
|
+
});
|
|
3205
|
+
const strings = [...call.text.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]).filter((item) => Boolean(item));
|
|
3206
|
+
if (call.name === "load" && strings[0]) {
|
|
3207
|
+
imports.push(factImport(base, "load", strings[0], strings.slice(1), callSpan));
|
|
3208
|
+
continue;
|
|
3209
|
+
}
|
|
3210
|
+
const targetName = /\bname\s*=\s*["']([^"']+)["']/.exec(call.text)?.[1];
|
|
3211
|
+
if (targetName) {
|
|
3212
|
+
const packagePath = base.path.replace(/(?:^|\/)(?:BUILD(?:\.bazel)?|[^/]+\.bzl)$/, "").replace(/^\//, "");
|
|
3213
|
+
const label = `//${packagePath}${packagePath ? ":" : ""}${targetName}`;
|
|
3214
|
+
symbols.push(factSymbol(base, "target", targetName, label, `${call.name}(name = ${JSON.stringify(targetName)})`, `bazel_${call.name}`, callSpan));
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
if (unclosed > 0) diagnostics.push({ code: "parse_error", severity: "warning", message: `${unclosed} unclosed Starlark call expression(s)` });
|
|
3218
|
+
const parseMs = performance2.now() - startedAt;
|
|
3219
|
+
const errorBytes = unclosed > 0 ? Math.min(base.sourceBytes, unclosed) : 0;
|
|
3220
|
+
return {
|
|
3221
|
+
schemaVersion: base.schemaVersion,
|
|
3222
|
+
path: base.path,
|
|
3223
|
+
language: base.language,
|
|
3224
|
+
sourceSha256: base.sourceSha256,
|
|
3225
|
+
parserRuntime: base.parserRuntime,
|
|
3226
|
+
grammar: base.grammar,
|
|
3227
|
+
status: unclosed > 0 ? "partial" : "parsed",
|
|
3228
|
+
rootType: "starlark_file",
|
|
3229
|
+
symbols,
|
|
3230
|
+
imports,
|
|
3231
|
+
calls,
|
|
3232
|
+
diagnostics,
|
|
3233
|
+
coverage: { sourceBytes: base.sourceBytes, parsedBytes: base.sourceBytes - errorBytes, errorBytes, errorByteRatio: base.sourceBytes === 0 ? 0 : errorBytes / base.sourceBytes, errorNodes: unclosed, missingNodes: 0, parseMs }
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
function parseStructuralFallback(input, language) {
|
|
3237
|
+
if (language === "proto") return parseProtoFallback({ ...input, language });
|
|
3238
|
+
if (language === "starlark") return parseStarlarkFallback({ ...input, language });
|
|
3239
|
+
return void 0;
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
// ../packages/brain-ast/src/tree-sitter-parser.ts
|
|
3243
|
+
import { performance as performance3 } from "perf_hooks";
|
|
3244
|
+
import { Parser as Parser2 } from "web-tree-sitter";
|
|
3245
|
+
var SYMBOL_TYPES = /* @__PURE__ */ new Map([
|
|
3246
|
+
["class_specifier", "class"],
|
|
3247
|
+
["struct_specifier", "struct"],
|
|
3248
|
+
["enum_specifier", "enum"],
|
|
3249
|
+
["namespace_definition", "namespace"],
|
|
3250
|
+
["function_definition", "function"],
|
|
3251
|
+
["type_definition", "type"],
|
|
3252
|
+
["alias_declaration", "type"],
|
|
3253
|
+
["type_alias_declaration", "type"],
|
|
3254
|
+
["class_declaration", "class"],
|
|
3255
|
+
["interface_declaration", "interface"],
|
|
3256
|
+
["enum_declaration", "enum"],
|
|
3257
|
+
["annotation_type_declaration", "interface"],
|
|
3258
|
+
["record_declaration", "struct"],
|
|
3259
|
+
["method_declaration", "method"],
|
|
3260
|
+
["constructor_declaration", "method"],
|
|
3261
|
+
["function_declaration", "function"],
|
|
3262
|
+
["generator_function_declaration", "function"],
|
|
3263
|
+
["class_definition", "class"],
|
|
3264
|
+
["function_definition", "function"],
|
|
3265
|
+
["function_item", "function"],
|
|
3266
|
+
["struct_item", "struct"],
|
|
3267
|
+
["enum_item", "enum"],
|
|
3268
|
+
["trait_item", "trait"],
|
|
3269
|
+
["mod_item", "module"],
|
|
3270
|
+
["type_item", "type"],
|
|
3271
|
+
["const_item", "constant"],
|
|
3272
|
+
["static_item", "constant"],
|
|
3273
|
+
["method_definition", "method"],
|
|
3274
|
+
["abstract_method_signature", "method"],
|
|
3275
|
+
["function_signature", "function"],
|
|
3276
|
+
["method_signature", "method"],
|
|
3277
|
+
["getter_signature", "method"],
|
|
3278
|
+
["setter_signature", "method"],
|
|
3279
|
+
["object_declaration", "class"],
|
|
3280
|
+
["protocol_declaration", "interface"],
|
|
3281
|
+
["actor_declaration", "class"],
|
|
3282
|
+
["function_declaration", "function"],
|
|
3283
|
+
["function_definition", "function"],
|
|
3284
|
+
["method_definition", "method"]
|
|
3285
|
+
]);
|
|
3286
|
+
var IMPORT_TYPES = /* @__PURE__ */ new Set([
|
|
3287
|
+
"preproc_include",
|
|
3288
|
+
"import_declaration",
|
|
3289
|
+
"import_statement",
|
|
3290
|
+
"import_from_statement",
|
|
3291
|
+
"export_statement",
|
|
3292
|
+
"use_declaration",
|
|
3293
|
+
"package_clause",
|
|
3294
|
+
"package_declaration",
|
|
3295
|
+
"library_import",
|
|
3296
|
+
"part_directive",
|
|
3297
|
+
"import_or_export"
|
|
3298
|
+
]);
|
|
3299
|
+
var CALL_TYPES = /* @__PURE__ */ new Set([
|
|
3300
|
+
"call_expression",
|
|
3301
|
+
"function_call",
|
|
3302
|
+
"method_invocation",
|
|
3303
|
+
"object_creation_expression",
|
|
3304
|
+
"method_reference",
|
|
3305
|
+
"command",
|
|
3306
|
+
"command_substitution"
|
|
3307
|
+
]);
|
|
3308
|
+
var IDENTIFIER_TYPES = /* @__PURE__ */ new Set([
|
|
3309
|
+
"identifier",
|
|
3310
|
+
"field_identifier",
|
|
3311
|
+
"type_identifier",
|
|
3312
|
+
"property_identifier",
|
|
3313
|
+
"simple_identifier",
|
|
3314
|
+
"operator_name",
|
|
3315
|
+
"constant",
|
|
3316
|
+
"destructor_name"
|
|
3317
|
+
]);
|
|
3318
|
+
var SCOPE_KINDS = /* @__PURE__ */ new Set(["class", "interface", "module", "namespace", "service", "struct", "trait"]);
|
|
3319
|
+
var DECLARATION_WRAPPER_TYPES = /* @__PURE__ */ new Set(["ambient_declaration", "decorated_definition", "export_statement"]);
|
|
3320
|
+
var JAVASCRIPT_FAMILY = /* @__PURE__ */ new Set(["javascript", "typescript"]);
|
|
3321
|
+
var DART_WIDGET_BASES = /* @__PURE__ */ new Map([
|
|
3322
|
+
["ConsumerWidget", "consumer"],
|
|
3323
|
+
["StatefulWidget", "stateful"],
|
|
3324
|
+
["StatelessWidget", "stateless"]
|
|
3325
|
+
]);
|
|
3326
|
+
var MAX_SOURCE_BYTES = 32 * 1024 * 1024;
|
|
3327
|
+
var MAX_FACTS_PER_KIND = 5e4;
|
|
3328
|
+
function ancestor(node, predicate) {
|
|
3329
|
+
for (let candidate = node.parent; candidate; candidate = candidate.parent) {
|
|
3330
|
+
if (predicate(candidate)) return candidate;
|
|
3331
|
+
}
|
|
3332
|
+
return void 0;
|
|
3333
|
+
}
|
|
3334
|
+
function symbolKind(node, language) {
|
|
3335
|
+
if (language === "kotlin" && node.type === "class_declaration" && /^interface\b/.test(node.text.trimStart())) {
|
|
3336
|
+
return "interface";
|
|
3337
|
+
}
|
|
3338
|
+
if ((language === "c" || language === "cpp") && node.type === "function_definition" && ancestor(node, (candidate) => candidate.type === "function_definition")) return void 0;
|
|
3339
|
+
if ((language === "c" || language === "cpp") && node.type === "function_definition" && !node.childForFieldName("type")) {
|
|
3340
|
+
const declarator = node.childForFieldName("declarator");
|
|
3341
|
+
const name = declarator ? innermostDeclaratorIdentifier(declarator)?.text.trim() : void 0;
|
|
3342
|
+
const isClassMember = Boolean(ancestor(node, (candidate) => candidate.type === "class_specifier" || candidate.type === "struct_specifier"));
|
|
3343
|
+
if (name && /^[A-Z][A-Z0-9_]{2,}$/.test(name) && !isClassMember && !declarator?.text.includes("::")) return void 0;
|
|
3344
|
+
}
|
|
3345
|
+
const direct = SYMBOL_TYPES.get(node.type);
|
|
3346
|
+
if (direct) return direct;
|
|
3347
|
+
if ((language === "c" || language === "cpp") && (node.type === "declaration" || node.type === "field_declaration")) {
|
|
3348
|
+
const callable = callableDeclarator(node);
|
|
3349
|
+
if (!callable) return void 0;
|
|
3350
|
+
return node.type === "field_declaration" || Boolean(ancestor(node, (candidate) => candidate.type === "class_specifier" || candidate.type === "struct_specifier")) ? "method" : "function";
|
|
3351
|
+
}
|
|
3352
|
+
if (JAVASCRIPT_FAMILY.has(language) && node.type === "variable_declarator") {
|
|
3353
|
+
const lexical = ancestor(node, (candidate) => candidate.type === "lexical_declaration");
|
|
3354
|
+
if (lexical?.parent?.type === "export_statement" && /^const\b/.test(lexical.text.trimStart())) return "constant";
|
|
3355
|
+
}
|
|
3356
|
+
return void 0;
|
|
3357
|
+
}
|
|
3358
|
+
function callableDeclarator(node) {
|
|
3359
|
+
let cursor = node.childForFieldName("declarator") ?? void 0;
|
|
3360
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3361
|
+
while (cursor && !visited.has(cursor.id)) {
|
|
3362
|
+
visited.add(cursor.id);
|
|
3363
|
+
if (cursor.type === "function_declarator") return cursor;
|
|
3364
|
+
cursor = cursor.childForFieldName("declarator") ?? void 0;
|
|
3365
|
+
}
|
|
3366
|
+
return void 0;
|
|
3367
|
+
}
|
|
3368
|
+
function spanFor(node) {
|
|
3369
|
+
return {
|
|
3370
|
+
startByte: node.startIndex,
|
|
3371
|
+
endByte: node.endIndex,
|
|
3372
|
+
startLine: node.startPosition.row + 1,
|
|
3373
|
+
startColumn: node.startPosition.column,
|
|
3374
|
+
endLine: node.endPosition.row + 1,
|
|
3375
|
+
endColumn: node.endPosition.column
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
function firstNamedDescendant(node, predicate) {
|
|
3379
|
+
const stack = [...node.namedChildren].filter((child) => child !== null).reverse();
|
|
3380
|
+
while (stack.length > 0) {
|
|
3381
|
+
const candidate = stack.pop();
|
|
3382
|
+
if (!candidate) continue;
|
|
3383
|
+
if (predicate(candidate)) return candidate;
|
|
3384
|
+
for (const child of [...candidate.namedChildren].reverse()) if (child) stack.push(child);
|
|
3385
|
+
}
|
|
3386
|
+
return void 0;
|
|
3387
|
+
}
|
|
3388
|
+
function firstDescendantField(node, fieldName) {
|
|
3389
|
+
const stack = [...node.namedChildren].filter((child) => child !== null).reverse();
|
|
3390
|
+
while (stack.length > 0) {
|
|
3391
|
+
const candidate = stack.pop();
|
|
3392
|
+
if (!candidate) continue;
|
|
3393
|
+
const field = candidate.childForFieldName(fieldName);
|
|
3394
|
+
if (field?.text.trim()) return field;
|
|
3395
|
+
for (const child of [...candidate.namedChildren].reverse()) if (child) stack.push(child);
|
|
3396
|
+
}
|
|
3397
|
+
return void 0;
|
|
3398
|
+
}
|
|
3399
|
+
function symbolName(node) {
|
|
3400
|
+
const direct = node.childForFieldName("name");
|
|
3401
|
+
if (direct?.text.trim()) return (innermostDeclaratorIdentifier(direct)?.text ?? direct.text).trim();
|
|
3402
|
+
if (node.type === "namespace_definition") return void 0;
|
|
3403
|
+
const declarator = node.childForFieldName("declarator");
|
|
3404
|
+
if (declarator) {
|
|
3405
|
+
const identifier2 = innermostDeclaratorIdentifier(declarator);
|
|
3406
|
+
if (identifier2?.text.trim()) return identifier2.text.trim();
|
|
3407
|
+
}
|
|
3408
|
+
const descendantName = firstDescendantField(node, "name");
|
|
3409
|
+
if (descendantName?.text.trim()) return descendantName.text.trim();
|
|
3410
|
+
const identifier = firstNamedDescendant(node, (candidate) => IDENTIFIER_TYPES.has(candidate.type));
|
|
3411
|
+
if (identifier?.text.trim()) return identifier.text.trim();
|
|
3412
|
+
const head = node.text.slice(0, 500).replace(/\s+/g, " ");
|
|
3413
|
+
return /\b(?:actor|class|def|enum|fn|func|fun|function|interface|namespace|object|protocol|record|struct|trait|type)\s+([A-Za-z_$][\w$]*)/.exec(head)?.[1] ?? /\b([A-Za-z_$][\w$]*)\s*\(/.exec(head)?.[1];
|
|
3414
|
+
}
|
|
3415
|
+
function innermostDeclaratorIdentifier(node) {
|
|
3416
|
+
let cursor = node;
|
|
3417
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3418
|
+
while (cursor && !visited.has(cursor.id)) {
|
|
3419
|
+
visited.add(cursor.id);
|
|
3420
|
+
if (IDENTIFIER_TYPES.has(cursor.type)) return cursor;
|
|
3421
|
+
const explicitName = cursor.childForFieldName("name");
|
|
3422
|
+
if (explicitName) {
|
|
3423
|
+
const nested = innermostDeclaratorIdentifier(explicitName);
|
|
3424
|
+
if (nested) return nested;
|
|
3425
|
+
}
|
|
3426
|
+
cursor = cursor.childForFieldName("declarator") ?? void 0;
|
|
3427
|
+
}
|
|
3428
|
+
return void 0;
|
|
3429
|
+
}
|
|
3430
|
+
function signatureFor(node) {
|
|
3431
|
+
const body = node.childForFieldName("body");
|
|
3432
|
+
const text = body ? node.text.slice(0, Math.max(0, body.startIndex - node.startIndex)) : node.text;
|
|
3433
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
3434
|
+
const exported = ancestor(node, (candidate) => candidate.type === "export_statement");
|
|
3435
|
+
return `${exported && !/^export\b/.test(normalized) ? "export " : ""}${normalized}`.slice(0, 1e3);
|
|
3436
|
+
}
|
|
3437
|
+
function declarationAnchor(node) {
|
|
3438
|
+
let anchor = node;
|
|
3439
|
+
while (anchor.parent && DECLARATION_WRAPPER_TYPES.has(anchor.parent.type)) anchor = anchor.parent;
|
|
3440
|
+
return anchor;
|
|
3441
|
+
}
|
|
3442
|
+
function isDocumentationComment(node, language) {
|
|
3443
|
+
if (language === "dart") return node.type === "documentation_comment";
|
|
3444
|
+
if (language === "java") return node.type === "block_comment" && node.text.trimStart().startsWith("/**");
|
|
3445
|
+
if (!JAVASCRIPT_FAMILY.has(language) || node.type !== "comment") return false;
|
|
3446
|
+
const text = node.text.trimStart();
|
|
3447
|
+
return text.startsWith("/**") || text.startsWith("///");
|
|
3448
|
+
}
|
|
3449
|
+
function leadingDocComment(node, language) {
|
|
3450
|
+
let cursor = declarationAnchor(node);
|
|
3451
|
+
const comments = [];
|
|
3452
|
+
for (let sibling = cursor.previousNamedSibling; sibling; sibling = sibling.previousNamedSibling) {
|
|
3453
|
+
if (!isDocumentationComment(sibling, language)) break;
|
|
3454
|
+
if (cursor.startPosition.row - sibling.endPosition.row > 1) break;
|
|
3455
|
+
comments.unshift(sibling.text);
|
|
3456
|
+
cursor = sibling;
|
|
3457
|
+
}
|
|
3458
|
+
const text = comments.join("\n").trim();
|
|
3459
|
+
return text.length > 0 ? text : void 0;
|
|
3460
|
+
}
|
|
3461
|
+
function dartClassMetadata(node, language, kind) {
|
|
3462
|
+
if (language !== "dart" || kind !== "class" || node.type !== "class_definition") return {};
|
|
3463
|
+
const superclassNode = node.childForFieldName("superclass");
|
|
3464
|
+
const superclassParts = superclassNode?.namedChildren.filter((child) => child !== null && child.type === "type_identifier").map((child) => child.text.trim()).filter(Boolean) ?? [];
|
|
3465
|
+
const superclass = superclassParts.length > 0 ? superclassParts.join(".") : void 0;
|
|
3466
|
+
const unqualifiedSuperclass = superclassParts.at(-1);
|
|
3467
|
+
const widgetKind = unqualifiedSuperclass ? DART_WIDGET_BASES.get(unqualifiedSuperclass) : void 0;
|
|
3468
|
+
return {
|
|
3469
|
+
...superclass ? { superclass } : {},
|
|
3470
|
+
widget: widgetKind !== void 0,
|
|
3471
|
+
...widgetKind ? { widgetKind } : {}
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
function javaClassMetadata(node, language, kind) {
|
|
3475
|
+
if (language !== "java" || kind !== "class" || node.type !== "class_declaration") return {};
|
|
3476
|
+
const superclassNode = node.childForFieldName("superclass");
|
|
3477
|
+
const superclass = superclassNode?.firstNamedChild?.text.trim();
|
|
3478
|
+
return superclass ? { superclass } : {};
|
|
3479
|
+
}
|
|
3480
|
+
function javaSymbolVisibility(node, language, kind) {
|
|
3481
|
+
if (language !== "java") return {};
|
|
3482
|
+
const modifiers = node.namedChildren.find((child) => child?.type === "modifiers")?.text ?? "";
|
|
3483
|
+
if (/(?:^|\s)public(?:\s|$)/.test(modifiers)) return { exported: true };
|
|
3484
|
+
if (/(?:^|\s)(?:private|protected)(?:\s|$)/.test(modifiers)) return { exported: false };
|
|
3485
|
+
if (kind !== "method" || node.type !== "method_declaration") return {};
|
|
3486
|
+
const body = node.parent;
|
|
3487
|
+
const declaration = body?.parent;
|
|
3488
|
+
if (body?.type !== "interface_body" || declaration?.type !== "interface_declaration") return {};
|
|
3489
|
+
return { exported: true };
|
|
3490
|
+
}
|
|
3491
|
+
function javaPackageName(root, language) {
|
|
3492
|
+
if (language !== "java") return void 0;
|
|
3493
|
+
const declaration = root.namedChildren.find((child) => child?.type === "package_declaration");
|
|
3494
|
+
const value = declaration?.firstNamedChild?.text.trim();
|
|
3495
|
+
return value || void 0;
|
|
3496
|
+
}
|
|
3497
|
+
function importFact(node, language, filePath, sourceHash) {
|
|
3498
|
+
const text = node.text.trim();
|
|
3499
|
+
let kind = "import";
|
|
3500
|
+
let specifier;
|
|
3501
|
+
let isStatic = false;
|
|
3502
|
+
let wildcard = false;
|
|
3503
|
+
if (node.type === "preproc_include" || /^#\s*include\b/.test(text)) {
|
|
3504
|
+
kind = "include";
|
|
3505
|
+
specifier = /[<"]([^>"]+)[>"]/.exec(text)?.[1];
|
|
3506
|
+
} else if (/^export\b/.test(text)) {
|
|
3507
|
+
kind = "export";
|
|
3508
|
+
specifier = /\bfrom\s*["']([^"']+)["']/.exec(text)?.[1] ?? /^export\s*\*\s*["']([^"']+)["']/.exec(text)?.[1];
|
|
3509
|
+
} else if (/^use\b/.test(text)) {
|
|
3510
|
+
kind = "use";
|
|
3511
|
+
specifier = text.replace(/^use\s+/, "").replace(/;$/, "").trim();
|
|
3512
|
+
} else if (/^package\b/.test(text)) {
|
|
3513
|
+
kind = "package";
|
|
3514
|
+
specifier = text.replace(/^package\s+/, "").replace(/[;\s]+$/, "").trim();
|
|
3515
|
+
} else if (language === "java" && node.type === "import_declaration") {
|
|
3516
|
+
isStatic = /^import\s+static\b/.test(text);
|
|
3517
|
+
specifier = text.replace(/^import\s+(?:static\s+)?/, "").replace(/[;\s]+$/, "").trim();
|
|
3518
|
+
wildcard = specifier.endsWith(".*");
|
|
3519
|
+
} else {
|
|
3520
|
+
specifier = /["']([^"']+)["']/.exec(text)?.[1] ?? /^from\s+([^\s]+)\s+import\b/.exec(text)?.[1] ?? /^import\s+([^\s;,]+)/.exec(text)?.[1];
|
|
3521
|
+
}
|
|
3522
|
+
if (!specifier) return void 0;
|
|
3523
|
+
const importedNames = text.match(/[A-Za-z_$][\w$]*/g)?.filter((name) => !["as", "export", "from", "import", "include", "package", "part", "show", "static", "use"].includes(name)) ?? [];
|
|
3524
|
+
const span = spanFor(node);
|
|
3525
|
+
return {
|
|
3526
|
+
factId: `import_${sha256(`${sourceHash}\0${filePath}\0${kind}\0${specifier}\0${span.startByte}`).slice(0, 24)}`,
|
|
3527
|
+
path: filePath,
|
|
3528
|
+
language,
|
|
3529
|
+
kind,
|
|
3530
|
+
specifier,
|
|
3531
|
+
importedNames: [...new Set(importedNames)].slice(0, 100),
|
|
3532
|
+
...isStatic ? { isStatic: true } : {},
|
|
3533
|
+
...wildcard ? { wildcard: true } : {},
|
|
3534
|
+
span,
|
|
3535
|
+
confidence: "high",
|
|
3536
|
+
provenance: "tree_sitter"
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
function dynamicImportFact(node, language, filePath, sourceHash) {
|
|
3540
|
+
if (!JAVASCRIPT_FAMILY.has(language) || node.type !== "call_expression") return void 0;
|
|
3541
|
+
const calleeNode = node.childForFieldName("function") ?? node.firstNamedChild;
|
|
3542
|
+
const callee = calleeNode?.text.trim();
|
|
3543
|
+
if (callee !== "import" && callee !== "require") return void 0;
|
|
3544
|
+
const argumentsNode = node.childForFieldName("arguments") ?? node.namedChildren.find((child) => child?.type === "arguments") ?? void 0;
|
|
3545
|
+
const expression = argumentsNode?.namedChildren[0]?.text.trim();
|
|
3546
|
+
if (!expression) return void 0;
|
|
3547
|
+
const quoted2 = /^(?:'([^']+)'|"([^"]+)"|`([^`$]+)`)$/s.exec(expression);
|
|
3548
|
+
const specifier = (quoted2?.[1] ?? quoted2?.[2] ?? quoted2?.[3] ?? expression).slice(0, 1e3);
|
|
3549
|
+
if (!specifier) return void 0;
|
|
3550
|
+
const kind = callee === "import" ? "dynamic_import" : "require";
|
|
3551
|
+
const span = spanFor(node);
|
|
3552
|
+
return {
|
|
3553
|
+
factId: `import_${sha256(`${sourceHash}\0${filePath}\0${kind}\0${specifier}\0${span.startByte}`).slice(0, 24)}`,
|
|
3554
|
+
path: filePath,
|
|
3555
|
+
language,
|
|
3556
|
+
kind,
|
|
3557
|
+
specifier,
|
|
3558
|
+
importedNames: [],
|
|
3559
|
+
span,
|
|
3560
|
+
confidence: quoted2 ? "high" : "medium",
|
|
3561
|
+
provenance: "tree_sitter"
|
|
3562
|
+
};
|
|
3563
|
+
}
|
|
3564
|
+
function callFact(node, language, filePath, sourceHash) {
|
|
3565
|
+
const calleeNode = node.type === "object_creation_expression" ? node.childForFieldName("type") ?? node.firstNamedChild : node.childForFieldName("function") ?? node.childForFieldName("name") ?? node.firstNamedChild;
|
|
3566
|
+
const callee = (node.type === "method_reference" ? node.text : calleeNode?.text)?.replace(/\s+/g, " ").trim();
|
|
3567
|
+
if (!callee || callee.length > 300) return void 0;
|
|
3568
|
+
const argumentsNode = node.childForFieldName("arguments") ?? node.namedChildren.find((child) => child?.type === "arguments") ?? void 0;
|
|
3569
|
+
const span = spanFor(node);
|
|
3570
|
+
return {
|
|
3571
|
+
factId: `call_${sha256(`${sourceHash}\0${filePath}\0${callee}\0${span.startByte}`).slice(0, 24)}`,
|
|
3572
|
+
path: filePath,
|
|
3573
|
+
language,
|
|
3574
|
+
callee,
|
|
3575
|
+
...argumentsNode ? { argumentCount: argumentsNode.namedChildCount } : {},
|
|
3576
|
+
span,
|
|
3577
|
+
confidence: "low",
|
|
3578
|
+
resolution: "unresolved",
|
|
3579
|
+
provenance: "tree_sitter"
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
function mergeErrorRanges(ranges) {
|
|
3583
|
+
ranges.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
3584
|
+
let bytes = 0;
|
|
3585
|
+
let start = -1;
|
|
3586
|
+
let end = -1;
|
|
3587
|
+
for (const [nextStart, nextEnd] of ranges) {
|
|
3588
|
+
if (nextStart > end) {
|
|
3589
|
+
if (end >= start) bytes += end - start;
|
|
3590
|
+
start = nextStart;
|
|
3591
|
+
end = nextEnd;
|
|
3592
|
+
} else {
|
|
3593
|
+
end = Math.max(end, nextEnd);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
if (end >= start) bytes += end - start;
|
|
3597
|
+
return bytes;
|
|
3598
|
+
}
|
|
3599
|
+
function syntaxRecoveryScore(root) {
|
|
3600
|
+
const errorRanges = [];
|
|
3601
|
+
let errorNodes = 0;
|
|
3602
|
+
let missingNodes = 0;
|
|
3603
|
+
const stack = [root];
|
|
3604
|
+
while (stack.length > 0) {
|
|
3605
|
+
const node = stack.pop();
|
|
3606
|
+
if (!node) continue;
|
|
3607
|
+
if (node.isError) {
|
|
3608
|
+
errorNodes += 1;
|
|
3609
|
+
errorRanges.push([node.startIndex, node.endIndex]);
|
|
3610
|
+
}
|
|
3611
|
+
if (node.isMissing) missingNodes += 1;
|
|
3612
|
+
for (const child of node.namedChildren) if (child) stack.push(child);
|
|
3613
|
+
}
|
|
3614
|
+
return { errorBytes: mergeErrorRanges(errorRanges), errorNodes, missingNodes };
|
|
3615
|
+
}
|
|
3616
|
+
function hasBetterSyntaxRecovery(candidate, baseline) {
|
|
3617
|
+
if (candidate.errorBytes !== baseline.errorBytes) return candidate.errorBytes < baseline.errorBytes;
|
|
3618
|
+
if (candidate.missingNodes !== baseline.missingNodes) return candidate.missingNodes < baseline.missingNodes;
|
|
3619
|
+
return candidate.errorNodes < baseline.errorNodes;
|
|
3620
|
+
}
|
|
3621
|
+
function normalizeCppParserSource(source) {
|
|
3622
|
+
const lines = source.split("\n");
|
|
3623
|
+
const normalized = [];
|
|
3624
|
+
const maskedRanges = [];
|
|
3625
|
+
let byteOffset = 0;
|
|
3626
|
+
let defineContinuation = false;
|
|
3627
|
+
let invocationDepth = 0;
|
|
3628
|
+
let maskedDefinitions = 0;
|
|
3629
|
+
let maskedInvocations = 0;
|
|
3630
|
+
let maskedSuffixes = 0;
|
|
3631
|
+
const maskLine = (line) => " ".repeat(line.length);
|
|
3632
|
+
const parenDelta = (line) => {
|
|
3633
|
+
let delta = 0;
|
|
3634
|
+
for (const character of line) {
|
|
3635
|
+
if (character === "(") delta += 1;
|
|
3636
|
+
else if (character === ")") delta -= 1;
|
|
3637
|
+
}
|
|
3638
|
+
return delta;
|
|
3639
|
+
};
|
|
3640
|
+
for (const line of lines) {
|
|
3641
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
3642
|
+
const trimmed = line.trimStart();
|
|
3643
|
+
if (!defineContinuation && /^#\s*define\b/.test(trimmed)) defineContinuation = true;
|
|
3644
|
+
if (defineContinuation) {
|
|
3645
|
+
normalized.push(maskLine(line));
|
|
3646
|
+
maskedRanges.push([byteOffset, byteOffset + lineBytes]);
|
|
3647
|
+
maskedDefinitions += 1;
|
|
3648
|
+
defineContinuation = line.trimEnd().endsWith("\\");
|
|
3649
|
+
byteOffset += lineBytes + 1;
|
|
3650
|
+
continue;
|
|
3651
|
+
}
|
|
3652
|
+
const startsInvocation = invocationDepth === 0 && /^[A-Z][A-Z0-9_]{2,}\s*\(/.test(trimmed);
|
|
3653
|
+
if (startsInvocation || invocationDepth > 0) {
|
|
3654
|
+
invocationDepth += parenDelta(line);
|
|
3655
|
+
normalized.push(maskLine(line));
|
|
3656
|
+
maskedRanges.push([byteOffset, byteOffset + lineBytes]);
|
|
3657
|
+
maskedInvocations += 1;
|
|
3658
|
+
if (invocationDepth <= 0) invocationDepth = 0;
|
|
3659
|
+
byteOffset += lineBytes + 1;
|
|
3660
|
+
continue;
|
|
3661
|
+
}
|
|
3662
|
+
let rewritten = line;
|
|
3663
|
+
const suffix = /\b([A-Z][A-Z0-9_]{2,})(?=\s*;\s*(?:\/\/.*)?$)/.exec(line);
|
|
3664
|
+
if (suffix?.index !== void 0) {
|
|
3665
|
+
const before = line.slice(0, suffix.index);
|
|
3666
|
+
const closingParen = before.lastIndexOf(")");
|
|
3667
|
+
const between = closingParen >= 0 ? before.slice(closingParen + 1) : "";
|
|
3668
|
+
if (closingParen >= 0 && /^\s*(?:(?:const|final|noexcept|override)\s*)*$/.test(between)) {
|
|
3669
|
+
const token = suffix[1];
|
|
3670
|
+
const tokenIndex = suffix.index;
|
|
3671
|
+
rewritten = `${line.slice(0, tokenIndex)}${" ".repeat(token.length)}${line.slice(tokenIndex + token.length)}`;
|
|
3672
|
+
const tokenByte = byteOffset + Buffer.byteLength(line.slice(0, tokenIndex), "utf8");
|
|
3673
|
+
maskedRanges.push([tokenByte, tokenByte + token.length]);
|
|
3674
|
+
maskedSuffixes += 1;
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
normalized.push(rewritten);
|
|
3678
|
+
byteOffset += lineBytes + 1;
|
|
3679
|
+
}
|
|
3680
|
+
return { source: normalized.join("\n"), maskedRanges, maskedDefinitions, maskedInvocations, maskedSuffixes };
|
|
3681
|
+
}
|
|
3682
|
+
function lexicalScopeEnd(node, language, source) {
|
|
3683
|
+
if (language !== "c" && language !== "cpp") return node.endIndex;
|
|
3684
|
+
const body = node.childForFieldName("body");
|
|
3685
|
+
if (!body) return node.endIndex;
|
|
3686
|
+
const bytes = Buffer.from(source, "utf8");
|
|
3687
|
+
let opening = body.startIndex;
|
|
3688
|
+
while (opening < Math.min(bytes.length, node.endIndex) && bytes[opening] !== 123) opening += 1;
|
|
3689
|
+
if (opening >= Math.min(bytes.length, node.endIndex)) return node.endIndex;
|
|
3690
|
+
let depth = 0;
|
|
3691
|
+
let quote = 0;
|
|
3692
|
+
let lineComment = false;
|
|
3693
|
+
let blockComment = false;
|
|
3694
|
+
for (let index = opening; index < bytes.length; index += 1) {
|
|
3695
|
+
const current = bytes[index];
|
|
3696
|
+
const next = bytes[index + 1];
|
|
3697
|
+
if (lineComment) {
|
|
3698
|
+
if (current === 10) lineComment = false;
|
|
3699
|
+
continue;
|
|
3700
|
+
}
|
|
3701
|
+
if (blockComment) {
|
|
3702
|
+
if (current === 42 && next === 47) {
|
|
3703
|
+
blockComment = false;
|
|
3704
|
+
index += 1;
|
|
3705
|
+
}
|
|
3706
|
+
continue;
|
|
3707
|
+
}
|
|
3708
|
+
if (quote !== 0) {
|
|
3709
|
+
if (current === 92) {
|
|
3710
|
+
index += 1;
|
|
3711
|
+
continue;
|
|
3712
|
+
}
|
|
3713
|
+
if (current === quote) quote = 0;
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3716
|
+
if (current === 47 && next === 47) {
|
|
3717
|
+
lineComment = true;
|
|
3718
|
+
index += 1;
|
|
3719
|
+
continue;
|
|
3720
|
+
}
|
|
3721
|
+
if (current === 47 && next === 42) {
|
|
3722
|
+
blockComment = true;
|
|
3723
|
+
index += 1;
|
|
3724
|
+
continue;
|
|
3725
|
+
}
|
|
3726
|
+
if (current === 34 || current === 39) {
|
|
3727
|
+
quote = current;
|
|
3728
|
+
continue;
|
|
3729
|
+
}
|
|
3730
|
+
if (current === 123) depth += 1;
|
|
3731
|
+
else if (current === 125) {
|
|
3732
|
+
depth -= 1;
|
|
3733
|
+
if (depth === 0) return index + 1;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return node.endIndex;
|
|
3737
|
+
}
|
|
3738
|
+
function extractFacts(root, language, filePath, sourceHash, sourceBytes, parseMs, parserSource, normalization) {
|
|
3739
|
+
const symbols = [];
|
|
3740
|
+
const imports = [];
|
|
3741
|
+
const calls = [];
|
|
3742
|
+
const diagnostics = [];
|
|
3743
|
+
const errorRanges = [];
|
|
3744
|
+
let errorNodes = 0;
|
|
3745
|
+
let missingNodes = 0;
|
|
3746
|
+
const packageName = javaPackageName(root, language);
|
|
3747
|
+
const stack = [{
|
|
3748
|
+
node: root,
|
|
3749
|
+
scopes: packageName ? [{ name: packageName, endByte: Number.MAX_SAFE_INTEGER }] : []
|
|
3750
|
+
}];
|
|
3751
|
+
while (stack.length > 0) {
|
|
3752
|
+
const current = stack.pop();
|
|
3753
|
+
if (!current) continue;
|
|
3754
|
+
const { node } = current;
|
|
3755
|
+
const scopes = [...current.scopes];
|
|
3756
|
+
while (scopes.length > 0 && scopes.at(-1).endByte <= node.startIndex) scopes.pop();
|
|
3757
|
+
const scope = scopes.map((entry) => entry.name);
|
|
3758
|
+
if (node.isMissing) {
|
|
3759
|
+
missingNodes += 1;
|
|
3760
|
+
if (diagnostics.length < 1e3) diagnostics.push({ code: "parse_error", severity: "warning", message: `parser inserted missing ${node.type}`, span: spanFor(node) });
|
|
3761
|
+
}
|
|
3762
|
+
if (node.isError) {
|
|
3763
|
+
errorNodes += 1;
|
|
3764
|
+
errorRanges.push([node.startIndex, node.endIndex]);
|
|
3765
|
+
if (diagnostics.length < 1e3) diagnostics.push({ code: "parse_error", severity: "warning", message: "Tree-sitter could not parse this source range", span: spanFor(node) });
|
|
3766
|
+
}
|
|
3767
|
+
const recovered = node.isError || Boolean(ancestor(node, (candidate) => candidate.isError));
|
|
3768
|
+
const kind = recovered ? void 0 : symbolKind(node, language);
|
|
3769
|
+
let childScopes = scopes;
|
|
3770
|
+
if (kind && symbols.length < MAX_FACTS_PER_KIND) {
|
|
3771
|
+
const name = symbolName(node);
|
|
3772
|
+
if (name) {
|
|
3773
|
+
const span = spanFor(node);
|
|
3774
|
+
const qualifiedName = [...scope, name].join(".");
|
|
3775
|
+
const docComment = leadingDocComment(node, language);
|
|
3776
|
+
const classMetadata = {
|
|
3777
|
+
...dartClassMetadata(node, language, kind),
|
|
3778
|
+
...javaClassMetadata(node, language, kind),
|
|
3779
|
+
...javaSymbolVisibility(node, language, kind)
|
|
3780
|
+
};
|
|
3781
|
+
symbols.push({
|
|
3782
|
+
factId: `symbol_${sha256(`${sourceHash}\0${filePath}\0${kind}\0${qualifiedName}\0${span.startByte}`).slice(0, 24)}`,
|
|
3783
|
+
path: filePath,
|
|
3784
|
+
language,
|
|
3785
|
+
kind,
|
|
3786
|
+
name,
|
|
3787
|
+
qualifiedName,
|
|
3788
|
+
signature: signatureFor(node),
|
|
3789
|
+
...docComment ? { docComment } : {},
|
|
3790
|
+
...classMetadata,
|
|
3791
|
+
nodeType: node.type,
|
|
3792
|
+
span,
|
|
3793
|
+
confidence: "high",
|
|
3794
|
+
provenance: "tree_sitter"
|
|
3795
|
+
});
|
|
3796
|
+
if (SCOPE_KINDS.has(kind)) childScopes = [...scopes, {
|
|
3797
|
+
name,
|
|
3798
|
+
endByte: lexicalScopeEnd(node, language, parserSource)
|
|
3799
|
+
}];
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
if (!recovered && IMPORT_TYPES.has(node.type) && !(language === "java" && node.type === "package_declaration") && imports.length < MAX_FACTS_PER_KIND) {
|
|
3803
|
+
const fact = importFact(node, language, filePath, sourceHash);
|
|
3804
|
+
if (fact) imports.push(fact);
|
|
3805
|
+
}
|
|
3806
|
+
if (!recovered && CALL_TYPES.has(node.type)) {
|
|
3807
|
+
const dynamicImport = imports.length < MAX_FACTS_PER_KIND ? dynamicImportFact(node, language, filePath, sourceHash) : void 0;
|
|
3808
|
+
if (dynamicImport) imports.push(dynamicImport);
|
|
3809
|
+
else if (calls.length < MAX_FACTS_PER_KIND) {
|
|
3810
|
+
const fact = callFact(node, language, filePath, sourceHash);
|
|
3811
|
+
if (fact) calls.push(fact);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
const children = node.namedChildren;
|
|
3815
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
3816
|
+
const child = children[index];
|
|
3817
|
+
if (child) stack.push({ node: child, scopes: childScopes });
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
if (normalization && normalization.maskedRanges.length > 0) {
|
|
3821
|
+
diagnostics.push({
|
|
3822
|
+
code: "unsupported_construct",
|
|
3823
|
+
severity: "warning",
|
|
3824
|
+
message: `parser masked ${normalization.maskedDefinitions} C/C++ macro-definition lines, ${normalization.maskedInvocations} declaration-macro lines, and ${normalization.maskedSuffixes} declaration suffixes while preserving source offsets`
|
|
3825
|
+
});
|
|
3826
|
+
}
|
|
3827
|
+
if (symbols.length >= MAX_FACTS_PER_KIND || imports.length >= MAX_FACTS_PER_KIND || calls.length >= MAX_FACTS_PER_KIND) {
|
|
3828
|
+
diagnostics.push({ code: "unsupported_construct", severity: "warning", message: `fact extraction reached the per-file cap of ${MAX_FACTS_PER_KIND}` });
|
|
3829
|
+
}
|
|
3830
|
+
const errorBytes = mergeErrorRanges(errorRanges);
|
|
3831
|
+
return {
|
|
3832
|
+
symbols,
|
|
3833
|
+
imports,
|
|
3834
|
+
calls,
|
|
3835
|
+
diagnostics,
|
|
3836
|
+
coverage: {
|
|
3837
|
+
sourceBytes,
|
|
3838
|
+
parsedBytes: Math.max(0, sourceBytes - errorBytes),
|
|
3839
|
+
errorBytes,
|
|
3840
|
+
errorByteRatio: sourceBytes === 0 ? 0 : errorBytes / sourceBytes,
|
|
3841
|
+
errorNodes,
|
|
3842
|
+
missingNodes,
|
|
3843
|
+
parseMs
|
|
3844
|
+
}
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
async function parseWithTreeSitter(input) {
|
|
3848
|
+
const filePath = normalizeRepoPath(input.path);
|
|
3849
|
+
const language = input.language ?? detectLanguage(filePath, input.source.slice(0, 500));
|
|
3850
|
+
if (!language) return void 0;
|
|
3851
|
+
const sourceBytes = Buffer.byteLength(input.source, "utf8");
|
|
3852
|
+
if (sourceBytes > MAX_SOURCE_BYTES) {
|
|
3853
|
+
const sourceHash2 = input.sourceSha256 ?? sha256(input.source);
|
|
3854
|
+
return {
|
|
3855
|
+
schemaVersion: 1,
|
|
3856
|
+
status: "failed",
|
|
3857
|
+
path: filePath,
|
|
3858
|
+
language,
|
|
3859
|
+
sourceSha256: sourceHash2,
|
|
3860
|
+
parserRuntime: "unavailable",
|
|
3861
|
+
grammar: { id: "unloaded", abi: 0, sha256: "0".repeat(64), runtime: "unavailable", source: "none", license: "none" },
|
|
3862
|
+
rootType: "",
|
|
3863
|
+
symbols: [],
|
|
3864
|
+
imports: [],
|
|
3865
|
+
calls: [],
|
|
3866
|
+
diagnostics: [{ code: "parser_failure", severity: "error", message: `source exceeds the ${MAX_SOURCE_BYTES}-byte parser limit` }],
|
|
3867
|
+
coverage: { sourceBytes, parsedBytes: 0, errorBytes: sourceBytes, errorByteRatio: 1, errorNodes: 0, missingNodes: 0, parseMs: 0 }
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
let loaded;
|
|
3871
|
+
try {
|
|
3872
|
+
loaded = await loadGrammar(language, filePath, { assetRoot: input.assetRoot, coreWasmPath: input.coreWasmPath });
|
|
3873
|
+
} catch (error) {
|
|
3874
|
+
if (error instanceof GrammarLoadError) throw error;
|
|
3875
|
+
throw new GrammarLoadError("grammar_unavailable", error instanceof Error ? error.message : String(error));
|
|
3876
|
+
}
|
|
3877
|
+
if (!loaded) return void 0;
|
|
3878
|
+
assertAstQueryCompatible(language, filePath, loaded.language, input.assetRoot);
|
|
3879
|
+
const sourceHash = input.sourceSha256 ?? sha256(input.source);
|
|
3880
|
+
const parser = new Parser2();
|
|
3881
|
+
parser.setLanguage(loaded.language);
|
|
3882
|
+
const startedAt = performance3.now();
|
|
3883
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_AST_FILE_TIMEOUT_MS;
|
|
3884
|
+
let timedOut = false;
|
|
3885
|
+
try {
|
|
3886
|
+
const parseOptions = {
|
|
3887
|
+
progressCallback: () => {
|
|
3888
|
+
timedOut = performance3.now() - startedAt > timeoutMs;
|
|
3889
|
+
return timedOut;
|
|
3890
|
+
}
|
|
3891
|
+
};
|
|
3892
|
+
const rawTree = parser.parse(input.source, null, parseOptions);
|
|
3893
|
+
const parseMs = performance3.now() - startedAt;
|
|
3894
|
+
if (!rawTree) {
|
|
3895
|
+
return {
|
|
3896
|
+
schemaVersion: 1,
|
|
3897
|
+
status: "failed",
|
|
3898
|
+
path: filePath,
|
|
3899
|
+
language,
|
|
3900
|
+
sourceSha256: sourceHash,
|
|
3901
|
+
parserRuntime: "web-tree-sitter",
|
|
3902
|
+
grammar: loaded.provenance,
|
|
3903
|
+
rootType: "",
|
|
3904
|
+
symbols: [],
|
|
3905
|
+
imports: [],
|
|
3906
|
+
calls: [],
|
|
3907
|
+
diagnostics: [{ code: timedOut ? "parse_timeout" : "parser_failure", severity: "error", message: timedOut ? `Tree-sitter exceeded ${timeoutMs}ms` : "Tree-sitter returned no syntax tree" }],
|
|
3908
|
+
coverage: { sourceBytes, parsedBytes: 0, errorBytes: sourceBytes, errorByteRatio: sourceBytes === 0 ? 0 : 1, errorNodes: 0, missingNodes: 0, parseMs }
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
const trees = [rawTree];
|
|
3912
|
+
try {
|
|
3913
|
+
let selectedTree = rawTree;
|
|
3914
|
+
let parserSource = input.source;
|
|
3915
|
+
let selectedNormalization;
|
|
3916
|
+
if (language === "c" || language === "cpp") {
|
|
3917
|
+
const rawScore = syntaxRecoveryScore(rawTree.rootNode);
|
|
3918
|
+
if (rawScore.errorNodes > 0 || rawScore.missingNodes > 0) {
|
|
3919
|
+
const normalization = normalizeCppParserSource(input.source);
|
|
3920
|
+
if (normalization.maskedRanges.length > 0) {
|
|
3921
|
+
const normalizedTree = parser.parse(normalization.source, null, parseOptions);
|
|
3922
|
+
if (normalizedTree) {
|
|
3923
|
+
trees.push(normalizedTree);
|
|
3924
|
+
const normalizedScore = syntaxRecoveryScore(normalizedTree.rootNode);
|
|
3925
|
+
if (hasBetterSyntaxRecovery(normalizedScore, rawScore)) {
|
|
3926
|
+
selectedTree = normalizedTree;
|
|
3927
|
+
parserSource = normalization.source;
|
|
3928
|
+
selectedNormalization = normalization;
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
const root = selectedTree.rootNode;
|
|
3935
|
+
const totalParseMs = performance3.now() - startedAt;
|
|
3936
|
+
const extracted = extractFacts(root, language, filePath, sourceHash, sourceBytes, totalParseMs, parserSource, selectedNormalization);
|
|
3937
|
+
return {
|
|
3938
|
+
schemaVersion: 1,
|
|
3939
|
+
status: extracted.coverage.errorNodes > 0 || extracted.coverage.missingNodes > 0 || extracted.diagnostics.length > 0 ? "partial" : "parsed",
|
|
3940
|
+
path: filePath,
|
|
3941
|
+
language,
|
|
3942
|
+
sourceSha256: sourceHash,
|
|
3943
|
+
parserRuntime: "web-tree-sitter",
|
|
3944
|
+
grammar: loaded.provenance,
|
|
3945
|
+
rootType: root.type,
|
|
3946
|
+
symbols: extracted.symbols,
|
|
3947
|
+
imports: extracted.imports,
|
|
3948
|
+
calls: extracted.calls,
|
|
3949
|
+
diagnostics: extracted.diagnostics,
|
|
3950
|
+
coverage: extracted.coverage
|
|
3951
|
+
};
|
|
3952
|
+
} finally {
|
|
3953
|
+
for (const tree of trees) tree.delete();
|
|
3954
|
+
}
|
|
3955
|
+
} finally {
|
|
3956
|
+
parser.delete();
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
// ../packages/brain-ast/src/parse-source.ts
|
|
3961
|
+
function unavailableResult(input, language, status, code, message2) {
|
|
3962
|
+
const sourceBytes = Buffer.byteLength(input.source, "utf8");
|
|
3963
|
+
return {
|
|
3964
|
+
schemaVersion: 1,
|
|
3965
|
+
status,
|
|
3966
|
+
path: normalizeRepoPath(input.path),
|
|
3967
|
+
language,
|
|
3968
|
+
sourceSha256: sha256(input.source),
|
|
3969
|
+
parserRuntime: "unavailable",
|
|
3970
|
+
grammar: { id: "unavailable", abi: 0, sha256: "0".repeat(64), runtime: "unavailable", source: "none", license: "none" },
|
|
3971
|
+
rootType: "",
|
|
3972
|
+
symbols: [],
|
|
3973
|
+
imports: [],
|
|
3974
|
+
calls: [],
|
|
3975
|
+
diagnostics: [{ code, severity: status === "failed" ? "error" : "warning", message: message2 }],
|
|
3976
|
+
coverage: {
|
|
3977
|
+
sourceBytes,
|
|
3978
|
+
parsedBytes: 0,
|
|
3979
|
+
errorBytes: status === "failed" ? sourceBytes : 0,
|
|
3980
|
+
errorByteRatio: status === "failed" && sourceBytes > 0 ? 1 : 0,
|
|
3981
|
+
errorNodes: 0,
|
|
3982
|
+
missingNodes: 0,
|
|
3983
|
+
parseMs: 0
|
|
3984
|
+
}
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3987
|
+
async function parseSource(input) {
|
|
3988
|
+
const actualSha = sha256(input.source);
|
|
3989
|
+
if (input.sourceSha256 && input.sourceSha256 !== actualSha) {
|
|
3990
|
+
throw new Error(`source hash mismatch for ${input.path}: expected ${input.sourceSha256}, received ${actualSha}`);
|
|
3991
|
+
}
|
|
3992
|
+
const normalized = { ...input, path: normalizeRepoPath(input.path), sourceSha256: actualSha };
|
|
3993
|
+
const language = input.language ?? detectLanguage(normalized.path, input.source.slice(0, 500));
|
|
3994
|
+
if (!language) return void 0;
|
|
3995
|
+
const fallback = parseStructuralFallback(normalized, language);
|
|
3996
|
+
if (fallback) return fallback;
|
|
3997
|
+
try {
|
|
3998
|
+
const parsed = await parseWithTreeSitter({ ...normalized, language });
|
|
3999
|
+
return parsed ?? unavailableResult(normalized, language, "unsupported", "grammar_unavailable", `no parser runtime is registered for ${language}`);
|
|
4000
|
+
} catch (error) {
|
|
4001
|
+
if (error instanceof GrammarLoadError) {
|
|
4002
|
+
return unavailableResult(normalized, language, "failed", error.code, error.message);
|
|
4003
|
+
}
|
|
4004
|
+
return unavailableResult(normalized, language, "failed", "parser_failure", error instanceof Error ? error.message : String(error));
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
// ../packages/brain-ast/src/worker-protocol.ts
|
|
4009
|
+
var AST_WORKER_PROTOCOL_VERSION = 1;
|
|
4010
|
+
function isAstWorkerRequest(value) {
|
|
4011
|
+
if (!value || typeof value !== "object") return false;
|
|
4012
|
+
const candidate = value;
|
|
4013
|
+
return candidate.protocolVersion === AST_WORKER_PROTOCOL_VERSION && candidate.type === "parse" && typeof candidate.jobId === "number" && Number.isSafeInteger(candidate.jobId) && candidate.jobId >= 0 && Boolean(candidate.input) && typeof candidate.input === "object" && typeof candidate.input.path === "string" && typeof candidate.input.source === "string";
|
|
4014
|
+
}
|
|
4015
|
+
function isAstWorkerResponse(value) {
|
|
4016
|
+
if (!value || typeof value !== "object") return false;
|
|
4017
|
+
const candidate = value;
|
|
4018
|
+
if (candidate.protocolVersion !== AST_WORKER_PROTOCOL_VERSION || candidate.type !== "result" && candidate.type !== "error" || typeof candidate.jobId !== "number" || !Number.isSafeInteger(candidate.jobId) || candidate.jobId < 0 || typeof candidate.threadId !== "number" || !Number.isSafeInteger(candidate.threadId) || candidate.threadId <= 0) return false;
|
|
4019
|
+
if (candidate.type === "result") return "result" in candidate;
|
|
4020
|
+
const serialized = "error" in candidate ? candidate.error : void 0;
|
|
4021
|
+
return Boolean(serialized) && typeof serialized === "object" && typeof serialized.name === "string" && typeof serialized.message === "string";
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
export {
|
|
4025
|
+
canonicalize,
|
|
4026
|
+
sha256,
|
|
4027
|
+
hashObject,
|
|
4028
|
+
createId,
|
|
4029
|
+
normalizeRepoPath,
|
|
4030
|
+
assertInside,
|
|
4031
|
+
aggregateGate,
|
|
4032
|
+
exitCodeForGate,
|
|
4033
|
+
nowIso,
|
|
4034
|
+
walkRepo,
|
|
4035
|
+
parseManifests,
|
|
4036
|
+
synthesizeStack,
|
|
4037
|
+
chunkMarkdown,
|
|
4038
|
+
findConventionalSourcePaths,
|
|
4039
|
+
AST_QUERY_VERSION,
|
|
4040
|
+
DEFAULT_AST_FILE_TIMEOUT_MS,
|
|
4041
|
+
isAstTypeDeclarationKind,
|
|
4042
|
+
parserRuntimeForGrammar,
|
|
4043
|
+
parseSource,
|
|
4044
|
+
AST_WORKER_PROTOCOL_VERSION,
|
|
4045
|
+
isAstWorkerRequest,
|
|
4046
|
+
isAstWorkerResponse
|
|
4047
|
+
};
|
|
4048
|
+
//# sourceMappingURL=chunk-6TLQP3LV.js.map
|