@ultimat3/core 19.0.0 → 19.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/core",
3
- "version": "19.0.0",
3
+ "version": "19.1.0",
4
4
  "description": "Ultimate's foundation: errors, context, env, config, clock, ids, logging, telemetry, lifecycle",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,6 +37,6 @@
37
37
  "test": "bun test"
38
38
  },
39
39
  "dependencies": {
40
- "@ultimat3/schema": "19.0.0"
40
+ "@ultimat3/schema": "19.1.0"
41
41
  }
42
42
  }
package/src/index.ts CHANGED
@@ -550,6 +550,7 @@ export { safeUrl, URL_ATTRIBUTES } from './safe-url';
550
550
  export { defineService, resetServices, type ServiceFactory } from './service';
551
551
  export type { FlightJoin, Scheduler, SingleFlight, SingleFlightOptions } from './single-flight';
552
552
  export { createSingleFlight } from './single-flight';
553
+ export { endOfLiteral, maskLiterals, QUOTES, stripComments } from './source-mask';
553
554
  export { timingSafeEqual } from './timing-safe-equal';
554
555
  export {
555
556
  frameworkVersion,
@@ -0,0 +1,118 @@
1
+ // Reading TypeScript source without a parser: comments — and optionally string contents — replaced
2
+ // by spaces, newlines kept so line numbers survive. Moved here from `@ultimat3/cli`'s `ts-scan.ts`
3
+ // on 2026-09-05 because `@ultimat3/i18n`'s key extractor (tier 1) needed exactly this and could not
4
+ // import a tier-5 package: a `// … t('…')` in a comment was a phantom key `x i18n sync` would have
5
+ // written as `"…": "⟦…⟧"`. One masking implementation, at the tier every scanner can reach.
6
+
7
+ // `ReadonlySet`, so a consumer cannot mutate what every scan reads.
8
+ export const QUOTES: ReadonlySet<string> = new Set(["'", '"', '`']);
9
+ const WORD = /[\w$]/;
10
+
11
+ /** After one of these words a `/` opens a regex; after any other identifier it divides. */
12
+ const REGEX_AFTER_WORDS = new Set(
13
+ 'await case delete do else in instanceof new of return throw typeof void yield'.split(' '),
14
+ );
15
+
16
+ /**
17
+ * Index just past the closing quote of the literal opening at `from`, or `from + 1` when a `'`/`"`
18
+ * does not close on its own line — which makes it text, not a literal. Only a template literal may
19
+ * span a newline, so the apostrophe in `<p>Don't panic</p>` is JSX text; read as an opener it ran
20
+ * forward to the next `'` in the FILE (the next `fix:` line) and blanked every declaration between,
21
+ * silently emptying the `errors` gate for the whole file. Same rule `endOfRegex` applies to a `/`.
22
+ * An escaped newline is still a continuation: the escape is consumed before the line test.
23
+ */
24
+ export function endOfLiteral(text: string, from: number): number {
25
+ const quote = text[from] as string;
26
+ const spansLines = quote === '`';
27
+ for (let i = from + 1; i < text.length; i += 1) {
28
+ if (text[i] === '\\') i += 1;
29
+ else if (text[i] === quote) return i + 1;
30
+ else if (!spansLines && text[i] === '\n') return from + 1;
31
+ }
32
+ return spansLines ? text.length : from + 1;
33
+ }
34
+
35
+ /**
36
+ * Whether the `/` at `at` opens a regex rather than divides — the call no scanner without a parser
37
+ * avoids. A regex cannot follow what ends an expression: an identifier that is not one of the words
38
+ * above, a number, `)`, `]`, a string's closing quote. Every other position is an operator's and
39
+ * opens one; `</` and `/>` are JSX delimiters. Read from the masked prefix, so a comment is space.
40
+ */
41
+ function opensRegex(out: readonly string[], at: number): boolean {
42
+ if (out[at + 1] === '>') return false;
43
+ let i = at - 1;
44
+ while (i >= 0 && /\s/.test(out[i] as string)) i -= 1;
45
+ if (i < 0) return true;
46
+ const ch = out[i] as string;
47
+ if (ch === '<' || ch === ')' || ch === ']' || QUOTES.has(ch)) return false;
48
+ if (!WORD.test(ch)) return true;
49
+ let start = i;
50
+ while (start >= 0 && WORD.test(out[start] as string)) start -= 1;
51
+ return REGEX_AFTER_WORDS.has(out.slice(start + 1, i + 1).join(''));
52
+ }
53
+
54
+ /**
55
+ * Index just past the closing `/` of the regex opening at `from`, or `from + 1` when it does not
56
+ * close on its own line — a literal may not span one, so an unterminated candidate was a division
57
+ * or a JSX delimiter after all. A `/` inside a `[…]` class does not close the literal.
58
+ */
59
+ function endOfRegex(text: string, from: number): number {
60
+ let inClass = false;
61
+ let escaped = false;
62
+ for (let i = from + 1; i < text.length; i += 1) {
63
+ const ch = text[i];
64
+ if (ch === '\n') break;
65
+ if (escaped) escaped = false;
66
+ else if (ch === '\\') escaped = true;
67
+ else if (inClass) inClass = ch !== ']';
68
+ else if (ch === '[') inClass = true;
69
+ else if (ch === '/') return i + 1;
70
+ }
71
+ return from + 1;
72
+ }
73
+
74
+ /**
75
+ * Comments — and optionally string contents — replaced by spaces, newlines kept so line numbers
76
+ * survive and quote delimiters kept so the caller can still find where a literal starts and ends.
77
+ * A regex body is masked the same way: `/(['"`])/` holds three quotes that delimit nothing, and
78
+ * reading one as an opening quote desyncs every literal after it.
79
+ */
80
+ function blankRegions(text: string, strings: boolean): string {
81
+ // `split('')` and NOT `[...text]`: the spread yields one element per CODE POINT while every
82
+ // index below runs over UTF-16 units (`text.length`, `text[i]`). One astral character — an emoji
83
+ // in a fixture, `piñata 🎉` — and `out` is shorter than `text`, so every write past it lands a
84
+ // slot early and the returned mask no longer aligns with the input. Measured: 22 files in this
85
+ // tree desynced, shipped source included, and eight rules read this mask.
86
+ const out = text.split('');
87
+ const blank = (from: number, to: number): void => {
88
+ for (let n = from; n < to; n += 1) if (out[n] !== '\n') out[n] = ' ';
89
+ };
90
+ let i = 0;
91
+ while (i < text.length) {
92
+ const ch = text[i] as string;
93
+ if (ch === '/' && (text[i + 1] === '/' || text[i + 1] === '*')) {
94
+ const line = text[i + 1] === '/';
95
+ const end = line ? text.indexOf('\n', i) : text.indexOf('*/', i + 2);
96
+ const stop = end === -1 ? text.length : line ? end : end + 2;
97
+ blank(i, stop);
98
+ i = stop;
99
+ continue;
100
+ }
101
+ // Not code: a regex body or a literal. `end === i + 1` blanks nothing and steps one char.
102
+ const end =
103
+ ch === '/' && opensRegex(out, i)
104
+ ? endOfRegex(text, i)
105
+ : QUOTES.has(ch)
106
+ ? endOfLiteral(text, i)
107
+ : i + 1;
108
+ if (strings) blank(i + 1, end - 1);
109
+ i = end;
110
+ }
111
+ return out.join('');
112
+ }
113
+
114
+ /** Comments gone, string literals intact — what a scan for declared codes or `t()` keys reads. */
115
+ export const stripComments = (text: string): string => blankRegions(text, false);
116
+
117
+ /** Comments and string contents gone, delimiters kept — what a scan for code structure reads. */
118
+ export const maskLiterals = (text: string): string => blankRegions(text, true);