Skip to content

Commit bf4b609

Browse files
committed
wip(pm): a refusal guard's region refuses in code and returns no value
A string saying "throw" refuses nothing, and a members-only region that returns a value is an allow-list by another spelling; both now keep T2 firing, each pinned by a control. Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
1 parent 22eafdc commit bf4b609

1 file changed

Lines changed: 31 additions & 4 deletions

File tree

‎scripts/pm/check-widening-tells.mjs‎

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
16081608
'the local path composed: an unread diff is not a narrow diff': 7,
16091609
'the surfaces, imported rather than restated': 11,
16101610
'T1 — a new key on a Zod object schema': 14,
1611-
'T2 — a new member of a closed set': 64,
1611+
'T2 — a new member of a closed set': 66,
16121612
'#16822 — the two accidental variables, and the evidence each one needs': 15,
16131613
'#16943 — the net member/key delta: a replaced line is not a net addition': 23,
16141614
'#17618 — a PARAMETER is not a key, and a closed set RE-SPELLED around fewer values is not a new one': 24,
@@ -2943,6 +2943,28 @@ export function enclosingConstruct(side, index) {
29432943

29442944
const escapeForRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29452945

2946+
/**
2947+
* One line with its string literals' contents and any trailing `//` comment
2948+
* blanked — the CODE of it, for a keyword test — or `null` when a string never
2949+
* closes on the line. `'please do not throw'` refuses nothing.
2950+
*/
2951+
function codeOfLine(s) {
2952+
let out = '';
2953+
for (let k = 0; k < s.length; k += 1) {
2954+
const ch = s[k];
2955+
if (ch === '/' && s[k + 1] === '/') break;
2956+
if (ch === "'" || ch === '"' || ch === '`') {
2957+
const end = endOfStringLiteral(s, k);
2958+
if (end === -1) return null;
2959+
out += `${ch}${' '.repeat(end - k - 1)}${ch}`;
2960+
k = end;
2961+
continue;
2962+
}
2963+
out += ch;
2964+
}
2965+
return out;
2966+
}
2967+
29462968
/**
29472969
* Is EVERY reader of the module-private `as const` array `name` a REFUSAL
29482970
* PREDICATE — so that a member added to it is a value the accept set LOSES?
@@ -2972,7 +2994,9 @@ const escapeForRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29722994
* NEGATED membership test whose consequent is a bare early exit, so only
29732995
* MEMBERS reach the rest of the block — where the rest of that block (read
29742996
* with {@link closerAhead}, and refused when that walk is unreadable)
2975-
* calls `.addIssue(` or `throw`s;
2997+
* calls `.addIssue(` or `throw`s, in CODE (a string saying "throw" refuses
2998+
* nothing), and ⛔ RETURNS NO VALUE: a region that hands members something
2999+
* non-members never get is an allow-list by another spelling;
29763000
* ④ every OTHER line naming `NAME` — ⛔ counted on every non-comment line,
29773001
* inside a string literal or not, so a name this reader mis-lexes is an
29783002
* unclassified reader and never an invisible one — is a PROSE render
@@ -3029,13 +3053,14 @@ export function refusalOnlyReaders(source, name) {
30293053
'\\.includes\\([^()]*\\)[ \\t]*\\)[ \\t]*return[ \\t]*;?[ \\t]*(?:\\/\\/.*)?$',
30303054
);
30313055
const REFUSAL = /\.addIssue\(|\bthrow\b/;
3056+
const VALUE_RETURN = /\breturn\b[ \t]*[^;\s]/;
30323057
const guards = [];
30333058
for (const r of readers) {
30343059
if (!GUARD.test(lines[r.line]) || guards.some((g) => g.line === r.line)) continue;
30353060
const end = closerAhead(lines, r.line + 1, 0);
30363061
if (end.unreadable || end.closer !== '}') continue;
3037-
const region = lines.slice(r.line + 1, end.line + 1);
3038-
if (!region.some((s) => !COMMENT_LINE.test(s) && REFUSAL.test(s))) continue;
3062+
const region = lines.slice(r.line + 1, end.line + 1).filter((s) => !COMMENT_LINE.test(s)).map(codeOfLine);
3063+
if (region.includes(null) || region.some((c) => VALUE_RETURN.test(c)) || !region.some((c) => REFUSAL.test(c))) continue;
30393064
guards.push({ line: r.line, until: end.line });
30403065
}
30413066
if (guards.length === 0) return no(`no reader of \`${name}\` is a guard that leaves only its members to a refusal`);
@@ -5610,6 +5635,8 @@ export function selfTest() {
56105635
t('⛔ an EXPORTED set fires — on its own declaration, or re-exported anywhere in the file', verdictOf(constructRun(ASC.patch.replace(' const SINGLE_SERIES_TYPES', ' export const SINGLE_SERIES_TYPES'), DENY_SET_SOURCE.replace('const SINGLE_SERIES_TYPES', 'export const SINGLE_SERIES_TYPES'))) === 'fires' && ascWith(`${DENY_SET_SOURCE}export { SINGLE_SERIES_TYPES };\n`) === 'fires');
56115636
t('⛔ NO head blob, no reading — fires', ascWith(null) === 'fires');
56125637
t('⛔ a members-only region that never refuses is no refusal — fires', ascWith(DENY_SET_SOURCE.replace("ctx.addIssue({ code: 'custom', message: 'one measure on ' + names });", 'console.log(names);')) === 'fires');
5638+
t('⛔ …and a STRING saying "throw" refuses nothing — fires', ascWith(DENY_SET_SOURCE.replace("ctx.addIssue({ code: 'custom', message: 'one measure on ' + names });", "console.log('we throw ' + names);")) === 'fires');
5639+
t('⛔ a members-only region that RETURNS a value hands members what non-members never get — an allow-list by another spelling — fires', ascWith(DENY_SET_SOURCE.replace(' return;\n }\n}', ' return names;\n }\n}')) === 'fires');
56135640
t('⛔ a members-only region the walk cannot LEX is no reading — fires', ascWith(DENY_SET_SOURCE.replace(' return;\n }', ' const re = /^x/;\n return;\n }')) === 'fires');
56145641
t('⛔ the name inside a STRING still counts as a reader — a mis-lexed name is never an invisible one', ascWith(`${DENY_SET_SOURCE}const LABEL = 'SINGLE_SERIES_TYPES';\n`) === 'fires');
56155642
t('⛔ two declarations of the name are ambiguous — fires', ascWith(`${DENY_SET_SOURCE}function shadow() {\n const SINGLE_SERIES_TYPES = ['x'] as const;\n return SINGLE_SERIES_TYPES;\n}\n`) === 'fires');

0 commit comments

Comments
 (0)