@jterrazz/test 15.0.0 → 15.1.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.
package/dist/checker.js CHANGED
@@ -218,7 +218,7 @@ function lineAt(text, index) {
218
218
  * Every quoted string and simple template literal in a source. Over-collection
219
219
  * only ever *hides* a dead-fixture finding, so a greedy regex is safe here.
220
220
  */
221
- const STRING_LITERAL = /'(?<single>(?:[^'\\\n]|\\.)*)'|"(?<double>(?:[^"\\\n]|\\.)*)"|`(?<template>(?:[^`$\\]|\\.)*)`/g;
221
+ const STRING_LITERAL = /'(?<single>(?:[^'\\\n]|\\.)*)'|"(?<double>(?:[^"\\\n]|\\.)*)"|`(?<template>(?:[^`$\\]|\\.)*)`/gu;
222
222
  function collectLiterals(text) {
223
223
  const found = /* @__PURE__ */ new Set();
224
224
  for (const match of text.matchAll(STRING_LITERAL)) {
@@ -233,12 +233,12 @@ function collectLiterals(text) {
233
233
  * variable makes the reference set incomplete, so the feature is reported at
234
234
  * warn level instead of error (a false positive would be worse than a miss).
235
235
  */
236
- const FIXTURE_VERB_ARG = /(?:\.(?:seed|request|fixture)|\btoMatch)\s*\(\s*(?<arg>[^,)]*)/g;
236
+ const FIXTURE_VERB_ARG = /(?:\.(?:seed|request|fixture)|\btoMatch)\s*\(\s*(?<arg>[^,)]*)/gu;
237
237
  function hasNonLiteralFixtureArg(text) {
238
238
  for (const match of text.matchAll(FIXTURE_VERB_ARG)) {
239
239
  const arg = (match.groups?.arg ?? "").trim();
240
240
  if (arg.length === 0) continue;
241
- if (!(/^'(?:[^'\\\n]|\\.)*'$/.test(arg) || /^"(?:[^"\\\n]|\\.)*"$/.test(arg) || /^`(?:[^`$\\]|\\.)*`$/.test(arg))) return true;
241
+ if (!(/^'(?:[^'\\\n]|\\.)*'$/u.test(arg) || /^"(?:[^"\\\n]|\\.)*"$/u.test(arg) || /^`(?:[^`$\\]|\\.)*`$/u.test(arg))) return true;
242
242
  }
243
243
  return false;
244
244
  }
@@ -252,18 +252,18 @@ function suppressedLines(text, id) {
252
252
  const suppressed = /* @__PURE__ */ new Set();
253
253
  const lines = text.split("\n");
254
254
  const applies = (raw) => {
255
- const ids = raw.replace(/--.*$/, "").trim().split(/[\s,]+/);
255
+ const ids = raw.replace(/--.*$/u, "").trim().split(/[\s,]+/u);
256
256
  return ids.includes(id) || ids.includes("*");
257
257
  };
258
258
  for (const [index, line] of lines.entries()) {
259
- let match = /checker-disable-next-line\s+(?<ids>.*)$/i.exec(line);
259
+ let match = /checker-disable-next-line\s+(?<ids>.*)$/iu.exec(line);
260
260
  if (match !== null && applies(match.groups?.ids ?? "")) {
261
- for (let next = index + 1; next < lines.length; next += 1) if (lines[next].trim().length > 0) {
261
+ for (let next = index + 1; next < lines.length; next += 1) if ((lines[next] ?? "").trim().length > 0) {
262
262
  suppressed.add(next + 1);
263
263
  break;
264
264
  }
265
265
  }
266
- match = /checker-disable-line\s+(?<ids>.*)$/i.exec(line);
266
+ match = /checker-disable-line\s+(?<ids>.*)$/iu.exec(line);
267
267
  if (match !== null && applies(match.groups?.ids ?? "")) suppressed.add(index + 1);
268
268
  }
269
269
  return suppressed;
@@ -314,21 +314,21 @@ function splitTopLevel(body) {
314
314
  /** The destructured export names of a `= await specification.<ctor>(` binding. */
315
315
  function runnerExportsOf(text) {
316
316
  const names = /* @__PURE__ */ new Set();
317
- const decl = new RegExp(String.raw`\{(?<names>[^}]*)\}\s*=\s*await\s+specification\.(?:${CONSTRUCTORS})\s*\(`, "g");
317
+ const decl = new RegExp(String.raw`\{(?<names>[^}]*)\}\s*=\s*await\s+specification\.(?:${CONSTRUCTORS})\s*\(`, "gu");
318
318
  for (const match of text.matchAll(decl)) for (const raw of (match.groups?.names ?? "").split(",")) {
319
- const key = raw.split(":")[0].trim();
319
+ const key = (raw.split(":")[0] ?? "").trim();
320
320
  if (key.length > 0 && !NON_RUNNER_BINDINGS.has(key)) names.add(key);
321
321
  }
322
322
  return names;
323
323
  }
324
324
  /** The leading callee identifier of an expression (`postgres()` → `postgres`). */
325
325
  function leadingCallee(value) {
326
- const match = /^(?<callee>[A-Za-z_$][\w$]*)\s*\(/.exec(value.trim());
326
+ const match = /^(?<callee>[A-Za-z_$][\w$]*)\s*\(/u.exec(value.trim());
327
327
  return match === null ? match : match.groups?.callee ?? null;
328
328
  }
329
329
  /** SQL factory count of a `services:` record, or null if non-literal/absent. */
330
330
  function sqlDatabaseCount(text) {
331
- const keyword = /\bservices\s*:/.exec(text);
331
+ const keyword = /\bservices\s*:/u.exec(text);
332
332
  if (keyword === null) return null;
333
333
  const braceIndex = text.indexOf("{", keyword.index + keyword[0].length);
334
334
  if (braceIndex === -1 || text.slice(keyword.index + keyword[0].length, braceIndex).trim() !== "") return null;
@@ -348,9 +348,9 @@ function sqlDatabaseCount(text) {
348
348
  function modelSpecFile(file) {
349
349
  const text = readSource(file);
350
350
  let dockerAware = false;
351
- for (const match of text.matchAll(/specification\.cli\s*\(/g)) {
351
+ for (const match of text.matchAll(/specification\.cli\s*\(/gu)) {
352
352
  const argsOpen = text.indexOf("(", match.index);
353
- if (argsOpen !== -1 && /\bdocker\s*:/.test(balancedParens(text, argsOpen))) dockerAware = true;
353
+ if (argsOpen !== -1 && /\bdocker\s*:/u.test(balancedParens(text, argsOpen))) dockerAware = true;
354
354
  }
355
355
  return {
356
356
  dockerAware,
@@ -362,17 +362,18 @@ function modelSpecFile(file) {
362
362
  /** Resolve a relative import specifier to a `.ts` path (else undefined). */
363
363
  function resolveRelativeImport(fromFile, specifier) {
364
364
  if (!specifier.startsWith(".")) return;
365
- return resolve(dirname(fromFile), specifier).replace(/\.js$/, ".ts");
365
+ return resolve(dirname(fromFile), specifier).replace(/\.js$/u, ".ts");
366
366
  }
367
367
  /** Every `import { a, b as c } from '…'` binding in a file. */
368
368
  function namedImports(text) {
369
369
  const bindings = [];
370
- for (const match of text.matchAll(/import\s+(?:type\s+)?\{(?<names>[^}]*)\}\s+from\s+['"](?<source>[^'"]+)['"]/g)) {
370
+ for (const match of text.matchAll(/import\s+(?:type\s+)?\{(?<names>[^}]*)\}\s+from\s+['"](?<source>[^'"]+)['"]/gu)) {
371
371
  const source = match.groups?.source ?? "";
372
372
  for (const raw of (match.groups?.names ?? "").split(",")) {
373
373
  const part = raw.trim();
374
374
  if (part.length === 0) continue;
375
- const [name, local] = part.split(/\s+as\s+/).map((token) => token.trim());
375
+ const [name, local] = part.split(/\s+as\s+/u).map((token) => token.trim());
376
+ if (name === void 0) continue;
376
377
  bindings.push({
377
378
  local: local ?? name,
378
379
  name,
@@ -405,7 +406,7 @@ function checkDockerRunnerAwaitUsing(rootDir) {
405
406
  }
406
407
  if (runners.size === 0) continue;
407
408
  const suppressed = suppressedLines(readText$1(testFile), "b5");
408
- for (const match of text.matchAll(/\b(?<kind>await\s+using|const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*await\s+(?<runner>[A-Za-z_$][\w$]*)\b[^;]*?\.exec\s*\(/g)) {
409
+ for (const match of text.matchAll(/\b(?<kind>await\s+using|const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*await\s+(?<runner>[A-Za-z_$][\w$]*)\b[^;]*?\.exec\s*\(/gu)) {
409
410
  const kind = match.groups?.kind ?? "";
410
411
  const runner = match.groups?.runner ?? "";
411
412
  if (kind.startsWith("await using") || !runners.has(runner)) continue;
@@ -425,18 +426,18 @@ function checkDockerRunnerAwaitUsing(rootDir) {
425
426
  /** Local inline runners a test declares itself (`specification.api(…)`). */
426
427
  function localRunnerBindings(text) {
427
428
  const locals = /* @__PURE__ */ new Set();
428
- const decl = new RegExp(String.raw`\{(?<names>[^}]*)\}\s*=\s*await\s+specification\.(?:${CONSTRUCTORS})\s*\(`, "g");
429
+ const decl = new RegExp(String.raw`\{(?<names>[^}]*)\}\s*=\s*await\s+specification\.(?:${CONSTRUCTORS})\s*\(`, "gu");
429
430
  for (const match of text.matchAll(decl)) for (const raw of (match.groups?.names ?? "").split(",")) {
430
431
  const part = raw.trim();
431
- const local = (part.includes(":") ? part.split(":")[1] : part).trim();
432
- if (local.length > 0 && !NON_RUNNER_BINDINGS.has(part.split(":")[0].trim())) locals.add(local);
432
+ const local = (part.includes(":") ? part.split(":")[1] ?? "" : part).trim();
433
+ if (local.length > 0 && !NON_RUNNER_BINDINGS.has((part.split(":")[0] ?? "").trim())) locals.add(local);
433
434
  }
434
435
  return locals;
435
436
  }
436
437
  /** The identifier immediately before a `.seed(`/`.table(` member, if any. */
437
438
  function memberObjectIdentifier(text, dotIndex) {
438
439
  const before = text.slice(0, dotIndex);
439
- const match = /(?<name>[A-Za-z_$][\w$]*)\s*$/.exec(before);
440
+ const match = /(?<name>[A-Za-z_$][\w$]*)\s*$/u.exec(before);
440
441
  return match === null ? match : match.groups?.name ?? null;
441
442
  }
442
443
  function checkDatabaseProperty(rootDir) {
@@ -455,17 +456,18 @@ function checkDatabaseProperty(rootDir) {
455
456
  const model = target === void 0 ? void 0 : enforced.get(target);
456
457
  if (model?.runnerExports.has(binding.name) === true) applicable.add(model);
457
458
  }
458
- if (applicable.size !== 1) continue;
459
- const count = [...applicable][0].sqlDatabaseCount ?? 0;
459
+ const [onlyApplicable] = applicable;
460
+ if (applicable.size !== 1 || onlyApplicable === void 0) continue;
461
+ const count = onlyApplicable.sqlDatabaseCount ?? 0;
460
462
  const locals = localRunnerBindings(text);
461
463
  const suppressed = suppressedLines(readText$1(testFile), "a7");
462
464
  const rel = relative(rootDir, testFile);
463
- for (const match of text.matchAll(/\.(?<verb>seed|table)\s*\(/g)) {
465
+ for (const match of text.matchAll(/\.(?<verb>seed|table)\s*\(/gu)) {
464
466
  const verb = match.groups?.verb ?? "";
465
467
  const object = memberObjectIdentifier(text, match.index);
466
468
  if (object !== null && locals.has(object)) continue;
467
469
  const args = balancedParens(text, text.indexOf("(", match.index));
468
- const hasDatabase = /\bdatabase\s*:/.test(args);
470
+ const hasDatabase = /\bdatabase\s*:/u.test(args);
469
471
  const line = lineAt(text, match.index);
470
472
  if (suppressed.has(line)) continue;
471
473
  if (count >= 2 && !hasDatabase) violations.push({
@@ -584,7 +586,7 @@ function checkPoolFixtures(rootDir) {
584
586
  return violations;
585
587
  }
586
588
  /** A `.fixture('…')` call with a plain string argument. */
587
- const FIXTURE_CALL = /\.fixture\(\s*(?<quote>['"`])(?<path>(?:[^'"`\\]|\\.)*)\k<quote>/g;
589
+ const FIXTURE_CALL = /\.fixture\(\s*(?<quote>['"`])(?<path>(?:[^'"`\\]|\\.)*)\k<quote>/gu;
588
590
  /**
589
591
  * Every fixture path a spec file names, with the line it names it on. A
590
592
  * `<case>.spec.yaml` is read through the runner's own parser; a `.ts` file
@@ -620,7 +622,7 @@ function listReferrers(specsRoot, pool) {
620
622
  /** The pool entry a `$FIXTURES/…` path names — its FIRST segment. */
621
623
  function poolEntryOf(path) {
622
624
  if (!path.startsWith("$FIXTURES/")) return;
623
- const entry = path.slice(10).split("/")[0];
625
+ const entry = path.slice(10).split("/")[0] ?? "";
624
626
  return entry.length > 0 ? entry : void 0;
625
627
  }
626
628
  /** Pool entry → the spec directories that reference it, each with its referrers. */
@@ -647,8 +649,9 @@ function misplacedPoolFixtures(rootDir) {
647
649
  for (const pool of findPools(rootDir)) {
648
650
  const usage = poolUsage(dirname(pool), pool);
649
651
  for (const [entry, leaves] of usage) {
650
- if (leaves.size !== 1 || !isDir(join(pool, entry))) continue;
651
- const [leaf, referrers] = [...leaves][0];
652
+ const [only] = leaves;
653
+ if (leaves.size !== 1 || only === void 0 || !isDir(join(pool, entry))) continue;
654
+ const [leaf, referrers] = only;
652
655
  misplaced.push({
653
656
  entry,
654
657
  leaf,
@@ -869,7 +872,7 @@ function checkBlockScalars(file, lineOf) {
869
872
  }
870
873
  return findings;
871
874
  }
872
- const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
875
+ const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
873
876
  /** Words the `.spec.yaml` suffix already says — a case name repeating one says nothing. */
874
877
  const REDUNDANT_WORDS = /* @__PURE__ */ new Set([
875
878
  "cli",
@@ -898,7 +901,7 @@ function checkFileName(rel) {
898
901
  }
899
902
  const MAX_DESCRIPTION = 100;
900
903
  /** A first word that is an identifier, not prose — exempt from the lowercase rule. */
901
- const SYMBOL_WORD = /^[A-Z0-9_]+$/;
904
+ const SYMBOL_WORD = /^[A-Z0-9_]+$/u;
902
905
  /**
903
906
  * The description IS the vitest test title, and it is held to the title rules:
904
907
  * one lowercase line of prose, no trailing period, short enough to read in a
@@ -915,26 +918,26 @@ function checkDescription(document) {
915
918
  }
916
919
  if (text.length > MAX_DESCRIPTION) findings.push(at(`description: is ${text.length} characters — keep it under ${MAX_DESCRIPTION}`));
917
920
  if (text.endsWith(".")) findings.push(at("description: ends with a period — a title is a fragment, not a sentence"));
918
- const first = text.split(/\s+/)[0] ?? "";
919
- if (/^[A-Z]/.test(text) && !SYMBOL_WORD.test(first)) findings.push(at("description: starts with a capital — a title is lowercase prose"));
921
+ const first = text.split(/\s+/u)[0] ?? "";
922
+ if (/^[A-Z]/u.test(text) && !SYMBOL_WORD.test(first)) findings.push(at("description: starts with a capital — a title is lowercase prose"));
920
923
  return findings;
921
924
  }
922
925
  /** The literals the update writer must have failed to tokenise, and their token. */
923
926
  const VOLATILE = [
924
927
  {
925
- pattern: /\b(?:127\.0\.0\.1|localhost|0\.0\.0\.0):\d{2,5}\b/,
928
+ pattern: /\b(?:127\.0\.0\.1|localhost|0\.0\.0\.0):\d{2,5}\b/u,
926
929
  token: "{{url}} (or {{port}})"
927
930
  },
928
931
  {
929
- pattern: /(?:^|[\s"'(=])(?:\/private)?\/tmp\//,
932
+ pattern: /(?:^|[\s"'(=])(?:\/private)?\/tmp\//u,
930
933
  token: "{{workdir}}"
931
934
  },
932
935
  {
933
- pattern: /(?:^|[\s"'(=])\/var\/folders\//,
936
+ pattern: /(?:^|[\s"'(=])\/var\/folders\//u,
934
937
  token: "{{workdir}}"
935
938
  },
936
939
  {
937
- pattern: /(?:^|[\s"'(=])\/(?:Users|home)\/[^\s"'/]+\//,
940
+ pattern: /(?:^|[\s"'(=])\/(?:Users|home)\/[^\s"'/]+\//u,
938
941
  token: "{{workdir}} or {{path}}"
939
942
  }
940
943
  ];
@@ -955,8 +958,8 @@ function checkVolatileLiterals(document) {
955
958
  }
956
959
  return findings;
957
960
  }
958
- const ISO8601 = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
959
- const UUID = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;
961
+ const ISO8601 = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/u;
962
+ const UUID = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/u;
960
963
  /** Everything the document itself FIXED: what it feeds in, and what its fixtures hold. */
961
964
  function fixedData(document, dir) {
962
965
  const parts = document.runs.map((run) => run.stdin ?? "");
@@ -1028,7 +1031,7 @@ function objectKeys(source, from) {
1028
1031
  let depth = 0;
1029
1032
  let quote = "";
1030
1033
  for (let index = from; index < source.length; index += 1) {
1031
- const char = source[index];
1034
+ const char = source[index] ?? "";
1032
1035
  if (quote !== "") {
1033
1036
  if (char === quote && source[index - 1] !== "\\") quote = "";
1034
1037
  continue;
@@ -1049,10 +1052,11 @@ function objectKeys(source, from) {
1049
1052
  };
1050
1053
  continue;
1051
1054
  }
1052
- if (depth === 1 && /[A-Za-z_$'"]/.test(char)) {
1053
- const rest = /^(?<quote>['"]?)(?<name>[A-Za-z_$][\w$]*)\k<quote>\s*:/.exec(source.slice(index));
1054
- if (rest?.groups) {
1055
- keys.push(rest.groups.name);
1055
+ if (depth === 1 && /[A-Za-z_$'"]/u.test(char)) {
1056
+ const rest = /^(?<quote>['"]?)(?<name>[A-Za-z_$][\w$]*)\k<quote>\s*:/u.exec(source.slice(index));
1057
+ const name = rest?.groups?.name;
1058
+ if (rest !== null && name !== void 0) {
1059
+ keys.push(name);
1056
1060
  index += rest[0].length - 1;
1057
1061
  }
1058
1062
  }
@@ -1083,12 +1087,12 @@ function registeredNames(dir) {
1083
1087
  const serve = /* @__PURE__ */ new Set();
1084
1088
  for (const entry of entries) {
1085
1089
  const text = readText(join(current, entry));
1086
- for (const call of text.matchAll(/specification\.cli\s*\(/g)) {
1090
+ for (const call of text.matchAll(/specification\.cli\s*\(/gu)) {
1087
1091
  const open = text.indexOf("{", call.index + call[0].length);
1088
1092
  if (open === -1) continue;
1089
1093
  const options = objectKeys(text, open);
1090
1094
  for (const [key, into] of [["env", env], ["serve", serve]]) {
1091
- const marker = new RegExp(String.raw`\b${key}\s*:\s*\{`).exec(text.slice(open, options.end));
1095
+ const marker = new RegExp(String.raw`\b${key}\s*:\s*\{`, "u").exec(text.slice(open, options.end));
1092
1096
  if (marker === null) continue;
1093
1097
  const at = open + marker.index + marker[0].length - 1;
1094
1098
  for (const name of objectKeys(text, at).keys) into.add(name);
@@ -1134,7 +1138,7 @@ function checkSpecConventions(text, rel, path) {
1134
1138
  }
1135
1139
  const lineAt = (node) => {
1136
1140
  const range = "range" in node && Array.isArray(node.range) ? node.range : void 0;
1137
- const key = node.key;
1141
+ const { key } = node;
1138
1142
  const target = range ?? (isScalar(key) && Array.isArray(key.range) ? key.range : void 0);
1139
1143
  return target === void 0 ? 1 : file.source.lineAt(target[0]);
1140
1144
  };
@@ -1204,7 +1208,7 @@ function fixSpecDocument(text, rel) {
1204
1208
  const reorder = (map, order) => {
1205
1209
  const sorted = [...pairsOf(map).filter((pair) => order.includes(keyName(pair) ?? ""))].sort((a, b) => order.indexOf(keyName(a)) - order.indexOf(keyName(b)));
1206
1210
  let cursor = 0;
1207
- map.items = pairsOf(map).map((pair) => order.includes(keyName(pair) ?? "") ? sorted[cursor++] : pair);
1211
+ map.items = pairsOf(map).map((pair) => (order.includes(keyName(pair) ?? "") ? sorted[cursor++] : pair) ?? pair);
1208
1212
  };
1209
1213
  const map = documentMap(file);
1210
1214
  if (map === null) return null;
@@ -1252,11 +1256,11 @@ function fixSpecFiles(rootDir) {
1252
1256
  * It shares TOKEN_KINDS with the runtime matcher so the channels cannot drift.
1253
1257
  */
1254
1258
  /** A well-formed token: `{{word}}` / `{{word#ref}}`. */
1255
- const VALID_TOKEN = /^[A-Za-z][A-Za-z0-9]*(?:#[\w.-]+)?$/;
1259
+ const VALID_TOKEN = /^[A-Za-z][A-Za-z0-9]*(?:#[\w.-]+)?$/u;
1256
1260
  /** Any `{{ … }}` block (no nested braces) — classified by the scanner below. */
1257
- const BRACE_BLOCK = /\{\{(?<inner>[^{}]*)\}\}/g;
1261
+ const BRACE_BLOCK = /\{\{(?<inner>[^{}]*)\}\}/gu;
1258
1262
  /** The leading identifier of a brace block, for malformed-ref classification. */
1259
- const LEADING_WORD = /^(?<kind>[A-Za-z][A-Za-z0-9]*)/;
1263
+ const LEADING_WORD = /^(?<kind>[A-Za-z][A-Za-z0-9]*)/u;
1260
1264
  const KNOWN = new Set(TOKEN_KINDS);
1261
1265
  /** Directories whose files carry the token grammar (D4). */
1262
1266
  const FIXTURE_DIRS = /* @__PURE__ */ new Set([GROUND_EXPECTED, GROUND_REQUESTS]);
@@ -1283,7 +1287,7 @@ function findUnknownTokens(text) {
1283
1287
  for (const [index, lineText] of lines.entries()) for (const match of lineText.matchAll(BRACE_BLOCK)) {
1284
1288
  const inner = match.groups?.inner ?? "";
1285
1289
  if (VALID_TOKEN.test(inner)) {
1286
- if (!KNOWN.has(inner.split("#")[0])) violations.push({
1290
+ if (!KNOWN.has(inner.split("#")[0] ?? "")) violations.push({
1287
1291
  line: index + 1,
1288
1292
  token: match[0]
1289
1293
  });
@@ -1303,7 +1307,7 @@ function findKnownTokens(text) {
1303
1307
  const lines = text.split("\n");
1304
1308
  for (const [index, lineText] of lines.entries()) for (const match of lineText.matchAll(BRACE_BLOCK)) {
1305
1309
  const inner = match.groups?.inner ?? "";
1306
- if (VALID_TOKEN.test(inner) && KNOWN.has(inner.split("#")[0])) found.push({
1310
+ if (VALID_TOKEN.test(inner) && KNOWN.has(inner.split("#")[0] ?? "")) found.push({
1307
1311
  line: index + 1,
1308
1312
  token: match[0]
1309
1313
  });
@@ -1325,8 +1329,8 @@ function firstLine(text) {
1325
1329
  for (const line of text.split("\n")) if (line.trim().length > 0) return line.trim();
1326
1330
  return "";
1327
1331
  }
1328
- const REQUEST_LINE = /^(?<method>GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS) \/\S*/;
1329
- const STATUS_LINE = /^HTTP\/\d(?:\.\d)? \d{3}\b/;
1332
+ const REQUEST_LINE = /^(?<method>GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS) \/\S*/u;
1333
+ const STATUS_LINE = /^HTTP\/\d(?:\.\d)? \d{3}\b/u;
1330
1334
  /**
1331
1335
  * Check one `<case>.spec.yaml` — D4b's shape through the SAME parser the runner
1332
1336
  * uses (one grammar, no drift), then D4's token vocabulary over the STREAMS
@@ -1386,10 +1390,7 @@ function checkConventionFiles(rootDir) {
1386
1390
  }
1387
1391
  if (entry.name.endsWith(".spec.yaml")) {
1388
1392
  const text = decodeText(path);
1389
- if (text !== null) {
1390
- violations.push(...checkSpecFile(text, rel));
1391
- violations.push(...checkSpecConventions(text, rel, path));
1392
- }
1393
+ if (text !== null) violations.push(...checkSpecFile(text, rel), ...checkSpecConventions(text, rel, path));
1393
1394
  continue;
1394
1395
  }
1395
1396
  if (inside === null) continue;