@descent-vtt/spec-guard 0.28.0 → 0.29.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/CHANGELOG.md CHANGED
@@ -10,6 +10,49 @@ what to do when upgrading; the family's policy, including how a flag or field
10
10
  is deprecated before it goes, is spec-core's
11
11
  [ADR-0009](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0009-versions-before-1-0.md).
12
12
 
13
+ ## 0.29.0
14
+
15
+ A regex rule is read in one dialect by both engines, the one ripgrep's
16
+ regex crate documents: `\w`, `\b`, `\d` and `\s` are Unicode's, a
17
+ character is a code point, and what the two dialects read apart is an
18
+ invalid directive that names it, so a rule no longer passes under
19
+ `--engine rg` where the scanner counts it.
20
+
21
+ ### Changed
22
+
23
+ - **A regex rule is read in one dialect by both engines, the one ripgrep's
24
+ regex crate documents**, where the scanner read JavaScript's and ripgrep,
25
+ which names the files the scanner reads, its own: a file the scanner
26
+ counted in and ripgrep left out passed the rule under `--engine rg`, and
27
+ under `auto` past its budget. On 0.28.0 `foo\b` over `fooé`, `\Wbar` over
28
+ `ébar`, `a\sb` with U+FEFF between, `^..$` over an emoji and `baz` with
29
+ `regex="true"` and `word="true"` over `ébaz` were each counted by
30
+ `--engine js` and passed by `--engine rg`, as were 441 of 2,500 rules
31
+ made at random. `\w`, `\d`, `\s`, their negations, `\b` and `\B` are the
32
+ crate's Unicode classes, a character is a code point, `ignore-case` folds
33
+ by Unicode's simple case folding, and `word="true"` puts one boundary
34
+ round a literal and a regex: no word character before or after. What the
35
+ two dialects read apart is an invalid directive that names it. ripgrep
36
+ reads a file's bytes as they are, where it took a UTF-8 byte order mark
37
+ off a file's start, and an empty match after a file's last line end is
38
+ not counted, as ripgrep counts none there
39
+ ([ADR-0007](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0007-search-scope.md)).
40
+ Upgrading: a regex rule over text that is not ASCII may count otherwise,
41
+ and fail or pass where it did not: `\w`, `\b` and `word="true"` take a
42
+ letter, digit or mark of any script for a word character, `\d` takes
43
+ `٣`, `\s` takes NEL and not U+FEFF, `.` and a negated class take an emoji
44
+ as one character, `ignore-case` folds `k` with the Kelvin sign, and
45
+ `\p{L}` and `\u{41}` are a class and a code point where they were
46
+ letters. A literal with `word="true"` no longer counts beside a mark,
47
+ ZWJ, `‿` or `Ⓐ`, and counts beside `²`. A regex rule with `\A`, `\z`,
48
+ `\<`, `\>`, `\b{...}`, `\a`, `\x{...}`, `\U`, `\pL`, a class inside a
49
+ class such as `[[:alpha:]]`, `&&`, `--` or `~~` in a class, `[]` or `[^]`
50
+ before a `]`, or a count with blanks in it is an invalid directive, exit
51
+ 1, and so is one with an escape JavaScript's `v` mode refuses, `\e`,
52
+ `\é`, an octal escape or a back-reference to no group; write it as both
53
+ dialects read it. A rule against a byte order mark at a file's start now
54
+ fails under ripgrep as under the scanner.
55
+
13
56
  ## 0.28.0
14
57
 
15
58
  A regex rule's `^` and `$` read a line with either engine, where they
package/README.md CHANGED
@@ -587,8 +587,8 @@ Passes when every listed path exists relative to `--root`. Directories count.
587
587
  | `min` / `max` | count (`max` on all but present) | Inclusive bounds |
588
588
  | `glob` | absence, count, structure | Include-only file filters, e.g. `*.ts,*.tsx` (ripgrep `-g` semantics) |
589
589
  | `exclude` | all but present | Paths to leave out, gitignore-style: `src/config/**`, `tests`, `*.test.ts` |
590
- | `regex` | absence, count | Treat `symbol` as a regular expression, JavaScript's, where `^` and `$` match at each line's start and end |
591
- | `word` | absence, count | Require word boundaries, so `Primary` does not match `PrimaryButton` |
590
+ | `regex` | absence, count | Treat `symbol` as a regular expression, written as JavaScript's and read as ripgrep reads it, where `^` and `$` match at each line's start and end (below) |
591
+ | `word` | absence, count | Require word boundaries, so `Primary` does not match `PrimaryButton`: no word character, ripgrep's `\w`, just before the match or just after it |
592
592
  | `ignore-case` | absence, count | Case-insensitive matching |
593
593
  | `comments` | absence, count | `ignore` (default) or `include` for matches inside comments |
594
594
  | `module` | import-absence, import-count | Which dependency, matched like `exclude` |
@@ -611,12 +611,31 @@ than no spec tool.
611
611
  A `regex` is read against each file's whole text, so a match may cross a
612
612
  line end. `^` and `$` match at the start and the end of every line, a line
613
613
  ending at LF, at CRLF or at a lone CR, as ripgrep reads one with `--crlf`;
614
- U+2028 and U+2029 end none, and no line follows a file's last line end.
614
+ U+2028 and U+2029 end none, and no line follows a file's last line end,
615
+ so an empty match there is not counted.
615
616
  `symbol="^\s*-?\s*uses:"` meets `uses:` on any line, with either engine
616
617
  ([ADR-0007](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0007-search-scope.md)).
617
618
  `\s` matches a line end too, so `\s+$` also meets a line followed by an
618
619
  empty one; `[ \t]+$` reads only the blanks a line ends with.
619
620
 
621
+ Both engines read a `regex` in one dialect: JavaScript's syntax, with the
622
+ meaning ripgrep's regex crate documents wherever the two dialects give the
623
+ same text two, since a file ripgrep's reading leaves out is one the scanner
624
+ never counts in. A character is a code point, and `ignore-case` folds by
625
+ Unicode's simple case folding. `\w` is a letter, a mark, a decimal digit, a
626
+ connector such as `_`, or Join_Control, in any script, and `\b`, `\B` and
627
+ `word="true"` are read with it; `\d` is a decimal digit of any script; `\s`
628
+ is Unicode's white space, so U+FEFF is none and NEL is one. What only
629
+ JavaScript reads, a look-around or a back-reference, is read by the scanner
630
+ alone, as ripgrep refuses it. What the crate reads and JavaScript would
631
+ read otherwise is an invalid directive that names it: `\A`, `\z`, `\<`,
632
+ `\>`, `\b{...}`, `\a`, `\x{...}`, `\U`, `\pL`, a class inside a class such
633
+ as `[[:alpha:]]`, `&&`, `--` or `~~` in a class, and a count with blanks
634
+ in it, `{2, 3}`. An escaped sign such as `\-` or `\#`, a lone `]` or `}`,
635
+ and `[\w-.]` read as they did
636
+ ([ADR-0007](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0007-search-scope.md),
637
+ amended 2026-10-24).
638
+
620
639
  ### `exclude` - everywhere except
621
640
 
622
641
  Most real rules are "nowhere except one place", not "not here":
@@ -1700,7 +1719,7 @@ comment on the offending line:
1700
1719
  steps:
1701
1720
  # ...checkout, setup-node and npm ci as above
1702
1721
  - run: npx --no-install @descent-vtt/spec-guard "docs/**/*.md" --format sarif > spec-guard.sarif || true
1703
- - uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
1722
+ - uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
1704
1723
  with:
1705
1724
  sarif_file: spec-guard.sarif
1706
1725
  category: spec-guard
@@ -1885,7 +1904,14 @@ Two honest caveats:
1885
1904
  *which files contain this text*, and the scanner does all the counting,
1886
1905
  classification and reporting for both. `--engine` now changes how long a run
1887
1906
  takes and nothing about its verdict, and the suite asserts that on trees built
1888
- from every case that used to split them.
1907
+ from every case that used to split them. A regex rule kept two readings
1908
+ until its dialect was one, and ripgrep took a byte order mark off a file's
1909
+ start: `foo\b` over `fooé` was counted by the scanner and passed under
1910
+ ripgrep. What still splits them is a file that is not UTF-8, which the
1911
+ scanner reads with U+FFFD for each byte it cannot, and a character newer
1912
+ than one of the two engines' Unicode
1913
+ ([ADR-0007](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0007-search-scope.md),
1914
+ amended 2026-10-24).
1889
1915
 
1890
1916
  ## Programmatic API
1891
1917
 
@@ -2026,8 +2052,8 @@ build fails if they stop being true. CI proves every one of those rules can
2026
2052
  fail, and `spec-guard cites` holds every ADR a comment in the code cites to
2027
2053
  existing and being in force.
2028
2054
 
2029
- Line coverage is 100%, CI's full mutation sweep kills 99.23% of 14,224 mutants
2030
- (0.28.0), and every survivor a release adds is replayed before it ships
2055
+ Line coverage is 100%, CI's full mutation sweep kills 99.27% of 14,580 mutants
2056
+ (0.29.0), and every survivor a release adds is replayed before it ships
2031
2057
  ([ADR-0003](https://github.com/DescentVTT/spec-guard/blob/main/docs/adr/0003-mutation-testing.md));
2032
2058
  [CONTRIBUTING.md](https://github.com/DescentVTT/spec-guard/blob/main/CONTRIBUTING.md)
2033
2059
  has how to build, test and release, and
@@ -0,0 +1,51 @@
1
+ /**
2
+ * One reading of a regex rule, for both engines. ADR-0007, amended
3
+ * 2026-10-24.
4
+ *
5
+ * ripgrep only names the files the scanner reads, so a file its reading of a
6
+ * pattern leaves out is a file whose matches are never counted, and a rule
7
+ * they would fail passes. ripgrep reads a pattern as the regex crate's
8
+ * documentation says, and JavaScript gave some of the same text another
9
+ * meaning: its `\w`, `\b` and `\d` were ASCII and its `\s` held U+FEFF and not
10
+ * NEL, where the crate's are Unicode's; a character was a UTF-16 unit; and
11
+ * `\A`, `\<` or `[[:alpha:]]` were letters and brackets. So the scanner is
12
+ * given each rule as the crate reads it, in JavaScript's `v` mode, which reads
13
+ * a character as a code point, folds case by Unicode's simple case folding as
14
+ * the crate does, and holds a class inside a class:
15
+ *
16
+ * - `\w`, `\d`, `\s`, their negations, `\b` and `\B` are the crate's Unicode
17
+ * ones, and so is the boundary `word="true"` puts round a rule;
18
+ * - `^` and `$` meet each line, as ripgrep reads one with `--crlf`;
19
+ * - what the crate reads and JavaScript read otherwise is refused, naming it,
20
+ * since one engine would count what the other never named;
21
+ * - what JavaScript read without `v` as itself is kept as itself: an escaped
22
+ * sign, a lone `]`, a `{` or `}` that is no count, and a `-` beside a class
23
+ * escape in a class. The crate reads each the same, but a `{` and that `-`,
24
+ * which it refuses, and which the scanner then answers alone;
25
+ * - what only JavaScript reads, a look-around or a back-reference, is the
26
+ * scanner's alone, as before: ripgrep refuses the pattern, and the scanner
27
+ * answers the search.
28
+ */
29
+ /**
30
+ * The regex crate's `\w`: `\p{Alphabetic}`, `\p{M}`, `\d`, `\p{Pc}` and
31
+ * `\p{Join_Control}`, as its documentation lists them (regex 1.13.1, read
32
+ * 2026-10-24). JavaScript's is `[A-Za-z0-9_]`, and a literal's word boundary
33
+ * here was `\p{L}`, `\p{N}` and `_`, which holds no mark and no ZWJ.
34
+ */
35
+ export declare const WORD_CHARACTER: string;
36
+ /**
37
+ * A regex rule's source as the scanner reads it, in JavaScript's `v` mode:
38
+ * the reading ripgrep has of it, where the two dialects meet.
39
+ *
40
+ * Throws a SyntaxError, worded as JavaScript words its own, for a rule the
41
+ * two engines would read differently.
42
+ */
43
+ export declare function readRegex(source: string): string;
44
+ /**
45
+ * The regular expression the scanner matches a regex rule with: the rule as
46
+ * `readRegex` reads it, between word boundaries for `word="true"`, which
47
+ * ripgrep's `--word-regexp` puts round a pattern as `\b{start-half}` and
48
+ * `\b{end-half}`: no word character before the match and none after it.
49
+ */
50
+ export declare function buildRegexRule(symbol: string, flags: string, word: boolean): RegExp;
51
+ //# sourceMappingURL=dialect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAgE,CAAC;AA6K5F;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA6ChD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAUnF"}
@@ -0,0 +1,265 @@
1
+ /**
2
+ * One reading of a regex rule, for both engines. ADR-0007, amended
3
+ * 2026-10-24.
4
+ *
5
+ * ripgrep only names the files the scanner reads, so a file its reading of a
6
+ * pattern leaves out is a file whose matches are never counted, and a rule
7
+ * they would fail passes. ripgrep reads a pattern as the regex crate's
8
+ * documentation says, and JavaScript gave some of the same text another
9
+ * meaning: its `\w`, `\b` and `\d` were ASCII and its `\s` held U+FEFF and not
10
+ * NEL, where the crate's are Unicode's; a character was a UTF-16 unit; and
11
+ * `\A`, `\<` or `[[:alpha:]]` were letters and brackets. So the scanner is
12
+ * given each rule as the crate reads it, in JavaScript's `v` mode, which reads
13
+ * a character as a code point, folds case by Unicode's simple case folding as
14
+ * the crate does, and holds a class inside a class:
15
+ *
16
+ * - `\w`, `\d`, `\s`, their negations, `\b` and `\B` are the crate's Unicode
17
+ * ones, and so is the boundary `word="true"` puts round a rule;
18
+ * - `^` and `$` meet each line, as ripgrep reads one with `--crlf`;
19
+ * - what the crate reads and JavaScript read otherwise is refused, naming it,
20
+ * since one engine would count what the other never named;
21
+ * - what JavaScript read without `v` as itself is kept as itself: an escaped
22
+ * sign, a lone `]`, a `{` or `}` that is no count, and a `-` beside a class
23
+ * escape in a class. The crate reads each the same, but a `{` and that `-`,
24
+ * which it refuses, and which the scanner then answers alone;
25
+ * - what only JavaScript reads, a look-around or a back-reference, is the
26
+ * scanner's alone, as before: ripgrep refuses the pattern, and the scanner
27
+ * answers the search.
28
+ */
29
+ /**
30
+ * The regex crate's `\w`: `\p{Alphabetic}`, `\p{M}`, `\d`, `\p{Pc}` and
31
+ * `\p{Join_Control}`, as its documentation lists them (regex 1.13.1, read
32
+ * 2026-10-24). JavaScript's is `[A-Za-z0-9_]`, and a literal's word boundary
33
+ * here was `\p{L}`, `\p{N}` and `_`, which holds no mark and no ZWJ.
34
+ */
35
+ export const WORD_CHARACTER = String.raw `[\p{Alphabetic}\p{M}\p{Nd}\p{Pc}\p{Join_Control}]`;
36
+ const NOT_WORD_CHARACTER = `[^${WORD_CHARACTER.slice(1)}`;
37
+ /** The crate's `\d`, `\s`, `\w` and their negations, each of which stands in a class as well as outside one. */
38
+ const CLASS_ESCAPES = {
39
+ d: String.raw `\p{Nd}`,
40
+ D: String.raw `\P{Nd}`,
41
+ s: String.raw `\p{White_Space}`,
42
+ S: String.raw `\P{White_Space}`,
43
+ w: WORD_CHARACTER,
44
+ W: NOT_WORD_CHARACTER,
45
+ };
46
+ /**
47
+ * A word character on one side and none on the other, the start and the end
48
+ * of the text standing for none, as the crate's `\b` reads.
49
+ */
50
+ const BOUNDARY = `(?:(?<=${WORD_CHARACTER})(?!${WORD_CHARACTER})|(?<!${WORD_CHARACTER})(?=${WORD_CHARACTER}))`;
51
+ const NOT_BOUNDARY = `(?:(?<=${WORD_CHARACTER})(?=${WORD_CHARACTER})|(?<!${WORD_CHARACTER})(?!${WORD_CHARACTER}))`;
52
+ /**
53
+ * U+2028 and U+2029, at which JavaScript's `m` ends a line and ripgrep
54
+ * does not. Made from their numbers, so that no separator stands in this file
55
+ * where an editor would show a line break.
56
+ */
57
+ const SEPARATORS = String.fromCharCode(0x2028, 0x2029);
58
+ /**
59
+ * `^` and `$` as ripgrep reads them with `--crlf`: at the start and at the
60
+ * end of each line, a line ending at LF, at CRLF or at a lone CR, with no line
61
+ * after the last line end. JavaScript's `m` reads three lines more, and each
62
+ * is taken back out: one between the CR and the LF of a CRLF, one after the
63
+ * last line end (and so in an empty file), and a line end at each separator.
64
+ * With `m` alone `\s+$` matched on every line of a CRLF file and on the
65
+ * last of every file, `^` met each CRLF line twice, and a line begun at a
66
+ * separator was counted in a file ripgrep never named. ADR-0007, amended
67
+ * 2026-10-19.
68
+ */
69
+ const LINE_START = `(?:^(?<![${SEPARATORS}])(?!(?<=\\r)\\n)(?=[\\s\\S]))`;
70
+ const LINE_END = `(?:$(?![${SEPARATORS}])(?!(?<=\\r)\\n)(?!(?<![^\\n\\r])(?![\\s\\S])))`;
71
+ /** What the crate reads in an escape that JavaScript read as the letter or the sign itself. */
72
+ const RIPGREP_ESCAPES = {
73
+ A: 'the start of the text',
74
+ z: 'the end of the text',
75
+ '<': 'the start of a word',
76
+ '>': 'the end of a word',
77
+ a: 'a bell, U+0007',
78
+ U: 'a code point in eight hex digits',
79
+ };
80
+ /** A count JavaScript reads after an atom: `{n}`, `{n,}` or `{n,m}`. */
81
+ const COUNT = /\{\d+(?:,\d*)?\}/y;
82
+ /**
83
+ * A count with blanks in it, `{2, 3}`, which ripgrep 15.0.0 reads as a count
84
+ * (measured; its documentation does not say) and JavaScript as characters.
85
+ */
86
+ const BLANK_COUNT = /\{\s*\d+\s*(?:,\s*\d*\s*)?\}/y;
87
+ /**
88
+ * An escape that holds a name or a number between brackets: `\p{...}`,
89
+ * `\P{...}`, `\u{...}` and `\k<...>`. Read as a match of its own, so that no
90
+ * reading of a rule steps back. One left open is read a character at a time,
91
+ * and `v` mode refuses it in the words it has for it read whole.
92
+ */
93
+ const NAMED = /\\[pPu]\{[^}]*\}|\\k<[^>]*>/y;
94
+ /** A group's name, which may hold a `$`; one left open is read as `NAMED` reads one. */
95
+ const GROUP_NAME = /\(\?<(?![=!])[^>]*>/y;
96
+ function refuse(source, reason) {
97
+ throw new SyntaxError(`Invalid regular expression: /${source}/: ${reason}`);
98
+ }
99
+ /**
100
+ * ASCII that is neither a letter nor a digit, which the crate reads as itself
101
+ * after a backslash, `<` and `>` aside, and which `v` mode reads as itself
102
+ * written as a hex escape, in a class and outside one.
103
+ */
104
+ function isAsciiSign(char) {
105
+ return char.charCodeAt(0) < 0x80 && !/[0-9A-Za-z]/.test(char);
106
+ }
107
+ function hex(char) {
108
+ return `\\x${char.charCodeAt(0).toString(16).padStart(2, '0')}`;
109
+ }
110
+ /** The escape that starts at `at`, which is a backslash. */
111
+ function escapeAt(source, at, inClass) {
112
+ const next = source.charAt(at + 1);
113
+ const known = CLASS_ESCAPES[next];
114
+ if (known !== undefined)
115
+ return { text: known, length: 2, set: true };
116
+ const ripgrep = RIPGREP_ESCAPES[next];
117
+ // In a class the crate refuses a `\<`, a `\A` and a `\z`, and the scanner
118
+ // answers as JavaScript reads it, which `v` mode accepts or refuses itself.
119
+ if (ripgrep !== undefined && (!inClass || next === 'a' || next === 'U')) {
120
+ refuse(source, `"\\${next}" is ${ripgrep} to ripgrep and "${next}" to JavaScript`);
121
+ }
122
+ const after = source.charAt(at + 2);
123
+ if (next === 'b' || next === 'B') {
124
+ if (inClass)
125
+ return { text: next === 'b' ? '\\x08' : '\\B', length: 2 };
126
+ if (next === 'b' && after === '{')
127
+ refuse(source, '"\\b{" is a word boundary of the kind its braces name to ripgrep, and a word boundary before a "{" to JavaScript');
128
+ return { text: next === 'b' ? BOUNDARY : NOT_BOUNDARY, length: 2 };
129
+ }
130
+ if ((next === 'p' || next === 'P') && after !== '{') {
131
+ refuse(source, `"\\${next}${after}" is a class of characters to ripgrep and "${next}${after}" to JavaScript`);
132
+ }
133
+ if (next === 'x' && after === '{')
134
+ refuse(source, '"\\x{" is the code point its braces hold to ripgrep, and an "x" and braces to JavaScript');
135
+ // No sign between the brackets is the class's or the rule's own. `v` mode
136
+ // refuses a `\k<...>` in a class, read whole or not.
137
+ NAMED.lastIndex = at;
138
+ const named = NAMED.exec(source);
139
+ if (named !== null)
140
+ return { text: named[0], length: named[0].length, set: next !== 'u' };
141
+ if (isAsciiSign(next))
142
+ return { text: hex(next), length: 2 };
143
+ // A letter, a digit, any other character, or nothing: `v` mode reads it as
144
+ // JavaScript reads it, or refuses it, as it refuses a trailing backslash.
145
+ // Two on from a trailing backslash is past the end, where every reading
146
+ // here stops.
147
+ return { text: source.slice(at, at + 2), length: 2 };
148
+ }
149
+ /** The class that starts at `at`, which is a `[`, and whether it is `[]` or `[^]`, which the crate does not close where JavaScript does. */
150
+ function classAt(source, at) {
151
+ let i = at + 1;
152
+ const negated = source.charAt(i) === '^';
153
+ if (negated)
154
+ i += 1;
155
+ if (source.charAt(i) === ']')
156
+ return { text: source.slice(at, i + 1), length: i + 1 - at, empty: true };
157
+ const atoms = [];
158
+ while (i < source.length && source.charAt(i) !== ']') {
159
+ const pair = source.slice(i, i + 2);
160
+ if (pair === '&&' || pair === '--' || pair === '~~') {
161
+ refuse(source, `"${pair}" in a class is an operation on two classes to ripgrep and two characters to JavaScript`);
162
+ }
163
+ const char = source.charAt(i);
164
+ if (char === '[')
165
+ refuse(source, '"[" in a class is a class inside it to ripgrep, as in [[:alpha:]], and a "[" to JavaScript');
166
+ if (char === '\\') {
167
+ const escape = escapeAt(source, i, true);
168
+ atoms.push({ text: escape.text, set: escape.set, dash: false });
169
+ i += escape.length;
170
+ continue;
171
+ }
172
+ const point = String.fromCodePoint(source.codePointAt(i));
173
+ atoms.push({ text: isAsciiSign(point) ? hex(point) : point, dash: point === '-' });
174
+ i += point.length;
175
+ }
176
+ // A `-` between two characters is a range to both. Beside a class escape,
177
+ // or after a range, JavaScript read it as itself. Two in a row are refused
178
+ // above, so neither end of a range is a `-` that could start another.
179
+ let body = '';
180
+ for (let k = 0; k < atoms.length; k += 1) {
181
+ const atom = atoms[k];
182
+ const end = atoms[k + 2];
183
+ const ranged = !atom.set && atoms[k + 1]?.dash === true && end !== undefined && !end.set;
184
+ body += ranged ? `${atom.text}-${end.text}` : atom.text;
185
+ k += ranged ? 2 : 0;
186
+ }
187
+ // The class's `]`, or nothing where the rule ends first: an unterminated
188
+ // class stays unterminated, for `v` mode to refuse.
189
+ const closed = source.charAt(i);
190
+ return { text: `[${negated ? '^' : ''}${body}${closed}`, length: i + closed.length - at, empty: false };
191
+ }
192
+ /**
193
+ * A regex rule's source as the scanner reads it, in JavaScript's `v` mode:
194
+ * the reading ripgrep has of it, where the two dialects meet.
195
+ *
196
+ * Throws a SyntaxError, worded as JavaScript words its own, for a rule the
197
+ * two engines would read differently.
198
+ */
199
+ export function readRegex(source) {
200
+ let read = '';
201
+ let at = 0;
202
+ let empty = '';
203
+ // `<=` for the `<` reads one place past the end, where `charAt` gives
204
+ // nothing and nothing is added: an equivalent mutant.
205
+ while (at < source.length) {
206
+ const char = source.charAt(at);
207
+ if (char === '\\') {
208
+ const escape = escapeAt(source, at, false);
209
+ read += escape.text;
210
+ at += escape.length;
211
+ continue;
212
+ }
213
+ if (char === '[') {
214
+ const found = classAt(source, at);
215
+ read += found.text;
216
+ if (found.empty)
217
+ empty = found.text;
218
+ at += found.length;
219
+ continue;
220
+ }
221
+ GROUP_NAME.lastIndex = at;
222
+ const group = GROUP_NAME.exec(source);
223
+ if (group !== null) {
224
+ read += group[0];
225
+ at += group[0].length;
226
+ continue;
227
+ }
228
+ if (char === '{') {
229
+ COUNT.lastIndex = at;
230
+ const count = COUNT.exec(source);
231
+ BLANK_COUNT.lastIndex = at;
232
+ const blank = count === null ? BLANK_COUNT.exec(source) : null;
233
+ if (blank !== null)
234
+ refuse(source, `"${blank[0]}" is a count to ripgrep, which reads one with blanks in it, and those characters to JavaScript`);
235
+ read += count === null ? '\\{' : count[0];
236
+ at += count === null ? 1 : count[0].length;
237
+ continue;
238
+ }
239
+ if (char === ']' && empty !== '') {
240
+ refuse(source, `"${empty}" before a "]" is a class holding the "]" to ripgrep, and ${empty === '[]' ? 'no character' : 'any character'} to JavaScript`);
241
+ }
242
+ read += char === '^' ? LINE_START : char === '$' ? LINE_END : char === ']' || char === '}' ? `\\${char}` : char;
243
+ at += 1;
244
+ }
245
+ return read;
246
+ }
247
+ /**
248
+ * The regular expression the scanner matches a regex rule with: the rule as
249
+ * `readRegex` reads it, between word boundaries for `word="true"`, which
250
+ * ripgrep's `--word-regexp` puts round a pattern as `\b{start-half}` and
251
+ * `\b{end-half}`: no word character before the match and none after it.
252
+ */
253
+ export function buildRegexRule(symbol, flags, word) {
254
+ const read = readRegex(symbol);
255
+ const source = word ? `(?<!${WORD_CHARACTER})(?:${read})(?!${WORD_CHARACTER})` : read;
256
+ try {
257
+ return new RegExp(source, `${flags}mv`);
258
+ }
259
+ catch (error) {
260
+ // V8 names the source it was given, which is the reading, not the rule.
261
+ const message = error.message;
262
+ refuse(symbol, message.slice(message.lastIndexOf(': ') + 2));
263
+ }
264
+ }
265
+ //# sourceMappingURL=dialect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect.js","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"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"}
package/dist/engine.d.ts CHANGED
@@ -251,7 +251,12 @@ export declare function parseRipgrepFiles(stdout: string, root: string, excludeF
251
251
  * principle that an unexplained line from a subprocess is still information.
252
252
  */
253
253
  export declare function parseRipgrepErrors(stderr: string): string[];
254
- /** Builds the matching RegExp shared by the JS engine. */
254
+ /**
255
+ * Builds the matching RegExp shared by the JS engine. A regex rule is read
256
+ * as ripgrep reads it (`dialect.ts`), and a literal's word boundary is the
257
+ * one ripgrep's `--word-regexp` reads: no word character, the regex crate's
258
+ * `\w`, on either side. ADR-0007, amended 2026-10-24.
259
+ */
255
260
  export declare function buildJsRegExp(symbol: string, options: SearchOptions): RegExp;
256
261
  interface CandidateFile {
257
262
  absolutePath: string;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -21,6 +21,7 @@
21
21
  import { spawn } from 'node:child_process';
22
22
  import path from 'node:path';
23
23
  import { createCommentMask } from './comments.js';
24
+ import { buildRegexRule, WORD_CHARACTER } from './dialect.js';
24
25
  import { createExcludeMatcher, createGlobMatcher, normalizeExclude, normalizeGlob, ripgrepGlobs, toPosix, walkFiles, } from './glob.js';
25
26
  import { nodeIo } from './io.js';
26
27
  import { NO_MEMO } from './memo.js';
@@ -325,6 +326,15 @@ export function buildRipgrepArgs(request, patterns = [request.symbol]) {
325
326
  // into memory only for a pattern that can match a line
326
327
  // end. ADR-0007, amended 2026-10-19.
327
328
  '--multiline',
329
+ // --encoding=none a file's bytes as they are, as the scanner reads
330
+ // them. By default ripgrep takes a UTF-8 byte order mark
331
+ // off the start of a file, so a rule against the mark,
332
+ // and the regex crate's `\S` at a file's start, were
333
+ // counted by the scanner in a file ripgrep never named;
334
+ // and it read a UTF-16 file as the text it holds, which
335
+ // the scanner does not.
336
+ // ADR-0007, amended 2026-10-24.
337
+ '--encoding=none',
328
338
  ];
329
339
  if (!options.regex)
330
340
  args.push('--fixed-strings');
@@ -649,58 +659,17 @@ export function parseRipgrepErrors(stderr) {
649
659
  }
650
660
  /* --------------------------------------------------------------- javascript */
651
661
  /**
652
- * U+2028 and U+2029, at which JavaScript's `m` ends a line and ripgrep
653
- * does not. Made from their numbers, so that no separator stands in this file
654
- * where an editor would show a line break.
662
+ * Builds the matching RegExp shared by the JS engine. A regex rule is read
663
+ * as ripgrep reads it (`dialect.ts`), and a literal's word boundary is the
664
+ * one ripgrep's `--word-regexp` reads: no word character, the regex crate's
665
+ * `\w`, on either side. ADR-0007, amended 2026-10-24.
655
666
  */
656
- const SEPARATORS = String.fromCharCode(0x2028, 0x2029);
657
- /**
658
- * `^` and `$` as ripgrep reads them with `--crlf`: at the start and at the
659
- * end of each line, a line ending at LF, at CRLF or at a lone CR, with no line
660
- * after the last line end. JavaScript's `m` reads three lines more, and each
661
- * is taken back out: one between the CR and the LF of a CRLF, one after the
662
- * last line end (and so in an empty file), and a line end at each separator.
663
- * With `m` alone `\s+$` matched on every line of a CRLF file and on the
664
- * last of every file, `^` met each CRLF line twice, and a line begun at a
665
- * separator was counted in a file ripgrep never named. ADR-0007, amended
666
- * 2026-10-19.
667
- */
668
- const LINE_START = `(?:^(?<![${SEPARATORS}])(?!(?<=\\r)\\n)(?=[\\s\\S]))`;
669
- const LINE_END = `(?:$(?![${SEPARATORS}])(?!(?<=\\r)\\n)(?!(?<![^\\n\\r])(?![\\s\\S])))`;
670
- /**
671
- * What in a pattern holds a `^` or a `$` that is not an anchor: an escape, a
672
- * named group's name and a reference to one (a name may hold a `$`), and a
673
- * character class, which in JavaScript ends at its first unescaped `]`. A
674
- * regex rule is read without the `u` flag, where nothing else does.
675
- *
676
- * Outside a class, an escape taken as a backslash before anything but white
677
- * space (`\\[\S\S]`) reads every pattern the same: a backslash before
678
- * white space escapes no `^`, `$`, `[`, `(` or backslash, so left untaken it
679
- * leaves each token where it was. That mutant is equivalent; inside a class it
680
- * is not, and `[\ $]` holds it.
681
- */
682
- const NOT_ANCHORS = /\\k<[^>]*>|\\[\s\S]|\(\?<(?![=!])[^>]*>|\[(?:\\[\s\S]|[^\\\]])*\]|[$^]/g;
683
- /** A pattern's source with each `^` and `$` read as the start and the end of a line. */
684
- function lineAnchors(source) {
685
- let read = '';
686
- let at = 0;
687
- for (const piece of source.matchAll(NOT_ANCHORS)) {
688
- const [token] = piece;
689
- read += source.slice(at, piece.index) + (token === '^' ? LINE_START : token === '$' ? LINE_END : token);
690
- at = piece.index + token.length;
691
- }
692
- return read + source.slice(at);
693
- }
694
- /** Builds the matching RegExp shared by the JS engine. */
695
667
  export function buildJsRegExp(symbol, options) {
696
668
  const flags = `g${options.ignoreCase ? 'i' : ''}`;
697
- if (options.regex) {
698
- const anchored = lineAnchors(symbol);
699
- const source = options.word ? `(?<![A-Za-z0-9_])(?:${anchored})(?![A-Za-z0-9_])` : anchored;
700
- return new RegExp(source, `${flags}m`);
701
- }
669
+ if (options.regex)
670
+ return buildRegexRule(symbol, flags, options.word);
702
671
  const escaped = escapeRegExp(symbol);
703
- const source = options.word ? `(?<![\\p{L}\\p{N}_])(?:${escaped})(?![\\p{L}\\p{N}_])` : escaped;
672
+ const source = options.word ? `(?<!${WORD_CHARACTER})(?:${escaped})(?!${WORD_CHARACTER})` : escaped;
704
673
  return new RegExp(source, `${flags}u`);
705
674
  }
706
675
  /**
@@ -978,12 +947,35 @@ getMask) {
978
947
  // module exists to remove.
979
948
  let starts = null;
980
949
  const lineOf = (index) => locate((starts ??= lineStarts(content)), index);
950
+ // Past an empty match by one character. A rule's regular expression has
951
+ // `u` or `v`, where V8 moves an index left between the two units of a
952
+ // character outside the Basic Multilingual Plane back to its first, so
953
+ // that one unit on met the same empty match again, for ever. A step of one
954
+ // unit always is that hang, which no assertion can see before a clock
955
+ // does: a sweep counts that mutant a timeout.
956
+ const past = (index) => index + (content.codePointAt(index) > 0xffff ? 2 : 1);
957
+ // No line follows a file's last line end, and an empty file holds none, so
958
+ // an empty match there is on no line, and ripgrep, which reads lines,
959
+ // names no file for it. ADR-0007, amended 2026-10-24.
960
+ const lineless = content.length === 0 || content.endsWith('\n') ? content.length : -1;
961
+ // At a character's second unit, between the two of one outside the Basic
962
+ // Multilingual Plane, where no character starts. V8 tries the place all the
963
+ // same where a match fails at the character before it, and a look-behind
964
+ // over a class holding such characters meets an empty match there, the
965
+ // boundaries of `\b` and of `word="true"` among them; ripgrep, which reads
966
+ // code points, meets none. Text read from a file holds no second unit
967
+ // without its first: UTF-8 that would encode one is read as U+FFFD.
968
+ const splits = (index) => (content.charCodeAt(index) & 0xfc00) === 0xdc00;
981
969
  let match;
982
- while ((match = regexp.exec(content)) !== null) {
970
+ while ((match = regexp.exec(content)) !== null && match.index !== lineless) {
971
+ if (splits(match.index)) {
972
+ regexp.lastIndex = match.index + 1;
973
+ continue;
974
+ }
983
975
  if (getMask?.().isComment(match.index)) {
984
976
  commentCount += 1;
985
977
  if (match[0].length === 0)
986
- regexp.lastIndex += 1;
978
+ regexp.lastIndex = past(regexp.lastIndex);
987
979
  continue;
988
980
  }
989
981
  count += 1;
@@ -1004,7 +996,7 @@ getMask) {
1004
996
  });
1005
997
  }
1006
998
  if (match[0].length === 0)
1007
- regexp.lastIndex += 1;
999
+ regexp.lastIndex = past(regexp.lastIndex);
1008
1000
  }
1009
1001
  // No sort: exec scans forward, so lines are discovered in increasing order and
1010
1002
  // a Map preserves insertion order. Sorting here was dead code - mutation