@descent-vtt/spec-guard 0.1.0 → 0.3.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 +90 -0
- package/README.md +213 -27
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -4
- package/dist/cli.js.map +1 -1
- package/dist/comments.d.ts +78 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +241 -0
- package/dist/comments.js.map +1 -0
- package/dist/engine.d.ts +97 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +365 -100
- package/dist/engine.js.map +1 -1
- package/dist/glob.d.ts +34 -0
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +47 -2
- package/dist/glob.js.map +1 -1
- package/dist/imports.d.ts +94 -0
- package/dist/imports.d.ts.map +1 -0
- package/dist/imports.js +492 -0
- package/dist/imports.js.map +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +24 -2
- package/dist/parser.js.map +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +37 -2
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts +12 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +168 -12
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +34 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/comments.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment classification.
|
|
3
|
+
*
|
|
4
|
+
* A documented codebase defeats a plain text search in the most ironic way
|
|
5
|
+
* available: the comment recording that a symbol was deleted is itself a match
|
|
6
|
+
* for that symbol. `@assert-absence symbol="LegacyThing"` then fails on
|
|
7
|
+
*
|
|
8
|
+
* // LegacyThing was removed in ADR-398
|
|
9
|
+
*
|
|
10
|
+
* which is the sentence proving the assertion true. So matches are classified
|
|
11
|
+
* and, by default, those inside comments do not count.
|
|
12
|
+
*
|
|
13
|
+
* The scanner tracks strings as well as comments, and it has to: a `//` inside
|
|
14
|
+
* a string literal is not a comment, and misreading one is the dangerous
|
|
15
|
+
* direction. Consider
|
|
16
|
+
*
|
|
17
|
+
* const url = "http://example.com"; const x = LegacyThing;
|
|
18
|
+
*
|
|
19
|
+
* Treating `//` in that URL as a comment start would hide a real use of
|
|
20
|
+
* LegacyThing and turn a violation into a silent pass. Every uncertainty here
|
|
21
|
+
* therefore resolves the same way: **when in doubt, it is code**. A match
|
|
22
|
+
* wrongly kept is a visible failure someone can argue with; a match wrongly
|
|
23
|
+
* dropped is a lie.
|
|
24
|
+
*
|
|
25
|
+
* Languages it does not recognise get no comment ranges at all, so their
|
|
26
|
+
* matches all count - and the result says so rather than implying the file was
|
|
27
|
+
* understood.
|
|
28
|
+
*/
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
const QUOTES = [
|
|
31
|
+
{ open: '"', close: '"', escape: true },
|
|
32
|
+
{ open: "'", close: "'", escape: true },
|
|
33
|
+
];
|
|
34
|
+
const C_LIKE = {
|
|
35
|
+
name: 'c-like',
|
|
36
|
+
line: ['//'],
|
|
37
|
+
block: [['/*', '*/']],
|
|
38
|
+
nested: false,
|
|
39
|
+
strings: QUOTES,
|
|
40
|
+
};
|
|
41
|
+
const JS_LIKE = {
|
|
42
|
+
...C_LIKE,
|
|
43
|
+
name: 'javascript',
|
|
44
|
+
strings: [...QUOTES, { open: '`', close: '`', escape: true }],
|
|
45
|
+
};
|
|
46
|
+
const C_SHARP = {
|
|
47
|
+
...C_LIKE,
|
|
48
|
+
name: 'c#',
|
|
49
|
+
// Verbatim strings come first so @" wins over ".
|
|
50
|
+
strings: [{ open: '@"', close: '"', escape: false }, ...QUOTES],
|
|
51
|
+
};
|
|
52
|
+
const RUST = {
|
|
53
|
+
name: 'rust',
|
|
54
|
+
line: ['//'],
|
|
55
|
+
block: [['/*', '*/']],
|
|
56
|
+
// Rust block comments nest, so /* /* */ */ is one comment, not one and a half.
|
|
57
|
+
nested: true,
|
|
58
|
+
strings: [
|
|
59
|
+
{ open: 'r#"', close: '"#', escape: false },
|
|
60
|
+
{ open: 'r"', close: '"', escape: false },
|
|
61
|
+
...QUOTES,
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
const GO = {
|
|
65
|
+
...C_LIKE,
|
|
66
|
+
name: 'go',
|
|
67
|
+
strings: [...QUOTES, { open: '`', close: '`', escape: false }],
|
|
68
|
+
};
|
|
69
|
+
const HASH = {
|
|
70
|
+
name: 'hash',
|
|
71
|
+
line: ['#'],
|
|
72
|
+
block: [],
|
|
73
|
+
nested: false,
|
|
74
|
+
// Triple quotes first: a Python docstring is a string, not a comment, and
|
|
75
|
+
// treating it as code is the conservative reading.
|
|
76
|
+
strings: [
|
|
77
|
+
{ open: '"""', close: '"""', escape: true },
|
|
78
|
+
{ open: "'''", close: "'''", escape: true },
|
|
79
|
+
...QUOTES,
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
const SQL_LIKE = {
|
|
83
|
+
name: 'sql-like',
|
|
84
|
+
line: ['--'],
|
|
85
|
+
block: [['/*', '*/']],
|
|
86
|
+
nested: false,
|
|
87
|
+
strings: QUOTES,
|
|
88
|
+
};
|
|
89
|
+
const MARKUP = {
|
|
90
|
+
name: 'markup',
|
|
91
|
+
line: [],
|
|
92
|
+
block: [['<!--', '-->']],
|
|
93
|
+
nested: false,
|
|
94
|
+
strings: [],
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Formats with no comments at all.
|
|
98
|
+
*
|
|
99
|
+
* Worth naming rather than leaving unknown: "this file has no comments" is
|
|
100
|
+
* something we know about JSON, and saying "I could not read it" instead would
|
|
101
|
+
* attach a caveat to every match in a package.json. Dialects that do allow
|
|
102
|
+
* comments in practice - tsconfig.json and its `//` lines - simply have them
|
|
103
|
+
* counted as code, which is the direction that fails loudly.
|
|
104
|
+
*/
|
|
105
|
+
const NO_COMMENTS = {
|
|
106
|
+
name: 'none',
|
|
107
|
+
line: [],
|
|
108
|
+
block: [],
|
|
109
|
+
nested: false,
|
|
110
|
+
strings: [],
|
|
111
|
+
};
|
|
112
|
+
/** Extension to comment syntax. Anything absent is left unclassified. */
|
|
113
|
+
const BY_EXTENSION = new Map([
|
|
114
|
+
...['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts', '.tsx'].map((extension) => [extension, JS_LIKE]),
|
|
115
|
+
...['.c', '.h', '.cc', '.cpp', '.cxx', '.hpp', '.java', '.kt', '.kts', '.scala', '.swift', '.dart', '.php', '.m', '.mm', '.zig'].map((extension) => [extension, C_LIKE]),
|
|
116
|
+
...['.cs', '.csx'].map((extension) => [extension, C_SHARP]),
|
|
117
|
+
...['.rs'].map((extension) => [extension, RUST]),
|
|
118
|
+
...['.go'].map((extension) => [extension, GO]),
|
|
119
|
+
...['.py', '.pyi', '.rb', '.sh', '.bash', '.zsh', '.yaml', '.yml', '.toml', '.tf', '.pl', '.r'].map((extension) => [extension, HASH]),
|
|
120
|
+
...['.sql', '.lua', '.hs', '.elm'].map((extension) => [extension, SQL_LIKE]),
|
|
121
|
+
...['.html', '.htm', '.xml', '.svg', '.vue', '.svelte', '.md', '.markdown'].map((extension) => [extension, MARKUP]),
|
|
122
|
+
// .jsonc is named for the comments it allows, so it gets the C-style reader.
|
|
123
|
+
...['.jsonc'].map((extension) => [extension, C_LIKE]),
|
|
124
|
+
...['.json', '.txt', '.csv', '.tsv', '.lock', '.log'].map((extension) => [extension, NO_COMMENTS]),
|
|
125
|
+
]);
|
|
126
|
+
/** The comment syntax for a path, or null when the language is unknown. */
|
|
127
|
+
export function syntaxFor(filePath) {
|
|
128
|
+
return BY_EXTENSION.get(path.extname(filePath).toLowerCase()) ?? null;
|
|
129
|
+
}
|
|
130
|
+
/** Where the literal opened at `at` ends, or the end of the file. */
|
|
131
|
+
function endOfString(source, at, rule) {
|
|
132
|
+
let index = at + rule.open.length;
|
|
133
|
+
while (index < source.length) {
|
|
134
|
+
if (rule.escape && source[index] === '\\') {
|
|
135
|
+
index += 2;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (source.startsWith(rule.close, index))
|
|
139
|
+
return index + rule.close.length;
|
|
140
|
+
index += 1;
|
|
141
|
+
}
|
|
142
|
+
return source.length;
|
|
143
|
+
}
|
|
144
|
+
/** Where the block comment opened at `at` ends, or the end of the file. */
|
|
145
|
+
function endOfBlock(source, at, pair, nested) {
|
|
146
|
+
const [open, close] = pair;
|
|
147
|
+
let index = at + open.length;
|
|
148
|
+
let depth = 1;
|
|
149
|
+
while (index < source.length && depth > 0) {
|
|
150
|
+
if (nested && source.startsWith(open, index)) {
|
|
151
|
+
depth += 1;
|
|
152
|
+
index += open.length;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (source.startsWith(close, index)) {
|
|
156
|
+
depth -= 1;
|
|
157
|
+
index += close.length;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
index += 1;
|
|
161
|
+
}
|
|
162
|
+
return index;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Finds every comment in the source.
|
|
166
|
+
*
|
|
167
|
+
* One pass, tracking strings so that comment markers inside them are ignored.
|
|
168
|
+
* An unterminated comment or string runs to the end of the file, which matches
|
|
169
|
+
* how a compiler would read it.
|
|
170
|
+
*
|
|
171
|
+
* The scan is written so that it cannot stand still: whatever the branches
|
|
172
|
+
* below decide, the loop advances by at least one character. That guarantee is
|
|
173
|
+
* the difference between a wrong answer and a hang, and this loop appends as it
|
|
174
|
+
* goes - standing still here would not spin, it would eat memory until the
|
|
175
|
+
* process died, on somebody's file, in somebody's CI.
|
|
176
|
+
*/
|
|
177
|
+
export function commentRanges(source, syntax) {
|
|
178
|
+
const ranges = [];
|
|
179
|
+
let index = 0;
|
|
180
|
+
while (index < source.length) {
|
|
181
|
+
const start = index;
|
|
182
|
+
const startsWith = (token) => source.startsWith(token, start);
|
|
183
|
+
// First match wins, in this order: a literal hides comment markers inside
|
|
184
|
+
// it, and a line comment hides a block opener on the same line. Each lookup
|
|
185
|
+
// sits in the branch that needs it, so none of them runs speculatively.
|
|
186
|
+
const stringRule = syntax.strings.find((rule) => startsWith(rule.open));
|
|
187
|
+
if (stringRule) {
|
|
188
|
+
index = endOfString(source, start, stringRule);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const lineToken = syntax.line.find((token) => startsWith(token));
|
|
192
|
+
if (lineToken !== undefined) {
|
|
193
|
+
const newline = source.indexOf('\n', start);
|
|
194
|
+
index = newline === -1 ? source.length : newline;
|
|
195
|
+
ranges.push([start, index]);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const blockPair = syntax.block.find(([open]) => startsWith(open));
|
|
199
|
+
if (blockPair) {
|
|
200
|
+
index = endOfBlock(source, start, blockPair, syntax.nested);
|
|
201
|
+
ranges.push([start, index]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// The single advance point, and the only place termination depends on.
|
|
206
|
+
if (index <= start)
|
|
207
|
+
index = start + 1;
|
|
208
|
+
}
|
|
209
|
+
return ranges;
|
|
210
|
+
}
|
|
211
|
+
const UNCLASSIFIED = {
|
|
212
|
+
classified: false,
|
|
213
|
+
syntax: null,
|
|
214
|
+
isComment: () => false,
|
|
215
|
+
};
|
|
216
|
+
export function createCommentMask(source, filePath) {
|
|
217
|
+
const syntax = syntaxFor(filePath);
|
|
218
|
+
if (!syntax)
|
|
219
|
+
return UNCLASSIFIED;
|
|
220
|
+
const ranges = commentRanges(source, syntax);
|
|
221
|
+
return {
|
|
222
|
+
classified: true,
|
|
223
|
+
syntax: syntax.name,
|
|
224
|
+
isComment(offset) {
|
|
225
|
+
let low = 0;
|
|
226
|
+
let high = ranges.length - 1;
|
|
227
|
+
while (low <= high) {
|
|
228
|
+
const mid = (low + high) >> 1;
|
|
229
|
+
const [start, end] = ranges[mid];
|
|
230
|
+
if (offset < start)
|
|
231
|
+
high = mid - 1;
|
|
232
|
+
else if (offset >= end)
|
|
233
|
+
low = mid + 1;
|
|
234
|
+
else
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* flags are answered by a single pass. Measured on a 2,000 file / 5 MB tree,
|
|
15
15
|
* eight assertions cost ~280ms as eight ripgrep passes and ~90ms as one.
|
|
16
16
|
*/
|
|
17
|
+
import { type CommentMask } from './comments.js';
|
|
17
18
|
import type { EngineName, MatchLocation, SearchOptions, SearchResult } from './types.js';
|
|
18
19
|
/** Files larger than this are skipped by both engines, keeping them in sync. */
|
|
19
20
|
export declare const MAX_FILE_SIZE: number;
|
|
@@ -37,10 +38,27 @@ export interface Engine {
|
|
|
37
38
|
}
|
|
38
39
|
export type EnginePreference = 'auto' | 'ripgrep' | 'javascript';
|
|
39
40
|
export declare function escapeRegExp(value: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Converts ripgrep's byte column into a character column.
|
|
43
|
+
*
|
|
44
|
+
* ripgrep counts bytes; the scanner counts characters, and so does every editor
|
|
45
|
+
* that a reader will paste the location into. On an ASCII line the two agree,
|
|
46
|
+
* which is why the difference went unnoticed - it only appears once a line
|
|
47
|
+
* holds a non-ASCII character before the match, and then the two engines report
|
|
48
|
+
* different columns for the same match.
|
|
49
|
+
*/
|
|
50
|
+
export declare function byteColumnToCharacter(line: string, byteOffset: number): number;
|
|
51
|
+
/**
|
|
52
|
+
* Orders matches by path, then by line. ripgrep searches in parallel and emits
|
|
53
|
+
* files in no fixed order, so this is what makes snippet output stable.
|
|
54
|
+
*/
|
|
55
|
+
export declare function sortLocations(locations: MatchLocation[]): MatchLocation[];
|
|
40
56
|
/** Per-pattern accumulator used by both engines. */
|
|
41
57
|
interface Tally {
|
|
42
58
|
count: number;
|
|
43
59
|
locations: MatchLocation[];
|
|
60
|
+
/** Matches skipped because they were inside a comment. */
|
|
61
|
+
commentCount: number;
|
|
44
62
|
}
|
|
45
63
|
/**
|
|
46
64
|
* True when no two of these literals can ever match overlapping text, which is
|
|
@@ -59,13 +77,90 @@ export declare function resetRipgrepProbe(): void;
|
|
|
59
77
|
export declare function findRipgrep(): Promise<string | null>;
|
|
60
78
|
/** Builds the argv for one ripgrep pass over `patterns`. */
|
|
61
79
|
export declare function buildRipgrepArgs(request: SearchRequest, patterns?: readonly string[]): string[];
|
|
80
|
+
/**
|
|
81
|
+
* Whether a set of patterns may share one ripgrep pass.
|
|
82
|
+
*
|
|
83
|
+
* Kept separate from the engine so the decision can be asserted directly: from
|
|
84
|
+
* the outside a correct batch and a correct set of separate passes are
|
|
85
|
+
* indistinguishable by design, which makes this the only place the rule is
|
|
86
|
+
* observable.
|
|
87
|
+
*/
|
|
88
|
+
export declare function shouldBatchPatterns(patterns: readonly string[], options: SearchOptions): boolean;
|
|
89
|
+
export interface RipgrepSink {
|
|
90
|
+
/** Feeds one line of ripgrep --json output. */
|
|
91
|
+
line(text: string): void;
|
|
92
|
+
readonly tallies: Map<string, Tally>;
|
|
93
|
+
readonly failure: Error | null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parses ripgrep's newline-delimited JSON into per-pattern tallies.
|
|
97
|
+
*
|
|
98
|
+
* This is deliberately pure and separate from the subprocess: every interesting
|
|
99
|
+
* case here - a base64 path, a match with no submatches, an excluded file, an
|
|
100
|
+
* unattributable match - is trivial to exercise as data and nearly impossible
|
|
101
|
+
* to provoke from a real ripgrep on demand.
|
|
102
|
+
*/
|
|
103
|
+
export declare function createRipgrepSink(request: SearchRequest, patterns: readonly string[]): RipgrepSink;
|
|
62
104
|
/** Builds the matching RegExp shared by the JS engine. */
|
|
63
105
|
export declare function buildJsRegExp(symbol: string, options: SearchOptions): RegExp;
|
|
106
|
+
interface CandidateFile {
|
|
107
|
+
absolutePath: string;
|
|
108
|
+
relativePath: string;
|
|
109
|
+
}
|
|
110
|
+
export interface Enumeration {
|
|
111
|
+
files: CandidateFile[];
|
|
112
|
+
/** True when the walk stopped early because the budget was reached. */
|
|
113
|
+
exceeded: boolean;
|
|
114
|
+
bytes: number;
|
|
115
|
+
}
|
|
116
|
+
export interface EnumerationBudget {
|
|
117
|
+
maxFiles: number;
|
|
118
|
+
maxBytes: number;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Lists the files a request would search, sorted by relative path.
|
|
122
|
+
*
|
|
123
|
+
* With a budget, the walk abandons as soon as the tree proves bigger than the
|
|
124
|
+
* budget allows. That makes it usable as a cheap probe: enumeration is stat-only
|
|
125
|
+
* work, so finding out a tree is "too big" costs a bounded number of stats
|
|
126
|
+
* rather than a full traversal.
|
|
127
|
+
*/
|
|
128
|
+
export declare function enumerateCandidates(request: SearchRequest, budget?: EnumerationBudget): Promise<Enumeration>;
|
|
64
129
|
/** Counts matches in one file and records per-line snippets. */
|
|
65
|
-
export declare function scanContent(content: string, relativePath: string, regexp: RegExp
|
|
130
|
+
export declare function scanContent(content: string, relativePath: string, regexp: RegExp,
|
|
131
|
+
/**
|
|
132
|
+
* Resolved lazily, and only once the file has produced a match. Classifying
|
|
133
|
+
* every file in scope would mean a comment scan of the whole tree; this way
|
|
134
|
+
* the cost is proportional to matches, which for an architecture assertion is
|
|
135
|
+
* usually zero.
|
|
136
|
+
*/
|
|
137
|
+
getMask?: () => CommentMask): Tally;
|
|
66
138
|
/** An engine that is guaranteed to implement the batch API. */
|
|
67
|
-
export type BatchEngine = Engine & Required<Pick<Engine, 'searchBatch'
|
|
139
|
+
export type BatchEngine = Engine & Required<Pick<Engine, 'searchBatch'>> & {
|
|
140
|
+
/** Scans a file list that the caller already enumerated. */
|
|
141
|
+
searchFiles(files: readonly CandidateFile[], requests: SearchRequest[]): Promise<SearchResult[]>;
|
|
142
|
+
};
|
|
68
143
|
export declare const javascriptEngine: BatchEngine;
|
|
144
|
+
/**
|
|
145
|
+
* How much scanning the JavaScript engine may do before ripgrep is worth a
|
|
146
|
+
* process spawn.
|
|
147
|
+
*
|
|
148
|
+
* Measured with scripts/bench-engines.mjs. ripgrep's cost is dominated by
|
|
149
|
+
* process startup and is nearly flat in tree size; the JavaScript scanner grows
|
|
150
|
+
* linearly. The crossover is therefore wherever a spawn costs, and that differs
|
|
151
|
+
* by an order of magnitude between platforms:
|
|
152
|
+
*
|
|
153
|
+
* Windows 11, Node 24, rg 15 spawn floor ~130ms crossover ~575 files
|
|
154
|
+
* Linux (container), Node 22, rg 13 spawn floor ~15ms crossover ~25 files
|
|
155
|
+
*
|
|
156
|
+
* The budget is set just below each crossover, so choosing JavaScript is never
|
|
157
|
+
* the slower option by more than a few milliseconds, while a small tree on
|
|
158
|
+
* Windows avoids a spawn that would cost ten times the whole search.
|
|
159
|
+
*/
|
|
160
|
+
export declare const SMALL_TREE_BUDGET: EnumerationBudget;
|
|
161
|
+
/** Exported so the code list can be asserted; EACCES and friends are not
|
|
162
|
+
* reproducible on demand from a real spawn. */
|
|
163
|
+
export declare function isMissingBinary(error: unknown): boolean;
|
|
69
164
|
/** Resolves the engine to use, honouring an explicit preference. */
|
|
70
165
|
export declare function resolveEngine(preference?: EnginePreference): Promise<Engine>;
|
|
71
166
|
export type CachedEngine = Engine & {
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|