karajan-code 4.23.0 → 4.25.0

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.
@@ -75,12 +75,27 @@ export function detectPhantomUnit({ sourceAnalysis, testSource, file = "<test>"
75
75
  const literalsOf = (tree, minLength) => {
76
76
  const out = [];
77
77
  walk(tree, (n) => {
78
- if (n.type === "StringLiteral" && n.value.trim().length >= minLength) out.push({ value: n.value, line: n.loc.start.line });
79
- if (n.type === "TemplateElement" && n.value.cooked && n.value.cooked.trim().length >= minLength) out.push({ value: n.value.cooked, line: n.loc.start.line });
78
+ if (n.type === "StringLiteral" && n.value.trim().length >= minLength) out.push({ value: n.value, line: n.loc.start.line, template: false });
79
+ if (n.type === "TemplateElement" && n.value.cooked && n.value.cooked.trim().length >= minLength) out.push({ value: n.value.cooked, line: n.loc.start.line, template: true });
80
80
  });
81
81
  return out;
82
82
  };
83
83
 
84
+ // KJC-BUG-0158 (GREBLA's field validation): an interpolated label —
85
+ // aria-label="Head al que reporta ${name}" — never matches the RESOLVED
86
+ // string a test looks for, and in Lit that is the usual case, not the
87
+ // exception. The static chunk carries markup before the label, so the right
88
+ // crossing is: the longest SUFFIX of a template chunk that is a PREFIX of the
89
+ // test's literal. Threshold contains false positives; a LIVE chunk producing
90
+ // the same start absolves — covering interpolation must never accuse live UI.
91
+ const PREFIX_MIN = 8;
92
+ const overlap = (chunk, wanted) => {
93
+ for (let k = Math.min(chunk.length, wanted.length); k >= PREFIX_MIN; k--) {
94
+ if (chunk.endsWith(wanted.slice(0, k))) return k;
95
+ }
96
+ return 0;
97
+ };
98
+
84
99
  /**
85
100
  * E2E detector: literals the test looks for that ONLY exist inside
86
101
  * unreachable spans of the source. A literal also present in reachable code
@@ -99,15 +114,37 @@ export function detectPhantomE2E({ sourceText, sourceAnalysis, testSource, file
99
114
  }
100
115
  const deadSpans = observableClasses(sourceAnalysis).flatMap((c) => c.unreachable.map((u) => [u.line, u.endLine]));
101
116
  const inDead = (line) => deadSpans.some(([a, b]) => line >= a && line <= b);
117
+ const sourceLits = literalsOf(sourceTree, minLength);
102
118
  const onlyInDead = new Map(); // literal → line where it lives
103
- for (const lit of literalsOf(sourceTree, minLength)) {
119
+ for (const lit of sourceLits) {
104
120
  if (inDead(lit.line)) { if (!onlyInDead.has(lit.value)) onlyInDead.set(lit.value, lit.line); }
105
121
  else onlyInDead.set(lit.value, -1); // seen reachable — poisoned, never reportable
106
122
  }
107
- const wanted = new Set(literalsOf(testTree, minLength).map((l) => l.value));
108
- const phantoms = [...wanted]
109
- .filter((v) => onlyInDead.get(v) !== undefined && onlyInDead.get(v) !== -1)
110
- .map((v) => ({ literal: v, line: onlyInDead.get(v), file }));
123
+ // KJC-BUG-0159 (GREBLA's field catch): file+line point at the TEST — that is
124
+ // where the fix happens; the dead code's spot travels as sourceLine. A
125
+ // finding at spec.js:2183 on a 32-line spec sends the reader nowhere.
126
+ const wanted = new Map(); // literal → its line IN THE TEST (first sighting)
127
+ for (const l of literalsOf(testTree, minLength)) if (!wanted.has(l.value)) wanted.set(l.value, l.line);
128
+ const phantoms = [...wanted.entries()]
129
+ .filter(([v]) => onlyInDead.get(v) !== undefined && onlyInDead.get(v) !== -1)
130
+ .map(([v, testLine]) => ({ literal: v, file, line: testLine, sourceLine: onlyInDead.get(v) }));
131
+ // KJC-BUG-0158: interpolated labels match by static-chunk prefix. Dead wins
132
+ // only when NO live chunk produces at least the same start.
133
+ const chunks = sourceLits.filter((l) => l.template);
134
+ for (const [v, testLine] of wanted) {
135
+ if (phantoms.some((p) => p.literal === v)) continue;
136
+ // the exact literal living in REACHABLE code absolves here too (codex's
137
+ // catch): that test does point at something alive.
138
+ if (onlyInDead.get(v) === -1) continue;
139
+ let kDead = 0, kLive = 0, deadLine = 0;
140
+ for (const c of chunks) {
141
+ // a chunk CONTAINING the whole literal is the strongest match either way
142
+ const k = c.value.includes(v) ? v.length : overlap(c.value, v);
143
+ if (inDead(c.line)) { if (k > kDead) { kDead = k; deadLine = c.line; } }
144
+ else if (k > kLive) kLive = k;
145
+ }
146
+ if (kDead >= PREFIX_MIN && kDead > kLive) phantoms.push({ literal: v, file, line: testLine, sourceLine: deadLine });
147
+ }
111
148
  return { observable: true, phantoms };
112
149
  }
113
150