refactor: align coverage counter provenance
This commit is contained in:
@@ -63,18 +63,18 @@ export type ProductionModuleInventory = Readonly<{
|
||||
files: readonly string[];
|
||||
preExclusionTotal: number;
|
||||
generatedExclusions: readonly string[];
|
||||
executableModules: readonly string[];
|
||||
nonExecutableModules: readonly string[];
|
||||
counterBearingModules: readonly string[];
|
||||
counterlessModules: readonly string[];
|
||||
}>;
|
||||
|
||||
export type RiskCoverageResult = Readonly<{
|
||||
status: "PASS" | "FAIL";
|
||||
selectedTotal: number;
|
||||
repositoryTotal: number;
|
||||
executableTotal: number;
|
||||
instrumentedExecutableTotal: number;
|
||||
nonExecutableTotal: number;
|
||||
nonExecutableModules: readonly string[];
|
||||
counterBearingTotal: number;
|
||||
instrumentedCounterBearingTotal: number;
|
||||
counterlessTotal: number;
|
||||
counterlessModules: readonly string[];
|
||||
preExclusionTotal: number;
|
||||
generatedExclusionCount: number;
|
||||
generatedExclusions: readonly string[];
|
||||
@@ -370,13 +370,12 @@ type TypeScriptAstNode = Readonly<{
|
||||
type?: unknown;
|
||||
body?: unknown;
|
||||
declaration?: unknown;
|
||||
specifiers?: unknown;
|
||||
declare?: unknown;
|
||||
const?: unknown;
|
||||
importKind?: unknown;
|
||||
}>;
|
||||
|
||||
function statementIsExecutable(value: unknown): boolean {
|
||||
function statementIsCoverageCounterBearing(value: unknown): boolean {
|
||||
if (!isRecord(value) || typeof value.type !== "string") {
|
||||
throw new TypeError("TypeScript parser returned an invalid statement");
|
||||
}
|
||||
@@ -393,7 +392,7 @@ function statementIsExecutable(value: unknown): boolean {
|
||||
return false;
|
||||
}
|
||||
if (statement.type === "ImportDeclaration") {
|
||||
return Array.isArray(statement.specifiers) && statement.specifiers.length === 0;
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
statement.type === "ExportAllDeclaration" ||
|
||||
@@ -406,7 +405,7 @@ function statementIsExecutable(value: unknown): boolean {
|
||||
statement.type === "ExportDefaultDeclaration"
|
||||
) {
|
||||
return statement.declaration !== null && statement.declaration !== undefined
|
||||
? statementIsExecutable(statement.declaration)
|
||||
? statementIsCoverageCounterBearing(statement.declaration)
|
||||
: false;
|
||||
}
|
||||
if (
|
||||
@@ -427,7 +426,7 @@ function statementIsExecutable(value: unknown): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function hasExecutableTypeScriptStatements(
|
||||
export function hasCoverageCounterBearingStatements(
|
||||
source: string,
|
||||
relativePath: string,
|
||||
): boolean {
|
||||
@@ -454,7 +453,7 @@ export function hasExecutableTypeScriptStatements(
|
||||
if (!isRecord(parsed) || !Array.isArray(parsed.body)) {
|
||||
throw new TypeError("TypeScript parser returned an invalid program");
|
||||
}
|
||||
return parsed.body.some(statementIsExecutable);
|
||||
return parsed.body.some(statementIsCoverageCounterBearing);
|
||||
}
|
||||
|
||||
export async function buildProductionModuleInventory(
|
||||
@@ -481,8 +480,8 @@ export async function buildProductionModuleInventory(
|
||||
}
|
||||
|
||||
const allModules: string[] = [];
|
||||
const executableModules: string[] = [];
|
||||
const nonExecutableModules: string[] = [];
|
||||
const counterBearingModules: string[] = [];
|
||||
const counterlessModules: string[] = [];
|
||||
async function visit(relativeDirectory: string): Promise<void> {
|
||||
const absoluteDirectory = path.join(repositoryRoot, relativeDirectory);
|
||||
const entries = await readDirectory(absoluteDirectory);
|
||||
@@ -543,10 +542,10 @@ export async function buildProductionModuleInventory(
|
||||
await handle?.close();
|
||||
}
|
||||
allModules.push(relativeTarget);
|
||||
if (hasExecutableTypeScriptStatements(source, relativeTarget)) {
|
||||
executableModules.push(relativeTarget);
|
||||
if (hasCoverageCounterBearingStatements(source, relativeTarget)) {
|
||||
counterBearingModules.push(relativeTarget);
|
||||
} else {
|
||||
nonExecutableModules.push(relativeTarget);
|
||||
counterlessModules.push(relativeTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,11 +564,11 @@ export async function buildProductionModuleInventory(
|
||||
files: Object.freeze(inventory),
|
||||
preExclusionTotal: allModules.length,
|
||||
generatedExclusions: Object.freeze([...generatedPaths].sort()),
|
||||
executableModules: Object.freeze(
|
||||
executableModules.filter((file) => !generated.has(file)).sort(),
|
||||
counterBearingModules: Object.freeze(
|
||||
counterBearingModules.filter((file) => !generated.has(file)).sort(),
|
||||
),
|
||||
nonExecutableModules: Object.freeze(
|
||||
nonExecutableModules.filter((file) => !generated.has(file)).sort(),
|
||||
counterlessModules: Object.freeze(
|
||||
counterlessModules.filter((file) => !generated.has(file)).sort(),
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -718,22 +717,22 @@ export function evaluateRiskCoverage(input: Readonly<{
|
||||
if (new Set(inventory).size !== inventory.length) {
|
||||
throw new TypeError("production module inventory contains a duplicate path");
|
||||
}
|
||||
const executableModules = input.inventory.executableModules.map((file) =>
|
||||
exactSourcePath(file, "executable inventory path"),
|
||||
const counterBearingModules = input.inventory.counterBearingModules.map((file) =>
|
||||
exactSourcePath(file, "counter-bearing inventory path"),
|
||||
);
|
||||
const nonExecutableModules = input.inventory.nonExecutableModules.map((file) =>
|
||||
exactSourcePath(file, "non-executable inventory path"),
|
||||
const counterlessModules = input.inventory.counterlessModules.map((file) =>
|
||||
exactSourcePath(file, "counterless inventory path"),
|
||||
);
|
||||
const executableSet = new Set(executableModules);
|
||||
const nonExecutableSet = new Set(nonExecutableModules);
|
||||
const partition = [...executableModules, ...nonExecutableModules].sort();
|
||||
const counterBearingSet = new Set(counterBearingModules);
|
||||
const counterlessSet = new Set(counterlessModules);
|
||||
const partition = [...counterBearingModules, ...counterlessModules].sort();
|
||||
if (
|
||||
executableSet.size !== executableModules.length ||
|
||||
nonExecutableSet.size !== nonExecutableModules.length ||
|
||||
executableModules.some((file) => nonExecutableSet.has(file)) ||
|
||||
counterBearingSet.size !== counterBearingModules.length ||
|
||||
counterlessSet.size !== counterlessModules.length ||
|
||||
counterBearingModules.some((file) => counterlessSet.has(file)) ||
|
||||
partition.join("\n") !== [...inventory].sort().join("\n")
|
||||
) {
|
||||
throw new TypeError("production inventory executable provenance is inconsistent");
|
||||
throw new TypeError("production inventory counter-bearing provenance is inconsistent");
|
||||
}
|
||||
const generatedExclusions = input.inventory.generatedExclusions.map((file) =>
|
||||
exactSourcePath(file, "generated exclusion"),
|
||||
@@ -780,14 +779,14 @@ export function evaluateRiskCoverage(input: Readonly<{
|
||||
);
|
||||
});
|
||||
const zeroCoverageSet = new Set(zeroCoverageModules);
|
||||
const zeroExecutableModules = zeroCoverageModules.filter((file) =>
|
||||
executableSet.has(file),
|
||||
const zeroCounterBearingModules = zeroCoverageModules.filter((file) =>
|
||||
counterBearingSet.has(file),
|
||||
);
|
||||
const nonExecutableWithCounters = nonExecutableModules.filter((file) => {
|
||||
const counterlessWithCounters = counterlessModules.filter((file) => {
|
||||
const metrics = selected.get(file);
|
||||
return metrics !== undefined && !zeroCoverageSet.has(file);
|
||||
});
|
||||
const instrumentedExecutableTotal = executableModules.filter((file) => {
|
||||
const instrumentedCounterBearingTotal = counterBearingModules.filter((file) => {
|
||||
const metrics = selected.get(file);
|
||||
return metrics !== undefined && !zeroCoverageSet.has(file);
|
||||
}).length;
|
||||
@@ -827,18 +826,18 @@ export function evaluateRiskCoverage(input: Readonly<{
|
||||
.filter(
|
||||
(file) =>
|
||||
!selected.has(file) ||
|
||||
(executableSet.has(file) && zeroCoverageSet.has(file)),
|
||||
(counterBearingSet.has(file) && zeroCoverageSet.has(file)),
|
||||
)
|
||||
.sort();
|
||||
failures.push(
|
||||
...inventory
|
||||
.filter((file) => !selected.has(file))
|
||||
.map((file) => `production module missing from coverage: ${file}`),
|
||||
...zeroExecutableModules.map(
|
||||
(file) => `production module has zero coverage totals: ${file}`,
|
||||
...zeroCounterBearingModules.map(
|
||||
(file) => `counter-bearing module has zero coverage totals: ${file}`,
|
||||
),
|
||||
...nonExecutableWithCounters.map(
|
||||
(file) => `non-executable module has coverage counters: ${file}`,
|
||||
...counterlessWithCounters.map(
|
||||
(file) => `counterless module has coverage counters: ${file}`,
|
||||
),
|
||||
);
|
||||
for (const modulePolicy of input.policy.criticalModules) {
|
||||
@@ -846,8 +845,10 @@ export function evaluateRiskCoverage(input: Readonly<{
|
||||
failures.push(`critical module is outside production inventory: ${modulePolicy.path}`);
|
||||
continue;
|
||||
}
|
||||
if (nonExecutableSet.has(modulePolicy.path)) {
|
||||
failures.push(`critical module cannot be non-executable: ${modulePolicy.path}`);
|
||||
if (counterlessSet.has(modulePolicy.path)) {
|
||||
failures.push(
|
||||
`critical policy-sensitive module cannot be counterless: ${modulePolicy.path}`,
|
||||
);
|
||||
}
|
||||
const actual = selected.get(modulePolicy.path);
|
||||
if (!actual) {
|
||||
@@ -863,8 +864,10 @@ export function evaluateRiskCoverage(input: Readonly<{
|
||||
if (!inventorySet.has(highRiskPath)) {
|
||||
failures.push(`high-risk module is outside production inventory: ${highRiskPath}`);
|
||||
}
|
||||
if (nonExecutableSet.has(highRiskPath)) {
|
||||
failures.push(`high-risk module cannot be non-executable: ${highRiskPath}`);
|
||||
if (counterlessSet.has(highRiskPath)) {
|
||||
failures.push(
|
||||
`high-risk policy-sensitive module cannot be counterless: ${highRiskPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const waiver of input.policy.waivers) {
|
||||
@@ -881,10 +884,10 @@ export function evaluateRiskCoverage(input: Readonly<{
|
||||
status: failures.length === 0 ? "PASS" : "FAIL",
|
||||
selectedTotal: inventory.length - uncoveredModules.length,
|
||||
repositoryTotal: inventory.length,
|
||||
executableTotal: executableModules.length,
|
||||
instrumentedExecutableTotal,
|
||||
nonExecutableTotal: nonExecutableModules.length,
|
||||
nonExecutableModules: Object.freeze([...nonExecutableModules].sort()),
|
||||
counterBearingTotal: counterBearingModules.length,
|
||||
instrumentedCounterBearingTotal,
|
||||
counterlessTotal: counterlessModules.length,
|
||||
counterlessModules: Object.freeze([...counterlessModules].sort()),
|
||||
preExclusionTotal: input.inventory.preExclusionTotal,
|
||||
generatedExclusionCount: generatedExclusions.length,
|
||||
generatedExclusions: Object.freeze([...generatedExclusions].sort()),
|
||||
|
||||
Reference in New Issue
Block a user