Skip to content

Commit ebcdce5

Browse files
committed
test(spec): the census reads code through the shared comment mask
Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 48949dd commit ebcdce5

1 file changed

Lines changed: 20 additions & 9 deletions

File tree

‎packages/spec/src/contracts/record-share-guest-recipient-census.test.ts‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import { fileURLToPath } from 'node:url';
3939

4040
import { describe, expect, it } from 'vitest';
4141

42+
import { maskComments } from '../../../../scripts/js-comment-mask.mjs';
43+
4244
const SPEC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
4345
const REPO_ROOT = path.resolve(SPEC_ROOT, '../..');
4446
const THIS_FILE = path.relative(REPO_ROOT, fileURLToPath(import.meta.url)).split(path.sep).join('/');
@@ -75,8 +77,13 @@ const PRODUCER_YAML = /^\s*(?:-\s+)?recipient_?[tT]ype\s*:\s*(['"]?)guest\1\s*(?
7577

7678
const lineOf = (text: string, index: number): number => text.slice(0, index).split('\n').length;
7779

78-
const producersIn = (ext: string, text: string, live = 'guest'): number[] => {
79-
if (!text.includes(live)) return [];
80+
/** Extensions read as JS/TS source: their comments are blanked first, since a comment writes no row. */
81+
const CODE_EXT = new Set(['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs']);
82+
83+
const producersIn = (ext: string, raw: string, live = 'guest'): number[] => {
84+
if (!raw.includes(live)) return [];
85+
// `maskComments` blanks comment content and keeps offsets and line numbers.
86+
const text = CODE_EXT.has(ext) ? maskComments(raw) : raw;
8087
const out: number[] = [];
8188
const code = new RegExp(PRODUCER_CODE.source.replace('guest', live), 'g');
8289
for (let m = code.exec(text); m; m = code.exec(text)) out.push(lineOf(text, m.index));
@@ -87,7 +94,7 @@ const producersIn = (ext: string, text: string, live = 'guest'): number[] => {
8794
return [...new Set(out)].sort((a, b) => a - b);
8895
};
8996

90-
/** The string literals inside the bracketed span that starts at `open`, comments stripped. */
97+
/** The string literals inside the bracketed span that starts at `open`, in comment-masked source. */
9198
const literalsInBrackets = (text: string, open: number): string[] => {
9299
let depth = 0;
93100
let end = -1;
@@ -96,12 +103,12 @@ const literalsInBrackets = (text: string, open: number): string[] => {
96103
else if (text[i] === ']') { depth -= 1; if (depth === 0) { end = i; break; } }
97104
}
98105
if (end < 0) return [];
99-
const body = text.slice(open + 1, end).replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
100-
return [...body.matchAll(/(['"`])([^'"`]*)\1/g)].map((m) => m[2]!);
106+
return [...text.slice(open + 1, end).matchAll(/(['"`])([^'"`]*)\1/g)].map((m) => m[2]!);
101107
};
102108

103109
/** Leg 2: the options of the `recipient_type` select in a file that declares `sys_record_share`, or `null`. */
104-
const recordShareSelectOptions = (text: string): string[] | null => {
110+
const recordShareSelectOptions = (raw: string): string[] | null => {
111+
const text = maskComments(raw);
105112
if (!/name:\s*['"]sys_record_share['"]/.test(text)) return null;
106113
const at = text.search(/recipient_type\s*:\s*Field\.select\(/);
107114
if (at < 0) return null;
@@ -110,10 +117,10 @@ const recordShareSelectOptions = (text: string): string[] | null => {
110117
};
111118

112119
/** Leg 3: the literal members of `RecordShareRecipientType`, or `null` when the file does not declare it. */
113-
const unionMembers = (text: string): string[] | null => {
114-
const m = /export\s+type\s+RecordShareRecipientType\s*=([^;]*);/.exec(text);
120+
const unionMembers = (raw: string): string[] | null => {
121+
const m = /export\s+type\s+RecordShareRecipientType\s*=([^;]*);/.exec(maskComments(raw));
115122
if (!m) return null;
116-
return [...m[1]!.replace(/\/\/[^\n]*/g, '').matchAll(/'([^']*)'/g)].map((x) => x[1]!);
123+
return [...m[1]!.matchAll(/'([^']*)'/g)].map((x) => x[1]!);
117124
};
118125

119126
describe('record-share guest recipient census — the matchers can see the value (planted positive controls)', () => {
@@ -130,6 +137,8 @@ describe('record-share guest recipient census — the matchers can see the value
130137
expect(producersIn('.ts', "({ anchor: 'guest', principalKind: 'guest', type: 'guest' })")).toEqual([]);
131138
expect(producersIn('.ts', "({ recipient_type: 'guest_portal' })")).toEqual([]);
132139
expect(producersIn('.md', 'The guest recipient type was removed.')).toEqual([]);
140+
// A comment writes no row; the same words in a string literal do.
141+
expect(producersIn('.ts', "// once: recipient_type: 'guest'\nconst x = 1;")).toEqual([]);
133142
});
134143

135144
it('leg 2 reads the options of the record-share select, and finds a planted guest option', () => {
@@ -143,6 +152,8 @@ describe('record-share guest recipient census — the matchers can see the value
143152
it('leg 3 reads the union members, and finds a planted guest member', () => {
144153
expect(unionMembers("export type RecordShareRecipientType =\n | 'user'\n | 'guest';")).toEqual(['user', 'guest']);
145154
expect(unionMembers("export type SharingRuleRecipientType = 'user' | 'guest';")).toBeNull();
155+
// A member named only in a comment is not a member.
156+
expect(unionMembers("export type RecordShareRecipientType =\n | 'user'\n // | 'guest'\n ;")).toEqual(['user']);
146157
});
147158
});
148159

0 commit comments

Comments
 (0)