verimu 0.0.17 → 0.0.19
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/README.md +15 -13
- package/dist/cli.mjs +1120 -95
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1081 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1075 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -13052,9 +13052,9 @@ var require_traverse = __commonJS({
|
|
|
13052
13052
|
Object.defineProperty(exports, "__esModule", {
|
|
13053
13053
|
value: true
|
|
13054
13054
|
});
|
|
13055
|
-
exports.default =
|
|
13055
|
+
exports.default = traverse;
|
|
13056
13056
|
var _index = require_definitions();
|
|
13057
|
-
function
|
|
13057
|
+
function traverse(node, handlers, state) {
|
|
13058
13058
|
if (typeof handlers === "function") {
|
|
13059
13059
|
handlers = {
|
|
13060
13060
|
enter: handlers
|
|
@@ -14093,11 +14093,10 @@ var require_lib3 = __commonJS({
|
|
|
14093
14093
|
// src/cli.ts
|
|
14094
14094
|
import { resolve } from "path";
|
|
14095
14095
|
import { createRequire } from "module";
|
|
14096
|
-
import { fileURLToPath } from "url";
|
|
14097
14096
|
|
|
14098
14097
|
// src/scan.ts
|
|
14099
14098
|
import { writeFile } from "fs/promises";
|
|
14100
|
-
import { basename, join as
|
|
14099
|
+
import { basename, join as join3, parse as parse2 } from "path";
|
|
14101
14100
|
|
|
14102
14101
|
// src/scanners/npm/npm-scanner.ts
|
|
14103
14102
|
import { readFile } from "fs/promises";
|
|
@@ -16751,6 +16750,15 @@ var ConsoleReporter = class {
|
|
|
16751
16750
|
lines.push(
|
|
16752
16751
|
` Findings: direct_evidence=${directEvidence}, indirect_no_evidence=${indirectNoEvidence}, unsupported=${unsupported}, analysis_error=${analysisErrors}`
|
|
16753
16752
|
);
|
|
16753
|
+
if (result.usageContext.ecosystemStatus.length > 0) {
|
|
16754
|
+
lines.push(" Analyzer status:");
|
|
16755
|
+
for (const status of result.usageContext.ecosystemStatus) {
|
|
16756
|
+
const note = status.note ? ` (${status.note})` : "";
|
|
16757
|
+
lines.push(
|
|
16758
|
+
` ${status.ecosystem}: ${status.status} via ${status.analyzer}${note}`
|
|
16759
|
+
);
|
|
16760
|
+
}
|
|
16761
|
+
}
|
|
16754
16762
|
if (result.usageContext.artifactPath) {
|
|
16755
16763
|
lines.push(` Artifact: ${result.usageContext.artifactPath}`);
|
|
16756
16764
|
}
|
|
@@ -17002,7 +17010,7 @@ var import_types = __toESM(require_lib3(), 1);
|
|
|
17002
17010
|
import { readdir, readFile as readFile14 } from "fs/promises";
|
|
17003
17011
|
import { join } from "path";
|
|
17004
17012
|
import { parse } from "@babel/parser";
|
|
17005
|
-
import
|
|
17013
|
+
import traverseModule from "@babel/traverse";
|
|
17006
17014
|
var JS_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
17007
17015
|
".js",
|
|
17008
17016
|
".jsx",
|
|
@@ -17032,6 +17040,24 @@ var JsAstAnalyzer = class {
|
|
|
17032
17040
|
return this.ecosystems.has(ecosystem);
|
|
17033
17041
|
}
|
|
17034
17042
|
async analyze(context) {
|
|
17043
|
+
const traverseFn = resolveTraverseFunction(traverseModule);
|
|
17044
|
+
if (!traverseFn) {
|
|
17045
|
+
return {
|
|
17046
|
+
packages: context.packages.map((pkg2) => ({
|
|
17047
|
+
packageName: pkg2.packageName,
|
|
17048
|
+
ecosystem: pkg2.ecosystem,
|
|
17049
|
+
status: "analysis_error",
|
|
17050
|
+
snippets: [],
|
|
17051
|
+
notes: "Failed to resolve @babel/traverse runtime export"
|
|
17052
|
+
})),
|
|
17053
|
+
errors: [{
|
|
17054
|
+
analyzer: this.name,
|
|
17055
|
+
ecosystem: context.ecosystem,
|
|
17056
|
+
error: "Failed to resolve @babel/traverse runtime export"
|
|
17057
|
+
}],
|
|
17058
|
+
snippetsProduced: 0
|
|
17059
|
+
};
|
|
17060
|
+
}
|
|
17035
17061
|
const packageMap = this.buildPackageMaps(context.packages);
|
|
17036
17062
|
const resultMap = /* @__PURE__ */ new Map();
|
|
17037
17063
|
const snippetKeyMap = /* @__PURE__ */ new Map();
|
|
@@ -17095,13 +17121,13 @@ var JsAstAnalyzer = class {
|
|
|
17095
17121
|
const matchCandidates = [];
|
|
17096
17122
|
const matchSeen = /* @__PURE__ */ new Set();
|
|
17097
17123
|
const symbolToPackage = /* @__PURE__ */ new Map();
|
|
17098
|
-
const addMatch = (
|
|
17099
|
-
const candidateKey = `${
|
|
17124
|
+
const addMatch = (packageKey3, line, matchKind, calledSymbol, confidence = 0.8) => {
|
|
17125
|
+
const candidateKey = `${packageKey3}:${line}:${matchKind}:${calledSymbol ?? ""}`;
|
|
17100
17126
|
if (matchSeen.has(candidateKey)) return;
|
|
17101
17127
|
matchSeen.add(candidateKey);
|
|
17102
|
-
matchCandidates.push({ packageKey:
|
|
17128
|
+
matchCandidates.push({ packageKey: packageKey3, line, matchKind, calledSymbol, confidence });
|
|
17103
17129
|
};
|
|
17104
|
-
|
|
17130
|
+
traverseFn(ast, {
|
|
17105
17131
|
ImportDeclaration: (path14) => {
|
|
17106
17132
|
const source = path14.node.source;
|
|
17107
17133
|
if (!(0, import_types.isStringLiteral)(source)) return;
|
|
@@ -17223,6 +17249,18 @@ var JsAstAnalyzer = class {
|
|
|
17223
17249
|
return `${ecosystem}::${packageName}`;
|
|
17224
17250
|
}
|
|
17225
17251
|
};
|
|
17252
|
+
function resolveTraverseFunction(moduleValue) {
|
|
17253
|
+
if (typeof moduleValue === "function") {
|
|
17254
|
+
return moduleValue;
|
|
17255
|
+
}
|
|
17256
|
+
if (typeof moduleValue === "object" && moduleValue !== null && "default" in moduleValue) {
|
|
17257
|
+
const candidate = moduleValue.default;
|
|
17258
|
+
if (typeof candidate === "function") {
|
|
17259
|
+
return candidate;
|
|
17260
|
+
}
|
|
17261
|
+
}
|
|
17262
|
+
return null;
|
|
17263
|
+
}
|
|
17226
17264
|
async function collectSourceFiles(rootPath) {
|
|
17227
17265
|
const files = [];
|
|
17228
17266
|
async function walk(dirPath) {
|
|
@@ -17318,9 +17356,9 @@ function collectIdentifiers(pattern) {
|
|
|
17318
17356
|
function resolveCallMatch(callee, symbolToPackage) {
|
|
17319
17357
|
const normalized = unwrapExpression(callee);
|
|
17320
17358
|
if ((0, import_types.isIdentifier)(normalized)) {
|
|
17321
|
-
const
|
|
17322
|
-
if (!
|
|
17323
|
-
return { packageKey:
|
|
17359
|
+
const packageKey3 = symbolToPackage.get(normalized.name);
|
|
17360
|
+
if (!packageKey3) return null;
|
|
17361
|
+
return { packageKey: packageKey3, calledSymbol: normalized.name };
|
|
17324
17362
|
}
|
|
17325
17363
|
if ((0, import_types.isMemberExpression)(normalized) || (0, import_types.isOptionalMemberExpression)(normalized)) {
|
|
17326
17364
|
return resolveMemberCallMatch(normalized, symbolToPackage);
|
|
@@ -17337,13 +17375,13 @@ function unwrapExpression(expression) {
|
|
|
17337
17375
|
function resolveMemberCallMatch(memberExpression, symbolToPackage) {
|
|
17338
17376
|
const objectExpr = unwrapExpression(memberExpression.object);
|
|
17339
17377
|
if (!(0, import_types.isIdentifier)(objectExpr)) return null;
|
|
17340
|
-
const
|
|
17341
|
-
if (!
|
|
17378
|
+
const packageKey3 = symbolToPackage.get(objectExpr.name);
|
|
17379
|
+
if (!packageKey3) return null;
|
|
17342
17380
|
const propertyName = propertyNameOf(memberExpression);
|
|
17343
17381
|
if (!propertyName) {
|
|
17344
|
-
return { packageKey:
|
|
17382
|
+
return { packageKey: packageKey3, calledSymbol: objectExpr.name };
|
|
17345
17383
|
}
|
|
17346
|
-
return { packageKey:
|
|
17384
|
+
return { packageKey: packageKey3, calledSymbol: `${objectExpr.name}.${propertyName}` };
|
|
17347
17385
|
}
|
|
17348
17386
|
function propertyNameOf(memberExpression) {
|
|
17349
17387
|
if (memberExpression.computed) {
|
|
@@ -17360,34 +17398,1021 @@ function propertyNameOf(memberExpression) {
|
|
|
17360
17398
|
return null;
|
|
17361
17399
|
}
|
|
17362
17400
|
|
|
17363
|
-
// src/context/analyzers/
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17401
|
+
// src/context/analyzers/shared.ts
|
|
17402
|
+
import { readdir as readdir2, readFile as readFile15 } from "fs/promises";
|
|
17403
|
+
import { join as join2 } from "path";
|
|
17404
|
+
var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
17405
|
+
".git",
|
|
17406
|
+
".hg",
|
|
17407
|
+
".svn",
|
|
17408
|
+
"node_modules",
|
|
17409
|
+
"dist",
|
|
17410
|
+
"build",
|
|
17411
|
+
"coverage",
|
|
17412
|
+
".next",
|
|
17413
|
+
".nuxt",
|
|
17414
|
+
".turbo",
|
|
17415
|
+
"vendor",
|
|
17416
|
+
".venv",
|
|
17417
|
+
"venv",
|
|
17418
|
+
"target",
|
|
17419
|
+
"bin",
|
|
17420
|
+
"obj"
|
|
17421
|
+
]);
|
|
17422
|
+
function packageKey(ecosystem, packageName) {
|
|
17423
|
+
return `${ecosystem}::${packageName}`;
|
|
17424
|
+
}
|
|
17425
|
+
function initState(packages) {
|
|
17426
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
17427
|
+
const snippetKeyMap = /* @__PURE__ */ new Map();
|
|
17428
|
+
for (const pkg2 of packages) {
|
|
17429
|
+
const key = packageKey(pkg2.ecosystem, pkg2.packageName);
|
|
17430
|
+
resultMap.set(key, {
|
|
17431
|
+
packageName: pkg2.packageName,
|
|
17432
|
+
ecosystem: pkg2.ecosystem,
|
|
17433
|
+
status: "indirect_no_evidence",
|
|
17434
|
+
snippets: []
|
|
17435
|
+
});
|
|
17436
|
+
snippetKeyMap.set(key, /* @__PURE__ */ new Set());
|
|
17372
17437
|
}
|
|
17438
|
+
return {
|
|
17439
|
+
resultMap,
|
|
17440
|
+
snippetKeyMap,
|
|
17441
|
+
errors: [],
|
|
17442
|
+
snippetsProduced: 0
|
|
17443
|
+
};
|
|
17444
|
+
}
|
|
17445
|
+
function errorResultFromMessage(context, analyzerName, message, notes) {
|
|
17446
|
+
return {
|
|
17447
|
+
packages: context.packages.map((pkg2) => ({
|
|
17448
|
+
packageName: pkg2.packageName,
|
|
17449
|
+
ecosystem: pkg2.ecosystem,
|
|
17450
|
+
status: "analysis_error",
|
|
17451
|
+
snippets: [],
|
|
17452
|
+
notes
|
|
17453
|
+
})),
|
|
17454
|
+
errors: [{ analyzer: analyzerName, ecosystem: context.ecosystem, error: message }],
|
|
17455
|
+
snippetsProduced: 0
|
|
17456
|
+
};
|
|
17457
|
+
}
|
|
17458
|
+
function toAnalyzerResult(state) {
|
|
17459
|
+
for (const result of state.resultMap.values()) {
|
|
17460
|
+
result.snippets = dedupeSnippets(result.snippets);
|
|
17461
|
+
if (result.snippets.length > 0) {
|
|
17462
|
+
result.status = "direct_evidence";
|
|
17463
|
+
}
|
|
17464
|
+
}
|
|
17465
|
+
return {
|
|
17466
|
+
packages: Array.from(state.resultMap.values()),
|
|
17467
|
+
errors: state.errors,
|
|
17468
|
+
snippetsProduced: state.snippetsProduced
|
|
17469
|
+
};
|
|
17470
|
+
}
|
|
17471
|
+
async function collectSourceFiles2(rootPath, extensions, ignoredDirs = DEFAULT_IGNORED_DIRS) {
|
|
17472
|
+
const files = [];
|
|
17473
|
+
async function walk(dirPath) {
|
|
17474
|
+
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
17475
|
+
for (const entry of entries) {
|
|
17476
|
+
const fullPath = join2(dirPath, entry.name);
|
|
17477
|
+
if (entry.isDirectory()) {
|
|
17478
|
+
if (ignoredDirs.has(entry.name)) continue;
|
|
17479
|
+
await walk(fullPath);
|
|
17480
|
+
continue;
|
|
17481
|
+
}
|
|
17482
|
+
if (!entry.isFile()) continue;
|
|
17483
|
+
if (!extensions.has(extensionOf2(entry.name))) continue;
|
|
17484
|
+
files.push(fullPath);
|
|
17485
|
+
}
|
|
17486
|
+
}
|
|
17487
|
+
await walk(rootPath);
|
|
17488
|
+
return files;
|
|
17489
|
+
}
|
|
17490
|
+
async function readSourceFile(analyzerName, ecosystem, filePath, errors) {
|
|
17491
|
+
try {
|
|
17492
|
+
return await readFile15(filePath, "utf-8");
|
|
17493
|
+
} catch (err) {
|
|
17494
|
+
errors.push({
|
|
17495
|
+
analyzer: analyzerName,
|
|
17496
|
+
ecosystem,
|
|
17497
|
+
error: `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
17498
|
+
});
|
|
17499
|
+
return null;
|
|
17500
|
+
}
|
|
17501
|
+
}
|
|
17502
|
+
function addCandidate(context, state, filePath, sourceText, candidate) {
|
|
17503
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) return;
|
|
17504
|
+
const packageResult = state.resultMap.get(candidate.packageKey);
|
|
17505
|
+
if (!packageResult) return;
|
|
17506
|
+
if (packageResult.snippets.length >= context.maxSnippetsPerPackage) return;
|
|
17507
|
+
const snippet = buildSnippet({
|
|
17508
|
+
projectPath: context.projectPath,
|
|
17509
|
+
filePath,
|
|
17510
|
+
sourceText,
|
|
17511
|
+
line: candidate.line,
|
|
17512
|
+
numContextLines: context.numContextLines,
|
|
17513
|
+
matchKind: candidate.matchKind,
|
|
17514
|
+
calledSymbol: candidate.calledSymbol,
|
|
17515
|
+
confidence: candidate.confidence ?? 0.8
|
|
17516
|
+
});
|
|
17517
|
+
const dedupeKey = `${snippet.filePath}:${snippet.startLine}:${snippet.endLine}:${snippet.matchKind}:${snippet.calledSymbol ?? ""}`;
|
|
17518
|
+
const packageSnippetKeys = state.snippetKeyMap.get(candidate.packageKey);
|
|
17519
|
+
if (!packageSnippetKeys || packageSnippetKeys.has(dedupeKey)) return;
|
|
17520
|
+
packageSnippetKeys.add(dedupeKey);
|
|
17521
|
+
packageResult.snippets.push(snippet);
|
|
17522
|
+
state.snippetsProduced += 1;
|
|
17523
|
+
}
|
|
17524
|
+
function extensionOf2(fileName) {
|
|
17525
|
+
const index = fileName.lastIndexOf(".");
|
|
17526
|
+
return index === -1 ? "" : fileName.slice(index).toLowerCase();
|
|
17527
|
+
}
|
|
17528
|
+
function basePackageName(name) {
|
|
17529
|
+
const slash = name.includes("/") ? name.split("/").at(-1) ?? name : name;
|
|
17530
|
+
const colon = slash.includes(":") ? slash.split(":").at(-1) ?? slash : slash;
|
|
17531
|
+
return colon;
|
|
17532
|
+
}
|
|
17533
|
+
function toIdentifierToken(value) {
|
|
17534
|
+
return value.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
17535
|
+
}
|
|
17536
|
+
function uniqueTokens(values) {
|
|
17537
|
+
const result = [];
|
|
17538
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17539
|
+
for (const value of values) {
|
|
17540
|
+
const trimmed = value.trim();
|
|
17541
|
+
if (!trimmed) continue;
|
|
17542
|
+
const key = trimmed.toLowerCase();
|
|
17543
|
+
if (seen.has(key)) continue;
|
|
17544
|
+
seen.add(key);
|
|
17545
|
+
result.push(trimmed);
|
|
17546
|
+
}
|
|
17547
|
+
return result;
|
|
17548
|
+
}
|
|
17549
|
+
|
|
17550
|
+
// src/context/analyzers/python-ast-analyzer.ts
|
|
17551
|
+
var PYTHON_EXTENSIONS = /* @__PURE__ */ new Set([".py"]);
|
|
17552
|
+
var PythonAstAnalyzer = class {
|
|
17553
|
+
name = "python-ast-analyzer";
|
|
17554
|
+
ecosystems = /* @__PURE__ */ new Set(["pip", "poetry", "uv"]);
|
|
17373
17555
|
supports(ecosystem) {
|
|
17374
17556
|
return this.ecosystems.has(ecosystem);
|
|
17375
17557
|
}
|
|
17376
17558
|
async analyze(context) {
|
|
17377
|
-
const
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17559
|
+
const state = initState(context.packages);
|
|
17560
|
+
const packagePatterns = context.packages.map(
|
|
17561
|
+
(pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
|
|
17562
|
+
);
|
|
17563
|
+
let files;
|
|
17564
|
+
try {
|
|
17565
|
+
files = await collectSourceFiles2(context.projectPath, PYTHON_EXTENSIONS);
|
|
17566
|
+
} catch (err) {
|
|
17567
|
+
return errorResultFromMessage(
|
|
17568
|
+
context,
|
|
17569
|
+
this.name,
|
|
17570
|
+
err instanceof Error ? err.message : String(err),
|
|
17571
|
+
"Failed to enumerate Python source files"
|
|
17572
|
+
);
|
|
17573
|
+
}
|
|
17574
|
+
for (const filePath of files) {
|
|
17575
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) break;
|
|
17576
|
+
const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
|
|
17577
|
+
if (sourceText === null) continue;
|
|
17578
|
+
const aliasToPackage = /* @__PURE__ */ new Map();
|
|
17579
|
+
const lines = sourceText.split(/\r?\n/);
|
|
17580
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
17581
|
+
const line = lines[idx];
|
|
17582
|
+
const trimmed = stripInlineComment(line).trim();
|
|
17583
|
+
const lineNumber = idx + 1;
|
|
17584
|
+
if (!trimmed) continue;
|
|
17585
|
+
const importMatch = trimmed.match(/^import\s+(.+)$/);
|
|
17586
|
+
if (importMatch) {
|
|
17587
|
+
const segments = importMatch[1].split(",").map((part) => part.trim()).filter(Boolean);
|
|
17588
|
+
for (const segment of segments) {
|
|
17589
|
+
const parsed = segment.match(/^([A-Za-z_][A-Za-z0-9_\.]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?$/);
|
|
17590
|
+
if (!parsed) continue;
|
|
17591
|
+
const moduleName = parsed[1];
|
|
17592
|
+
const alias = parsed[2] ?? moduleName.split(".").at(0) ?? moduleName;
|
|
17593
|
+
this.addImportForModule(
|
|
17594
|
+
context,
|
|
17595
|
+
state,
|
|
17596
|
+
filePath,
|
|
17597
|
+
sourceText,
|
|
17598
|
+
packagePatterns,
|
|
17599
|
+
aliasToPackage,
|
|
17600
|
+
moduleName,
|
|
17601
|
+
alias,
|
|
17602
|
+
lineNumber
|
|
17603
|
+
);
|
|
17604
|
+
}
|
|
17605
|
+
continue;
|
|
17606
|
+
}
|
|
17607
|
+
const fromMatch = trimmed.match(
|
|
17608
|
+
/^from\s+([A-Za-z_][A-Za-z0-9_\.]*)\s+import\s+(.+)$/
|
|
17609
|
+
);
|
|
17610
|
+
if (fromMatch) {
|
|
17611
|
+
const moduleName = fromMatch[1];
|
|
17612
|
+
const imported = fromMatch[2].split(",").map((part) => part.trim()).filter(Boolean);
|
|
17613
|
+
for (const part of imported) {
|
|
17614
|
+
const parsed = part.match(/^([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?$/);
|
|
17615
|
+
if (!parsed) continue;
|
|
17616
|
+
const name = parsed[1];
|
|
17617
|
+
const alias = parsed[2] ?? name;
|
|
17618
|
+
this.addImportForModule(
|
|
17619
|
+
context,
|
|
17620
|
+
state,
|
|
17621
|
+
filePath,
|
|
17622
|
+
sourceText,
|
|
17623
|
+
packagePatterns,
|
|
17624
|
+
aliasToPackage,
|
|
17625
|
+
moduleName,
|
|
17626
|
+
alias,
|
|
17627
|
+
lineNumber
|
|
17628
|
+
);
|
|
17629
|
+
}
|
|
17630
|
+
continue;
|
|
17631
|
+
}
|
|
17632
|
+
for (const [alias, pkgKey] of aliasToPackage.entries()) {
|
|
17633
|
+
const escapedAlias = escapeRegex(alias);
|
|
17634
|
+
const directCall = new RegExp(`\\b${escapedAlias}\\s*\\(`);
|
|
17635
|
+
if (directCall.test(trimmed)) {
|
|
17636
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17637
|
+
packageKey: pkgKey,
|
|
17638
|
+
line: lineNumber,
|
|
17639
|
+
matchKind: "call",
|
|
17640
|
+
calledSymbol: alias,
|
|
17641
|
+
confidence: 0.78
|
|
17642
|
+
});
|
|
17643
|
+
}
|
|
17644
|
+
const memberCall = new RegExp(`\\b${escapedAlias}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`);
|
|
17645
|
+
const match = trimmed.match(memberCall);
|
|
17646
|
+
if (match) {
|
|
17647
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17648
|
+
packageKey: pkgKey,
|
|
17649
|
+
line: lineNumber,
|
|
17650
|
+
matchKind: "call",
|
|
17651
|
+
calledSymbol: `${alias}.${match[1]}`,
|
|
17652
|
+
confidence: 0.8
|
|
17653
|
+
});
|
|
17654
|
+
}
|
|
17655
|
+
}
|
|
17656
|
+
}
|
|
17657
|
+
}
|
|
17658
|
+
return toAnalyzerResult(state);
|
|
17659
|
+
}
|
|
17660
|
+
addImportForModule(context, state, filePath, sourceText, packagePatterns, aliasToPackage, moduleName, alias, lineNumber) {
|
|
17661
|
+
const normalizedModule = moduleName.toLowerCase();
|
|
17662
|
+
for (const pkg2 of packagePatterns) {
|
|
17663
|
+
const matched = pkg2.modules.some(
|
|
17664
|
+
(candidate) => normalizedModule === candidate || normalizedModule.startsWith(`${candidate}.`)
|
|
17665
|
+
);
|
|
17666
|
+
if (!matched) continue;
|
|
17667
|
+
aliasToPackage.set(alias, pkg2.key);
|
|
17668
|
+
aliasToPackage.set(moduleName.split(".").at(0) ?? moduleName, pkg2.key);
|
|
17669
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17670
|
+
packageKey: pkg2.key,
|
|
17671
|
+
line: lineNumber,
|
|
17672
|
+
matchKind: "import",
|
|
17673
|
+
confidence: 0.95
|
|
17674
|
+
});
|
|
17675
|
+
}
|
|
17676
|
+
}
|
|
17677
|
+
patternForPackage(packageName, ecosystem) {
|
|
17678
|
+
const normalized = toIdentifierToken(packageName).replace(/_/g, "-");
|
|
17679
|
+
const base = toIdentifierToken(basePackageName(packageName));
|
|
17680
|
+
const packageModules = [
|
|
17681
|
+
normalized.replace(/-/g, "_"),
|
|
17682
|
+
base.replace(/-/g, "_"),
|
|
17683
|
+
base.replace(/_/g, "")
|
|
17684
|
+
];
|
|
17685
|
+
if (normalized === "pyyaml" || base === "pyyaml") {
|
|
17686
|
+
packageModules.push("yaml");
|
|
17687
|
+
}
|
|
17688
|
+
return {
|
|
17689
|
+
key: packageKey(ecosystem, packageName),
|
|
17690
|
+
modules: uniqueTokens(packageModules.map((v) => v.toLowerCase()))
|
|
17691
|
+
};
|
|
17692
|
+
}
|
|
17693
|
+
};
|
|
17694
|
+
function stripInlineComment(line) {
|
|
17695
|
+
const hashIndex = line.indexOf("#");
|
|
17696
|
+
return hashIndex === -1 ? line : line.slice(0, hashIndex);
|
|
17697
|
+
}
|
|
17698
|
+
function escapeRegex(value) {
|
|
17699
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17700
|
+
}
|
|
17701
|
+
|
|
17702
|
+
// src/context/analyzers/java-ast-analyzer.ts
|
|
17703
|
+
var JAVA_EXTENSIONS = /* @__PURE__ */ new Set([".java"]);
|
|
17704
|
+
var JavaAstAnalyzer = class {
|
|
17705
|
+
name = "java-ast-analyzer";
|
|
17706
|
+
ecosystems = /* @__PURE__ */ new Set(["maven"]);
|
|
17707
|
+
supports(ecosystem) {
|
|
17708
|
+
return this.ecosystems.has(ecosystem);
|
|
17709
|
+
}
|
|
17710
|
+
async analyze(context) {
|
|
17711
|
+
const state = initState(context.packages);
|
|
17712
|
+
const packagePatterns = context.packages.map(
|
|
17713
|
+
(pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
|
|
17714
|
+
);
|
|
17715
|
+
let files;
|
|
17716
|
+
try {
|
|
17717
|
+
files = await collectSourceFiles2(context.projectPath, JAVA_EXTENSIONS);
|
|
17718
|
+
} catch (err) {
|
|
17719
|
+
return errorResultFromMessage(
|
|
17720
|
+
context,
|
|
17721
|
+
this.name,
|
|
17722
|
+
err instanceof Error ? err.message : String(err),
|
|
17723
|
+
"Failed to enumerate Java source files"
|
|
17724
|
+
);
|
|
17725
|
+
}
|
|
17726
|
+
for (const filePath of files) {
|
|
17727
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) break;
|
|
17728
|
+
const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
|
|
17729
|
+
if (sourceText === null) continue;
|
|
17730
|
+
const lines = sourceText.split(/\r?\n/);
|
|
17731
|
+
const symbolToPackage = /* @__PURE__ */ new Map();
|
|
17732
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
17733
|
+
const line = stripLineComment(lines[idx]).trim();
|
|
17734
|
+
const lineNumber = idx + 1;
|
|
17735
|
+
if (!line) continue;
|
|
17736
|
+
const importMatch = line.match(/^import\s+(?:static\s+)?([A-Za-z0-9_.*]+)\s*;/);
|
|
17737
|
+
if (importMatch) {
|
|
17738
|
+
const importPath = importMatch[1].replace(/\.\*$/, "");
|
|
17739
|
+
const simpleName = importPath.split(".").at(-1) ?? importPath;
|
|
17740
|
+
for (const pkg2 of packagePatterns) {
|
|
17741
|
+
if (!pkg2.candidates.some((candidate) => importPath.startsWith(candidate))) continue;
|
|
17742
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17743
|
+
packageKey: pkg2.key,
|
|
17744
|
+
line: lineNumber,
|
|
17745
|
+
matchKind: "import",
|
|
17746
|
+
confidence: 0.94
|
|
17747
|
+
});
|
|
17748
|
+
if (simpleName && /^[A-Za-z_][A-Za-z0-9_]*$/.test(simpleName)) {
|
|
17749
|
+
symbolToPackage.set(simpleName, pkg2.key);
|
|
17750
|
+
}
|
|
17751
|
+
}
|
|
17752
|
+
continue;
|
|
17753
|
+
}
|
|
17754
|
+
const varDecl = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=|;)/);
|
|
17755
|
+
if (varDecl) {
|
|
17756
|
+
const typeName = varDecl[1];
|
|
17757
|
+
const variableName = varDecl[2];
|
|
17758
|
+
const pkgKey2 = symbolToPackage.get(typeName);
|
|
17759
|
+
if (pkgKey2) {
|
|
17760
|
+
symbolToPackage.set(variableName, pkgKey2);
|
|
17761
|
+
}
|
|
17762
|
+
}
|
|
17763
|
+
const callMatch = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
17764
|
+
if (!callMatch) continue;
|
|
17765
|
+
const lhs = callMatch[1];
|
|
17766
|
+
const member = callMatch[2];
|
|
17767
|
+
const pkgKey = symbolToPackage.get(lhs);
|
|
17768
|
+
if (!pkgKey) continue;
|
|
17769
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17770
|
+
packageKey: pkgKey,
|
|
17771
|
+
line: lineNumber,
|
|
17772
|
+
matchKind: "call",
|
|
17773
|
+
calledSymbol: `${lhs}.${member}`,
|
|
17774
|
+
confidence: 0.8
|
|
17775
|
+
});
|
|
17776
|
+
}
|
|
17777
|
+
}
|
|
17778
|
+
return toAnalyzerResult(state);
|
|
17779
|
+
}
|
|
17780
|
+
patternForPackage(packageName, ecosystem) {
|
|
17781
|
+
const [groupIdRaw, artifactIdRaw] = packageName.split(":");
|
|
17782
|
+
const groupId = (groupIdRaw ?? "").trim();
|
|
17783
|
+
const artifactId = (artifactIdRaw ?? "").trim();
|
|
17784
|
+
const candidates = uniqueTokens([
|
|
17785
|
+
groupId,
|
|
17786
|
+
groupId && artifactId ? `${groupId}.${artifactId.replace(/-/g, ".")}` : "",
|
|
17787
|
+
artifactId ? artifactId.replace(/-/g, ".") : "",
|
|
17788
|
+
artifactId ? toIdentifierToken(artifactId).replace(/_/g, ".") : ""
|
|
17789
|
+
]);
|
|
17384
17790
|
return {
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
snippetsProduced: 0
|
|
17791
|
+
key: packageKey(ecosystem, packageName),
|
|
17792
|
+
candidates
|
|
17388
17793
|
};
|
|
17389
17794
|
}
|
|
17390
17795
|
};
|
|
17796
|
+
function stripLineComment(line) {
|
|
17797
|
+
const idx = line.indexOf("//");
|
|
17798
|
+
return idx === -1 ? line : line.slice(0, idx);
|
|
17799
|
+
}
|
|
17800
|
+
|
|
17801
|
+
// src/context/analyzers/dotnet-ast-analyzer.ts
|
|
17802
|
+
var CSHARP_EXTENSIONS = /* @__PURE__ */ new Set([".cs"]);
|
|
17803
|
+
var DotnetAstAnalyzer = class {
|
|
17804
|
+
name = "dotnet-ast-analyzer";
|
|
17805
|
+
ecosystems = /* @__PURE__ */ new Set(["nuget"]);
|
|
17806
|
+
supports(ecosystem) {
|
|
17807
|
+
return this.ecosystems.has(ecosystem);
|
|
17808
|
+
}
|
|
17809
|
+
async analyze(context) {
|
|
17810
|
+
const state = initState(context.packages);
|
|
17811
|
+
const packagePatterns = context.packages.map(
|
|
17812
|
+
(pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
|
|
17813
|
+
);
|
|
17814
|
+
let files;
|
|
17815
|
+
try {
|
|
17816
|
+
files = await collectSourceFiles2(context.projectPath, CSHARP_EXTENSIONS);
|
|
17817
|
+
} catch (err) {
|
|
17818
|
+
return errorResultFromMessage(
|
|
17819
|
+
context,
|
|
17820
|
+
this.name,
|
|
17821
|
+
err instanceof Error ? err.message : String(err),
|
|
17822
|
+
"Failed to enumerate C# source files"
|
|
17823
|
+
);
|
|
17824
|
+
}
|
|
17825
|
+
for (const filePath of files) {
|
|
17826
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) break;
|
|
17827
|
+
const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
|
|
17828
|
+
if (sourceText === null) continue;
|
|
17829
|
+
const lines = sourceText.split(/\r?\n/);
|
|
17830
|
+
const symbolToPackage = /* @__PURE__ */ new Map();
|
|
17831
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
17832
|
+
const line = stripLineComment2(lines[idx]).trim();
|
|
17833
|
+
const lineNumber = idx + 1;
|
|
17834
|
+
if (!line) continue;
|
|
17835
|
+
const aliasUsing = line.match(/^using\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([A-Za-z0-9_.]+)\s*;/);
|
|
17836
|
+
if (aliasUsing) {
|
|
17837
|
+
const alias = aliasUsing[1];
|
|
17838
|
+
const targetNamespace = aliasUsing[2];
|
|
17839
|
+
for (const pkg2 of packagePatterns) {
|
|
17840
|
+
if (!pkg2.namespaceCandidates.some((candidate) => targetNamespace.startsWith(candidate))) continue;
|
|
17841
|
+
symbolToPackage.set(alias, pkg2.key);
|
|
17842
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17843
|
+
packageKey: pkg2.key,
|
|
17844
|
+
line: lineNumber,
|
|
17845
|
+
matchKind: "import",
|
|
17846
|
+
confidence: 0.93
|
|
17847
|
+
});
|
|
17848
|
+
}
|
|
17849
|
+
continue;
|
|
17850
|
+
}
|
|
17851
|
+
const usingMatch = line.match(/^using\s+([A-Za-z0-9_.]+)\s*;/);
|
|
17852
|
+
if (usingMatch) {
|
|
17853
|
+
const namespaceName = usingMatch[1];
|
|
17854
|
+
const tailSymbol = namespaceName.split(".").at(-1) ?? namespaceName;
|
|
17855
|
+
for (const pkg2 of packagePatterns) {
|
|
17856
|
+
if (!pkg2.namespaceCandidates.some((candidate) => namespaceName.startsWith(candidate))) continue;
|
|
17857
|
+
symbolToPackage.set(tailSymbol, pkg2.key);
|
|
17858
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17859
|
+
packageKey: pkg2.key,
|
|
17860
|
+
line: lineNumber,
|
|
17861
|
+
matchKind: "import",
|
|
17862
|
+
confidence: 0.93
|
|
17863
|
+
});
|
|
17864
|
+
}
|
|
17865
|
+
continue;
|
|
17866
|
+
}
|
|
17867
|
+
const varDecl = line.match(/^([A-Za-z_][A-Za-z0-9_.]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=|;)/);
|
|
17868
|
+
if (varDecl) {
|
|
17869
|
+
const typeName = varDecl[1].split(".").at(-1) ?? varDecl[1];
|
|
17870
|
+
const variableName = varDecl[2];
|
|
17871
|
+
const pkgKey = symbolToPackage.get(typeName);
|
|
17872
|
+
if (pkgKey) {
|
|
17873
|
+
symbolToPackage.set(variableName, pkgKey);
|
|
17874
|
+
}
|
|
17875
|
+
}
|
|
17876
|
+
const memberCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
17877
|
+
if (memberCall) {
|
|
17878
|
+
const lhs = memberCall[1];
|
|
17879
|
+
const method = memberCall[2];
|
|
17880
|
+
const pkgKey = symbolToPackage.get(lhs) ?? this.findSymbolCandidatePackage(lhs, packagePatterns);
|
|
17881
|
+
if (pkgKey) {
|
|
17882
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17883
|
+
packageKey: pkgKey,
|
|
17884
|
+
line: lineNumber,
|
|
17885
|
+
matchKind: "call",
|
|
17886
|
+
calledSymbol: `${lhs}.${method}`,
|
|
17887
|
+
confidence: 0.79
|
|
17888
|
+
});
|
|
17889
|
+
}
|
|
17890
|
+
}
|
|
17891
|
+
}
|
|
17892
|
+
}
|
|
17893
|
+
return toAnalyzerResult(state);
|
|
17894
|
+
}
|
|
17895
|
+
patternForPackage(packageName, ecosystem) {
|
|
17896
|
+
const dotted = packageName.replace(/-/g, ".");
|
|
17897
|
+
const segments = dotted.split(".");
|
|
17898
|
+
const symbolCandidates = uniqueTokens([
|
|
17899
|
+
segments.at(-1) ?? "",
|
|
17900
|
+
segments.at(-2) ?? "",
|
|
17901
|
+
packageName.split(".").at(-1) ?? ""
|
|
17902
|
+
]);
|
|
17903
|
+
const namespaceCandidates = uniqueTokens([
|
|
17904
|
+
dotted,
|
|
17905
|
+
segments.slice(0, -1).join("."),
|
|
17906
|
+
segments.slice(0, Math.min(2, segments.length)).join(".")
|
|
17907
|
+
]);
|
|
17908
|
+
return {
|
|
17909
|
+
key: packageKey(ecosystem, packageName),
|
|
17910
|
+
namespaceCandidates,
|
|
17911
|
+
symbolCandidates
|
|
17912
|
+
};
|
|
17913
|
+
}
|
|
17914
|
+
findSymbolCandidatePackage(symbol, packagePatterns) {
|
|
17915
|
+
for (const pkg2 of packagePatterns) {
|
|
17916
|
+
if (pkg2.symbolCandidates.includes(symbol)) {
|
|
17917
|
+
return pkg2.key;
|
|
17918
|
+
}
|
|
17919
|
+
}
|
|
17920
|
+
return null;
|
|
17921
|
+
}
|
|
17922
|
+
};
|
|
17923
|
+
function stripLineComment2(line) {
|
|
17924
|
+
const idx = line.indexOf("//");
|
|
17925
|
+
return idx === -1 ? line : line.slice(0, idx);
|
|
17926
|
+
}
|
|
17927
|
+
|
|
17928
|
+
// src/context/analyzers/rust-ast-analyzer.ts
|
|
17929
|
+
var RUST_EXTENSIONS = /* @__PURE__ */ new Set([".rs"]);
|
|
17930
|
+
var RustAstAnalyzer = class {
|
|
17931
|
+
name = "rust-ast-analyzer";
|
|
17932
|
+
ecosystems = /* @__PURE__ */ new Set(["cargo"]);
|
|
17933
|
+
supports(ecosystem) {
|
|
17934
|
+
return this.ecosystems.has(ecosystem);
|
|
17935
|
+
}
|
|
17936
|
+
async analyze(context) {
|
|
17937
|
+
const state = initState(context.packages);
|
|
17938
|
+
const packagePatterns = context.packages.map(
|
|
17939
|
+
(pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
|
|
17940
|
+
);
|
|
17941
|
+
let files;
|
|
17942
|
+
try {
|
|
17943
|
+
files = await collectSourceFiles2(context.projectPath, RUST_EXTENSIONS);
|
|
17944
|
+
} catch (err) {
|
|
17945
|
+
return errorResultFromMessage(
|
|
17946
|
+
context,
|
|
17947
|
+
this.name,
|
|
17948
|
+
err instanceof Error ? err.message : String(err),
|
|
17949
|
+
"Failed to enumerate Rust source files"
|
|
17950
|
+
);
|
|
17951
|
+
}
|
|
17952
|
+
for (const filePath of files) {
|
|
17953
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) break;
|
|
17954
|
+
const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
|
|
17955
|
+
if (sourceText === null) continue;
|
|
17956
|
+
const lines = sourceText.split(/\r?\n/);
|
|
17957
|
+
const symbolToPackage = /* @__PURE__ */ new Map();
|
|
17958
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
17959
|
+
const line = stripLineComment3(lines[idx]).trim();
|
|
17960
|
+
const lineNumber = idx + 1;
|
|
17961
|
+
if (!line) continue;
|
|
17962
|
+
const useMatch = line.match(/^use\s+([A-Za-z_][A-Za-z0-9_:]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/);
|
|
17963
|
+
if (useMatch) {
|
|
17964
|
+
const pathExpr = useMatch[1];
|
|
17965
|
+
const alias = useMatch[2] ?? pathExpr.split("::").at(0) ?? pathExpr;
|
|
17966
|
+
const crate = pathExpr.split("::").at(0) ?? pathExpr;
|
|
17967
|
+
for (const pkg2 of packagePatterns) {
|
|
17968
|
+
if (!pkg2.crateNames.includes(crate)) continue;
|
|
17969
|
+
symbolToPackage.set(alias, pkg2.key);
|
|
17970
|
+
symbolToPackage.set(crate, pkg2.key);
|
|
17971
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17972
|
+
packageKey: pkg2.key,
|
|
17973
|
+
line: lineNumber,
|
|
17974
|
+
matchKind: "import",
|
|
17975
|
+
confidence: 0.94
|
|
17976
|
+
});
|
|
17977
|
+
}
|
|
17978
|
+
continue;
|
|
17979
|
+
}
|
|
17980
|
+
const externMatch = line.match(/^extern\s+crate\s+([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/);
|
|
17981
|
+
if (externMatch) {
|
|
17982
|
+
const crate = externMatch[1];
|
|
17983
|
+
const alias = externMatch[2] ?? crate;
|
|
17984
|
+
for (const pkg2 of packagePatterns) {
|
|
17985
|
+
if (!pkg2.crateNames.includes(crate)) continue;
|
|
17986
|
+
symbolToPackage.set(alias, pkg2.key);
|
|
17987
|
+
symbolToPackage.set(crate, pkg2.key);
|
|
17988
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
17989
|
+
packageKey: pkg2.key,
|
|
17990
|
+
line: lineNumber,
|
|
17991
|
+
matchKind: "import",
|
|
17992
|
+
confidence: 0.94
|
|
17993
|
+
});
|
|
17994
|
+
}
|
|
17995
|
+
continue;
|
|
17996
|
+
}
|
|
17997
|
+
const scopedCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)::([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
17998
|
+
if (scopedCall) {
|
|
17999
|
+
const lhs = scopedCall[1];
|
|
18000
|
+
const func = scopedCall[2];
|
|
18001
|
+
const pkgKey = symbolToPackage.get(lhs);
|
|
18002
|
+
if (pkgKey) {
|
|
18003
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18004
|
+
packageKey: pkgKey,
|
|
18005
|
+
line: lineNumber,
|
|
18006
|
+
matchKind: "call",
|
|
18007
|
+
calledSymbol: `${lhs}::${func}`,
|
|
18008
|
+
confidence: 0.8
|
|
18009
|
+
});
|
|
18010
|
+
}
|
|
18011
|
+
}
|
|
18012
|
+
const methodCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
18013
|
+
if (methodCall) {
|
|
18014
|
+
const lhs = methodCall[1];
|
|
18015
|
+
const method = methodCall[2];
|
|
18016
|
+
const pkgKey = symbolToPackage.get(lhs);
|
|
18017
|
+
if (pkgKey) {
|
|
18018
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18019
|
+
packageKey: pkgKey,
|
|
18020
|
+
line: lineNumber,
|
|
18021
|
+
matchKind: "call",
|
|
18022
|
+
calledSymbol: `${lhs}.${method}`,
|
|
18023
|
+
confidence: 0.78
|
|
18024
|
+
});
|
|
18025
|
+
}
|
|
18026
|
+
}
|
|
18027
|
+
}
|
|
18028
|
+
}
|
|
18029
|
+
return toAnalyzerResult(state);
|
|
18030
|
+
}
|
|
18031
|
+
patternForPackage(packageName, ecosystem) {
|
|
18032
|
+
const crateName = packageName.replace(/-/g, "_");
|
|
18033
|
+
return {
|
|
18034
|
+
key: packageKey(ecosystem, packageName),
|
|
18035
|
+
crateNames: uniqueTokens([crateName, packageName])
|
|
18036
|
+
};
|
|
18037
|
+
}
|
|
18038
|
+
};
|
|
18039
|
+
function stripLineComment3(line) {
|
|
18040
|
+
const idx = line.indexOf("//");
|
|
18041
|
+
return idx === -1 ? line : line.slice(0, idx);
|
|
18042
|
+
}
|
|
18043
|
+
|
|
18044
|
+
// src/context/analyzers/go-ast-analyzer.ts
|
|
18045
|
+
var GO_EXTENSIONS = /* @__PURE__ */ new Set([".go"]);
|
|
18046
|
+
var GoAstAnalyzer = class {
|
|
18047
|
+
name = "go-ast-analyzer";
|
|
18048
|
+
ecosystems = /* @__PURE__ */ new Set(["go"]);
|
|
18049
|
+
supports(ecosystem) {
|
|
18050
|
+
return this.ecosystems.has(ecosystem);
|
|
18051
|
+
}
|
|
18052
|
+
async analyze(context) {
|
|
18053
|
+
const state = initState(context.packages);
|
|
18054
|
+
const packagePatterns = context.packages.map((pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem));
|
|
18055
|
+
let files;
|
|
18056
|
+
try {
|
|
18057
|
+
files = await collectSourceFiles2(context.projectPath, GO_EXTENSIONS);
|
|
18058
|
+
} catch (err) {
|
|
18059
|
+
return errorResultFromMessage(
|
|
18060
|
+
context,
|
|
18061
|
+
this.name,
|
|
18062
|
+
err instanceof Error ? err.message : String(err),
|
|
18063
|
+
"Failed to enumerate Go source files"
|
|
18064
|
+
);
|
|
18065
|
+
}
|
|
18066
|
+
for (const filePath of files) {
|
|
18067
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) break;
|
|
18068
|
+
const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
|
|
18069
|
+
if (sourceText === null) continue;
|
|
18070
|
+
const aliasesByPackage = /* @__PURE__ */ new Map();
|
|
18071
|
+
for (const pkg2 of packagePatterns) {
|
|
18072
|
+
aliasesByPackage.set(pkg2.key, new Set(pkg2.aliases));
|
|
18073
|
+
}
|
|
18074
|
+
const lines = sourceText.split(/\r?\n/);
|
|
18075
|
+
let inImportBlock = false;
|
|
18076
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
18077
|
+
const lineNumber = idx + 1;
|
|
18078
|
+
const line = lines[idx];
|
|
18079
|
+
const trimmed = line.trim();
|
|
18080
|
+
if (trimmed.startsWith("import (")) {
|
|
18081
|
+
inImportBlock = true;
|
|
18082
|
+
continue;
|
|
18083
|
+
}
|
|
18084
|
+
if (inImportBlock && trimmed === ")") {
|
|
18085
|
+
inImportBlock = false;
|
|
18086
|
+
continue;
|
|
18087
|
+
}
|
|
18088
|
+
const match = this.extractImport(trimmed, inImportBlock);
|
|
18089
|
+
if (match) {
|
|
18090
|
+
for (const pkg2 of packagePatterns) {
|
|
18091
|
+
if (match.importPath !== pkg2.packageName) continue;
|
|
18092
|
+
const alias = match.alias && match.alias !== "_" && match.alias !== "." ? toIdentifierToken(match.alias) : pkg2.aliases[0];
|
|
18093
|
+
if (alias) {
|
|
18094
|
+
aliasesByPackage.get(pkg2.key)?.add(alias);
|
|
18095
|
+
}
|
|
18096
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18097
|
+
packageKey: pkg2.key,
|
|
18098
|
+
line: lineNumber,
|
|
18099
|
+
matchKind: "import",
|
|
18100
|
+
confidence: 0.95
|
|
18101
|
+
});
|
|
18102
|
+
}
|
|
18103
|
+
}
|
|
18104
|
+
for (const pkg2 of packagePatterns) {
|
|
18105
|
+
const aliases = aliasesByPackage.get(pkg2.key);
|
|
18106
|
+
if (!aliases) continue;
|
|
18107
|
+
for (const alias of aliases) {
|
|
18108
|
+
if (!alias) continue;
|
|
18109
|
+
const escapedAlias = escapeRegex2(alias);
|
|
18110
|
+
const callRegex = new RegExp(`\\b${escapedAlias}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`);
|
|
18111
|
+
const callMatch = line.match(callRegex);
|
|
18112
|
+
if (!callMatch) continue;
|
|
18113
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18114
|
+
packageKey: pkg2.key,
|
|
18115
|
+
line: lineNumber,
|
|
18116
|
+
matchKind: "call",
|
|
18117
|
+
calledSymbol: `${alias}.${callMatch[1]}`,
|
|
18118
|
+
confidence: 0.8
|
|
18119
|
+
});
|
|
18120
|
+
}
|
|
18121
|
+
}
|
|
18122
|
+
}
|
|
18123
|
+
}
|
|
18124
|
+
return toAnalyzerResult(state);
|
|
18125
|
+
}
|
|
18126
|
+
patternForPackage(packageName, ecosystem) {
|
|
18127
|
+
const baseName = basePackageName(packageName);
|
|
18128
|
+
const identifierBase = toIdentifierToken(baseName.replace(/^v[0-9]+$/, ""));
|
|
18129
|
+
const shortened = identifierBase.replace(/go$/i, "") || identifierBase;
|
|
18130
|
+
return {
|
|
18131
|
+
key: packageKey(ecosystem, packageName),
|
|
18132
|
+
packageName,
|
|
18133
|
+
aliases: uniqueTokens([identifierBase, shortened])
|
|
18134
|
+
};
|
|
18135
|
+
}
|
|
18136
|
+
extractImport(trimmedLine, inImportBlock) {
|
|
18137
|
+
if (!inImportBlock && !trimmedLine.startsWith("import ")) return null;
|
|
18138
|
+
if (inImportBlock) {
|
|
18139
|
+
const blockMatch = trimmedLine.match(/^(?:(\.|_|[A-Za-z_][A-Za-z0-9_]*)\s+)?\"([^\"]+)\"/);
|
|
18140
|
+
if (!blockMatch) return null;
|
|
18141
|
+
return {
|
|
18142
|
+
alias: blockMatch[1] ?? null,
|
|
18143
|
+
importPath: blockMatch[2]
|
|
18144
|
+
};
|
|
18145
|
+
}
|
|
18146
|
+
const singleMatch = trimmedLine.match(/^import\s+(?:(\.|_|[A-Za-z_][A-Za-z0-9_]*)\s+)?\"([^\"]+)\"/);
|
|
18147
|
+
if (!singleMatch) return null;
|
|
18148
|
+
return {
|
|
18149
|
+
alias: singleMatch[1] ?? null,
|
|
18150
|
+
importPath: singleMatch[2]
|
|
18151
|
+
};
|
|
18152
|
+
}
|
|
18153
|
+
};
|
|
18154
|
+
function escapeRegex2(value) {
|
|
18155
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18156
|
+
}
|
|
18157
|
+
|
|
18158
|
+
// src/context/analyzers/ruby-ast-analyzer.ts
|
|
18159
|
+
var RUBY_EXTENSIONS = /* @__PURE__ */ new Set([".rb"]);
|
|
18160
|
+
var RubyAstAnalyzer = class {
|
|
18161
|
+
name = "ruby-ast-analyzer";
|
|
18162
|
+
ecosystems = /* @__PURE__ */ new Set(["ruby"]);
|
|
18163
|
+
supports(ecosystem) {
|
|
18164
|
+
return this.ecosystems.has(ecosystem);
|
|
18165
|
+
}
|
|
18166
|
+
async analyze(context) {
|
|
18167
|
+
const state = initState(context.packages);
|
|
18168
|
+
const packagePatterns = context.packages.map(
|
|
18169
|
+
(pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
|
|
18170
|
+
);
|
|
18171
|
+
let files;
|
|
18172
|
+
try {
|
|
18173
|
+
files = await collectSourceFiles2(context.projectPath, RUBY_EXTENSIONS);
|
|
18174
|
+
} catch (err) {
|
|
18175
|
+
return errorResultFromMessage(
|
|
18176
|
+
context,
|
|
18177
|
+
this.name,
|
|
18178
|
+
err instanceof Error ? err.message : String(err),
|
|
18179
|
+
"Failed to enumerate Ruby source files"
|
|
18180
|
+
);
|
|
18181
|
+
}
|
|
18182
|
+
for (const filePath of files) {
|
|
18183
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) break;
|
|
18184
|
+
const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
|
|
18185
|
+
if (sourceText === null) continue;
|
|
18186
|
+
const lines = sourceText.split(/\r?\n/);
|
|
18187
|
+
const symbolToPackage = /* @__PURE__ */ new Map();
|
|
18188
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
18189
|
+
const line = stripInlineComment2(lines[idx]).trim();
|
|
18190
|
+
const lineNumber = idx + 1;
|
|
18191
|
+
if (!line) continue;
|
|
18192
|
+
const requireMatch = line.match(/^require(?:_relative)?\s+['"]([^'"]+)['"]/);
|
|
18193
|
+
if (requireMatch) {
|
|
18194
|
+
const requiredPath = requireMatch[1];
|
|
18195
|
+
for (const pkg2 of packagePatterns) {
|
|
18196
|
+
if (!pkg2.requireCandidates.some((candidate) => requiredPath.includes(candidate))) continue;
|
|
18197
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18198
|
+
packageKey: pkg2.key,
|
|
18199
|
+
line: lineNumber,
|
|
18200
|
+
matchKind: "require",
|
|
18201
|
+
confidence: 0.93
|
|
18202
|
+
});
|
|
18203
|
+
for (const constant of pkg2.constantCandidates) {
|
|
18204
|
+
symbolToPackage.set(constant, pkg2.key);
|
|
18205
|
+
}
|
|
18206
|
+
}
|
|
18207
|
+
continue;
|
|
18208
|
+
}
|
|
18209
|
+
const includeMatch = line.match(/^include\s+([A-Za-z_][A-Za-z0-9_:]*)/);
|
|
18210
|
+
if (includeMatch) {
|
|
18211
|
+
const moduleName = includeMatch[1];
|
|
18212
|
+
for (const pkg2 of packagePatterns) {
|
|
18213
|
+
if (!pkg2.constantCandidates.some((candidate) => moduleName.startsWith(candidate))) continue;
|
|
18214
|
+
symbolToPackage.set(moduleName.split("::").at(0) ?? moduleName, pkg2.key);
|
|
18215
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18216
|
+
packageKey: pkg2.key,
|
|
18217
|
+
line: lineNumber,
|
|
18218
|
+
matchKind: "import",
|
|
18219
|
+
confidence: 0.9
|
|
18220
|
+
});
|
|
18221
|
+
}
|
|
18222
|
+
continue;
|
|
18223
|
+
}
|
|
18224
|
+
const namespacedCall = line.match(/\b([A-Z][A-Za-z0-9_:]*)\s*(?:\.|::)\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
18225
|
+
if (namespacedCall) {
|
|
18226
|
+
const lhs = namespacedCall[1].split("::").at(0) ?? namespacedCall[1];
|
|
18227
|
+
const method = namespacedCall[2];
|
|
18228
|
+
const pkgKey = symbolToPackage.get(lhs) ?? this.findConstantCandidatePackage(lhs, packagePatterns);
|
|
18229
|
+
if (pkgKey) {
|
|
18230
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18231
|
+
packageKey: pkgKey,
|
|
18232
|
+
line: lineNumber,
|
|
18233
|
+
matchKind: "call",
|
|
18234
|
+
calledSymbol: `${lhs}.${method}`,
|
|
18235
|
+
confidence: 0.78
|
|
18236
|
+
});
|
|
18237
|
+
}
|
|
18238
|
+
}
|
|
18239
|
+
}
|
|
18240
|
+
}
|
|
18241
|
+
return toAnalyzerResult(state);
|
|
18242
|
+
}
|
|
18243
|
+
patternForPackage(packageName, ecosystem) {
|
|
18244
|
+
const base = basePackageName(packageName);
|
|
18245
|
+
const normalized = base.toLowerCase();
|
|
18246
|
+
const requireCandidates = uniqueTokens([
|
|
18247
|
+
normalized,
|
|
18248
|
+
normalized.replace(/-/g, "/"),
|
|
18249
|
+
normalized.replace(/-/g, "_")
|
|
18250
|
+
]);
|
|
18251
|
+
const constantParts = normalized.replace(/[^a-z0-9_/-]/g, "").split(/[\/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1));
|
|
18252
|
+
const constant = constantParts.join("::");
|
|
18253
|
+
const collapsedConstant = constantParts.join("");
|
|
18254
|
+
return {
|
|
18255
|
+
key: packageKey(ecosystem, packageName),
|
|
18256
|
+
requireCandidates,
|
|
18257
|
+
constantCandidates: uniqueTokens([constant, collapsedConstant, constantParts.at(0) ?? ""])
|
|
18258
|
+
};
|
|
18259
|
+
}
|
|
18260
|
+
findConstantCandidatePackage(constant, packagePatterns) {
|
|
18261
|
+
for (const pkg2 of packagePatterns) {
|
|
18262
|
+
if (pkg2.constantCandidates.includes(constant)) {
|
|
18263
|
+
return pkg2.key;
|
|
18264
|
+
}
|
|
18265
|
+
}
|
|
18266
|
+
return null;
|
|
18267
|
+
}
|
|
18268
|
+
};
|
|
18269
|
+
function stripInlineComment2(line) {
|
|
18270
|
+
const idx = line.indexOf("#");
|
|
18271
|
+
return idx === -1 ? line : line.slice(0, idx);
|
|
18272
|
+
}
|
|
18273
|
+
|
|
18274
|
+
// src/context/analyzers/php-ast-analyzer.ts
|
|
18275
|
+
var PHP_EXTENSIONS = /* @__PURE__ */ new Set([".php"]);
|
|
18276
|
+
var PhpAstAnalyzer = class {
|
|
18277
|
+
name = "php-ast-analyzer";
|
|
18278
|
+
ecosystems = /* @__PURE__ */ new Set(["composer"]);
|
|
18279
|
+
supports(ecosystem) {
|
|
18280
|
+
return this.ecosystems.has(ecosystem);
|
|
18281
|
+
}
|
|
18282
|
+
async analyze(context) {
|
|
18283
|
+
const state = initState(context.packages);
|
|
18284
|
+
const packagePatterns = context.packages.map(
|
|
18285
|
+
(pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
|
|
18286
|
+
);
|
|
18287
|
+
let files;
|
|
18288
|
+
try {
|
|
18289
|
+
files = await collectSourceFiles2(context.projectPath, PHP_EXTENSIONS);
|
|
18290
|
+
} catch (err) {
|
|
18291
|
+
return errorResultFromMessage(
|
|
18292
|
+
context,
|
|
18293
|
+
this.name,
|
|
18294
|
+
err instanceof Error ? err.message : String(err),
|
|
18295
|
+
"Failed to enumerate PHP source files"
|
|
18296
|
+
);
|
|
18297
|
+
}
|
|
18298
|
+
for (const filePath of files) {
|
|
18299
|
+
if (state.snippetsProduced >= context.maxSnippetsTotal) break;
|
|
18300
|
+
const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
|
|
18301
|
+
if (sourceText === null) continue;
|
|
18302
|
+
const lines = sourceText.split(/\r?\n/);
|
|
18303
|
+
const symbolToPackage = /* @__PURE__ */ new Map();
|
|
18304
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
18305
|
+
const line = stripLineComment4(lines[idx]).trim();
|
|
18306
|
+
const lineNumber = idx + 1;
|
|
18307
|
+
if (!line) continue;
|
|
18308
|
+
const useMatch = line.match(/^use\s+([A-Za-z0-9_\\]+)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/i);
|
|
18309
|
+
if (useMatch) {
|
|
18310
|
+
const namespacePath = useMatch[1];
|
|
18311
|
+
const alias = useMatch[2] ?? namespacePath.split("\\").at(-1) ?? namespacePath;
|
|
18312
|
+
for (const pkg2 of packagePatterns) {
|
|
18313
|
+
const normalizedNamespace = namespacePath.toLowerCase();
|
|
18314
|
+
if (!pkg2.namespaceCandidates.some((candidate) => normalizedNamespace.startsWith(candidate.toLowerCase()))) continue;
|
|
18315
|
+
symbolToPackage.set(alias, pkg2.key);
|
|
18316
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18317
|
+
packageKey: pkg2.key,
|
|
18318
|
+
line: lineNumber,
|
|
18319
|
+
matchKind: "import",
|
|
18320
|
+
confidence: 0.94
|
|
18321
|
+
});
|
|
18322
|
+
}
|
|
18323
|
+
continue;
|
|
18324
|
+
}
|
|
18325
|
+
const requireMatch = line.match(/^require(?:_once)?\s*\(?\s*['"]([^'"]+)['"]\s*\)?\s*;/i);
|
|
18326
|
+
if (requireMatch) {
|
|
18327
|
+
const pathExpr = requireMatch[1].toLowerCase();
|
|
18328
|
+
for (const pkg2 of packagePatterns) {
|
|
18329
|
+
const hasVendor = pathExpr.includes(pkg2.vendor.toLowerCase());
|
|
18330
|
+
const hasPackage = pathExpr.includes(pkg2.package.toLowerCase());
|
|
18331
|
+
if (!hasVendor && !hasPackage) continue;
|
|
18332
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18333
|
+
packageKey: pkg2.key,
|
|
18334
|
+
line: lineNumber,
|
|
18335
|
+
matchKind: "require",
|
|
18336
|
+
confidence: 0.9
|
|
18337
|
+
});
|
|
18338
|
+
}
|
|
18339
|
+
continue;
|
|
18340
|
+
}
|
|
18341
|
+
const staticCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)::([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
18342
|
+
if (staticCall) {
|
|
18343
|
+
const lhs = staticCall[1];
|
|
18344
|
+
const method = staticCall[2];
|
|
18345
|
+
const pkgKey = symbolToPackage.get(lhs) ?? this.findSymbolCandidatePackage(lhs, packagePatterns);
|
|
18346
|
+
if (pkgKey) {
|
|
18347
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18348
|
+
packageKey: pkgKey,
|
|
18349
|
+
line: lineNumber,
|
|
18350
|
+
matchKind: "call",
|
|
18351
|
+
calledSymbol: `${lhs}::${method}`,
|
|
18352
|
+
confidence: 0.8
|
|
18353
|
+
});
|
|
18354
|
+
}
|
|
18355
|
+
}
|
|
18356
|
+
const constructorMatch = line.match(/new\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
|
|
18357
|
+
if (constructorMatch) {
|
|
18358
|
+
const className = constructorMatch[1];
|
|
18359
|
+
const pkgKey = symbolToPackage.get(className) ?? this.findSymbolCandidatePackage(className, packagePatterns);
|
|
18360
|
+
if (pkgKey) {
|
|
18361
|
+
addCandidate(context, state, filePath, sourceText, {
|
|
18362
|
+
packageKey: pkgKey,
|
|
18363
|
+
line: lineNumber,
|
|
18364
|
+
matchKind: "call",
|
|
18365
|
+
calledSymbol: `new ${className}`,
|
|
18366
|
+
confidence: 0.76
|
|
18367
|
+
});
|
|
18368
|
+
}
|
|
18369
|
+
}
|
|
18370
|
+
}
|
|
18371
|
+
}
|
|
18372
|
+
return toAnalyzerResult(state);
|
|
18373
|
+
}
|
|
18374
|
+
patternForPackage(packageName, ecosystem) {
|
|
18375
|
+
const [vendorRaw, packageRaw] = packageName.split("/");
|
|
18376
|
+
const vendor = vendorRaw ?? packageName;
|
|
18377
|
+
const packagePart = packageRaw ?? packageName;
|
|
18378
|
+
const namespaceCandidates = uniqueTokens([
|
|
18379
|
+
pascalize(vendor),
|
|
18380
|
+
pascalize(packagePart),
|
|
18381
|
+
`${pascalize(vendor)}\\${pascalize(packagePart)}`,
|
|
18382
|
+
`${pascalize(vendor)}\\${pascalize(packagePart.replace(/-/g, "_"))}`
|
|
18383
|
+
]);
|
|
18384
|
+
const symbolCandidates = uniqueTokens([
|
|
18385
|
+
pascalize(packagePart),
|
|
18386
|
+
pascalize(vendor),
|
|
18387
|
+
pascalize(packagePart).split("\\").at(-1) ?? ""
|
|
18388
|
+
]);
|
|
18389
|
+
return {
|
|
18390
|
+
key: packageKey(ecosystem, packageName),
|
|
18391
|
+
vendor,
|
|
18392
|
+
package: packagePart,
|
|
18393
|
+
namespaceCandidates,
|
|
18394
|
+
symbolCandidates
|
|
18395
|
+
};
|
|
18396
|
+
}
|
|
18397
|
+
findSymbolCandidatePackage(symbol, packagePatterns) {
|
|
18398
|
+
const normalized = symbol.toLowerCase();
|
|
18399
|
+
for (const pkg2 of packagePatterns) {
|
|
18400
|
+
if (pkg2.symbolCandidates.some((candidate) => candidate.toLowerCase() === normalized)) {
|
|
18401
|
+
return pkg2.key;
|
|
18402
|
+
}
|
|
18403
|
+
}
|
|
18404
|
+
return null;
|
|
18405
|
+
}
|
|
18406
|
+
};
|
|
18407
|
+
function pascalize(input) {
|
|
18408
|
+
return input.split(/[\/_.-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("\\");
|
|
18409
|
+
}
|
|
18410
|
+
function stripLineComment4(line) {
|
|
18411
|
+
const slashIdx = line.indexOf("//");
|
|
18412
|
+
if (slashIdx !== -1) return line.slice(0, slashIdx);
|
|
18413
|
+
const hashIdx = line.indexOf("#");
|
|
18414
|
+
return hashIdx === -1 ? line : line.slice(0, hashIdx);
|
|
18415
|
+
}
|
|
17391
18416
|
|
|
17392
18417
|
// src/context/usage-context-engine.ts
|
|
17393
18418
|
var DEFAULT_MAX_SNIPPETS_PER_PACKAGE = 20;
|
|
@@ -17397,41 +18422,13 @@ var UsageContextEngine = class {
|
|
|
17397
18422
|
constructor(analyzers) {
|
|
17398
18423
|
this.analyzers = analyzers ?? [
|
|
17399
18424
|
new JsAstAnalyzer(),
|
|
17400
|
-
new
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
),
|
|
17405
|
-
new
|
|
17406
|
-
|
|
17407
|
-
["maven"],
|
|
17408
|
-
"Java AST analyzer is not yet implemented in this release"
|
|
17409
|
-
),
|
|
17410
|
-
new UnsupportedAnalyzer(
|
|
17411
|
-
"dotnet-ast-analyzer",
|
|
17412
|
-
["nuget"],
|
|
17413
|
-
"NuGet/C# AST analyzer is not yet implemented in this release"
|
|
17414
|
-
),
|
|
17415
|
-
new UnsupportedAnalyzer(
|
|
17416
|
-
"rust-ast-analyzer",
|
|
17417
|
-
["cargo"],
|
|
17418
|
-
"Rust AST analyzer is not yet implemented in this release"
|
|
17419
|
-
),
|
|
17420
|
-
new UnsupportedAnalyzer(
|
|
17421
|
-
"go-ast-analyzer",
|
|
17422
|
-
["go"],
|
|
17423
|
-
"Go AST analyzer is not yet implemented in this release"
|
|
17424
|
-
),
|
|
17425
|
-
new UnsupportedAnalyzer(
|
|
17426
|
-
"ruby-ast-analyzer",
|
|
17427
|
-
["ruby"],
|
|
17428
|
-
"Ruby AST analyzer is not yet implemented in this release"
|
|
17429
|
-
),
|
|
17430
|
-
new UnsupportedAnalyzer(
|
|
17431
|
-
"php-ast-analyzer",
|
|
17432
|
-
["composer"],
|
|
17433
|
-
"PHP AST analyzer is not yet implemented in this release"
|
|
17434
|
-
)
|
|
18425
|
+
new PythonAstAnalyzer(),
|
|
18426
|
+
new JavaAstAnalyzer(),
|
|
18427
|
+
new DotnetAstAnalyzer(),
|
|
18428
|
+
new RustAstAnalyzer(),
|
|
18429
|
+
new GoAstAnalyzer(),
|
|
18430
|
+
new RubyAstAnalyzer(),
|
|
18431
|
+
new PhpAstAnalyzer()
|
|
17435
18432
|
];
|
|
17436
18433
|
}
|
|
17437
18434
|
async analyze(input) {
|
|
@@ -17491,7 +18488,7 @@ var UsageContextEngine = class {
|
|
|
17491
18488
|
try {
|
|
17492
18489
|
const result = await analyzer.analyze(runContext);
|
|
17493
18490
|
const resultByKey = new Map(
|
|
17494
|
-
result.packages.map((pkg2) => [
|
|
18491
|
+
result.packages.map((pkg2) => [packageKey2(pkg2.ecosystem, pkg2.packageName), pkg2])
|
|
17495
18492
|
);
|
|
17496
18493
|
errors.push(...result.errors);
|
|
17497
18494
|
remainingSnippets = Math.max(0, remainingSnippets - result.snippetsProduced);
|
|
@@ -17499,7 +18496,7 @@ var UsageContextEngine = class {
|
|
|
17499
18496
|
let unsupportedCount = 0;
|
|
17500
18497
|
let analysisErrorCount = 0;
|
|
17501
18498
|
for (const pkg2 of packages) {
|
|
17502
|
-
const analyzed = resultByKey.get(
|
|
18499
|
+
const analyzed = resultByKey.get(packageKey2(pkg2.ecosystem, pkg2.packageName)) ?? {
|
|
17503
18500
|
packageName: pkg2.packageName,
|
|
17504
18501
|
ecosystem: pkg2.ecosystem,
|
|
17505
18502
|
status: "analysis_error",
|
|
@@ -17604,13 +18601,13 @@ var UsageContextEngine = class {
|
|
|
17604
18601
|
buildVulnerablePackages(vulnerabilities, dependencies) {
|
|
17605
18602
|
const directMap = /* @__PURE__ */ new Map();
|
|
17606
18603
|
for (const dependency of dependencies) {
|
|
17607
|
-
const key =
|
|
18604
|
+
const key = packageKey2(dependency.ecosystem, dependency.name);
|
|
17608
18605
|
const existing = directMap.get(key) ?? false;
|
|
17609
18606
|
directMap.set(key, existing || dependency.direct);
|
|
17610
18607
|
}
|
|
17611
18608
|
const grouped = /* @__PURE__ */ new Map();
|
|
17612
18609
|
for (const vulnerability of vulnerabilities) {
|
|
17613
|
-
const key =
|
|
18610
|
+
const key = packageKey2(vulnerability.ecosystem, vulnerability.packageName);
|
|
17614
18611
|
const existing = grouped.get(key);
|
|
17615
18612
|
if (existing) {
|
|
17616
18613
|
existing.vulnerabilities.push(vulnerability);
|
|
@@ -17634,7 +18631,7 @@ var UsageContextEngine = class {
|
|
|
17634
18631
|
return n > 0 ? n : fallback;
|
|
17635
18632
|
}
|
|
17636
18633
|
};
|
|
17637
|
-
function
|
|
18634
|
+
function packageKey2(ecosystem, packageName) {
|
|
17638
18635
|
return `${ecosystem}::${packageName}`;
|
|
17639
18636
|
}
|
|
17640
18637
|
function groupByEcosystem(packages) {
|
|
@@ -17780,9 +18777,9 @@ function deriveArtifactOutputPaths(cycloneDxOutput) {
|
|
|
17780
18777
|
}
|
|
17781
18778
|
return {
|
|
17782
18779
|
cyclonedx: cycloneDxOutput,
|
|
17783
|
-
spdx:
|
|
17784
|
-
swid:
|
|
17785
|
-
usageContext:
|
|
18780
|
+
spdx: join3(parsed.dir, `${baseName}.spdx.json`),
|
|
18781
|
+
swid: join3(parsed.dir, `${baseName}.swid.xml`),
|
|
18782
|
+
usageContext: join3(parsed.dir, `${baseName}.usage-context.json`)
|
|
17786
18783
|
};
|
|
17787
18784
|
}
|
|
17788
18785
|
function buildUploadPayload(report) {
|
|
@@ -17841,16 +18838,27 @@ function renderPlatformScan(projectPath, result) {
|
|
|
17841
18838
|
return lines.join("\n");
|
|
17842
18839
|
}
|
|
17843
18840
|
function collectVulnerabilities(result) {
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
|
|
18841
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
18842
|
+
for (const scanResult of result.scanResponse.scan_results ?? []) {
|
|
18843
|
+
for (const vuln of scanResult.vulnerabilities ?? []) {
|
|
18844
|
+
const next = {
|
|
18845
|
+
dependencyName: scanResult.dependency_name,
|
|
18846
|
+
version: scanResult.version,
|
|
18847
|
+
cveId: vuln.cve_id,
|
|
18848
|
+
severity: normalizeSeverity(vuln.severity ?? "UNKNOWN"),
|
|
18849
|
+
summary: pickSummary(vuln),
|
|
18850
|
+
fixedVersion: pickFixedVersion(vuln)
|
|
18851
|
+
};
|
|
18852
|
+
const key = `${next.dependencyName}::${next.version}::${next.cveId}`;
|
|
18853
|
+
const existing = deduped.get(key);
|
|
18854
|
+
if (!existing) {
|
|
18855
|
+
deduped.set(key, next);
|
|
18856
|
+
continue;
|
|
18857
|
+
}
|
|
18858
|
+
deduped.set(key, mergeVulnerability(existing, next));
|
|
18859
|
+
}
|
|
18860
|
+
}
|
|
18861
|
+
return Array.from(deduped.values());
|
|
17854
18862
|
}
|
|
17855
18863
|
function pickSummary(vuln) {
|
|
17856
18864
|
const value = vuln.summary ?? vuln.description;
|
|
@@ -17883,6 +18891,26 @@ function normalizeSeverity(severity) {
|
|
|
17883
18891
|
return "UNKNOWN";
|
|
17884
18892
|
}
|
|
17885
18893
|
}
|
|
18894
|
+
function mergeVulnerability(current, incoming) {
|
|
18895
|
+
const severity = severityOrder2(incoming.severity) < severityOrder2(current.severity) ? incoming.severity : current.severity;
|
|
18896
|
+
const summary = pickPreferredSummary(current.summary, incoming.summary);
|
|
18897
|
+
const fixedVersion = current.fixedVersion ?? incoming.fixedVersion ?? null;
|
|
18898
|
+
return {
|
|
18899
|
+
...current,
|
|
18900
|
+
severity,
|
|
18901
|
+
summary,
|
|
18902
|
+
fixedVersion
|
|
18903
|
+
};
|
|
18904
|
+
}
|
|
18905
|
+
function pickPreferredSummary(a, b) {
|
|
18906
|
+
const normalizedA = (a ?? "").trim();
|
|
18907
|
+
const normalizedB = (b ?? "").trim();
|
|
18908
|
+
if (!normalizedA) return normalizedB || "No description available";
|
|
18909
|
+
if (!normalizedB) return normalizedA;
|
|
18910
|
+
if (normalizedA === "No description available") return normalizedB;
|
|
18911
|
+
if (normalizedB === "No description available") return normalizedA;
|
|
18912
|
+
return normalizedB.length > normalizedA.length ? normalizedB : normalizedA;
|
|
18913
|
+
}
|
|
17886
18914
|
function summarizeBySeverity(severities) {
|
|
17887
18915
|
const summary = {
|
|
17888
18916
|
CRITICAL: 0,
|
|
@@ -18121,13 +19149,10 @@ function printHelp() {
|
|
|
18121
19149
|
Dashboard: https://app.verimu.com
|
|
18122
19150
|
`);
|
|
18123
19151
|
}
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
process.exit(2);
|
|
18129
|
-
});
|
|
18130
|
-
}
|
|
19152
|
+
main().catch((err) => {
|
|
19153
|
+
console.error("Fatal:", err);
|
|
19154
|
+
process.exit(2);
|
|
19155
|
+
});
|
|
18131
19156
|
export {
|
|
18132
19157
|
parseArgs
|
|
18133
19158
|
};
|