@descent-vtt/spec-graph 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 +477 -0
- package/README.md +109 -17
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +38 -19
- package/dist/cli.js.map +1 -1
- package/dist/directives.d.ts +9 -2
- package/dist/directives.js +15 -4
- package/dist/directives.js.map +1 -1
- package/dist/extract.d.ts +1 -1
- package/dist/extract.js +50 -16
- package/dist/extract.js.map +1 -1
- package/dist/glob.d.ts +44 -44
- package/dist/glob.js +152 -116
- package/dist/glob.js.map +1 -1
- package/dist/identity.d.ts +12 -4
- package/dist/identity.js +32 -8
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.js +10 -2
- package/dist/lifecycle.js.map +1 -1
- package/dist/markdown.d.ts +53 -161
- package/dist/markdown.js +42 -864
- package/dist/markdown.js.map +1 -1
- package/dist/report.d.ts +23 -0
- package/dist/report.js +80 -0
- package/dist/report.js.map +1 -1
- package/dist/rules.js +48 -14
- package/dist/rules.js.map +1 -1
- package/dist/sections.d.ts +1 -1
- package/dist/sections.js +18 -4
- package/dist/sections.js.map +1 -1
- package/dist/select.d.ts +2 -2
- package/dist/select.js +3 -3
- package/dist/select.js.map +1 -1
- package/dist/source.d.ts +6 -19
- package/dist/source.js +4 -70
- package/dist/source.js.map +1 -1
- package/dist/types.d.ts +23 -3
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
- package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
- package/dist/vendor/spec-core/markdown/headings.js +199 -0
- package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
- package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
- package/dist/vendor/spec-core/markdown/index.js +4 -0
- package/dist/vendor/spec-core/markdown/index.js.map +1 -0
- package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
- package/dist/vendor/spec-core/markdown/layout.js +28 -0
- package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
- package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/links.js +364 -0
- package/dist/vendor/spec-core/markdown/links.js.map +1 -0
- package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/lists.js +127 -0
- package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
- package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
- package/dist/vendor/spec-core/markdown/scan.js +439 -0
- package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
- package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
- package/dist/vendor/spec-core/markdown/syntax.js +181 -0
- package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
- package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
- package/dist/vendor/spec-core/markdown/tables.js +86 -0
- package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
- package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
- package/dist/vendor/spec-core/markdown/types.js +9 -0
- package/dist/vendor/spec-core/markdown/types.js.map +1 -0
- package/dist/vendor/spec-core/path/index.d.ts +1 -0
- package/dist/vendor/spec-core/path/index.js +2 -0
- package/dist/vendor/spec-core/path/index.js.map +1 -0
- package/dist/vendor/spec-core/path/posix.d.ts +83 -0
- package/dist/vendor/spec-core/path/posix.js +150 -0
- package/dist/vendor/spec-core/path/posix.js.map +1 -0
- package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
- package/dist/vendor/spec-core/pattern/automaton.js +340 -0
- package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
- package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
- package/dist/vendor/spec-core/pattern/charset.js +116 -0
- package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
- package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
- package/dist/vendor/spec-core/pattern/glob.js +481 -0
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
- package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
- package/dist/vendor/spec-core/pattern/index.js +4 -0
- package/dist/vendor/spec-core/pattern/index.js.map +1 -0
- package/dist/{regex.d.ts → vendor/spec-core/pattern/regex.d.ts} +20 -29
- package/dist/{regex.js → vendor/spec-core/pattern/regex.js} +40 -49
- package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
- package/dist/vendor/spec-core/text/index.d.ts +2 -0
- package/dist/vendor/spec-core/text/index.js +2 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -0
- package/dist/vendor/spec-core/text/lines.d.ts +72 -0
- package/dist/vendor/spec-core/text/lines.js +156 -0
- package/dist/vendor/spec-core/text/lines.js.map +1 -0
- package/dist/yaml.d.ts +44 -12
- package/dist/yaml.js +115 -206
- package/dist/yaml.js.map +1 -1
- package/package.json +3 -2
- package/src/vendor/spec-core/LICENSE +21 -0
- package/src/vendor/spec-core/README.md +8 -0
- package/dist/regex.js.map +0 -1
package/dist/glob.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pattern matching and directory walking.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The patterns are the `path` dialect every spec-* tool reads, from spec-core:
|
|
5
|
+
* `**`, `*`, `?`, `{a,b}`, `[abc]` and `[!a-z]`, matched against whole
|
|
6
|
+
* repository-relative POSIX paths by an automaton that cannot backtrack, with
|
|
7
|
+
* case respected on every host (ADR-0022). What stays here is spec-graph's own:
|
|
8
|
+
* how a list with `!` entries in it reads, which directories a walk never
|
|
9
|
+
* enters, and how a reference target - which is not a path - is matched.
|
|
9
10
|
*/
|
|
10
|
-
import { type
|
|
11
|
+
import { type RegexMatcher } from './vendor/spec-core/pattern/index.js';
|
|
11
12
|
/** Directories skipped unless a pattern explicitly names them. */
|
|
12
13
|
export declare const DEFAULT_IGNORED_DIRECTORIES: readonly string[];
|
|
13
14
|
/** Files larger than this are skipped: a specification is not a binary. */
|
|
@@ -30,41 +31,32 @@ export interface WalkOptions {
|
|
|
30
31
|
/** True when the string contains glob syntax rather than being a literal path. */
|
|
31
32
|
export declare function isGlob(pattern: string): boolean;
|
|
32
33
|
/**
|
|
33
|
-
* Compiles a glob to an anchored regular expression
|
|
34
|
+
* Compiles a glob to an anchored regular expression, in the dialect spec-graph
|
|
35
|
+
* read before it adopted spec-core's.
|
|
34
36
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Nothing in the pipeline matches with this any more - see {@link compileGlob},
|
|
40
|
-
* which gives the same answers without the backtracking. It stays for callers
|
|
41
|
-
* who want a `RegExp`, and as the oracle the tests hold the automaton to.
|
|
37
|
+
* @deprecated Nothing matches with this. It stays for callers who want a
|
|
38
|
+
* `RegExp`, and as the record of the old reading that `tests/glob.test.ts`
|
|
39
|
+
* holds every difference against, each one named in ADR-0022. It no longer
|
|
40
|
+
* folds case on Windows alone: an answer must not depend on the host.
|
|
42
41
|
*/
|
|
43
42
|
export declare function globToRegExp(pattern: string): RegExp;
|
|
44
43
|
export interface GlobOptions {
|
|
45
|
-
/** Fold case.
|
|
44
|
+
/** Fold case. Off unless asked for, on every host. */
|
|
46
45
|
readonly ignoreCase?: boolean | undefined;
|
|
47
46
|
}
|
|
48
47
|
/**
|
|
49
|
-
* Compiles
|
|
50
|
-
*
|
|
51
|
-
* A glob has no nested quantifiers, and ADR-0017 took that to mean there was no
|
|
52
|
-
* backtracking hazard in handing one to `RegExp`. Nobody had timed it. Each
|
|
53
|
-
* `*` is a `[^/]*`, and against a subject that fails to match, a backtracking
|
|
54
|
-
* engine tries every way of dividing it between them - the subject's length to
|
|
55
|
-
* the power of the stars:
|
|
48
|
+
* Compiles one glob to a matcher over whole repository-relative paths.
|
|
56
49
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* *-*-*-x a 10,000-character reference target 120s 0.9ms
|
|
61
|
-
* ```
|
|
50
|
+
* A pattern with no glob syntax names that path and nothing beneath it; a bare
|
|
51
|
+
* directory meaning its contents is a reading of {@link createGlobMatcher}'s.
|
|
52
|
+
* Throws a `GlobError` that names the glob as it was written.
|
|
62
53
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
54
|
+
* Matching is linear in the path whatever the pattern. It was not always: a
|
|
55
|
+
* glob's stars are not nested, but each is a `[^/]*` to `RegExp`, and a subject
|
|
56
|
+
* that fails is divided between them every way there is - `*-*-*-x` took two
|
|
57
|
+
* minutes over a 10,000-character reference target (ADR-0017).
|
|
66
58
|
*/
|
|
67
|
-
export declare function compileGlob(pattern: string, options?: GlobOptions):
|
|
59
|
+
export declare function compileGlob(pattern: string, options?: GlobOptions): RegexMatcher;
|
|
68
60
|
export interface GlobMatcher {
|
|
69
61
|
(path: string): boolean;
|
|
70
62
|
}
|
|
@@ -72,29 +64,37 @@ export interface GlobMatcher {
|
|
|
72
64
|
* Builds a matcher from include patterns, honouring `!` negations.
|
|
73
65
|
*
|
|
74
66
|
* Later patterns win, so `docs/**\/*.md` followed by `!docs/drafts/**` reads the
|
|
75
|
-
* way a `.gitignore` does.
|
|
67
|
+
* way a `.gitignore` does. A pattern with no glob syntax in it names a file, or
|
|
68
|
+
* a directory and everything beneath it; `docs/` names only what is beneath.
|
|
76
69
|
*/
|
|
77
70
|
export declare function createGlobMatcher(patterns: readonly string[]): GlobMatcher;
|
|
78
71
|
/**
|
|
79
72
|
* Builds a predicate over reference *targets*, not paths.
|
|
80
73
|
*
|
|
81
74
|
* Separate from {@link createGlobMatcher} on purpose. That one is
|
|
82
|
-
* path-oriented:
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* nothing else.
|
|
75
|
+
* path-oriented: a bare name there is a directory and everything under it. A
|
|
76
|
+
* reference target has no such structure - `trap 55` is a name, not a location
|
|
77
|
+
* - so a bare pattern matches it literally and nothing else.
|
|
86
78
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
79
|
+
* Case is ignored, on every platform, because a citation's spelling is the
|
|
80
|
+
* author's and not the filesystem's. The fold is simple case mapping, one code
|
|
81
|
+
* point at a time and without a locale, so every host gives the same answer:
|
|
82
|
+
* U+00B5 (micro sign) and U+03BC (mu) stay two targets, as they have been since
|
|
83
|
+
* a Windows-only `i` flag made them one there (ADR-0017).
|
|
90
84
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
85
|
+
* A target is text a document holds rather than a path on this host, and two
|
|
86
|
+
* things follow. A `\` escapes the character after it: there is no Windows
|
|
87
|
+
* separator to allow for. And a `.` or `..` segment is text: in a path the
|
|
88
|
+
* dialect drops the one and refuses the other as climbing out of the root, but
|
|
89
|
+
* `../../notes/gone.md` is a link somebody wrote and may want left alone.
|
|
95
90
|
*/
|
|
96
91
|
export declare function createReferenceFilter(patterns: readonly string[]): (target: string) => boolean;
|
|
97
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* The literal directory a pattern's matches all sit under, or `''` for the root.
|
|
94
|
+
*
|
|
95
|
+
* `{docs,specs}/**` has two, and this is the directory they share; the walk
|
|
96
|
+
* itself starts at each.
|
|
97
|
+
*/
|
|
98
98
|
export declare function globBase(pattern: string): string;
|
|
99
99
|
/**
|
|
100
100
|
* Walks the tree and returns every file matching the patterns.
|
package/dist/glob.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pattern matching and directory walking.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The patterns are the `path` dialect every spec-* tool reads, from spec-core:
|
|
5
|
+
* `**`, `*`, `?`, `{a,b}`, `[abc]` and `[!a-z]`, matched against whole
|
|
6
|
+
* repository-relative POSIX paths by an automaton that cannot backtrack, with
|
|
7
|
+
* case respected on every host (ADR-0022). What stays here is spec-graph's own:
|
|
8
|
+
* how a list with `!` entries in it reads, which directories a walk never
|
|
9
|
+
* enters, and how a reference target - which is not a path - is matched.
|
|
9
10
|
*/
|
|
10
11
|
import { readdir, stat } from 'node:fs/promises';
|
|
11
|
-
import { isAbsolutePath, joinPosix,
|
|
12
|
-
import {
|
|
12
|
+
import { isAbsolutePath, joinPosix, toPosix } from './paths.js';
|
|
13
|
+
import { compileGlob as compileFamilyGlob, GlobError, isGlobSyntax, parseGlob, parseGlobList, } from './vendor/spec-core/pattern/index.js';
|
|
13
14
|
/** Directories skipped unless a pattern explicitly names them. */
|
|
14
15
|
export const DEFAULT_IGNORED_DIRECTORIES = Object.freeze([
|
|
15
16
|
'.git',
|
|
@@ -32,55 +33,51 @@ export const DEFAULT_IGNORED_DIRECTORIES = Object.freeze([
|
|
|
32
33
|
]);
|
|
33
34
|
/** Files larger than this are skipped: a specification is not a binary. */
|
|
34
35
|
export const MAX_FILE_SIZE = 4 * 1024 * 1024;
|
|
36
|
+
/**
|
|
37
|
+
* How a path pattern is read everywhere in spec-graph.
|
|
38
|
+
*
|
|
39
|
+
* A literal names a file or a directory and everything beneath it, which is
|
|
40
|
+
* what `--ignore docs/drafts` has always meant. A `\` is a separator, because a
|
|
41
|
+
* pattern typed on a Windows shell arrives with them and spec-graph has always
|
|
42
|
+
* read them that way; it cannot also be an escape.
|
|
43
|
+
*/
|
|
44
|
+
const PATH = { dialect: 'path', caseSensitive: true, literal: 'either', backslash: 'separator' };
|
|
35
45
|
/** True when the string contains glob syntax rather than being a literal path. */
|
|
36
46
|
export function isGlob(pattern) {
|
|
37
|
-
return
|
|
47
|
+
return isGlobSyntax(pattern);
|
|
38
48
|
}
|
|
39
49
|
/**
|
|
40
|
-
* Compiles a glob to an anchored regular expression
|
|
50
|
+
* Compiles a glob to an anchored regular expression, in the dialect spec-graph
|
|
51
|
+
* read before it adopted spec-core's.
|
|
41
52
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Nothing in the pipeline matches with this any more - see {@link compileGlob},
|
|
47
|
-
* which gives the same answers without the backtracking. It stays for callers
|
|
48
|
-
* who want a `RegExp`, and as the oracle the tests hold the automaton to.
|
|
53
|
+
* @deprecated Nothing matches with this. It stays for callers who want a
|
|
54
|
+
* `RegExp`, and as the record of the old reading that `tests/glob.test.ts`
|
|
55
|
+
* holds every difference against, each one named in ADR-0022. It no longer
|
|
56
|
+
* folds case on Windows alone: an answer must not depend on the host.
|
|
49
57
|
*/
|
|
50
58
|
export function globToRegExp(pattern) {
|
|
51
|
-
return new RegExp(`^${globSource(pattern)}
|
|
59
|
+
return new RegExp(`^${globSource(pattern)}$`);
|
|
52
60
|
}
|
|
53
61
|
/**
|
|
54
|
-
* Compiles
|
|
55
|
-
*
|
|
56
|
-
* A glob has no nested quantifiers, and ADR-0017 took that to mean there was no
|
|
57
|
-
* backtracking hazard in handing one to `RegExp`. Nobody had timed it. Each
|
|
58
|
-
* `*` is a `[^/]*`, and against a subject that fails to match, a backtracking
|
|
59
|
-
* engine tries every way of dividing it between them - the subject's length to
|
|
60
|
-
* the power of the stars:
|
|
62
|
+
* Compiles one glob to a matcher over whole repository-relative paths.
|
|
61
63
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* *-*-*-x a 10,000-character reference target 120s 0.9ms
|
|
66
|
-
* ```
|
|
64
|
+
* A pattern with no glob syntax names that path and nothing beneath it; a bare
|
|
65
|
+
* directory meaning its contents is a reading of {@link createGlobMatcher}'s.
|
|
66
|
+
* Throws a `GlobError` that names the glob as it was written.
|
|
67
67
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
68
|
+
* Matching is linear in the path whatever the pattern. It was not always: a
|
|
69
|
+
* glob's stars are not nested, but each is a `[^/]*` to `RegExp`, and a subject
|
|
70
|
+
* that fails is divided between them every way there is - `*-*-*-x` took two
|
|
71
|
+
* minutes over a 10,000-character reference target (ADR-0017).
|
|
71
72
|
*/
|
|
72
73
|
export function compileGlob(pattern, options = {}) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// The message describes an expression the user never wrote, so it is told
|
|
79
|
-
// against the glob they did.
|
|
80
|
-
throw new Error(`invalid glob "${pattern}": ${error.message}`);
|
|
81
|
-
}
|
|
74
|
+
// `literal: 'file'` is the dialect's reading of any value but `directory`
|
|
75
|
+
// and `either`, so a mutant that blanks it reads the same. It is spelled out
|
|
76
|
+
// because what an unknown value means is spec-core's to change.
|
|
77
|
+
const glob = compileFamilyGlob(pattern, { ...PATH, literal: 'file', caseSensitive: options.ignoreCase !== true });
|
|
78
|
+
return { test: (path) => glob.match(path), size: glob.automaton.kinds.length };
|
|
82
79
|
}
|
|
83
|
-
/** The expression a glob
|
|
80
|
+
/** The expression a glob stood for before 0.9.0, before anchors and flags. */
|
|
84
81
|
function globSource(pattern) {
|
|
85
82
|
let source = '';
|
|
86
83
|
let i = 0;
|
|
@@ -146,83 +143,118 @@ function globSource(pattern) {
|
|
|
146
143
|
function escapeClass(body) {
|
|
147
144
|
return body.replace(/[\\^\]]/g, '\\$&');
|
|
148
145
|
}
|
|
146
|
+
/** Compiles a list of path patterns, or throws naming the one that is not a glob. */
|
|
147
|
+
function pathList(patterns) {
|
|
148
|
+
const parsed = parseGlobList(patterns, PATH);
|
|
149
|
+
if (!parsed.ok)
|
|
150
|
+
throw new Error(`invalid glob ${parsed.error}`);
|
|
151
|
+
return parsed.list;
|
|
152
|
+
}
|
|
149
153
|
/**
|
|
150
154
|
* Builds a matcher from include patterns, honouring `!` negations.
|
|
151
155
|
*
|
|
152
156
|
* Later patterns win, so `docs/**\/*.md` followed by `!docs/drafts/**` reads the
|
|
153
|
-
* way a `.gitignore` does.
|
|
157
|
+
* way a `.gitignore` does. A pattern with no glob syntax in it names a file, or
|
|
158
|
+
* a directory and everything beneath it; `docs/` names only what is beneath.
|
|
154
159
|
*/
|
|
155
160
|
export function createGlobMatcher(patterns) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
const body = negated ? pattern.slice(1) : pattern;
|
|
159
|
-
const normalised = normalisePosix(toPosix(body));
|
|
160
|
-
// A bare directory means everything under it.
|
|
161
|
-
const expanded = isGlob(normalised) ? normalised : `${normalised}/**`;
|
|
162
|
-
return {
|
|
163
|
-
negated,
|
|
164
|
-
exact: isGlob(normalised) ? null : normalised.toLowerCase(),
|
|
165
|
-
expression: compileGlob(expanded),
|
|
166
|
-
direct: compileGlob(normalised),
|
|
167
|
-
};
|
|
168
|
-
});
|
|
169
|
-
return (path) => {
|
|
170
|
-
let included = false;
|
|
171
|
-
for (const entry of compiled) {
|
|
172
|
-
const hit = entry.direct.test(path) ||
|
|
173
|
-
entry.expression.test(path) ||
|
|
174
|
-
(entry.exact !== null && entry.exact === path.toLowerCase());
|
|
175
|
-
if (hit)
|
|
176
|
-
included = !entry.negated;
|
|
177
|
-
}
|
|
178
|
-
return included;
|
|
179
|
-
};
|
|
161
|
+
const list = pathList(patterns);
|
|
162
|
+
return (path) => list.match(path);
|
|
180
163
|
}
|
|
181
164
|
/**
|
|
182
165
|
* Builds a predicate over reference *targets*, not paths.
|
|
183
166
|
*
|
|
184
167
|
* Separate from {@link createGlobMatcher} on purpose. That one is
|
|
185
|
-
* path-oriented:
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* nothing else.
|
|
168
|
+
* path-oriented: a bare name there is a directory and everything under it. A
|
|
169
|
+
* reference target has no such structure - `trap 55` is a name, not a location
|
|
170
|
+
* - so a bare pattern matches it literally and nothing else.
|
|
189
171
|
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
172
|
+
* Case is ignored, on every platform, because a citation's spelling is the
|
|
173
|
+
* author's and not the filesystem's. The fold is simple case mapping, one code
|
|
174
|
+
* point at a time and without a locale, so every host gives the same answer:
|
|
175
|
+
* U+00B5 (micro sign) and U+03BC (mu) stay two targets, as they have been since
|
|
176
|
+
* a Windows-only `i` flag made them one there (ADR-0017).
|
|
193
177
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
178
|
+
* A target is text a document holds rather than a path on this host, and two
|
|
179
|
+
* things follow. A `\` escapes the character after it: there is no Windows
|
|
180
|
+
* separator to allow for. And a `.` or `..` segment is text: in a path the
|
|
181
|
+
* dialect drops the one and refuses the other as climbing out of the root, but
|
|
182
|
+
* `../../notes/gone.md` is a link somebody wrote and may want left alone.
|
|
198
183
|
*/
|
|
199
184
|
export function createReferenceFilter(patterns) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
185
|
+
// A blank pattern reaches the dialect, which refuses it, as every pattern
|
|
186
|
+
// spec-graph reads is refused when it names nothing. It used to be dropped,
|
|
187
|
+
// so an unset variable in `--ignore-ref "$TAGS"` went unnoticed.
|
|
188
|
+
const globs = patterns.map((pattern) => {
|
|
189
|
+
const literalDots = pattern
|
|
190
|
+
.trim()
|
|
191
|
+
.split('/')
|
|
192
|
+
.map((segment) => (segment === '.' ? '\\.' : segment === '..' ? '\\.\\.' : segment))
|
|
193
|
+
.join('/');
|
|
194
|
+
// Every option spelled out, though the dialect reads `{}` the same way
|
|
195
|
+
// today: its defaults are spec-core's to change, and this reading is
|
|
196
|
+
// spec-graph's to keep. A mutant that drops one is equivalent until then.
|
|
197
|
+
const parsed = parseGlob(literalDots, { dialect: 'path', caseSensitive: false, literal: 'file' });
|
|
198
|
+
if (!parsed.ok)
|
|
199
|
+
throw new GlobError(pattern, parsed.error);
|
|
200
|
+
return parsed.glob;
|
|
201
|
+
});
|
|
207
202
|
return (target) => {
|
|
208
|
-
const value = target.trim()
|
|
209
|
-
return
|
|
203
|
+
const value = target.trim();
|
|
204
|
+
return globs.some((glob) => glob.match(value));
|
|
210
205
|
};
|
|
211
206
|
}
|
|
212
|
-
/**
|
|
207
|
+
/**
|
|
208
|
+
* The literal directory a pattern's matches all sit under, or `''` for the root.
|
|
209
|
+
*
|
|
210
|
+
* `{docs,specs}/**` has two, and this is the directory they share; the walk
|
|
211
|
+
* itself starts at each.
|
|
212
|
+
*/
|
|
213
213
|
export function globBase(pattern) {
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
214
|
+
const { glob } = pathList([pattern]).entries[0];
|
|
215
|
+
const bases = glob.bases.map((base) => base.split('/'));
|
|
216
|
+
const shared = bases[0];
|
|
217
|
+
let length = shared.length;
|
|
218
|
+
for (const base of bases) {
|
|
219
|
+
let same = 0;
|
|
220
|
+
// `<=` would read one past the end, where both sides are undefined and
|
|
221
|
+
// equal, and slice the same prefix: an equivalent mutant, left untested.
|
|
222
|
+
while (same < length && base[same] === shared[same])
|
|
223
|
+
same += 1;
|
|
224
|
+
length = same;
|
|
225
|
+
}
|
|
226
|
+
return shared.slice(0, length).join('/');
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Whether a base names directories that exist with exactly that spelling.
|
|
230
|
+
*
|
|
231
|
+
* A walk starts where a pattern's literal prefix points rather than at the
|
|
232
|
+
* root, and on a filesystem that ignores case, `readdir('Docs')` lists `docs`.
|
|
233
|
+
* Every file under it would come back spelled `Docs/...` - a node id, a
|
|
234
|
+
* baseline key - and `Docs/**` would find it on Windows and macOS and nothing
|
|
235
|
+
* on Linux. Asking each parent for the name gives git's answer on every host. A
|
|
236
|
+
* base with an empty segment is rooted at `/`, which is never inside the root.
|
|
237
|
+
*/
|
|
238
|
+
async function spelledAsOnDisk(root, base) {
|
|
239
|
+
if (base.length === 0)
|
|
240
|
+
return true;
|
|
241
|
+
let directory = root;
|
|
242
|
+
for (const segment of base.split('/')) {
|
|
243
|
+
let names;
|
|
244
|
+
try {
|
|
245
|
+
names = await readdir(directory);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Answering `true` here would change nothing a test can see: the walk
|
|
249
|
+
// would then read a directory beneath this one, fail the same way, and
|
|
250
|
+
// find nothing.
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
if (!names.includes(segment))
|
|
254
|
+
return false;
|
|
255
|
+
directory = `${directory}/${segment}`;
|
|
221
256
|
}
|
|
222
|
-
|
|
223
|
-
if (literal.length === segments.length && literal.length > 0)
|
|
224
|
-
literal.pop();
|
|
225
|
-
return literal.join('/');
|
|
257
|
+
return true;
|
|
226
258
|
}
|
|
227
259
|
/**
|
|
228
260
|
* Walks the tree and returns every file matching the patterns.
|
|
@@ -232,7 +264,7 @@ export function globBase(pattern) {
|
|
|
232
264
|
*/
|
|
233
265
|
export async function walkFiles(options) {
|
|
234
266
|
const root = toPosix(options.root).replace(/\/+$/, '');
|
|
235
|
-
const
|
|
267
|
+
const patterns = pathList(options.patterns);
|
|
236
268
|
const maxSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
237
269
|
// Ignores come in two shapes and both are documented. A bare name prunes any
|
|
238
270
|
// directory called that, at any depth, the way a `.gitignore` line does.
|
|
@@ -240,22 +272,25 @@ export async function walkFiles(options) {
|
|
|
240
272
|
// repository-relative path, which is what `--ignore "docs/drafts/**"` means -
|
|
241
273
|
// and treating that as a directory name silently excluded nothing at all.
|
|
242
274
|
const ignores = options.ignore ?? [];
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
const excluded = (path) =>
|
|
250
|
-
// Only walk the directories the patterns can possibly reach
|
|
275
|
+
// The empty pattern is no directory's name, so it goes to the dialect, which
|
|
276
|
+
// refuses it as it refuses an empty include: a pattern that names nothing is
|
|
277
|
+
// a typo or an unset variable, and was an ignore that ignored nothing.
|
|
278
|
+
const bare = (pattern) => pattern.length > 0 && !isGlob(pattern) && !pattern.includes('/');
|
|
279
|
+
const ignoredNames = new Set([...DEFAULT_IGNORED_DIRECTORIES, ...ignores.filter(bare)]);
|
|
280
|
+
const ignoreList = pathList(ignores.filter((pattern) => !bare(pattern)));
|
|
281
|
+
const excluded = (path) => ignoreList.match(path);
|
|
282
|
+
// Only walk the directories the patterns can possibly reach: each pattern's
|
|
283
|
+
// literal prefix, one per brace alternative. A negated pattern adds none,
|
|
284
|
+
// and adding its would change no answer - whatever lies beneath it that no
|
|
285
|
+
// other pattern reaches, no other pattern matches either - so that mutant is
|
|
286
|
+
// equivalent. No positive pattern at all walks nothing, which is the answer.
|
|
251
287
|
const bases = new Set();
|
|
252
|
-
for (const
|
|
253
|
-
if (
|
|
288
|
+
for (const entry of patterns.entries) {
|
|
289
|
+
if (entry.negated)
|
|
254
290
|
continue;
|
|
255
|
-
bases
|
|
291
|
+
for (const base of entry.glob.bases)
|
|
292
|
+
bases.add(base);
|
|
256
293
|
}
|
|
257
|
-
if (bases.size === 0)
|
|
258
|
-
bases.add('');
|
|
259
294
|
// Drop any base already contained in another: walking it again would only
|
|
260
295
|
// repeat work. The empty base is the repository root, which contains
|
|
261
296
|
// everything, so when it is present it is the only root worth walking.
|
|
@@ -296,7 +331,7 @@ export async function walkFiles(options) {
|
|
|
296
331
|
await walk(child);
|
|
297
332
|
continue;
|
|
298
333
|
}
|
|
299
|
-
if (!
|
|
334
|
+
if (!patterns.match(child) || excluded(child) || info.size > maxSize)
|
|
300
335
|
continue;
|
|
301
336
|
out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });
|
|
302
337
|
}
|
|
@@ -307,7 +342,7 @@ export async function walkFiles(options) {
|
|
|
307
342
|
}
|
|
308
343
|
if (!entry.isFile())
|
|
309
344
|
continue;
|
|
310
|
-
if (!
|
|
345
|
+
if (!patterns.match(child) || excluded(child))
|
|
311
346
|
continue;
|
|
312
347
|
try {
|
|
313
348
|
const info = await stat(`${root}/${child}`);
|
|
@@ -321,7 +356,8 @@ export async function walkFiles(options) {
|
|
|
321
356
|
}
|
|
322
357
|
};
|
|
323
358
|
for (const base of roots)
|
|
324
|
-
await
|
|
359
|
+
if (await spelledAsOnDisk(root, base))
|
|
360
|
+
await walk(base);
|
|
325
361
|
return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
326
362
|
}
|
|
327
363
|
/**
|
package/dist/glob.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pattern matching and directory walking.\n *\n * A dependency-free glob, because pulling in a matcher for `docs/**\\/*.md` would\n * be the single largest thing in the package. The supported syntax is the\n * portion people actually type - `**`, `*`, `?`, `{a,b}`, `[abc]`, and a leading\n * `!` for negation - and everything is matched against repository-relative\n * POSIX paths so results do not depend on the host platform.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\n\nimport { isAbsolutePath, joinPosix, normalisePosix, toPosix } from './paths.js';\nimport { compilePattern, type Matcher } from './regex.js';\n\n/** Directories skipped unless a pattern explicitly names them. */\nexport const DEFAULT_IGNORED_DIRECTORIES: readonly string[] = Object.freeze([\n '.git',\n '.hg',\n '.svn',\n '.cache',\n '.next',\n '.nuxt',\n '.turbo',\n '.venv',\n 'node_modules',\n 'bower_components',\n 'vendor',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'coverage',\n '__pycache__',\n]);\n\n/** Files larger than this are skipped: a specification is not a binary. */\nexport const MAX_FILE_SIZE = 4 * 1024 * 1024;\n\nexport interface WalkedFile {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Absolute host path, for reading. */\n readonly absolute: string;\n readonly size: number;\n}\n\nexport interface WalkOptions {\n readonly root: string;\n readonly patterns: readonly string[];\n readonly ignore?: readonly string[] | undefined;\n readonly maxFileSize?: number | undefined;\n /** Follow directory symlinks. Off by default: cycles are real. */\n readonly followSymlinks?: boolean | undefined;\n}\n\n/** True when the string contains glob syntax rather than being a literal path. */\nexport function isGlob(pattern: string): boolean {\n return /[*?[\\]{}]/.test(pattern);\n}\n\n/**\n * Compiles a glob to an anchored regular expression.\n *\n * `**` crosses directory separators; `*` and `?` do not. A trailing `/` or a\n * bare directory name matches everything beneath it, which is what people mean\n * when they write `--ignore drafts`.\n *\n * Nothing in the pipeline matches with this any more - see {@link compileGlob},\n * which gives the same answers without the backtracking. It stays for callers\n * who want a `RegExp`, and as the oracle the tests hold the automaton to.\n */\nexport function globToRegExp(pattern: string): RegExp {\n return new RegExp(`^${globSource(pattern)}$`, process.platform === 'win32' ? 'i' : '');\n}\n\nexport interface GlobOptions {\n /** Fold case. Defaults to the host's convention: on for Windows, off elsewhere. */\n readonly ignoreCase?: boolean | undefined;\n}\n\n/**\n * Compiles a glob to the automaton every matcher here uses.\n *\n * A glob has no nested quantifiers, and ADR-0017 took that to mean there was no\n * backtracking hazard in handing one to `RegExp`. Nobody had timed it. Each\n * `*` is a `[^/]*`, and against a subject that fails to match, a backtracking\n * engine tries every way of dividing it between them - the subject's length to\n * the power of the stars:\n *\n * ```text\n * RegExp here\n * **\\/*-*-*-*.md a 643-character hyphenated file name 2.3s 0.2ms\n * *-*-*-x a 10,000-character reference target 120s 0.9ms\n * ```\n *\n * The first is bounded by the filesystem, which caps a name at 255 bytes. The\n * second is not: `--ignore-ref` patterns are matched against targets read out\n * of documents, and a document is whatever somebody wrote.\n */\nexport function compileGlob(pattern: string, options: GlobOptions = {}): Matcher {\n const source = globSource(pattern);\n try {\n return compilePattern(`^${source}$`, { ignoreCase: options.ignoreCase ?? process.platform === 'win32' });\n } catch (error) {\n // The message describes an expression the user never wrote, so it is told\n // against the glob they did.\n throw new Error(`invalid glob \"${pattern}\": ${(error as Error).message}`);\n }\n}\n\n/** The expression a glob stands for, before anchors and flags. */\nfunction globSource(pattern: string): string {\n let source = '';\n let i = 0;\n const braces: number[] = [];\n\n while (i < pattern.length) {\n const ch = pattern[i] as string;\n\n if (ch === '*') {\n const doubled = pattern[i + 1] === '*';\n if (doubled) {\n const slashAfter = pattern[i + 2] === '/';\n // `a/**/b` must also match `a/b`, so the separator is folded in.\n source += slashAfter ? '(?:.*/)?' : '.*';\n i += slashAfter ? 3 : 2;\n } else {\n source += '[^/]*';\n i += 1;\n }\n continue;\n }\n\n if (ch === '?') {\n source += '[^/]';\n i += 1;\n continue;\n }\n\n if (ch === '[') {\n const close = pattern.indexOf(']', i + 1);\n if (close === -1) {\n source += '\\\\[';\n i += 1;\n continue;\n }\n const body = pattern.slice(i + 1, close);\n source += `[${body.startsWith('!') ? `^${escapeClass(body.slice(1))}` : escapeClass(body)}]`;\n i = close + 1;\n continue;\n }\n\n if (ch === '{') {\n braces.push(source.length);\n source += '(?:';\n i += 1;\n continue;\n }\n if (ch === '}' && braces.length > 0) {\n braces.pop();\n source += ')';\n i += 1;\n continue;\n }\n if (ch === ',' && braces.length > 0) {\n source += '|';\n i += 1;\n continue;\n }\n\n source += ch.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n i += 1;\n }\n\n // Otherwise reported as an unmatched parenthesis, in a glob that has none.\n if (braces.length > 0) throw new Error(`invalid glob \"${pattern}\": unclosed \"{\"`);\n return source;\n}\n\nfunction escapeClass(body: string): string {\n return body.replace(/[\\\\^\\]]/g, '\\\\$&');\n}\n\nexport interface GlobMatcher {\n (path: string): boolean;\n}\n\n/**\n * Builds a matcher from include patterns, honouring `!` negations.\n *\n * Later patterns win, so `docs/**\\/*.md` followed by `!docs/drafts/**` reads the\n * way a `.gitignore` does.\n */\nexport function createGlobMatcher(patterns: readonly string[]): GlobMatcher {\n const compiled = patterns.map((pattern) => {\n const negated = pattern.startsWith('!');\n const body = negated ? pattern.slice(1) : pattern;\n const normalised = normalisePosix(toPosix(body));\n // A bare directory means everything under it.\n const expanded = isGlob(normalised) ? normalised : `${normalised}/**`;\n return {\n negated,\n exact: isGlob(normalised) ? null : normalised.toLowerCase(),\n expression: compileGlob(expanded),\n direct: compileGlob(normalised),\n };\n });\n\n return (path: string): boolean => {\n let included = false;\n for (const entry of compiled) {\n const hit =\n entry.direct.test(path) ||\n entry.expression.test(path) ||\n (entry.exact !== null && entry.exact === path.toLowerCase());\n if (hit) included = !entry.negated;\n }\n return included;\n };\n}\n\n/**\n * Builds a predicate over reference *targets*, not paths.\n *\n * Separate from {@link createGlobMatcher} on purpose. That one is\n * path-oriented: it expands a bare name to `name/**` because a directory means\n * everything under it. A reference target has no such structure - `trap 55` is\n * a name, not a location - so a bare pattern must match it literally and\n * nothing else.\n *\n * Matching is case-insensitive on every platform. Path matching inherits the\n * host filesystem's case rules, which is right for paths and wrong here: a\n * repository's findings must not depend on which machine ran the check.\n *\n * Both sides are lower-cased, and that is the whole of the case rule. Folding\n * as well used to happen on Windows only, where it made U+00B5 (micro sign)\n * and U+03BC (mu) the same target - one machine's answer, which is what this\n * function exists not to give.\n */\nexport function createReferenceFilter(patterns: readonly string[]): (target: string) => boolean {\n if (patterns.length === 0) return () => false;\n const compiled = patterns\n .filter((pattern) => pattern.trim().length > 0)\n .map((pattern) => compileGlob(pattern.trim().toLowerCase(), { ignoreCase: false }));\n if (compiled.length === 0) return () => false;\n return (target: string): boolean => {\n const value = target.trim().toLowerCase();\n return compiled.some((expression) => expression.test(value));\n };\n}\n\n/** The literal directory prefix of a pattern, used to avoid walking the world. */\nexport function globBase(pattern: string): string {\n const normalised = normalisePosix(toPosix(pattern.startsWith('!') ? pattern.slice(1) : pattern));\n const segments = normalised.split('/');\n const literal: string[] = [];\n for (const segment of segments) {\n if (isGlob(segment)) break;\n literal.push(segment);\n }\n // The last literal segment may be the file itself rather than a directory.\n if (literal.length === segments.length && literal.length > 0) literal.pop();\n return literal.join('/');\n}\n\n/**\n * Walks the tree and returns every file matching the patterns.\n *\n * Directories are pruned as early as possible: a repository with a 2 GB\n * `node_modules` should cost nothing to scan.\n */\nexport async function walkFiles(options: WalkOptions): Promise<WalkedFile[]> {\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const matcher = createGlobMatcher(options.patterns);\n const maxSize = options.maxFileSize ?? MAX_FILE_SIZE;\n\n // Ignores come in two shapes and both are documented. A bare name prunes any\n // directory called that, at any depth, the way a `.gitignore` line does.\n // Anything carrying a separator or glob syntax is matched against the\n // repository-relative path, which is what `--ignore \"docs/drafts/**\"` means -\n // and treating that as a directory name silently excluded nothing at all.\n const ignores = options.ignore ?? [];\n const ignoredNames = new Set([\n ...DEFAULT_IGNORED_DIRECTORIES,\n ...ignores.filter((pattern) => !isGlob(pattern) && !pattern.includes('/')),\n ]);\n const pathIgnores = ignores.filter((pattern) => isGlob(pattern) || pattern.includes('/'));\n const ignoreMatcher = pathIgnores.length > 0 ? createGlobMatcher(pathIgnores) : null;\n const excluded = (path: string): boolean => ignoreMatcher !== null && ignoreMatcher(path);\n\n // Only walk the directories the patterns can possibly reach.\n const bases = new Set<string>();\n for (const pattern of options.patterns) {\n if (pattern.startsWith('!')) continue;\n bases.add(globBase(pattern));\n }\n if (bases.size === 0) bases.add('');\n // Drop any base already contained in another: walking it again would only\n // repeat work. The empty base is the repository root, which contains\n // everything, so when it is present it is the only root worth walking.\n const roots = bases.has('')\n ? ['']\n : [...bases].filter((base) => ![...bases].some((other) => other !== base && base.startsWith(`${other}/`)));\n\n const out = new Map<string, WalkedFile>();\n const visited = new Set<string>();\n\n const walk = async (relative: string): Promise<void> => {\n const absolute = relative.length === 0 ? root : `${root}/${relative}`;\n if (visited.has(absolute)) return;\n visited.add(absolute);\n\n let entries;\n try {\n entries = await readdir(absolute, { withFileTypes: true });\n } catch {\n // An unreadable directory is not a reason to fail the whole run.\n return;\n }\n\n for (const entry of entries) {\n const child = relative.length === 0 ? entry.name : `${relative}/${entry.name}`;\n\n if (entry.isDirectory()) {\n // Pruning a whole subtree is an optimisation; a pattern that only\n // matches the files inside it is still honoured when they are filtered.\n if (ignoredNames.has(entry.name) || excluded(child)) continue;\n await walk(child);\n continue;\n }\n\n if (entry.isSymbolicLink()) {\n if (!options.followSymlinks) continue;\n try {\n const info = await stat(`${root}/${child}`);\n if (info.isDirectory()) {\n await walk(child);\n continue;\n }\n if (!matcher(child) || excluded(child) || info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n continue;\n }\n\n if (!entry.isFile()) continue;\n if (!matcher(child) || excluded(child)) continue;\n\n try {\n const info = await stat(`${root}/${child}`);\n if (info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n }\n };\n\n for (const base of roots) await walk(base);\n\n return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Resolves a repository-relative path against the root, in POSIX form.\n *\n * An absolute path already names one place, and joining it under the root names\n * another: `--baseline /tmp/b.json` read `<root>/tmp/b.json`, found nothing\n * there, and accepted nothing without saying so.\n */\nexport function underRoot(root: string, relative: string): string {\n if (isAbsolutePath(relative)) return toPosix(relative);\n return joinPosix(toPosix(root), relative);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pattern matching and directory walking.\n *\n * The patterns are the `path` dialect every spec-* tool reads, from spec-core:\n * `**`, `*`, `?`, `{a,b}`, `[abc]` and `[!a-z]`, matched against whole\n * repository-relative POSIX paths by an automaton that cannot backtrack, with\n * case respected on every host (ADR-0022). What stays here is spec-graph's own:\n * how a list with `!` entries in it reads, which directories a walk never\n * enters, and how a reference target - which is not a path - is matched.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\n\nimport { isAbsolutePath, joinPosix, toPosix } from './paths.js';\nimport {\n compileGlob as compileFamilyGlob,\n GlobError,\n isGlobSyntax,\n parseGlob,\n parseGlobList,\n type Glob,\n type GlobList,\n type GlobOptions as FamilyGlobOptions,\n type RegexMatcher,\n} from './vendor/spec-core/pattern/index.js';\n\n/** Directories skipped unless a pattern explicitly names them. */\nexport const DEFAULT_IGNORED_DIRECTORIES: readonly string[] = Object.freeze([\n '.git',\n '.hg',\n '.svn',\n '.cache',\n '.next',\n '.nuxt',\n '.turbo',\n '.venv',\n 'node_modules',\n 'bower_components',\n 'vendor',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'coverage',\n '__pycache__',\n]);\n\n/** Files larger than this are skipped: a specification is not a binary. */\nexport const MAX_FILE_SIZE = 4 * 1024 * 1024;\n\nexport interface WalkedFile {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Absolute host path, for reading. */\n readonly absolute: string;\n readonly size: number;\n}\n\nexport interface WalkOptions {\n readonly root: string;\n readonly patterns: readonly string[];\n readonly ignore?: readonly string[] | undefined;\n readonly maxFileSize?: number | undefined;\n /** Follow directory symlinks. Off by default: cycles are real. */\n readonly followSymlinks?: boolean | undefined;\n}\n\n/**\n * How a path pattern is read everywhere in spec-graph.\n *\n * A literal names a file or a directory and everything beneath it, which is\n * what `--ignore docs/drafts` has always meant. A `\\` is a separator, because a\n * pattern typed on a Windows shell arrives with them and spec-graph has always\n * read them that way; it cannot also be an escape.\n */\nconst PATH: FamilyGlobOptions = { dialect: 'path', caseSensitive: true, literal: 'either', backslash: 'separator' };\n\n/** True when the string contains glob syntax rather than being a literal path. */\nexport function isGlob(pattern: string): boolean {\n return isGlobSyntax(pattern);\n}\n\n/**\n * Compiles a glob to an anchored regular expression, in the dialect spec-graph\n * read before it adopted spec-core's.\n *\n * @deprecated Nothing matches with this. It stays for callers who want a\n * `RegExp`, and as the record of the old reading that `tests/glob.test.ts`\n * holds every difference against, each one named in ADR-0022. It no longer\n * folds case on Windows alone: an answer must not depend on the host.\n */\nexport function globToRegExp(pattern: string): RegExp {\n return new RegExp(`^${globSource(pattern)}$`);\n}\n\nexport interface GlobOptions {\n /** Fold case. Off unless asked for, on every host. */\n readonly ignoreCase?: boolean | undefined;\n}\n\n/**\n * Compiles one glob to a matcher over whole repository-relative paths.\n *\n * A pattern with no glob syntax names that path and nothing beneath it; a bare\n * directory meaning its contents is a reading of {@link createGlobMatcher}'s.\n * Throws a `GlobError` that names the glob as it was written.\n *\n * Matching is linear in the path whatever the pattern. It was not always: a\n * glob's stars are not nested, but each is a `[^/]*` to `RegExp`, and a subject\n * that fails is divided between them every way there is - `*-*-*-x` took two\n * minutes over a 10,000-character reference target (ADR-0017).\n */\nexport function compileGlob(pattern: string, options: GlobOptions = {}): RegexMatcher {\n // `literal: 'file'` is the dialect's reading of any value but `directory`\n // and `either`, so a mutant that blanks it reads the same. It is spelled out\n // because what an unknown value means is spec-core's to change.\n const glob = compileFamilyGlob(pattern, { ...PATH, literal: 'file', caseSensitive: options.ignoreCase !== true });\n return { test: (path) => glob.match(path), size: glob.automaton.kinds.length };\n}\n\n/** The expression a glob stood for before 0.9.0, before anchors and flags. */\nfunction globSource(pattern: string): string {\n let source = '';\n let i = 0;\n const braces: number[] = [];\n\n while (i < pattern.length) {\n const ch = pattern[i] as string;\n\n if (ch === '*') {\n const doubled = pattern[i + 1] === '*';\n if (doubled) {\n const slashAfter = pattern[i + 2] === '/';\n // `a/**/b` must also match `a/b`, so the separator is folded in.\n source += slashAfter ? '(?:.*/)?' : '.*';\n i += slashAfter ? 3 : 2;\n } else {\n source += '[^/]*';\n i += 1;\n }\n continue;\n }\n\n if (ch === '?') {\n source += '[^/]';\n i += 1;\n continue;\n }\n\n if (ch === '[') {\n const close = pattern.indexOf(']', i + 1);\n if (close === -1) {\n source += '\\\\[';\n i += 1;\n continue;\n }\n const body = pattern.slice(i + 1, close);\n source += `[${body.startsWith('!') ? `^${escapeClass(body.slice(1))}` : escapeClass(body)}]`;\n i = close + 1;\n continue;\n }\n\n if (ch === '{') {\n braces.push(source.length);\n source += '(?:';\n i += 1;\n continue;\n }\n if (ch === '}' && braces.length > 0) {\n braces.pop();\n source += ')';\n i += 1;\n continue;\n }\n if (ch === ',' && braces.length > 0) {\n source += '|';\n i += 1;\n continue;\n }\n\n source += ch.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n i += 1;\n }\n\n // Otherwise reported as an unmatched parenthesis, in a glob that has none.\n if (braces.length > 0) throw new Error(`invalid glob \"${pattern}\": unclosed \"{\"`);\n return source;\n}\n\nfunction escapeClass(body: string): string {\n return body.replace(/[\\\\^\\]]/g, '\\\\$&');\n}\n\nexport interface GlobMatcher {\n (path: string): boolean;\n}\n\n/** Compiles a list of path patterns, or throws naming the one that is not a glob. */\nfunction pathList(patterns: readonly string[]): GlobList {\n const parsed = parseGlobList(patterns, PATH);\n if (!parsed.ok) throw new Error(`invalid glob ${parsed.error}`);\n return parsed.list;\n}\n\n/**\n * Builds a matcher from include patterns, honouring `!` negations.\n *\n * Later patterns win, so `docs/**\\/*.md` followed by `!docs/drafts/**` reads the\n * way a `.gitignore` does. A pattern with no glob syntax in it names a file, or\n * a directory and everything beneath it; `docs/` names only what is beneath.\n */\nexport function createGlobMatcher(patterns: readonly string[]): GlobMatcher {\n const list = pathList(patterns);\n return (path: string): boolean => list.match(path);\n}\n\n/**\n * Builds a predicate over reference *targets*, not paths.\n *\n * Separate from {@link createGlobMatcher} on purpose. That one is\n * path-oriented: a bare name there is a directory and everything under it. A\n * reference target has no such structure - `trap 55` is a name, not a location\n * - so a bare pattern matches it literally and nothing else.\n *\n * Case is ignored, on every platform, because a citation's spelling is the\n * author's and not the filesystem's. The fold is simple case mapping, one code\n * point at a time and without a locale, so every host gives the same answer:\n * U+00B5 (micro sign) and U+03BC (mu) stay two targets, as they have been since\n * a Windows-only `i` flag made them one there (ADR-0017).\n *\n * A target is text a document holds rather than a path on this host, and two\n * things follow. A `\\` escapes the character after it: there is no Windows\n * separator to allow for. And a `.` or `..` segment is text: in a path the\n * dialect drops the one and refuses the other as climbing out of the root, but\n * `../../notes/gone.md` is a link somebody wrote and may want left alone.\n */\nexport function createReferenceFilter(patterns: readonly string[]): (target: string) => boolean {\n // A blank pattern reaches the dialect, which refuses it, as every pattern\n // spec-graph reads is refused when it names nothing. It used to be dropped,\n // so an unset variable in `--ignore-ref \"$TAGS\"` went unnoticed.\n const globs: Glob[] = patterns.map((pattern) => {\n const literalDots = pattern\n .trim()\n .split('/')\n .map((segment) => (segment === '.' ? '\\\\.' : segment === '..' ? '\\\\.\\\\.' : segment))\n .join('/');\n // Every option spelled out, though the dialect reads `{}` the same way\n // today: its defaults are spec-core's to change, and this reading is\n // spec-graph's to keep. A mutant that drops one is equivalent until then.\n const parsed = parseGlob(literalDots, { dialect: 'path', caseSensitive: false, literal: 'file' });\n if (!parsed.ok) throw new GlobError(pattern, parsed.error);\n return parsed.glob;\n });\n return (target: string): boolean => {\n const value = target.trim();\n return globs.some((glob) => glob.match(value));\n };\n}\n\n/**\n * The literal directory a pattern's matches all sit under, or `''` for the root.\n *\n * `{docs,specs}/**` has two, and this is the directory they share; the walk\n * itself starts at each.\n */\nexport function globBase(pattern: string): string {\n const { glob } = pathList([pattern]).entries[0] as { readonly glob: Glob };\n const bases = glob.bases.map((base) => base.split('/'));\n const shared = bases[0] as string[];\n let length = shared.length;\n for (const base of bases) {\n let same = 0;\n // `<=` would read one past the end, where both sides are undefined and\n // equal, and slice the same prefix: an equivalent mutant, left untested.\n while (same < length && base[same] === shared[same]) same += 1;\n length = same;\n }\n return shared.slice(0, length).join('/');\n}\n\n/**\n * Whether a base names directories that exist with exactly that spelling.\n *\n * A walk starts where a pattern's literal prefix points rather than at the\n * root, and on a filesystem that ignores case, `readdir('Docs')` lists `docs`.\n * Every file under it would come back spelled `Docs/...` - a node id, a\n * baseline key - and `Docs/**` would find it on Windows and macOS and nothing\n * on Linux. Asking each parent for the name gives git's answer on every host. A\n * base with an empty segment is rooted at `/`, which is never inside the root.\n */\nasync function spelledAsOnDisk(root: string, base: string): Promise<boolean> {\n if (base.length === 0) return true;\n let directory = root;\n for (const segment of base.split('/')) {\n let names: string[];\n try {\n names = await readdir(directory);\n } catch {\n // Answering `true` here would change nothing a test can see: the walk\n // would then read a directory beneath this one, fail the same way, and\n // find nothing.\n return false;\n }\n if (!names.includes(segment)) return false;\n directory = `${directory}/${segment}`;\n }\n return true;\n}\n\n/**\n * Walks the tree and returns every file matching the patterns.\n *\n * Directories are pruned as early as possible: a repository with a 2 GB\n * `node_modules` should cost nothing to scan.\n */\nexport async function walkFiles(options: WalkOptions): Promise<WalkedFile[]> {\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const patterns = pathList(options.patterns);\n const maxSize = options.maxFileSize ?? MAX_FILE_SIZE;\n\n // Ignores come in two shapes and both are documented. A bare name prunes any\n // directory called that, at any depth, the way a `.gitignore` line does.\n // Anything carrying a separator or glob syntax is matched against the\n // repository-relative path, which is what `--ignore \"docs/drafts/**\"` means -\n // and treating that as a directory name silently excluded nothing at all.\n const ignores = options.ignore ?? [];\n // The empty pattern is no directory's name, so it goes to the dialect, which\n // refuses it as it refuses an empty include: a pattern that names nothing is\n // a typo or an unset variable, and was an ignore that ignored nothing.\n const bare = (pattern: string): boolean => pattern.length > 0 && !isGlob(pattern) && !pattern.includes('/');\n const ignoredNames = new Set([...DEFAULT_IGNORED_DIRECTORIES, ...ignores.filter(bare)]);\n const ignoreList = pathList(ignores.filter((pattern) => !bare(pattern)));\n const excluded = (path: string): boolean => ignoreList.match(path);\n\n // Only walk the directories the patterns can possibly reach: each pattern's\n // literal prefix, one per brace alternative. A negated pattern adds none,\n // and adding its would change no answer - whatever lies beneath it that no\n // other pattern reaches, no other pattern matches either - so that mutant is\n // equivalent. No positive pattern at all walks nothing, which is the answer.\n const bases = new Set<string>();\n for (const entry of patterns.entries) {\n if (entry.negated) continue;\n for (const base of entry.glob.bases) bases.add(base);\n }\n // Drop any base already contained in another: walking it again would only\n // repeat work. The empty base is the repository root, which contains\n // everything, so when it is present it is the only root worth walking.\n const roots = bases.has('')\n ? ['']\n : [...bases].filter((base) => ![...bases].some((other) => other !== base && base.startsWith(`${other}/`)));\n\n const out = new Map<string, WalkedFile>();\n const visited = new Set<string>();\n\n const walk = async (relative: string): Promise<void> => {\n const absolute = relative.length === 0 ? root : `${root}/${relative}`;\n if (visited.has(absolute)) return;\n visited.add(absolute);\n\n let entries;\n try {\n entries = await readdir(absolute, { withFileTypes: true });\n } catch {\n // An unreadable directory is not a reason to fail the whole run.\n return;\n }\n\n for (const entry of entries) {\n const child = relative.length === 0 ? entry.name : `${relative}/${entry.name}`;\n\n if (entry.isDirectory()) {\n // Pruning a whole subtree is an optimisation; a pattern that only\n // matches the files inside it is still honoured when they are filtered.\n if (ignoredNames.has(entry.name) || excluded(child)) continue;\n await walk(child);\n continue;\n }\n\n if (entry.isSymbolicLink()) {\n if (!options.followSymlinks) continue;\n try {\n const info = await stat(`${root}/${child}`);\n if (info.isDirectory()) {\n await walk(child);\n continue;\n }\n if (!patterns.match(child) || excluded(child) || info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n continue;\n }\n\n if (!entry.isFile()) continue;\n if (!patterns.match(child) || excluded(child)) continue;\n\n try {\n const info = await stat(`${root}/${child}`);\n if (info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n }\n };\n\n for (const base of roots) if (await spelledAsOnDisk(root, base)) await walk(base);\n\n return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Resolves a repository-relative path against the root, in POSIX form.\n *\n * An absolute path already names one place, and joining it under the root names\n * another: `--baseline /tmp/b.json` read `<root>/tmp/b.json`, found nothing\n * there, and accepted nothing without saying so.\n */\nexport function underRoot(root: string, relative: string): string {\n if (isAbsolutePath(relative)) return toPosix(relative);\n return joinPosix(toPosix(root), relative);\n}\n"]}
|