-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcreateFilePathFilter.ts
More file actions
108 lines (91 loc) · 3.46 KB
/
createFilePathFilter.ts
File metadata and controls
108 lines (91 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/*
* @Author: lzw
* @Date: 2022-09-07 09:04:38
* @LastEditors: renxia
* @LastEditTime: 2024-05-14 16:45:19
* @Description: create filter
* @see https://github.com/rollup/plugins/blob/master/packages/pluginutils/src/createFilter.ts
*/
import { resolve, posix, isAbsolute, extname } from 'node:path';
import { ensureArray } from '../common/objects';
import { normalizePath } from './path';
function getMatcherString(id: string, resolutionBase: string | false | null | undefined) {
if (resolutionBase === false || isAbsolute(id) || id.startsWith('*')) return normalizePath(id);
// resolve('') is valid and will default to process.cwd()
const basePath = normalizePath(resolve(resolutionBase || ''))
// escape all possible (posix + win) path characters that might interfere with regex
.replace(/[$()*+.?[\]^{|}-]/g, '\\$&');
// Note that we use posix.join because:
// 1. the basePath has been normalized to use /
// 2. the incoming glob (id) matcher, also uses /
// otherwise Node will force backslash (\) on windows
return posix.join(basePath, normalizePath(id));
}
/**
* A valid glob pattern, or array of patterns.
*/
export type FilterPattern = ReadonlyArray<string | RegExp> | string | RegExp | null;
export interface FilePathFilterOptions {
/** A pattern, or array of patterns, which specify the files should operate on. */
include?: FilterPattern;
/** A pattern, or array of patterns, which specify the files should ignore. */
exclude?: FilterPattern;
extensions?: string[];
/**
* custom glob matcher. For example:
* ### use picomatch
* ```ts
* import picomatch from 'picomatch';
*
* const globMatcher = (pathId, ruleIdNormalized, _ruleId) => {
* return picomatch(ruleIdNormalized, { dot: true })(pathId);
* };
* ```
* ### use micromatch
* ```ts
* import { isMatch } from 'micromatch';
*
* const globMatcher = (pathId, ruleIdNormalized, _ruleId) => {
* return isMatch(pathId, ruleIdNormalized, { dot: true });
* };
* ```
*/
globMatcher?: (pathId: string, ruleIdNormalized: string, ruleId: string) => boolean;
/** resolve with base path for options.include and options.exclude */
resolve?: string | false | null;
}
export function createFilePathFilter(options: FilePathFilterOptions = {}) {
let { extensions = [] } = options;
const getMatcher = (id: string | RegExp) => {
if (id instanceof RegExp) return id;
const pattern = getMatcherString(id, options.resolve);
return {
test: (what: string) => {
if (what.includes(id)) return true;
if (options.globMatcher) return options.globMatcher(what, pattern, id);
return false;
},
};
};
const includeMatchers = ensureArray(options.include).map(d => getMatcher(d));
const excludeMatchers = ensureArray(options.exclude).map(d => getMatcher(d));
if (extensions.length > 0) {
extensions = extensions.filter(Boolean).map(d => (d.startsWith('.') ? d : `.${d}`));
}
return function result(id: unknown): boolean {
if (typeof id !== 'string') return false;
if (/\0/.test(id)) return false;
const pathId = normalizePath(id);
if (extensions.length > 0) {
const ext = extname(id);
if (!ext || !extensions.includes(ext)) return false;
}
for (const matcher of excludeMatchers) {
if (matcher.test(pathId)) return false;
}
for (const matcher of includeMatchers) {
if (matcher.test(pathId)) return true;
}
return includeMatchers.length === 0;
};
}