tamperward 2.23.7 → 2.23.8
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/dist/cli/index.js +483 -219
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2053,7 +2053,135 @@ var init_lint_suppression = __esm({
|
|
|
2053
2053
|
}
|
|
2054
2054
|
});
|
|
2055
2055
|
|
|
2056
|
+
// src/detectors/repo.ts
|
|
2057
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2058
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2059
|
+
import { join as join6 } from "node:path";
|
|
2060
|
+
function trackedFiles(ctx) {
|
|
2061
|
+
if (!ctx) return null;
|
|
2062
|
+
if (ctx.trackedFiles) return ctx.trackedFiles;
|
|
2063
|
+
if (!ctx.cwd) return null;
|
|
2064
|
+
const hit = cache2.get(ctx.cwd);
|
|
2065
|
+
if (hit !== void 0) return hit;
|
|
2066
|
+
let files = null;
|
|
2067
|
+
try {
|
|
2068
|
+
files = execFileSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
2069
|
+
cwd: ctx.cwd,
|
|
2070
|
+
encoding: "utf8",
|
|
2071
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
2072
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2073
|
+
}).split("\0").filter(Boolean);
|
|
2074
|
+
} catch {
|
|
2075
|
+
files = null;
|
|
2076
|
+
}
|
|
2077
|
+
cache2.set(ctx.cwd, files);
|
|
2078
|
+
return files;
|
|
2079
|
+
}
|
|
2080
|
+
function trackedContent(path, ctx) {
|
|
2081
|
+
if (!ctx) return null;
|
|
2082
|
+
if (ctx.trackedContents) return ctx.trackedContents[path] ?? null;
|
|
2083
|
+
if (!ctx.cwd) return null;
|
|
2084
|
+
const key2 = ctx.cwd + "\0" + path;
|
|
2085
|
+
const hit = contentCache.get(key2);
|
|
2086
|
+
if (hit !== void 0) return hit;
|
|
2087
|
+
let src = null;
|
|
2088
|
+
try {
|
|
2089
|
+
src = readFileSync3(join6(ctx.cwd, path), "utf8");
|
|
2090
|
+
} catch {
|
|
2091
|
+
src = null;
|
|
2092
|
+
}
|
|
2093
|
+
contentCache.set(key2, src);
|
|
2094
|
+
return src;
|
|
2095
|
+
}
|
|
2096
|
+
function branchRefs(ctx) {
|
|
2097
|
+
if (!ctx?.cwd) return null;
|
|
2098
|
+
const hit = refsCache.get(ctx.cwd);
|
|
2099
|
+
if (hit !== void 0) return hit;
|
|
2100
|
+
let refs = null;
|
|
2101
|
+
try {
|
|
2102
|
+
const out3 = execFileSync5("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/remotes"], {
|
|
2103
|
+
cwd: ctx.cwd,
|
|
2104
|
+
encoding: "utf8",
|
|
2105
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2106
|
+
});
|
|
2107
|
+
refs = new Set(out3.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
2108
|
+
} catch {
|
|
2109
|
+
refs = null;
|
|
2110
|
+
}
|
|
2111
|
+
refsCache.set(ctx.cwd, refs);
|
|
2112
|
+
return refs;
|
|
2113
|
+
}
|
|
2114
|
+
function branchExists(name, ctx) {
|
|
2115
|
+
const refs = branchRefs(ctx);
|
|
2116
|
+
if (!refs) return null;
|
|
2117
|
+
if (refs.has(name)) return true;
|
|
2118
|
+
for (const r of refs) if (r.endsWith("/" + name)) return true;
|
|
2119
|
+
return false;
|
|
2120
|
+
}
|
|
2121
|
+
function revIsHead(rev2, ctx) {
|
|
2122
|
+
if (!ctx?.cwd || rev2.startsWith("-")) return null;
|
|
2123
|
+
const parse = (r) => {
|
|
2124
|
+
try {
|
|
2125
|
+
return execFileSync5("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${r}^{commit}`], {
|
|
2126
|
+
cwd: ctx.cwd,
|
|
2127
|
+
encoding: "utf8",
|
|
2128
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2129
|
+
}).trim();
|
|
2130
|
+
} catch {
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
const head = parse("HEAD");
|
|
2135
|
+
const target = parse(rev2);
|
|
2136
|
+
if (!head || !target) return null;
|
|
2137
|
+
return head === target;
|
|
2138
|
+
}
|
|
2139
|
+
function defaultBranch(ctx) {
|
|
2140
|
+
if (!ctx?.cwd) return null;
|
|
2141
|
+
const hit = branchCache.get(ctx.cwd);
|
|
2142
|
+
if (hit !== void 0) return hit;
|
|
2143
|
+
let name = null;
|
|
2144
|
+
try {
|
|
2145
|
+
const ref = execFileSync5("git", ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"], {
|
|
2146
|
+
cwd: ctx.cwd,
|
|
2147
|
+
encoding: "utf8",
|
|
2148
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2149
|
+
}).trim();
|
|
2150
|
+
name = ref.replace(/^origin\//, "") || null;
|
|
2151
|
+
} catch {
|
|
2152
|
+
name = null;
|
|
2153
|
+
}
|
|
2154
|
+
branchCache.set(ctx.cwd, name);
|
|
2155
|
+
return name;
|
|
2156
|
+
}
|
|
2157
|
+
function containsProtected(token, policy, category, ctx, except) {
|
|
2158
|
+
const dir = dirOf(token);
|
|
2159
|
+
if (!dir || dir === "." || dir === "/" || dir.startsWith("-")) return false;
|
|
2160
|
+
const counts = (p) => isProtected(p, policy, category) && !(except && isProtected(p, policy, except));
|
|
2161
|
+
const files = trackedFiles(ctx);
|
|
2162
|
+
if (files) {
|
|
2163
|
+
const prefix = dir + "/";
|
|
2164
|
+
return files.some((f) => f.startsWith(prefix) && counts(f));
|
|
2165
|
+
}
|
|
2166
|
+
if (counts(dir + "/probe")) return true;
|
|
2167
|
+
const base = dir.split("/").pop() ?? "";
|
|
2168
|
+
return category === "tests" && /^(?:test|tests|__tests__|spec|specs)$/i.test(base);
|
|
2169
|
+
}
|
|
2170
|
+
var cache2, contentCache, branchCache, refsCache, dirOf;
|
|
2171
|
+
var init_repo = __esm({
|
|
2172
|
+
"src/detectors/repo.ts"() {
|
|
2173
|
+
"use strict";
|
|
2174
|
+
init_policy();
|
|
2175
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
2176
|
+
contentCache = /* @__PURE__ */ new Map();
|
|
2177
|
+
branchCache = /* @__PURE__ */ new Map();
|
|
2178
|
+
refsCache = /* @__PURE__ */ new Map();
|
|
2179
|
+
dirOf = (t) => t.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2056
2183
|
// src/detectors/test-skip.ts
|
|
2184
|
+
import { posix as posix2 } from "node:path";
|
|
2057
2185
|
function matchesOutsideString(p, content, lang) {
|
|
2058
2186
|
if (p.comment) return p.re.test(content);
|
|
2059
2187
|
const re = p.re.global ? p.re : new RegExp(p.re.source, p.re.flags + "g");
|
|
@@ -2123,61 +2251,29 @@ function astContext(path, source) {
|
|
|
2123
2251
|
function symbolAt(ctx, node) {
|
|
2124
2252
|
return ctx.checker.getSymbolAtLocation(node) ?? null;
|
|
2125
2253
|
}
|
|
2126
|
-
function
|
|
2127
|
-
const
|
|
2128
|
-
|
|
2129
|
-
const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
for (const
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
if (!clause) continue;
|
|
2138
|
-
if (clause.name && stmt.moduleSpecifier.text.startsWith("node:test")) add(clause.name);
|
|
2139
|
-
const bindings = clause.namedBindings;
|
|
2140
|
-
if (bindings && ts.isNamedImports(bindings)) {
|
|
2141
|
-
for (const el of bindings.elements) {
|
|
2142
|
-
const imported = el.propertyName?.text ?? el.name.text;
|
|
2143
|
-
if (JS_RUNNERS.has(imported)) add(el.name);
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
continue;
|
|
2147
|
-
}
|
|
2148
|
-
if (!ts.isVariableStatement(stmt)) continue;
|
|
2149
|
-
for (const decl of stmt.declarationList.declarations) {
|
|
2150
|
-
if (ts.isObjectBindingPattern(decl.name) && decl.initializer) {
|
|
2151
|
-
const mod = requiredModule(decl.initializer);
|
|
2152
|
-
if (!mod || !JS_TEST_MODULES.has(mod)) continue;
|
|
2153
|
-
for (const el of decl.name.elements) {
|
|
2154
|
-
if (!ts.isIdentifier(el.name)) continue;
|
|
2155
|
-
const imported = el.propertyName && ts.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
|
|
2156
|
-
if (JS_RUNNERS.has(imported)) add(el.name);
|
|
2157
|
-
}
|
|
2158
|
-
} else if (ts.isIdentifier(decl.name) && decl.initializer && ts.isIdentifier(decl.initializer)) {
|
|
2159
|
-
simpleAliases.push({ local: decl.name, source: decl.initializer });
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
for (let pass = 0; pass < simpleAliases.length + 1; pass++) {
|
|
2164
|
-
let changed = false;
|
|
2165
|
-
for (const alias of simpleAliases) {
|
|
2166
|
-
const localSym = symbolAt(ctx, alias.local);
|
|
2167
|
-
if (!localSym || out3.has(localSym)) continue;
|
|
2168
|
-
const sourceSym = symbolAt(ctx, alias.source);
|
|
2169
|
-
const sourceBinding = sourceSym ? out3.get(sourceSym) : void 0;
|
|
2170
|
-
const implicitGlobal = sourceSym == null && JS_RUNNERS.has(alias.source.text);
|
|
2171
|
-
if (!sourceBinding && !implicitGlobal) continue;
|
|
2172
|
-
out3.set(localSym, {
|
|
2173
|
-
causes: [alias.local, ...sourceBinding?.causes ?? []]
|
|
2174
|
-
});
|
|
2175
|
-
changed = true;
|
|
2176
|
-
}
|
|
2177
|
-
if (!changed) break;
|
|
2254
|
+
function relativeCandidates(fromPath, spec) {
|
|
2255
|
+
const base = posix2.normalize(posix2.join(posix2.dirname(fromPath), spec));
|
|
2256
|
+
if (base.startsWith("../") || base.startsWith("/")) return [];
|
|
2257
|
+
const bases = [base];
|
|
2258
|
+
const swapped = base.replace(/\.([mc]?)js$/, ".$1ts");
|
|
2259
|
+
if (swapped !== base) bases.push(swapped);
|
|
2260
|
+
const out3 = [];
|
|
2261
|
+
for (const b of bases) {
|
|
2262
|
+
out3.push(b);
|
|
2263
|
+
for (const ext of MODULE_EXTENSIONS) out3.push(b + ext);
|
|
2264
|
+
for (const ext of MODULE_EXTENSIONS) out3.push(b + "/index" + ext);
|
|
2178
2265
|
}
|
|
2179
2266
|
return out3;
|
|
2180
2267
|
}
|
|
2268
|
+
function isExported(stmt) {
|
|
2269
|
+
return ts.canHaveModifiers(stmt) && (ts.getModifiers(stmt) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
2270
|
+
}
|
|
2271
|
+
function cjsExportTarget(expr) {
|
|
2272
|
+
const isModuleExports = (e) => ts.isIdentifier(e) && e.text === "exports" || ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.expression.text === "module" && e.name.text === "exports";
|
|
2273
|
+
if (isModuleExports(expr)) return "";
|
|
2274
|
+
if (ts.isPropertyAccessExpression(expr) && isModuleExports(expr.expression)) return expr.name.text;
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2181
2277
|
function topLevelStaticStrings(ctx) {
|
|
2182
2278
|
const pending = /* @__PURE__ */ new Map();
|
|
2183
2279
|
for (const stmt of ctx.sf.statements) {
|
|
@@ -2244,7 +2340,7 @@ function staticPropertyName(expr, ctx, strings) {
|
|
|
2244
2340
|
}
|
|
2245
2341
|
return null;
|
|
2246
2342
|
}
|
|
2247
|
-
function runnerChain(expr, ctx,
|
|
2343
|
+
function runnerChain(expr, ctx, binder, strings) {
|
|
2248
2344
|
const props = [];
|
|
2249
2345
|
const causes = [];
|
|
2250
2346
|
let cur = expr;
|
|
@@ -2264,16 +2360,29 @@ function runnerChain(expr, ctx, runners, strings) {
|
|
|
2264
2360
|
if (props.length === 1) terminalNode = cur.argumentExpression;
|
|
2265
2361
|
cur = cur.expression;
|
|
2266
2362
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
if (
|
|
2270
|
-
const
|
|
2271
|
-
if (!
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2363
|
+
let verdict2;
|
|
2364
|
+
let namespace = null;
|
|
2365
|
+
if (ts.isIdentifier(cur)) {
|
|
2366
|
+
const sym = symbolAt(ctx, cur);
|
|
2367
|
+
if (!sym) {
|
|
2368
|
+
verdict2 = JS_RUNNERS.has(cur.text) ? RUNNER : UNKNOWN;
|
|
2369
|
+
} else {
|
|
2370
|
+
verdict2 = binder.classify(sym);
|
|
2371
|
+
if (verdict2.kind !== "runner") namespace = binder.namespaceOf(sym);
|
|
2372
|
+
}
|
|
2373
|
+
} else {
|
|
2374
|
+
namespace = requiredModule(cur);
|
|
2375
|
+
verdict2 = namespace == null ? UNKNOWN : NON_RUNNER;
|
|
2376
|
+
}
|
|
2377
|
+
let root = ts.isIdentifier(cur) ? cur.text : "";
|
|
2378
|
+
if (namespace != null && props.length > 0) {
|
|
2379
|
+
verdict2 = withCause(binder.memberOf(namespace, props[0]), cur);
|
|
2380
|
+
if (verdict2.kind === "runner") root = props.shift() ?? root;
|
|
2275
2381
|
}
|
|
2276
|
-
|
|
2382
|
+
if (verdict2.kind === "unknown") return "unknown";
|
|
2383
|
+
if (verdict2.kind === "non-runner") return null;
|
|
2384
|
+
causes.push(...verdict2.causes);
|
|
2385
|
+
return { root, props, terminalNode, causes };
|
|
2277
2386
|
}
|
|
2278
2387
|
function optionDisables(value) {
|
|
2279
2388
|
return !(value.kind === ts.SyntaxKind.FalseKeyword || ts.isNumericLiteral(value) && Number(value.text) === 0);
|
|
@@ -2292,11 +2401,12 @@ function structuralNodeKey(node, sf) {
|
|
|
2292
2401
|
}
|
|
2293
2402
|
return `${node.kind}(${children.map((child) => structuralNodeKey(child, sf)).join(",")})`;
|
|
2294
2403
|
}
|
|
2295
|
-
function semanticSkipHits(ctx) {
|
|
2296
|
-
const runners = importAliases(ctx);
|
|
2404
|
+
function semanticSkipHits(ctx, binder) {
|
|
2297
2405
|
const strings = topLevelStaticStrings(ctx);
|
|
2298
2406
|
const hits = [];
|
|
2407
|
+
const unowned = /* @__PURE__ */ new Set();
|
|
2299
2408
|
const callOrdinals = /* @__PURE__ */ new Map();
|
|
2409
|
+
const lineOf = (pos) => ctx.sf.getLineAndCharacterOfPosition(pos).line + 1;
|
|
2300
2410
|
const visit = (node) => {
|
|
2301
2411
|
if (ts.isCallExpression(node)) {
|
|
2302
2412
|
const callText = node.getText(ctx.sf).trim();
|
|
@@ -2304,8 +2414,13 @@ function semanticSkipHits(ctx) {
|
|
|
2304
2414
|
const ordinal = (callOrdinals.get(callIdentity) ?? 0) + 1;
|
|
2305
2415
|
callOrdinals.set(callIdentity, ordinal);
|
|
2306
2416
|
const callKey = `${callIdentity}\0${ordinal}`;
|
|
2307
|
-
const chain = runnerChain(node.expression, ctx,
|
|
2308
|
-
if (chain) {
|
|
2417
|
+
const chain = runnerChain(node.expression, ctx, binder, strings);
|
|
2418
|
+
if (chain === "unknown") {
|
|
2419
|
+
const callee = node.expression;
|
|
2420
|
+
const first = lineOf(callee.getStart(ctx.sf));
|
|
2421
|
+
const last = lineOf(callee.getEnd());
|
|
2422
|
+
for (let line = first; line <= last; line++) unowned.add(line);
|
|
2423
|
+
} else if (chain) {
|
|
2309
2424
|
const { props, terminalNode, causes } = chain;
|
|
2310
2425
|
const terminal = props.at(-1);
|
|
2311
2426
|
const push = (target, extraCauses, why) => {
|
|
@@ -2317,7 +2432,7 @@ function semanticSkipHits(ctx) {
|
|
|
2317
2432
|
evidence: callText
|
|
2318
2433
|
});
|
|
2319
2434
|
};
|
|
2320
|
-
if (terminal && (terminal === "skip" || terminal === "only" || terminal === "todo") && props.slice(0, -1).every((p) => JS_CHAIN_MODIFIERS.has(p))) {
|
|
2435
|
+
if (terminal && (terminal === "skip" || terminal === "only" || terminal === "todo") && props.slice(0, -1).every((p) => JS_CHAIN_MODIFIERS.has(p) || JS_RUNNERS.has(p))) {
|
|
2321
2436
|
push(terminalNode, [], "a .skip/.only/.todo marker");
|
|
2322
2437
|
} else if (terminal && ["skipIf", "runIf"].includes(terminal) && props.length === 1) {
|
|
2323
2438
|
push(terminalNode, [], "a .skipIf()/.runIf() condition (the test runs only when the condition allows)");
|
|
@@ -2356,20 +2471,32 @@ function semanticSkipHits(ctx) {
|
|
|
2356
2471
|
ts.forEachChild(node, visit);
|
|
2357
2472
|
};
|
|
2358
2473
|
visit(ctx.sf);
|
|
2359
|
-
return hits;
|
|
2474
|
+
return { hits, unowned };
|
|
2475
|
+
}
|
|
2476
|
+
function moduleSource(changes, side, ctx) {
|
|
2477
|
+
const touched = /* @__PURE__ */ new Map();
|
|
2478
|
+
for (const c of changes) {
|
|
2479
|
+
if (c.kind !== "file") continue;
|
|
2480
|
+
touched.set(c.path, c[side]);
|
|
2481
|
+
if (side === "before" && c.oldPath != null) touched.set(c.oldPath, c.before);
|
|
2482
|
+
}
|
|
2483
|
+
return (path) => touched.has(path) ? touched.get(path) ?? null : trackedContent(path, ctx);
|
|
2360
2484
|
}
|
|
2361
|
-
function astSkipHits(c) {
|
|
2362
|
-
if (c.after == null) return
|
|
2485
|
+
function astSkipHits(c, resolvers) {
|
|
2486
|
+
if (c.after == null) return NO_AST;
|
|
2363
2487
|
const added = addedLineNumbers(c);
|
|
2364
|
-
if (added.size === 0) return
|
|
2488
|
+
if (added.size === 0) return NO_AST;
|
|
2365
2489
|
const afterCtx = astContext(c.path, c.after);
|
|
2366
|
-
if (!afterCtx) return
|
|
2490
|
+
if (!afterCtx) return NO_AST;
|
|
2367
2491
|
const beforeCtx = c.before == null ? null : astContext(c.path, c.before);
|
|
2368
|
-
const beforeKeys = beforeCtx == null ? null : new Set(
|
|
2492
|
+
const beforeKeys = beforeCtx == null ? null : new Set(
|
|
2493
|
+
semanticSkipHits(beforeCtx, resolvers.before.binderFor(c.oldPath ?? c.path, beforeCtx)).hits.map((hit) => hit.semanticKey)
|
|
2494
|
+
);
|
|
2369
2495
|
const lineOf = (node) => afterCtx.sf.getLineAndCharacterOfPosition(node.getStart(afterCtx.sf)).line + 1;
|
|
2370
2496
|
const hits = [];
|
|
2371
2497
|
const seen = /* @__PURE__ */ new Set();
|
|
2372
|
-
|
|
2498
|
+
const analysis = semanticSkipHits(afterCtx, resolvers.after.binderFor(c.path, afterCtx));
|
|
2499
|
+
for (const hit of analysis.hits) {
|
|
2373
2500
|
const directLine = lineOf(hit.terminalNode);
|
|
2374
2501
|
let findingLine = added.has(directLine) ? directLine : null;
|
|
2375
2502
|
if (findingLine == null) {
|
|
@@ -2389,21 +2516,22 @@ function astSkipHits(c) {
|
|
|
2389
2516
|
seen.add(dedupe);
|
|
2390
2517
|
hits.push({ line: findingLine, why: hit.why, evidence: hit.evidence });
|
|
2391
2518
|
}
|
|
2392
|
-
return { hits, authoritative: true };
|
|
2519
|
+
return { hits, authoritative: true, unowned: analysis.unowned };
|
|
2393
2520
|
}
|
|
2394
|
-
var RULE4, acc, JS_RUNNER, JS_MOD, PATTERNS2, JS_RUNNERS, JS_CHAIN_MODIFIERS, JS_TEST_MODULES, testSkip;
|
|
2521
|
+
var RULE4, acc, JS_RUNNER, JS_MOD, PATTERNS2, JS_RUNNERS, JS_CHAIN_MODIFIERS, JS_DEFAULT_RUNNER_MODULES, JS_TEST_MODULES, RUNNER, NON_RUNNER, UNKNOWN, withCause, MODULE_EXTENSIONS, RunnerResolver, ModuleBinder, NO_AST, testSkip;
|
|
2395
2522
|
var init_test_skip = __esm({
|
|
2396
2523
|
"src/detectors/test-skip.ts"() {
|
|
2397
2524
|
"use strict";
|
|
2398
2525
|
init_ts_lazy();
|
|
2399
2526
|
init_select();
|
|
2400
2527
|
init_policy();
|
|
2528
|
+
init_repo();
|
|
2401
2529
|
init_files();
|
|
2402
2530
|
init_finding();
|
|
2403
2531
|
RULE4 = "test-skip";
|
|
2404
2532
|
acc = (names) => `(?:\\s*\\.\\s*(?:${names})(?![\\w$])|\\s*\\[\\s*['"\`](?:${names})['"\`]\\s*\\])`;
|
|
2405
2533
|
JS_RUNNER = "\\b(?:it|test|describe|suite)";
|
|
2406
|
-
JS_MOD = "concurrent|sequential|shuffle";
|
|
2534
|
+
JS_MOD = "concurrent|sequential|shuffle|serial|parallel";
|
|
2407
2535
|
PATTERNS2 = {
|
|
2408
2536
|
js: [
|
|
2409
2537
|
// The skip/only/todo family on a runner, by dot-or-bracket access, with one optional
|
|
@@ -2482,7 +2610,8 @@ var init_test_skip = __esm({
|
|
|
2482
2610
|
]
|
|
2483
2611
|
};
|
|
2484
2612
|
JS_RUNNERS = /* @__PURE__ */ new Set(["it", "test", "describe", "suite"]);
|
|
2485
|
-
JS_CHAIN_MODIFIERS = /* @__PURE__ */ new Set(["concurrent", "sequential", "shuffle"]);
|
|
2613
|
+
JS_CHAIN_MODIFIERS = /* @__PURE__ */ new Set(["concurrent", "sequential", "shuffle", "serial", "parallel"]);
|
|
2614
|
+
JS_DEFAULT_RUNNER_MODULES = /* @__PURE__ */ new Set(["node:test", "node:test/promises", "@playwright/test"]);
|
|
2486
2615
|
JS_TEST_MODULES = /* @__PURE__ */ new Set([
|
|
2487
2616
|
"vitest",
|
|
2488
2617
|
"@jest/globals",
|
|
@@ -2492,12 +2621,272 @@ var init_test_skip = __esm({
|
|
|
2492
2621
|
"bun:test",
|
|
2493
2622
|
"@playwright/test"
|
|
2494
2623
|
]);
|
|
2624
|
+
RUNNER = { kind: "runner", causes: [] };
|
|
2625
|
+
NON_RUNNER = { kind: "non-runner" };
|
|
2626
|
+
UNKNOWN = { kind: "unknown" };
|
|
2627
|
+
withCause = (v, cause) => v.kind === "runner" ? { kind: "runner", causes: [cause, ...v.causes] } : v;
|
|
2628
|
+
MODULE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2629
|
+
RunnerResolver = class _RunnerResolver {
|
|
2630
|
+
constructor(source) {
|
|
2631
|
+
this.source = source;
|
|
2632
|
+
}
|
|
2633
|
+
binders = /* @__PURE__ */ new Map();
|
|
2634
|
+
static MAX_DEPTH = 4;
|
|
2635
|
+
binderFor(path, ctx) {
|
|
2636
|
+
const hit = this.binders.get(path);
|
|
2637
|
+
if (hit && hit.ctx === ctx) return hit;
|
|
2638
|
+
const binder = new ModuleBinder(ctx, path, this, 0);
|
|
2639
|
+
this.binders.set(path, binder);
|
|
2640
|
+
return binder;
|
|
2641
|
+
}
|
|
2642
|
+
/** The verdict for `name` exported by `spec` as imported from `fromPath`. */
|
|
2643
|
+
exportOf(fromPath, spec, name, depth) {
|
|
2644
|
+
if (JS_TEST_MODULES.has(spec)) {
|
|
2645
|
+
if (name === "default") return JS_DEFAULT_RUNNER_MODULES.has(spec) ? RUNNER : UNKNOWN;
|
|
2646
|
+
return JS_RUNNERS.has(name) ? RUNNER : NON_RUNNER;
|
|
2647
|
+
}
|
|
2648
|
+
if (!spec.startsWith("./") && !spec.startsWith("../")) return UNKNOWN;
|
|
2649
|
+
if (depth >= _RunnerResolver.MAX_DEPTH) return UNKNOWN;
|
|
2650
|
+
for (const candidate of relativeCandidates(fromPath, spec)) {
|
|
2651
|
+
if (this.binders.has(candidate)) {
|
|
2652
|
+
const cached = this.binders.get(candidate);
|
|
2653
|
+
return cached ? cached.exported(name) : UNKNOWN;
|
|
2654
|
+
}
|
|
2655
|
+
const src = this.source(candidate);
|
|
2656
|
+
if (src == null) continue;
|
|
2657
|
+
const ctx = astContext(candidate, src);
|
|
2658
|
+
const binder = ctx ? new ModuleBinder(ctx, candidate, this, depth + 1) : null;
|
|
2659
|
+
this.binders.set(candidate, binder);
|
|
2660
|
+
return binder ? binder.exported(name) : UNKNOWN;
|
|
2661
|
+
}
|
|
2662
|
+
return UNKNOWN;
|
|
2663
|
+
}
|
|
2664
|
+
};
|
|
2665
|
+
ModuleBinder = class {
|
|
2666
|
+
constructor(ctx, path, resolver, depth) {
|
|
2667
|
+
this.ctx = ctx;
|
|
2668
|
+
this.path = path;
|
|
2669
|
+
this.resolver = resolver;
|
|
2670
|
+
this.depth = depth;
|
|
2671
|
+
}
|
|
2672
|
+
memo = /* @__PURE__ */ new Map();
|
|
2673
|
+
resolving = /* @__PURE__ */ new Set();
|
|
2674
|
+
exports = null;
|
|
2675
|
+
starExports = [];
|
|
2676
|
+
opaqueCjs = false;
|
|
2677
|
+
moduleExport(spec, name) {
|
|
2678
|
+
return this.resolver.exportOf(this.path, spec, name, this.depth);
|
|
2679
|
+
}
|
|
2680
|
+
/** The verdict for member `name` of the module object `spec` denotes. */
|
|
2681
|
+
memberOf(spec, name) {
|
|
2682
|
+
return this.moduleExport(spec, name);
|
|
2683
|
+
}
|
|
2684
|
+
/** The module specifier a symbol stands for as a namespace object
|
|
2685
|
+
* (`import * as v from 'vitest'`, `const v = require('vitest')`), else null. */
|
|
2686
|
+
namespaceOf(sym) {
|
|
2687
|
+
const decl = sym.valueDeclaration ?? sym.declarations?.[0];
|
|
2688
|
+
if (!decl) return null;
|
|
2689
|
+
if (ts.isNamespaceImport(decl)) {
|
|
2690
|
+
const imp = decl.parent.parent;
|
|
2691
|
+
return ts.isImportDeclaration(imp) ? moduleName(imp.moduleSpecifier) : null;
|
|
2692
|
+
}
|
|
2693
|
+
if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name) && decl.initializer) {
|
|
2694
|
+
return requiredModule(decl.initializer);
|
|
2695
|
+
}
|
|
2696
|
+
return null;
|
|
2697
|
+
}
|
|
2698
|
+
/** The verdict for a root identifier's symbol. */
|
|
2699
|
+
classify(sym) {
|
|
2700
|
+
const cached = this.memo.get(sym);
|
|
2701
|
+
if (cached) return cached;
|
|
2702
|
+
if (this.resolving.has(sym)) return UNKNOWN;
|
|
2703
|
+
this.resolving.add(sym);
|
|
2704
|
+
const verdict2 = this.classifyDeclaration(sym);
|
|
2705
|
+
this.resolving.delete(sym);
|
|
2706
|
+
this.memo.set(sym, verdict2);
|
|
2707
|
+
return verdict2;
|
|
2708
|
+
}
|
|
2709
|
+
classifyDeclaration(sym) {
|
|
2710
|
+
const decl = sym.valueDeclaration ?? sym.declarations?.[0];
|
|
2711
|
+
if (!decl) return UNKNOWN;
|
|
2712
|
+
if (ts.isImportSpecifier(decl)) {
|
|
2713
|
+
const imp = decl.parent.parent.parent;
|
|
2714
|
+
const spec = ts.isImportDeclaration(imp) ? moduleName(imp.moduleSpecifier) : null;
|
|
2715
|
+
if (spec == null) return UNKNOWN;
|
|
2716
|
+
return withCause(this.moduleExport(spec, (decl.propertyName ?? decl.name).text), decl.name);
|
|
2717
|
+
}
|
|
2718
|
+
if (ts.isImportClause(decl)) {
|
|
2719
|
+
const spec = ts.isImportDeclaration(decl.parent) ? moduleName(decl.parent.moduleSpecifier) : null;
|
|
2720
|
+
if (spec == null || !decl.name) return UNKNOWN;
|
|
2721
|
+
return withCause(this.moduleExport(spec, "default"), decl.name);
|
|
2722
|
+
}
|
|
2723
|
+
if (ts.isNamespaceImport(decl) || ts.isParameter(decl) || ts.isFunctionDeclaration(decl) || ts.isClassDeclaration(decl) || ts.isEnumDeclaration(decl) || ts.isModuleDeclaration(decl)) return NON_RUNNER;
|
|
2724
|
+
if (ts.isBindingElement(decl)) return this.classifyBindingElement(decl);
|
|
2725
|
+
if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) {
|
|
2726
|
+
if (!decl.initializer) return UNKNOWN;
|
|
2727
|
+
return withCause(this.classifyExpression(decl.initializer), decl.name);
|
|
2728
|
+
}
|
|
2729
|
+
return UNKNOWN;
|
|
2730
|
+
}
|
|
2731
|
+
/** `const { it } = require('vitest')`, `const { test } = fixtures`: the element
|
|
2732
|
+
* is what the module (or namespace) exports under its property name. */
|
|
2733
|
+
classifyBindingElement(decl) {
|
|
2734
|
+
const pattern = decl.parent;
|
|
2735
|
+
const owner = pattern.parent;
|
|
2736
|
+
if (ts.isParameter(owner) || !ts.isObjectBindingPattern(pattern) || !ts.isIdentifier(decl.name)) {
|
|
2737
|
+
return NON_RUNNER;
|
|
2738
|
+
}
|
|
2739
|
+
if (!ts.isVariableDeclaration(owner) || !owner.initializer) return UNKNOWN;
|
|
2740
|
+
const key2 = decl.propertyName ? ts.isIdentifier(decl.propertyName) || ts.isStringLiteralLike(decl.propertyName) ? decl.propertyName.text : null : decl.name.text;
|
|
2741
|
+
if (key2 == null) return UNKNOWN;
|
|
2742
|
+
const spec = this.namespaceSpec(owner.initializer);
|
|
2743
|
+
if (spec != null) return withCause(this.moduleExport(spec, key2), decl.name);
|
|
2744
|
+
if (ts.isIdentifier(owner.initializer)) {
|
|
2745
|
+
const inner = symbolAt(this.ctx, owner.initializer);
|
|
2746
|
+
const verdict2 = inner ? this.classify(inner) : UNKNOWN;
|
|
2747
|
+
return verdict2.kind === "unknown" ? UNKNOWN : NON_RUNNER;
|
|
2748
|
+
}
|
|
2749
|
+
return NON_RUNNER;
|
|
2750
|
+
}
|
|
2751
|
+
/** The module specifier `expr` denotes as a namespace object, else null. */
|
|
2752
|
+
namespaceSpec(expr) {
|
|
2753
|
+
const direct = requiredModule(expr);
|
|
2754
|
+
if (direct != null) return direct;
|
|
2755
|
+
if (!ts.isIdentifier(expr)) return null;
|
|
2756
|
+
const sym = symbolAt(this.ctx, expr);
|
|
2757
|
+
return sym ? this.namespaceOf(sym) : null;
|
|
2758
|
+
}
|
|
2759
|
+
/** The verdict for an expression used as a runner: an initialiser or a
|
|
2760
|
+
* re-exported value. */
|
|
2761
|
+
classifyExpression(expr) {
|
|
2762
|
+
if (ts.isParenthesizedExpression(expr) || ts.isAsExpression(expr) || ts.isSatisfiesExpression(expr) || ts.isTypeAssertionExpression(expr) || ts.isNonNullExpression(expr)) return this.classifyExpression(expr.expression);
|
|
2763
|
+
if (ts.isIdentifier(expr)) {
|
|
2764
|
+
const sym = symbolAt(this.ctx, expr);
|
|
2765
|
+
if (sym) return this.classify(sym);
|
|
2766
|
+
return JS_RUNNERS.has(expr.text) ? RUNNER : UNKNOWN;
|
|
2767
|
+
}
|
|
2768
|
+
if (ts.isCallExpression(expr)) {
|
|
2769
|
+
const spec = requiredModule(expr);
|
|
2770
|
+
if (spec != null) return this.moduleExport(spec, "default");
|
|
2771
|
+
const callee = expr.expression;
|
|
2772
|
+
if (ts.isPropertyAccessExpression(callee) && callee.name.text === "extend") {
|
|
2773
|
+
return this.classifyExpression(callee.expression);
|
|
2774
|
+
}
|
|
2775
|
+
return NON_RUNNER;
|
|
2776
|
+
}
|
|
2777
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
2778
|
+
const spec = this.namespaceSpec(expr.expression);
|
|
2779
|
+
return spec != null ? this.moduleExport(spec, expr.name.text) : UNKNOWN;
|
|
2780
|
+
}
|
|
2781
|
+
if (ts.isElementAccessExpression(expr)) {
|
|
2782
|
+
const spec = this.namespaceSpec(expr.expression);
|
|
2783
|
+
const name = ts.isStringLiteralLike(expr.argumentExpression) ? expr.argumentExpression.text : null;
|
|
2784
|
+
return spec != null && name != null ? this.moduleExport(spec, name) : UNKNOWN;
|
|
2785
|
+
}
|
|
2786
|
+
if (ts.isLiteralExpression(expr) || ts.isTemplateExpression(expr) || ts.isObjectLiteralExpression(expr) || ts.isArrayLiteralExpression(expr) || ts.isFunctionExpression(expr) || ts.isArrowFunction(expr) || ts.isClassExpression(expr) || ts.isNewExpression(expr) || ts.isBinaryExpression(expr) || ts.isPrefixUnaryExpression(expr) || ts.isPostfixUnaryExpression(expr) || ts.isJsxElement(expr) || ts.isJsxSelfClosingElement(expr) || expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword || expr.kind === ts.SyntaxKind.NullKeyword) return NON_RUNNER;
|
|
2787
|
+
return UNKNOWN;
|
|
2788
|
+
}
|
|
2789
|
+
/** The verdict for what this module exports under `name` (`default` for the
|
|
2790
|
+
* default export or a whole-module `module.exports = …`). */
|
|
2791
|
+
exported(name) {
|
|
2792
|
+
if (!this.exports) this.collectExports();
|
|
2793
|
+
const local = this.exports?.get(name);
|
|
2794
|
+
if (local) return local();
|
|
2795
|
+
for (const spec of this.starExports) {
|
|
2796
|
+
const v = this.moduleExport(spec, name);
|
|
2797
|
+
if (v.kind !== "non-runner") return v;
|
|
2798
|
+
}
|
|
2799
|
+
return this.opaqueCjs ? UNKNOWN : NON_RUNNER;
|
|
2800
|
+
}
|
|
2801
|
+
collectExports() {
|
|
2802
|
+
const exports = /* @__PURE__ */ new Map();
|
|
2803
|
+
this.exports = exports;
|
|
2804
|
+
const ctx = this.ctx;
|
|
2805
|
+
const bySymbol = (id) => () => {
|
|
2806
|
+
const sym = symbolAt(ctx, id);
|
|
2807
|
+
return sym ? this.classify(sym) : UNKNOWN;
|
|
2808
|
+
};
|
|
2809
|
+
for (const stmt of ctx.sf.statements) {
|
|
2810
|
+
if (ts.isExportDeclaration(stmt)) {
|
|
2811
|
+
const spec = stmt.moduleSpecifier ? moduleName(stmt.moduleSpecifier) : null;
|
|
2812
|
+
if (!stmt.exportClause) {
|
|
2813
|
+
if (spec != null) this.starExports.push(spec);
|
|
2814
|
+
else this.opaqueCjs = true;
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
if (!ts.isNamedExports(stmt.exportClause)) continue;
|
|
2818
|
+
for (const el of stmt.exportClause.elements) {
|
|
2819
|
+
const exportedName = el.name.text;
|
|
2820
|
+
const localName = (el.propertyName ?? el.name).text;
|
|
2821
|
+
if (spec != null) {
|
|
2822
|
+
exports.set(exportedName, () => this.moduleExport(spec, localName));
|
|
2823
|
+
} else {
|
|
2824
|
+
exports.set(exportedName, () => {
|
|
2825
|
+
const sym = ctx.checker.getExportSpecifierLocalTargetSymbol(el);
|
|
2826
|
+
return sym ? this.classify(sym) : UNKNOWN;
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
continue;
|
|
2831
|
+
}
|
|
2832
|
+
if (ts.isExportAssignment(stmt)) {
|
|
2833
|
+
exports.set("default", () => this.classifyExpression(stmt.expression));
|
|
2834
|
+
continue;
|
|
2835
|
+
}
|
|
2836
|
+
if (isExported(stmt)) {
|
|
2837
|
+
if (ts.isVariableStatement(stmt)) {
|
|
2838
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
2839
|
+
if (ts.isIdentifier(decl.name)) exports.set(decl.name.text, bySymbol(decl.name));
|
|
2840
|
+
else if (ts.isObjectBindingPattern(decl.name)) {
|
|
2841
|
+
for (const el of decl.name.elements) {
|
|
2842
|
+
if (ts.isIdentifier(el.name)) exports.set(el.name.text, bySymbol(el.name));
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
} else if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name) {
|
|
2847
|
+
exports.set(stmt.name.text, () => NON_RUNNER);
|
|
2848
|
+
}
|
|
2849
|
+
continue;
|
|
2850
|
+
}
|
|
2851
|
+
if (ts.isExpressionStatement(stmt) && ts.isBinaryExpression(stmt.expression) && stmt.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
2852
|
+
const target = cjsExportTarget(stmt.expression.left);
|
|
2853
|
+
if (target == null) continue;
|
|
2854
|
+
const value = stmt.expression.right;
|
|
2855
|
+
if (target !== "") {
|
|
2856
|
+
exports.set(target, () => this.classifyExpression(value));
|
|
2857
|
+
} else if (ts.isObjectLiteralExpression(value)) {
|
|
2858
|
+
for (const prop of value.properties) {
|
|
2859
|
+
if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name))) {
|
|
2860
|
+
const init = prop.initializer;
|
|
2861
|
+
exports.set(prop.name.text, () => this.classifyExpression(init));
|
|
2862
|
+
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
2863
|
+
exports.set(prop.name.text, () => {
|
|
2864
|
+
const sym = ctx.checker.getShorthandAssignmentValueSymbol(prop);
|
|
2865
|
+
return sym ? this.classify(sym) : UNKNOWN;
|
|
2866
|
+
});
|
|
2867
|
+
} else {
|
|
2868
|
+
this.opaqueCjs = true;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
} else {
|
|
2872
|
+
exports.set("default", () => this.classifyExpression(value));
|
|
2873
|
+
this.opaqueCjs = true;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
};
|
|
2879
|
+
NO_AST = { hits: [], authoritative: false, unowned: /* @__PURE__ */ new Set() };
|
|
2495
2880
|
testSkip = {
|
|
2496
2881
|
id: RULE4,
|
|
2497
2882
|
surface: ["file"],
|
|
2498
2883
|
certainty: "mechanical",
|
|
2499
|
-
run(changes, policy) {
|
|
2884
|
+
run(changes, policy, _view, ctx) {
|
|
2500
2885
|
const out3 = [];
|
|
2886
|
+
const resolvers = {
|
|
2887
|
+
before: new RunnerResolver(moduleSource(changes, "before", ctx)),
|
|
2888
|
+
after: new RunnerResolver(moduleSource(changes, "after", ctx))
|
|
2889
|
+
};
|
|
2501
2890
|
for (const c of changes) {
|
|
2502
2891
|
if (c.kind !== "file") continue;
|
|
2503
2892
|
if (!isProtected(c.path, policy, "tests")) continue;
|
|
@@ -2505,9 +2894,11 @@ var init_test_skip = __esm({
|
|
|
2505
2894
|
const patterns = PATTERNS2[lang ?? "js"];
|
|
2506
2895
|
const astHitLines = /* @__PURE__ */ new Set();
|
|
2507
2896
|
let astAuthoritative = false;
|
|
2897
|
+
let unowned = /* @__PURE__ */ new Set();
|
|
2508
2898
|
if (lang === "js" && c.after != null) {
|
|
2509
|
-
const analysis = astSkipHits(c);
|
|
2899
|
+
const analysis = astSkipHits(c, resolvers);
|
|
2510
2900
|
astAuthoritative = analysis.authoritative;
|
|
2901
|
+
unowned = analysis.unowned;
|
|
2511
2902
|
for (const hit of analysis.hits) {
|
|
2512
2903
|
astHitLines.add(hit.line);
|
|
2513
2904
|
out3.push(
|
|
@@ -2525,7 +2916,7 @@ var init_test_skip = __esm({
|
|
|
2525
2916
|
if (l.newLine != null && astHitLines.has(l.newLine)) continue;
|
|
2526
2917
|
const comment = isCommentLine(l.content.trim(), lang);
|
|
2527
2918
|
for (const p of patterns) {
|
|
2528
|
-
if (astAuthoritative && p.astOwned) continue;
|
|
2919
|
+
if (astAuthoritative && p.astOwned && !(l.newLine != null && unowned.has(l.newLine))) continue;
|
|
2529
2920
|
if (comment && !p.comment) continue;
|
|
2530
2921
|
if (matchesOutsideString(p, l.content, lang)) {
|
|
2531
2922
|
out3.push(
|
|
@@ -2548,133 +2939,6 @@ var init_test_skip = __esm({
|
|
|
2548
2939
|
}
|
|
2549
2940
|
});
|
|
2550
2941
|
|
|
2551
|
-
// src/detectors/repo.ts
|
|
2552
|
-
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2553
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2554
|
-
import { join as join6 } from "node:path";
|
|
2555
|
-
function trackedFiles(ctx) {
|
|
2556
|
-
if (!ctx) return null;
|
|
2557
|
-
if (ctx.trackedFiles) return ctx.trackedFiles;
|
|
2558
|
-
if (!ctx.cwd) return null;
|
|
2559
|
-
const hit = cache2.get(ctx.cwd);
|
|
2560
|
-
if (hit !== void 0) return hit;
|
|
2561
|
-
let files = null;
|
|
2562
|
-
try {
|
|
2563
|
-
files = execFileSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
2564
|
-
cwd: ctx.cwd,
|
|
2565
|
-
encoding: "utf8",
|
|
2566
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
2567
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2568
|
-
}).split("\0").filter(Boolean);
|
|
2569
|
-
} catch {
|
|
2570
|
-
files = null;
|
|
2571
|
-
}
|
|
2572
|
-
cache2.set(ctx.cwd, files);
|
|
2573
|
-
return files;
|
|
2574
|
-
}
|
|
2575
|
-
function trackedContent(path, ctx) {
|
|
2576
|
-
if (!ctx) return null;
|
|
2577
|
-
if (ctx.trackedContents) return ctx.trackedContents[path] ?? null;
|
|
2578
|
-
if (!ctx.cwd) return null;
|
|
2579
|
-
const key2 = ctx.cwd + "\0" + path;
|
|
2580
|
-
const hit = contentCache.get(key2);
|
|
2581
|
-
if (hit !== void 0) return hit;
|
|
2582
|
-
let src = null;
|
|
2583
|
-
try {
|
|
2584
|
-
src = readFileSync3(join6(ctx.cwd, path), "utf8");
|
|
2585
|
-
} catch {
|
|
2586
|
-
src = null;
|
|
2587
|
-
}
|
|
2588
|
-
contentCache.set(key2, src);
|
|
2589
|
-
return src;
|
|
2590
|
-
}
|
|
2591
|
-
function branchRefs(ctx) {
|
|
2592
|
-
if (!ctx?.cwd) return null;
|
|
2593
|
-
const hit = refsCache.get(ctx.cwd);
|
|
2594
|
-
if (hit !== void 0) return hit;
|
|
2595
|
-
let refs = null;
|
|
2596
|
-
try {
|
|
2597
|
-
const out3 = execFileSync5("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/remotes"], {
|
|
2598
|
-
cwd: ctx.cwd,
|
|
2599
|
-
encoding: "utf8",
|
|
2600
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2601
|
-
});
|
|
2602
|
-
refs = new Set(out3.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
2603
|
-
} catch {
|
|
2604
|
-
refs = null;
|
|
2605
|
-
}
|
|
2606
|
-
refsCache.set(ctx.cwd, refs);
|
|
2607
|
-
return refs;
|
|
2608
|
-
}
|
|
2609
|
-
function branchExists(name, ctx) {
|
|
2610
|
-
const refs = branchRefs(ctx);
|
|
2611
|
-
if (!refs) return null;
|
|
2612
|
-
if (refs.has(name)) return true;
|
|
2613
|
-
for (const r of refs) if (r.endsWith("/" + name)) return true;
|
|
2614
|
-
return false;
|
|
2615
|
-
}
|
|
2616
|
-
function revIsHead(rev2, ctx) {
|
|
2617
|
-
if (!ctx?.cwd || rev2.startsWith("-")) return null;
|
|
2618
|
-
const parse = (r) => {
|
|
2619
|
-
try {
|
|
2620
|
-
return execFileSync5("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${r}^{commit}`], {
|
|
2621
|
-
cwd: ctx.cwd,
|
|
2622
|
-
encoding: "utf8",
|
|
2623
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2624
|
-
}).trim();
|
|
2625
|
-
} catch {
|
|
2626
|
-
return null;
|
|
2627
|
-
}
|
|
2628
|
-
};
|
|
2629
|
-
const head = parse("HEAD");
|
|
2630
|
-
const target = parse(rev2);
|
|
2631
|
-
if (!head || !target) return null;
|
|
2632
|
-
return head === target;
|
|
2633
|
-
}
|
|
2634
|
-
function defaultBranch(ctx) {
|
|
2635
|
-
if (!ctx?.cwd) return null;
|
|
2636
|
-
const hit = branchCache.get(ctx.cwd);
|
|
2637
|
-
if (hit !== void 0) return hit;
|
|
2638
|
-
let name = null;
|
|
2639
|
-
try {
|
|
2640
|
-
const ref = execFileSync5("git", ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"], {
|
|
2641
|
-
cwd: ctx.cwd,
|
|
2642
|
-
encoding: "utf8",
|
|
2643
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2644
|
-
}).trim();
|
|
2645
|
-
name = ref.replace(/^origin\//, "") || null;
|
|
2646
|
-
} catch {
|
|
2647
|
-
name = null;
|
|
2648
|
-
}
|
|
2649
|
-
branchCache.set(ctx.cwd, name);
|
|
2650
|
-
return name;
|
|
2651
|
-
}
|
|
2652
|
-
function containsProtected(token, policy, category, ctx, except) {
|
|
2653
|
-
const dir = dirOf(token);
|
|
2654
|
-
if (!dir || dir === "." || dir === "/" || dir.startsWith("-")) return false;
|
|
2655
|
-
const counts = (p) => isProtected(p, policy, category) && !(except && isProtected(p, policy, except));
|
|
2656
|
-
const files = trackedFiles(ctx);
|
|
2657
|
-
if (files) {
|
|
2658
|
-
const prefix = dir + "/";
|
|
2659
|
-
return files.some((f) => f.startsWith(prefix) && counts(f));
|
|
2660
|
-
}
|
|
2661
|
-
if (counts(dir + "/probe")) return true;
|
|
2662
|
-
const base = dir.split("/").pop() ?? "";
|
|
2663
|
-
return category === "tests" && /^(?:test|tests|__tests__|spec|specs)$/i.test(base);
|
|
2664
|
-
}
|
|
2665
|
-
var cache2, contentCache, branchCache, refsCache, dirOf;
|
|
2666
|
-
var init_repo = __esm({
|
|
2667
|
-
"src/detectors/repo.ts"() {
|
|
2668
|
-
"use strict";
|
|
2669
|
-
init_policy();
|
|
2670
|
-
cache2 = /* @__PURE__ */ new Map();
|
|
2671
|
-
contentCache = /* @__PURE__ */ new Map();
|
|
2672
|
-
branchCache = /* @__PURE__ */ new Map();
|
|
2673
|
-
refsCache = /* @__PURE__ */ new Map();
|
|
2674
|
-
dirOf = (t) => t.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
2675
|
-
}
|
|
2676
|
-
});
|
|
2677
|
-
|
|
2678
2942
|
// src/detectors/coverage-lowering.ts
|
|
2679
2943
|
function keyName(name) {
|
|
2680
2944
|
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
@@ -4105,8 +4369,8 @@ function stripComment(line) {
|
|
|
4105
4369
|
}
|
|
4106
4370
|
function checkIdentity(segment) {
|
|
4107
4371
|
const core = segment.trim().replace(PREFIX, "").replace(/\s+/g, " ");
|
|
4108
|
-
const viaRunner =
|
|
4109
|
-
const rest = core.replace(
|
|
4372
|
+
const viaRunner = RUNNER2.test(core);
|
|
4373
|
+
const rest = core.replace(RUNNER2, "");
|
|
4110
4374
|
const tool = rest.match(TOOL)?.[1] ?? (viaRunner ? rest.match(SCRIPT)?.[1] : void 0);
|
|
4111
4375
|
if (!tool) return null;
|
|
4112
4376
|
const toks = rest.replace(/^tamperward@\S+/, "tamperward").split(" ");
|
|
@@ -4122,7 +4386,7 @@ function checkIdentity(segment) {
|
|
|
4122
4386
|
return tool;
|
|
4123
4387
|
}
|
|
4124
4388
|
function pinOfSegment(segment) {
|
|
4125
|
-
const m = segment.trim().replace(PREFIX, "").replace(
|
|
4389
|
+
const m = segment.trim().replace(PREFIX, "").replace(RUNNER2, "").match(/^tamperward(?:@(\S+))?\b/);
|
|
4126
4390
|
return m ? m[1] ?? "" : null;
|
|
4127
4391
|
}
|
|
4128
4392
|
function split(stmt) {
|
|
@@ -4985,7 +5249,7 @@ function installerOf(script) {
|
|
|
4985
5249
|
const raw = seg.trim();
|
|
4986
5250
|
if (!raw) continue;
|
|
4987
5251
|
if (/^(?:HUSKY|LEFTHOOK)=0\b/.test(raw)) continue;
|
|
4988
|
-
const core = raw.replace(PREFIX, "").replace(
|
|
5252
|
+
const core = raw.replace(PREFIX, "").replace(RUNNER2, "").replace(/\s+/g, " ");
|
|
4989
5253
|
const m = core.match(/^(?:node\s+)?(?:\S*\/)?(husky|lefthook|simple-git-hooks|pre-commit)(?:\.js)?(?:\s+(\S+))?/);
|
|
4990
5254
|
if (!m) continue;
|
|
4991
5255
|
const [, tool, sub] = m;
|
|
@@ -5283,7 +5547,7 @@ function xargsWritesHook(toks, fed, policy, ctx) {
|
|
|
5283
5547
|
if ((cmd === "sed" || cmd === "perl") && args.some((a) => /^-[A-Za-z]*i|^--in-place/.test(a))) return `xargs ${cmd} -i rewrites a protected hook named earlier in the line`;
|
|
5284
5548
|
return null;
|
|
5285
5549
|
}
|
|
5286
|
-
var isComment,
|
|
5550
|
+
var isComment, RUNNER2, TOOL, SCRIPT, SUBCOMMAND_TOOLS, PREFIX, MODE, CWD, unq, alwaysTrue, EXIT_ZERO, EXIT_ANY, FN_DEF, SENSITIVE_ENV, assignedNames, WAIT_PID, TRANSPARENT, strip, baseIdentity, isCodeowners, GATE_CRITICAL, isDoc, isLefthook, isLefthookLocal, isPreCommitConfig, isPackageJson, GATE, str, list, redirectingEnv, off, STAGE_ALIAS, GATE_STAGES, ALL_STAGES, normStage, INSTALL_SCRIPTS, GIT_HOOKS, ownerExec, SYMBOLIC, WRAPPERS, INTERPRETERS, INLINE_FLAG, EDITORS, SCRIPT_WORD, HOOK_BASENAMES, REDIRECT_TARGET;
|
|
5287
5551
|
var init_hook_wiring = __esm({
|
|
5288
5552
|
"src/detectors/hook-wiring.ts"() {
|
|
5289
5553
|
"use strict";
|
|
@@ -5292,7 +5556,7 @@ var init_hook_wiring = __esm({
|
|
|
5292
5556
|
init_wiring();
|
|
5293
5557
|
init_repo();
|
|
5294
5558
|
isComment = (line) => /^\s*#/.test(line);
|
|
5295
|
-
|
|
5559
|
+
RUNNER2 = /^(?:npx|pnpm|yarn|bunx|bun|npm)\s+(?:(?:exec|dlx|x|run)\s+)?(?:(?:--yes|-y|--no-install|--silent|-s|--)\s+)*/;
|
|
5296
5560
|
TOOL = /^(tamperward|jest|vitest|eslint|tsc|lint-staged|lefthook|husky|pytest|make|cargo|go|pre-commit|mypy|ruff|rubocop|prettier|gradlew?)\b/;
|
|
5297
5561
|
SCRIPT = /^(test|tests|lint|typecheck|type-check|coverage|check)\b/;
|
|
5298
5562
|
SUBCOMMAND_TOOLS = /* @__PURE__ */ new Set(["tamperward", "make", "cargo", "go", "lefthook", "husky", "pre-commit"]);
|
|
@@ -7206,7 +7470,7 @@ function namesProtectedSnapshot(seg, policy) {
|
|
|
7206
7470
|
}
|
|
7207
7471
|
return null;
|
|
7208
7472
|
}
|
|
7209
|
-
var RULE10,
|
|
7473
|
+
var RULE10, RUNNER3, PM_TEST_SCRIPT, UPDATE_ANY, UPDATE_WITH_RUNNER, INSTA_ACCEPT, UPDATE_EXPECT, REGEN_SCRIPT, MUTATE, snapshotRewrite;
|
|
7210
7474
|
var init_snapshot_rewrite = __esm({
|
|
7211
7475
|
"src/detectors/snapshot-rewrite.ts"() {
|
|
7212
7476
|
"use strict";
|
|
@@ -7214,7 +7478,7 @@ var init_snapshot_rewrite = __esm({
|
|
|
7214
7478
|
init_finding();
|
|
7215
7479
|
init_command();
|
|
7216
7480
|
RULE10 = "snapshot-rewrite";
|
|
7217
|
-
|
|
7481
|
+
RUNNER3 = /\b(?:jest|vitest|playwright|ava)\b/;
|
|
7218
7482
|
PM_TEST_SCRIPT = /(?:^|\s)(?:npm|pnpm|yarn|bun)\s+(?:t|tst|test|run(?:-script)?\s+\S*test\S*)(?:\s|$)/;
|
|
7219
7483
|
UPDATE_ANY = /--(?:update-?[sS]napshots?|test-update-snapshots|snapshot-update)\b/;
|
|
7220
7484
|
UPDATE_WITH_RUNNER = /(?:^|\s)(?:-u|--update)(?:\s|$)/;
|
|
@@ -7234,7 +7498,7 @@ var init_snapshot_rewrite = __esm({
|
|
|
7234
7498
|
for (const seg of segments(c.raw)) {
|
|
7235
7499
|
let why = null;
|
|
7236
7500
|
if (UPDATE_ANY.test(seg)) why = "the snapshot-update flag rewrites every failing snapshot to match current output";
|
|
7237
|
-
else if ((
|
|
7501
|
+
else if ((RUNNER3.test(seg) || PM_TEST_SCRIPT.test(seg)) && UPDATE_WITH_RUNNER.test(seg)) why = "running the test runner in update mode rewrites failing snapshots to match current output";
|
|
7238
7502
|
else if (INSTA_ACCEPT.test(seg)) why = "cargo insta accept rewrites every pending snapshot to match current output";
|
|
7239
7503
|
else if (UPDATE_EXPECT.test(seg)) why = "UPDATE_EXPECT=1 rewrites every expect-test expectation to match current output";
|
|
7240
7504
|
else if (REGEN_SCRIPT.test(seg)) why = "a regeneration script rewrites the recorded expected output from current output";
|
|
@@ -7642,7 +7906,7 @@ var init_engine = __esm({
|
|
|
7642
7906
|
|
|
7643
7907
|
// src/policy-load.ts
|
|
7644
7908
|
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "node:fs";
|
|
7645
|
-
import { isAbsolute as isAbsolute7, join as join8, posix as
|
|
7909
|
+
import { isAbsolute as isAbsolute7, join as join8, posix as posix3 } from "node:path";
|
|
7646
7910
|
function normalizeVersion(v, where = POLICY_FILE) {
|
|
7647
7911
|
if (v === void 0 || v === null) return 1;
|
|
7648
7912
|
if (typeof v !== "number" || !Number.isInteger(v) || v < 1) {
|
|
@@ -7652,7 +7916,7 @@ function normalizeVersion(v, where = POLICY_FILE) {
|
|
|
7652
7916
|
}
|
|
7653
7917
|
function ledgerInsideRepo(ledger) {
|
|
7654
7918
|
if (!ledger.trim() || isAbsolute7(ledger) || /^[A-Za-z]:[\\/]/.test(ledger)) return false;
|
|
7655
|
-
const norm2 =
|
|
7919
|
+
const norm2 = posix3.normalize(ledger.replace(/\\/g, "/"));
|
|
7656
7920
|
return norm2 !== ".." && !norm2.startsWith("../") && !norm2.startsWith("/");
|
|
7657
7921
|
}
|
|
7658
7922
|
function validate(r, where) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.8",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|