diff --git a/__mocks__/fs.js b/__mocks__/fs.js index 52562f0..00917f3 100644 --- a/__mocks__/fs.js +++ b/__mocks__/fs.js @@ -10,10 +10,14 @@ const readFileSync = jest.fn(filePath => { return '{ "rules": { "semi": "error" } }'; } - if (filePath.indexOf("eslintignore")) { - return "*ignored*\n**/ignored/**\n"; + if (filePath.indexOf("eslintignore") >= 0) { + return "**/*eslintignored*\n"; + } + + if (filePath.indexOf("prettierignore") >= 0) { + return "**/*prettierignored*\n"; } else { - throw new Error("readFileSync mock does nto yet handle ", filePath); + throw new Error("readFileSync mock does not yet handle ", filePath); } }); diff --git a/__mocks__/glob.js b/__mocks__/glob.js index 4e27191..a85d6bf 100644 --- a/__mocks__/glob.js +++ b/__mocks__/glob.js @@ -35,14 +35,21 @@ module.exports = jest.fn(function mockGlob(globString, options, callback) { fredProject("no-change/2.js"), fredProject("no-change/3.js") ]); - } else if (globString.includes("ignored")) { + } else if (globString.includes("eslintignored")) { callback(null, [ - fredProject("ignored1.js"), - fredProject("ignored2.js"), - fredProject("ignored3.js"), + fredProject("eslintignored1.js"), + fredProject("eslintignored2.js"), + fredProject("eslintignored3.js"), fredProject("applied4.js") ]); - } else if (globString.includes("no-eslint-ignore")) { + } else if (globString.includes("prettierignored")) { + callback(null, [ + fredProject("prettierignored1.js"), + fredProject("prettierignored2.js"), + fredProject("prettierignored3.js"), + fredProject("applied4.js") + ]); + } else if (globString.includes("no-ignore")) { callback(null, [ barneyProject("no-ignore/1.js"), barneyProject("no-ignore/2.js"), diff --git a/src/format-files.js b/src/format-files.js index a3ffe87..5ba31ac 100644 --- a/src/format-files.js +++ b/src/format-files.js @@ -20,9 +20,14 @@ const LINE_SEPERATOR_REGEX = /(\r|\n|\r\n)/; const rxGlob = Observable.bindNodeCallback(glob); const rxReadFile = Observable.bindNodeCallback(fs.readFile); const rxWriteFile = Observable.bindNodeCallback(fs.writeFile); -const findUpSyncMemoized = memoize(findUpSync, function resolver(...args) { - return args.join("::"); -}); +const findUpEslintignoreSyncMemoized = memoize( + findUpEslintignoreSync, + findUpMemoizeResolver +); +const findUpPrettierignoreSyncMemoized = memoize( + findUpPrettierignoreSync, + findUpMemoizeResolver +); const getIsIgnoredMemoized = memoize(getIsIgnored); @@ -46,6 +51,7 @@ function formatFilesFromArgv({ prettierPath, ignore: ignoreGlobs = [], eslintIgnore: applyEslintIgnore = true, + prettierIgnore: applyPrettierIgnore = true, eslintConfigPath, prettierLast, ...prettierOptions @@ -71,13 +77,14 @@ function formatFilesFromArgv({ if (stdin) { return formatStdin({ filePath: stdinFilepath, ...prettierESLintOptions }); } else { - return formatFilesFromGlobs( + return formatFilesFromGlobs({ fileGlobs, - [...ignoreGlobs], // make a copy to avoid manipulation + ignoreGlobs: [...ignoreGlobs], // make a copy to avoid manipulation cliOptions, prettierESLintOptions, - applyEslintIgnore - ); + applyEslintIgnore, + applyPrettierIgnore + }); } } @@ -97,13 +104,14 @@ async function formatStdin(prettierESLintOptions) { } } -function formatFilesFromGlobs( +function formatFilesFromGlobs({ fileGlobs, ignoreGlobs, cliOptions, prettierESLintOptions, - applyEslintIgnore -) { + applyEslintIgnore, + applyPrettierIgnore +}) { const concurrentGlobs = 3; const concurrentFormats = 10; return new Promise(resolve => { @@ -112,7 +120,12 @@ function formatFilesFromGlobs( const unchanged = []; Observable.from(fileGlobs) .mergeMap( - getFilesFromGlob.bind(null, ignoreGlobs, applyEslintIgnore), + getFilesFromGlob.bind( + null, + ignoreGlobs, + applyEslintIgnore, + applyPrettierIgnore + ), null, concurrentGlobs ) @@ -184,7 +197,12 @@ function formatFilesFromGlobs( }); } -function getFilesFromGlob(ignoreGlobs, applyEslintIgnore, fileGlob) { +function getFilesFromGlob( + ignoreGlobs, + applyEslintIgnore, + applyPrettierIgnore, + fileGlob +) { const globOptions = { ignore: ignoreGlobs }; if (!fileGlob.includes("node_modules")) { // basically, we're going to protect you from doing something @@ -193,9 +211,15 @@ function getFilesFromGlob(ignoreGlobs, applyEslintIgnore, fileGlob) { } return rxGlob(fileGlob, globOptions).map(filePaths => { return filePaths.filter(filePath => { - return applyEslintIgnore - ? !isFilePathMatchedByEslintignore(filePath) - : true; + if (applyEslintIgnore && isFilePathMatchedByEslintignore(filePath)) { + return false; + } + + if (applyPrettierIgnore && isFilePathMatchedByPrettierignore(filePath)) { + return false; + } + + return true; }); }); } @@ -243,7 +267,7 @@ function formatFile(filePath, prettierESLintOptions, cliOptions) { function getNearestEslintignorePath(filePath) { const { dir } = path.parse(filePath); - return findUpSyncMemoized(".eslintignore", dir); + return findUpEslintignoreSyncMemoized(".eslintignore", dir); } function isFilePathMatchedByEslintignore(filePath) { @@ -261,10 +285,38 @@ function isFilePathMatchedByEslintignore(filePath) { return isIgnored(filePathRelativeToEslintignoreDir); } -function findUpSync(filename, cwd) { +function getNearestPrettierignorePath(filePath) { + const { dir } = path.parse(filePath); + return findUpPrettierignoreSyncMemoized(".prettierignore", dir); +} + +function isFilePathMatchedByPrettierignore(filePath) { + const prettierignorePath = getNearestPrettierignorePath(filePath); + if (!prettierignorePath) { + return false; + } + + const prettierignoreDir = path.parse(prettierignorePath).dir; + const filePathRelativeToPrettierignoreDir = path.relative( + prettierignoreDir, + filePath + ); + const isIgnored = getIsIgnoredMemoized(prettierignorePath); + return isIgnored(filePathRelativeToPrettierignoreDir); +} + +function findUpMemoizeResolver(...args) { + return args.join("::"); +} + +function findUpEslintignoreSync(filename, cwd) { return findUp.sync(".eslintignore", { cwd }); } +function findUpPrettierignoreSync(filename, cwd) { + return findUp.sync(".prettierignore", { cwd }); +} + function getIsIgnored(filename) { const ignoreLines = fs .readFileSync(filename, "utf8") diff --git a/src/format-files.test.js b/src/format-files.test.js index 75b0a4f..d974b71 100644 --- a/src/format-files.test.js +++ b/src/format-files.test.js @@ -194,7 +194,7 @@ test("allows you to specify an ignore glob", async () => { }); test("wont modify a file if it is eslint ignored", async () => { - await formatFiles({ _: ["src/**/ignored*.js"], write: true }); + await formatFiles({ _: ["src/**/eslintignored*.js"], write: true }); expect(fsMock.readFile).toHaveBeenCalledTimes(1); expect(fsMock.writeFile).toHaveBeenCalledTimes(1); expect(fsMock.readFile).toHaveBeenCalledWith( @@ -213,7 +213,7 @@ test("wont modify a file if it is eslint ignored", async () => { test("will modify a file if it is eslint ignored with noIgnore", async () => { await formatFiles({ - _: ["src/**/ignored*.js"], + _: ["src/**/eslintignored*.js"], write: true, eslintIgnore: false }); @@ -223,12 +223,42 @@ test("will modify a file if it is eslint ignored with noIgnore", async () => { expect(console.error).toHaveBeenCalledWith(ignoredOutput); }); -test("will not blow up if an .eslintignore cannot be found", async () => { +test("wont modify a file if it is prettier ignored", async () => { + await formatFiles({ _: ["src/**/prettierignored*.js"], write: true }); + expect(fsMock.readFile).toHaveBeenCalledTimes(1); + expect(fsMock.writeFile).toHaveBeenCalledTimes(1); + expect(fsMock.readFile).toHaveBeenCalledWith( + expect.stringMatching(/applied/), + "utf8", + expect.any(Function) + ); + expect(fsMock.writeFile).toHaveBeenCalledWith( + expect.stringMatching(/applied/), + expect.stringMatching(/MOCK_OUTPUT.*?applied/), + expect.any(Function) + ); + const ignoredOutput = expect.stringMatching(/success.*1.*file/); + expect(console.error).toHaveBeenCalledWith(ignoredOutput); +}); + +test("will modify a file if it is prettier ignored with noIgnore", async () => { + await formatFiles({ + _: ["src/**/prettierignored*.js"], + write: true, + prettierIgnore: false + }); + expect(fsMock.readFile).toHaveBeenCalledTimes(4); + expect(fsMock.writeFile).toHaveBeenCalledTimes(4); + const ignoredOutput = expect.stringMatching(/success.*4.*files/); + expect(console.error).toHaveBeenCalledWith(ignoredOutput); +}); + +test("will not blow up if an .eslintignore or .prettierignore cannot be found", async () => { const originalSync = findUpMock.sync; findUpMock.sync = () => null; try { await formatFiles({ - _: ["src/**/no-eslint-ignore/*.js"], + _: ["src/**/no-ignore/*.js"], write: true }); } catch (error) { diff --git a/src/parser.js b/src/parser.js index d9bc5d4..bcb2a43 100644 --- a/src/parser.js +++ b/src/parser.js @@ -39,6 +39,15 @@ const parser = yargs (can use --no-eslint-ignore to disable this) ` }, + "prettier-ignore": { + default: true, + type: "boolean", + describe: oneLine` + Only format matching files even if + they are not ignored by .prettierignore. + (can use --no-prettier-ignore to disable this) + ` + }, "list-different": { default: false, type: "boolean",