-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
feat(ci): check:test-masking flags inline-reimplemented prod conditions (#6348) #6393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,8 @@ import { execFileSync } from "node:child_process"; | |||||||||||||||||||||||||||||||||||||||||||||
| import { pathToFileURL } from "node:url"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const TEST_RE = /\.(test|spec)\.(ts|tsx)$/; | ||||||||||||||||||||||||||||||||||||||||||||||
| // Production TypeScript sources (excludes test files, handled separately via TEST_RE). | ||||||||||||||||||||||||||||||||||||||||||||||
| const PROD_SRC_RE = /\.(ts|tsx|mts|cts)$/; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** Conta chamadas de assert.*( / assert( / expect( . */ | ||||||||||||||||||||||||||||||||||||||||||||||
| export function countAssertions(src) { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -57,6 +59,169 @@ export function countExtendedTautologies(src) { | |||||||||||||||||||||||||||||||||||||||||||||
| return count; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // ─── (6348) Subcheck 4: inline-reimplemented prod conditions (REPORT-ONLY) ─── | ||||||||||||||||||||||||||||||||||||||||||||||
| // A test that copies a conditional expression out of production code (instead of | ||||||||||||||||||||||||||||||||||||||||||||||
| // importing and exercising the symbol that owns it) is the wrong-shape-contract-test | ||||||||||||||||||||||||||||||||||||||||||||||
| // class (#6216): the assertion re-encodes the branch locally, so it stays green even | ||||||||||||||||||||||||||||||||||||||||||||||
| // when the real prod condition drifts. This subcheck is a HEURISTIC, textual gate | ||||||||||||||||||||||||||||||||||||||||||||||
| // mirroring the count* helpers above — it never parses an AST. It is REPORT-ONLY for | ||||||||||||||||||||||||||||||||||||||||||||||
| // now (warns, does not fail the gate). | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** Collapse all runs of whitespace to a single space and trim. */ | ||||||||||||||||||||||||||||||||||||||||||||||
| function normalizeWhitespace(s) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return (s || "").replace(/\s+/g, " ").trim(); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||
| * Count "significant" tokens in a normalized condition. Single-char identifiers | ||||||||||||||||||||||||||||||||||||||||||||||
| * (`x`, `i`) and single-digit numeric literals (`0`, `1`) are treated as noise and | ||||||||||||||||||||||||||||||||||||||||||||||
| * NOT counted; operators and multi-char identifiers/numbers ARE. This is what makes | ||||||||||||||||||||||||||||||||||||||||||||||
| * `x > 0` trivial (1 significant token) while `status >= 500` is meaningful (3): | ||||||||||||||||||||||||||||||||||||||||||||||
| * - `status >= 500` → status, >=, 500 → 3 | ||||||||||||||||||||||||||||||||||||||||||||||
| * - `x === LIMIT && y` → ===, LIMIT, && → 3 | ||||||||||||||||||||||||||||||||||||||||||||||
| * - `x > 0` → > → 1 | ||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||
| export function countSignificantTokens(cond) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const tokens = | ||||||||||||||||||||||||||||||||||||||||||||||
| (cond || "").match( | ||||||||||||||||||||||||||||||||||||||||||||||
| /===|!==|==|!=|>=|<=|&&|\|\||[<>+\-*/%!]|[A-Za-z_$][\w$]*|\d+(?:\.\d+)?/g | ||||||||||||||||||||||||||||||||||||||||||||||
| ) || []; | ||||||||||||||||||||||||||||||||||||||||||||||
| let count = 0; | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const tk of tokens) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (/^[A-Za-z_$]/.test(tk)) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (tk.length >= 2) count++; // multi-char identifier | ||||||||||||||||||||||||||||||||||||||||||||||
| } else if (/^\d/.test(tk)) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (tk.length >= 2) count++; // multi-digit number | ||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||
| count++; // operator | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return count; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** A condition is "meaningful" when it carries ≥3 significant tokens. */ | ||||||||||||||||||||||||||||||||||||||||||||||
| function isSignificantCondition(cond) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return countSignificantTokens(cond) >= 3; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||
| * Extract meaningful (≥3-token) conditional expressions from a production source, | ||||||||||||||||||||||||||||||||||||||||||||||
| * paired with the nearest enclosing declared symbol that "owns" them. Covers | ||||||||||||||||||||||||||||||||||||||||||||||
| * `if (...)` (via paren balancing) and comparison-bearing ternaries (`a === b ? … : …`). | ||||||||||||||||||||||||||||||||||||||||||||||
| * Returns [{ condition (whitespace-normalized), owner }]. | ||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||
| export function extractProdConditions(src) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const results = []; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!src) return results; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Declarations (function / const / let / var) with their positions, so each | ||||||||||||||||||||||||||||||||||||||||||||||
| // condition can be attributed to the symbol whose body it lives in. | ||||||||||||||||||||||||||||||||||||||||||||||
| const decls = []; | ||||||||||||||||||||||||||||||||||||||||||||||
| const declRe = | ||||||||||||||||||||||||||||||||||||||||||||||
| /(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)|(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g; | ||||||||||||||||||||||||||||||||||||||||||||||
| let dm; | ||||||||||||||||||||||||||||||||||||||||||||||
| while ((dm = declRe.exec(src))) { | ||||||||||||||||||||||||||||||||||||||||||||||
| decls.push({ index: dm.index, name: dm[1] || dm[2] }); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| const ownerAt = (idx) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| let owner = ""; | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const d of decls) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (d.index <= idx) owner = d.name; | ||||||||||||||||||||||||||||||||||||||||||||||
| else break; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return owner; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const seen = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const pushCond = (raw, owner) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const norm = normalizeWhitespace(raw); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!norm || seen.has(norm) || !isSignificantCondition(norm)) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| seen.add(norm); | ||||||||||||||||||||||||||||||||||||||||||||||
| results.push({ condition: norm, owner }); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // if (...) — balance parentheses to capture the full condition. | ||||||||||||||||||||||||||||||||||||||||||||||
| const ifRe = /\bif\s*\(/g; | ||||||||||||||||||||||||||||||||||||||||||||||
| let m; | ||||||||||||||||||||||||||||||||||||||||||||||
| while ((m = ifRe.exec(src))) { | ||||||||||||||||||||||||||||||||||||||||||||||
| let depth = 1; | ||||||||||||||||||||||||||||||||||||||||||||||
| let i = m.index + m[0].length; | ||||||||||||||||||||||||||||||||||||||||||||||
| for (; i < src.length && depth > 0; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const ch = src[i]; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (ch === "(") depth++; | ||||||||||||||||||||||||||||||||||||||||||||||
| else if (ch === ")") depth--; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| pushCond(src.slice(m.index + m[0].length, i - 1), ownerAt(m.index)); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+146
to
+155
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the parentheses in an
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Comparison-bearing ternaries: `<lhs> <cmp> <rhs> ? … : …` (best-effort, low-noise). | ||||||||||||||||||||||||||||||||||||||||||||||
| const ternRe = | ||||||||||||||||||||||||||||||||||||||||||||||
| /([A-Za-z_$][\w$).\]]*\s*(?:===|!==|==|!=|>=|<=|>|<)\s*[^?;{}\n]+?)\s*\?/g; | ||||||||||||||||||||||||||||||||||||||||||||||
| let t; | ||||||||||||||||||||||||||||||||||||||||||||||
| while ((t = ternRe.exec(src))) { | ||||||||||||||||||||||||||||||||||||||||||||||
| pushCond(t[1], ownerAt(t.index)); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return results; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||
| * Collect the identifiers/module specifiers a test file imports, so we can tell | ||||||||||||||||||||||||||||||||||||||||||||||
| * whether it exercises a prod symbol through the real import (clean) or merely | ||||||||||||||||||||||||||||||||||||||||||||||
| * re-implements one of its conditions locally (masked). Returns a Set of names: | ||||||||||||||||||||||||||||||||||||||||||||||
| * imported bindings, module paths, and module basenames. | ||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||
| export function extractImports(src) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const names = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!src) return names; | ||||||||||||||||||||||||||||||||||||||||||||||
| const addModule = (mod) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| names.add(mod); | ||||||||||||||||||||||||||||||||||||||||||||||
| const base = mod.split("/").pop().replace(/\.\w+$/, ""); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (base) names.add(base); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| let m; | ||||||||||||||||||||||||||||||||||||||||||||||
| const importRe = /import\s+(?:type\s+)?([^;]*?)\s+from\s+['"]([^'"]+)['"]/g; | ||||||||||||||||||||||||||||||||||||||||||||||
| while ((m = importRe.exec(src))) { | ||||||||||||||||||||||||||||||||||||||||||||||
| addModule(m[2]); | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const id of m[1].match(/[A-Za-z_$][\w$]*/g) || []) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (id !== "as" && id !== "type") names.add(id); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| const dynRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g; | ||||||||||||||||||||||||||||||||||||||||||||||
| while ((m = dynRe.exec(src))) addModule(m[1]); | ||||||||||||||||||||||||||||||||||||||||||||||
| const reqRe = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g; | ||||||||||||||||||||||||||||||||||||||||||||||
| while ((m = reqRe.exec(src))) addModule(m[1]); | ||||||||||||||||||||||||||||||||||||||||||||||
| return names; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||
| * PURE core of subcheck 4. Given the sources of the prod files changed in a PR, | ||||||||||||||||||||||||||||||||||||||||||||||
| * one test file's source, and the set of names that test imports: return the prod | ||||||||||||||||||||||||||||||||||||||||||||||
| * conditions the test re-implements textually WITHOUT importing the symbol that owns | ||||||||||||||||||||||||||||||||||||||||||||||
| * them. Whitespace is squashed on both sides so spacing differences never mask a hit. | ||||||||||||||||||||||||||||||||||||||||||||||
| * Returns [{ condition, owner }]. | ||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||
| export function findReimplementedConditions(prodSources, testSource, testImports) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const flags = []; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!testSource) return flags; | ||||||||||||||||||||||||||||||||||||||||||||||
| const imports = | ||||||||||||||||||||||||||||||||||||||||||||||
| testImports instanceof Set ? testImports : new Set(testImports || []); | ||||||||||||||||||||||||||||||||||||||||||||||
| const squash = (s) => (s || "").replace(/\s+/g, ""); | ||||||||||||||||||||||||||||||||||||||||||||||
| const testSq = squash(testSource); | ||||||||||||||||||||||||||||||||||||||||||||||
| const seen = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const prod of prodSources || []) { | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const { condition, owner } of extractProdConditions(prod)) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (owner && imports.has(owner)) continue; // exercised through the real import | ||||||||||||||||||||||||||||||||||||||||||||||
| if (seen.has(condition)) continue; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (testSq.includes(squash(condition))) { | ||||||||||||||||||||||||||||||||||||||||||||||
| seen.add(condition); | ||||||||||||||||||||||||||||||||||||||||||||||
| flags.push({ condition, owner: owner || null }); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return flags; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+204
to
+223
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a common condition (such as To prevent this false positive, we should group all extracted conditions from all production files and map them to their set of owners. If the test imports at least one of the owners of that condition, we can safely exempt it. export function findReimplementedConditions(prodSources, testSource, testImports) {
const flags = [];
if (!testSource) return flags;
const imports =
testImports instanceof Set ? testImports : new Set(testImports || []);
const squash = (s) => (s || "").replace(/\s+/g, "");
const testSq = squash(testSource);
const condOwners = new Map();
for (const prod of prodSources || []) {
for (const { condition, owner } of extractProdConditions(prod)) {
const sq = squash(condition);
if (!condOwners.has(sq)) {
condOwners.set(sq, { original: condition, owners: new Set() });
}
if (owner) {
condOwners.get(sq).owners.add(owner);
}
}
}
for (const [sq, { original, owners }] of condOwners) {
let importedAnyOwner = false;
for (const owner of owners) {
if (imports.has(owner)) {
importedAnyOwner = true;
break;
}
}
if (importedAnyOwner) continue;
if (testSq.includes(sq)) {
const owner = owners.size > 0 ? Array.from(owners)[0] : null;
flags.push({ condition: original, owner });
}
}
return flags;
} |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||
| * (6A.10 subcheck 1) Sinaliza arquivos de teste DELETADOS ou renomeados-e-não- | ||||||||||||||||||||||||||||||||||||||||||||||
| * substituídos. Recebe lista de paths de arquivos de teste que foram deletados | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -241,14 +406,59 @@ function main() { | |||||||||||||||||||||||||||||||||||||||||||||
| // Only exempts the reduction signal; tautology/skip/deletion signals still fire. | ||||||||||||||||||||||||||||||||||||||||||||||
| let assertReductionAllowlist = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||
| let deletionAllowlist = {}; | ||||||||||||||||||||||||||||||||||||||||||||||
| let reimplementedAllowlist = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const raw = JSON.parse(fs.readFileSync("config/quality/test-masking-allowlist.json", "utf8")); | ||||||||||||||||||||||||||||||||||||||||||||||
| assertReductionAllowlist = new Set(Object.keys(raw).filter((k) => !k.startsWith("_"))); | ||||||||||||||||||||||||||||||||||||||||||||||
| deletionAllowlist = raw._deletedWithReplacement || {}; | ||||||||||||||||||||||||||||||||||||||||||||||
| reimplementedAllowlist = new Set(raw._reimplementedConditions || []); | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||
| // no allowlist file — treat as empty | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // (6348 subcheck 4, REPORT-ONLY) Tests that inline-reimplement a prod condition | ||||||||||||||||||||||||||||||||||||||||||||||
| // instead of importing the symbol that owns it. Prod files changed in this PR | ||||||||||||||||||||||||||||||||||||||||||||||
| // (added/copied/modified TS sources) are the reference corpus; each changed test | ||||||||||||||||||||||||||||||||||||||||||||||
| // file is scanned against them. Warns only — it never fails the gate for now. | ||||||||||||||||||||||||||||||||||||||||||||||
| const prodChanged = git(["diff", "--name-only", "--diff-filter=ACM", `${base}...HEAD`]) | ||||||||||||||||||||||||||||||||||||||||||||||
| .split("\n") | ||||||||||||||||||||||||||||||||||||||||||||||
| .map((s) => s.trim()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .filter((f) => PROD_SRC_RE.test(f) && !TEST_RE.test(f) && fs.existsSync(f)); | ||||||||||||||||||||||||||||||||||||||||||||||
| const prodSources = prodChanged.map((f) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| return fs.readFileSync(f, "utf8"); | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||
| return ""; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| const changedTests = git(["diff", "--name-only", "--diff-filter=ACM", `${base}...HEAD`]) | ||||||||||||||||||||||||||||||||||||||||||||||
| .split("\n") | ||||||||||||||||||||||||||||||||||||||||||||||
| .map((s) => s.trim()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .filter((f) => TEST_RE.test(f) && fs.existsSync(f)); | ||||||||||||||||||||||||||||||||||||||||||||||
| const reimplementedFlags = []; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (prodSources.length) { | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const tf of changedTests) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (reimplementedAllowlist.has(tf)) continue; | ||||||||||||||||||||||||||||||||||||||||||||||
| const src = fs.readFileSync(tf, "utf8"); | ||||||||||||||||||||||||||||||||||||||||||||||
| for (const hit of findReimplementedConditions(prodSources, src, extractImports(src))) { | ||||||||||||||||||||||||||||||||||||||||||||||
| reimplementedFlags.push( | ||||||||||||||||||||||||||||||||||||||||||||||
| `${tf}: re-implementa a condição \`${hit.condition}\`` + | ||||||||||||||||||||||||||||||||||||||||||||||
| (hit.owner ? ` (dona: ${hit.owner})` : "") + | ||||||||||||||||||||||||||||||||||||||||||||||
| " — asserte através do import real em vez de copiar a condição" | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| if (reimplementedFlags.length) { | ||||||||||||||||||||||||||||||||||||||||||||||
| console.warn( | ||||||||||||||||||||||||||||||||||||||||||||||
| `[test-masking] (report-only) ${reimplementedFlags.length} teste(s) re-implementam ` + | ||||||||||||||||||||||||||||||||||||||||||||||
| `condição de produção em vez de importar o símbolo dono (classe #6216):\n` + | ||||||||||||||||||||||||||||||||||||||||||||||
| reimplementedFlags.map((f) => " ⚠ " + f).join("\n") + | ||||||||||||||||||||||||||||||||||||||||||||||
| `\n → importe o símbolo/função dono e asserte através dele (evita contrato duplicado ` + | ||||||||||||||||||||||||||||||||||||||||||||||
| `que diverge silenciosamente). Report-only por enquanto — não falha o gate.` | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const deletedFlags = evaluateDeletedFiles( | ||||||||||||||||||||||||||||||||||||||||||||||
| [...deletedTests, ...relocatedOutOfTest], | ||||||||||||||||||||||||||||||||||||||||||||||
| deletionAllowlist | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In TypeScript, variable declarations often have type annotations (e.g.,
const isServerError: Handler = ...) and classes are frequently used. The currentdeclRefails to capture variable names with type annotations because of the strict\s*=check, and it completely ignores class declarations. This leads to false positives where conditions are flagged as reimplemented even when their owning symbols or classes are imported.We can make the type annotation optional in the regex and add support for class declarations to ensure they are correctly resolved as owners.