Skip to content

Commit 835cdc8

Browse files
committed
fixed regression if lint runs more than cssFilesRefreshRate
1 parent 4b21061 commit 835cdc8

File tree

1 file changed

+86
-15
lines changed

1 file changed

+86
-15
lines changed

lib/util/cssFiles.js

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
1+
// @ts-check
12
'use strict';
23

34
const fg = require('fast-glob');
45
const fs = require('fs');
56
const postcss = require('postcss');
6-
const removeDuplicatesFromArray = require('./removeDuplicatesFromArray');
77

8-
let previousGlobsResults = [];
8+
const classRegexp = /\.([^\.\,\s\n\:\(\)\[\]\'~\+\>\*\\]*)/gim;
9+
910
let lastUpdate = null;
1011
let classnamesFromFiles = [];
1112

13+
/**
14+
* @type {Map<string, {
15+
* timestamp: number;
16+
* classes: ReadonlySet<string>
17+
* }>}
18+
*/
19+
const prevFileCache = new Map();
20+
21+
const SetHelper = {
22+
/**
23+
*
24+
* @template T
25+
* @param {Set<T>} set
26+
* @param {Iterable<T>} items
27+
*/
28+
addMany: (set, items) => {
29+
for (const item of items) {
30+
set.add(item);
31+
}
32+
},
33+
};
34+
1235
/**
1336
* Read CSS files and extract classnames
1437
* @param {Array} patterns Glob patterns to locate files
@@ -17,26 +40,74 @@ let classnamesFromFiles = [];
1740
*/
1841
const generateClassnamesListSync = (patterns, refreshRate = 5_000) => {
1942
const now = new Date().getTime();
20-
const files = fg.sync(patterns, { suppressErrors: true });
21-
const newGlobs = previousGlobsResults.flat().join(',') != files.flat().join(',');
2243
const expired = lastUpdate === null || now - lastUpdate > refreshRate;
23-
if (newGlobs || expired) {
24-
previousGlobsResults = files;
25-
lastUpdate = now;
26-
let detectedClassnames = [];
27-
for (const file of files) {
44+
45+
if (!expired) {
46+
return classnamesFromFiles;
47+
}
48+
const files = fg.sync(patterns, { suppressErrors: true, stats: true });
49+
lastUpdate = now;
50+
51+
/**
52+
* @type {Set<string>}
53+
*/
54+
const detectedClassnames = new Set();
55+
/**
56+
* @type {Set<string>}
57+
*/
58+
const filesSet = new Set();
59+
for (const { path: file, stats } of files) {
60+
const prevData = prevFileCache.get(file);
61+
const timestamp = stats?.mtimeMs;
62+
/**
63+
* @type {ReadonlySet<string>}
64+
*/
65+
let classes;
66+
// file is not changed -> we do need to do extra work
67+
if (prevData && prevData?.timestamp === timestamp) {
68+
classes = prevData.classes;
69+
} else {
70+
/**
71+
* @type {Set<string>}
72+
*/
73+
const curClasses = new Set();
2874
const data = fs.readFileSync(file, 'utf-8');
2975
const root = postcss.parse(data);
3076
root.walkRules((rule) => {
31-
const regexp = /\.([^\.\,\s\n\:\(\)\[\]\'~\+\>\*\\]*)/gim;
32-
const matches = [...rule.selector.matchAll(regexp)];
33-
const classnames = matches.map((arr) => arr[1]);
34-
detectedClassnames.push(...classnames);
77+
for (const match of rule.selector.matchAll(classRegexp)) {
78+
curClasses.add(match[1]);
79+
}
3580
});
36-
detectedClassnames = removeDuplicatesFromArray(detectedClassnames);
81+
82+
classes = curClasses;
83+
84+
if (timestamp) {
85+
prevFileCache.set(file, {
86+
classes,
87+
timestamp,
88+
});
89+
}
90+
}
91+
92+
SetHelper.addMany(detectedClassnames, classes);
93+
filesSet.add(file);
94+
}
95+
// avoiding memory leak
96+
{
97+
/**
98+
* @type {string[]}
99+
*/
100+
const keysToDelete = [];
101+
for (const cachedFilePath of prevFileCache.keys()) {
102+
if (!filesSet.has(cachedFilePath)) {
103+
keysToDelete.push(cachedFilePath);
104+
}
105+
}
106+
for (const key of keysToDelete) {
107+
prevFileCache.delete(key);
37108
}
38-
classnamesFromFiles = detectedClassnames;
39109
}
110+
classnamesFromFiles = [...detectedClassnames];
40111
return classnamesFromFiles;
41112
};
42113

0 commit comments

Comments
 (0)