@clossys/writer 0.3.2

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/LICENSE +21 -0
  3. package/README.md +593 -0
  4. package/dist/addressability.d.ts +245 -0
  5. package/dist/addressability.d.ts.map +1 -0
  6. package/dist/addressability.js +567 -0
  7. package/dist/addressability.js.map +1 -0
  8. package/dist/checker.d.ts +159 -0
  9. package/dist/checker.d.ts.map +1 -0
  10. package/dist/checker.js +177 -0
  11. package/dist/checker.js.map +1 -0
  12. package/dist/cli.d.ts +149 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +1049 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/copy-gate.d.ts +107 -0
  17. package/dist/copy-gate.d.ts.map +1 -0
  18. package/dist/copy-gate.js +132 -0
  19. package/dist/copy-gate.js.map +1 -0
  20. package/dist/fingerprint.d.ts +61 -0
  21. package/dist/fingerprint.d.ts.map +1 -0
  22. package/dist/fingerprint.js +64 -0
  23. package/dist/fingerprint.js.map +1 -0
  24. package/dist/index.d.ts +47 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +63 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/locale-coverage.d.ts +227 -0
  29. package/dist/locale-coverage.d.ts.map +1 -0
  30. package/dist/locale-coverage.js +414 -0
  31. package/dist/locale-coverage.js.map +1 -0
  32. package/dist/passage.d.ts +294 -0
  33. package/dist/passage.d.ts.map +1 -0
  34. package/dist/passage.js +463 -0
  35. package/dist/passage.js.map +1 -0
  36. package/dist/path-exclusions.d.ts +114 -0
  37. package/dist/path-exclusions.d.ts.map +1 -0
  38. package/dist/path-exclusions.js +213 -0
  39. package/dist/path-exclusions.js.map +1 -0
  40. package/dist/registry.d.ts +61 -0
  41. package/dist/registry.d.ts.map +1 -0
  42. package/dist/registry.js +64 -0
  43. package/dist/registry.js.map +1 -0
  44. package/dist/resolve.d.ts +33 -0
  45. package/dist/resolve.d.ts.map +1 -0
  46. package/dist/resolve.js +133 -0
  47. package/dist/resolve.js.map +1 -0
  48. package/dist/scan.d.ts +457 -0
  49. package/dist/scan.d.ts.map +1 -0
  50. package/dist/scan.js +1698 -0
  51. package/dist/scan.js.map +1 -0
  52. package/dist/schema.d.ts +81 -0
  53. package/dist/schema.d.ts.map +1 -0
  54. package/dist/schema.js +482 -0
  55. package/dist/schema.js.map +1 -0
  56. package/dist/types.d.ts +268 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +40 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/voice/checker.d.ts +186 -0
  61. package/dist/voice/checker.d.ts.map +1 -0
  62. package/dist/voice/checker.js +483 -0
  63. package/dist/voice/checker.js.map +1 -0
  64. package/dist/voice/derivation-coverage.d.ts +168 -0
  65. package/dist/voice/derivation-coverage.d.ts.map +1 -0
  66. package/dist/voice/derivation-coverage.js +181 -0
  67. package/dist/voice/derivation-coverage.js.map +1 -0
  68. package/dist/voice/fields.d.ts +107 -0
  69. package/dist/voice/fields.d.ts.map +1 -0
  70. package/dist/voice/fields.js +144 -0
  71. package/dist/voice/fields.js.map +1 -0
  72. package/dist/voice/index.d.ts +19 -0
  73. package/dist/voice/index.d.ts.map +1 -0
  74. package/dist/voice/index.js +24 -0
  75. package/dist/voice/index.js.map +1 -0
  76. package/dist/voice/internal/parse-template.d.ts +27 -0
  77. package/dist/voice/internal/parse-template.d.ts.map +1 -0
  78. package/dist/voice/internal/parse-template.js +111 -0
  79. package/dist/voice/internal/parse-template.js.map +1 -0
  80. package/dist/voice/internal/pattern-safety.d.ts +111 -0
  81. package/dist/voice/internal/pattern-safety.d.ts.map +1 -0
  82. package/dist/voice/internal/pattern-safety.js +278 -0
  83. package/dist/voice/internal/pattern-safety.js.map +1 -0
  84. package/dist/voice/schema.d.ts +65 -0
  85. package/dist/voice/schema.d.ts.map +1 -0
  86. package/dist/voice/schema.js +535 -0
  87. package/dist/voice/schema.js.map +1 -0
  88. package/dist/voice/types.d.ts +282 -0
  89. package/dist/voice/types.d.ts.map +1 -0
  90. package/dist/voice/types.js +37 -0
  91. package/dist/voice/types.js.map +1 -0
  92. package/package.json +72 -0
  93. package/src/addressability.ts +664 -0
  94. package/src/checker.ts +293 -0
  95. package/src/cli.ts +1166 -0
  96. package/src/copy-gate.ts +197 -0
  97. package/src/fingerprint.ts +66 -0
  98. package/src/index.ts +163 -0
  99. package/src/locale-coverage.ts +512 -0
  100. package/src/passage.ts +644 -0
  101. package/src/path-exclusions.ts +265 -0
  102. package/src/registry.ts +103 -0
  103. package/src/resolve.ts +172 -0
  104. package/src/scan.ts +1959 -0
  105. package/src/schema.ts +528 -0
  106. package/src/types.ts +301 -0
  107. package/src/voice/checker.ts +590 -0
  108. package/src/voice/derivation-coverage.ts +217 -0
  109. package/src/voice/fields.ts +160 -0
  110. package/src/voice/index.ts +55 -0
  111. package/src/voice/internal/parse-template.ts +114 -0
  112. package/src/voice/internal/pattern-safety.ts +336 -0
  113. package/src/voice/schema.ts +585 -0
  114. package/src/voice/types.ts +329 -0
  115. package/templates/voice-record.template.jsonc +135 -0
@@ -0,0 +1,567 @@
1
+ /**
2
+ * `scanAddressabilitySources` and `checkAddressability` — the
3
+ * copy-ADDRESSABILITY gate: is user-facing prose resolved from a copy
4
+ * registry BY ID, or typed directly into a component? A DIFFERENT, STRICTER
5
+ * question than `copy-gate.ts`'s traceability check. Traceability accepts a
6
+ * literal that happens to match a registered `CopyEntry.text`'s SHAPE, or
7
+ * that carries a `copy:<id>` citation comment — both of which still leave
8
+ * the actual sentence sitting in the component's own source. That is cheap
9
+ * to verify but NOT cheap to change: marketing renaming a CTA means editing
10
+ * every component that typed it, not one registry entry. Addressability is
11
+ * the property that keeps that rename cheap: the component's own source
12
+ * contains an id, never the sentence. This file does not accept a citation
13
+ * or a text-shape match as satisfying — see `extractAddressabilityCandidates`
14
+ * below, which has no `CopyRecord` input at all and therefore nothing to
15
+ * match against; a literal is either not present as authored prose in a
16
+ * position this gate can see, or it is a violation.
17
+ *
18
+ * Measured across four real sites at the time this gate was written: 2
19
+ * inline prose strings on one, 4 attribute-borne on another, 0 elsewhere —
20
+ * so the property currently holds, by luck rather than enforcement. This
21
+ * file is what turns that measurement into something that stays true.
22
+ *
23
+ * NO PARSER LIBRARY, no new dependency — `packages/copy` ships zero runtime
24
+ * dependencies and this file adds none. It reuses `scan.ts`'s
25
+ * `extractCopyCandidates` for the genuinely hard part (finding every
26
+ * string/template literal and JSX text-node BOUNDARY correctly — escaped
27
+ * quotes, nested interpolation, comments, regex literals, and JSX itself),
28
+ * and `path-exclusions.ts`'s `validatePathExclusions`/`matchesPathExclusion`
29
+ * for the file-scope exclusion mechanism, exactly the way `scan.ts` itself
30
+ * does. This file's own walker (`scanAddressabilitySources`) mirrors
31
+ * `scan.ts`'s `scanCopySourceTree` and `@example/ui`'s
32
+ * `style-scan.ts`'s `scanStyleSources` closely (same extensions/skipDirs
33
+ * defaults, same `SKIP_FILE_RE`, same fail-closed-on-unreadable-directory
34
+ * contract) — a thin per-file dispatch loop, not a second tokenizer: the
35
+ * actual literal/JSX-boundary parsing is ENTIRELY delegated to `scan.ts`.
36
+ *
37
+ * ============================================================================
38
+ * THREE POSITIONS, THREE CLASSIFICATIONS
39
+ * ============================================================================
40
+ *
41
+ * 1. MARKUP TEXT NODES (`<span>Hello</span>`'s `Hello`) — `scan.ts`'s own
42
+ * `kind: "jsx-text"` candidates. `scan.ts` already dropped
43
+ * whitespace-only and no-letters runs before these ever reach this
44
+ * file, so every one remaining is real, rendered prose authored
45
+ * directly in markup — never resolved through an id, by construction
46
+ * (a JSX text node IS the literal text; an id-resolved child would be
47
+ * an EXPRESSION child, `{copy(id)}`, which `scan.ts`'s tokenizer treats
48
+ * as code, never a text-node candidate at all — see `scan.ts`'s own
49
+ * top doc comment, "JSX TEXT NODES"). ALWAYS a violation.
50
+ *
51
+ * 2. THE FOUR USER-FACING ATTRIBUTES — `aria-label`, `placeholder`, `alt`,
52
+ * `title`. These carry prose a person reads (an accessible name, a
53
+ * hint, alt text, a tooltip) and are NOT text nodes; a scanner that
54
+ * only understands text nodes reports zero on a real component whose
55
+ * ENTIRE user-facing surface is `<input aria-label="..." placeholder=
56
+ * "..." />` and calls that a clean pass. `scan.ts` itself already
57
+ * excludes `aria-label` from its own candidate list (bundled under
58
+ * `"aria-or-data-attribute-value"` together with `aria-hidden`,
59
+ * `aria-describedby`, every `data-*` attribute — a deliberate SCOPE
60
+ * decision for traceability, see `scan.ts`'s exclusion #4) and leaves
61
+ * `placeholder`/`alt`/`title` as ordinary default-included candidates
62
+ * (neither aria/data-shaped nor on its structural denylist). This gate
63
+ * re-derives each literal's own attribute NAME locally — a string
64
+ * literal never spans a line (see `scan.ts`'s `scanStringLiteral`), so
65
+ * "the identifier immediately before this literal's own quote, on the
66
+ * SAME line" is an exact lookup, not an approximation — specifically to
67
+ * recover `aria-label` back into scope while leaving every OTHER
68
+ * aria-*, data-* attribute exactly where `scan.ts` already decided it
69
+ * belongs: out of copy governance entirely. A literal-valued occurrence
70
+ * of one of the four, carrying at least one letter, is ALWAYS a
71
+ * violation — there is no citation or registry-text-match escape hatch
72
+ * here (see this file's top comment for why) — UNLESS the assignment
73
+ * this gate re-derived is itself a DESTRUCTURING-PATTERN DEFAULT or a
74
+ * plain (non-destructured) PARAMETER DEFAULT (`{ "aria-label":
75
+ * ariaLabel = "Pagination" }`, `{ placeholder = "Search" }`,
76
+ * `function f(alt = "...")`), never a JSX attribute at all — see
77
+ * `isDestructuringOrParameterDefault`. A default value in a
78
+ * destructuring pattern is exactly the property this gate exists to
79
+ * protect: the CONSUMER can override it, so the component's own source
80
+ * does not lock the sentence in the way a hardcoded JSX attribute
81
+ * does. Reporting one as a violation would not just be a false
82
+ * positive — it would INVERT the verdict on a construct that is
83
+ * already correctly addressable.
84
+ *
85
+ * 3. EVERYTHING ELSE THIS GATE CANNOT CONFIDENTLY CLASSIFY — a template
86
+ * literal (in ANY position, including one of the four attributes above:
87
+ * a template can carry an interpolated copy id, a partial sentence, or
88
+ * genuine inline prose, and this gate does not attempt to tell those
89
+ * apart), an object/array literal VALUE (`scan.ts` only excludes object
90
+ * KEYS, never values — `{ tooltip: "Explain this" }`'s `"Explain
91
+ * this"` is a default-included candidate with no attribute name at
92
+ * all), or a plain string in a prop that is none of the four (a custom
93
+ * `tooltip`, a bare variable assignment, ...) — UNLESS that string is
94
+ * itself shaped like a CSS/Tailwind utility class LIST (`"border-t
95
+ * border-line-base pt-xs"`, `"grid-cols-1 tablet:grid-cols-2"`), which
96
+ * is definitively not prose and is excluded here for the same reason
97
+ * `scan.ts` already excludes that shape when it DOES have an attribute
98
+ * name to key off (a literal `className="..."` value) — see
99
+ * `looksLikeUtilityClassList`. Everything else in this category is
100
+ * NEVER silently treated as clean, and NEVER treated as a violation
101
+ * either — reported as `unchecked`, the same discipline `scan.ts`'s own
102
+ * `UncheckedItem` and `checkCopyTraceability`'s `unchecked` passthrough
103
+ * already hold JSX parse ambiguities to. A measurement may round an
104
+ * unknown down to "probably fine"; a gate may not — that is the whole
105
+ * reason this file exists rather than a grep for `aria-label="`.
106
+ *
107
+ * `scan.ts`'s own exclusions for genuinely non-user-facing literals
108
+ * (`className`, `data-testid`, an import specifier, a `cx(...)` argument, a
109
+ * developer diagnostic, a decorative no-letters glyph, an enum-shaped
110
+ * single token, a `type`/`interface` context) apply here for free: a
111
+ * literal `scan.ts` excludes for one of THOSE reasons never becomes a
112
+ * `candidates` entry at all, so it never reaches this file's own
113
+ * classification and is never reported by it, in any of the three
114
+ * categories above. This is what keeps this gate from flagging `className`,
115
+ * `data-*`, an import specifier, or a `testId` — see this file's own test
116
+ * suite for a fixture proving exactly that.
117
+ *
118
+ * ============================================================================
119
+ * THE TERNARY
120
+ * ============================================================================
121
+ *
122
+ * Deliberately DIFFERENT from `copy-gate.ts`/`scan.ts`'s "an `unchecked[]`
123
+ * list gates a `2` before findings are counted" precedence — see issue #407.
124
+ * On this gate specifically, partial coverage of a real tree is not a rare
125
+ * edge case: hundreds of string positions are token data (category 3 above)
126
+ * this gate cannot classify BY DESIGN, on every real tree it has ever been
127
+ * run against. Letting "at least one unclassified position" outrank a
128
+ * confirmed violation therefore does not model "an occasional coverage gap
129
+ * might be hiding something" — it makes the `"violated"` branch permanently
130
+ * unreachable outside a fixture, because a real tree always has unclassified
131
+ * positions somewhere. A gate that can only ever report `"indeterminate"` or
132
+ * `"satisfied"` in production has one usable state, and a caller that
133
+ * learns to treat `2` as "coverage is never complete, ignore it" will walk
134
+ * straight past five named, file-and-line violations without ever seeing
135
+ * them — see `packages/inspector/src/review-evidence.ts`'s own argument
136
+ * against the mirror image of this mistake (folding an outage into a
137
+ * verdict); this is folding a verdict into an outage.
138
+ *
139
+ * - `"violated"` — at least one inline user-facing prose string found
140
+ * (category 1 or 2 above), REGARDLESS of how many string positions are
141
+ * unclassified. The coverage gap is not hidden: `reasons` and
142
+ * `unchecked` are still populated and still printed (`cli.ts`'s
143
+ * `printAddressabilityAccounting` reports `unchecked` unconditionally,
144
+ * independent of verdict) — a `"violated"` verdict never claims full
145
+ * coverage, it claims that what WAS seen included a definite violation,
146
+ * which is true regardless of what else was not seen.
147
+ * - `"indeterminate"` — zero violations, AND the source tree could not be
148
+ * read at all (this file's `scanAddressabilitySources` fails closed,
149
+ * throwing, exactly like `scan.ts`'s `scanCopySourceTree`), ZERO
150
+ * components were successfully scanned (nothing matched, or every
151
+ * matched file failed to parse), a `pathExclusions` entry was
152
+ * malformed, or ANY string position could not be confidently classified
153
+ * (category 3 above, or a JSX construct `scan.ts` itself could not
154
+ * resolve). The honest "found nothing, but did not see everything"
155
+ * case — it must never read as a pass.
156
+ * - `"satisfied"` — at least one component scanned, every string
157
+ * position classified, and no inline user-facing prose found.
158
+ */
159
+ import { readdirSync, readFileSync, statSync } from "node:fs";
160
+ import { extname, join, relative, sep } from "node:path";
161
+ import { extractCopyCandidates } from "./scan.js";
162
+ import { matchesPathExclusion, validatePathExclusions, } from "./path-exclusions.js";
163
+ const DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
164
+ const DEFAULT_SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
165
+ /** `.test.ts(x)`, `.spec.ts(x)`, `.check.ts(x)`, and bare `.d.ts` — the identical pattern `scan.ts` and `@example/ui`'s `style-scan.ts` both use, reused verbatim rather than reinvented (not exported by `scan.ts`, so redefined locally here — the same precedent `style-scan.ts` sets for its own copy of this constant). */
166
+ const SKIP_FILE_RE = /\.(test|spec|check)\.(ts|tsx|js|jsx)$|\.d\.ts$/;
167
+ /**
168
+ * Walks `root` recursively, reads every matching file, and extracts
169
+ * addressability violations/unchecked positions from each (see
170
+ * `extractAddressabilityCandidates`). FAILS CLOSED on an unreadable
171
+ * directory or file — throws a plain `Error`, matching `scan.ts`'s
172
+ * `scanCopySourceTree` exactly, for the identical reason: a directory this
173
+ * function could not read might be hiding an unknown amount of inline
174
+ * prose, and reporting "0 files scanned" for that case would read as a
175
+ * clean pass when nothing was actually verified.
176
+ */
177
+ export function scanAddressabilitySources(root, options = {}) {
178
+ const extensions = new Set(options.extensions ?? DEFAULT_EXTENSIONS);
179
+ const skipDirs = new Set(options.skipDirs ?? DEFAULT_SKIP_DIRS);
180
+ const pathExclusionValidation = validatePathExclusions(options.pathExclusions);
181
+ const matchCounts = new Map();
182
+ for (const exclusion of pathExclusionValidation.valid)
183
+ matchCounts.set(exclusion, 0);
184
+ const result = {
185
+ filesScanned: 0,
186
+ violations: [],
187
+ unchecked: [],
188
+ skippedByDesign: [],
189
+ parseFailures: [],
190
+ excludedFiles: [],
191
+ pathExclusionFindings: [...pathExclusionValidation.findings],
192
+ };
193
+ function walk(dir) {
194
+ let entries;
195
+ try {
196
+ entries = readdirSync(dir);
197
+ }
198
+ catch (error) {
199
+ throw new Error(`scanAddressabilitySources: cannot read directory "${dir}": ${error instanceof Error ? error.message : String(error)}`);
200
+ }
201
+ for (const entry of entries) {
202
+ if (skipDirs.has(entry))
203
+ continue;
204
+ const full = join(dir, entry);
205
+ let stat;
206
+ try {
207
+ stat = statSync(full);
208
+ }
209
+ catch {
210
+ continue; // broken symlink — nothing to read, not a directory-listing failure
211
+ }
212
+ if (stat.isDirectory()) {
213
+ walk(full);
214
+ continue;
215
+ }
216
+ if (!stat.isFile())
217
+ continue;
218
+ if (!extensions.has(extname(entry).toLowerCase()))
219
+ continue;
220
+ const relPath = relative(root, full).split(sep).join("/");
221
+ let matchedExclusion;
222
+ for (const exclusion of pathExclusionValidation.valid) {
223
+ if (matchesPathExclusion(relPath, exclusion)) {
224
+ matchCounts.set(exclusion, (matchCounts.get(exclusion) ?? 0) + 1);
225
+ matchedExclusion ??= exclusion;
226
+ }
227
+ }
228
+ if (matchedExclusion) {
229
+ result.excludedFiles.push({ file: relPath, reason: matchedExclusion.reason, pattern: matchedExclusion.path });
230
+ continue;
231
+ }
232
+ if (SKIP_FILE_RE.test(entry)) {
233
+ result.skippedByDesign.push({ file: relPath, reason: "test-or-check-file" });
234
+ continue;
235
+ }
236
+ let content;
237
+ try {
238
+ content = readFileSync(full, "utf8");
239
+ }
240
+ catch (error) {
241
+ throw new Error(`scanAddressabilitySources: cannot read file "${full}": ${error instanceof Error ? error.message : String(error)}`);
242
+ }
243
+ const extracted = extractAddressabilityCandidates(content, relPath);
244
+ if (extracted.parseFailure) {
245
+ result.parseFailures.push({ file: relPath, detail: extracted.parseFailure });
246
+ continue; // no positions trusted from a file scan.ts could not reliably tokenize
247
+ }
248
+ result.filesScanned++;
249
+ result.violations.push(...extracted.violations);
250
+ result.unchecked.push(...extracted.unchecked);
251
+ }
252
+ }
253
+ walk(root);
254
+ for (const exclusion of pathExclusionValidation.valid) {
255
+ if ((matchCounts.get(exclusion) ?? 0) === 0) {
256
+ result.pathExclusionFindings.push({
257
+ rule: "path-exclusion-unused",
258
+ severity: "warning",
259
+ message: `pathExclusions entry "${exclusion.path}" (reason: "${exclusion.reason}") did not match any scanned file. Remove it, or check for a typo/renamed path.`,
260
+ path: exclusion.path,
261
+ });
262
+ }
263
+ }
264
+ return result;
265
+ }
266
+ // ------------------------------------------------------------- extraction
267
+ /** `ariaLabel` -> `arialabel`, `aria-label` -> `arialabel` — the identical case/hyphen-insensitive normalization `scan.ts`'s own `normalizeIdentifier` performs (not exported, so reimplemented locally — a one-line heuristic, not worth a cross-file dependency). */
268
+ function normalizeAttrName(name) {
269
+ return name
270
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
271
+ .toLowerCase()
272
+ .replace(/-/g, "");
273
+ }
274
+ function hasProse(text) {
275
+ return /\p{L}/u.test(text.trim());
276
+ }
277
+ /**
278
+ * Finds the identifier immediately before `raw` on its own source line —
279
+ * `raw` INCLUDES its own quotes, exactly as `scan.ts` slices it, so
280
+ * `lineText.indexOf(raw)` lands on the literal's own opening quote. Safe
281
+ * ONLY for a plain string literal: `scan.ts`'s `scanStringLiteral` never
282
+ * lets a string span a raw newline (an unterminated string ends the
283
+ * literal at end-of-line instead), so a single-line lookup is exact here,
284
+ * never an approximation — unlike a template literal, which CAN span
285
+ * multiple lines and is never passed through this function (see
286
+ * `extractAddressabilityCandidates`, which routes every template literal
287
+ * straight to `unchecked` without ever attempting this lookup).
288
+ */
289
+ function attributeNameFor(lines, line, raw) {
290
+ const lineText = lines[line - 1];
291
+ if (lineText === undefined)
292
+ return undefined;
293
+ const idx = lineText.indexOf(raw);
294
+ if (idx === -1)
295
+ return undefined;
296
+ const prefix = lineText.slice(0, idx);
297
+ const match = /([A-Za-z_$][A-Za-z0-9_$-]*)\s*=\s*\{?\s*$/.exec(prefix);
298
+ return match?.[1];
299
+ }
300
+ /**
301
+ * How many lines of LEADING context `isDestructuringOrParameterDefault`
302
+ * looks back through to find the `{`/`(`/`,` that precedes a destructured
303
+ * property or parameter — each property in a real multi-line destructure
304
+ * (see `Pagination.tsx`'s own `PaginationProps` destructure) is normally
305
+ * its own line, so the delimiter is usually one line up, on the PRIOR
306
+ * property's trailing comma. A small, bounded window, not an attempt to
307
+ * parse the whole enclosing pattern.
308
+ */
309
+ const DESTRUCTURING_CONTEXT_LINES = 5;
310
+ /**
311
+ * `{`/`(` (the pattern's or parameter list's own opening delimiter) or a
312
+ * bare `,` (separating this property/parameter from the previous one),
313
+ * optionally preceded by a rename (`identifier:` or `"quoted-key":`, for
314
+ * `{ "aria-label": ariaLabel = "..." }`), then the bound identifier
315
+ * itself and `=` — anchored at the END of the search text, matching
316
+ * exactly what precedes a destructuring-pattern DEFAULT VALUE or a plain
317
+ * (non-destructured) parameter default. Deliberately does NOT match
318
+ * through any OTHER non-whitespace token: if something this narrow
319
+ * heuristic cannot account for sits between the delimiter and the
320
+ * identifier, the match simply fails and `isDestructuringOrParameterDefault`
321
+ * returns `false`, falling back to the pre-existing (JSX-attribute)
322
+ * assumption rather than guessing.
323
+ */
324
+ const DESTRUCTURING_OR_PARAMETER_DEFAULT_RE = /[{(,]\s*(?:(?:"[^"]*"|'[^']*'|[A-Za-z_$][A-Za-z0-9_$]*)\s*:\s*)?[A-Za-z_$][A-Za-z0-9_$-]*\s*=\s*\{?\s*$/;
325
+ /**
326
+ * Whether the `identifier =` assignment `attributeNameFor` found
327
+ * immediately before `raw` is actually a DESTRUCTURING-PATTERN DEFAULT
328
+ * (`{ label = "Save" }`, or renamed `{ "aria-label": ariaLabel = "Save"
329
+ * }`) or a plain, non-destructured PARAMETER DEFAULT (`function
330
+ * f(ariaLabel = "Save")`) — never a JSX attribute assignment at all
331
+ * (`aria-label="Save"`). `attributeNameFor`'s own regex cannot tell these
332
+ * apart on its own: both are exactly `identifier = "..."` in the raw
333
+ * source text it looks at. They ARE distinguishable, though, by what
334
+ * immediately precedes the identifier once an optional rename is skipped
335
+ * over: a JSX attribute is always preceded by a tag name, another
336
+ * attribute's already-finished value, or `<` itself — NEVER by `{`, `(`,
337
+ * or `,`. A destructuring pattern's own property, or a parameter list's
338
+ * own argument, is ALWAYS preceded by exactly one of those three (`{`/`(`
339
+ * for the first property/argument, `,` for every one after) — JSX
340
+ * attributes, by contrast, are never comma-separated, and neither a
341
+ * pattern nor a parameter list opens with `<`. See
342
+ * `DESTRUCTURING_OR_PARAMETER_DEFAULT_RE`'s own doc comment for exactly
343
+ * what is and is not matched.
344
+ */
345
+ function isDestructuringOrParameterDefault(lines, line, raw) {
346
+ const lineText = lines[line - 1];
347
+ if (lineText === undefined)
348
+ return false;
349
+ const idx = lineText.indexOf(raw);
350
+ if (idx === -1)
351
+ return false;
352
+ const start = Math.max(0, line - 1 - DESTRUCTURING_CONTEXT_LINES);
353
+ const context = [...lines.slice(start, line - 1), lineText.slice(0, idx)].join("\n");
354
+ return DESTRUCTURING_OR_PARAMETER_DEFAULT_RE.test(context);
355
+ }
356
+ /**
357
+ * Each token in a CSS/Tailwind-shaped utility class LIST: an optional
358
+ * leading `-` (negative utilities, e.g. `-mt-2`), a lowercase
359
+ * alphanumeric run, then one or more `-`/`:`/`/`-separated lowercase
360
+ * alphanumeric segments — `border-t`, `border-line-base`, `pt-xs`,
361
+ * `grid-cols-1`, `tablet:grid-cols-2` all match; a bare English word
362
+ * (`flex`, `hidden`, `low`, `open`) does NOT, because it has no separated
363
+ * segment at all (the trailing group requires at least one repetition).
364
+ * That asymmetry is deliberate — see `looksLikeUtilityClassList`'s own
365
+ * doc comment.
366
+ */
367
+ const CLASS_TOKEN_RE = /^-?[a-z0-9]+(?:[:/-][a-z0-9.]+)+$/;
368
+ /**
369
+ * Whether `raw` (a plain string literal's own raw source text, quotes
370
+ * included — never a template literal, see this function's call site)
371
+ * is shaped like a space-separated list of CSS/Tailwind utility class
372
+ * names rather than prose. `scan.ts` already excludes this exact shape
373
+ * when the literal sits in an actual `className` ATTRIBUTE (see this
374
+ * file's top doc comment, "THREE POSITIONS"); this catches the SAME
375
+ * shape one level removed — a class list assigned to a variable or
376
+ * sitting in an object-literal value (`Faq.tsx`'s `"border-t
377
+ * border-line-base pt-xs"`, `FieldGroup.tsx`'s `"grid-cols-1
378
+ * tablet:grid-cols-2"`) — which has no attribute name for `scan.ts`'s own
379
+ * exclusion to key off, and so reaches this file as an ordinary "string"
380
+ * candidate instead.
381
+ *
382
+ * Deliberately conservative: EVERY token must match `CLASS_TOKEN_RE`,
383
+ * which requires at least one `-`/`:`/`/`-separated segment — a shape a
384
+ * hand-typed English sentence does not reliably have across every one of
385
+ * its words, but a Tailwind utility name always does. A bare,
386
+ * separator-free utility class (`"flex"`, `"hidden"`) mixed into such a
387
+ * list still fails this check and is left `unchecked` rather than risk a
388
+ * real short English word (`"low"`, `"open"`) being swept in as
389
+ * definitively clean — an intentional under-classification, not an
390
+ * oversight; see this file's own test suite.
391
+ */
392
+ function looksLikeUtilityClassList(raw) {
393
+ const quote = raw[0];
394
+ if (quote !== '"' && quote !== "'")
395
+ return false; // a template literal never reaches this — see this function's call site
396
+ const text = raw.slice(1, -1).trim();
397
+ if (text.length === 0 || text.length > 300)
398
+ return false;
399
+ const tokens = text.split(/\s+/);
400
+ if (tokens.length < 2 || tokens.length > 40)
401
+ return false;
402
+ return tokens.every((t) => t.length <= 60 && CLASS_TOKEN_RE.test(t));
403
+ }
404
+ /**
405
+ * Pure — no I/O. Classifies every string/template literal and JSX text
406
+ * node `scan.ts`'s `extractCopyCandidates` finds in `content` into one of
407
+ * this file's three positions (see top doc comment). Exported so a test
408
+ * can exercise classification directly against a source-text fixture,
409
+ * mirroring `extractCopyCandidates` itself.
410
+ */
411
+ export function extractAddressabilityCandidates(content, filePath) {
412
+ const { candidates, excluded, unchecked: jsxUnchecked, parseFailure } = extractCopyCandidates(content, filePath);
413
+ if (parseFailure)
414
+ return { violations: [], unchecked: [], parseFailure };
415
+ const lines = content.split("\n");
416
+ const violations = [];
417
+ const unchecked = [];
418
+ // JSX constructs scan.ts itself recognized but could not resolve
419
+ // (unclosed elements, unterminated attribute values/expressions, ...) —
420
+ // passed through unchanged. A region scan.ts could not classify is a
421
+ // region THIS gate cannot classify either.
422
+ for (const u of jsxUnchecked) {
423
+ unchecked.push({ file: u.file, line: u.line, kind: u.kind, detail: u.detail });
424
+ }
425
+ // ---- position 1: markup text nodes — always a violation ----
426
+ for (const c of candidates) {
427
+ if (c.kind !== "jsx-text")
428
+ continue;
429
+ violations.push({ file: c.file, line: c.line, position: "markup-text", raw: c.raw });
430
+ }
431
+ // ---- position 2a: aria-label, recovered from scan.ts's own excluded
432
+ // "aria-or-data-attribute-value" bucket, which bundles it together with
433
+ // every OTHER aria-*/data-* attribute (see this file's top doc comment).
434
+ for (const e of excluded) {
435
+ if (e.reason !== "aria-or-data-attribute-value")
436
+ continue;
437
+ // scan.ts's own attribute-name check (which decided this literal
438
+ // belongs here) runs identically regardless of quote kind, so this
439
+ // bucket can contain either a plain string OR a template literal —
440
+ // `attributeNameFor`'s single-line lookup is safe for both here
441
+ // specifically because a template literal used as an ATTRIBUTE VALUE
442
+ // is always `name={`...`}` on one line in practice (a multi-line one
443
+ // would fail this lookup and fall into "unresolved-attribute-name"
444
+ // below, never silently misattributed).
445
+ const attrName = attributeNameFor(lines, e.line, e.raw);
446
+ if (attrName === undefined) {
447
+ // scan.ts confirmed this literal sits in an aria-*/data-* attribute
448
+ // position, but this file's own (deliberately simpler, JSX-only)
449
+ // lookup could not re-derive which one — never silently dropped.
450
+ unchecked.push({
451
+ file: e.file,
452
+ line: e.line,
453
+ kind: "unresolved-attribute-name",
454
+ detail: `an aria-*/data-* attribute value ${e.raw} could not be traced back to its own attribute name on the same line`,
455
+ });
456
+ continue;
457
+ }
458
+ if (normalizeAttrName(attrName) !== "arialabel")
459
+ continue; // a different aria-*/data-* attribute — out of this gate's scope, same as scan.ts's own traceability gate
460
+ if (isDestructuringOrParameterDefault(lines, e.line, e.raw))
461
+ continue; // a default value the consumer can override, not a JSX attribute at all — see isDestructuringOrParameterDefault
462
+ if (e.raw.startsWith("`")) {
463
+ // A template literal in the ONE attribute this gate DOES track —
464
+ // could carry an interpolated copy id or genuine inline prose, and
465
+ // this gate does not attempt to tell those apart (see this file's
466
+ // top doc comment, category 3). Indeterminate, never a violation.
467
+ unchecked.push({
468
+ file: e.file,
469
+ line: e.line,
470
+ kind: "template-literal",
471
+ detail: `template literal ${e.raw} (aria-label) could carry an interpolated copy id, a partial sentence, or genuine inline prose — this gate does not attempt to tell those apart`,
472
+ });
473
+ continue;
474
+ }
475
+ if (!hasProse(e.raw))
476
+ continue; // e.g. aria-hidden-shaped booleans/empty values that happened to still match the aria/data check
477
+ violations.push({ file: e.file, line: e.line, position: "user-facing-attribute", attribute: "aria-label", raw: e.raw });
478
+ }
479
+ // ---- position 2b: placeholder/alt/title — not aria/data-shaped and not
480
+ // on scan.ts's structural denylist, so these are already ordinary
481
+ // default-included "string" candidates. Reclassified here rather than
482
+ // left to fall through to position 3 below.
483
+ const claimed = new Set();
484
+ for (const c of candidates) {
485
+ if (c.kind !== "string")
486
+ continue;
487
+ const attrName = attributeNameFor(lines, c.line, c.raw);
488
+ if (attrName === undefined)
489
+ continue;
490
+ const normalized = normalizeAttrName(attrName);
491
+ // "arialabel" never reaches this branch in practice — scan.ts already
492
+ // routed it into `excluded` (see position 2a above) before it could
493
+ // ever become a "string"-kind candidate — but the explicit exclusion
494
+ // documents that this loop is deliberately NOT the aria-label path.
495
+ const attribute = ["placeholder", "alt", "title"].find((n) => n === normalized);
496
+ if (!attribute)
497
+ continue;
498
+ // Handled here either way (violation OR a default value excluded
499
+ // below) — `claimed` marks it done so position 3 below never
500
+ // re-examines it and reports it as unclassified instead.
501
+ claimed.add(`${c.line}::${c.raw}`);
502
+ if (isDestructuringOrParameterDefault(lines, c.line, c.raw))
503
+ continue; // a default value the consumer can override, not a JSX attribute at all — see isDestructuringOrParameterDefault
504
+ violations.push({ file: c.file, line: c.line, position: "user-facing-attribute", attribute, raw: c.raw });
505
+ }
506
+ // ---- position 3: everything else scan.ts kept as a real candidate —
507
+ // never silently treated as clean, never treated as a violation either
508
+ // — except a plain string shaped like a utility class LIST, which is
509
+ // definitively not prose (see looksLikeUtilityClassList).
510
+ for (const c of candidates) {
511
+ if (c.kind === "jsx-text")
512
+ continue; // already position 1
513
+ if (c.kind === "string" && claimed.has(`${c.line}::${c.raw}`))
514
+ continue; // already position 2
515
+ if (c.kind === "string" && looksLikeUtilityClassList(c.raw))
516
+ continue; // a class-name list, not prose
517
+ unchecked.push({
518
+ file: c.file,
519
+ line: c.line,
520
+ kind: c.kind === "template" ? "template-literal" : "unclassified-string-position",
521
+ detail: c.kind === "template"
522
+ ? `template literal ${c.raw} could carry an interpolated copy id, a partial sentence, or genuine inline prose — this gate does not attempt to tell those apart`
523
+ : `string ${c.raw} is not a JSX markup text node or one of aria-label/placeholder/alt/title — could be an object/array literal value or some other prop this gate does not classify`,
524
+ });
525
+ }
526
+ return { violations, unchecked };
527
+ }
528
+ /**
529
+ * Pure — takes an already-computed `AddressabilityScanResult`, decides the
530
+ * ternary verdict. Mirrors `copy-gate.ts`'s `checkCopyTraceability` split
531
+ * from `scan.ts`: this function does no I/O and never throws.
532
+ */
533
+ export function checkAddressability(scan) {
534
+ const reasons = [];
535
+ if (scan.filesScanned === 0) {
536
+ reasons.push(scan.parseFailures.length > 0
537
+ ? "every matched file failed to parse — nothing was actually scanned"
538
+ : "no components were scanned");
539
+ }
540
+ else if (scan.parseFailures.length > 0) {
541
+ reasons.push(`${scan.parseFailures.length} matched file(s) could not be parsed and were never examined`);
542
+ }
543
+ const invalidPathExclusions = scan.pathExclusionFindings.filter((f) => f.severity === "error");
544
+ if (invalidPathExclusions.length > 0) {
545
+ reasons.push(`${invalidPathExclusions.length} pathExclusions entr${invalidPathExclusions.length === 1 ? "y is" : "ies are"} invalid`);
546
+ }
547
+ if (scan.unchecked.length > 0) {
548
+ reasons.push(`${scan.unchecked.length} string position(s) could not be confidently classified`);
549
+ }
550
+ const base = { violations: scan.violations, unchecked: scan.unchecked, filesScanned: scan.filesScanned };
551
+ // A real finding wins over an incomplete picture — see this file's top
552
+ // doc comment, "THE TERNARY", and issue #407. Deliberately the OPPOSITE
553
+ // of copy-gate.ts's/scan.ts's "a 2 gates before findings are counted"
554
+ // precedence: on THIS gate, partial coverage (category 3's unclassified
555
+ // positions) is the PERMANENT state of any real tree, so letting it
556
+ // outrank a violation makes "violated" unreachable outside a fixture.
557
+ // The coverage gap is not lost by returning "violated" here — `reasons`
558
+ // is still populated below and still printed unconditionally by
559
+ // `cli.ts`'s `printAddressabilityAccounting`; only the verdict/exit-code
560
+ // changes.
561
+ if (scan.violations.length > 0)
562
+ return { verdict: "violated", reasons, ...base };
563
+ if (reasons.length > 0)
564
+ return { verdict: "indeterminate", reasons, ...base };
565
+ return { verdict: "satisfied", reasons, ...base };
566
+ }
567
+ //# sourceMappingURL=addressability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addressability.js","sourceRoot":"","sources":["../src/addressability.ts"],"names":[],"mappings":"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"}