@descent-vtt/spec-guard 0.7.0 → 0.9.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 +147 -0
- package/README.md +224 -27
- package/dist/cli.d.ts +37 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +275 -59
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +66 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +165 -0
- package/dist/config.js.map +1 -0
- package/dist/engine.d.ts +22 -8
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +118 -67
- package/dist/engine.js.map +1 -1
- package/dist/facts.d.ts +106 -0
- package/dist/facts.d.ts.map +1 -0
- package/dist/facts.js +188 -0
- package/dist/facts.js.map +1 -0
- package/dist/glob.d.ts +17 -21
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +13 -24
- package/dist/glob.js.map +1 -1
- package/dist/imports.d.ts +6 -1
- package/dist/imports.d.ts.map +1 -1
- package/dist/imports.js +9 -5
- package/dist/imports.js.map +1 -1
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/io.d.ts +51 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +44 -0
- package/dist/io.js.map +1 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +23 -18
- package/dist/mcp.js.map +1 -1
- package/dist/memo.d.ts +53 -0
- package/dist/memo.d.ts.map +1 -0
- package/dist/memo.js +67 -0
- package/dist/memo.js.map +1 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +17 -0
- package/dist/parser.js.map +1 -1
- package/dist/query.d.ts +5 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +12 -3
- package/dist/query.js.map +1 -1
- package/dist/reporter.d.ts +10 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +33 -4
- package/dist/reporter.js.map +1 -1
- package/dist/rules.d.ts +12 -1
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +45 -1
- package/dist/rules.js.map +1 -1
- package/dist/runner.d.ts +42 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +261 -76
- package/dist/runner.js.map +1 -1
- package/dist/specs.d.ts +3 -1
- package/dist/specs.d.ts.map +1 -1
- package/dist/specs.js +6 -5
- package/dist/specs.js.map +1 -1
- package/dist/structure.d.ts +104 -0
- package/dist/structure.d.ts.map +1 -0
- package/dist/structure.js +254 -0
- package/dist/structure.js.map +1 -0
- package/dist/types.d.ts +50 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/watch.d.ts +125 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +321 -0
- package/dist/watch.js.map +1 -0
- package/package.json +8 -2
package/dist/config.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The options a project keeps in its `package.json`, under `"specGuard"`.
|
|
3
|
+
*
|
|
4
|
+
* Validated from a value, never read from a file: the command line reads the
|
|
5
|
+
* file, and every malformed option can then be tested on a plain object. The
|
|
6
|
+
* keys are the options that are a project's policy - which specs, and how
|
|
7
|
+
* strictly to hold them - and nothing that describes one invocation. See
|
|
8
|
+
* ADR-0014.
|
|
9
|
+
*/
|
|
10
|
+
/** The name the options sit under in `package.json`. */
|
|
11
|
+
export const CONFIG_KEY = 'specGuard';
|
|
12
|
+
/** Every key, in the order the documentation lists them. */
|
|
13
|
+
export const CONFIG_KEYS = [
|
|
14
|
+
'specs',
|
|
15
|
+
'engine',
|
|
16
|
+
'strict',
|
|
17
|
+
'allowMissingTargets',
|
|
18
|
+
'allowEmptyScope',
|
|
19
|
+
'ignoreStatus',
|
|
20
|
+
'includeSpecs',
|
|
21
|
+
'defaultSkips',
|
|
22
|
+
'maxSnippets',
|
|
23
|
+
'concurrency',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Options that exist on the command line and are refused here by name.
|
|
27
|
+
*
|
|
28
|
+
* Each describes one invocation rather than a project. `"format": "sarif"`
|
|
29
|
+
* would make every developer's local run print SARIF, and `root` is where the
|
|
30
|
+
* configuration was found in the first place.
|
|
31
|
+
*/
|
|
32
|
+
export const INVOCATION_OPTIONS = new Set([
|
|
33
|
+
'root',
|
|
34
|
+
'format',
|
|
35
|
+
'json',
|
|
36
|
+
'verbose',
|
|
37
|
+
'color',
|
|
38
|
+
'failFast',
|
|
39
|
+
'printBaseline',
|
|
40
|
+
'allowEmpty',
|
|
41
|
+
'watch',
|
|
42
|
+
]);
|
|
43
|
+
/** A configuration that cannot be used, with the words to say why. */
|
|
44
|
+
export class ConfigError extends Error {
|
|
45
|
+
}
|
|
46
|
+
const ENGINE_ALIASES = {
|
|
47
|
+
auto: 'auto',
|
|
48
|
+
rg: 'ripgrep',
|
|
49
|
+
ripgrep: 'ripgrep',
|
|
50
|
+
js: 'javascript',
|
|
51
|
+
javascript: 'javascript',
|
|
52
|
+
node: 'javascript',
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The engine a name asks for, whether the name came from a flag or a file.
|
|
56
|
+
*
|
|
57
|
+
* One function, so `--engine rgg` and `"engine": "rgg"` fail with the same
|
|
58
|
+
* words. Case is ignored, as the flag always has.
|
|
59
|
+
*/
|
|
60
|
+
export function engineNamed(name) {
|
|
61
|
+
const engine = ENGINE_ALIASES[name.toLowerCase()];
|
|
62
|
+
if (engine === undefined)
|
|
63
|
+
throw new ConfigError(`Unknown engine "${name}". Expected auto, rg or js.`);
|
|
64
|
+
return engine;
|
|
65
|
+
}
|
|
66
|
+
/** A value as a message names it: `"yes"`, `3`, `null`, an array, an object. */
|
|
67
|
+
function describe(value) {
|
|
68
|
+
if (Array.isArray(value))
|
|
69
|
+
return 'an array';
|
|
70
|
+
if (value !== null && typeof value === 'object')
|
|
71
|
+
return 'an object';
|
|
72
|
+
return JSON.stringify(value);
|
|
73
|
+
}
|
|
74
|
+
function isObject(value) {
|
|
75
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Checks one key's value, or says what is wrong with it.
|
|
79
|
+
*
|
|
80
|
+
* A string is not a boolean and a number in a string is not a number. JSON has
|
|
81
|
+
* both types, and accepting `"true"` would be a second grammar for one file.
|
|
82
|
+
*/
|
|
83
|
+
function checkValue(key, value) {
|
|
84
|
+
switch (key) {
|
|
85
|
+
case 'specs':
|
|
86
|
+
return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === 'string' && entry.trim() !== '')
|
|
87
|
+
? null
|
|
88
|
+
: `must be a non-empty list of spec globs, got ${describe(value)}`;
|
|
89
|
+
case 'engine':
|
|
90
|
+
return typeof value === 'string' ? null : `must be a string, got ${describe(value)}`;
|
|
91
|
+
case 'maxSnippets':
|
|
92
|
+
return Number.isInteger(value) && value >= 0 ? null : `must be an integer, 0 or more, got ${describe(value)}`;
|
|
93
|
+
case 'concurrency':
|
|
94
|
+
return Number.isInteger(value) && value >= 1 ? null : `must be an integer, 1 or more, got ${describe(value)}`;
|
|
95
|
+
default:
|
|
96
|
+
return typeof value === 'boolean' ? null : `must be true or false, got ${describe(value)}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The configuration in a root's `package.json`, read by whoever is asking.
|
|
101
|
+
*
|
|
102
|
+
* The reader is a parameter because this module reads nothing itself: the
|
|
103
|
+
* command line passes the filesystem, and a watch session passes the door that
|
|
104
|
+
* records what it read, so an edit to `package.json` reaches the session like
|
|
105
|
+
* an edit to any other file. A file that is not there - or a root that is not a
|
|
106
|
+
* directory - is no configuration. A file that is there and cannot be read is
|
|
107
|
+
* an error, because nobody can tell what it would have said.
|
|
108
|
+
*/
|
|
109
|
+
export async function loadConfig(root, read) {
|
|
110
|
+
// Joined by hand rather than with node:path, which would be this module's
|
|
111
|
+
// only import: a root with either separator at its end is still one root.
|
|
112
|
+
const file = `${root.replace(/[\\/]+$/, '')}/package.json`;
|
|
113
|
+
let text;
|
|
114
|
+
try {
|
|
115
|
+
text = await read(file);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const code = error.code;
|
|
119
|
+
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
120
|
+
return {};
|
|
121
|
+
throw new ConfigError(`package.json could not be read (${error.message}), so its "${CONFIG_KEY}" options cannot be read.`);
|
|
122
|
+
}
|
|
123
|
+
return parseConfig(text);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The configuration in a `package.json`'s text, or none.
|
|
127
|
+
*
|
|
128
|
+
* A `package.json` with no `"specGuard"` is no configuration rather than a
|
|
129
|
+
* mistake. Anything else that cannot be used throws a `ConfigError` naming the
|
|
130
|
+
* file and the key, so a run never starts under options nobody meant.
|
|
131
|
+
*/
|
|
132
|
+
export function parseConfig(text, file = 'package.json') {
|
|
133
|
+
let manifest;
|
|
134
|
+
try {
|
|
135
|
+
manifest = JSON.parse(text);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw new ConfigError(`${file} is not valid JSON (${error.message}), so its "${CONFIG_KEY}" options cannot be read.`);
|
|
139
|
+
}
|
|
140
|
+
const options = isObject(manifest) ? manifest[CONFIG_KEY] : undefined;
|
|
141
|
+
if (options === undefined)
|
|
142
|
+
return {};
|
|
143
|
+
if (!isObject(options))
|
|
144
|
+
throw new ConfigError(`${file}: "${CONFIG_KEY}" must be an object, got ${describe(options)}.`);
|
|
145
|
+
const config = {};
|
|
146
|
+
for (const [key, value] of Object.entries(options)) {
|
|
147
|
+
if (INVOCATION_OPTIONS.has(key)) {
|
|
148
|
+
throw new ConfigError(`${file}: "${CONFIG_KEY}.${key}" is chosen on the command line, not in ${file}.`);
|
|
149
|
+
}
|
|
150
|
+
if (!CONFIG_KEYS.includes(key)) {
|
|
151
|
+
throw new ConfigError(`${file}: unknown option "${key}" in "${CONFIG_KEY}". Options are ${CONFIG_KEYS.join(', ')}.`);
|
|
152
|
+
}
|
|
153
|
+
const issue = checkValue(key, value);
|
|
154
|
+
if (issue !== null)
|
|
155
|
+
throw new ConfigError(`${file}: "${CONFIG_KEY}.${key}" ${issue}.`);
|
|
156
|
+
try {
|
|
157
|
+
config[key] = key === 'engine' ? engineNamed(value) : value;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw new ConfigError(`${file}: "${CONFIG_KEY}.${key}": ${error.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return config;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* flags are answered by a single pass.
|
|
20
20
|
*/
|
|
21
21
|
import { type CommentMask } from './comments.js';
|
|
22
|
-
import { type
|
|
22
|
+
import { type Io } from './io.js';
|
|
23
|
+
import { type Memo } from './memo.js';
|
|
23
24
|
import { type SkippedPath } from './scope.js';
|
|
24
25
|
import type { EngineName, MatchLocation, SearchOptions, SearchResult } from './types.js';
|
|
25
26
|
/** Files larger than this are skipped by both engines, keeping them in sync. */
|
|
@@ -222,13 +223,13 @@ export declare const ANY_FILE_PROBE: EnumerationBudget;
|
|
|
222
223
|
*/
|
|
223
224
|
export declare function enumerateCandidates(request: WalkRequest, budget?: EnumerationBudget,
|
|
224
225
|
/**
|
|
225
|
-
*
|
|
226
|
-
* `walkFiles` takes one: the interesting behaviour here
|
|
227
|
-
* enumeration does with a directory it *cannot* read, and there
|
|
228
|
-
* portable way to create one - Windows has no chmod, and a permission
|
|
229
|
-
* by a test is a permission bit a failed test leaves behind.
|
|
226
|
+
* The door the enumeration reads through, forwarded to the walk. Injectable
|
|
227
|
+
* for the same reason `walkFiles` takes one: the interesting behaviour here
|
|
228
|
+
* is what the enumeration does with a directory it *cannot* read, and there
|
|
229
|
+
* is no portable way to create one - Windows has no chmod, and a permission
|
|
230
|
+
* bit set by a test is a permission bit a failed test leaves behind.
|
|
230
231
|
*/
|
|
231
|
-
|
|
232
|
+
io?: Io): Promise<Enumeration>;
|
|
232
233
|
/** Counts matches in one file and records per-line snippets. */
|
|
233
234
|
export declare function scanContent(content: string, relativePath: string, regexp: RegExp,
|
|
234
235
|
/**
|
|
@@ -243,6 +244,15 @@ export type BatchEngine = Engine & Required<Pick<Engine, 'searchBatch'>> & {
|
|
|
243
244
|
/** Scans a file list that the caller already enumerated. */
|
|
244
245
|
searchFiles(files: readonly CandidateFile[], requests: SearchRequest[], unreadable?: readonly string[], skipped?: readonly SkippedPath[]): Promise<SearchResult[]>;
|
|
245
246
|
};
|
|
247
|
+
/**
|
|
248
|
+
* A scanner that reads through the given door, and remembers its scans in the
|
|
249
|
+
* given memo.
|
|
250
|
+
*
|
|
251
|
+
* One per watch session rule, so the session sees what each rule read
|
|
252
|
+
* (ADR-0014). A plain run shares the one below, which reads the filesystem and
|
|
253
|
+
* remembers nothing.
|
|
254
|
+
*/
|
|
255
|
+
export declare function createJavaScriptEngine(io?: Io, memo?: Memo): BatchEngine;
|
|
246
256
|
export declare const javascriptEngine: BatchEngine;
|
|
247
257
|
/**
|
|
248
258
|
* How much scanning the JavaScript engine may do before ripgrep is worth a
|
|
@@ -283,7 +293,11 @@ export type CachedEngine = Engine & Required<Pick<Engine, 'searchBatch'>> & {
|
|
|
283
293
|
/**
|
|
284
294
|
* Wraps an engine with a de-duplicating cache plus an automatic fallback to the
|
|
285
295
|
* JS engine, so a ripgrep hiccup degrades to "slower" instead of "broken".
|
|
296
|
+
*
|
|
297
|
+
* The fallback is a parameter because a watch session's scanner reads through a
|
|
298
|
+
* door of its own, and falling back to the shared one would read past it.
|
|
299
|
+
* Nothing falls back from the fallback: its failure is the run's failure.
|
|
286
300
|
*/
|
|
287
|
-
export declare function createCachedEngine(engine: Engine): CachedEngine;
|
|
301
|
+
export declare function createCachedEngine(engine: Engine, fallback?: BatchEngine): CachedEngine;
|
|
288
302
|
export {};
|
|
289
303
|
//# sourceMappingURL=engine.d.ts.map
|
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;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
CHANGED
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
* flags are answered by a single pass.
|
|
20
20
|
*/
|
|
21
21
|
import { spawn } from 'node:child_process';
|
|
22
|
-
import { promises as fs } from 'node:fs';
|
|
23
22
|
import path from 'node:path';
|
|
24
23
|
import { createCommentMask } from './comments.js';
|
|
25
|
-
import { createExcludeMatcher, createGlobMatcher,
|
|
24
|
+
import { createExcludeMatcher, createGlobMatcher, toPosix, walkFiles } from './glob.js';
|
|
25
|
+
import { nodeIo } from './io.js';
|
|
26
|
+
import { NO_MEMO } from './memo.js';
|
|
26
27
|
import { isBinary, LedgerBuilder, UNCERTAIN_REASONS } from './scope.js';
|
|
27
28
|
import { lineStarts, locate } from './text.js';
|
|
28
29
|
/** Files larger than this are skipped by both engines, keeping them in sync. */
|
|
@@ -409,8 +410,33 @@ export function buildJsRegExp(symbol, options) {
|
|
|
409
410
|
const source = options.word ? `(?<![\\p{L}\\p{N}_])(?:${escaped})(?![\\p{L}\\p{N}_])` : escaped;
|
|
410
411
|
return new RegExp(source, `${flags}u`);
|
|
411
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Scans one file's bytes for every pattern of a pass.
|
|
415
|
+
*
|
|
416
|
+
* A pure function of the bytes, the path (which decides the comment syntax),
|
|
417
|
+
* the patterns and how they match, which is what lets a watch session remember
|
|
418
|
+
* it (ADR-0014).
|
|
419
|
+
*/
|
|
420
|
+
function scanFile(buffer, relativePath, regexps, ignoreComments) {
|
|
421
|
+
const content = buffer.toString('utf8');
|
|
422
|
+
let mask;
|
|
423
|
+
const getMask = ignoreComments ? () => (mask ??= createCommentMask(content, relativePath)) : undefined;
|
|
424
|
+
const tallies = new Map();
|
|
425
|
+
for (const [pattern, regexp] of regexps) {
|
|
426
|
+
tallies.set(pattern, scanContent(content, relativePath, regexp, getMask));
|
|
427
|
+
}
|
|
428
|
+
// Read after the loop, where every pattern has had its chance to ask.
|
|
429
|
+
const built = mask;
|
|
430
|
+
return { tallies, binary: isBinary(buffer), classified: built?.classified };
|
|
431
|
+
}
|
|
412
432
|
class JavaScriptEngine {
|
|
433
|
+
io;
|
|
434
|
+
memo;
|
|
413
435
|
name = 'javascript';
|
|
436
|
+
constructor(io, memo) {
|
|
437
|
+
this.io = io;
|
|
438
|
+
this.memo = memo;
|
|
439
|
+
}
|
|
414
440
|
async search(request) {
|
|
415
441
|
const [result] = await this.searchBatch([request]);
|
|
416
442
|
return result;
|
|
@@ -424,7 +450,7 @@ class JavaScriptEngine {
|
|
|
424
450
|
const [first] = requests;
|
|
425
451
|
if (!first)
|
|
426
452
|
return [];
|
|
427
|
-
const enumeration = await enumerateCandidates(first);
|
|
453
|
+
const enumeration = await enumerateCandidates(first, undefined, this.io);
|
|
428
454
|
return this.searchFiles(enumeration.files, requests, [], enumeration.skipped);
|
|
429
455
|
}
|
|
430
456
|
/**
|
|
@@ -438,48 +464,68 @@ class JavaScriptEngine {
|
|
|
438
464
|
const [first] = requests;
|
|
439
465
|
if (!first)
|
|
440
466
|
return [];
|
|
441
|
-
const ledger = new LedgerBuilder();
|
|
442
|
-
for (const entry of skipped)
|
|
443
|
-
ledger.add(entry.path, entry.reason, entry.matches);
|
|
444
|
-
for (const entry of unreadable)
|
|
445
|
-
ledger.add(entry, 'unreadable');
|
|
446
467
|
const patterns = [...new Set(requests.map((request) => request.symbol))];
|
|
447
468
|
const regexps = new Map(patterns.map((pattern) => [pattern, buildJsRegExp(pattern, first.options)]));
|
|
448
469
|
const tallies = new Map(patterns.map((pattern) => [pattern, emptyTally()]));
|
|
470
|
+
// One ledger and one count of unclassified files per pattern, not one for
|
|
471
|
+
// the pass. A pass answers every rule over the same scope at once, and a
|
|
472
|
+
// binary file holding one rule's symbol is not a gap in another rule's
|
|
473
|
+
// answer - yet a shared ledger reported it against every rule in the pass,
|
|
474
|
+
// with all their matches added up, and --strict failed rules over a file
|
|
475
|
+
// that did not hold their symbol. Found by holding ADR-0014's watch
|
|
476
|
+
// session, which runs each rule alone, to a plain run.
|
|
477
|
+
const ledgers = new Map(patterns.map((pattern) => [pattern, new LedgerBuilder()]));
|
|
478
|
+
const unclassified = new Map(patterns.map((pattern) => [pattern, 0]));
|
|
479
|
+
for (const ledger of ledgers.values()) {
|
|
480
|
+
// What every rule shares: what the walk could not list, and what ripgrep
|
|
481
|
+
// could not read.
|
|
482
|
+
for (const entry of skipped)
|
|
483
|
+
ledger.add(entry.path, entry.reason, entry.matches);
|
|
484
|
+
for (const entry of unreadable)
|
|
485
|
+
ledger.add(entry, 'unreadable');
|
|
486
|
+
}
|
|
487
|
+
// Everything a file's scan depends on besides its bytes and its path, which
|
|
488
|
+
// decides its comment syntax. See memo.ts for why a missing input here is
|
|
489
|
+
// the one defect a memo can have.
|
|
490
|
+
const { regex, word, ignoreCase, ignoreComments } = first.options;
|
|
491
|
+
const scanInputs = [JSON.stringify([regex, word, ignoreCase, ignoreComments]), JSON.stringify(patterns)];
|
|
449
492
|
// No floor of 1: an empty file list starts no readers, and a loop that
|
|
450
493
|
// never runs produces the same empty tallies as one that runs once and
|
|
451
494
|
// finds nothing. The guard was defending against an outcome it shared.
|
|
452
495
|
const concurrency = readConcurrency(files.length);
|
|
453
496
|
let cursor = 0;
|
|
454
|
-
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
const
|
|
459
|
-
let unclassifiedFiles = 0;
|
|
497
|
+
// What each reader found, by file. Entered in the ledgers and the tallies
|
|
498
|
+
// in walk order below, rather than by each reader as its read ends: that
|
|
499
|
+
// was the order reads happened to finish in, which differed between two
|
|
500
|
+
// runs of one tree - and past a ledger's cap, so did which paths it named.
|
|
501
|
+
const scans = new Map();
|
|
460
502
|
const worker = async () => {
|
|
461
503
|
while (cursor < files.length) {
|
|
462
504
|
const file = files[cursor++];
|
|
463
505
|
/* c8 ignore next -- cursor is bounded by files.length */
|
|
464
506
|
if (!file)
|
|
465
507
|
return;
|
|
466
|
-
const buffer = await
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
508
|
+
const buffer = await this.io.readFile(file.absolutePath).catch(() => null);
|
|
509
|
+
// A file we cannot open might hold anything, so it is recorded rather
|
|
510
|
+
// than passed over as though it had been read and found clean.
|
|
511
|
+
scans.set(file.relativePath, buffer === null
|
|
512
|
+
? null
|
|
513
|
+
: this.memo.remember(buffer, [...scanInputs, file.relativePath], () => scanFile(buffer, file.relativePath, regexps, ignoreComments)));
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
517
|
+
// Merge in walk order so snippets come out sorted by path, like ripgrep's,
|
|
518
|
+
// and so do the ledgers.
|
|
519
|
+
const byFile = new Map(patterns.map((pattern) => [pattern, new Map()]));
|
|
520
|
+
for (const file of files) {
|
|
521
|
+
const scan = scans.get(file.relativePath);
|
|
522
|
+
if (scan === null) {
|
|
523
|
+
for (const ledger of ledgers.values())
|
|
470
524
|
ledger.add(file.relativePath, 'unreadable');
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const getMask = first.options.ignoreComments
|
|
476
|
-
? () => (mask ??= createCommentMask(content, file.relativePath))
|
|
477
|
-
: undefined;
|
|
478
|
-
const scanned = new Map();
|
|
479
|
-
for (const [pattern, regexp] of regexps) {
|
|
480
|
-
scanned.set(pattern, scanContent(content, file.relativePath, regexp, getMask));
|
|
481
|
-
}
|
|
482
|
-
if (isBinary(buffer)) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
for (const [pattern, tally] of scan.tallies) {
|
|
528
|
+
if (scan.binary) {
|
|
483
529
|
// Searched, but not counted. Skipping binary files silently was a way
|
|
484
530
|
// to pass an assertion by never looking; searching them and saying
|
|
485
531
|
// what was found leaves the decision with the reader.
|
|
@@ -489,26 +535,17 @@ class JavaScriptEngine {
|
|
|
489
535
|
// found clean - and leaving it out is also what keeps the two engines
|
|
490
536
|
// reporting the same thing, since ripgrep only ever hands the scanner
|
|
491
537
|
// files that matched.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
ledger.add(file.relativePath, 'binary', found);
|
|
538
|
+
if (tally.count > 0)
|
|
539
|
+
ledgers.get(pattern).add(file.relativePath, 'binary', tally.count);
|
|
495
540
|
continue;
|
|
496
541
|
}
|
|
497
|
-
// A mask
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
};
|
|
505
|
-
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
506
|
-
// Merge in walk order so snippets come out sorted by path, like ripgrep's.
|
|
507
|
-
for (const file of files) {
|
|
508
|
-
const scanned = perFile.get(file.relativePath);
|
|
509
|
-
if (!scanned)
|
|
510
|
-
continue;
|
|
511
|
-
for (const [pattern, tally] of scanned) {
|
|
542
|
+
// A mask is built only once some pattern has matched, and a file this
|
|
543
|
+
// pattern did not match is not one whose comments it counted as code.
|
|
544
|
+
// No comment count here: a file whose comments cannot be told from its
|
|
545
|
+
// code has none, so every match it holds is in the count.
|
|
546
|
+
if (scan.classified === false && tally.count > 0) {
|
|
547
|
+
unclassified.set(pattern, unclassified.get(pattern) + 1);
|
|
548
|
+
}
|
|
512
549
|
const total = tallies.get(pattern);
|
|
513
550
|
total.count += tally.count;
|
|
514
551
|
total.commentCount += tally.commentCount;
|
|
@@ -520,16 +557,15 @@ class JavaScriptEngine {
|
|
|
520
557
|
}
|
|
521
558
|
}
|
|
522
559
|
}
|
|
523
|
-
const scope = ledger.build();
|
|
524
560
|
return requests.map((request) => {
|
|
525
|
-
const tally = tallies.get(request.symbol)
|
|
561
|
+
const tally = tallies.get(request.symbol);
|
|
526
562
|
return {
|
|
527
563
|
count: tally.count,
|
|
528
564
|
commentMatches: tally.commentCount,
|
|
529
|
-
unclassifiedFiles,
|
|
565
|
+
unclassifiedFiles: unclassified.get(request.symbol),
|
|
530
566
|
matches: tally.locations,
|
|
531
|
-
fileCounts: byFile.get(request.symbol)
|
|
532
|
-
scope,
|
|
567
|
+
fileCounts: byFile.get(request.symbol),
|
|
568
|
+
scope: ledgers.get(request.symbol).build(),
|
|
533
569
|
engine: this.name,
|
|
534
570
|
};
|
|
535
571
|
});
|
|
@@ -554,13 +590,13 @@ export const ANY_FILE_PROBE = { maxFiles: 0, maxBytes: 0 };
|
|
|
554
590
|
*/
|
|
555
591
|
export async function enumerateCandidates(request, budget,
|
|
556
592
|
/**
|
|
557
|
-
*
|
|
558
|
-
* `walkFiles` takes one: the interesting behaviour here
|
|
559
|
-
* enumeration does with a directory it *cannot* read, and there
|
|
560
|
-
* portable way to create one - Windows has no chmod, and a permission
|
|
561
|
-
* by a test is a permission bit a failed test leaves behind.
|
|
593
|
+
* The door the enumeration reads through, forwarded to the walk. Injectable
|
|
594
|
+
* for the same reason `walkFiles` takes one: the interesting behaviour here
|
|
595
|
+
* is what the enumeration does with a directory it *cannot* read, and there
|
|
596
|
+
* is no portable way to create one - Windows has no chmod, and a permission
|
|
597
|
+
* bit set by a test is a permission bit a failed test leaves behind.
|
|
562
598
|
*/
|
|
563
|
-
|
|
599
|
+
io = nodeIo) {
|
|
564
600
|
const matcher = createGlobMatcher(request.options.globs);
|
|
565
601
|
const excluded = createExcludeMatcher(request.options.excludeGlobs);
|
|
566
602
|
// excludeFiles is applied here rather than after the walk so that a budgeted
|
|
@@ -597,7 +633,7 @@ readDirectory) {
|
|
|
597
633
|
};
|
|
598
634
|
outer: for (const target of targetsOf(request)) {
|
|
599
635
|
const absoluteTarget = path.resolve(request.root, target);
|
|
600
|
-
const stats = await
|
|
636
|
+
const stats = await io.stat(absoluteTarget);
|
|
601
637
|
if (!stats)
|
|
602
638
|
continue;
|
|
603
639
|
if (stats.isFile()) {
|
|
@@ -612,7 +648,7 @@ readDirectory) {
|
|
|
612
648
|
const walkOptions = {
|
|
613
649
|
scope: request.options.scope,
|
|
614
650
|
onSkip: (relativePath, reason) => note(prefix ? `${prefix}/${relativePath}` : relativePath, reason),
|
|
615
|
-
|
|
651
|
+
io,
|
|
616
652
|
};
|
|
617
653
|
for await (const file of walkFiles(absoluteTarget, walkOptions)) {
|
|
618
654
|
if (!withinSizeLimit(file.size))
|
|
@@ -681,7 +717,18 @@ getMask) {
|
|
|
681
717
|
// comparator changed any result.
|
|
682
718
|
return { count, locations: [...byLine.values()], commentCount };
|
|
683
719
|
}
|
|
684
|
-
|
|
720
|
+
/**
|
|
721
|
+
* A scanner that reads through the given door, and remembers its scans in the
|
|
722
|
+
* given memo.
|
|
723
|
+
*
|
|
724
|
+
* One per watch session rule, so the session sees what each rule read
|
|
725
|
+
* (ADR-0014). A plain run shares the one below, which reads the filesystem and
|
|
726
|
+
* remembers nothing.
|
|
727
|
+
*/
|
|
728
|
+
export function createJavaScriptEngine(io = nodeIo, memo = NO_MEMO) {
|
|
729
|
+
return new JavaScriptEngine(io, memo);
|
|
730
|
+
}
|
|
731
|
+
export const javascriptEngine = createJavaScriptEngine();
|
|
685
732
|
/**
|
|
686
733
|
* How much scanning the JavaScript engine may do before ripgrep is worth a
|
|
687
734
|
* process spawn.
|
|
@@ -806,8 +853,12 @@ export async function resolveEngine(preference = 'auto') {
|
|
|
806
853
|
/**
|
|
807
854
|
* Wraps an engine with a de-duplicating cache plus an automatic fallback to the
|
|
808
855
|
* JS engine, so a ripgrep hiccup degrades to "slower" instead of "broken".
|
|
856
|
+
*
|
|
857
|
+
* The fallback is a parameter because a watch session's scanner reads through a
|
|
858
|
+
* door of its own, and falling back to the shared one would read past it.
|
|
859
|
+
* Nothing falls back from the fallback: its failure is the run's failure.
|
|
809
860
|
*/
|
|
810
|
-
export function createCachedEngine(engine) {
|
|
861
|
+
export function createCachedEngine(engine, fallback = javascriptEngine) {
|
|
811
862
|
const cache = new Map();
|
|
812
863
|
const fallbacks = [];
|
|
813
864
|
const keyOf = searchKey;
|
|
@@ -826,10 +877,10 @@ export function createCachedEngine(engine) {
|
|
|
826
877
|
let result = cache.get(key);
|
|
827
878
|
if (!result) {
|
|
828
879
|
result = engine.search(request).catch(async (error) => {
|
|
829
|
-
if (engine ===
|
|
880
|
+
if (engine === fallback)
|
|
830
881
|
throw error;
|
|
831
882
|
recordFallback(error);
|
|
832
|
-
return
|
|
883
|
+
return fallback.search(request);
|
|
833
884
|
});
|
|
834
885
|
cache.set(key, result);
|
|
835
886
|
}
|
|
@@ -844,10 +895,10 @@ export function createCachedEngine(engine) {
|
|
|
844
895
|
// does nothing. The guard could not change an outcome, which is why
|
|
845
896
|
// nothing could be written to hold it in place.
|
|
846
897
|
const pending = runSearches(engine, uncached).catch(async (error) => {
|
|
847
|
-
if (engine ===
|
|
898
|
+
if (engine === fallback)
|
|
848
899
|
throw error;
|
|
849
900
|
recordFallback(error);
|
|
850
|
-
return runSearches(
|
|
901
|
+
return runSearches(fallback, uncached);
|
|
851
902
|
});
|
|
852
903
|
uncached.forEach((request, index) => {
|
|
853
904
|
cache.set(keyOf(request), pending.then((results) => results[index]));
|