diff --git a/flow-typed/stylelint.js b/flow-typed/stylelint.js index 4e2f2764fc..826bbf3886 100644 --- a/flow-typed/stylelint.js +++ b/flow-typed/stylelint.js @@ -147,5 +147,6 @@ export type stylelint$standaloneOptions = { customSyntax?: string, formatter?: "compact" | "json" | "string" | "unix" | "verbose" | Function, disableDefaultIgnores?: boolean, - fix?: boolean + fix?: boolean, + allowEmptyInput: boolean }; diff --git a/lib/__tests__/ignore.test.js b/lib/__tests__/ignore.test.js index b5822bad00..f6f68aa66c 100644 --- a/lib/__tests__/ignore.test.js +++ b/lib/__tests__/ignore.test.js @@ -1,5 +1,6 @@ "use strict"; +const NoFilesFoundError = require("../utils/noFilesFoundError"); const path = require("path"); const standalone = require("../standalone"); @@ -191,8 +192,10 @@ describe("extending config with ignoreFiles glob ignoring one by negation", () = }); describe("specified `ignorePath` file ignoring one file", () => { - let results; + const files = [`${fixturesPath}/empty-block.css`]; + const noFilesErrorMessage = new NoFilesFoundError(files); let actualCwd; + let errorMessage; beforeAll(() => { actualCwd = process.cwd(); @@ -205,24 +208,28 @@ describe("specified `ignorePath` file ignoring one file", () => { beforeEach(() => { return standalone({ - files: [`${fixturesPath}/empty-block.css`], + files, config: { rules: { "block-no-empty": true } }, ignorePath: path.join(__dirname, "fixtures/ignore.txt") - }).then(data => (results = data.results)); + }).catch(actualError => { + errorMessage = actualError; + }); }); it("no files read", () => { - expect(results).toHaveLength(0); + expect(errorMessage).toEqual(noFilesErrorMessage); }); }); describe("specified `ignorePattern` file ignoring one file", () => { - let results; + const files = [`${fixturesPath}/empty-block.css`]; + const noFilesErrorMessage = new NoFilesFoundError(files); let actualCwd; + let errorMessage; beforeAll(() => { actualCwd = process.cwd(); @@ -235,24 +242,31 @@ describe("specified `ignorePattern` file ignoring one file", () => { beforeEach(() => { return standalone({ - files: [`${fixturesPath}/empty-block.css`], + files, config: { rules: { "block-no-empty": true } }, ignorePattern: "fixtures/empty-block.css" - }).then(data => (results = data.results)); + }).catch(actualError => { + errorMessage = actualError; + }); }); it("no files read", () => { - expect(results).toHaveLength(0); + expect(errorMessage).toEqual(noFilesErrorMessage); }); }); describe("specified `ignorePattern` file ignoring two files", () => { - let results; + const files = [ + `${fixturesPath}/empty-block.css`, + `${fixturesPath}/no-syntax-error.css` + ]; + const noFilesErrorMessage = new NoFilesFoundError(files); let actualCwd; + let errorMessage; beforeAll(() => { actualCwd = process.cwd(); @@ -265,10 +279,7 @@ describe("specified `ignorePattern` file ignoring two files", () => { beforeEach(() => { return standalone({ - files: [ - `${fixturesPath}/empty-block.css`, - `${fixturesPath}/no-syntax-error.css` - ], + files, config: { rules: { "block-no-empty": true @@ -278,11 +289,13 @@ describe("specified `ignorePattern` file ignoring two files", () => { "fixtures/empty-block.css", "fixtures/no-syntax-error.css" ] - }).then(data => (results = data.results)); + }).catch(actualError => { + errorMessage = actualError; + }); }); it("no files read", () => { - expect(results).toHaveLength(0); + expect(errorMessage).toEqual(noFilesErrorMessage); }); }); @@ -416,9 +429,11 @@ describe("using codeFilename and ignoreFiles with configBasedir", () => { }); describe("file in node_modules", () => { + const files = [`${fixturesPath}/node_modules/test.css`]; + const noFilesErrorMessage = new NoFilesFoundError(files); const lint = () => standalone({ - files: [`${fixturesPath}/node_modules/test.css`], + files, config: { rules: { "block-no-empty": true @@ -427,16 +442,18 @@ describe("file in node_modules", () => { }); it("is ignored", () => { - return lint().then(output => { - expect(output.results).toHaveLength(0); + return lint().catch(actualError => { + expect(actualError).toEqual(noFilesErrorMessage); }); }); }); describe("file in bower_components", () => { + const files = [`${fixturesPath}/bower_components/test.css`]; + const noFilesErrorMessage = new NoFilesFoundError(files); const lint = () => standalone({ - files: [`${fixturesPath}/bower_components/test.css`], + files, config: { rules: { "block-no-empty": true @@ -445,8 +462,8 @@ describe("file in bower_components", () => { }); it("is ignored", () => { - return lint().then(output => { - expect(output.results).toHaveLength(0); + return lint().catch(actualError => { + expect(actualError).toEqual(noFilesErrorMessage); }); }); }); diff --git a/lib/__tests__/standalone.test.js b/lib/__tests__/standalone.test.js index dfe1c85799..94adbc4792 100644 --- a/lib/__tests__/standalone.test.js +++ b/lib/__tests__/standalone.test.js @@ -1,6 +1,7 @@ "use strict"; const configBlockNoEmpty = require("./fixtures/config-block-no-empty"); +const NoFilesFoundError = require("../utils/noFilesFoundError"); const path = require("path"); const standalone = require("../standalone"); @@ -114,10 +115,23 @@ it("standalone without input css and file(s) should throw error", () => { ); }); -it("standalone with non-existent-file quietly exits", () => { +it("standalone with non-existent-file throws an error", () => { + const files = `${fixturesPath}/non-existent-file.css`; + const expectedError = new NoFilesFoundError(files); + return standalone({ - files: `${fixturesPath}/non-existent-file.css`, + files, config: configBlockNoEmpty + }).catch(actualError => { + expect(actualError).toEqual(expectedError); + }); +}); + +it("standalone with non-existent-file and allowEmptyInput enabled quietly exits", () => { + return standalone({ + files: `${fixturesPath}/non-existent-file.css`, + config: configBlockNoEmpty, + allowEmptyInput: true }).then(linted => { expect(typeof linted.output).toBe("string"); expect(linted.results).toHaveLength(0); diff --git a/lib/cli.js b/lib/cli.js index 8aa69ea6b1..a00537f4b2 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -125,7 +125,8 @@ const EXIT_CODE_ERROR = 2; stdinFilename: any, syntax: any, v: string, - version: string + version: string, + allowEmptyInput: boolean }, input: any, help: any, @@ -153,7 +154,8 @@ const EXIT_CODE_ERROR = 2; maxWarnings?: any, syntax?: any, disableDefaultIgnores?: any, - ignorePattern?: any + ignorePattern?: any, + allowEmptyInput?: boolean }*/ const meowOptions /*: meowOptionsType*/ = { @@ -278,6 +280,10 @@ const meowOptions /*: meowOptionsType*/ = { --version, -v Show the currently installed version of stylelint. + + --allow-empty-input + + When glob pattern matches no files, the process will exit without throwing an error. `, flags: { cache: { @@ -478,6 +484,10 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { return; } + if (cli.flags.allowEmptyInput) { + optionsBase.allowEmptyInput = cli.flags.allowEmptyInput; + } + return Promise.resolve() .then(() => { // Add input/code into options diff --git a/lib/standalone.js b/lib/standalone.js index 37da535903..a4f8f06555 100644 --- a/lib/standalone.js +++ b/lib/standalone.js @@ -14,6 +14,7 @@ const globby /*: Function*/ = require("globby"); const hash = require("./utils/hash"); const ignore = require("ignore"); const needlessDisables /*: Function*/ = require("./needlessDisables"); +const NoFilesFoundError = require("./utils/noFilesFoundError"); const path = require("path"); const pify = require("pify"); const pkg = require("../package.json"); @@ -42,6 +43,7 @@ module.exports = function( const reportNeedlessDisables = options.reportNeedlessDisables; const maxWarnings = options.maxWarnings; const syntax = options.syntax; + const allowEmptyInput = options.allowEmptyInput || false; const useCache = options.cache || false; let fileCache; const startTime = Date.now(); @@ -198,6 +200,10 @@ module.exports = function( ); if (!filePaths.length) { + if (!allowEmptyInput) { + throw new NoFilesFoundError(fileList); + } + return Promise.all([]); } diff --git a/lib/utils/noFilesFoundError.js b/lib/utils/noFilesFoundError.js new file mode 100644 index 0000000000..a91eb26515 --- /dev/null +++ b/lib/utils/noFilesFoundError.js @@ -0,0 +1,17 @@ +"use strict"; + +class NoFilesFoundError extends Error { + constructor(fileList) { + super(); + + if (typeof fileList === "string") { + fileList = [fileList]; + } + + const pattern = fileList.filter(i => !i.startsWith("!")).join(", "); + + this.message = `No files matching the pattern "${pattern}" were found.`; + } +} + +module.exports = NoFilesFoundError;