-
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathindex.js
More file actions
148 lines (119 loc) · 4.14 KB
/
Copy pathindex.js
File metadata and controls
148 lines (119 loc) · 4.14 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { readFileSync, statSync } from 'node:fs';
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import escapeRegex from 'escape-string-regexp';
import { glob, globSync } from 'tinyglobby';
import { parse } from './lib/parse-variable.js';
const defaultOptions = {
ignore: [],
ignoreFiles: [],
fileExtensions: ['scss']
};
function buildGlobPattern(dir, options) {
return path.join(dir, `**/*.${options.fileExtensions}`).replaceAll('\\', '/');
}
async function findAsync(dirPath, opts = {}) {
const options = parseOptions(opts);
const dir = path.resolve(dirPath);
const dirStat = await stat(dir);
if (!dirStat.isDirectory()) {
throw new Error(`"${dir}": Not a valid directory!`);
}
// Array of all Sass files
const sassFiles = await glob(
buildGlobPattern(dir, options),
{ ignore: options.ignoreFiles, expandDirectories: false }
);
const parsePromises = sassFiles.map(file => parseFileAsync(file, options));
// Parsed content and variables from each file
const parsedFiles = await Promise.all(parsePromises);
return aggregateResults(parsedFiles);
}
function findSync(dirPath, opts = {}) {
const options = parseOptions(opts);
const dir = path.resolve(dirPath);
const dirStat = statSync(dir);
if (!dirStat.isDirectory()) {
throw new Error(`"${dir}": Not a valid directory!`);
}
// Array of all Sass files
const sassFiles = globSync(
buildGlobPattern(dir, options),
{ ignore: options.ignoreFiles, expandDirectories: false }
);
const parsedFiles = sassFiles.map(file => parseFileSync(file, options));
return aggregateResults(parsedFiles);
}
function aggregateResults(parsedFiles) {
const variables = [];
let combinedContent = '';
for (const result of parsedFiles) {
variables.push(...result.variables);
combinedContent += result.fileContent;
}
return filterVariables(combinedContent, variables);
}
async function parseFileAsync(file, options) {
const content = await readFile(file, 'utf8');
return parseFileContent(file, content, options.ignore);
}
function parseFileSync(file, options) {
const content = readFileSync(file, 'utf8');
return parseFileContent(file, content, options.ignore);
}
function parseFileContent(fileName, content, ignoreList) {
// Strip BOM mark if present, and remove front-matter comments (e.g. Jekyll, YAML)
const fileContent = (content.codePointAt(0) === 0xFE_FF ? content.slice(1) : content)
.replaceAll(/^---$/gm, '');
const variables = parse(fileName, fileContent, ignoreList);
return {
fileContent,
variables
};
}
function filterVariables(combinedContent, variables) {
const unusedVars = variables.filter(variable => {
const re = new RegExp(`(${escapeRegex(variable.name)})\\b(?!-)`, 'g');
return (combinedContent.match(re) ?? []).length === 1;
});
return {
unused: unusedVars,
total: variables.length
};
}
function parseOptions(opts) {
opts ??= {};
const options = {
ignore: opts.ignore ?? defaultOptions.ignore,
ignoreFiles: opts.ignoreFiles ?? defaultOptions.ignoreFiles,
fileExtensions: opts.fileExtensions ?? defaultOptions.fileExtensions
};
for (const option of ['ignore', 'ignoreFiles']) {
if (!Array.isArray(options[option])) {
throw new TypeError(`\`${option}\` should be an Array`);
}
}
// Trim list of ignored variables
options.ignore = options.ignore.map(val => val.trim());
// Trim list of ignored files
options.ignoreFiles = options.ignoreFiles.map(val => val.trim());
let extensions = options.fileExtensions;
extensions = Array.isArray(extensions) ? extensions : [extensions];
// Trim, strip a leading dot, and drop blanks
extensions = extensions
.map(extension => {
const trimmed = extension.trim();
return trimmed.startsWith('.') ? trimmed.slice(1) : trimmed;
})
.filter(Boolean);
if (extensions.length === 0) {
throw new TypeError('`fileExtensions` must contain at least one non-empty extension');
}
// The following is glob-specific syntax
options.fileExtensions = extensions.length > 1 ? `+(${extensions.join('|')})` : extensions[0];
return options;
}
export {
findSync as find,
findAsync
};