stylelint-plugin-rhythmguard 3.2.0 → 3.4.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 +31 -0
- package/CONTRIBUTING.md +19 -3
- package/README.md +2 -1
- package/package.json +9 -8
- package/src/audit/args.js +103 -307
- package/src/audit/config.js +1 -1
- package/src/audit/contract.js +6 -28
- package/src/audit/index.js +4 -7
- package/src/audit/render-markdown.js +3 -0
- package/src/audit/render-text.js +2 -1
- package/src/audit/report.js +26 -19
- package/src/audit/scan/files.js +262 -0
- package/src/audit/scan/stylesheets.js +275 -0
- package/src/audit/scan/templates.js +175 -0
- package/src/cli/doctor.js +1 -1
- package/src/cli/quickstart.js +5 -2
- package/src/{utils → core}/length.js +24 -1
- package/src/{utils → core}/options.js +32 -108
- package/src/core/scale-inference.js +560 -0
- package/src/{utils → core}/token-packages.json +7 -0
- package/src/{utils → core}/token-sources.js +155 -17
- package/src/{utils/value-utils.js → core/value-nodes.js} +1 -17
- package/src/eslint/rules/tailwind-class-use-motion-scale.js +2 -2
- package/src/eslint/rules/tailwind-class-use-scale.js +2 -2
- package/src/rules/no-offscale-transform/index.js +22 -91
- package/src/rules/prefer-token/index.js +15 -73
- package/src/rules/report.js +75 -0
- package/src/rules/use-motion-scale/index.js +25 -42
- package/src/rules/use-scale/index.js +21 -94
- package/src/rules/validate.js +132 -0
- package/types/audit.d.ts +11 -0
- package/src/audit/scan.js +0 -676
- package/src/utils/scale-inference.js +0 -351
- /package/src/{utils/constants.js → core/css-vocabulary.js} +0 -0
- /package/src/{utils → core}/tailwind-class-analysis.js +0 -0
- /package/src/{utils → core}/tailwind-motion-analysis.js +0 -0
- /package/src/{utils → core}/time.js +0 -0
- /package/src/{utils → core}/token-map.js +0 -0
package/src/audit/contract.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const {
|
|
5
5
|
addDefinition,
|
|
6
|
-
|
|
6
|
+
collectCssTokens,
|
|
7
7
|
createTokenKindMatcher,
|
|
8
8
|
getNormalizedValueKeys,
|
|
9
|
-
} = require('../
|
|
9
|
+
} = require('../core/token-sources');
|
|
10
10
|
const { formatPath } = require('./shared');
|
|
11
11
|
|
|
12
12
|
function collectTokenContract({
|
|
@@ -95,33 +95,10 @@ function collectTokenContract({
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** Custom properties and Sass tokens declared in one stylesheet, added to the definitions map. */
|
|
98
99
|
function collectTokenDefinitions(source, file, definitions, matchesKind, baseFontSize) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
while ((match = declarationPattern.exec(source)) !== null) {
|
|
103
|
-
const token = match[1];
|
|
104
|
-
if (!matchesKind(token)) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
addDefinition(definitions, {
|
|
109
|
-
baseFontSize,
|
|
110
|
-
file,
|
|
111
|
-
source: file,
|
|
112
|
-
token,
|
|
113
|
-
value: match[2].trim(),
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
for (const sassToken of collectScssTokens(source, matchesKind)) {
|
|
118
|
-
addDefinition(definitions, {
|
|
119
|
-
baseFontSize,
|
|
120
|
-
file,
|
|
121
|
-
source: file,
|
|
122
|
-
token: sassToken.token,
|
|
123
|
-
value: sassToken.value,
|
|
124
|
-
});
|
|
100
|
+
for (const { scope, token, value } of collectCssTokens(source, matchesKind)) {
|
|
101
|
+
addDefinition(definitions, { baseFontSize, file, scope, source: file, token, value });
|
|
125
102
|
}
|
|
126
103
|
}
|
|
127
104
|
|
|
@@ -394,6 +371,7 @@ function toAuditContractReport(report) {
|
|
|
394
371
|
files: report.scale ? report.scale.files : [],
|
|
395
372
|
offScaleProperties: report.offScaleProperties || {},
|
|
396
373
|
offScaleValues: report.offScaleValues,
|
|
374
|
+
rejected: report.scale && report.scale.rejected ? report.scale.rejected : null,
|
|
397
375
|
source: report.scale ? report.scale.source : 'default',
|
|
398
376
|
tokenOpportunities: report.tokenOpportunities,
|
|
399
377
|
values: report.scale ? report.scale.values : null,
|
package/src/audit/index.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
toAuditContractReport,
|
|
8
|
-
} = require('../cli/audit');
|
|
9
|
-
const { parseTokenSources } = require('../utils/token-sources');
|
|
3
|
+
const { loadAuditConfig } = require('./config');
|
|
4
|
+
const { AUDIT_JSON_SCHEMA, toAuditContractReport } = require('./contract');
|
|
5
|
+
const { createAuditReport } = require('./report');
|
|
6
|
+
const { parseTokenSources } = require('../core/token-sources');
|
|
10
7
|
|
|
11
8
|
module.exports = {
|
|
12
9
|
AUDIT_JSON_SCHEMA,
|
|
@@ -4,6 +4,9 @@ const { sortCountMap } = require('./contract');
|
|
|
4
4
|
const { escapeMarkdown } = require('./render-utils');
|
|
5
5
|
|
|
6
6
|
function describeScaleSource(scale) {
|
|
7
|
+
if (scale.rejected) {
|
|
8
|
+
return `${scale.source} (${scale.rejected.source} rejected: ${scale.rejected.reasons.join(', ')})`;
|
|
9
|
+
}
|
|
7
10
|
return scale.files.length > 0
|
|
8
11
|
? `${scale.source} (${scale.files.join(', ')})`
|
|
9
12
|
: scale.source;
|
package/src/audit/render-text.js
CHANGED
|
@@ -28,7 +28,8 @@ function renderText(report) {
|
|
|
28
28
|
if (report.scale) {
|
|
29
29
|
lines.push(` Scale ${report.scale.values.join(', ')}`);
|
|
30
30
|
const files = report.scale.files.length > 0 ? ` (${report.scale.files.join(', ')})` : '';
|
|
31
|
-
|
|
31
|
+
const rejected = report.scale.rejected ? ` (${report.scale.rejected.source} rejected: ${report.scale.rejected.reasons.join(', ')})` : '';
|
|
32
|
+
lines.push(` Scale source ${report.scale.source}${files}${rejected}`);
|
|
32
33
|
}
|
|
33
34
|
lines.push('');
|
|
34
35
|
|
package/src/audit/report.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('node:fs');
|
|
|
5
5
|
const {
|
|
6
6
|
createTokenKindMatcher,
|
|
7
7
|
parseTokenSources,
|
|
8
|
-
} = require('../
|
|
8
|
+
} = require('../core/token-sources');
|
|
9
9
|
const {
|
|
10
10
|
applyBaselineComparison,
|
|
11
11
|
writeBaseline,
|
|
@@ -18,15 +18,15 @@ const {
|
|
|
18
18
|
} = require('./config');
|
|
19
19
|
const { buildReport, collectTokenDefinitions } = require('./contract');
|
|
20
20
|
const { DEFAULT_SCALE, formatPath } = require('./shared');
|
|
21
|
-
const { discoverTokenPackages, scaleFromDefinitions } = require('../utils/scale-inference');
|
|
22
21
|
const {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} = require('./scan');
|
|
22
|
+
assessScale,
|
|
23
|
+
discoverTokenPackages,
|
|
24
|
+
inferScaleFromDefinitions,
|
|
25
|
+
scaleFromDefinitions,
|
|
26
|
+
} = require('../core/scale-inference');
|
|
27
|
+
const { assertDirectory, getScanFiles } = require('./scan/files');
|
|
28
|
+
const { collectCssFindings, runStylelintAudit } = require('./scan/stylesheets');
|
|
29
|
+
const { collectTailwindFindings, collectTailwindMotionFindings } = require('./scan/templates');
|
|
30
30
|
|
|
31
31
|
async function createAuditReport(options) {
|
|
32
32
|
const parsed = normalizeCreateAuditOptions(options);
|
|
@@ -129,6 +129,7 @@ function resolveAuditScale({ baseFontSize, cssFiles, requested, tokenSourceResul
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
let rejected = null;
|
|
132
133
|
const definitions = new Map();
|
|
133
134
|
const matchesKind = createTokenKindMatcher('spacing');
|
|
134
135
|
for (const filePath of cssFiles) {
|
|
@@ -147,20 +148,26 @@ function resolveAuditScale({ baseFontSize, cssFiles, requested, tokenSourceResul
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
if (definitions.size > 0) {
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
151
|
+
const inferred = inferScaleFromDefinitions(definitions, baseFontSize);
|
|
152
|
+
if (inferred) {
|
|
153
|
+
const { values } = inferred;
|
|
152
154
|
const files = new Set();
|
|
153
|
-
for (const definition of definitions.values()) {
|
|
155
|
+
for (const definition of inferred.definitions.values()) {
|
|
154
156
|
for (const file of definition.files) {
|
|
155
157
|
files.add(file);
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
const sortedFiles = Array.from(files).sort();
|
|
161
|
+
const assessment = assessScale({ files: sortedFiles, source: 'scanned-css', values });
|
|
162
|
+
if (assessment.plausible) {
|
|
163
|
+
return {
|
|
164
|
+
files: sortedFiles,
|
|
165
|
+
source: 'scanned-css',
|
|
166
|
+
tokenCount: inferred.definitions.size,
|
|
167
|
+
values,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
rejected = { files: sortedFiles, reasons: assessment.reasons, source: 'scanned-css', values };
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
173
|
|
|
@@ -178,7 +185,7 @@ function resolveAuditScale({ baseFontSize, cssFiles, requested, tokenSourceResul
|
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
|
|
181
|
-
return { files: [], source: 'fallback', tokenCount: 0, values: DEFAULT_SCALE };
|
|
188
|
+
return { files: [], ...(rejected ? { rejected } : {}), source: 'fallback', tokenCount: 0, values: DEFAULT_SCALE };
|
|
182
189
|
}
|
|
183
190
|
|
|
184
191
|
function normalizeCreateAuditOptions(options = {}) {
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which files the audit looks at: directory walking, ignore patterns (root-relative
|
|
5
|
+
* paths and globs), git-scoped selection (--since, --staged), and the file-type
|
|
6
|
+
* predicates. Nothing here parses CSS.
|
|
7
|
+
*/
|
|
8
|
+
const { execFileSync } = require('node:child_process');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const {
|
|
12
|
+
SKIP_DIRS,
|
|
13
|
+
TEMPLATE_EXTENSIONS,
|
|
14
|
+
} = require('../shared');
|
|
15
|
+
|
|
16
|
+
function assertDirectory(dir) {
|
|
17
|
+
if (!dir) {
|
|
18
|
+
throw new Error('Missing audit directory.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const resolvedDir = path.resolve(dir);
|
|
22
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
23
|
+
throw new Error(`Directory not found: ${dir}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!fs.statSync(resolvedDir).isDirectory()) {
|
|
27
|
+
throw new Error(`Not a directory: ${dir}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return resolvedDir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function walkFiles(rootDir, ignorePatterns = []) {
|
|
34
|
+
const cssFiles = [];
|
|
35
|
+
const templateFiles = [];
|
|
36
|
+
const ignoreMatchers = createIgnoreMatchers(ignorePatterns);
|
|
37
|
+
|
|
38
|
+
function walk(currentDir) {
|
|
39
|
+
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
|
40
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
41
|
+
const relativePath = toPosixRelativePath(rootDir, fullPath);
|
|
42
|
+
|
|
43
|
+
if (shouldIgnorePath(relativePath, entry, ignoreMatchers)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
walk(fullPath);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (entry.isFile()) {
|
|
53
|
+
if (isCssFile(fullPath)) {
|
|
54
|
+
cssFiles.push(fullPath);
|
|
55
|
+
} else if (isTemplateFile(fullPath)) {
|
|
56
|
+
templateFiles.push(fullPath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
walk(rootDir);
|
|
63
|
+
return { cssFiles, templateFiles };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getScanFiles(rootDir, ignorePatterns, parsed) {
|
|
67
|
+
if (parsed.staged || parsed.since) {
|
|
68
|
+
return getGitChangedScanFiles(rootDir, ignorePatterns, parsed);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...walkFiles(rootDir, ignorePatterns),
|
|
73
|
+
scanScope: {
|
|
74
|
+
mode: 'full',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getGitChangedScanFiles(rootDir, ignorePatterns, parsed) {
|
|
80
|
+
const args = parsed.staged
|
|
81
|
+
? ['diff', '--name-only', '--cached', '--diff-filter=ACMR', '--']
|
|
82
|
+
: ['diff', '--name-only', '--diff-filter=ACMR', parsed.since, '--'];
|
|
83
|
+
let output = '';
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
output = execFileSync('git', args, {
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
90
|
+
});
|
|
91
|
+
} catch (err) {
|
|
92
|
+
const stderr = err.stderr ? String(err.stderr).trim() : err.message;
|
|
93
|
+
throw new Error(`Unable to read changed files from git: ${stderr}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cssFiles = [];
|
|
97
|
+
const templateFiles = [];
|
|
98
|
+
const ignoreMatchers = createIgnoreMatchers(ignorePatterns);
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const changedFiles = output.split(/\r?\n/)
|
|
101
|
+
.map((filePath) => filePath.trim())
|
|
102
|
+
.filter(Boolean);
|
|
103
|
+
|
|
104
|
+
for (const filePath of changedFiles) {
|
|
105
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
106
|
+
|
|
107
|
+
if (!isPathInside(rootDir, fullPath) || seen.has(fullPath) || !fs.existsSync(fullPath)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const stat = fs.statSync(fullPath);
|
|
112
|
+
if (!stat.isFile()) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const relativePath = toPosixRelativePath(rootDir, fullPath);
|
|
117
|
+
if (shouldIgnoreRelativeFile(relativePath, ignoreMatchers)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
seen.add(fullPath);
|
|
122
|
+
if (isCssFile(fullPath)) {
|
|
123
|
+
cssFiles.push(fullPath);
|
|
124
|
+
} else if (isTemplateFile(fullPath)) {
|
|
125
|
+
templateFiles.push(fullPath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
cssFiles,
|
|
131
|
+
scanScope: {
|
|
132
|
+
changedFiles: changedFiles.length,
|
|
133
|
+
mode: parsed.staged ? 'staged' : 'since',
|
|
134
|
+
since: parsed.since,
|
|
135
|
+
},
|
|
136
|
+
templateFiles,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isPathInside(rootDir, filePath) {
|
|
141
|
+
const relativePath = path.relative(rootDir, filePath);
|
|
142
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function shouldIgnorePath(relativePath, entry, ignoreMatchers) {
|
|
146
|
+
return ignoreMatchers.some((matcher) => matcher.test(relativePath))
|
|
147
|
+
|| (entry.isDirectory() && SKIP_DIRS.has(entry.name));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function shouldIgnoreRelativeFile(relativePath, ignoreMatchers) {
|
|
151
|
+
const segments = relativePath.split('/');
|
|
152
|
+
return segments.some((segment) => SKIP_DIRS.has(segment))
|
|
153
|
+
|| ignoreMatchers.some((matcher) => matcher.test(relativePath));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createIgnoreMatchers(patterns) {
|
|
157
|
+
const variants = new Set();
|
|
158
|
+
|
|
159
|
+
for (const pattern of patterns) {
|
|
160
|
+
addIgnorePatternVariants(variants, pattern);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return Array.from(variants, (pattern) => globToRegExp(pattern));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function addIgnorePatternVariants(variants, pattern) {
|
|
167
|
+
if (!pattern) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
variants.add(pattern);
|
|
172
|
+
|
|
173
|
+
if (!pattern.includes('/')) {
|
|
174
|
+
variants.add(`${pattern}/**`);
|
|
175
|
+
variants.add(`**/${pattern}`);
|
|
176
|
+
variants.add(`**/${pattern}/**`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (pattern.endsWith('/**')) {
|
|
181
|
+
variants.add(pattern.slice(0, -3));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!hasGlob(pattern)) {
|
|
186
|
+
variants.add(`${pattern}/**`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function hasGlob(pattern) {
|
|
191
|
+
return /[*?]/.test(pattern);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function globToRegExp(pattern) {
|
|
195
|
+
let source = '^';
|
|
196
|
+
|
|
197
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
198
|
+
const char = pattern[index];
|
|
199
|
+
const nextChar = pattern[index + 1];
|
|
200
|
+
|
|
201
|
+
if (char === '*' && nextChar === '*') {
|
|
202
|
+
source += '.*';
|
|
203
|
+
index++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (char === '*') {
|
|
208
|
+
source += '[^/]*';
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (char === '?') {
|
|
213
|
+
source += '[^/]';
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
source += escapeRegExp(char);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return new RegExp(`${source}$`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function escapeRegExp(value) {
|
|
224
|
+
return value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function toPosixRelativePath(rootDir, filePath) {
|
|
228
|
+
return path.relative(rootDir, filePath).split(path.sep).join('/');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isScssFile(filePath) {
|
|
232
|
+
return filePath.endsWith('.scss');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// "CSS files" in the audit means authored stylesheets: .css always, .scss too;
|
|
236
|
+
// whether .scss can be parsed is decided when the run starts (stylesheets.js).
|
|
237
|
+
function isCssFile(filePath) {
|
|
238
|
+
return filePath.endsWith('.css') || isScssFile(filePath);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isTemplateFile(filePath) {
|
|
242
|
+
return TEMPLATE_EXTENSIONS.has(path.extname(filePath));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = {
|
|
246
|
+
addIgnorePatternVariants,
|
|
247
|
+
assertDirectory,
|
|
248
|
+
createIgnoreMatchers,
|
|
249
|
+
escapeRegExp,
|
|
250
|
+
getGitChangedScanFiles,
|
|
251
|
+
getScanFiles,
|
|
252
|
+
globToRegExp,
|
|
253
|
+
hasGlob,
|
|
254
|
+
isCssFile,
|
|
255
|
+
isPathInside,
|
|
256
|
+
isScssFile,
|
|
257
|
+
isTemplateFile,
|
|
258
|
+
shouldIgnorePath,
|
|
259
|
+
shouldIgnoreRelativeFile,
|
|
260
|
+
toPosixRelativePath,
|
|
261
|
+
walkFiles,
|
|
262
|
+
};
|