cspell-glob 8.9.0 → 8.10.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/dist/{esm/GlobMatcher.d.ts → GlobMatcher.d.ts} +9 -2
- package/dist/{esm/GlobMatcher.js → GlobMatcher.js} +87 -32
- package/dist/{esm/GlobMatcherTypes.d.ts → GlobMatcherTypes.d.ts} +8 -0
- package/dist/{esm/globHelper.d.ts → globHelper.d.ts} +21 -9
- package/dist/globHelper.js +430 -0
- package/package.json +14 -9
- package/dist/esm/globHelper.js +0 -258
- /package/dist/{esm/GlobMatcherTypes.js → GlobMatcherTypes.js} +0 -0
- /package/dist/{esm/index.d.ts → index.d.ts} +0 -0
- /package/dist/{esm/index.js → index.js} +0 -0
|
@@ -65,22 +65,29 @@ export declare class GlobMatcher {
|
|
|
65
65
|
readonly path: PathInterface;
|
|
66
66
|
readonly patterns: GlobPatternWithRoot[];
|
|
67
67
|
readonly patternsNormalizedToRoot: GlobPatternNormalized[];
|
|
68
|
+
/**
|
|
69
|
+
* path or href of the root directory.
|
|
70
|
+
*/
|
|
68
71
|
readonly root: string;
|
|
69
72
|
readonly dot: boolean;
|
|
70
73
|
readonly options: NormalizedGlobMatchOptions;
|
|
74
|
+
/**
|
|
75
|
+
* Instance ID
|
|
76
|
+
*/
|
|
77
|
+
readonly id: number;
|
|
71
78
|
/**
|
|
72
79
|
* Construct a `.gitignore` emulator
|
|
73
80
|
* @param patterns - the contents of a `.gitignore` style file or an array of individual glob rules.
|
|
74
81
|
* @param root - the working directory
|
|
75
82
|
*/
|
|
76
|
-
constructor(patterns: GlobPattern | GlobPattern[], root?: string, nodePath?: PathInterface);
|
|
83
|
+
constructor(patterns: GlobPattern | GlobPattern[], root?: string | URL, nodePath?: PathInterface);
|
|
77
84
|
/**
|
|
78
85
|
* Construct a `.gitignore` emulator
|
|
79
86
|
* @param patterns - the contents of a `.gitignore` style file or an array of individual glob rules.
|
|
80
87
|
* @param options - to set the root and other options
|
|
81
88
|
*/
|
|
82
89
|
constructor(patterns: GlobPattern | GlobPattern[], options?: GlobMatchOptions);
|
|
83
|
-
constructor(patterns: GlobPattern | GlobPattern[], rootOrOptions?: string | GlobMatchOptions);
|
|
90
|
+
constructor(patterns: GlobPattern | GlobPattern[], rootOrOptions?: string | URL | GlobMatchOptions);
|
|
84
91
|
/**
|
|
85
92
|
* Check to see if a filename matches any of the globs.
|
|
86
93
|
* If filename is relative, it is considered relative to the root.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as Path from 'node:path';
|
|
2
|
+
import { FileUrlBuilder } from '@cspell/url';
|
|
2
3
|
import mm from 'micromatch';
|
|
3
|
-
import {
|
|
4
|
+
import { GlobPatterns, isRelativeValueNested, normalizeGlobPatterns, normalizeGlobToRoot } from './globHelper.js';
|
|
5
|
+
const traceMode = false;
|
|
6
|
+
let idGlobMatcher = 0;
|
|
4
7
|
export class GlobMatcher {
|
|
5
8
|
/**
|
|
6
9
|
* @param filename full path of file to match against.
|
|
@@ -10,17 +13,34 @@ export class GlobMatcher {
|
|
|
10
13
|
path;
|
|
11
14
|
patterns;
|
|
12
15
|
patternsNormalizedToRoot;
|
|
16
|
+
/**
|
|
17
|
+
* path or href of the root directory.
|
|
18
|
+
*/
|
|
13
19
|
root;
|
|
14
20
|
dot;
|
|
15
21
|
options;
|
|
22
|
+
/**
|
|
23
|
+
* Instance ID
|
|
24
|
+
*/
|
|
25
|
+
id;
|
|
16
26
|
constructor(patterns, rootOrOptions, _nodePath) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
27
|
+
this.id = idGlobMatcher++;
|
|
28
|
+
// traceMode && console.warn('GlobMatcher(%d)', this.id, new Error('trace'));
|
|
29
|
+
const options = typeof rootOrOptions === 'string' || rootOrOptions instanceof URL
|
|
30
|
+
? { root: rootOrOptions.toString() }
|
|
31
|
+
: rootOrOptions ?? {};
|
|
32
|
+
const mode = options.mode ?? 'exclude';
|
|
20
33
|
const isExcludeMode = mode !== 'include';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
34
|
+
const nodePath = options.nodePath ?? _nodePath ?? Path;
|
|
35
|
+
this.path = nodePath;
|
|
36
|
+
const cwd = options.cwd ?? nodePath.resolve();
|
|
37
|
+
const dot = options.dot ?? isExcludeMode;
|
|
38
|
+
const nested = options.nested ?? isExcludeMode;
|
|
39
|
+
const nobrace = options.nobrace;
|
|
40
|
+
const root = options.root ?? nodePath.resolve();
|
|
41
|
+
const builder = new FileUrlBuilder({ path: nodePath });
|
|
42
|
+
const rootURL = builder.toFileDirURL(root);
|
|
43
|
+
const normalizedRoot = builder.urlToFilePathOrHref(rootURL);
|
|
24
44
|
this.options = { root: normalizedRoot, dot, nodePath, nested, mode, nobrace, cwd };
|
|
25
45
|
patterns = Array.isArray(patterns)
|
|
26
46
|
? patterns
|
|
@@ -31,12 +51,11 @@ export class GlobMatcher {
|
|
|
31
51
|
this.patternsNormalizedToRoot = globPatterns
|
|
32
52
|
.map((g) => normalizeGlobToRoot(g, normalizedRoot, nodePath))
|
|
33
53
|
// Only keep globs that do not match the root when using exclude mode.
|
|
34
|
-
.filter((g) =>
|
|
54
|
+
.filter((g) => builder.relative(builder.toFileDirURL(g.root), rootURL) === '');
|
|
35
55
|
this.patterns = globPatterns;
|
|
36
56
|
this.root = normalizedRoot;
|
|
37
|
-
this.path = nodePath;
|
|
38
57
|
this.dot = dot;
|
|
39
|
-
this.matchEx = buildMatcherFn(this.patterns, this.options);
|
|
58
|
+
this.matchEx = buildMatcherFn(this.id, this.patterns, this.options);
|
|
40
59
|
}
|
|
41
60
|
/**
|
|
42
61
|
* Check to see if a filename matches any of the globs.
|
|
@@ -62,9 +81,12 @@ export class GlobMatcher {
|
|
|
62
81
|
* @param options - defines root and other options
|
|
63
82
|
* @returns a function given a filename returns true if it matches.
|
|
64
83
|
*/
|
|
65
|
-
function buildMatcherFn(patterns, options) {
|
|
66
|
-
|
|
84
|
+
function buildMatcherFn(_id, patterns, options) {
|
|
85
|
+
// outputBuildMatcherFnPerfData(_id, patterns, options);
|
|
86
|
+
const { nodePath, dot, nobrace } = options;
|
|
87
|
+
const builder = new FileUrlBuilder({ path: nodePath });
|
|
67
88
|
const makeReOptions = { dot, nobrace };
|
|
89
|
+
const suffixDir = GlobPatterns.suffixDir;
|
|
68
90
|
const rules = patterns
|
|
69
91
|
.map((pattern, index) => ({ pattern, index }))
|
|
70
92
|
.filter((r) => !!r.pattern.glob)
|
|
@@ -74,29 +96,43 @@ function buildMatcherFn(patterns, options) {
|
|
|
74
96
|
const glob = pattern.glob.replace(/^!/, '');
|
|
75
97
|
const isNeg = (matchNeg && matchNeg[0].length & 1 && true) || false;
|
|
76
98
|
const reg = mm.makeRe(glob, makeReOptions);
|
|
77
|
-
const fn = (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
99
|
+
const fn = pattern.glob.endsWith(suffixDir)
|
|
100
|
+
? (filename) => {
|
|
101
|
+
// Note: this is a hack to get around the limitations of globs.
|
|
102
|
+
// We want to match a filename with a trailing slash, but micromatch does not support it.
|
|
103
|
+
// So it is necessary to pretend that the filename has a space at the end.
|
|
104
|
+
return reg.test(filename) || (filename.endsWith('/') && reg.test(filename + ' '));
|
|
105
|
+
}
|
|
106
|
+
: (filename) => {
|
|
107
|
+
return reg.test(filename);
|
|
108
|
+
};
|
|
81
109
|
return { pattern, index, isNeg, fn, reg };
|
|
82
110
|
});
|
|
83
111
|
const negRules = rules.filter((r) => r.isNeg);
|
|
84
112
|
const posRules = rules.filter((r) => !r.isNeg);
|
|
113
|
+
const mapRoots = new Map();
|
|
85
114
|
// const negRegEx = negRules.map((r) => r.reg).map((r) => r.toString());
|
|
86
115
|
// const posRegEx = posRules.map((r) => r.reg).map((r) => r.toString());
|
|
87
116
|
// console.error('buildMatcherFn %o', { negRegEx, posRegEx, stack: new Error().stack });
|
|
88
117
|
// const negReg = joinRegExp(negRegEx);
|
|
89
118
|
// const posReg = joinRegExp(posRegEx);
|
|
90
119
|
const fn = (filename) => {
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
let lastRoot = '
|
|
120
|
+
const fileUrl = builder.toFileURL(filename);
|
|
121
|
+
const relFilePathname = builder.relative(new URL('file:///'), fileUrl);
|
|
122
|
+
let lastRoot = new URL('placeHolder://');
|
|
94
123
|
let lastRel = '';
|
|
124
|
+
function rootToUrl(root) {
|
|
125
|
+
const found = mapRoots.get(root);
|
|
126
|
+
if (found)
|
|
127
|
+
return found;
|
|
128
|
+
const url = builder.toFileDirURL(root);
|
|
129
|
+
mapRoots.set(root, url);
|
|
130
|
+
return url;
|
|
131
|
+
}
|
|
95
132
|
function relativeToRoot(root) {
|
|
96
|
-
if (root !== lastRoot) {
|
|
133
|
+
if (root.href !== lastRoot.href) {
|
|
97
134
|
lastRoot = root;
|
|
98
|
-
|
|
99
|
-
lastRel = path.sep === '\\' ? relName.replaceAll('\\', '/') : relName;
|
|
135
|
+
lastRel = builder.relative(root, fileUrl);
|
|
100
136
|
}
|
|
101
137
|
return lastRel;
|
|
102
138
|
}
|
|
@@ -104,11 +140,16 @@ function buildMatcherFn(patterns, options) {
|
|
|
104
140
|
for (const rule of rules) {
|
|
105
141
|
const pattern = rule.pattern;
|
|
106
142
|
const root = pattern.root;
|
|
143
|
+
const rootURL = rootToUrl(root);
|
|
107
144
|
const isRelPat = !pattern.isGlobalPattern;
|
|
108
|
-
|
|
109
|
-
|
|
145
|
+
let fname = relFilePathname;
|
|
146
|
+
if (isRelPat) {
|
|
147
|
+
const relPathToFile = relativeToRoot(rootURL);
|
|
148
|
+
if (!isRelativeValueNested(relPathToFile)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
fname = relPathToFile;
|
|
110
152
|
}
|
|
111
|
-
const fname = isRelPat ? relativeToRoot(root) : fNameNormalize;
|
|
112
153
|
if (rule.fn(fname)) {
|
|
113
154
|
return {
|
|
114
155
|
matched,
|
|
@@ -121,15 +162,29 @@ function buildMatcherFn(patterns, options) {
|
|
|
121
162
|
}
|
|
122
163
|
}
|
|
123
164
|
}
|
|
124
|
-
|
|
165
|
+
const result = testRules(negRules, false) || testRules(posRules, true) || { matched: false };
|
|
166
|
+
traceMode && logMatchTest(_id, filename, result);
|
|
167
|
+
return result;
|
|
125
168
|
};
|
|
126
169
|
return fn;
|
|
127
170
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
171
|
+
function logMatchTest(id, filename, match) {
|
|
172
|
+
console.warn('%s;%d;%s', filename, id, JSON.stringify(match.matched));
|
|
173
|
+
}
|
|
174
|
+
// function outputBuildMatcherFnPerfData(patterns: GlobPatternWithRoot[], options: NormalizedGlobMatchOptions) {
|
|
175
|
+
// console.warn(
|
|
176
|
+
// JSON.stringify({
|
|
177
|
+
// options: {
|
|
178
|
+
// ...options,
|
|
179
|
+
// nodePath: undefined,
|
|
180
|
+
// root: Path.relative(process.cwd(), options.root),
|
|
181
|
+
// cwd: Path.relative(process.cwd(), options.cwd),
|
|
182
|
+
// },
|
|
183
|
+
// patterns: patterns.map(({ glob, root, isGlobalPattern }) => ({
|
|
184
|
+
// glob,
|
|
185
|
+
// root: isGlobalPattern ? undefined : Path.relative(process.cwd(), root),
|
|
186
|
+
// })),
|
|
187
|
+
// }) + ',',
|
|
188
|
+
// );
|
|
134
189
|
// }
|
|
135
190
|
//# sourceMappingURL=GlobMatcher.js.map
|
|
@@ -4,6 +4,13 @@ export interface PathInterface {
|
|
|
4
4
|
resolve(...paths: string[]): string;
|
|
5
5
|
relative(from: string, to: string): string;
|
|
6
6
|
isAbsolute(p: string): boolean;
|
|
7
|
+
parse(p: string): {
|
|
8
|
+
root: string;
|
|
9
|
+
dir: string;
|
|
10
|
+
base: string;
|
|
11
|
+
ext: string;
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
7
14
|
sep: string;
|
|
8
15
|
}
|
|
9
16
|
export type GlobMatch = GlobMatchRule | GlobMatchNoRule;
|
|
@@ -43,6 +50,7 @@ export interface GlobPatternWithRoot extends GlobPatternWithOptionalRoot {
|
|
|
43
50
|
root: string;
|
|
44
51
|
/**
|
|
45
52
|
* Global patterns do not need to be relative to the root.
|
|
53
|
+
* Note: Some patterns start with `**` but they are tied to the root. In this case, `isGlobalPattern` is `false`.
|
|
46
54
|
*/
|
|
47
55
|
isGlobalPattern: boolean;
|
|
48
56
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { GlobPattern, GlobPatternNormalized, GlobPatternWithOptionalRoot, GlobPatternWithRoot, PathInterface } from './GlobMatcherTypes.js';
|
|
2
|
+
export declare const GlobPlaceHolders: {
|
|
3
|
+
cwd: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const GlobPatterns: {
|
|
6
|
+
suffixAny: string;
|
|
7
|
+
/**
|
|
8
|
+
* Use as as suffix for a directory. Example `node_modules/` becomes `node_modules/**/*`.
|
|
9
|
+
*/
|
|
10
|
+
suffixDir: string;
|
|
11
|
+
prefixAny: string;
|
|
12
|
+
};
|
|
2
13
|
/**
|
|
3
14
|
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
|
|
4
15
|
* @param fileOrGlob - file (with absolute path) or glob.
|
|
@@ -6,15 +17,10 @@ import type { GlobPattern, GlobPatternNormalized, GlobPatternWithOptionalRoot, G
|
|
|
6
17
|
* @param path - optional node path methods - used for testing
|
|
7
18
|
*/
|
|
8
19
|
export declare function fileOrGlobToGlob(fileOrGlob: string | GlobPattern, root: string, path?: PathInterface): GlobPatternWithRoot;
|
|
9
|
-
/**
|
|
10
|
-
* Decide if a childPath is contained within a root or at the same level.
|
|
11
|
-
* @param root - absolute path
|
|
12
|
-
* @param childPath - absolute path
|
|
13
|
-
*/
|
|
14
|
-
export declare function doesRootContainPath(root: string, child: string, path: PathInterface): boolean;
|
|
15
20
|
export declare function isGlobPatternWithOptionalRoot(g: GlobPattern): g is GlobPatternWithOptionalRoot;
|
|
16
|
-
export declare function isGlobPatternWithRoot(g:
|
|
21
|
+
export declare function isGlobPatternWithRoot(g: GlobPattern): g is GlobPatternWithRoot;
|
|
17
22
|
export declare function isGlobPatternNormalized(g: GlobPattern | GlobPatternNormalized): g is GlobPatternNormalized;
|
|
23
|
+
export declare function isGlobPatternNormalizedToRoot(g: GlobPattern | GlobPatternNormalized, options: NormalizeOptions): g is GlobPatternNormalized;
|
|
18
24
|
export interface NormalizeOptions {
|
|
19
25
|
/**
|
|
20
26
|
* Indicates that the glob should be modified to match nested patterns.
|
|
@@ -51,18 +57,24 @@ export declare function normalizeGlobPattern(g: GlobPattern, options: NormalizeO
|
|
|
51
57
|
* @param path - Node Path modules to use (testing only)
|
|
52
58
|
*/
|
|
53
59
|
export declare function normalizeGlobToRoot<Glob extends GlobPatternWithRoot>(glob: Glob, root: string, path: PathInterface): Glob;
|
|
60
|
+
export declare function isRelativeValueNested(rel: string): boolean;
|
|
54
61
|
/**
|
|
55
62
|
* Rebase a glob string to a new prefix
|
|
56
63
|
* @param glob - glob string
|
|
57
64
|
* @param rebaseTo - glob prefix
|
|
58
65
|
*/
|
|
59
|
-
declare function rebaseGlob(glob: string, rebaseTo: string): string | undefined;
|
|
66
|
+
export declare function rebaseGlob(glob: string, rebaseTo: string): string | undefined;
|
|
60
67
|
/**
|
|
61
68
|
* Trims any trailing spaces, tabs, line-feeds, new-lines, and comments
|
|
62
69
|
* @param glob - glob string
|
|
63
70
|
* @returns trimmed glob
|
|
64
71
|
*/
|
|
65
|
-
declare function trimGlob(glob: string): string;
|
|
72
|
+
export declare function trimGlob(glob: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Test if a glob pattern has a leading `**`.
|
|
75
|
+
* @param glob - the glob
|
|
76
|
+
* @returns true if the glob pattern starts with `**`
|
|
77
|
+
*/
|
|
66
78
|
declare function isGlobalGlob(glob: string): boolean;
|
|
67
79
|
export declare const __testing__: {
|
|
68
80
|
rebaseGlob: typeof rebaseGlob;
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/* eslint-disable no-irregular-whitespace */
|
|
2
|
+
import * as Path from 'node:path';
|
|
3
|
+
import { FileUrlBuilder } from '@cspell/url';
|
|
4
|
+
const { posix } = Path;
|
|
5
|
+
// const relRegExp = /^\..?[\\/]/;
|
|
6
|
+
/** test for glob patterns starting with `**` */
|
|
7
|
+
const isGlobalPatternRegExp = /^!*[*]{2}/;
|
|
8
|
+
const hasGlobCharactersRegExp = /[*?{}[\]]/;
|
|
9
|
+
const fileUrlBuilder = new FileUrlBuilder();
|
|
10
|
+
export const GlobPlaceHolders = {
|
|
11
|
+
cwd: '${cwd}',
|
|
12
|
+
};
|
|
13
|
+
export const GlobPatterns = {
|
|
14
|
+
suffixAny: '/**',
|
|
15
|
+
/**
|
|
16
|
+
* Use as as suffix for a directory. Example `node_modules/` becomes `node_modules/**/*`.
|
|
17
|
+
*/
|
|
18
|
+
suffixDir: '/**/*',
|
|
19
|
+
prefixAny: '**/',
|
|
20
|
+
};
|
|
21
|
+
let cacheCalls = 0;
|
|
22
|
+
let cacheMisses = 0;
|
|
23
|
+
let cachePath = Path;
|
|
24
|
+
let cacheRoot = '<>';
|
|
25
|
+
const cache = new Map();
|
|
26
|
+
const debugCache = false;
|
|
27
|
+
/**
|
|
28
|
+
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
|
|
29
|
+
* @param fileOrGlob - file (with absolute path) or glob.
|
|
30
|
+
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
|
|
31
|
+
* @param path - optional node path methods - used for testing
|
|
32
|
+
*/
|
|
33
|
+
export function fileOrGlobToGlob(fileOrGlob, root, path = Path) {
|
|
34
|
+
if (cacheRoot !== root || cachePath !== path) {
|
|
35
|
+
cache.clear();
|
|
36
|
+
cacheCalls = 0;
|
|
37
|
+
cacheMisses = 0;
|
|
38
|
+
cacheRoot = root;
|
|
39
|
+
cachePath = path;
|
|
40
|
+
}
|
|
41
|
+
++cacheCalls;
|
|
42
|
+
debugCache &&
|
|
43
|
+
!(cacheCalls & 0x7) &&
|
|
44
|
+
console.error('cache miss rate: %d%% cache size: %d', (cacheMisses / cacheCalls) * 100, cache.size);
|
|
45
|
+
const found = cache.get(fileOrGlob);
|
|
46
|
+
if (found)
|
|
47
|
+
return found;
|
|
48
|
+
++cacheMisses;
|
|
49
|
+
const pattern = _fileOrGlobToGlob(fileOrGlob, root, path);
|
|
50
|
+
cache.set(fileOrGlob, pattern);
|
|
51
|
+
return pattern;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
|
|
55
|
+
* @param fileOrGlob - file (with absolute path) or glob.
|
|
56
|
+
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
|
|
57
|
+
* @param path - optional node path methods - used for testing
|
|
58
|
+
*/
|
|
59
|
+
function _fileOrGlobToGlob(fileOrGlob, root, path = Path) {
|
|
60
|
+
const toForwardSlash = path.sep === '\\' ? (p) => p.replaceAll('\\', '/') : (p) => p;
|
|
61
|
+
const builder = urlBuilder(path);
|
|
62
|
+
fileOrGlob = typeof fileOrGlob === 'string' ? toForwardSlash(fileOrGlob) : fileOrGlob;
|
|
63
|
+
const rootUrl = builder.toFileDirURL(root);
|
|
64
|
+
// Normalize root
|
|
65
|
+
root = builder.urlToFilePathOrHref(rootUrl);
|
|
66
|
+
const pattern = toGlobPatternWithRoot(fileOrGlob, root, builder);
|
|
67
|
+
// if (root.includes(GlobPlaceHolders.cwd) || pattern.root.includes(GlobPlaceHolders.cwd)) {
|
|
68
|
+
// console.warn('fileOrGlobToGlob: root or pattern contains ${cwd}', { root, pattern, fileOrGlob });
|
|
69
|
+
// }
|
|
70
|
+
return pattern;
|
|
71
|
+
}
|
|
72
|
+
function toGlobPatternWithRoot(glob, root, builder) {
|
|
73
|
+
function toPattern() {
|
|
74
|
+
if (isGlobPatternWithRoot(glob))
|
|
75
|
+
return fixPatternRoot({ ...glob }, builder);
|
|
76
|
+
const rootUrl = builder.toFileDirURL(root);
|
|
77
|
+
if (typeof glob === 'string')
|
|
78
|
+
return filePathOrGlobToGlob(glob, rootUrl, builder);
|
|
79
|
+
const pattern = { isGlobalPattern: isGlobalGlob(glob.glob), ...glob, root: glob.root ?? root };
|
|
80
|
+
fixPatternRoot(pattern, builder);
|
|
81
|
+
// pattern.glob might still be a file or a relative glob pattern.
|
|
82
|
+
fixPatternGlob(pattern, builder);
|
|
83
|
+
return pattern;
|
|
84
|
+
}
|
|
85
|
+
const pattern = toPattern();
|
|
86
|
+
if (pattern.glob.startsWith(GlobPlaceHolders.cwd)) {
|
|
87
|
+
pattern.root = GlobPlaceHolders.cwd;
|
|
88
|
+
pattern.glob = pattern.glob.replace(GlobPlaceHolders.cwd, '');
|
|
89
|
+
}
|
|
90
|
+
return pattern;
|
|
91
|
+
}
|
|
92
|
+
export function isGlobPatternWithOptionalRoot(g) {
|
|
93
|
+
return typeof g !== 'string' && typeof g.glob === 'string';
|
|
94
|
+
}
|
|
95
|
+
export function isGlobPatternWithRoot(g) {
|
|
96
|
+
if (typeof g === 'string')
|
|
97
|
+
return false;
|
|
98
|
+
return typeof g.root === 'string' && 'isGlobalPattern' in g;
|
|
99
|
+
}
|
|
100
|
+
export function isGlobPatternNormalized(g) {
|
|
101
|
+
if (!isGlobPatternWithRoot(g))
|
|
102
|
+
return false;
|
|
103
|
+
const gr = g;
|
|
104
|
+
return 'rawGlob' in gr && 'rawRoot' in gr && typeof gr.rawGlob === 'string';
|
|
105
|
+
}
|
|
106
|
+
export function isGlobPatternNormalizedToRoot(g, options) {
|
|
107
|
+
if (!isGlobPatternNormalized(g))
|
|
108
|
+
return false;
|
|
109
|
+
return g.root === options.root;
|
|
110
|
+
}
|
|
111
|
+
function urlBuilder(path = Path) {
|
|
112
|
+
return path === Path ? fileUrlBuilder : new FileUrlBuilder({ path });
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* @param pattern glob pattern
|
|
116
|
+
* @param nested when true add `**/<glob>/**`
|
|
117
|
+
* @returns the set of matching globs.
|
|
118
|
+
*/
|
|
119
|
+
function normalizePattern(pattern, nested) {
|
|
120
|
+
pattern = pattern.replace(/^(!!)+/, '');
|
|
121
|
+
const isNeg = pattern.startsWith('!');
|
|
122
|
+
const prefix = isNeg ? '!' : '';
|
|
123
|
+
pattern = isNeg ? pattern.slice(1) : pattern;
|
|
124
|
+
const patterns = nested ? normalizePatternNested(pattern) : normalizePatternGeneral(pattern);
|
|
125
|
+
return patterns.map((p) => prefix + p);
|
|
126
|
+
}
|
|
127
|
+
function normalizePatternNested(pattern) {
|
|
128
|
+
// no slashes will match files names or folders
|
|
129
|
+
if (!pattern.includes('/')) {
|
|
130
|
+
if (pattern === '**')
|
|
131
|
+
return ['**'];
|
|
132
|
+
return ['**/' + pattern, '**/' + pattern + '/**'];
|
|
133
|
+
}
|
|
134
|
+
const hasLeadingSlash = pattern.startsWith('/');
|
|
135
|
+
pattern = hasLeadingSlash ? pattern.slice(1) : pattern;
|
|
136
|
+
if (pattern.endsWith('/')) {
|
|
137
|
+
// See: https://git-scm.com/docs/gitignore#_pattern_format
|
|
138
|
+
// if it only has a trailing slash, allow matching against a nested directory.
|
|
139
|
+
return hasLeadingSlash || pattern.slice(0, -1).includes('/') ? [pattern + '**/*'] : ['**/' + pattern + '**/*'];
|
|
140
|
+
}
|
|
141
|
+
if (pattern.endsWith('**')) {
|
|
142
|
+
return [pattern];
|
|
143
|
+
}
|
|
144
|
+
return [pattern, pattern + '/**'];
|
|
145
|
+
}
|
|
146
|
+
function normalizePatternGeneral(pattern) {
|
|
147
|
+
pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern;
|
|
148
|
+
pattern = pattern.endsWith('/') ? pattern + '**/*' : pattern;
|
|
149
|
+
return [pattern];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
*
|
|
153
|
+
* @param patterns - glob patterns to normalize.
|
|
154
|
+
* @param options - Normalization options.
|
|
155
|
+
*/
|
|
156
|
+
export function normalizeGlobPatterns(patterns, options) {
|
|
157
|
+
function* normalize() {
|
|
158
|
+
for (const glob of patterns) {
|
|
159
|
+
if (isGlobPatternNormalized(glob)) {
|
|
160
|
+
yield isGlobPatternNormalizedToRoot(glob, options)
|
|
161
|
+
? glob
|
|
162
|
+
: normalizeGlobToRoot(glob, options.root, options.nodePath || Path);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
yield* normalizeGlobPattern(glob, options);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return [...normalize()];
|
|
169
|
+
}
|
|
170
|
+
export function normalizeGlobPattern(g, options) {
|
|
171
|
+
const { root, nodePath: path = Path, nested } = options;
|
|
172
|
+
const builder = urlBuilder(path);
|
|
173
|
+
const cwd = options.cwd ?? path.resolve();
|
|
174
|
+
const cwdUrl = builder.toFileDirURL(cwd);
|
|
175
|
+
const rootUrl = builder.toFileDirURL(root, cwdUrl);
|
|
176
|
+
const gIsGlobalPattern = isGlobPatternWithRoot(g) ? g.isGlobalPattern : undefined;
|
|
177
|
+
g = !isGlobPatternWithOptionalRoot(g) ? { glob: g } : g;
|
|
178
|
+
const gr = { ...g, root: g.root ?? root };
|
|
179
|
+
const rawRoot = gr.root;
|
|
180
|
+
const rawGlob = g.glob;
|
|
181
|
+
gr.glob = trimGlob(g.glob);
|
|
182
|
+
if (gr.glob.startsWith(GlobPlaceHolders.cwd)) {
|
|
183
|
+
gr.glob = gr.glob.replace(GlobPlaceHolders.cwd, '');
|
|
184
|
+
gr.root = GlobPlaceHolders.cwd;
|
|
185
|
+
}
|
|
186
|
+
if (gr.root.startsWith(GlobPlaceHolders.cwd)) {
|
|
187
|
+
const relRoot = gr.root.replace(GlobPlaceHolders.cwd, './');
|
|
188
|
+
const r = builder.toFileDirURL(relRoot, cwdUrl);
|
|
189
|
+
r.pathname = posix.normalize(r.pathname);
|
|
190
|
+
gr.root = builder.urlToFilePathOrHref(r);
|
|
191
|
+
}
|
|
192
|
+
const isGlobalPattern = gIsGlobalPattern ?? isGlobalGlob(gr.glob);
|
|
193
|
+
gr.root = builder.urlToFilePathOrHref(builder.toFileDirURL(gr.root, rootUrl));
|
|
194
|
+
const globs = normalizePattern(gr.glob, nested);
|
|
195
|
+
return globs.map((glob) => ({ ...gr, glob, rawGlob, rawRoot, isGlobalPattern }));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Try to adjust the root of a glob to match a new root. If it is not possible, the original glob is returned.
|
|
199
|
+
* Note: this does NOT generate absolutely correct glob patterns. The results are intended to be used as a
|
|
200
|
+
* first pass only filter. Followed by testing against the original glob/root pair.
|
|
201
|
+
* @param glob - glob to map
|
|
202
|
+
* @param root - new root to use if possible
|
|
203
|
+
* @param path - Node Path modules to use (testing only)
|
|
204
|
+
*/
|
|
205
|
+
export function normalizeGlobToRoot(glob, root, path) {
|
|
206
|
+
const builder = urlBuilder(path);
|
|
207
|
+
glob = { ...glob };
|
|
208
|
+
fixPatternRoot(glob, builder);
|
|
209
|
+
const rootURL = builder.toFileDirURL(root);
|
|
210
|
+
root = builder.urlToFilePathOrHref(rootURL);
|
|
211
|
+
if (glob.root === root) {
|
|
212
|
+
return glob;
|
|
213
|
+
}
|
|
214
|
+
const globRootUrl = builder.toFileDirURL(glob.root);
|
|
215
|
+
const relFromRootToGlob = builder.relative(rootURL, globRootUrl);
|
|
216
|
+
if (!relFromRootToGlob) {
|
|
217
|
+
return glob;
|
|
218
|
+
}
|
|
219
|
+
if (glob.isGlobalPattern) {
|
|
220
|
+
return { ...glob, root };
|
|
221
|
+
}
|
|
222
|
+
const relFromGlobToRoot = builder.relative(globRootUrl, rootURL);
|
|
223
|
+
const globIsUnderRoot = isRelativeValueNested(relFromRootToGlob);
|
|
224
|
+
const rootIsUnderGlob = isRelativeValueNested(relFromGlobToRoot);
|
|
225
|
+
// Root and Glob are not in the same part of the directory tree.
|
|
226
|
+
if (!globIsUnderRoot && !rootIsUnderGlob) {
|
|
227
|
+
return glob;
|
|
228
|
+
}
|
|
229
|
+
const isNeg = glob.glob.startsWith('!');
|
|
230
|
+
const g = isNeg ? glob.glob.slice(1) : glob.glob;
|
|
231
|
+
const prefix = isNeg ? '!' : '';
|
|
232
|
+
// prefix with root
|
|
233
|
+
if (globIsUnderRoot) {
|
|
234
|
+
const relGlob = relFromRootToGlob;
|
|
235
|
+
return {
|
|
236
|
+
...glob,
|
|
237
|
+
glob: prefix + posix.join(relGlob, g),
|
|
238
|
+
root,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// The root is under the glob root
|
|
242
|
+
// The more difficult case, the glob is higher than the root
|
|
243
|
+
// A best effort is made, but does not do advanced matching.
|
|
244
|
+
const relGlob = (relFromGlobToRoot + '/').replaceAll('//', '/');
|
|
245
|
+
const rebasedGlob = rebaseGlob(g, relGlob);
|
|
246
|
+
return rebasedGlob ? { ...glob, glob: prefix + rebasedGlob, root } : glob;
|
|
247
|
+
}
|
|
248
|
+
export function isRelativeValueNested(rel) {
|
|
249
|
+
return !rel || !(rel === '..' || rel.startsWith('../') || rel.startsWith('/'));
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Rebase a glob string to a new prefix
|
|
253
|
+
* @param glob - glob string
|
|
254
|
+
* @param rebaseTo - glob prefix
|
|
255
|
+
*/
|
|
256
|
+
export function rebaseGlob(glob, rebaseTo) {
|
|
257
|
+
if (!rebaseTo || rebaseTo === '/')
|
|
258
|
+
return glob;
|
|
259
|
+
if (glob.startsWith('**'))
|
|
260
|
+
return glob;
|
|
261
|
+
rebaseTo = rebaseTo.endsWith('/') ? rebaseTo : rebaseTo + '/';
|
|
262
|
+
if (glob.startsWith(rebaseTo)) {
|
|
263
|
+
return glob.slice(rebaseTo.length);
|
|
264
|
+
}
|
|
265
|
+
const relParts = rebaseTo.split('/');
|
|
266
|
+
const globParts = glob.split('/');
|
|
267
|
+
for (let i = 0; i < relParts.length && i < globParts.length; ++i) {
|
|
268
|
+
const relSeg = relParts[i];
|
|
269
|
+
const globSeg = globParts[i];
|
|
270
|
+
// the empty segment due to the end relGlob / allows for us to test against an empty segment.
|
|
271
|
+
if (!relSeg || globSeg === '**') {
|
|
272
|
+
return globParts.slice(i).join('/');
|
|
273
|
+
}
|
|
274
|
+
if (relSeg !== globSeg && globSeg !== '*') {
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Trims any trailing spaces, tabs, line-feeds, new-lines, and comments
|
|
282
|
+
* @param glob - glob string
|
|
283
|
+
* @returns trimmed glob
|
|
284
|
+
*/
|
|
285
|
+
export function trimGlob(glob) {
|
|
286
|
+
glob = globRemoveComment(glob);
|
|
287
|
+
glob = trimGlobLeft(glob);
|
|
288
|
+
glob = trimGlobRight(glob);
|
|
289
|
+
return glob;
|
|
290
|
+
}
|
|
291
|
+
function globRemoveComment(glob) {
|
|
292
|
+
return glob.replace(/(?<=^|\s)#.*/, '');
|
|
293
|
+
}
|
|
294
|
+
const spaces = {
|
|
295
|
+
' ': true,
|
|
296
|
+
'\t': true,
|
|
297
|
+
'\n': true,
|
|
298
|
+
'\r': true,
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Trim any trailing spaces, tabs, line-feeds, or new-lines
|
|
302
|
+
* Handles a trailing \<space>
|
|
303
|
+
* @param glob - glob string
|
|
304
|
+
* @returns glob string with space to the right removed.
|
|
305
|
+
*/
|
|
306
|
+
function trimGlobRight(glob) {
|
|
307
|
+
const lenMin1 = glob.length - 1;
|
|
308
|
+
let i = lenMin1;
|
|
309
|
+
while (i >= 0 && glob[i] in spaces) {
|
|
310
|
+
--i;
|
|
311
|
+
}
|
|
312
|
+
if (glob[i] === '\\') {
|
|
313
|
+
++i;
|
|
314
|
+
}
|
|
315
|
+
++i;
|
|
316
|
+
return i ? glob.slice(0, i) : '';
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Trim any leading spaces, tabs, line-feeds, or new-lines
|
|
320
|
+
* @param glob - any string
|
|
321
|
+
* @returns string with leading spaces removed.
|
|
322
|
+
*/
|
|
323
|
+
function trimGlobLeft(glob) {
|
|
324
|
+
return glob.trimStart();
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Test if a glob pattern has a leading `**`.
|
|
328
|
+
* @param glob - the glob
|
|
329
|
+
* @returns true if the glob pattern starts with `**`
|
|
330
|
+
*/
|
|
331
|
+
function isGlobalGlob(glob) {
|
|
332
|
+
return isGlobalPatternRegExp.test(glob);
|
|
333
|
+
}
|
|
334
|
+
function hasGlobCharacters(glob) {
|
|
335
|
+
return hasGlobCharactersRegExp.test(glob);
|
|
336
|
+
}
|
|
337
|
+
function isGlobPart(part) {
|
|
338
|
+
if (part === GlobPlaceHolders.cwd)
|
|
339
|
+
return false;
|
|
340
|
+
return hasGlobCharacters(part);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Split a glob into a path and a glob portion.
|
|
344
|
+
* The path portion does not contain any glob characters.
|
|
345
|
+
* Path might be empty. The glob portion should always be non-empty.
|
|
346
|
+
* @param glob - glob string pattern
|
|
347
|
+
* @returns
|
|
348
|
+
*/
|
|
349
|
+
function splitGlob(glob) {
|
|
350
|
+
const parts = glob.split('/');
|
|
351
|
+
const p = parts.findIndex(isGlobPart);
|
|
352
|
+
const s = p < 0 ? parts.length - 1 : p;
|
|
353
|
+
return createSplitGlob(s ? parts.slice(0, s).join('/') + '/' : undefined, parts.slice(s).join('/'));
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Split a glob into a path and a glob portion.
|
|
357
|
+
* The path portion does not contain any glob characters.
|
|
358
|
+
* Path might be empty. The glob portion should always be non-empty.
|
|
359
|
+
* @param glob - glob string pattern
|
|
360
|
+
* @param relOnly - Indicates that only `..` and `.` path segments are considered for the path.
|
|
361
|
+
* @returns
|
|
362
|
+
*/
|
|
363
|
+
function splitGlobRel(glob) {
|
|
364
|
+
const parts = glob.split('/');
|
|
365
|
+
if (!parts.includes('..') && !parts.includes('.'))
|
|
366
|
+
return { path: undefined, glob };
|
|
367
|
+
const firstGlobPartIdx = parts.findIndex(isGlobPart);
|
|
368
|
+
const lastRelIdx = Math.max(parts.lastIndexOf('..'), parts.lastIndexOf('.'));
|
|
369
|
+
const p = firstGlobPartIdx >= 0 ? Math.min(firstGlobPartIdx, lastRelIdx + 1) : lastRelIdx + 1;
|
|
370
|
+
const s = p < 0 ? parts.length - 1 : p;
|
|
371
|
+
return createSplitGlob(s ? parts.slice(0, s).join('/') + '/' : undefined, parts.slice(s).join('/'));
|
|
372
|
+
}
|
|
373
|
+
function createSplitGlob(path, glob) {
|
|
374
|
+
glob = path ? '/' + glob : glob;
|
|
375
|
+
glob = glob.startsWith('/**') ? glob.slice(1) : glob;
|
|
376
|
+
return { path, glob };
|
|
377
|
+
}
|
|
378
|
+
function rootToUrl(root, builder) {
|
|
379
|
+
if (root.startsWith(GlobPlaceHolders.cwd)) {
|
|
380
|
+
return new URL(builder.normalizeFilePathForUrl(root.replace(GlobPlaceHolders.cwd, '.')), builder.cwd);
|
|
381
|
+
}
|
|
382
|
+
return builder.toFileDirURL(root);
|
|
383
|
+
}
|
|
384
|
+
function fixPatternRoot(glob, builder) {
|
|
385
|
+
// Gets resolved later.
|
|
386
|
+
if (glob.root.startsWith(GlobPlaceHolders.cwd)) {
|
|
387
|
+
return glob;
|
|
388
|
+
}
|
|
389
|
+
glob.root = builder.urlToFilePathOrHref(rootToUrl(glob.root, builder));
|
|
390
|
+
return glob;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Adjust the glob pattern in case it is a file or a relative glob.
|
|
394
|
+
* @param glob
|
|
395
|
+
* @param builder
|
|
396
|
+
* @returns
|
|
397
|
+
*/
|
|
398
|
+
function fixPatternGlob(glob, builder) {
|
|
399
|
+
const rootURL = builder.toFileURL(glob.root);
|
|
400
|
+
const split = splitGlobRel(glob.glob);
|
|
401
|
+
glob.glob = split.glob;
|
|
402
|
+
if (split.path !== undefined) {
|
|
403
|
+
const relRootPath = split.path.startsWith('/') ? '.' + split.path : split.path;
|
|
404
|
+
glob.root = builder.urlToFilePathOrHref(builder.toFileDirURL(relRootPath, glob.root));
|
|
405
|
+
}
|
|
406
|
+
fixPatternRelativeToRoot(glob, rootURL, builder);
|
|
407
|
+
}
|
|
408
|
+
function fixPatternRelativeToRoot(glob, root, builder) {
|
|
409
|
+
if (glob.root.startsWith(GlobPlaceHolders.cwd))
|
|
410
|
+
return;
|
|
411
|
+
const rel = builder.relative(root, builder.toFileDirURL(glob.root));
|
|
412
|
+
if (rel.startsWith('/') || rel.startsWith('../'))
|
|
413
|
+
return;
|
|
414
|
+
glob.root = builder.urlToFilePathOrHref(root);
|
|
415
|
+
glob.glob = rel + glob.glob;
|
|
416
|
+
}
|
|
417
|
+
function filePathOrGlobToGlob(filePathOrGlob, root, builder) {
|
|
418
|
+
const isGlobalPattern = isGlobalGlob(filePathOrGlob);
|
|
419
|
+
const { path, glob } = builder.isAbsolute(filePathOrGlob)
|
|
420
|
+
? splitGlob(filePathOrGlob)
|
|
421
|
+
: splitGlobRel(filePathOrGlob);
|
|
422
|
+
const url = builder.toFileDirURL(path || './', root);
|
|
423
|
+
return { root: builder.urlToFilePathOrHref(url), glob, isGlobalPattern };
|
|
424
|
+
}
|
|
425
|
+
export const __testing__ = {
|
|
426
|
+
rebaseGlob,
|
|
427
|
+
trimGlob,
|
|
428
|
+
isGlobalGlob,
|
|
429
|
+
};
|
|
430
|
+
//# sourceMappingURL=globHelper.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cspell-glob",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.10.0",
|
|
4
4
|
"description": "Glob matcher for cspell",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cspell",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"sideEffects": false,
|
|
14
|
-
"types": "dist/
|
|
15
|
-
"module": "dist/
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"module": "dist/index.js",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
|
-
"import": "./dist/
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
@@ -25,18 +25,22 @@
|
|
|
25
25
|
"!**/__mocks__",
|
|
26
26
|
"!**/test/**",
|
|
27
27
|
"!**/*.test.*",
|
|
28
|
+
"!**/perf/**",
|
|
29
|
+
"!**/*.perf.*",
|
|
28
30
|
"!**/*.spec.*",
|
|
29
31
|
"!**/*.map"
|
|
30
32
|
],
|
|
31
33
|
"scripts": {
|
|
32
34
|
"clean": "shx rm -rf dist temp coverage \"*.tsbuildInfo\"",
|
|
33
|
-
"build": "tsc -
|
|
34
|
-
"build:esm": "tsc -p tsconfig.esm.json",
|
|
35
|
+
"build": "tsc -p .",
|
|
35
36
|
"clean-build": "pnpm run clean && pnpm run build",
|
|
36
37
|
"coverage": "vitest run --coverage",
|
|
38
|
+
"test:perf": "insight --file \"dist/perf/**/*.perf.{mjs,js}\" -t 1000",
|
|
39
|
+
"test:perf:ts": "insight --register ts-node/esm --file \"src/perf/**/*.perf.{mts,ts}\" -t 1000",
|
|
40
|
+
"test:perf:prof": "NODE_ENV=production node --cpu-prof ../../node_modules/perf-insight/bin.mjs --file \"dist/perf/**/*.perf.{mjs,js}\" -t 1000",
|
|
37
41
|
"test:watch": "vitest",
|
|
38
42
|
"test": "vitest run",
|
|
39
|
-
"watch": "tsc -
|
|
43
|
+
"watch": "tsc -p . -w"
|
|
40
44
|
},
|
|
41
45
|
"repository": {
|
|
42
46
|
"type": "git",
|
|
@@ -50,10 +54,11 @@
|
|
|
50
54
|
"node": ">=18"
|
|
51
55
|
},
|
|
52
56
|
"dependencies": {
|
|
57
|
+
"@cspell/url": "8.10.0",
|
|
53
58
|
"micromatch": "^4.0.7"
|
|
54
59
|
},
|
|
55
60
|
"devDependencies": {
|
|
56
|
-
"@types/micromatch": "^4.0.
|
|
61
|
+
"@types/micromatch": "^4.0.9"
|
|
57
62
|
},
|
|
58
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "a5dde6ae7e2ac86ac956220d4b8c39a0e58e1bc6"
|
|
59
64
|
}
|
package/dist/esm/globHelper.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-irregular-whitespace */
|
|
2
|
-
import * as Path from 'node:path';
|
|
3
|
-
const { posix } = Path;
|
|
4
|
-
const relRegExp = /^\.[\\/]/;
|
|
5
|
-
/** test for glob patterns starting with `**` */
|
|
6
|
-
const isGlobalPatternRegExp = /^!*[*]{2}/;
|
|
7
|
-
/**
|
|
8
|
-
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
|
|
9
|
-
* @param fileOrGlob - file (with absolute path) or glob.
|
|
10
|
-
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
|
|
11
|
-
* @param path - optional node path methods - used for testing
|
|
12
|
-
*/
|
|
13
|
-
export function fileOrGlobToGlob(fileOrGlob, root, path = Path) {
|
|
14
|
-
const pathToGlob = path.sep === '\\' ? (p) => p.replaceAll('\\', '/') : (p) => p;
|
|
15
|
-
const isGlobalPattern = false;
|
|
16
|
-
if (isGlobPatternWithOptionalRoot(fileOrGlob)) {
|
|
17
|
-
const useRoot = fileOrGlob.root ?? root;
|
|
18
|
-
const isGlobalPattern = isGlobPatternWithRoot(fileOrGlob)
|
|
19
|
-
? fileOrGlob.isGlobalPattern
|
|
20
|
-
: isGlobalGlob(fileOrGlob.glob);
|
|
21
|
-
return { ...fileOrGlob, root: useRoot, isGlobalPattern };
|
|
22
|
-
}
|
|
23
|
-
if (doesRootContainPath(root, fileOrGlob, path) || relRegExp.test(fileOrGlob)) {
|
|
24
|
-
const rel = path.relative(root, path.resolve(root, fileOrGlob));
|
|
25
|
-
return { glob: pathToGlob(rel), root, isGlobalPattern };
|
|
26
|
-
}
|
|
27
|
-
return { glob: pathToGlob(fileOrGlob), root, isGlobalPattern };
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Decide if a childPath is contained within a root or at the same level.
|
|
31
|
-
* @param root - absolute path
|
|
32
|
-
* @param childPath - absolute path
|
|
33
|
-
*/
|
|
34
|
-
export function doesRootContainPath(root, child, path) {
|
|
35
|
-
if (child.startsWith(root))
|
|
36
|
-
return true;
|
|
37
|
-
const rel = path.relative(root, child);
|
|
38
|
-
return !rel || (rel !== child && !rel.startsWith('..') && !path.isAbsolute(rel));
|
|
39
|
-
}
|
|
40
|
-
export function isGlobPatternWithOptionalRoot(g) {
|
|
41
|
-
return typeof g !== 'string' && typeof g.glob === 'string';
|
|
42
|
-
}
|
|
43
|
-
export function isGlobPatternWithRoot(g) {
|
|
44
|
-
return typeof g.root === 'string' && 'isGlobalPattern' in g;
|
|
45
|
-
}
|
|
46
|
-
export function isGlobPatternNormalized(g) {
|
|
47
|
-
if (!isGlobPatternWithOptionalRoot(g))
|
|
48
|
-
return false;
|
|
49
|
-
if (!isGlobPatternWithRoot(g))
|
|
50
|
-
return false;
|
|
51
|
-
const gr = g;
|
|
52
|
-
return 'rawGlob' in gr && 'rawRoot' in gr && typeof gr.rawGlob === 'string';
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* @param pattern glob pattern
|
|
56
|
-
* @param nested when true add `**/<glob>/**`
|
|
57
|
-
* @returns the set of matching globs.
|
|
58
|
-
*/
|
|
59
|
-
function normalizePattern(pattern, nested) {
|
|
60
|
-
pattern = pattern.replace(/^(!!)+/, '');
|
|
61
|
-
const isNeg = pattern.startsWith('!');
|
|
62
|
-
const prefix = isNeg ? '!' : '';
|
|
63
|
-
pattern = isNeg ? pattern.slice(1) : pattern;
|
|
64
|
-
const patterns = nested ? normalizePatternNested(pattern) : normalizePatternGeneral(pattern);
|
|
65
|
-
return patterns.map((p) => prefix + p);
|
|
66
|
-
}
|
|
67
|
-
function normalizePatternNested(pattern) {
|
|
68
|
-
// no slashes will match files names or folders
|
|
69
|
-
if (!pattern.includes('/')) {
|
|
70
|
-
if (pattern === '**')
|
|
71
|
-
return ['**'];
|
|
72
|
-
return ['**/' + pattern, '**/' + pattern + '/**'];
|
|
73
|
-
}
|
|
74
|
-
const hasLeadingSlash = pattern.startsWith('/');
|
|
75
|
-
pattern = hasLeadingSlash ? pattern.slice(1) : pattern;
|
|
76
|
-
if (pattern.endsWith('/')) {
|
|
77
|
-
// legacy behavior, if it only has a trailing slash, allow matching against a nested directory.
|
|
78
|
-
return hasLeadingSlash || pattern.slice(0, -1).includes('/') ? [pattern + '**/*'] : ['**/' + pattern + '**/*'];
|
|
79
|
-
}
|
|
80
|
-
if (pattern.endsWith('**')) {
|
|
81
|
-
return [pattern];
|
|
82
|
-
}
|
|
83
|
-
return [pattern, pattern + '/**'];
|
|
84
|
-
}
|
|
85
|
-
function normalizePatternGeneral(pattern) {
|
|
86
|
-
pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern;
|
|
87
|
-
pattern = pattern.endsWith('/') ? pattern + '**/*' : pattern;
|
|
88
|
-
return [pattern];
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
*
|
|
92
|
-
* @param patterns - glob patterns to normalize.
|
|
93
|
-
* @param options - Normalization options.
|
|
94
|
-
*/
|
|
95
|
-
export function normalizeGlobPatterns(patterns, options) {
|
|
96
|
-
function* normalize() {
|
|
97
|
-
for (const glob of patterns) {
|
|
98
|
-
if (isGlobPatternNormalized(glob)) {
|
|
99
|
-
yield glob;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
yield* normalizeGlobPattern(glob, options);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return [...normalize()];
|
|
106
|
-
}
|
|
107
|
-
export function normalizeGlobPattern(g, options) {
|
|
108
|
-
const { root, nodePath: path = Path, nested, cwd = Path.resolve() } = options;
|
|
109
|
-
g = !isGlobPatternWithOptionalRoot(g) ? { glob: g } : g;
|
|
110
|
-
const gr = { ...g, root: g.root ?? root };
|
|
111
|
-
const rawRoot = gr.root;
|
|
112
|
-
const rawGlob = g.glob;
|
|
113
|
-
gr.glob = gr.glob.trim(); // trimGlob(g.glob);
|
|
114
|
-
if (gr.glob.startsWith('${cwd}')) {
|
|
115
|
-
gr.glob = gr.glob.replace('${cwd}', '');
|
|
116
|
-
gr.root = '${cwd}';
|
|
117
|
-
}
|
|
118
|
-
if (gr.root.startsWith('${cwd}')) {
|
|
119
|
-
gr.root = path.resolve(gr.root.replace('${cwd}', cwd));
|
|
120
|
-
}
|
|
121
|
-
const isGlobalPattern = isGlobalGlob(gr.glob);
|
|
122
|
-
gr.root = path.resolve(root, path.normalize(gr.root));
|
|
123
|
-
const globs = normalizePattern(gr.glob, nested);
|
|
124
|
-
return globs.map((glob) => ({ ...gr, glob, rawGlob, rawRoot, isGlobalPattern }));
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Try to adjust the root of a glob to match a new root. If it is not possible, the original glob is returned.
|
|
128
|
-
* Note: this does NOT generate absolutely correct glob patterns. The results are intended to be used as a
|
|
129
|
-
* first pass only filter. Followed by testing against the original glob/root pair.
|
|
130
|
-
* @param glob - glob to map
|
|
131
|
-
* @param root - new root to use if possible
|
|
132
|
-
* @param path - Node Path modules to use (testing only)
|
|
133
|
-
*/
|
|
134
|
-
export function normalizeGlobToRoot(glob, root, path) {
|
|
135
|
-
function relToGlob(relativePath) {
|
|
136
|
-
return path.sep === '\\' ? relativePath.replaceAll('\\', '/') : relativePath;
|
|
137
|
-
}
|
|
138
|
-
if (glob.root === root) {
|
|
139
|
-
return glob;
|
|
140
|
-
}
|
|
141
|
-
const relFromRootToGlob = path.relative(root, glob.root);
|
|
142
|
-
if (!relFromRootToGlob) {
|
|
143
|
-
return glob;
|
|
144
|
-
}
|
|
145
|
-
if (glob.isGlobalPattern) {
|
|
146
|
-
return { ...glob, root };
|
|
147
|
-
}
|
|
148
|
-
const relFromGlobToRoot = path.relative(glob.root, root);
|
|
149
|
-
const globIsUnderRoot = relFromRootToGlob[0] !== '.' && !path.isAbsolute(relFromRootToGlob);
|
|
150
|
-
const rootIsUnderGlob = relFromGlobToRoot[0] !== '.' && !path.isAbsolute(relFromGlobToRoot);
|
|
151
|
-
// Root and Glob are not in the same part of the directory tree.
|
|
152
|
-
if (!globIsUnderRoot && !rootIsUnderGlob) {
|
|
153
|
-
return glob;
|
|
154
|
-
}
|
|
155
|
-
const isNeg = glob.glob.startsWith('!');
|
|
156
|
-
const g = isNeg ? glob.glob.slice(1) : glob.glob;
|
|
157
|
-
const prefix = isNeg ? '!' : '';
|
|
158
|
-
// prefix with root
|
|
159
|
-
if (globIsUnderRoot) {
|
|
160
|
-
const relGlob = relToGlob(relFromRootToGlob);
|
|
161
|
-
return {
|
|
162
|
-
...glob,
|
|
163
|
-
glob: prefix + posix.join(relGlob, g),
|
|
164
|
-
root,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
// The root is under the glob root
|
|
168
|
-
// The more difficult case, the glob is higher than the root
|
|
169
|
-
// A best effort is made, but does not do advanced matching.
|
|
170
|
-
const relGlob = relToGlob(relFromGlobToRoot) + '/';
|
|
171
|
-
const rebasedGlob = rebaseGlob(g, relGlob);
|
|
172
|
-
return rebasedGlob ? { ...glob, glob: prefix + rebasedGlob, root } : glob;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Rebase a glob string to a new prefix
|
|
176
|
-
* @param glob - glob string
|
|
177
|
-
* @param rebaseTo - glob prefix
|
|
178
|
-
*/
|
|
179
|
-
function rebaseGlob(glob, rebaseTo) {
|
|
180
|
-
if (!rebaseTo || rebaseTo === '/')
|
|
181
|
-
return glob;
|
|
182
|
-
if (glob.startsWith('**'))
|
|
183
|
-
return glob;
|
|
184
|
-
rebaseTo = rebaseTo.endsWith('/') ? rebaseTo : rebaseTo + '/';
|
|
185
|
-
if (glob.startsWith(rebaseTo)) {
|
|
186
|
-
return glob.slice(rebaseTo.length);
|
|
187
|
-
}
|
|
188
|
-
const relParts = rebaseTo.split('/');
|
|
189
|
-
const globParts = glob.split('/');
|
|
190
|
-
for (let i = 0; i < relParts.length && i < globParts.length; ++i) {
|
|
191
|
-
const relSeg = relParts[i];
|
|
192
|
-
const globSeg = globParts[i];
|
|
193
|
-
// the empty segment due to the end relGlob / allows for us to test against an empty segment.
|
|
194
|
-
if (!relSeg || globSeg === '**') {
|
|
195
|
-
return globParts.slice(i).join('/');
|
|
196
|
-
}
|
|
197
|
-
if (relSeg !== globSeg && globSeg !== '*') {
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return undefined;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Trims any trailing spaces, tabs, line-feeds, new-lines, and comments
|
|
205
|
-
* @param glob - glob string
|
|
206
|
-
* @returns trimmed glob
|
|
207
|
-
*/
|
|
208
|
-
function trimGlob(glob) {
|
|
209
|
-
glob = glob.replaceAll(/(?<!\\)#.*/g, '');
|
|
210
|
-
glob = trimGlobLeft(glob);
|
|
211
|
-
glob = trimGlobRight(glob);
|
|
212
|
-
return glob;
|
|
213
|
-
}
|
|
214
|
-
const spaces = {
|
|
215
|
-
' ': true,
|
|
216
|
-
'\t': true,
|
|
217
|
-
'\n': true,
|
|
218
|
-
'\r': true,
|
|
219
|
-
};
|
|
220
|
-
/**
|
|
221
|
-
* Trim any trailing spaces, tabs, line-feeds, or new-lines
|
|
222
|
-
* Handles a trailing \<space>
|
|
223
|
-
* @param glob - glob string
|
|
224
|
-
* @returns glob string with space to the right removed.
|
|
225
|
-
*/
|
|
226
|
-
function trimGlobRight(glob) {
|
|
227
|
-
const lenMin1 = glob.length - 1;
|
|
228
|
-
let i = lenMin1;
|
|
229
|
-
while (i >= 0 && glob[i] in spaces) {
|
|
230
|
-
--i;
|
|
231
|
-
}
|
|
232
|
-
if (glob[i] === '\\' && i < lenMin1) {
|
|
233
|
-
++i;
|
|
234
|
-
}
|
|
235
|
-
++i;
|
|
236
|
-
return i ? glob.slice(0, i) : '';
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Trim any leading spaces, tabs, line-feeds, or new-lines
|
|
240
|
-
* @param glob - any string
|
|
241
|
-
* @returns string with leading spaces removed.
|
|
242
|
-
*/
|
|
243
|
-
function trimGlobLeft(glob) {
|
|
244
|
-
let i = 0;
|
|
245
|
-
while (i < glob.length && glob[i] in spaces) {
|
|
246
|
-
++i;
|
|
247
|
-
}
|
|
248
|
-
return glob.slice(i);
|
|
249
|
-
}
|
|
250
|
-
function isGlobalGlob(glob) {
|
|
251
|
-
return isGlobalPatternRegExp.test(glob);
|
|
252
|
-
}
|
|
253
|
-
export const __testing__ = {
|
|
254
|
-
rebaseGlob,
|
|
255
|
-
trimGlob,
|
|
256
|
-
isGlobalGlob,
|
|
257
|
-
};
|
|
258
|
-
//# sourceMappingURL=globHelper.js.map
|
|
File without changes
|
|
File without changes
|
|
File without changes
|