Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 54 additions & 20 deletions lib/util/cssFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
const fg = require('fast-glob');
const fs = require('fs');
const postcss = require('postcss');
const removeDuplicatesFromArray = require('./removeDuplicatesFromArray');

let previousGlobsResults = [];
const classRegexp = /\.([^\.\,\s\n\:\(\)\[\]\'~\+\>\*\\]*)/gim;

let lastUpdate = null;
let classnamesFromFiles = [];

/**
* @type {Map<string, number}
*/
const prevEditedTimestamp = new Map()

/**
* Read CSS files and extract classnames
* @param {Array} patterns Glob patterns to locate files
Expand All @@ -17,27 +22,56 @@ let classnamesFromFiles = [];
*/
const generateClassnamesListSync = (patterns, refreshRate = 5_000) => {
const now = new Date().getTime();
const files = fg.sync(patterns, { suppressErrors: true });
const newGlobs = previousGlobsResults.flat().join(',') != files.flat().join(',');
const expired = lastUpdate === null || now - lastUpdate > refreshRate;
if (newGlobs || expired) {
previousGlobsResults = files;
lastUpdate = now;
let detectedClassnames = [];
for (const file of files) {
const data = fs.readFileSync(file, 'utf-8');
const root = postcss.parse(data);
root.walkRules((rule) => {
const regexp = /\.([^\.\,\s\n\:\(\)\[\]\'~\+\>\*\\]*)/gim;
const matches = [...rule.selector.matchAll(regexp)];
const classnames = matches.map((arr) => arr[1]);
detectedClassnames.push(...classnames);
});
detectedClassnames = removeDuplicatesFromArray(detectedClassnames);

if (!expired) {
return classnamesFromFiles;
}
const files = fg.sync(patterns, { suppressErrors: true, stats: true });
lastUpdate = now;

/**
* @type {Set<string}
*/
const detectedClassnames = new Set();
/**
* @type {Set<string>}
*/
const filesSet = new Set();
for (const { path: file, stats } of files) {
filesSet.add(file);
if (!stats) {}
// file is not changed -> we do need to do extra work
else if (prevEditedTimestamp.get(file) === stats.mtimeMs) {
continue;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@XantreDev @francoismassart This seems to have introduced a regression if the linter takes longer than cssFilesRefreshRate to complete.

When iterating through the css files to parse for class names, if the file is unchanged we skip it, meaning that none of the classnames in any of the CSS files are honored.

skipping file .../globals.css
{ classnamesFromFiles: [] }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Detected class names need to be also cached

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@snyamathi Should it resolve the problem? #341

} else {
prevEditedTimestamp.set(file, stats.mtimeMs);
}
classnamesFromFiles = detectedClassnames;
const data = fs.readFileSync(file, 'utf-8');
const root = postcss.parse(data);
root.walkRules((rule) => {
for (const match of rule.selector.matchAll(classRegexp)) {
detectedClassnames.add(match[1]);
}
});
}
return classnamesFromFiles;
// avoiding memory leak
{
/**
* @type {string[]}
*/
const keysToDelete = []
for (const cachedFilePath of prevEditedTimestamp.keys()) {
if (!filesSet.has(cachedFilePath)) {
keysToDelete.push(cachedFilePath);
}
}
for (const key of keysToDelete) {
prevEditedTimestamp.delete(key);
}
}
classnamesFromFiles = [...detectedClassnames];
return classnamesFromFiles

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ends up being an empty array since we hit the file is not changed block for every file.

};

module.exports = generateClassnamesListSync;
3 changes: 2 additions & 1 deletion tests/lib/rules/no-custom-classname.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ ruleTester.run("no-custom-classname", rule, {
</div>`,
options: [
{
cssFiles: ["./tests/lib/**/*.css"],
cssFiles: ["./tests/**/*.css"],
cssFilesRefreshRate: 0,
},
],
},
Expand Down