verimu 0.0.18 → 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 +1116 -87
- 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
|
|
@@ -14096,7 +14096,7 @@ import { createRequire } from "module";
|
|
|
14096
14096
|
|
|
14097
14097
|
// src/scan.ts
|
|
14098
14098
|
import { writeFile } from "fs/promises";
|
|
14099
|
-
import { basename, join as
|
|
14099
|
+
import { basename, join as join3, parse as parse2 } from "path";
|
|
14100
14100
|
|
|
14101
14101
|
// src/scanners/npm/npm-scanner.ts
|
|
14102
14102
|
import { readFile } from "fs/promises";
|
|
@@ -16750,6 +16750,15 @@ var ConsoleReporter = class {
|
|
|
16750
16750
|
lines.push(
|
|
16751
16751
|
` Findings: direct_evidence=${directEvidence}, indirect_no_evidence=${indirectNoEvidence}, unsupported=${unsupported}, analysis_error=${analysisErrors}`
|
|
16752
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
|
+
}
|
|
16753
16762
|
if (result.usageContext.artifactPath) {
|
|
16754
16763
|
lines.push(` Artifact: ${result.usageContext.artifactPath}`);
|
|
16755
16764
|
}
|
|
@@ -17001,7 +17010,7 @@ var import_types = __toESM(require_lib3(), 1);
|
|
|
17001
17010
|
import { readdir, readFile as readFile14 } from "fs/promises";
|
|
17002
17011
|
import { join } from "path";
|
|
17003
17012
|
import { parse } from "@babel/parser";
|
|
17004
|
-
import
|
|
17013
|
+
import traverseModule from "@babel/traverse";
|
|
17005
17014
|
var JS_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
17006
17015
|
".js",
|
|
17007
17016
|
".jsx",
|
|
@@ -17031,6 +17040,24 @@ var JsAstAnalyzer = class {
|
|
|
17031
17040
|
return this.ecosystems.has(ecosystem);
|
|
17032
17041
|
}
|
|
17033
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
|
+
}
|
|
17034
17061
|
const packageMap = this.buildPackageMaps(context.packages);
|
|
17035
17062
|
const resultMap = /* @__PURE__ */ new Map();
|
|
17036
17063
|
const snippetKeyMap = /* @__PURE__ */ new Map();
|
|
@@ -17094,13 +17121,13 @@ var JsAstAnalyzer = class {
|
|
|
17094
17121
|
const matchCandidates = [];
|
|
17095
17122
|
const matchSeen = /* @__PURE__ */ new Set();
|
|
17096
17123
|
const symbolToPackage = /* @__PURE__ */ new Map();
|
|
17097
|
-
const addMatch = (
|
|
17098
|
-
const candidateKey = `${
|
|
17124
|
+
const addMatch = (packageKey3, line, matchKind, calledSymbol, confidence = 0.8) => {
|
|
17125
|
+
const candidateKey = `${packageKey3}:${line}:${matchKind}:${calledSymbol ?? ""}`;
|
|
17099
17126
|
if (matchSeen.has(candidateKey)) return;
|
|
17100
17127
|
matchSeen.add(candidateKey);
|
|
17101
|
-
matchCandidates.push({ packageKey:
|
|
17128
|
+
matchCandidates.push({ packageKey: packageKey3, line, matchKind, calledSymbol, confidence });
|
|
17102
17129
|
};
|
|
17103
|
-
|
|
17130
|
+
traverseFn(ast, {
|
|
17104
17131
|
ImportDeclaration: (path14) => {
|
|
17105
17132
|
const source = path14.node.source;
|
|
17106
17133
|
if (!(0, import_types.isStringLiteral)(source)) return;
|
|
@@ -17222,6 +17249,18 @@ var JsAstAnalyzer = class {
|
|
|
17222
17249
|
return `${ecosystem}::${packageName}`;
|
|
17223
17250
|
}
|
|
17224
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
|
+
}
|
|
17225
17264
|
async function collectSourceFiles(rootPath) {
|
|
17226
17265
|
const files = [];
|
|
17227
17266
|
async function walk(dirPath) {
|
|
@@ -17317,9 +17356,9 @@ function collectIdentifiers(pattern) {
|
|
|
17317
17356
|
function resolveCallMatch(callee, symbolToPackage) {
|
|
17318
17357
|
const normalized = unwrapExpression(callee);
|
|
17319
17358
|
if ((0, import_types.isIdentifier)(normalized)) {
|
|
17320
|
-
const
|
|
17321
|
-
if (!
|
|
17322
|
-
return { packageKey:
|
|
17359
|
+
const packageKey3 = symbolToPackage.get(normalized.name);
|
|
17360
|
+
if (!packageKey3) return null;
|
|
17361
|
+
return { packageKey: packageKey3, calledSymbol: normalized.name };
|
|
17323
17362
|
}
|
|
17324
17363
|
if ((0, import_types.isMemberExpression)(normalized) || (0, import_types.isOptionalMemberExpression)(normalized)) {
|
|
17325
17364
|
return resolveMemberCallMatch(normalized, symbolToPackage);
|
|
@@ -17336,13 +17375,13 @@ function unwrapExpression(expression) {
|
|
|
17336
17375
|
function resolveMemberCallMatch(memberExpression, symbolToPackage) {
|
|
17337
17376
|
const objectExpr = unwrapExpression(memberExpression.object);
|
|
17338
17377
|
if (!(0, import_types.isIdentifier)(objectExpr)) return null;
|
|
17339
|
-
const
|
|
17340
|
-
if (!
|
|
17378
|
+
const packageKey3 = symbolToPackage.get(objectExpr.name);
|
|
17379
|
+
if (!packageKey3) return null;
|
|
17341
17380
|
const propertyName = propertyNameOf(memberExpression);
|
|
17342
17381
|
if (!propertyName) {
|
|
17343
|
-
return { packageKey:
|
|
17382
|
+
return { packageKey: packageKey3, calledSymbol: objectExpr.name };
|
|
17344
17383
|
}
|
|
17345
|
-
return { packageKey:
|
|
17384
|
+
return { packageKey: packageKey3, calledSymbol: `${objectExpr.name}.${propertyName}` };
|
|
17346
17385
|
}
|
|
17347
17386
|
function propertyNameOf(memberExpression) {
|
|
17348
17387
|
if (memberExpression.computed) {
|
|
@@ -17359,34 +17398,1021 @@ function propertyNameOf(memberExpression) {
|
|
|
17359
17398
|
return null;
|
|
17360
17399
|
}
|
|
17361
17400
|
|
|
17362
|
-
// src/context/analyzers/
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
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());
|
|
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
|
+
}
|
|
17371
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"]);
|
|
17372
17555
|
supports(ecosystem) {
|
|
17373
17556
|
return this.ecosystems.has(ecosystem);
|
|
17374
17557
|
}
|
|
17375
17558
|
async analyze(context) {
|
|
17376
|
-
const
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
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
|
+
}
|
|
17383
17688
|
return {
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
snippetsProduced: 0
|
|
17689
|
+
key: packageKey(ecosystem, packageName),
|
|
17690
|
+
modules: uniqueTokens(packageModules.map((v) => v.toLowerCase()))
|
|
17387
17691
|
};
|
|
17388
17692
|
}
|
|
17389
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
|
+
]);
|
|
17790
|
+
return {
|
|
17791
|
+
key: packageKey(ecosystem, packageName),
|
|
17792
|
+
candidates
|
|
17793
|
+
};
|
|
17794
|
+
}
|
|
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
|
+
}
|
|
17390
18416
|
|
|
17391
18417
|
// src/context/usage-context-engine.ts
|
|
17392
18418
|
var DEFAULT_MAX_SNIPPETS_PER_PACKAGE = 20;
|
|
@@ -17396,41 +18422,13 @@ var UsageContextEngine = class {
|
|
|
17396
18422
|
constructor(analyzers) {
|
|
17397
18423
|
this.analyzers = analyzers ?? [
|
|
17398
18424
|
new JsAstAnalyzer(),
|
|
17399
|
-
new
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
),
|
|
17404
|
-
new
|
|
17405
|
-
|
|
17406
|
-
["maven"],
|
|
17407
|
-
"Java AST analyzer is not yet implemented in this release"
|
|
17408
|
-
),
|
|
17409
|
-
new UnsupportedAnalyzer(
|
|
17410
|
-
"dotnet-ast-analyzer",
|
|
17411
|
-
["nuget"],
|
|
17412
|
-
"NuGet/C# AST analyzer is not yet implemented in this release"
|
|
17413
|
-
),
|
|
17414
|
-
new UnsupportedAnalyzer(
|
|
17415
|
-
"rust-ast-analyzer",
|
|
17416
|
-
["cargo"],
|
|
17417
|
-
"Rust AST analyzer is not yet implemented in this release"
|
|
17418
|
-
),
|
|
17419
|
-
new UnsupportedAnalyzer(
|
|
17420
|
-
"go-ast-analyzer",
|
|
17421
|
-
["go"],
|
|
17422
|
-
"Go AST analyzer is not yet implemented in this release"
|
|
17423
|
-
),
|
|
17424
|
-
new UnsupportedAnalyzer(
|
|
17425
|
-
"ruby-ast-analyzer",
|
|
17426
|
-
["ruby"],
|
|
17427
|
-
"Ruby AST analyzer is not yet implemented in this release"
|
|
17428
|
-
),
|
|
17429
|
-
new UnsupportedAnalyzer(
|
|
17430
|
-
"php-ast-analyzer",
|
|
17431
|
-
["composer"],
|
|
17432
|
-
"PHP AST analyzer is not yet implemented in this release"
|
|
17433
|
-
)
|
|
18425
|
+
new PythonAstAnalyzer(),
|
|
18426
|
+
new JavaAstAnalyzer(),
|
|
18427
|
+
new DotnetAstAnalyzer(),
|
|
18428
|
+
new RustAstAnalyzer(),
|
|
18429
|
+
new GoAstAnalyzer(),
|
|
18430
|
+
new RubyAstAnalyzer(),
|
|
18431
|
+
new PhpAstAnalyzer()
|
|
17434
18432
|
];
|
|
17435
18433
|
}
|
|
17436
18434
|
async analyze(input) {
|
|
@@ -17490,7 +18488,7 @@ var UsageContextEngine = class {
|
|
|
17490
18488
|
try {
|
|
17491
18489
|
const result = await analyzer.analyze(runContext);
|
|
17492
18490
|
const resultByKey = new Map(
|
|
17493
|
-
result.packages.map((pkg2) => [
|
|
18491
|
+
result.packages.map((pkg2) => [packageKey2(pkg2.ecosystem, pkg2.packageName), pkg2])
|
|
17494
18492
|
);
|
|
17495
18493
|
errors.push(...result.errors);
|
|
17496
18494
|
remainingSnippets = Math.max(0, remainingSnippets - result.snippetsProduced);
|
|
@@ -17498,7 +18496,7 @@ var UsageContextEngine = class {
|
|
|
17498
18496
|
let unsupportedCount = 0;
|
|
17499
18497
|
let analysisErrorCount = 0;
|
|
17500
18498
|
for (const pkg2 of packages) {
|
|
17501
|
-
const analyzed = resultByKey.get(
|
|
18499
|
+
const analyzed = resultByKey.get(packageKey2(pkg2.ecosystem, pkg2.packageName)) ?? {
|
|
17502
18500
|
packageName: pkg2.packageName,
|
|
17503
18501
|
ecosystem: pkg2.ecosystem,
|
|
17504
18502
|
status: "analysis_error",
|
|
@@ -17603,13 +18601,13 @@ var UsageContextEngine = class {
|
|
|
17603
18601
|
buildVulnerablePackages(vulnerabilities, dependencies) {
|
|
17604
18602
|
const directMap = /* @__PURE__ */ new Map();
|
|
17605
18603
|
for (const dependency of dependencies) {
|
|
17606
|
-
const key =
|
|
18604
|
+
const key = packageKey2(dependency.ecosystem, dependency.name);
|
|
17607
18605
|
const existing = directMap.get(key) ?? false;
|
|
17608
18606
|
directMap.set(key, existing || dependency.direct);
|
|
17609
18607
|
}
|
|
17610
18608
|
const grouped = /* @__PURE__ */ new Map();
|
|
17611
18609
|
for (const vulnerability of vulnerabilities) {
|
|
17612
|
-
const key =
|
|
18610
|
+
const key = packageKey2(vulnerability.ecosystem, vulnerability.packageName);
|
|
17613
18611
|
const existing = grouped.get(key);
|
|
17614
18612
|
if (existing) {
|
|
17615
18613
|
existing.vulnerabilities.push(vulnerability);
|
|
@@ -17633,7 +18631,7 @@ var UsageContextEngine = class {
|
|
|
17633
18631
|
return n > 0 ? n : fallback;
|
|
17634
18632
|
}
|
|
17635
18633
|
};
|
|
17636
|
-
function
|
|
18634
|
+
function packageKey2(ecosystem, packageName) {
|
|
17637
18635
|
return `${ecosystem}::${packageName}`;
|
|
17638
18636
|
}
|
|
17639
18637
|
function groupByEcosystem(packages) {
|
|
@@ -17779,9 +18777,9 @@ function deriveArtifactOutputPaths(cycloneDxOutput) {
|
|
|
17779
18777
|
}
|
|
17780
18778
|
return {
|
|
17781
18779
|
cyclonedx: cycloneDxOutput,
|
|
17782
|
-
spdx:
|
|
17783
|
-
swid:
|
|
17784
|
-
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`)
|
|
17785
18783
|
};
|
|
17786
18784
|
}
|
|
17787
18785
|
function buildUploadPayload(report) {
|
|
@@ -17840,16 +18838,27 @@ function renderPlatformScan(projectPath, result) {
|
|
|
17840
18838
|
return lines.join("\n");
|
|
17841
18839
|
}
|
|
17842
18840
|
function collectVulnerabilities(result) {
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
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());
|
|
17853
18862
|
}
|
|
17854
18863
|
function pickSummary(vuln) {
|
|
17855
18864
|
const value = vuln.summary ?? vuln.description;
|
|
@@ -17882,6 +18891,26 @@ function normalizeSeverity(severity) {
|
|
|
17882
18891
|
return "UNKNOWN";
|
|
17883
18892
|
}
|
|
17884
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
|
+
}
|
|
17885
18914
|
function summarizeBySeverity(severities) {
|
|
17886
18915
|
const summary = {
|
|
17887
18916
|
CRITICAL: 0,
|