Files
renovate/lib/logger/once.ts
Sebastian Poxhofer a473e73b3e fix(linters): forbid inert counts in v8 coverage-ignore comments (#45287)
Adds a custom oxlint rule renovate/v8-ignore-no-count that flags the
count form '/* v8 ignore next <N> */'. The count is not honored by
vitest's ast-v8-to-istanbul hint parser, which captures only the
keyword (if/else/next/file); 'next' always exempts exactly the next
AST node. Trailing counts therefore suggest a line range that is never
applied and mislead readers.

The rule is autofixable (drops the count, preserving actual behavior)
and has been applied to all existing occurrences.
2026-08-22 11:25:31 +00:00

85 lines
2.2 KiB
TypeScript

// Do not static import `bunyan` here!
// Otherwise otel can't instrument it.
import { createHash } from 'node:crypto';
import { stringify } from 'safe-stable-stringify';
type OmitFn = (...args: any[]) => any;
// TODO: use `callsite` package instead?
/**
* Get the single frame of this function's callers stack.
*
* @param omitFn Starting from this function, stack frames will be ignored.
* @returns The string containing file name, line number and column name.
*
* @example getCallSite() // => 'Object.<anonymous> (/path/to/file.js:10:15)'
*/
function getCallSite(omitFn: OmitFn): string | null {
const stackTraceLimitOrig = Error.stackTraceLimit;
// We don't use `Error.captureStackTrace` directly, we simply restore it later.
// oxlint-disable-next-line typescript/unbound-method
const prepareStackTraceOrig = Error.prepareStackTrace;
let result: string | null = null;
try {
const res: { stack: string[] } = { stack: [] };
Error.stackTraceLimit = 1;
Error.prepareStackTrace = (_err, stack) => stack;
Error.captureStackTrace(res, omitFn);
const [callsite] = res.stack;
// v8 ignore else -- TODO: add test #40625
if (callsite) {
result = callsite.toString();
}
/* v8 ignore next -- should not happen */
} catch {
// no-op
} finally {
Error.stackTraceLimit = stackTraceLimitOrig;
Error.prepareStackTrace = prepareStackTraceOrig;
}
return result;
}
const keys = new Set<string>();
export function once(
callback: () => void,
omitFn: OmitFn = once,
p1: string | Record<string, any>,
p2?: string,
): void {
const callsite = getCallSite(omitFn);
/* v8 ignore next -- should not happen */
if (!callsite) {
return;
}
const paramsKey = hashParams(p1, p2);
const key = `${callsite}|${paramsKey}`;
if (!keys.has(key)) {
keys.add(key);
callback();
}
}
/**
* Before processing each repository,
* all keys are supposed to be reset.
*/
export function reset(): void {
keys.clear();
}
function hashParams(p1: string | Record<string, any>, p2?: string): string {
const data =
p2 === undefined ? stringify(p1) : `${stringify(p1)}|${stringify(p2)}`;
return createHash('sha256').update(data).digest('hex');
}