@cejel/cejel 0.6.10 → 0.6.11
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.
|
@@ -5042,7 +5042,9 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono, useV27FalseAss
|
|
|
5042
5042
|
uncreditedCiWorkflows(repoPath, workflows, inventory),
|
|
5043
5043
|
publishingDetails,
|
|
5044
5044
|
checkedPublishingDetails,
|
|
5045
|
-
|
|
5045
|
+
// Eighth pass (#659): v17's test-file list, the one A1 counts, read only when a runner is
|
|
5046
|
+
// given a positional word (testPathReader reads it once).
|
|
5047
|
+
checkReading(repoPath, inventory, () => findConcreteTestFiles(repoPath, repoFiles, WITAN_RUBRIC_VERSION_V17))
|
|
5046
5048
|
);
|
|
5047
5049
|
}) : /* @__PURE__ */ new Map();
|
|
5048
5050
|
const uncreditedRootWorkflows = useV27FalseAssuranceClosure && mono ? withContentReadSignals(
|
|
@@ -6174,14 +6176,16 @@ function firstCheckLineByJob(contents, isGuardLine, reading, jobs, items, notCre
|
|
|
6174
6176
|
const first = /* @__PURE__ */ new Map();
|
|
6175
6177
|
const runSteps = githubActionsRunSteps(contents, isGuardLine);
|
|
6176
6178
|
const environment = checkPathEnvironmentProblems(contents, jobs, items, runSteps);
|
|
6179
|
+
const uvSyncedJobs = /* @__PURE__ */ new Set();
|
|
6177
6180
|
for (const step of runSteps) {
|
|
6181
|
+
const uvProjectSynced = uvSyncedJobs.has(step.job);
|
|
6182
|
+
const stepCommands = joinShellContinuationLines(step.lines.map(({ text }) => text)).flatMap((text) => shellCommands(text));
|
|
6183
|
+
if (stepCommands.some(runsProjectUvSync)) uvSyncedJobs.add(step.job);
|
|
6178
6184
|
if (step.conditional || step.job === -1 || !step.ownKey) continue;
|
|
6179
|
-
const commands =
|
|
6180
|
-
(text) => shellCommands(text).map((words) => ({ words }))
|
|
6181
|
-
);
|
|
6185
|
+
const commands = stepCommands.map((words) => ({ words }));
|
|
6182
6186
|
if (stepShellIsConditional(step, commands)) continue;
|
|
6183
6187
|
const changesDirectory = commands.some(({ words }) => ["cd", "pushd"].includes(commandHead(words).word));
|
|
6184
|
-
const stepReading = changesDirectory ? { ...reading, ...ROOT_FILES_UNREAD } : reading;
|
|
6188
|
+
const stepReading = { ...changesDirectory ? { ...reading, ...ROOT_FILES_UNREAD } : reading, uvProjectSynced };
|
|
6185
6189
|
const isCheck = (words) => isCheckCommand(words, stepReading);
|
|
6186
6190
|
for (const { words } of commands) {
|
|
6187
6191
|
const reason = unrecognisedCheckReason(words, stepReading) ?? checkLineReason(words, stepReading) ?? buildLineReason(words, stepReading) ?? seventhPassReason(words, stepReading);
|
|
@@ -6305,18 +6309,27 @@ var ROOT_FILES_UNREAD = {
|
|
|
6305
6309
|
// Seventh pass (#641, item D): an install, and any npm, pnpm, yarn or bun command, reads the
|
|
6306
6310
|
// manifest and configuration of the directory it runs in, which the root readings do not describe.
|
|
6307
6311
|
installRunsNoProjectScript: false,
|
|
6308
|
-
packageManagerConfigPlain: false
|
|
6312
|
+
packageManagerConfigPlain: false,
|
|
6313
|
+
// Eighth pass (#660): uv reads the pyproject.toml at or above the directory it runs in.
|
|
6314
|
+
uvProject: "unread"
|
|
6309
6315
|
};
|
|
6310
6316
|
var UNREAD_CHECK_READING = {
|
|
6311
6317
|
...ROOT_FILES_UNREAD,
|
|
6312
6318
|
pathMayExist: () => true,
|
|
6313
|
-
pythonModuleMayExist: () => true
|
|
6319
|
+
pythonModuleMayExist: () => true,
|
|
6320
|
+
testPath: () => "unread",
|
|
6321
|
+
uvProjectSynced: false
|
|
6314
6322
|
};
|
|
6315
|
-
function checkReading(root, inventory) {
|
|
6323
|
+
function checkReading(root, inventory, testFiles) {
|
|
6316
6324
|
const tsconfig = rootTsconfigReading(root, inventory);
|
|
6317
6325
|
const pythonModuleMayExist = pythonModuleReader(inventory);
|
|
6326
|
+
const testPath = testPathReader(testFiles);
|
|
6327
|
+
const uvProject = uvProjectReading(root, inventory);
|
|
6318
6328
|
const tscAdmitted = Object.assign((words) => tscRunWritesNothingRead(words, tsconfig, inventory), {
|
|
6319
|
-
moduleMayExist: (name) => pythonModuleMayExist(name, true)
|
|
6329
|
+
moduleMayExist: (name) => pythonModuleMayExist(name, true),
|
|
6330
|
+
// Eighth pass (#659, #660): a script or recipe runs from the root.
|
|
6331
|
+
testPath: (word, kind) => testPath(word, kind, true),
|
|
6332
|
+
uvRunBuildsProject: uvProject !== "no-build"
|
|
6320
6333
|
});
|
|
6321
6334
|
const testScript = rootTestScriptReading(root, inventory, tscAdmitted);
|
|
6322
6335
|
const build = rootBuildScriptReading(root, inventory, tscAdmitted);
|
|
@@ -6337,7 +6350,10 @@ function checkReading(root, inventory) {
|
|
|
6337
6350
|
pathMayExist: (name, fromRoot) => fromRoot ? inventory.entriesLower.has(name.toLowerCase()) : inventory.pathPartsLower.has(name.toLowerCase()),
|
|
6338
6351
|
installRunsNoProjectScript: rootInstallRunsNoProjectScript(root, inventory),
|
|
6339
6352
|
packageManagerConfigPlain: packageManagerConfigIsPlain(root, inventory),
|
|
6340
|
-
pythonModuleMayExist
|
|
6353
|
+
pythonModuleMayExist,
|
|
6354
|
+
testPath,
|
|
6355
|
+
uvProject,
|
|
6356
|
+
uvProjectSynced: false
|
|
6341
6357
|
};
|
|
6342
6358
|
}
|
|
6343
6359
|
var PYTHON_MODULE_FILE_PATTERN = /\.(?:py|pyc|pyw|so|pyd)$/;
|
|
@@ -7150,7 +7166,7 @@ function commandsAreAndChain(text) {
|
|
|
7150
7166
|
const list = shellCommandList(text);
|
|
7151
7167
|
return list.length > 0 && list.every(({ words, after }) => !commandIsBackgrounded(words) && (after.length === 0 || after.length === 1 && after[0] === "&&"));
|
|
7152
7168
|
}
|
|
7153
|
-
function peelCommandLaunchers(input) {
|
|
7169
|
+
function peelCommandLaunchers(input, admission) {
|
|
7154
7170
|
const head = commandHead(input);
|
|
7155
7171
|
if (head.inverted || commandIsBackgrounded(input)) return null;
|
|
7156
7172
|
let words = [head.word, ...input.slice(head.argsFrom)];
|
|
@@ -7166,6 +7182,7 @@ function peelCommandLaunchers(input) {
|
|
|
7166
7182
|
if (first === "env" || first === "cross-env" || first === "npx" || first === "pnpx" || first === "bunx") next = 1;
|
|
7167
7183
|
else if (first === "pnpm" && second === "exec" || first === "bundle" && second === "exec") next = 2;
|
|
7168
7184
|
else if (SCRIPT_ALIAS_TOOLS.has(first) && second === "run") return null;
|
|
7185
|
+
else if (first === "uv" && second === "run" && admission.uvRunBuildsProject) return null;
|
|
7169
7186
|
else if (CHECK_COMMAND_RUN_LAUNCHERS.has(first) && second === "run") next = 2;
|
|
7170
7187
|
else if (first === "coverage" && second === "run") {
|
|
7171
7188
|
words = ["python", ...words.slice(2)];
|
|
@@ -7189,9 +7206,9 @@ function scriptCommandKind(words, tscAdmitted) {
|
|
|
7189
7206
|
const python = (first === "python" || first === "python3") && second === "-m";
|
|
7190
7207
|
if (python && tscAdmitted.moduleMayExist(third)) return "other";
|
|
7191
7208
|
if (KNOWN_TEST_RUNNER_WORDS.has(first) || KNOWN_TEST_RUNNER_SUBCOMMANDS.get(first)?.includes(second)) {
|
|
7192
|
-
return checkFormAdmits(words) === true ? "runner" : "other";
|
|
7209
|
+
return checkFormAdmits(words, tscAdmitted.testPath) === true ? "runner" : "other";
|
|
7193
7210
|
}
|
|
7194
|
-
if (python && KNOWN_TEST_RUNNER_MODULES.has(third)) return checkFormAdmits(words.slice(2)) === true ? "runner" : "other";
|
|
7211
|
+
if (python && KNOWN_TEST_RUNNER_MODULES.has(third)) return checkFormAdmits(words.slice(2), tscAdmitted.testPath) === true ? "runner" : "other";
|
|
7195
7212
|
if (first === "tsc") return tscAdmitted(words) ? "check" : "other";
|
|
7196
7213
|
if (PRE_RUNNER_CHECK_WORDS.has(first) || python && PRE_RUNNER_CHECK_WORDS.has(third)) return "check";
|
|
7197
7214
|
return "other";
|
|
@@ -7208,7 +7225,7 @@ function packageScriptRun(words, scripts) {
|
|
|
7208
7225
|
if (words.length === 2 && tool === "yarn" && Object.hasOwn(scripts, first)) return first;
|
|
7209
7226
|
return void 0;
|
|
7210
7227
|
}
|
|
7211
|
-
function packageScriptCommands(scripts, name) {
|
|
7228
|
+
function packageScriptCommands(scripts, name, admission) {
|
|
7212
7229
|
const commands = [];
|
|
7213
7230
|
const visit = (script, depth, stack) => {
|
|
7214
7231
|
if (depth > SCRIPT_EXPANSION_MAX_DEPTH || stack.has(script) || !Object.hasOwn(scripts, script)) return false;
|
|
@@ -7218,7 +7235,7 @@ function packageScriptCommands(scripts, name) {
|
|
|
7218
7235
|
const body = scripts[part];
|
|
7219
7236
|
if (typeof body !== "string" || !commandsAreAndChain(body)) return false;
|
|
7220
7237
|
for (const words of shellCommands(body)) {
|
|
7221
|
-
const peeled = peelCommandLaunchers(words);
|
|
7238
|
+
const peeled = peelCommandLaunchers(words, admission);
|
|
7222
7239
|
const run = peeled === null ? void 0 : packageScriptRun(peeled, scripts);
|
|
7223
7240
|
if (run !== void 0) {
|
|
7224
7241
|
if (!visit(run, depth + 1, inner)) return false;
|
|
@@ -7240,7 +7257,7 @@ function rootPackageScripts(root, inventory) {
|
|
|
7240
7257
|
function rootTestScriptReading(root, inventory, tscAdmitted) {
|
|
7241
7258
|
const scripts = rootPackageScripts(root, inventory);
|
|
7242
7259
|
if (scripts === void 0 || typeof scripts.test !== "string") return "unread";
|
|
7243
|
-
const commands = packageScriptCommands(scripts, "test");
|
|
7260
|
+
const commands = packageScriptCommands(scripts, "test", tscAdmitted);
|
|
7244
7261
|
const kinds = (commands ?? []).map((words) => words === null ? "other" : scriptCommandKind(words, tscAdmitted));
|
|
7245
7262
|
return commands !== void 0 && kindsRunKnownRunner(kinds) ? "runner" : "unrecognised";
|
|
7246
7263
|
}
|
|
@@ -7331,7 +7348,7 @@ function makeTargetCommandKinds(rules, target, mayBeUpToDate, tscAdmitted) {
|
|
|
7331
7348
|
if (ignored || !commandsAreAndChain(text)) kinds.push("other");
|
|
7332
7349
|
else {
|
|
7333
7350
|
for (const words of shellCommands(text)) {
|
|
7334
|
-
const peeled = peelCommandLaunchers(words);
|
|
7351
|
+
const peeled = peelCommandLaunchers(words, tscAdmitted);
|
|
7335
7352
|
kinds.push(peeled === null ? "other" : scriptCommandKind(peeled, tscAdmitted));
|
|
7336
7353
|
}
|
|
7337
7354
|
}
|
|
@@ -7365,7 +7382,7 @@ function rootMakeTargetReadings(root, inventory, tscAdmitted) {
|
|
|
7365
7382
|
function rootBuildScriptReading(root, inventory, tscAdmitted) {
|
|
7366
7383
|
const scripts = rootPackageScripts(root, inventory);
|
|
7367
7384
|
if (scripts === void 0 || !inventory.scanned.has("tsconfig.json")) return "other";
|
|
7368
|
-
const commands = packageScriptCommands(scripts, "build");
|
|
7385
|
+
const commands = packageScriptCommands(scripts, "build", tscAdmitted);
|
|
7369
7386
|
const runsOnlyTsc = commands !== void 0 && commands.length > 0 && commands.every((words) => words !== null && words[0] === "tsc" && !words.includes("--if-present"));
|
|
7370
7387
|
if (!runsOnlyTsc) return "other";
|
|
7371
7388
|
return commands.every((words) => words !== null && tscAdmitted(words)) ? "admitted" : "output-unread";
|
|
@@ -7439,6 +7456,7 @@ function isCheckCommand(input, reading, assignmentsRead = true, pathsRead = true
|
|
|
7439
7456
|
if (read === void 0) return false;
|
|
7440
7457
|
if (read.packageManager && !reading.packageManagerConfigPlain) return false;
|
|
7441
7458
|
if (assignmentsRead && read.assignments.some((key) => !isInertEnvironmentKey(key))) return false;
|
|
7459
|
+
if (read.uv !== void 0 && uvRunProblem(read.uv, reading) !== void 0) return false;
|
|
7442
7460
|
let own = read.words;
|
|
7443
7461
|
if (read.alias !== void 0) {
|
|
7444
7462
|
const alias = reading.scriptAlias(read.alias, read.words);
|
|
@@ -7458,7 +7476,7 @@ function isCheckCommand(input, reading, assignmentsRead = true, pathsRead = true
|
|
|
7458
7476
|
if (NPM_FAMILY_TEST_SCRIPT_PATTERN.test(text)) {
|
|
7459
7477
|
if (!reading.testScriptRuns) return false;
|
|
7460
7478
|
if (packageTestArgumentsProblem(words, reading) !== void 0) return false;
|
|
7461
|
-
} else if (checkFormAdmits(words) !== true) {
|
|
7479
|
+
} else if (checkFormAdmits(words, stepTestPath(reading)) !== true) {
|
|
7462
7480
|
return false;
|
|
7463
7481
|
}
|
|
7464
7482
|
return verificationCommandPatterns().some((pattern) => {
|
|
@@ -7531,9 +7549,86 @@ function checkCommandWords(input) {
|
|
|
7531
7549
|
} else if (CHECK_COMMAND_RUN_LAUNCHERS.has(launcher) && words[start + 1] === "run") {
|
|
7532
7550
|
start += 2;
|
|
7533
7551
|
if (SCRIPT_ALIAS_TOOLS.has(launcher)) return { words: words.slice(start), packageManager, assignments, alias: launcher };
|
|
7552
|
+
if (launcher === "uv") {
|
|
7553
|
+
let noProject = false;
|
|
7554
|
+
let noSync = false;
|
|
7555
|
+
let unread;
|
|
7556
|
+
for (; start < words.length; start += 1) {
|
|
7557
|
+
const word = words[start] ?? "";
|
|
7558
|
+
const option = word.split("=")[0] ?? word;
|
|
7559
|
+
if (word === "--no-project") noProject = true;
|
|
7560
|
+
else if (word === "--no-sync") noSync = true;
|
|
7561
|
+
else if (UV_RUN_PROJECT_OPTIONS.has(option)) {
|
|
7562
|
+
unread = option;
|
|
7563
|
+
if (!word.includes("=")) start += 1;
|
|
7564
|
+
} else break;
|
|
7565
|
+
}
|
|
7566
|
+
return { words: words.slice(start), packageManager, assignments, uv: { noProject, noSync, ...unread === void 0 ? {} : { unread } } };
|
|
7567
|
+
}
|
|
7534
7568
|
}
|
|
7535
7569
|
return { words: words.slice(start), packageManager, assignments };
|
|
7536
7570
|
}
|
|
7571
|
+
var UV_RUN_PROJECT_OPTIONS = /* @__PURE__ */ new Set(["--package", "--project", "--directory"]);
|
|
7572
|
+
var UV_PROJECT_ADMITTED_KEYS = /* @__PURE__ */ new Set(["package", "dev-dependencies", "required-version", "index"]);
|
|
7573
|
+
function readUvProject(text) {
|
|
7574
|
+
let buildSystem = false;
|
|
7575
|
+
let packaged;
|
|
7576
|
+
const lines = text.split(/\r?\n/);
|
|
7577
|
+
let table = [];
|
|
7578
|
+
const uvKeyUnread = (path) => path[0] === "tool" && (path.length === 1 || path[1] === "uv" && (path.length === 2 || !UV_PROJECT_ADMITTED_KEYS.has(path[2] ?? "")));
|
|
7579
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
7580
|
+
const trimmed = (lines[index] ?? "").trim();
|
|
7581
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
7582
|
+
const header = /^\[\[?([^\]]*)\]\]?/.exec(trimmed);
|
|
7583
|
+
if (header) {
|
|
7584
|
+
if ((header[1] ?? "").includes("\\")) return "unread";
|
|
7585
|
+
table = tomlKeyPath(header[1] ?? "");
|
|
7586
|
+
if (table[0] === "build-system") buildSystem = true;
|
|
7587
|
+
if (table[0] === "tool" && table[1] === "uv" && table.length > 2 && !UV_PROJECT_ADMITTED_KEYS.has(table[2] ?? "")) return "unread";
|
|
7588
|
+
continue;
|
|
7589
|
+
}
|
|
7590
|
+
const equals = tomlKeyEnd(trimmed);
|
|
7591
|
+
if (equals === -1) continue;
|
|
7592
|
+
const scanner = new TomlValueScanner();
|
|
7593
|
+
scanner.feed(trimmed.slice(equals + 1));
|
|
7594
|
+
while (!scanner.complete() && index + 1 < lines.length) {
|
|
7595
|
+
index += 1;
|
|
7596
|
+
scanner.feed(`
|
|
7597
|
+
${lines[index] ?? ""}`);
|
|
7598
|
+
}
|
|
7599
|
+
if (trimmed.slice(0, equals).includes("\\")) return "unread";
|
|
7600
|
+
const path = [...table, ...tomlKeyPath(trimmed.slice(0, equals))];
|
|
7601
|
+
if (path[0] === "build-system") buildSystem = true;
|
|
7602
|
+
if (uvKeyUnread(path)) return "unread";
|
|
7603
|
+
if (path[0] === "tool" && path[1] === "uv" && path[2] === "package") {
|
|
7604
|
+
const code = scanner.code().trim();
|
|
7605
|
+
if (path.length !== 3 || code !== "true" && code !== "false") return "unread";
|
|
7606
|
+
packaged = code === "true";
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7609
|
+
if (packaged === false) return "no-build";
|
|
7610
|
+
return packaged === true || buildSystem ? "builds" : "no-build";
|
|
7611
|
+
}
|
|
7612
|
+
function uvProjectReading(root, inventory) {
|
|
7613
|
+
if (inventory.hasUvConfiguration || !inventory.scanned.has("pyproject.toml")) return "unread";
|
|
7614
|
+
const text = readRepoText(join2(root, "pyproject.toml"), "utf8");
|
|
7615
|
+
return text === void 0 ? "unread" : readUvProject(text);
|
|
7616
|
+
}
|
|
7617
|
+
function uvRunProblem(uv, reading) {
|
|
7618
|
+
if (uv.unread !== void 0) return UV_RUN_UNREAD_REASON;
|
|
7619
|
+
if (uv.noProject) return void 0;
|
|
7620
|
+
if (reading.uvProject === "unread") return UV_RUN_UNREAD_REASON;
|
|
7621
|
+
if (reading.uvProject === "no-build") return void 0;
|
|
7622
|
+
if (!uv.noSync) return UV_INSTALLS_PROJECT_REASON;
|
|
7623
|
+
return reading.uvProjectSynced ? UV_SYNCED_PROJECT_REASON : void 0;
|
|
7624
|
+
}
|
|
7625
|
+
function runsProjectUvSync(input) {
|
|
7626
|
+
const head = commandHead(input);
|
|
7627
|
+
const words = [head.word, ...input.slice(head.argsFrom)];
|
|
7628
|
+
let start = 0;
|
|
7629
|
+
while (start < words.length && SHELL_ASSIGNMENT_PATTERN.test(words[start] ?? "")) start += 1;
|
|
7630
|
+
return words[start] === "uv" && words[start + 1] === "sync" && !words.includes("--no-install-project");
|
|
7631
|
+
}
|
|
7537
7632
|
var NPM_TEST_KEPT_WORDS = /* @__PURE__ */ new Set(["--silent", "-s"]);
|
|
7538
7633
|
var PACKAGE_SELECTING_FLAGS = /* @__PURE__ */ new Set([
|
|
7539
7634
|
"--prefix",
|
|
@@ -7569,7 +7664,12 @@ function packageTestArgumentsProblem(words, reading) {
|
|
|
7569
7664
|
if (last === void 0) return UNREAD_TEST_ARGUMENT_REASON;
|
|
7570
7665
|
const runner = [...last, ...passed];
|
|
7571
7666
|
const python = (runner[0] === "python" || runner[0] === "python3") && runner[1] === "-m";
|
|
7572
|
-
|
|
7667
|
+
const admitted = checkFormAdmits(python ? runner.slice(2) : runner, stepTestPath(reading));
|
|
7668
|
+
if (typeof admitted === "object") return positionalReason(admitted);
|
|
7669
|
+
return admitted === true ? void 0 : RUNNER_ARGUMENT_REASON;
|
|
7670
|
+
}
|
|
7671
|
+
function stepTestPath(reading) {
|
|
7672
|
+
return (word, kind) => reading.testPath(word, kind, reading.pipNamesFromRoot);
|
|
7573
7673
|
}
|
|
7574
7674
|
function checkLineReason(input, reading) {
|
|
7575
7675
|
if (commandIsBackgrounded(input)) return void 0;
|
|
@@ -7627,6 +7727,10 @@ function seventhPassReason(input, reading) {
|
|
|
7627
7727
|
if (read === void 0 || read.packageManager && !reading.packageManagerConfigPlain) return void 0;
|
|
7628
7728
|
if (read.assignments.some((key) => !isInertEnvironmentKey(key))) return void 0;
|
|
7629
7729
|
let words = read.words;
|
|
7730
|
+
if (read.uv !== void 0) {
|
|
7731
|
+
const problem = uvRunProblem(read.uv, reading);
|
|
7732
|
+
if (problem !== void 0) return words.length > 0 && !isPathCommandWord(words[0] ?? "") && namesCheckAtStart(words) ? problem : void 0;
|
|
7733
|
+
}
|
|
7630
7734
|
if (read.alias !== void 0) {
|
|
7631
7735
|
const alias = reading.scriptAlias(read.alias, words);
|
|
7632
7736
|
if (alias.kind === "script") return alias.reading === "runner" ? void 0 : SCRIPT_ALIAS_SCRIPT_REASON(read.alias, alias.name);
|
|
@@ -7648,7 +7752,9 @@ function seventhPassReason(input, reading) {
|
|
|
7648
7752
|
const first = (words[0] ?? "").toLowerCase();
|
|
7649
7753
|
if (first === "make" || LINT_OR_FORMAT_COMMANDS.has(first) || NPM_FAMILY_TEST_SCRIPT_PATTERN.test(words.join(" "))) return void 0;
|
|
7650
7754
|
if (words.some((word) => REPORT_ONLY_FLAGS.has(word))) return void 0;
|
|
7651
|
-
|
|
7755
|
+
const admitted = checkFormAdmits(words, stepTestPath(reading));
|
|
7756
|
+
if (typeof admitted === "object") return positionalReason(admitted);
|
|
7757
|
+
return admitted === false ? RUNNER_ARGUMENT_REASON : void 0;
|
|
7652
7758
|
}
|
|
7653
7759
|
var JUST_RECIPE_HEADER_PATTERN = /^@?([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:(?!=)(.*)$/;
|
|
7654
7760
|
var JUST_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
@@ -7711,7 +7817,7 @@ function rootJustRecipeReader(root, inventory, tscAdmitted) {
|
|
|
7711
7817
|
}
|
|
7712
7818
|
if (ignored || !commandsAreAndChain(text)) kinds.push("other");
|
|
7713
7819
|
else for (const words of shellCommands(text)) {
|
|
7714
|
-
const peeled = peelCommandLaunchers(words);
|
|
7820
|
+
const peeled = peelCommandLaunchers(words, tscAdmitted);
|
|
7715
7821
|
kinds.push(peeled === null ? "other" : scriptCommandKind(peeled, tscAdmitted));
|
|
7716
7822
|
}
|
|
7717
7823
|
if (kinds.length > SCRIPT_EXPANSION_MAX_COMMANDS) return false;
|
|
@@ -7777,7 +7883,7 @@ function rootToxReading(root, inventory, tscAdmitted) {
|
|
|
7777
7883
|
for (const line of commands.split("\n").map((entry) => entry.trim()).filter((entry) => entry !== "")) {
|
|
7778
7884
|
if (line.startsWith("-") || line.startsWith("!") || /[|&;<>$`()]/.test(line)) kinds.push("other");
|
|
7779
7885
|
else {
|
|
7780
|
-
const peeled = peelCommandLaunchers(shellWords(line));
|
|
7886
|
+
const peeled = peelCommandLaunchers(shellWords(line), tscAdmitted);
|
|
7781
7887
|
kinds.push(peeled === null ? "other" : scriptCommandKind(peeled, tscAdmitted));
|
|
7782
7888
|
}
|
|
7783
7889
|
if (kinds.length > SCRIPT_EXPANSION_MAX_COMMANDS) return false;
|
|
@@ -7802,7 +7908,11 @@ var SCRIPT_ALIAS_TOOLS = /* @__PURE__ */ new Set(["pdm", "hatch", "pipenv"]);
|
|
|
7802
7908
|
var PDM_RUN_HOOKS = ["pre_run", "post_run", "pre_script", "post_script", "_"];
|
|
7803
7909
|
var HATCH_UNREAD_SETTING_PATTERN = /env-vars|pre-install-commands|post-install-commands/;
|
|
7804
7910
|
var HATCH_ENV_ADMITTED_OPTIONS = /* @__PURE__ */ new Set(["skip-install", "description", "dependencies", "extra-dependencies"]);
|
|
7805
|
-
var
|
|
7911
|
+
var PROJECT_INSTALL_REASON = (runs, tool) => `the check runs ${runs}, so ${tool} first installs the project, running its build code`;
|
|
7912
|
+
var HATCH_INSTALLS_PROJECT_REASON = PROJECT_INSTALL_REASON("hatch run in an environment that does not set skip-install", "hatch");
|
|
7913
|
+
var UV_INSTALLS_PROJECT_REASON = PROJECT_INSTALL_REASON("uv run in a project uv builds", "uv");
|
|
7914
|
+
var UV_SYNCED_PROJECT_REASON = PROJECT_INSTALL_REASON("uv run --no-sync after a uv sync of a project uv builds", "uv");
|
|
7915
|
+
var UV_RUN_UNREAD_REASON = CONFIGURATION_UNREAD_REASON("uv run");
|
|
7806
7916
|
function readHatchSkipInstall(fileSet, readText, isIndexName) {
|
|
7807
7917
|
const skip = /* @__PURE__ */ new Map();
|
|
7808
7918
|
for (const [file, prefix] of [["hatch.toml", []], ["pyproject.toml", ["tool", "hatch"]]]) {
|
|
@@ -7895,7 +8005,7 @@ function scriptAliasBodyKinds(text, shell, tscAdmitted) {
|
|
|
7895
8005
|
if (shell ? !commandsAreAndChain(text) : /[|&;<>$`(){}\\\r\n]/.test(text)) return ["other"];
|
|
7896
8006
|
const commands = shell ? shellCommands(text) : [shellWords(text)];
|
|
7897
8007
|
return commands.map((words) => {
|
|
7898
|
-
const peeled = peelCommandLaunchers(words);
|
|
8008
|
+
const peeled = peelCommandLaunchers(words, tscAdmitted);
|
|
7899
8009
|
return peeled === null ? "other" : scriptCommandKind(peeled, tscAdmitted);
|
|
7900
8010
|
});
|
|
7901
8011
|
}
|
|
@@ -8048,13 +8158,200 @@ var CHECK_FORMS = new Map([
|
|
|
8048
8158
|
["pyright", [checkForm([], ["--warnings"], ["-p", "--project", "--pythonversion"])]],
|
|
8049
8159
|
...["ava", "uvu", "tap", "tape", "jasmine", "qunit", "lab", "ts-mocha"].map((runner) => [runner, [PLAIN_RUNNER_FORM]])
|
|
8050
8160
|
]);
|
|
8051
|
-
|
|
8161
|
+
var POSITIONAL_READINGS = /* @__PURE__ */ new Map([
|
|
8162
|
+
["pytest", "path"],
|
|
8163
|
+
["mocha", "path"],
|
|
8164
|
+
["node", "path"],
|
|
8165
|
+
["tsx", "path"],
|
|
8166
|
+
["deno", "path"],
|
|
8167
|
+
["rspec", "path"],
|
|
8168
|
+
["phpunit", "path"],
|
|
8169
|
+
["dotnet", "path"],
|
|
8170
|
+
["ava", "path"],
|
|
8171
|
+
["tap", "path"],
|
|
8172
|
+
["tape", "path"],
|
|
8173
|
+
["jasmine", "path"],
|
|
8174
|
+
["qunit", "path"],
|
|
8175
|
+
["lab", "path"],
|
|
8176
|
+
["ts-mocha", "path"],
|
|
8177
|
+
["go", "go"],
|
|
8178
|
+
["unittest", "module"],
|
|
8179
|
+
["uvu", "uvu"]
|
|
8180
|
+
]);
|
|
8181
|
+
var UNRESOLVED_PATH_WORD_PATTERN = /[$`'"{}\\[\]]|^~/;
|
|
8182
|
+
var GLOB_WORD_PATTERN = /[*?]/;
|
|
8183
|
+
var TEST_PATH_GLOB_BUDGET = 2e6;
|
|
8184
|
+
function repositoryRelativePath(word) {
|
|
8185
|
+
if (word.startsWith("/")) return null;
|
|
8186
|
+
const parts = [];
|
|
8187
|
+
for (const part of word.split("/")) {
|
|
8188
|
+
if (part === "" || part === ".") continue;
|
|
8189
|
+
if (part === "..") {
|
|
8190
|
+
if (parts.length === 0) return null;
|
|
8191
|
+
parts.pop();
|
|
8192
|
+
} else parts.push(part);
|
|
8193
|
+
}
|
|
8194
|
+
return parts.join("/");
|
|
8195
|
+
}
|
|
8196
|
+
function globMatchesPath(glob, path, budget) {
|
|
8197
|
+
const part = (pattern, text) => {
|
|
8198
|
+
budget.steps -= (pattern.length + 1) * (text.length + 1);
|
|
8199
|
+
if (budget.steps < 0) return void 0;
|
|
8200
|
+
let p = 0;
|
|
8201
|
+
let t = 0;
|
|
8202
|
+
let star = -1;
|
|
8203
|
+
let mark = 0;
|
|
8204
|
+
while (t < text.length) {
|
|
8205
|
+
if (p < pattern.length && (pattern[p] === "?" || pattern[p] === text[t])) {
|
|
8206
|
+
p += 1;
|
|
8207
|
+
t += 1;
|
|
8208
|
+
} else if (p < pattern.length && pattern[p] === "*") {
|
|
8209
|
+
star = p;
|
|
8210
|
+
mark = t;
|
|
8211
|
+
p += 1;
|
|
8212
|
+
} else if (star !== -1) {
|
|
8213
|
+
p = star + 1;
|
|
8214
|
+
mark += 1;
|
|
8215
|
+
t = mark;
|
|
8216
|
+
} else return false;
|
|
8217
|
+
}
|
|
8218
|
+
while (p < pattern.length && pattern[p] === "*") p += 1;
|
|
8219
|
+
return p === pattern.length;
|
|
8220
|
+
};
|
|
8221
|
+
let reachable = new Array(path.length + 1).fill(false);
|
|
8222
|
+
reachable[0] = true;
|
|
8223
|
+
for (const pattern of glob) {
|
|
8224
|
+
budget.steps -= path.length + 1;
|
|
8225
|
+
if (budget.steps < 0) return void 0;
|
|
8226
|
+
const next = new Array(path.length + 1).fill(false);
|
|
8227
|
+
for (let at = 0; at <= path.length; at += 1) {
|
|
8228
|
+
if (!reachable[at]) continue;
|
|
8229
|
+
if (pattern === "**") {
|
|
8230
|
+
for (let end = at; end <= path.length; end += 1) next[end] = true;
|
|
8231
|
+
break;
|
|
8232
|
+
}
|
|
8233
|
+
if (at === path.length) continue;
|
|
8234
|
+
const matched = part(pattern, path[at] ?? "");
|
|
8235
|
+
if (matched === void 0) return void 0;
|
|
8236
|
+
if (matched) next[at + 1] = true;
|
|
8237
|
+
}
|
|
8238
|
+
reachable = next;
|
|
8239
|
+
}
|
|
8240
|
+
return reachable[path.length] === true;
|
|
8241
|
+
}
|
|
8242
|
+
function testPathReader(testFiles) {
|
|
8243
|
+
let read;
|
|
8244
|
+
const budget = { steps: TEST_PATH_GLOB_BUDGET };
|
|
8245
|
+
const inventory = () => {
|
|
8246
|
+
if (read !== void 0) return read;
|
|
8247
|
+
const trie = { directories: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Set(), goPackages: /* @__PURE__ */ new Set(), goTrees: /* @__PURE__ */ new Set(), parts: testFiles().map((file) => file.split("/")) };
|
|
8248
|
+
for (const file of trie.parts) {
|
|
8249
|
+
const goTest = (file[file.length - 1] ?? "").endsWith("_test.go");
|
|
8250
|
+
let skipped = false;
|
|
8251
|
+
let node = 0;
|
|
8252
|
+
if (goTest) trie.goTrees.add(node);
|
|
8253
|
+
for (let at = 0; at < file.length - 1; at += 1) {
|
|
8254
|
+
const name = file[at] ?? "";
|
|
8255
|
+
const key = `${node}/${name}`;
|
|
8256
|
+
let next = trie.directories.get(key);
|
|
8257
|
+
if (next === void 0) {
|
|
8258
|
+
next = trie.directories.size + 1;
|
|
8259
|
+
trie.directories.set(key, next);
|
|
8260
|
+
}
|
|
8261
|
+
node = next;
|
|
8262
|
+
skipped ||= name === "testdata" || name.startsWith(".") || name.startsWith("_");
|
|
8263
|
+
if (goTest && !skipped) trie.goTrees.add(node);
|
|
8264
|
+
}
|
|
8265
|
+
trie.files.add(`${node}/${file[file.length - 1] ?? ""}`);
|
|
8266
|
+
if (goTest && !skipped) trie.goPackages.add(node);
|
|
8267
|
+
}
|
|
8268
|
+
read = trie;
|
|
8269
|
+
return read;
|
|
8270
|
+
};
|
|
8271
|
+
const child = (node, name) => inventory().directories.get(`${node}/${name}`);
|
|
8272
|
+
const hasFile = (node, name) => inventory().files.has(`${node}/${name}`);
|
|
8273
|
+
const directoryAt = (parts) => {
|
|
8274
|
+
let node = 0;
|
|
8275
|
+
for (const name of parts) {
|
|
8276
|
+
node = child(node, name);
|
|
8277
|
+
if (node === void 0) return void 0;
|
|
8278
|
+
}
|
|
8279
|
+
return node;
|
|
8280
|
+
};
|
|
8281
|
+
const listed = (path) => {
|
|
8282
|
+
const parts = path === "" ? [] : path.split("/");
|
|
8283
|
+
if (parts.length === 0) return inventory().parts.length > 0;
|
|
8284
|
+
if (directoryAt(parts) !== void 0) return true;
|
|
8285
|
+
const parent = directoryAt(parts.slice(0, -1));
|
|
8286
|
+
return parent !== void 0 && hasFile(parent, parts[parts.length - 1] ?? "");
|
|
8287
|
+
};
|
|
8288
|
+
const pathReading = (word) => {
|
|
8289
|
+
if (GLOB_WORD_PATTERN.test(word)) {
|
|
8290
|
+
const glob = repositoryRelativePath(word);
|
|
8291
|
+
if (glob === null) return "no-test";
|
|
8292
|
+
const pattern = glob.split("/");
|
|
8293
|
+
for (const file of inventory().parts) {
|
|
8294
|
+
const matched = globMatchesPath(pattern, file, budget);
|
|
8295
|
+
if (matched === void 0) return "unread";
|
|
8296
|
+
if (matched) return "test";
|
|
8297
|
+
}
|
|
8298
|
+
return "no-test";
|
|
8299
|
+
}
|
|
8300
|
+
const path = repositoryRelativePath(word);
|
|
8301
|
+
return path !== null && listed(path) ? "test" : "no-test";
|
|
8302
|
+
};
|
|
8303
|
+
return (word, kind, fromRoot) => {
|
|
8304
|
+
if (!fromRoot || word === "" || UNRESOLVED_PATH_WORD_PATTERN.test(word)) return "unread";
|
|
8305
|
+
if (kind === "go") {
|
|
8306
|
+
if (GLOB_WORD_PATTERN.test(word)) return "unread";
|
|
8307
|
+
const tree = word === "..." || word.endsWith("/...");
|
|
8308
|
+
const directory = tree ? word.slice(0, -3) : word;
|
|
8309
|
+
if (!(directory === "" || directory === "." || directory.startsWith("./") || directory.startsWith("../") || directory === "..")) return "unread";
|
|
8310
|
+
if (directory.includes("...")) return "unread";
|
|
8311
|
+
const path = repositoryRelativePath(directory);
|
|
8312
|
+
if (path === null) return "no-test";
|
|
8313
|
+
const node = directoryAt(path === "" ? [] : path.split("/"));
|
|
8314
|
+
return node !== void 0 && (tree ? inventory().goTrees : inventory().goPackages).has(node) ? "test" : "no-test";
|
|
8315
|
+
}
|
|
8316
|
+
if (kind === "module" && !word.includes("/") && !word.endsWith(".py")) {
|
|
8317
|
+
const parts = word.split(".");
|
|
8318
|
+
if (parts.some((name) => name === "")) return "unread";
|
|
8319
|
+
let node = 0;
|
|
8320
|
+
for (const name of parts) {
|
|
8321
|
+
if (hasFile(node, `${name}.py`)) return "test";
|
|
8322
|
+
node = child(node, name);
|
|
8323
|
+
if (node === void 0) return "no-test";
|
|
8324
|
+
}
|
|
8325
|
+
return "test";
|
|
8326
|
+
}
|
|
8327
|
+
return pathReading(word);
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
var NAMEABLE_POSITIONAL_WORD_PATTERN = /^[A-Za-z0-9_./:*?@+=,-]{1,80}$/;
|
|
8331
|
+
var POSITIONAL_NO_TEST_REASON = (word) => NAMEABLE_POSITIONAL_WORD_PATTERN.test(word) ? `the check points at \`${word}\`, where Cejel finds no test file` : "the check points at a path where Cejel finds no test file";
|
|
8332
|
+
function positionalProblem(runner, positionals, testPath) {
|
|
8333
|
+
const kind = POSITIONAL_READINGS.get(runner);
|
|
8334
|
+
if (kind === void 0) return void 0;
|
|
8335
|
+
for (const [at, positional] of positionals.entries()) {
|
|
8336
|
+
if (kind === "module" && at === 0 && positional === "discover") {
|
|
8337
|
+
if (positionals.length > 1) return { word: positional, unread: true };
|
|
8338
|
+
continue;
|
|
8339
|
+
}
|
|
8340
|
+
if (kind === "uvu" && at > 0) return { word: positional, unread: true };
|
|
8341
|
+
const word = runner === "pytest" && positional.includes("::") ? positional.slice(0, positional.indexOf("::")) : positional;
|
|
8342
|
+
const reading = testPath(word, kind === "uvu" ? "path" : kind);
|
|
8343
|
+
if (reading !== "test") return { word: positional, unread: reading === "unread" };
|
|
8344
|
+
}
|
|
8345
|
+
return void 0;
|
|
8346
|
+
}
|
|
8347
|
+
function checkFormAdmits(words, testPath) {
|
|
8052
8348
|
const forms = CHECK_FORMS.get(words[0] ?? "");
|
|
8053
8349
|
if (forms === void 0) return void 0;
|
|
8054
8350
|
const form = forms.find(({ sub }) => sub.every((word, at) => words[1 + at] === word));
|
|
8055
8351
|
if (form === void 0) return false;
|
|
8056
8352
|
const args = words.slice(1 + form.sub.length);
|
|
8057
8353
|
let positionals = 0;
|
|
8354
|
+
const paths = [];
|
|
8058
8355
|
for (let index = 0; index < args.length; index += 1) {
|
|
8059
8356
|
const arg = args[index] ?? "";
|
|
8060
8357
|
if (arg === "--") return form.afterSeparator !== void 0 && args.slice(index + 1).every((word) => form.afterSeparator?.has(word));
|
|
@@ -8078,17 +8375,22 @@ function checkFormAdmits(words) {
|
|
|
8078
8375
|
if (form.firstPositional !== void 0 && positionals === 0) {
|
|
8079
8376
|
if (!form.firstPositional.has(arg)) return false;
|
|
8080
8377
|
} else if (!form.positional) return false;
|
|
8378
|
+
else paths.push(arg);
|
|
8081
8379
|
positionals += 1;
|
|
8082
8380
|
}
|
|
8083
|
-
return true;
|
|
8381
|
+
if (testPath === void 0 || paths.length === 0) return true;
|
|
8382
|
+
return positionalProblem(words[0] ?? "", paths, testPath) ?? true;
|
|
8084
8383
|
}
|
|
8085
8384
|
var RUNNER_ARGUMENT_REASON = "the check carries a flag or argument that Cejel does not know to keep it failing when a test fails";
|
|
8385
|
+
function positionalReason(problem) {
|
|
8386
|
+
return problem.unread ? UNREAD_TEST_ARGUMENT_REASON : POSITIONAL_NO_TEST_REASON(problem.word);
|
|
8387
|
+
}
|
|
8086
8388
|
function rootTestScriptLastRunner(root, inventory, tscAdmitted) {
|
|
8087
8389
|
const scripts = rootPackageScripts(root, inventory);
|
|
8088
8390
|
const body = scripts?.test;
|
|
8089
8391
|
if (scripts === void 0 || typeof body !== "string" || !commandsAreAndChain(body)) return void 0;
|
|
8090
8392
|
const commands = shellCommands(body);
|
|
8091
|
-
const peeled = peelCommandLaunchers(commands[commands.length - 1] ?? []);
|
|
8393
|
+
const peeled = peelCommandLaunchers(commands[commands.length - 1] ?? [], tscAdmitted);
|
|
8092
8394
|
if (peeled === null || packageScriptRun(peeled, scripts) !== void 0) return void 0;
|
|
8093
8395
|
return scriptCommandKind(peeled, tscAdmitted) === "runner" ? peeled : void 0;
|
|
8094
8396
|
}
|
|
@@ -9974,6 +10276,7 @@ var PYTEST_CONFIG_FILE_NAMES = ["pytest.toml", ".pytest.toml", "pytest.ini", ".p
|
|
|
9974
10276
|
var PYTEST_EXECUTABLE_PATTERN = /^(?:pytest|py\.test)(?:\.exe)?$/;
|
|
9975
10277
|
var PYTEST_ADDOPTS_ASSIGNMENT = "PYTEST_ADDOPTS=";
|
|
9976
10278
|
var PYTEST_ADDOPTS_LINE_PATTERN = /^[ \t]*(?:-[ \t]*)?(?:export[ \t]+)?PYTEST_ADDOPTS[ \t]*(?::|[:?+]?=)(.*)$/;
|
|
10279
|
+
var PYTEST_ADDOPTS_KEY_PATTERN = /^[ \t]*PYTEST_ADDOPTS(?:[ \t]*:|[ \t]+[:?+]?=)/;
|
|
9977
10280
|
var CI_SCRIPT_KEY_PATTERN = /^([ \t]*)(?:-[ \t]*)?(?:script|before_script|after_script|bash|pwsh|powershell|sh|command)[ \t]*:[ \t]*(.*)$/;
|
|
9978
10281
|
var A1_NOTE_PATH_MAX_LENGTH = 120;
|
|
9979
10282
|
function a1NotePath(path) {
|
|
@@ -10522,10 +10825,11 @@ function resolvePytestRun(invocation, repo) {
|
|
|
10522
10825
|
const commandAt = (tokens) => (token, label) => () => file === "" ? void 0 : { path: file, line: repo.lineOf(file, [token, ...tokens], anchor), label };
|
|
10523
10826
|
const groups = [];
|
|
10524
10827
|
if (invocation.addopts && invocation.addopts.length > 0) {
|
|
10828
|
+
const elsewhere = invocation.addoptsAt;
|
|
10525
10829
|
groups.push({
|
|
10526
10830
|
words: invocation.addopts,
|
|
10527
|
-
where: `in PYTEST_ADDOPTS in ${a1NotePath(invocation.source ?? "")}`,
|
|
10528
|
-
at: (_token, label) => () => file === "" ? void 0 : { path: file, line: repo.lineOf(file, ["PYTEST_ADDOPTS"], invocation.addoptsAnchor ?? anchor), label }
|
|
10831
|
+
where: `in PYTEST_ADDOPTS in ${a1NotePath(elsewhere?.source ?? invocation.source ?? "")}`,
|
|
10832
|
+
at: (_token, label) => () => elsewhere !== void 0 ? { path: elsewhere.file, line: repo.lineOf(elsewhere.file, ["PYTEST_ADDOPTS"], elsewhere.line), label } : file === "" ? void 0 : { path: file, line: repo.lineOf(file, ["PYTEST_ADDOPTS"], invocation.addoptsAnchor ?? anchor), label }
|
|
10529
10833
|
});
|
|
10530
10834
|
}
|
|
10531
10835
|
if (invocation.args.length > 0) groups.push({ words: invocation.args, where: commandWhere, at: commandAt(["pytest", "py.test"]) });
|
|
@@ -11036,7 +11340,7 @@ function stripShellHeredocBodies(text) {
|
|
|
11036
11340
|
}
|
|
11037
11341
|
return kept.join("\n");
|
|
11038
11342
|
}
|
|
11039
|
-
function readShellCommands(text, directory, addopts, visit, unknownDirectory) {
|
|
11343
|
+
function readShellCommands(text, directory, addopts, visit, unknownDirectory, mentions) {
|
|
11040
11344
|
let current = directory;
|
|
11041
11345
|
let why = directory === null ? unknownDirectory : void 0;
|
|
11042
11346
|
let exported = addopts;
|
|
@@ -11048,9 +11352,13 @@ function readShellCommands(text, directory, addopts, visit, unknownDirectory) {
|
|
|
11048
11352
|
if (start >= all.length) continue;
|
|
11049
11353
|
all[start] = (all[start] ?? "").replace(/^[({]+/, "").replace(/^[@+-]+(?=[A-Za-z./{$])/, "");
|
|
11050
11354
|
let local = exported;
|
|
11355
|
+
let leading = 0;
|
|
11051
11356
|
while (start < all.length && /^[A-Za-z_]\w*=/.test(all[start] ?? "")) {
|
|
11052
11357
|
const assignment = all[start] ?? "";
|
|
11053
|
-
if (assignment.startsWith(PYTEST_ADDOPTS_ASSIGNMENT))
|
|
11358
|
+
if (assignment.startsWith(PYTEST_ADDOPTS_ASSIGNMENT)) {
|
|
11359
|
+
local = shellWords(assignment.slice(PYTEST_ADDOPTS_ASSIGNMENT.length));
|
|
11360
|
+
leading += 1;
|
|
11361
|
+
}
|
|
11054
11362
|
start += 1;
|
|
11055
11363
|
}
|
|
11056
11364
|
const words = start === 0 ? all : all.slice(start);
|
|
@@ -11061,10 +11369,17 @@ function readShellCommands(text, directory, addopts, visit, unknownDirectory) {
|
|
|
11061
11369
|
const name = words[0] ?? "";
|
|
11062
11370
|
if (name === "export") {
|
|
11063
11371
|
for (const word of words.slice(1)) {
|
|
11064
|
-
if (word.startsWith(PYTEST_ADDOPTS_ASSIGNMENT))
|
|
11372
|
+
if (word.startsWith(PYTEST_ADDOPTS_ASSIGNMENT)) {
|
|
11373
|
+
exported = shellWords(word.slice(PYTEST_ADDOPTS_ASSIGNMENT.length));
|
|
11374
|
+
if (mentions !== void 0) mentions.read += 1;
|
|
11375
|
+
}
|
|
11065
11376
|
}
|
|
11066
11377
|
continue;
|
|
11067
11378
|
}
|
|
11379
|
+
if (mentions !== void 0 && (leading > 0 || name === "env")) {
|
|
11380
|
+
const make = makeCommandOf(words);
|
|
11381
|
+
if (make !== void 0) mentions.read += leading + make.addoptsAssignments;
|
|
11382
|
+
}
|
|
11068
11383
|
if (name === "cd" || name === "pushd") {
|
|
11069
11384
|
const target = words[1];
|
|
11070
11385
|
const before = current;
|
|
@@ -11370,6 +11685,13 @@ function readGithubRunSteps(text) {
|
|
|
11370
11685
|
return /* @__PURE__ */ new Map();
|
|
11371
11686
|
};
|
|
11372
11687
|
const scalarAt = (index) => index === void 0 ? void 0 : yamlPlainScalar(lines[index]?.value ?? "");
|
|
11688
|
+
const writtenAt = (index) => {
|
|
11689
|
+
const value = lines[index]?.value ?? "";
|
|
11690
|
+
const scalar = yamlPlainScalar(value);
|
|
11691
|
+
const quote = value[0];
|
|
11692
|
+
const close = quote === '"' || quote === "'" ? value.lastIndexOf(quote) : -1;
|
|
11693
|
+
return close > 0 && value.slice(1, close) === scalar ? value.slice(0, close + 1) : scalar;
|
|
11694
|
+
};
|
|
11373
11695
|
const isFalse = (value) => value !== void 0 && /^(?:false|\$\{\{\s*false\s*\}\})$/.test(value);
|
|
11374
11696
|
const defaultDirectory = (keys) => scalarAt(childKeys(childKeys(keys.get("defaults")).get("run")).get("working-directory"));
|
|
11375
11697
|
const addoptsLineIn = (keys) => childKeys(keys.get("env")).get("PYTEST_ADDOPTS");
|
|
@@ -11429,13 +11751,17 @@ function readGithubRunSteps(text) {
|
|
|
11429
11751
|
} else {
|
|
11430
11752
|
command = yamlPlainScalar(line.value);
|
|
11431
11753
|
}
|
|
11754
|
+
const expression = addoptsValue !== void 0 && addoptsValue.includes("${{");
|
|
11755
|
+
const stepAddoptsLine = addoptsLineIn(stepKeys);
|
|
11432
11756
|
steps.push({
|
|
11433
11757
|
command,
|
|
11434
11758
|
directory,
|
|
11435
11759
|
line: index,
|
|
11760
|
+
addoptsKeyLines: [root.addoptsLine, job.addoptsLine, stepAddoptsLine].filter((at) => at !== void 0),
|
|
11436
11761
|
...directory === null && directoryValue !== void 0 ? { unknownDirectory: `working-directory: ${directoryValue}` } : {},
|
|
11437
|
-
...addoptsValue === void 0 ||
|
|
11438
|
-
...addoptsValue === void 0 ||
|
|
11762
|
+
...addoptsValue === void 0 || expression ? {} : { addopts: shellWords(addoptsValue) },
|
|
11763
|
+
...addoptsValue === void 0 || addoptsLine === void 0 ? {} : { addoptsLine },
|
|
11764
|
+
...expression ? { addoptsNotKnown: `PYTEST_ADDOPTS: ${addoptsLine === void 0 ? addoptsValue : writtenAt(addoptsLine)}` } : {}
|
|
11439
11765
|
});
|
|
11440
11766
|
}
|
|
11441
11767
|
return steps;
|
|
@@ -11452,8 +11778,30 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11452
11778
|
let hatchScripts;
|
|
11453
11779
|
let hatchExpansionsLeft = HATCH_EXPANSION_BUDGET;
|
|
11454
11780
|
const environmentTexts = [];
|
|
11455
|
-
|
|
11781
|
+
let addoptsMentionNotRead = false;
|
|
11782
|
+
const noteAddoptsMentions = (text, read) => {
|
|
11783
|
+
if (wordCount(text, "PYTEST_ADDOPTS") !== read) addoptsMentionNotRead = true;
|
|
11784
|
+
};
|
|
11785
|
+
const makeEnvironments = [];
|
|
11786
|
+
let readingCi = true;
|
|
11787
|
+
const visitor = (source, file, anchor, depth = 0, envAddopts, envNotKnown, addoptsAt) => (words, directory, addopts, unknownDirectory) => {
|
|
11456
11788
|
for (const target of recipeTargetsNamed(words)) namedTargets.add(target);
|
|
11789
|
+
const makeRun = readingCi ? makeCommandOf(words) : void 0;
|
|
11790
|
+
if (makeRun !== void 0) {
|
|
11791
|
+
const inForce2 = makeRun.addopts ?? addopts;
|
|
11792
|
+
const notKnown = envNotKnown?.reason ?? (inForce2?.some((word) => /[$`]/.test(word)) ? `PYTEST_ADDOPTS=${inForce2.join(" ")}` : void 0);
|
|
11793
|
+
if (inForce2 !== void 0 || notKnown !== void 0) {
|
|
11794
|
+
let runIn2 = makeRun.namesFile ? null : directory;
|
|
11795
|
+
for (const step of makeRun.directories) runIn2 = runIn2 === null ? null : resolveCommandPath(runIn2, step);
|
|
11796
|
+
makeEnvironments.push({
|
|
11797
|
+
directory: runIn2,
|
|
11798
|
+
targets: makeRun.targets,
|
|
11799
|
+
overrides: makeRun.overrides,
|
|
11800
|
+
...notKnown !== void 0 ? { notKnown } : inForce2 !== void 0 ? { addopts: inForce2 } : {},
|
|
11801
|
+
at: { source, file, line: envAddopts !== void 0 && inForce2 === envAddopts.words ? envAddopts.line : anchor }
|
|
11802
|
+
});
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11457
11805
|
for (const name of makeAssignmentsNamed(words)) makeOverrides.add(name);
|
|
11458
11806
|
for (const environment of toxEnvironmentsNamed(words)) namedToxEnvironments.add(environment);
|
|
11459
11807
|
const posargs = toxPosargsOf(words);
|
|
@@ -11488,7 +11836,7 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11488
11836
|
const substituted = substituteHatchArguments(command, hatch.args);
|
|
11489
11837
|
const first = substituted.trim().split(/\s+/)[0] ?? "";
|
|
11490
11838
|
const inner = hatchScripts.get(hatch.environment)?.has(first) ? `hatch run ${hatch.environment}:${substituted.trim()}` : substituted;
|
|
11491
|
-
readShellCommands(inner, directory === null ? null : repoDirectory(script.file), addopts, visitor(a1NotePath(script.file), script.file, script.line, depth + 1), unknownDirectory);
|
|
11839
|
+
readShellCommands(inner, directory === null ? null : repoDirectory(script.file), addopts, visitor(a1NotePath(script.file), script.file, script.line, depth + 1, void 0, envNotKnown), unknownDirectory);
|
|
11492
11840
|
}
|
|
11493
11841
|
return;
|
|
11494
11842
|
}
|
|
@@ -11508,7 +11856,12 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11508
11856
|
return;
|
|
11509
11857
|
}
|
|
11510
11858
|
const inForce = found.addopts ?? addopts;
|
|
11859
|
+
if (inForce === void 0 && envNotKnown !== void 0) {
|
|
11860
|
+
invocations.push({ source: envNotKnown.source, file: envNotKnown.file, anchor: envNotKnown.line, directory: "", args: [], text: textOf(), unread: envNotKnown.reason });
|
|
11861
|
+
return;
|
|
11862
|
+
}
|
|
11511
11863
|
const addoptsAnchor = inForce !== void 0 && envAddopts !== void 0 && inForce === envAddopts.words ? envAddopts.line : void 0;
|
|
11864
|
+
const elsewhere = inForce !== void 0 && addoptsAt !== void 0 && inForce === addoptsAt.words ? addoptsAt : void 0;
|
|
11512
11865
|
invocations.push({
|
|
11513
11866
|
source,
|
|
11514
11867
|
file,
|
|
@@ -11517,7 +11870,8 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11517
11870
|
directory: runIn,
|
|
11518
11871
|
args: found.args,
|
|
11519
11872
|
...inForce === void 0 ? {} : { addopts: inForce },
|
|
11520
|
-
...addoptsAnchor === void 0 ? {} : { addoptsAnchor }
|
|
11873
|
+
...addoptsAnchor === void 0 ? {} : { addoptsAnchor },
|
|
11874
|
+
...elsewhere === void 0 ? {} : { addoptsAt: { source: elsewhere.source, file: elsewhere.file, line: elsewhere.line } }
|
|
11521
11875
|
});
|
|
11522
11876
|
};
|
|
11523
11877
|
const usable = repoFiles.filter((file) => !GENERATED_OR_VENDOR_PATH_PATTERN.test(file));
|
|
@@ -11528,10 +11882,24 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11528
11882
|
const remote = readGithubRemoteSteps(text);
|
|
11529
11883
|
const writesAddopts = text.includes("GITHUB_ENV") && text.includes("PYTEST_ADDOPTS") ? "PYTEST_ADDOPTS written to $GITHUB_ENV" : void 0;
|
|
11530
11884
|
const unreadWhy = writesAddopts ?? remote.checkoutPath;
|
|
11885
|
+
const keyLines = /* @__PURE__ */ new Set();
|
|
11886
|
+
const shellMentions = writesAddopts === void 0 && text.includes("PYTEST_ADDOPTS") ? { read: 0 } : void 0;
|
|
11531
11887
|
for (const step of readGithubRunSteps(text)) {
|
|
11888
|
+
for (const at of step.addoptsKeyLines) keyLines.add(at);
|
|
11532
11889
|
const envAddopts = step.addopts !== void 0 && step.addoptsLine !== void 0 ? { words: step.addopts, line: step.addoptsLine } : void 0;
|
|
11533
|
-
|
|
11534
|
-
|
|
11890
|
+
const notKnownReason = writesAddopts ?? step.addoptsNotKnown;
|
|
11891
|
+
const envNotKnown = notKnownReason === void 0 ? void 0 : { reason: notKnownReason, source, file, line: writesAddopts === void 0 && step.addoptsLine !== void 0 ? step.addoptsLine : step.line };
|
|
11892
|
+
if (unreadWhy !== void 0) readShellCommands(step.command, null, step.addopts, visitor(source, file, step.line, 0, envAddopts, envNotKnown), unreadWhy, shellMentions);
|
|
11893
|
+
else readShellCommands(step.command, step.directory, step.addopts, visitor(source, file, step.line, 0, envAddopts, envNotKnown), step.unknownDirectory, shellMentions);
|
|
11894
|
+
}
|
|
11895
|
+
if (text.includes("PYTEST_ADDOPTS")) {
|
|
11896
|
+
let read = keyLines.size + (shellMentions?.read ?? 0);
|
|
11897
|
+
if (writesAddopts !== void 0) {
|
|
11898
|
+
text.split(/\r?\n/).forEach((line, index) => {
|
|
11899
|
+
if (!keyLines.has(index) && line.includes("GITHUB_ENV")) read += wordCount(line, "PYTEST_ADDOPTS");
|
|
11900
|
+
});
|
|
11901
|
+
}
|
|
11902
|
+
noteAddoptsMentions(text, read);
|
|
11535
11903
|
}
|
|
11536
11904
|
for (const step of remote.pytestInputs) {
|
|
11537
11905
|
invocations.push({ source, file, anchor: step.line, directory: "", args: [], text: step.uses, unread: step.uses });
|
|
@@ -11566,8 +11934,13 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11566
11934
|
environmentTexts.push(text);
|
|
11567
11935
|
const jenkinsDir = file === "Jenkinsfile" ? jenkinsDirCall(text) : void 0;
|
|
11568
11936
|
const addopts = readCiAddopts(text);
|
|
11937
|
+
const shellMentions = text.includes("PYTEST_ADDOPTS") ? { read: 0 } : void 0;
|
|
11569
11938
|
for (const command of ciShellTexts(text)) {
|
|
11570
|
-
readShellCommands(command, jenkinsDir === void 0 ? "" : null, addopts, visitor(source, file, 0), jenkinsDir);
|
|
11939
|
+
readShellCommands(command, jenkinsDir === void 0 ? "" : null, addopts, visitor(source, file, 0), jenkinsDir, shellMentions);
|
|
11940
|
+
}
|
|
11941
|
+
if (shellMentions !== void 0) {
|
|
11942
|
+
const keyLine = text.split(/\r?\n/).find((line) => PYTEST_ADDOPTS_LINE_PATTERN.test(line));
|
|
11943
|
+
noteAddoptsMentions(text, shellMentions.read + (keyLine !== void 0 && PYTEST_ADDOPTS_KEY_PATTERN.test(keyLine) ? 1 : 0));
|
|
11571
11944
|
}
|
|
11572
11945
|
}
|
|
11573
11946
|
const noxAt = invocations.length;
|
|
@@ -11622,18 +11995,22 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11622
11995
|
}
|
|
11623
11996
|
return noxRuns;
|
|
11624
11997
|
};
|
|
11998
|
+
readingCi = false;
|
|
11999
|
+
const environments = distinctMakeEnvironments(makeEnvironments);
|
|
11625
12000
|
for (const file of usable.filter(isRecipeFile)) {
|
|
11626
12001
|
const text = readText(file);
|
|
11627
12002
|
if (text === void 0) continue;
|
|
11628
12003
|
const just = /[Jj]ustfile$/.test(file);
|
|
11629
12004
|
const targets = just ? readJustRecipes(text) : readRecipeTargets(text);
|
|
11630
12005
|
const headers = just ? new Map([...readJustRecipes(text)].map(([name, recipe]) => [name, recipe.line])) : makeTargetLines(text);
|
|
11631
|
-
const setByEnvironment = (name) => environmentTexts.some((ciText) => containsWord(ciText, name));
|
|
12006
|
+
const setByEnvironment = (name) => name === "PYTEST_ADDOPTS" ? addoptsMentionNotRead : environmentTexts.some((ciText) => containsWord(ciText, name));
|
|
11632
12007
|
const justVariables = just ? readJustVariables(text) : void 0;
|
|
11633
12008
|
const make = just ? void 0 : readMakeVariables(text, setByEnvironment);
|
|
11634
12009
|
const budget = { left: MAKE_EXPANSION_BUDGET };
|
|
11635
|
-
const
|
|
11636
|
-
const
|
|
12010
|
+
const unknownIn = (variables) => (name) => variables?.unknown.has(name) === true || makeOverrides.has(name);
|
|
12011
|
+
const isUnknown = unknownIn(make);
|
|
12012
|
+
const expandWith = (variables) => (command) => justVariables !== void 0 ? { text: expandJustVariables(command, justVariables), unknown: false } : expandMakeVariables(command, variables?.values ?? /* @__PURE__ */ new Map(), unknownIn(variables), budget);
|
|
12013
|
+
const expand = expandWith(make);
|
|
11637
12014
|
let addoptsUnknown = false;
|
|
11638
12015
|
let addoptsText;
|
|
11639
12016
|
if (justVariables !== void 0) {
|
|
@@ -11655,30 +12032,63 @@ function readPytestInvocations(repoFiles, readText) {
|
|
|
11655
12032
|
}
|
|
11656
12033
|
const addopts = addoptsText === void 0 ? void 0 : shellWords(shellWords(addoptsText.trim()).join(" "));
|
|
11657
12034
|
const unknownRunsPytest = (command, expanded) => command.toLowerCase().includes("pytest") || expanded.toLowerCase().includes("pytest") || makeReferencesIn(expanded).some((name) => make?.pytestNames.has(name) === true);
|
|
11658
|
-
const
|
|
11659
|
-
const
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
const
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
12035
|
+
const firstTarget = [...targets.keys()][0];
|
|
12036
|
+
const readRecipes = (names, variables, inForceAddopts, notKnown, addoptsAt) => {
|
|
12037
|
+
const expandHere = variables === make ? expand : expandWith(variables);
|
|
12038
|
+
const reached = /* @__PURE__ */ new Set();
|
|
12039
|
+
const queue = names.filter((name) => name !== void 0 && targets.has(name));
|
|
12040
|
+
while (queue.length > 0) {
|
|
12041
|
+
const name = queue.shift() ?? "";
|
|
12042
|
+
if (reached.has(name)) continue;
|
|
12043
|
+
reached.add(name);
|
|
12044
|
+
const target = targets.get(name);
|
|
12045
|
+
if (!target) continue;
|
|
12046
|
+
queue.push(...target.dependencies.filter((dependency) => targets.has(dependency)));
|
|
12047
|
+
const commands = joinRecipeContinuations(target.commands);
|
|
12048
|
+
for (const command of commands) {
|
|
12049
|
+
const expanded = expandHere(command);
|
|
12050
|
+
if (expanded.unknown && unknownRunsPytest(command, expanded.text)) {
|
|
12051
|
+
invocations.push({ source: a1NotePath(file), file, anchor: headers.get(name) ?? 0, directory: "", args: [], text: command, unread: command });
|
|
12052
|
+
continue;
|
|
12053
|
+
}
|
|
12054
|
+
readShellCommands(expanded.text, repoDirectory(file), inForceAddopts, (words, directory, inForce, why) => {
|
|
12055
|
+
queue.push(...recipeTargetsNamed(words).filter((named) => targets.has(named)));
|
|
12056
|
+
const visit = visitor(a1NotePath(file), file, headers.get(name) ?? 0, 0, void 0, void 0, addoptsAt);
|
|
12057
|
+
if (notKnown !== void 0) visit(words, null, inForce, notKnown);
|
|
12058
|
+
else visit(words, directory, inForce, why);
|
|
12059
|
+
});
|
|
11675
12060
|
}
|
|
11676
|
-
readShellCommands(expanded.text, repoDirectory(file), addopts, (words, directory, inForce, why) => {
|
|
11677
|
-
queue.push(...recipeTargetsNamed(words).filter((named) => targets.has(named)));
|
|
11678
|
-
if (addoptsUnknown) visitor(a1NotePath(file), file, headers.get(name) ?? 0)(words, null, inForce, "PYTEST_ADDOPTS");
|
|
11679
|
-
else visitor(a1NotePath(file), file, headers.get(name) ?? 0)(words, directory, inForce, why);
|
|
11680
|
-
});
|
|
11681
12061
|
}
|
|
12062
|
+
};
|
|
12063
|
+
readRecipes([firstTarget, "test", "tests", "check", ...namedTargets], make, addopts, addoptsUnknown ? "PYTEST_ADDOPTS" : void 0);
|
|
12064
|
+
if (make === void 0 || addoptsUnknown) continue;
|
|
12065
|
+
const here = repoDirectory(file);
|
|
12066
|
+
const baseKey = addopts?.join("\0");
|
|
12067
|
+
for (const environment of environments) {
|
|
12068
|
+
if (environment.directory !== null && environment.directory !== here) continue;
|
|
12069
|
+
const names = environment.targets.map((target) => target === "" ? firstTarget : target);
|
|
12070
|
+
if (environment.notKnown !== void 0) {
|
|
12071
|
+
readRecipes(names, make, void 0, environment.notKnown);
|
|
12072
|
+
continue;
|
|
12073
|
+
}
|
|
12074
|
+
const words = environment.addopts ?? [];
|
|
12075
|
+
const local = readMakeVariables(text, setByEnvironment, { name: "PYTEST_ADDOPTS", value: words.join(" "), overrides: environment.overrides });
|
|
12076
|
+
if (local.unknown.has("PYTEST_ADDOPTS")) {
|
|
12077
|
+
readRecipes(names, local, void 0, "PYTEST_ADDOPTS");
|
|
12078
|
+
continue;
|
|
12079
|
+
}
|
|
12080
|
+
if (local.fromEnvironment) {
|
|
12081
|
+
readRecipes(names, local, words, void 0, { words, ...environment.at });
|
|
12082
|
+
continue;
|
|
12083
|
+
}
|
|
12084
|
+
const expanded = expandWith(local)(local.values.get("PYTEST_ADDOPTS") ?? "");
|
|
12085
|
+
if (expanded.unknown) {
|
|
12086
|
+
readRecipes(names, local, void 0, "PYTEST_ADDOPTS");
|
|
12087
|
+
continue;
|
|
12088
|
+
}
|
|
12089
|
+
const inForce = shellWords(shellWords(expanded.text.trim()).join(" "));
|
|
12090
|
+
if (inForce.join("\0") === baseKey) continue;
|
|
12091
|
+
readRecipes(names, local, inForce, void 0);
|
|
11682
12092
|
}
|
|
11683
12093
|
}
|
|
11684
12094
|
const toxenv = readToxenvValues(environmentTexts);
|
|
@@ -11822,6 +12232,95 @@ function makeAssignmentsNamed(words) {
|
|
|
11822
12232
|
return equals > 0 && /^[A-Za-z_][\w.-]*$/.test(word.slice(0, equals)) ? [word.slice(0, equals)] : [];
|
|
11823
12233
|
});
|
|
11824
12234
|
}
|
|
12235
|
+
var MAKE_LONG_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
12236
|
+
"--directory",
|
|
12237
|
+
"--file",
|
|
12238
|
+
"--makefile",
|
|
12239
|
+
"--include-dir",
|
|
12240
|
+
"--old-file",
|
|
12241
|
+
"--assume-old",
|
|
12242
|
+
"--new-file",
|
|
12243
|
+
"--assume-new",
|
|
12244
|
+
"--what-if",
|
|
12245
|
+
"--jobs",
|
|
12246
|
+
"--load-average",
|
|
12247
|
+
"--max-load",
|
|
12248
|
+
"--output-sync",
|
|
12249
|
+
"--debug"
|
|
12250
|
+
]);
|
|
12251
|
+
function makeCommandOf(words) {
|
|
12252
|
+
let index = 0;
|
|
12253
|
+
let addopts;
|
|
12254
|
+
let addoptsAssignments = 0;
|
|
12255
|
+
if (words[0] === "env") {
|
|
12256
|
+
index = 1;
|
|
12257
|
+
while ((words[index] ?? "").startsWith("-") || /^[A-Za-z_]\w*=/.test(words[index] ?? "")) {
|
|
12258
|
+
const assignment = words[index] ?? "";
|
|
12259
|
+
if (assignment.startsWith(PYTEST_ADDOPTS_ASSIGNMENT)) {
|
|
12260
|
+
addopts = shellWords(assignment.slice(PYTEST_ADDOPTS_ASSIGNMENT.length));
|
|
12261
|
+
addoptsAssignments += 1;
|
|
12262
|
+
}
|
|
12263
|
+
index += 1;
|
|
12264
|
+
}
|
|
12265
|
+
}
|
|
12266
|
+
const head = words[index] ?? "";
|
|
12267
|
+
const name = basename(head);
|
|
12268
|
+
if (name !== "make" && name !== "gmake" && !/^\$[({]MAKE[)}]$/.test(head)) return void 0;
|
|
12269
|
+
const directories = [];
|
|
12270
|
+
const targets = [];
|
|
12271
|
+
let overrides = false;
|
|
12272
|
+
let namesFile = false;
|
|
12273
|
+
for (index += 1; index < words.length; index += 1) {
|
|
12274
|
+
const word = words[index] ?? "";
|
|
12275
|
+
if (word.startsWith("--")) {
|
|
12276
|
+
const equals = word.indexOf("=");
|
|
12277
|
+
const option = equals === -1 ? word : word.slice(0, equals);
|
|
12278
|
+
const value = equals === -1 ? MAKE_LONG_OPTIONS_WITH_VALUE.has(option) ? words[index += 1] : void 0 : word.slice(equals + 1);
|
|
12279
|
+
if (option === "--environment-overrides") overrides = true;
|
|
12280
|
+
else if (option === "--directory" && value !== void 0) directories.push(value);
|
|
12281
|
+
else if (option === "--file" || option === "--makefile") namesFile = true;
|
|
12282
|
+
continue;
|
|
12283
|
+
}
|
|
12284
|
+
if (word.startsWith("-") && word.length > 1) {
|
|
12285
|
+
for (let at = 1; at < word.length; at += 1) {
|
|
12286
|
+
const flag = word[at] ?? "";
|
|
12287
|
+
if (flag === "e") overrides = true;
|
|
12288
|
+
else if ("CfIoW".includes(flag)) {
|
|
12289
|
+
const value = at + 1 < word.length ? word.slice(at + 1) : words[index += 1];
|
|
12290
|
+
if (flag === "C" && value !== void 0) directories.push(value);
|
|
12291
|
+
if (flag === "f") namesFile = true;
|
|
12292
|
+
break;
|
|
12293
|
+
} else if (flag === "j" || flag === "l") {
|
|
12294
|
+
if (at + 1 === word.length && /^\d+(?:\.\d+)?$/.test(words[index + 1] ?? "")) index += 1;
|
|
12295
|
+
break;
|
|
12296
|
+
}
|
|
12297
|
+
}
|
|
12298
|
+
continue;
|
|
12299
|
+
}
|
|
12300
|
+
if (!word.includes("=")) targets.push(word);
|
|
12301
|
+
}
|
|
12302
|
+
return { directories, targets, overrides, namesFile, addoptsAssignments, ...addopts === void 0 ? {} : { addopts } };
|
|
12303
|
+
}
|
|
12304
|
+
var MAKE_ENVIRONMENTS_READ = 16;
|
|
12305
|
+
function distinctMakeEnvironments(environments) {
|
|
12306
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
12307
|
+
let rest;
|
|
12308
|
+
for (const environment of environments) {
|
|
12309
|
+
const key = [environment.directory ?? "", environment.notKnown ?? "", ...environment.addopts ?? [], String(environment.overrides)].join("\0");
|
|
12310
|
+
let entry = byKey.get(key);
|
|
12311
|
+
if (entry === void 0 && byKey.size < MAKE_ENVIRONMENTS_READ) {
|
|
12312
|
+
entry = { environment, targets: /* @__PURE__ */ new Set() };
|
|
12313
|
+
byKey.set(key, entry);
|
|
12314
|
+
}
|
|
12315
|
+
if (entry === void 0) {
|
|
12316
|
+
rest ??= { environment: { directory: null, targets: [], overrides: false, notKnown: "PYTEST_ADDOPTS", at: environment.at }, targets: /* @__PURE__ */ new Set() };
|
|
12317
|
+
entry = rest;
|
|
12318
|
+
}
|
|
12319
|
+
if (environment.targets.length === 0) entry.targets.add("");
|
|
12320
|
+
for (const target of environment.targets) entry.targets.add(target);
|
|
12321
|
+
}
|
|
12322
|
+
return [...byKey.values(), ...rest === void 0 ? [] : [rest]].map(({ environment, targets }) => ({ ...environment, targets: [...targets] }));
|
|
12323
|
+
}
|
|
11825
12324
|
function asToxCommand(words) {
|
|
11826
12325
|
return asPythonToolCommand(words, "tox");
|
|
11827
12326
|
}
|
|
@@ -11860,12 +12359,14 @@ function makeTargetLines(text) {
|
|
|
11860
12359
|
return lines;
|
|
11861
12360
|
}
|
|
11862
12361
|
var MAKE_EXPANSION_BUDGET = 4096;
|
|
11863
|
-
function readMakeVariables(text, setByEnvironment = () => false) {
|
|
12362
|
+
function readMakeVariables(text, setByEnvironment = () => false, environment) {
|
|
11864
12363
|
const values = /* @__PURE__ */ new Map();
|
|
11865
12364
|
const simple = /* @__PURE__ */ new Set();
|
|
11866
12365
|
const unknown = /* @__PURE__ */ new Set();
|
|
11867
12366
|
const pytestNames = /* @__PURE__ */ new Set();
|
|
11868
12367
|
const budget = { left: MAKE_EXPANSION_BUDGET };
|
|
12368
|
+
let fromEnvironment = environment !== void 0;
|
|
12369
|
+
if (environment !== void 0) values.set(environment.name, environment.value);
|
|
11869
12370
|
const forget = (name) => {
|
|
11870
12371
|
unknown.add(name);
|
|
11871
12372
|
values.delete(name);
|
|
@@ -11908,10 +12409,13 @@ function readMakeVariables(text, setByEnvironment = () => false) {
|
|
|
11908
12409
|
const value = (match[3] ?? "").trim();
|
|
11909
12410
|
if (value.toLowerCase().includes("pytest")) pytestNames.add(name);
|
|
11910
12411
|
if (unknown.has(name)) continue;
|
|
12412
|
+
const fromTheEnvironment = environment !== void 0 && name === environment.name;
|
|
12413
|
+
if (fromTheEnvironment && environment.overrides && !/^override[ \t]/.test(line)) continue;
|
|
11911
12414
|
if (conditional > 0 || operator === "!=" || operator === ":::=" || operator === "?=" && setByEnvironment(name)) {
|
|
11912
12415
|
forget(name);
|
|
11913
12416
|
continue;
|
|
11914
12417
|
}
|
|
12418
|
+
if (fromTheEnvironment && operator !== "?=") fromEnvironment = false;
|
|
11915
12419
|
if (operator === "?=") {
|
|
11916
12420
|
if (!values.has(name)) {
|
|
11917
12421
|
values.set(name, value);
|
|
@@ -11935,7 +12439,7 @@ function readMakeVariables(text, setByEnvironment = () => false) {
|
|
|
11935
12439
|
simple.delete(name);
|
|
11936
12440
|
}
|
|
11937
12441
|
}
|
|
11938
|
-
return { values, unknown, pytestNames };
|
|
12442
|
+
return { values, unknown, pytestNames, fromEnvironment };
|
|
11939
12443
|
}
|
|
11940
12444
|
function expandMakeVariables(command, values, isUnknown, budget, depth = 0) {
|
|
11941
12445
|
if (!command.includes("$")) return { text: command, unknown: false };
|
|
@@ -11986,6 +12490,15 @@ function containsWord(text, name) {
|
|
|
11986
12490
|
}
|
|
11987
12491
|
return false;
|
|
11988
12492
|
}
|
|
12493
|
+
function wordCount(text, name) {
|
|
12494
|
+
if (name === "") return 0;
|
|
12495
|
+
const word = (code) => code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
|
|
12496
|
+
let count = 0;
|
|
12497
|
+
for (let at = text.indexOf(name); at !== -1; at = text.indexOf(name, at + 1)) {
|
|
12498
|
+
if ((at === 0 || !word(text.charCodeAt(at - 1))) && (at + name.length >= text.length || !word(text.charCodeAt(at + name.length)))) count += 1;
|
|
12499
|
+
}
|
|
12500
|
+
return count;
|
|
12501
|
+
}
|
|
11989
12502
|
function makeReferencesIn(text) {
|
|
11990
12503
|
const names = [];
|
|
11991
12504
|
for (let at = text.indexOf("$"); at !== -1; at = text.indexOf("$", at + 1)) {
|
|
@@ -12372,6 +12885,7 @@ function isGovernedPytestFile(skip, file) {
|
|
|
12372
12885
|
var PYTEST_DEFAULT_NORECURSE_SEGMENT_PATTERN = /^(?:.*\.egg|\..*|_darcs|build|CVS|dist|node_modules|venv|\{arch\})$/;
|
|
12373
12886
|
var PYTEST_COLLECT_IGNORE_PATTERN = /\b(?:collect_ignore|collect_ignore_glob|pytest_ignore_collect)\b/;
|
|
12374
12887
|
var PYTEST_COLLECTION_HOOK_PATTERN = /^def[ \t]+pytest_collection_modifyitems[ \t]*\(/;
|
|
12888
|
+
var PYTEST_CLASS_COLLECTION_HOOK_PATTERN = /^[ \t]+(?:async[ \t]+)?def[ \t]+pytest_collection_modifyitems[ \t]*\(/;
|
|
12375
12889
|
var PYTEST_COLLECTION_HOOK_NAME = "pytest_collection_modifyitems";
|
|
12376
12890
|
var PYTEST_COLLECTION_HOOK_NAME_PATTERN = /\bpytest_collection_modifyitems\b/;
|
|
12377
12891
|
var PYTEST_HOOK_CONDITIONAL_PATTERN = /^(?:if|elif|else|while|try|except|finally|with|match|case|return|break|continue|yield)\b|\bskipif\b|\bif\b/;
|
|
@@ -13904,11 +14418,39 @@ function findPytestRunWideSkips(repoPath, repoFiles) {
|
|
|
13904
14418
|
const source = pythonSourceLines(text);
|
|
13905
14419
|
if (source.masked.some((code) => PYTEST_COLLECT_IGNORE_PATTERN.test(code))) ignoresCollection = true;
|
|
13906
14420
|
let last = -1;
|
|
14421
|
+
let blocked = false;
|
|
14422
|
+
const classHooks = [];
|
|
14423
|
+
const blocks = [];
|
|
14424
|
+
const openHooks = [];
|
|
13907
14425
|
for (const { first, last: final } of pythonLogicalLines(source)) {
|
|
13908
|
-
|
|
13909
|
-
|
|
14426
|
+
const indent = pythonLineIndent(source, first);
|
|
14427
|
+
const opening = source.masked[first] ?? "";
|
|
14428
|
+
if (indent !== -1) {
|
|
14429
|
+
while (blocks.length > 0 && (blocks[blocks.length - 1]?.indent ?? 0) >= indent) {
|
|
14430
|
+
if (blocks.pop()?.hook === true) openHooks.pop();
|
|
14431
|
+
}
|
|
14432
|
+
const enclosing = blocks[blocks.length - 1]?.className;
|
|
14433
|
+
if (indent > 0 && enclosing !== void 0 && PYTEST_CLASS_COLLECTION_HOOK_PATTERN.test(opening)) {
|
|
14434
|
+
classHooks.push({ line: first, className: enclosing, touches: false });
|
|
14435
|
+
openHooks.push(classHooks.length - 1);
|
|
14436
|
+
blocks.push({ indent, hook: true });
|
|
14437
|
+
} else {
|
|
14438
|
+
let logical = "";
|
|
14439
|
+
for (let line = first; line <= final; line += 1) logical += `${source.masked[line] ?? ""}
|
|
14440
|
+
`;
|
|
14441
|
+
const open = openHooks[openHooks.length - 1];
|
|
14442
|
+
if (open !== void 0 && /\bitems\b/.test(logical)) classHooks[open].touches = true;
|
|
14443
|
+
if (logical.trimEnd().endsWith(":")) {
|
|
14444
|
+
const className = /^[ \t]*class[ \t]+([A-Za-z_]\w*)/.exec(opening)?.[1];
|
|
14445
|
+
blocks.push({ indent, hook: false, ...className === void 0 ? {} : { className } });
|
|
14446
|
+
}
|
|
14447
|
+
}
|
|
14448
|
+
}
|
|
14449
|
+
if (PYTEST_COLLECTION_HOOK_PATTERN.test(opening)) {
|
|
14450
|
+
if (!blocked) last = first;
|
|
13910
14451
|
continue;
|
|
13911
14452
|
}
|
|
14453
|
+
if (blocked) continue;
|
|
13912
14454
|
let named = false;
|
|
13913
14455
|
let code = "";
|
|
13914
14456
|
for (let line = first; line <= final && !named; line += 1) {
|
|
@@ -13923,13 +14465,25 @@ function findPytestRunWideSkips(repoPath, repoFiles) {
|
|
|
13923
14465
|
}
|
|
13924
14466
|
if (named) {
|
|
13925
14467
|
last = -1;
|
|
13926
|
-
|
|
14468
|
+
blocked = true;
|
|
13927
14469
|
}
|
|
13928
14470
|
}
|
|
14471
|
+
const scope = dirname2(file);
|
|
14472
|
+
for (const hook of classHooks) {
|
|
14473
|
+
if (!hook.touches) continue;
|
|
14474
|
+
const reason = `${hook.className}.${PYTEST_COLLECTION_HOOK_NAME}`;
|
|
14475
|
+
found.push({
|
|
14476
|
+
path: file,
|
|
14477
|
+
line: hook.line + 1,
|
|
14478
|
+
scope: scope === "." ? "" : scope,
|
|
14479
|
+
kind: "unread",
|
|
14480
|
+
unread: reason,
|
|
14481
|
+
withheld: `${A1_HOOK_NOT_READ}\`${reason}\` in ${a1NotePath(file)}`
|
|
14482
|
+
});
|
|
14483
|
+
}
|
|
13929
14484
|
if (last === -1) continue;
|
|
13930
14485
|
const reading = readPytestCollectionHook(source, last, builtinsNamedIn);
|
|
13931
14486
|
if (reading === void 0) continue;
|
|
13932
|
-
const scope = dirname2(file);
|
|
13933
14487
|
const where = { path: file, line: last + 1, scope: scope === "." ? "" : scope };
|
|
13934
14488
|
if (reading.kind === "not read") {
|
|
13935
14489
|
const because = reading.builtins === void 0 ? "" : ` calls a builtin, ${reading.builtins}`;
|
|
@@ -23731,7 +24285,7 @@ function buildWitanCliSummary(report) {
|
|
|
23731
24285
|
// src/scan.ts
|
|
23732
24286
|
function runCejelScan(options) {
|
|
23733
24287
|
const identity = deriveProductIdentity(options.repoPath, options.productName);
|
|
23734
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24288
|
+
const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
23735
24289
|
const report = scoreRepoWithPublicCejel({
|
|
23736
24290
|
productSlug: identity.productSlug,
|
|
23737
24291
|
productDisplayName: options.productDisplayName ?? identity.productDisplayName,
|
|
@@ -23782,6 +24336,7 @@ export {
|
|
|
23782
24336
|
formatVerdictCapSentence,
|
|
23783
24337
|
buildVerdictExplanation,
|
|
23784
24338
|
buildRelyingPartySummary,
|
|
24339
|
+
METRIC_DERIVED_FINDING_PATTERN,
|
|
23785
24340
|
buildPlainSummary,
|
|
23786
24341
|
renderWitanHtmlReport,
|
|
23787
24342
|
renderWitanBadgeEndpoint,
|