@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 +43 -0
- package/README.md +33 -7
- package/dist/dialect.d.ts +51 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +265 -0
- package/dist/dialect.js.map +1 -0
- package/dist/engine.d.ts +6 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +43 -51
- package/dist/engine.js.map +1 -1
- package/dist/polyglot.d.ts.map +1 -1
- package/dist/polyglot.js +14 -11
- package/dist/polyglot.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +8 -3
- package/dist/runner.js.map +1 -1
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +7 -0
- package/dist/structure.js.map +1 -1
- package/package.json +1 -1
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@
|
|
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.
|
|
2030
|
-
(0.
|
|
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"}
|
package/dist/dialect.js
ADDED
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAA,mDAAmD,CAAC;AAE5F,MAAM,kBAAkB,GAAG,KAAK,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAE1D,gHAAgH;AAChH,MAAM,aAAa,GAAqC;IACtD,CAAC,EAAE,MAAM,CAAC,GAAG,CAAA,QAAQ;IACrB,CAAC,EAAE,MAAM,CAAC,GAAG,CAAA,QAAQ;IACrB,CAAC,EAAE,MAAM,CAAC,GAAG,CAAA,iBAAiB;IAC9B,CAAC,EAAE,MAAM,CAAC,GAAG,CAAA,iBAAiB;IAC9B,CAAC,EAAE,cAAc;IACjB,CAAC,EAAE,kBAAkB;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,QAAQ,GAAG,UAAU,cAAc,OAAO,cAAc,SAAS,cAAc,OAAO,cAAc,IAAI,CAAC;AAC/G,MAAM,YAAY,GAAG,UAAU,cAAc,OAAO,cAAc,SAAS,cAAc,OAAO,cAAc,IAAI,CAAC;AAEnH;;;;GAIG;AACH,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAG,YAAY,UAAU,gCAAgC,CAAC;AAC1E,MAAM,QAAQ,GAAG,WAAW,UAAU,kDAAkD,CAAC;AAEzF,+FAA+F;AAC/F,MAAM,eAAe,GAAqC;IACxD,CAAC,EAAE,uBAAuB;IAC1B,CAAC,EAAE,qBAAqB;IACxB,GAAG,EAAE,qBAAqB;IAC1B,GAAG,EAAE,mBAAmB;IACxB,CAAC,EAAE,gBAAgB;IACnB,CAAC,EAAE,kCAAkC;CACtC,CAAC;AAEF,wEAAwE;AACxE,MAAM,KAAK,GAAG,mBAAmB,CAAC;AAElC;;;GAGG;AACH,MAAM,WAAW,GAAG,+BAA+B,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,KAAK,GAAG,8BAA8B,CAAC;AAE7C,wFAAwF;AACxF,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAE1C,SAAS,MAAM,CAAC,MAAc,EAAE,MAAc;IAC5C,MAAM,IAAI,WAAW,CAAC,gCAAgC,MAAM,MAAM,MAAM,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,GAAG,CAAC,IAAY;IACvB,OAAO,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAClE,CAAC;AAWD,4DAA4D;AAC5D,SAAS,QAAQ,CAAC,MAAc,EAAE,EAAU,EAAE,OAAgB;IAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACtE,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACtC,0EAA0E;IAC1E,4EAA4E;IAC5E,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,QAAQ,OAAO,oBAAoB,IAAI,iBAAiB,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACjC,IAAI,OAAO;YAAE,OAAO,EAAE,IAAI,EAAE,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACxE,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG;YAAE,MAAM,CAAC,MAAM,EAAE,kHAAkH,CAAC,CAAC;QACtK,OAAO,EAAE,IAAI,EAAE,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;QACpD,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,KAAK,8CAA8C,IAAI,GAAG,KAAK,iBAAiB,CAAC,CAAC;IAChH,CAAC;IACD,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG;QAAE,MAAM,CAAC,MAAM,EAAE,0FAA0F,CAAC,CAAC;IAC9I,0EAA0E;IAC1E,qDAAqD;IACrD,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;IACrB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC;IAC1F,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAC7D,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,cAAc;IACd,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACvD,CAAC;AAED,4IAA4I;AAC5I,SAAS,OAAO,CAAC,MAAc,EAAE,EAAU;IACzC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACf,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;IACzC,IAAI,OAAO;QAAE,CAAC,IAAI,CAAC,CAAC;IACpB,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACxG,MAAM,KAAK,GAA0D,EAAE,CAAC;IACxE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACpC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACpD,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,yFAAyF,CAAC,CAAC;QACpH,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,GAAG;YAAE,MAAM,CAAC,MAAM,EAAE,4FAA4F,CAAC,CAAC;QAC/H,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YACzC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAChE,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC;YACnB,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,KAAK,GAAG,EAAE,CAAC,CAAC;QACnF,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC;IACpB,CAAC;IACD,0EAA0E;IAC1E,2EAA2E;IAC3E,sEAAsE;IACtE,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QACvB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QACzF,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACxD,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,yEAAyE;IACzE,oDAAoD;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,GAAG,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1G,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,sEAAsE;IACtE,sDAAsD;IACtD,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;YAC3C,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;YACpB,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC;YACpB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAClC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;YACnB,IAAI,KAAK,CAAC,KAAK;gBAAE,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC;YACpC,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC;YACnB,SAAS;QACX,CAAC;QACD,UAAU,CAAC,SAAS,GAAG,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;YACjB,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACjC,WAAW,CAAC,SAAS,GAAG,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC/D,IAAI,KAAK,KAAK,IAAI;gBAAE,MAAM,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,gGAAgG,CAAC,CAAC;YACjJ,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1C,EAAE,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACjC,MAAM,CAAC,MAAM,EAAE,IAAI,KAAK,6DAA6D,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,gBAAgB,CAAC,CAAC;QAC1J,CAAC;QACD,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAChH,EAAE,IAAI,CAAC,CAAC;IACV,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,KAAa,EAAE,IAAa;IACzE,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,cAAc,OAAO,IAAI,OAAO,cAAc,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IACtF,IAAI,CAAC;QACH,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wEAAwE;QACxE,MAAM,OAAO,GAAI,KAAe,CAAC,OAAO,CAAC;QACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC"}
|
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
|
-
/**
|
|
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;
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
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
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
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
|
-
|
|
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 ? `(
|
|
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
|
|
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
|
|
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
|