fix(providence): normalization of native glob dirent.parentPath for optimisedGlob

This commit is contained in:
Thijs Louisse 2024-10-24 16:50:22 +02:00 committed by Thijs Louisse
parent 2d4fb0ecdb
commit 6f3137c963
3 changed files with 85 additions and 41 deletions

View file

@ -0,0 +1,5 @@
---
'providence-analytics': patch
---
fix normalization of native glob dirent.parentPath for optimisedGlob

View file

@ -144,7 +144,13 @@ function isRootGlob(glob) {
* @returns {string} * @returns {string}
*/ */
function direntToLocalPath(dirent, { cwd }) { function direntToLocalPath(dirent, { cwd }) {
const folder = (dirent.parentPath || dirent.path).replace(/(^.*)\/(.*\..*$)/, '$1'); const parentPath = toPosixPath(dirent.parentPath || dirent.path);
// Since `fs.glob` can return parent paths with files included, we need to strip the file part
const parts = parentPath.split('/');
const lastPart = parts[parts.length - 1];
const isLastPartFile = !lastPart.startsWith('.') && lastPart.split('.').length > 1;
const folder = isLastPartFile ? parts.slice(0, parts.length - 1).join('/') : parentPath;
return toPosixPath(path.join(folder, dirent.name)).replace( return toPosixPath(path.join(folder, dirent.name)).replace(
new RegExp(`^${toPosixPath(cwd)}/`), new RegExp(`^${toPosixPath(cwd)}/`),
'', '',
@ -270,7 +276,7 @@ const getAllDirentsRelativeToCwd = memoize(
/** /**
* @param {string|string[]} globOrGlobs * @param {string|string[]} globOrGlobs
* @param {{ fs: FsLike; cwd: string; exclude?: Function; stats?: boolean }} cfg * @param {Partial<FastGlobtions> & {exclude?:Function; stats: boolean; cwd:string}} cfg
* @returns {Promise<string[]|DirentWithPath[]>} * @returns {Promise<string[]|DirentWithPath[]>}
*/ */
async function nativeGlob(globOrGlobs, { fs, cwd, exclude, stats }) { async function nativeGlob(globOrGlobs, { fs, cwd, exclude, stats }) {
@ -288,6 +294,54 @@ async function nativeGlob(globOrGlobs, { fs, cwd, exclude, stats }) {
return results; return results;
} }
/**
*
* @param {{globs: string[]; ignoreGlobs: string[]; options: FastGlobtions; regularGlobs:string[]}} config
* @returns {Promise<string[]|void>}
*/
async function getNativeGlobResults({ globs, options, ignoreGlobs, regularGlobs }) {
const optionsNotSupportedByNativeGlob = ['onlyDirectories', 'dot'];
const hasGlobWithFullPath = globs.some(isRootGlob);
const doesConfigAllowNative =
!optionsNotSupportedByNativeGlob.some(opt => options[opt]) && !hasGlobWithFullPath;
if (!doesConfigAllowNative) return undefined;
const negativeGlobs = [...ignoreGlobs, ...regularGlobs.filter(r => r.startsWith('!'))].map(r =>
r.slice(1),
);
const negativeResults = negativeGlobs.length
? // @ts-ignore
/** @type {string[]} */ (await nativeGlob(negativeGlobs, options))
: [];
const positiveGlobs = regularGlobs.filter(r => !r.startsWith('!'));
const result = /** @type {DirentWithPath[]} */ (
await nativeGlob(positiveGlobs, {
cwd: /** @type {string} */ (options.cwd),
fs: options.fs,
stats: true,
// we cannot use the exclude option here, because it's not working correctly
})
);
const direntsFiltered = result.filter(
dirent =>
!negativeResults.includes(
direntToLocalPath(dirent, { cwd: /** @type {string} */ (options.cwd) }),
),
);
return postprocessOptions(
direntsFiltered.map(dirent => ({
dirent,
relativeToCwdPath: direntToLocalPath(dirent, { cwd: /** @type {string} */ (options.cwd) }),
})),
options,
);
}
/** /**
* Lightweight glob implementation. * Lightweight glob implementation.
* It's a drop-in replacement for globby, but it's faster, a few hundred lines of code and has no dependencies. * It's a drop-in replacement for globby, but it's faster, a few hundred lines of code and has no dependencies.
@ -326,40 +380,10 @@ export async function optimisedGlob(globOrGlobs, providedOptions = {}) {
); );
const globs = toUniqueArray([...regularGlobs, ...ignoreGlobs]); const globs = toUniqueArray([...regularGlobs, ...ignoreGlobs]);
const optionsNotSupportedByNativeGlob = ['onlyDirectories', 'dot']; if (isExperimentalFsGlobEnabled && options.fs?.promises.glob) {
const hasGlobWithFullPath = globs.some(isRootGlob); const params = { regularGlobs, ignoreGlobs, options, globs };
const doesConfigAllowNative = const nativeGlobResults = await getNativeGlobResults(params);
!optionsNotSupportedByNativeGlob.some(opt => options[opt]) && !hasGlobWithFullPath; if (nativeGlobResults) return nativeGlobResults;
if (isExperimentalFsGlobEnabled && options.fs.promises.glob && doesConfigAllowNative) {
const negativeGlobs = [...ignoreGlobs, ...regularGlobs.filter(r => r.startsWith('!'))].map(r =>
r.slice(1),
);
const negativeResults = negativeGlobs.length
? /** @type {string[]} */ (await nativeGlob(negativeGlobs, options))
: [];
const positiveGlobs = regularGlobs.filter(r => !r.startsWith('!'));
const result = /** @type {DirentWithPath[]} */ (
await nativeGlob(positiveGlobs, {
cwd: options.cwd,
fs: options.fs,
stats: true,
// we cannot use the exclude option here, because it's not working correctly
})
);
const direntsFiltered = result.filter(
dirent => !negativeResults.includes(direntToLocalPath(dirent, { cwd: options.cwd })),
);
return postprocessOptions(
direntsFiltered.map(dirent => ({
dirent,
relativeToCwdPath: direntToLocalPath(dirent, { cwd: options.cwd }),
})),
options,
);
} }
/** @type {RegExp[]} */ /** @type {RegExp[]} */
@ -390,7 +414,6 @@ export async function optimisedGlob(globOrGlobs, providedOptions = {}) {
const isRootPath = isRootGlob(startPath); const isRootPath = isRootGlob(startPath);
const cwd = isRootPath ? '/' : options.cwd; const cwd = isRootPath ? '/' : options.cwd;
const fullStartPath = path.join(cwd, startPath); const fullStartPath = path.join(cwd, startPath);
try { try {
const allDirEntsRelativeToCwd = await getAllDirentsRelativeToCwd(fullStartPath, { const allDirEntsRelativeToCwd = await getAllDirentsRelativeToCwd(fullStartPath, {
cwd, cwd,

View file

@ -40,8 +40,6 @@ async function runOptimisedGlobAndCheckGlobbyParity(patterns, options) {
); );
} }
console.debug({ optimisedGlobResult, globbyResult });
expect(optimisedGlobResult).to.deep.equal(globbyResult); expect(optimisedGlobResult).to.deep.equal(globbyResult);
return optimisedGlobResult; return optimisedGlobResult;
@ -74,6 +72,7 @@ function runSuiteForOptimisedGlob() {
'/fakeFs/my/folder/lvl1/lvl2/lvl3/some/anotherFile.d.ts': 'content', '/fakeFs/my/folder/lvl1/lvl2/lvl3/some/anotherFile.d.ts': 'content',
'/fakeFs/my/.hiddenFile.js': 'content', '/fakeFs/my/.hiddenFile.js': 'content',
'/fakeFs/my/.hiddenFolder/file.js': 'content',
}; };
mockFs(fakeFs); mockFs(fakeFs);
}); });
@ -275,9 +274,17 @@ function runSuiteForOptimisedGlob() {
}); });
it('"dot" allows hidden files" ', async () => { it('"dot" allows hidden files" ', async () => {
const files = await runOptimisedGlobAndCheckGlobbyParity('*/*', { ...testCfg, dot: true }); const files = await runOptimisedGlobAndCheckGlobbyParity('*/*', {
...testCfg,
dot: true,
});
expect(files).to.deep.equal(['my/.hiddenFile.js']); expect(files).to.deep.equal(['my/.hiddenFile.js']);
const files2 = await runOptimisedGlobAndCheckGlobbyParity('*/*/*', {
...testCfg,
dot: true,
});
expect(files2).to.deep.equal(['my/.hiddenFolder/file.js']);
}); });
it('"ignore" filters out files" ', async () => { it('"ignore" filters out files" ', async () => {
@ -335,6 +342,15 @@ function runSuiteForOptimisedGlob() {
// ); // );
// expect(files3).to.deep.equal(['/fakeFs/my/folder/lvl1/some/file.js']); // expect(files3).to.deep.equal(['/fakeFs/my/folder/lvl1/some/file.js']);
}); });
it('starts from hidden folders', async () => {
const files = await runOptimisedGlobAndCheckGlobbyParity('**/*', {
...testCfg,
cwd: '/fakeFs/my/.hiddenFolder',
dot: true,
});
expect(files).to.deep.equal(['file.js']);
});
}); });
}); });
} }