From 877ab4cc66dfa51f5d8d14c89aeadc3ea41a1916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iiro=20J=C3=A4ppinen?= Date: Mon, 18 Apr 2022 11:04:16 +0300 Subject: [PATCH] fix: restore functionality of parent globs for a single configuration file Parent globs like `../*.js` were broken when introducing support for multiple configuration files in version 12.2.0. This is because lint-staged incorrectly grouped all files inside a single configuration's base directory to belong to that config, meaning files can only match a single configuration. This has now been fixed so that when a configuration has a parent glob, it will receive all staged files. Currently this means there can only be a single configuration file containing a parent glob, because all files will belong to that config. This aims to restore the previous behaviour, where only a single configuration file was supported. It shouldn't be necessary to use parent globs anymore, because they can be replaced by using multiple configuration files instead. --- lib/generateTasks.js | 5 ++--- lib/groupFilesByConfig.js | 22 ++++++++++++++-------- test/integration.test.js | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/lib/generateTasks.js b/lib/generateTasks.js index 164d69b49..99e3e91cf 100644 --- a/lib/generateTasks.js +++ b/lib/generateTasks.js @@ -21,9 +21,7 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f const relativeFiles = files.map((file) => normalize(path.relative(cwd, file))) - return Object.entries(config).map(([rawPattern, commands]) => { - let pattern = rawPattern - + return Object.entries(config).map(([pattern, commands]) => { const isParentDirPattern = pattern.startsWith('../') // Only worry about children of the CWD unless the pattern explicitly @@ -40,6 +38,7 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f // match against filenames in every directory. This makes `*.js` // match both `test.js` and `subdirectory/test.js`. matchBase: !pattern.includes('/'), + posixSlashes: true, strictBrackets: true, }) diff --git a/lib/groupFilesByConfig.js b/lib/groupFilesByConfig.js index a63836ab7..8d734bad8 100644 --- a/lib/groupFilesByConfig.js +++ b/lib/groupFilesByConfig.js @@ -30,18 +30,24 @@ export const groupFilesByConfig = async ({ configs, files }) => { return relative && !relative.startsWith('..') && !path.isAbsolute(relative) } - const scopedFiles = new Set() + /** This config should match all files since it has a parent glob */ + const includeAllFiles = Object.keys(config).some((glob) => glob.startsWith('..')) + + const scopedFiles = new Set(includeAllFiles ? filesSet : undefined) /** - * If file is inside the config file's directory, assign it to that configuration - * and remove it from the set. This means only one configuration can match a file. + * Without a parent glob, if file is inside the config file's directory, + * assign it to that configuration. */ - filesSet.forEach((file) => { - if (isInsideDir(file)) { - scopedFiles.add(file) - } - }) + if (!includeAllFiles) { + filesSet.forEach((file) => { + if (isInsideDir(file)) { + scopedFiles.add(file) + } + }) + } + /** Files should only match a single config */ scopedFiles.forEach((file) => { filesSet.delete(file) }) diff --git a/test/integration.test.js b/test/integration.test.js index 6c0064daa..8d427a0fa 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -1217,7 +1217,7 @@ describe('lint-staged', () => { await gitCommit({ shell: true, cwd: path.join(cwd, 'deeper') }) // 'file.js' was ignored - expect(await readFile('file.js')).toMatch('') + expect(await readFile('file.js')).toEqual('') // 'deeper/file.js' matched 'deeper/.lintstagedrc.json' expect(await readFile('deeper/file.js')).toMatch('level-1') @@ -1229,7 +1229,40 @@ describe('lint-staged', () => { expect(await readFile('deeper/even/deeper/file.js')).toMatch('level-2') // 'a/very/deep/file/path/file.js' was ignored - expect(await readFile('a/very/deep/file/path/file.js')).toMatch('') + expect(await readFile('a/very/deep/file/path/file.js')).toEqual('') + }) + + it('should support parent globs', async () => { + // Add some empty files + await writeFile('file.js', '') + await writeFile('deeper/file.js', '') + await writeFile('deeper/even/file.js', '') + await writeFile('deeper/even/deeper/file.js', '') + await writeFile('a/very/deep/file/path/file.js', '') + + // Include single-level parent glob in deeper config + await writeFile( + 'deeper/even/.lintstagedrc.cjs', + `module.exports = { '../*.js': (files) => files.map((f) => \`echo level-2 > \${f}\`) }` + ) + + // Stage all files + await execGit(['add', '.']) + + // Run lint-staged with `--shell` so that tasks do their thing + // Run in 'deeper/' so that root config is ignored + await gitCommit({ shell: true, cwd: path.join(cwd, 'deeper/even') }) + + // Two levels above, no match + expect(await readFile('file.js')).toEqual('') + + // One level above, match + expect(await readFile('deeper/file.js')).toMatch('level-2') + + // Not directly in the above-level, no match + expect(await readFile('deeper/even/file.js')).toEqual('') + expect(await readFile('deeper/even/deeper/file.js')).toEqual('') + expect(await readFile('a/very/deep/file/path/file.js')).toEqual('') }) it('should not care about staged file outside current cwd with another staged file', async () => {