diff --git a/jest-setup.js b/jest-setup.js index 86a66415f4..148a89b2a9 100644 --- a/jest-setup.js +++ b/jest-setup.js @@ -32,6 +32,7 @@ global.testRule = (rule, schema) => { }; let passingTestCases = schema.accept || []; + if (!schema.skipBasicChecks) { passingTestCases = passingTestCases.concat(basicChecks); } @@ -40,6 +41,7 @@ global.testRule = (rule, schema) => { describe("accept", () => { passingTestCases.forEach(testCase => { const spec = testCase.only ? it.only : it; + describe(JSON.stringify(schema.config, replacer), () => { describe(JSON.stringify(testCase.code), () => { spec(testCase.description || "no description", () => { @@ -48,15 +50,18 @@ global.testRule = (rule, schema) => { config: stylelintConfig, syntax: schema.syntax }; + return stylelint(options).then(output => { expect(output.results[0].warnings).toEqual([]); expect(output.results[0].parseErrors).toEqual([]); + if (!schema.fix) return; // Check the fix return stylelint(Object.assign({ fix: true }, options)).then( output => { const fixedCode = getOutputCss(output); + expect(fixedCode).toBe(testCase.code); } ); @@ -72,6 +77,7 @@ global.testRule = (rule, schema) => { describe("reject", () => { schema.reject.forEach(testCase => { const spec = testCase.only ? it.only : it; + describe(JSON.stringify(schema.config, replacer), () => { describe(JSON.stringify(testCase.code), () => { spec(testCase.description || "no description", () => { @@ -80,6 +86,7 @@ global.testRule = (rule, schema) => { config: stylelintConfig, syntax: schema.syntax }; + return stylelint(options).then(output => { const warning = output.results[0].warnings[0]; @@ -89,16 +96,22 @@ global.testRule = (rule, schema) => { if (testCase.message !== undefined) { expect(_.get(warning, "text")).toBe(testCase.message); } + if (testCase.line !== undefined) { expect(_.get(warning, "line")).toBe(testCase.line); } + if (testCase.column !== undefined) { expect(_.get(warning, "column")).toBe(testCase.column); } if (!schema.fix) return; - if (!testCase.fixed && testCase.fixed !== "" && !testCase.unfixable) { + if ( + !testCase.fixed && + testCase.fixed !== "" && + !testCase.unfixable + ) { throw new Error( "If using { fix: true } in test schema, all reject cases must have { fixed: .. }" ); @@ -108,6 +121,7 @@ global.testRule = (rule, schema) => { return stylelint(Object.assign({ fix: true }, options)) .then(output => { const fixedCode = getOutputCss(output); + if (!testCase.unfixable) { expect(fixedCode).toBe(testCase.fixed); expect(fixedCode).not.toBe(testCase.code); @@ -116,8 +130,10 @@ global.testRule = (rule, schema) => { if (testCase.fixed) { expect(fixedCode).toBe(testCase.fixed); } + expect(fixedCode).toBe(testCase.code); } + return { fixedCode, warnings: output.results[0].warnings @@ -151,10 +167,12 @@ global.testRule = (rule, schema) => { function getOutputCss(output) { const result = output.results[0]._postcssResult; const css = result.root.toString(result.opts.syntax); + if (result.opts.syntax === less) { // Less needs us to manually strip whitespace at the end of single-line comments ¯\_(ツ)_/¯ return css.replace(/(\n?\s*\/\/.*?)[ \t]*(\r?\n)/g, "$1$2"); } + return css; } diff --git a/lib/__tests__/createLinter.test.js b/lib/__tests__/createLinter.test.js index 6176de7ae6..eee476c4a4 100644 --- a/lib/__tests__/createLinter.test.js +++ b/lib/__tests__/createLinter.test.js @@ -10,6 +10,7 @@ it("createLinter().getConfigForFile augmented config loads", () => { __dirname, "fixtures/getConfigForFile/a/b/foo.css" ); + return linter.getConfigForFile(filepath).then(result => { expect(result).toEqual({ config: { diff --git a/lib/__tests__/defaultSeverity.test.js b/lib/__tests__/defaultSeverity.test.js index 64407ed53d..46f78288ee 100644 --- a/lib/__tests__/defaultSeverity.test.js +++ b/lib/__tests__/defaultSeverity.test.js @@ -9,10 +9,12 @@ it("`defaultSeverity` option set to warning", () => { "block-no-empty": true } }; + return postcssPlugin .process("a {}", { from: undefined }, config) .then(result => { const warnings = result.warnings(); + expect(warnings).toHaveLength(1); expect(warnings[0].text.indexOf("block-no-empty")).not.toBe(-1); expect(warnings[0].severity).toBe("warning"); diff --git a/lib/__tests__/disableRanges.test.js b/lib/__tests__/disableRanges.test.js index b0ecb1aa3a..cac168117e 100644 --- a/lib/__tests__/disableRanges.test.js +++ b/lib/__tests__/disableRanges.test.js @@ -233,6 +233,7 @@ it("SCSS // line-disabling comment", () => { const scssSource = `a { color: pink !important; // stylelint-disable-line declaration-no-important }`; + return postcss() .use(assignDisabledRanges) .process(scssSource, { syntax: scss, from: undefined }) @@ -253,6 +254,7 @@ it("Less // line-disabling comment", () => { const lessSource = `a { color: pink !important; // stylelint-disable-line declaration-no-important }`; + return postcss() .use(assignDisabledRanges) .process(lessSource, { syntax: less, from: undefined }) diff --git a/lib/__tests__/fixtures/plugin-conditionally-check-color-hex-case.js b/lib/__tests__/fixtures/plugin-conditionally-check-color-hex-case.js index 620fc5a2c0..780a5a038a 100644 --- a/lib/__tests__/fixtures/plugin-conditionally-check-color-hex-case.js +++ b/lib/__tests__/fixtures/plugin-conditionally-check-color-hex-case.js @@ -14,8 +14,10 @@ module.exports = stylelint.createPlugin(ruleName, function( options, context ); + return (root, result) => { if (root.toString().indexOf("@@check-color-hex-case") === -1) return; + colorHexCaseRule(root, result); }; }); diff --git a/lib/__tests__/fixtures/processor-fenced-blocks.js b/lib/__tests__/fixtures/processor-fenced-blocks.js index 22d043e9d0..b76c56baf8 100644 --- a/lib/__tests__/fixtures/processor-fenced-blocks.js +++ b/lib/__tests__/fixtures/processor-fenced-blocks.js @@ -7,10 +7,12 @@ module.exports = function() { arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const specialMessage = options.specialMessage || "was processed"; + return { code(input) { const blocks = execall(/```start([\s\S]+?)```end/g, input); const toLint = blocks.map(match => match.sub[0]).join("\n\n"); + return toLint; }, result(stylelintResult) { diff --git a/lib/__tests__/fixtures/processor-triple-question-marks.js b/lib/__tests__/fixtures/processor-triple-question-marks.js index 7822ce6baa..e2a34af43f 100644 --- a/lib/__tests__/fixtures/processor-triple-question-marks.js +++ b/lib/__tests__/fixtures/processor-triple-question-marks.js @@ -4,13 +4,16 @@ const execall = require("execall"); module.exports = function() { let found = false; + return { code(input) { const blocks = execall(/\?\?\?start([\s\S]+?)\?\?\?end/g, input); const toLint = blocks.map(match => match.sub[0]).join("\n\n"); + if (toLint.length > 0) { found = true; } + return toLint; }, result(stylelintResult) { diff --git a/lib/__tests__/needlessDisables.test.js b/lib/__tests__/needlessDisables.test.js index 912bea0b5c..11ac1b71c5 100644 --- a/lib/__tests__/needlessDisables.test.js +++ b/lib/__tests__/needlessDisables.test.js @@ -35,6 +35,7 @@ it("needlessDisables simple case", () => { ignoreDisables: true }).then(linted => { const report = needlessDisables(linted.results); + expect(report).toHaveLength(1); expect(report[0].ranges).toEqual([ { start: 7, end: 9 }, diff --git a/lib/__tests__/normalizeRuleSettings.test.js b/lib/__tests__/normalizeRuleSettings.test.js index 384be4044c..c20822ed9b 100644 --- a/lib/__tests__/normalizeRuleSettings.test.js +++ b/lib/__tests__/normalizeRuleSettings.test.js @@ -4,22 +4,24 @@ const normalizeRuleSettings = require("../normalizeRuleSettings"); describe("rules whose primary option IS NOT an array", () => { it("solo null returns null", () => { - expect(normalizeRuleSettings(null, "foo")).toBe(null); + expect(normalizeRuleSettings(null, "foo")).toBeNull(); }); it("arrayed null returns null", () => { - expect(normalizeRuleSettings([null], "foo")).toBe(null); + expect(normalizeRuleSettings([null], "foo")).toBeNull(); }); it("solo number returns arrayed number", () => { const actual = normalizeRuleSettings(2, "foo"); const expected = [2]; + expect(actual).toEqual(expected); }); it("arrayed number returns arrayed number if rule is not special", () => { const actual = normalizeRuleSettings([2], "foo"); const expected = [2]; + expect(actual).toEqual(expected); }); @@ -29,18 +31,21 @@ describe("rules whose primary option IS NOT an array", () => { "block-no-empty" ); const expected = [2, { severity: "warning" }]; + expect(actual).toEqual(expected); }); it("solo string returns arrayed string", () => { const actual = normalizeRuleSettings("always", "foo"); const expected = ["always"]; + expect(actual).toEqual(expected); }); it("arrayed string returns arrayed string", () => { const actual = normalizeRuleSettings(["always"], "foo"); const expected = ["always"]; + expect(actual).toEqual(expected); }); @@ -50,18 +55,21 @@ describe("rules whose primary option IS NOT an array", () => { "foo" ); const expected = ["always", { severity: "warning" }]; + expect(actual).toEqual(expected); }); it("solo boolean returns arrayed boolean", () => { const actual = normalizeRuleSettings(true, "foo"); const expected = [true]; + expect(actual).toEqual(expected); }); it("arrayed boolean returns arrayed boolean if rule is not special", () => { const actual = normalizeRuleSettings([false], "foo"); const expected = [false]; + expect(actual).toEqual(expected); }); @@ -71,17 +79,18 @@ describe("rules whose primary option IS NOT an array", () => { "block-no-empty" ); const expected = [true, { severity: "warning" }]; + expect(actual).toEqual(expected); }); }); describe("rules whose primary option CAN BE an array", () => { it("solo null returns null", () => { - expect(normalizeRuleSettings(null, "foo")).toBe(null); + expect(normalizeRuleSettings(null, "foo")).toBeNull(); }); it("arrayed null returns null", () => { - expect(normalizeRuleSettings([null], "foo")).toBe(null); + expect(normalizeRuleSettings([null], "foo")).toBeNull(); }); it("solo primary option array is nested within an array", () => { @@ -91,6 +100,7 @@ describe("rules whose primary option CAN BE an array", () => { true ); const expected = [["calc", "rgba"]]; + expect(actual).toEqual(expected); }); @@ -101,6 +111,7 @@ describe("rules whose primary option CAN BE an array", () => { true ); const expected = [["calc", "rgba"]]; + expect(actual).toEqual(expected); }); @@ -111,6 +122,7 @@ describe("rules whose primary option CAN BE an array", () => { true ); const expected = [["calc", "rgba"], { severity: "warning" }]; + expect(actual).toEqual(expected); }); @@ -120,6 +132,7 @@ describe("rules whose primary option CAN BE an array", () => { "rulename-bar" ); const expected = ["alphabetical", { severity: "warning" }]; + expect(actual).toEqual(expected); }); @@ -130,6 +143,7 @@ describe("rules whose primary option CAN BE an array", () => { true ); const expected = [[{ foo: 1 }, { foo: 2 }]]; + expect(actual).toEqual(expected); }); @@ -140,6 +154,7 @@ describe("rules whose primary option CAN BE an array", () => { true ); const expected = [[{ foo: 1 }, { foo: 2 }], { severity: "warning" }]; + expect(actual).toEqual(expected); }); }); diff --git a/lib/__tests__/plugins.test.js b/lib/__tests__/plugins.test.js index 63d11a3b1d..637a5827eb 100644 --- a/lib/__tests__/plugins.test.js +++ b/lib/__tests__/plugins.test.js @@ -260,6 +260,7 @@ it("plugin with primary option array", () => { "plugin/primary-array": ["foo", "bar"] } }; + return postcss() .use(stylelint(config)) .process("a {}", { from: undefined }) @@ -275,6 +276,7 @@ it("plugin with primary option array within options array", () => { "plugin/primary-array": [["foo", "bar"], { something: true }] } }; + return postcss() .use(stylelint(config)) .process("a {}", { from: undefined }) @@ -290,6 +292,7 @@ it("plugin with async rule", () => { "plugin/async": true } }; + return postcss() .use(stylelint(config)) .process("a {}", { from: undefined }) diff --git a/lib/__tests__/postcssPlugin.test.js b/lib/__tests__/postcssPlugin.test.js index fcc96b06c3..6207e4dfc1 100644 --- a/lib/__tests__/postcssPlugin.test.js +++ b/lib/__tests__/postcssPlugin.test.js @@ -19,10 +19,12 @@ it("`configFile` option with absolute path", () => { const config = { configFile: path.join(__dirname, "fixtures/config-block-no-empty.json") }; + return postcssPlugin .process("a {}", { from: undefined }, config) .then(postcssResult => { const warnings = postcssResult.warnings(); + expect(warnings).toHaveLength(1); expect(warnings[0].text.indexOf("block-no-empty")).not.toBe(-1); }); @@ -43,6 +45,7 @@ it("`configFile` option without rules", () => { const config = { configFile: path.join(__dirname, "fixtures/config-without-rules.json") }; + return postcssPlugin .process("a {}", { from: undefined }, config) .then(() => { @@ -62,6 +65,7 @@ it("`configFile` option with undefined rule", () => { configFile: path.join(__dirname, "fixtures/config-with-undefined-rule.json") }; const ruleName = "unknown-rule"; + return postcssPlugin .process("a {}", { from: undefined }, config) .then(() => { @@ -80,6 +84,7 @@ it("`ignoreFiles` options is not empty and file ignored", () => { ignoreFiles: "**/foo.css", from: "foo.css" }; + return postcssPlugin .process("a {}", { from: undefined }, config) .then(postcssResult => { @@ -95,6 +100,7 @@ it("`ignoreFiles` options is not empty and file not ignored", () => { ignoreFiles: "**/bar.css", from: "foo.css" }; + return postcssPlugin .process("a {}", { from: undefined }, config) .then(postcssResult => { diff --git a/lib/__tests__/printConfig.test.js b/lib/__tests__/printConfig.test.js index 59c7a3df81..7a914ef249 100644 --- a/lib/__tests__/printConfig.test.js +++ b/lib/__tests__/printConfig.test.js @@ -9,6 +9,7 @@ it("printConfig uses getConfigForFile to retrieve the config", () => { __dirname, "fixtures/getConfigForFile/a/b/foo.css" ); + return printConfig({ files: [filepath] }).then(result => { diff --git a/lib/__tests__/processors.test.js b/lib/__tests__/processors.test.js index 0f7ba9c779..3304d814e7 100644 --- a/lib/__tests__/processors.test.js +++ b/lib/__tests__/processors.test.js @@ -10,6 +10,7 @@ describe("processor transforms input and output", () => { beforeEach(() => { const code = "one\ntwo\n```start\na {}\nb { color: pink }\n```end\nthree"; + return standalone({ code, config: { @@ -50,6 +51,7 @@ describe("processor accepts options", () => { beforeEach(() => { const code = "one\ntwo\n```start\na {}\nb { color: pink }\n```end\nthree"; + return standalone({ code, config: { @@ -74,6 +76,7 @@ describe("multiple processors", () => { const code = "one\ntwo\n```start\na {}\nb { color: pink }\n```end\nthree???startc {}???end" + "\n\n???start```start\na {}\nb { color: pink }\n```end???end"; + return standalone({ code, config: { @@ -171,6 +174,7 @@ describe("processor gets to modify result on CssSyntaxError", () => { beforeEach(() => { const code = "one\ntwo\n```start\na {}\nb { color: 'pink; }\n```end\nthree"; + return standalone({ code, config: { diff --git a/lib/__tests__/standalone-cache.test.js b/lib/__tests__/standalone-cache.test.js index 2d83c32dd4..bbff55d10c 100644 --- a/lib/__tests__/standalone-cache.test.js +++ b/lib/__tests__/standalone-cache.test.js @@ -57,6 +57,7 @@ describe("standalone cache", () => { return fileExists(expectedCacheFilePath) .then(isFileExist => { expect(!!isFileExist).toBe(true); + return readFile(expectedCacheFilePath, "utf8"); }) .then(fileContents => { @@ -84,8 +85,10 @@ describe("standalone cache", () => { const isNewFileLinted = !!output.results.find( file => file.source === newFileDest ); + expect(isValidFileLinted).toBe(false); expect(isNewFileLinted).toBe(true); + // Ensure cache file contains linted css files return readFile(expectedCacheFilePath, "utf8"); }) @@ -100,7 +103,9 @@ describe("standalone cache", () => { it("all files are linted on config change", () => { const changedConfig = getConfig(); + changedConfig.config.rules["block-no-empty"] = false; + return cpFile(validFile, newFileDest) .then(() => { // All file should be re-linted as config has changed @@ -114,6 +119,7 @@ describe("standalone cache", () => { const isNewFileLinted = !!output.results.find( file => file.source === newFileDest ); + expect(isValidFileLinted).toBe(true); expect(isNewFileLinted).toBe(true); }); @@ -134,8 +140,10 @@ describe("standalone cache", () => { const isInvalidFileLinted = !!output.results.find( file => file.source === newFileDest ); + expect(isValidFileLinted).toBe(false); expect(isInvalidFileLinted).toBe(true); + // Ensure cache file doesn't contain invalid file return readFile(expectedCacheFilePath, "utf8"); }) @@ -158,6 +166,7 @@ describe("standalone cache", () => { }) .then(fileContents => { const cachedFiles = JSON.parse(fileContents); + expect(typeof cachedFiles[validFile] === "object").toBe(true); expect(typeof cachedFiles[newFileDest] === "undefined").toBe(true); }); @@ -167,6 +176,7 @@ describe("standalone cache", () => { noCacheConfig.cache = false; let cacheFileExists = true; + return standalone(noCacheConfig).then(() => { return fileExists(expectedCacheFilePath) .then(() => { @@ -192,8 +202,10 @@ describe("standalone cache", () => { } }; let cacheFileExists = true; + return standalone(config).then(lintResults => { expect(lintResults.errored).toBe(false); + return fileExists(expectedCacheFilePath) .then(() => { throw new Error( @@ -214,6 +226,7 @@ describe("standalone cache uses cacheLocation", () => { cacheLocationDir, `.stylelintcache_${hash(cwd)}` ); + afterEach(() => { // clean up after each test return Promise.all([ @@ -229,7 +242,9 @@ describe("standalone cache uses cacheLocation", () => { }); it("cacheLocation is a file", () => { const config = getConfig(); + config.cacheLocation = cacheLocationFile; + return standalone(config) .then(() => { // Ensure cache file is created @@ -237,6 +252,7 @@ describe("standalone cache uses cacheLocation", () => { }) .then(fileStats => { expect(!!fileStats).toBe(true); + // Ensure cache file contains cached entity return readFile(cacheLocationFile, "utf8"); }) @@ -249,7 +265,9 @@ describe("standalone cache uses cacheLocation", () => { }); it("cacheLocation is a directory", () => { const config = getConfig(); + config.cacheLocation = cacheLocationDir; + return standalone(config) .then(() => { return fileExists(expectedCacheFilePath); @@ -257,6 +275,7 @@ describe("standalone cache uses cacheLocation", () => { .then(cacheFileStats => { // Ensure cache file is created expect(!!cacheFileStats).toBe(true); + // Ensure cache file contains cached entity return readFile(expectedCacheFilePath, "utf8"); }) diff --git a/lib/__tests__/standalone-formatter.test.js b/lib/__tests__/standalone-formatter.test.js index b8e3c292af..421a859b9e 100644 --- a/lib/__tests__/standalone-formatter.test.js +++ b/lib/__tests__/standalone-formatter.test.js @@ -14,6 +14,7 @@ it("standalone with input css and alternate formatter specified by keyword", () const output = linted.output; const strippedOutput = stripAnsi(output); + expect(typeof output).toBe("string"); expect(strippedOutput.indexOf("1:3")).not.toBe(-1); expect(strippedOutput.indexOf("block-no-empty")).not.toBe(-1); @@ -29,6 +30,7 @@ it("standalone with input css and alternate formatter function", () => { const output = linted.output; const strippedOutput = stripAnsi(output); + expect(typeof output).toBe("string"); expect(strippedOutput.indexOf("1:3")).not.toBe(-1); expect(strippedOutput.indexOf("block-no-empty")).not.toBe(-1); diff --git a/lib/__tests__/standalone-syntax.test.js b/lib/__tests__/standalone-syntax.test.js index 22d87b678d..4b9e1c664f 100644 --- a/lib/__tests__/standalone-syntax.test.js +++ b/lib/__tests__/standalone-syntax.test.js @@ -23,6 +23,7 @@ it("standalone with scss syntax", () => { formatter: stringFormatter }).then(linted => { const strippedOutput = stripAnsi(linted.output); + expect(typeof linted.output).toBe("string"); expect(strippedOutput.indexOf("2:3")).not.toBe(-1); expect(strippedOutput.indexOf("block-no-empty")).not.toBe(-1); @@ -43,6 +44,7 @@ it("standalone with sugarss syntax", () => { formatter: stringFormatter }).then(linted => { const strippedOutput = stripAnsi(linted.output); + expect(typeof linted.output).toBe("string"); expect(strippedOutput.indexOf("3:9")).not.toBe(-1); expect(strippedOutput.indexOf("length-zero-no-unit")).not.toBe(-1); @@ -63,6 +65,7 @@ it("standalone with Less syntax", () => { formatter: stringFormatter }).then(linted => { const strippedOutput = stripAnsi(linted.output); + expect(typeof linted.output).toBe("string"); expect(strippedOutput.indexOf("2:3")).not.toBe(-1); expect(strippedOutput.indexOf("block-no-empty")).not.toBe(-1); @@ -101,29 +104,34 @@ it("standalone with postcss-html syntax", () => { formatter: stringFormatter }).then(linted => { const results = linted.results; + expect(results).toHaveLength(4); const atRuleEmptyLineBeforeResult = results.find(r => /[/\\]at-rule-empty-line-before\.html$/.test(r.source) ); + expect(atRuleEmptyLineBeforeResult.errored).toBeFalsy(); expect(atRuleEmptyLineBeforeResult.warnings).toHaveLength(0); const commentEmptyLineBeforeResult = results.find(r => /[/\\]comment-empty-line-before\.html$/.test(r.source) ); + expect(commentEmptyLineBeforeResult.errored).toBeFalsy(); expect(commentEmptyLineBeforeResult.warnings).toHaveLength(0); const noEmptySourceResult = results.find(r => /[/\\]no-empty-source\.html$/.test(r.source) ); + expect(noEmptySourceResult.errored).toBeFalsy(); expect(noEmptySourceResult.warnings).toHaveLength(0); const ruleEmptyLineBeforeResult = results.find(r => /[/\\]rule-empty-line-before\.html$/.test(r.source) ); + expect(ruleEmptyLineBeforeResult.errored).toBe(true); expect(ruleEmptyLineBeforeResult.warnings).toHaveLength(1); expect(ruleEmptyLineBeforeResult.warnings[0].line).toBe(8); @@ -148,6 +156,7 @@ describe("standalone with syntax set by extension", () => { it("parsed as SASS", () => { const sassResult = results.find(r => path.extname(r.source) === ".sass"); + expect(sassResult._postcssResult.root.source.lang).toBe("sass"); }); }); @@ -162,6 +171,7 @@ describe("standalone with syntax set by extension", () => { it("parsed as SCSS", () => { const scssResult = results.find(r => path.extname(r.source) === ".scss"); + expect(scssResult._postcssResult.root.source.lang).toBe("scss"); }); }); @@ -176,6 +186,7 @@ describe("standalone with syntax set by extension", () => { it("parsed as Less", () => { const lessResult = results.find(r => path.extname(r.source) === ".less"); + expect(lessResult._postcssResult.root.source.lang).toBe("less"); }); }); @@ -190,6 +201,7 @@ describe("standalone with syntax set by extension", () => { it("parsed as SugarSS", () => { const sssResult = results.find(r => path.extname(r.source) === ".sss"); + expect(sssResult._postcssResult.root.source.lang).toBe("sugarss"); }); }); @@ -244,6 +256,7 @@ describe("standalone with syntax set by extension", () => { const lessResult = results.find(r => path.extname(r.source) === ".less"); const sassResult = results.find(r => path.extname(r.source) === ".sass"); const scssResult = results.find(r => path.extname(r.source) === ".scss"); + expect(sssResult._postcssResult.root.source.lang).toBe("sugarss"); expect(lessResult._postcssResult.root.source.lang).toBe("less"); expect(sassResult._postcssResult.root.source.lang).toBe("sass"); @@ -265,6 +278,7 @@ it("standalone with automatic syntax inference", () => { const htmlResult = results.find(r => path.extname(r.source) === ".html"); const vueResult = results.find(r => path.extname(r.source) === ".vue"); const mdResult = results.find(r => path.extname(r.source) === ".markdown"); + expect(htmlResult.warnings[0].line).toBe(8); expect(vueResult.warnings[0].line).toBe(3); expect(mdResult.warnings[0].line).toBe(6); @@ -288,15 +302,18 @@ it("standalone with postcss-safe-parser", () => { fix: true }).then(data => { const results = data.results; + expect(results).toHaveLength(6); return Promise.all( results.map(result => { if (/\.(css|pcss|postcss)$/i.test(result.source)) { const root = result._postcssResult.root; + expect(results[0].errored).toBeFalsy(); expect(results[0].warnings).toHaveLength(0); expect(root.toString()).not.toBe(root.source.input.css); + return pify(fs.writeFile)( root.source.input.file, root.source.input.css @@ -304,6 +321,7 @@ it("standalone with postcss-safe-parser", () => { } else { expect(result.warnings).toHaveLength(1); const error = result.warnings[0]; + expect(error.line).toBe(1); expect(error.column).toBe(1); expect(error.rule).toBe("CssSyntaxError"); diff --git a/lib/__tests__/standalone.test.js b/lib/__tests__/standalone.test.js index d2b772800d..14d5cffece 100644 --- a/lib/__tests__/standalone.test.js +++ b/lib/__tests__/standalone.test.js @@ -56,6 +56,7 @@ describe("standalone with two file-specific globs", () => { expect(results).toHaveLength(2); expect(results[0].warnings).toHaveLength(1); expect(results[1].warnings).toHaveLength(1); + // Ordering of the files is non-deterministic, I believe if (results[0].source.indexOf("empty-block") !== -1) { expect(results[0].warnings[0].rule).toBe("block-no-empty"); @@ -107,6 +108,7 @@ it("standalone without input css and file(s) should throw error", () => { const expectedError = new Error( "You must pass stylelint a `files` glob or a `code` string, though not both" ); + expect(() => standalone({ config: configBlockNoEmpty })).toThrow( expectedError ); @@ -268,6 +270,7 @@ describe("standalone with different configs per file", () => { const resultA = results.find( result => result.source.indexOf("a.css") !== -1 ); + expect(resultA.warnings).toHaveLength(0); }); @@ -275,6 +278,7 @@ describe("standalone with different configs per file", () => { const resultB = results.find( result => result.source.indexOf("b.css") !== -1 ); + expect(resultB.warnings).toHaveLength(1); }); @@ -282,6 +286,7 @@ describe("standalone with different configs per file", () => { const resultB = results.find( result => result.source.indexOf("b.css") !== -1 ); + expect(resultB.warnings[0].text.indexOf("Unexpected empty block")).not.toBe( -1 ); @@ -291,6 +296,7 @@ describe("standalone with different configs per file", () => { const resultC = results.find( result => result.source.indexOf("c.css") !== -1 ); + expect(resultC.warnings).toHaveLength(0); }); @@ -298,6 +304,7 @@ describe("standalone with different configs per file", () => { const resultD = results.find( result => result.source.indexOf("d.css") !== -1 ); + expect(resultD.warnings).toHaveLength(0); }); }); diff --git a/lib/assignDisabledRanges.js b/lib/assignDisabledRanges.js index 33d007e7bd..9e489f04e4 100644 --- a/lib/assignDisabledRanges.js +++ b/lib/assignDisabledRanges.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); const COMMAND_PREFIX = "stylelint-"; @@ -28,6 +29,7 @@ module.exports = function( const disabledRanges /*: disabledRangeObject*/ = { all: [] }; + result.stylelint.disabledRanges = disabledRanges; root.walkComments(checkComment); @@ -55,11 +57,13 @@ module.exports = function( plugin: "stylelint" }); } + if (ruleIsDisabled(ruleName)) { throw comment.error(`"${ruleName}" has already been disabled`, { plugin: "stylelint" }); } + if (ruleName === ALL_RULES) { Object.keys(disabledRanges).forEach(disabledRuleName => { startDisabledRange(line, disabledRuleName); @@ -79,9 +83,11 @@ module.exports = function( plugin: "stylelint" }); } + Object.keys(disabledRanges).forEach(ruleName => { startDisabledRange(comment.source.start.line, ruleName); }); + return; } @@ -90,6 +96,7 @@ module.exports = function( plugin: "stylelint" }); } + startDisabledRange(comment.source.start.line, ruleToDisable); }); } @@ -106,11 +113,13 @@ module.exports = function( plugin: "stylelint" }); } + Object.keys(disabledRanges).forEach(ruleName => { if (!_.get(_.last(disabledRanges[ruleName]), "end")) { endDisabledRange(comment.source.end.line, ruleName); } }); + return; } @@ -126,12 +135,15 @@ module.exports = function( _.clone(_.last(disabledRanges[ALL_RULES])) ); } + endDisabledRange(comment.source.end.line, ruleToEnable); + return; } if (ruleIsDisabled(ruleToEnable)) { endDisabledRange(comment.source.end.line, ruleToEnable); + return; } @@ -168,23 +180,28 @@ module.exports = function( const rules = _.compact(fullText.slice(command.length).split(",")).map(r => r.trim() ); + if (_.isEmpty(rules)) { return [ALL_RULES]; } + return rules; } function startDisabledRange(line /*: number*/, ruleName /*: string*/) { const rangeObj = { start: line }; + ensureRuleRanges(ruleName); disabledRanges[ruleName].push(rangeObj); } function endDisabledRange(line /*: number*/, ruleName /*: string*/) { const lastRangeForRule = _.last(disabledRanges[ruleName]); + if (!lastRangeForRule) { return; } + // Add an `end` prop to the last range of that rule lastRangeForRule.end = line; } @@ -197,9 +214,12 @@ module.exports = function( function ruleIsDisabled(ruleName /*: string*/) /*: boolean*/ { if (disabledRanges[ruleName] === undefined) return false; + if (_.last(disabledRanges[ruleName]) === undefined) return false; + if (_.get(_.last(disabledRanges[ruleName]), "end") === undefined) return true; + return false; } }; diff --git a/lib/augmentConfig.js b/lib/augmentConfig.js index c6019cfa42..ec84b036c0 100644 --- a/lib/augmentConfig.js +++ b/lib/augmentConfig.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); const configurationError = require("./utils/configurationError"); const dynamicRequire = require("./dynamicRequire"); @@ -21,6 +22,7 @@ function augmentConfigBasic( return Promise.resolve() .then(() => { if (!allowOverrides) return config; + return _.merge(config, stylelint._options.configOverrides); }) .then(augmentedConfig => { @@ -42,10 +44,12 @@ function augmentConfigExtended( }*/ ) /*: Promise*/ { const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow + if (!cosmiconfigResult) return Promise.resolve(null); const configDir = path.dirname(cosmiconfigResult.filepath || ""); const cleanedConfig = _.omit(cosmiconfigResult.config, "ignoreFiles"); + return augmentConfigBasic(stylelint, cleanedConfig, configDir).then( augmentedConfig => { return { @@ -64,6 +68,7 @@ function augmentConfigFull( }*/ ) /*: Promise*/ { const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow + if (!cosmiconfigResult) return Promise.resolve(null); const config = cosmiconfigResult.config; @@ -108,6 +113,7 @@ function absolutizePaths( if (config.ignoreFiles) { config.ignoreFiles = [].concat(config.ignoreFiles).map(glob => { if (path.isAbsolute(glob.replace(/^!/, ""))) return glob; + return globjoin(configDir, glob); }); } @@ -150,6 +156,7 @@ function extendConfig( configDir /*: string*/ ) /*: Promise*/ { if (config.extends === undefined) return Promise.resolve(config); + const normalizedExtends = Array.isArray(config.extends) ? config.extends : [config.extends]; @@ -165,6 +172,7 @@ function extendConfig( extendLookup ).then(extendResult => { if (!extendResult) return resultConfig; + return mergeConfigs(resultConfig, extendResult.config); }); }); @@ -184,6 +192,7 @@ function loadExtendedConfig( extendLookup /*: string*/ ) /*: Promise*/ { const extendPath = getModulePath(configDir, extendLookup); + return stylelint._extendExplorer.load(extendPath); } @@ -198,24 +207,30 @@ function mergeConfigs( b /*: stylelint$config*/ ) /*: stylelint$config*/ { const pluginMerger = {}; + if (a.plugins || b.plugins) { pluginMerger.plugins = []; + if (a.plugins) { pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins); } + if (b.plugins) { pluginMerger.plugins = _.uniq(pluginMerger.plugins.concat(b.plugins)); } } const processorMerger = {}; + if (a.processors || b.processors) { processorMerger.processors = []; + if (a.processors) { processorMerger.processors = processorMerger.processors.concat( a.processors ); } + if (b.processors) { processorMerger.processors = _.uniq( processorMerger.processors.concat(b.processors) @@ -224,6 +239,7 @@ function mergeConfigs( } const rulesMerger = {}; + if (a.rules || b.rules) { rulesMerger.rules = Object.assign({}, a.rules, b.rules); } @@ -236,6 +252,7 @@ function mergeConfigs( pluginMerger, rulesMerger ); + return result; } @@ -250,6 +267,7 @@ function addPluginFunctions( const pluginFunctions = normalizedPlugins.reduce((result, pluginLookup) => { let pluginImport = dynamicRequire(pluginLookup); + // Handle either ES6 or CommonJS modules pluginImport = pluginImport.default || pluginImport; @@ -286,6 +304,7 @@ function addPluginFunctions( }, {}); config.pluginFunctions = pluginFunctions; + return config; } @@ -293,7 +312,9 @@ function normalizeAllRuleSettings( config /*: stylelint$config*/ ) /*: stylelint$config*/ { const normalizedRules = {}; + if (!config.rules) return config; + Object.keys(config.rules).forEach(ruleName => { const rawRuleSettings = _.get(config, ["rules", ruleName]); @@ -303,6 +324,7 @@ function normalizeAllRuleSettings( if (!rule) { throw configurationError(`Undefined rule ${ruleName}`); } + normalizedRules[ruleName] = normalizeRuleSettings( rawRuleSettings, ruleName, @@ -310,6 +332,7 @@ function normalizeAllRuleSettings( ); }); config.rules = normalizedRules; + return config; } @@ -324,6 +347,7 @@ function normalizeAllRuleSettings( // provided options // - Push the processor's code and result processors to their respective arrays const processorCache = new Map(); + function addProcessorFunctions( config /*: stylelint$config*/ ) /*: stylelint$config*/ { @@ -331,10 +355,12 @@ function addProcessorFunctions( const codeProcessors = []; const resultProcessors = []; + [].concat(config.processors).forEach(processorConfig => { const processorKey = JSON.stringify(processorConfig); let initializedProcessor; + if (processorCache.has(processorKey)) { initializedProcessor = processorCache.get(processorKey); } else { @@ -342,6 +368,7 @@ function addProcessorFunctions( const processorLookup = processorConfig[0]; const processorOptions = processorConfig[1]; let processor = dynamicRequire(processorLookup); + processor = processor.default || processor; initializedProcessor = processor(processorOptions); processorCache.set(processorKey, initializedProcessor); @@ -350,6 +377,7 @@ function addProcessorFunctions( if (initializedProcessor && initializedProcessor.code) { codeProcessors.push(initializedProcessor.code); } + if (initializedProcessor && initializedProcessor.result) { resultProcessors.push(initializedProcessor.result); } @@ -357,6 +385,7 @@ function addProcessorFunctions( config.codeProcessors = codeProcessors; config.resultProcessors = resultProcessors; + return config; } diff --git a/lib/cli.js b/lib/cli.js index 657a5b63d7..78f8df5888 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -367,16 +367,19 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { meowOptions.flags, cli.flags ); + if (invalidOptionsMessage) { process.stderr.write(invalidOptionsMessage); process.exit(EXIT_CODE_ERROR); // eslint-disable-line no-process-exit } let formatter = cli.flags.formatter; + if (cli.flags.customFormatter) { const customFormatter = path.isAbsolute(cli.flags.customFormatter) ? cli.flags.customFormatter : path.join(process.cwd(), cli.flags.customFormatter); + formatter = dynamicRequire(customFormatter); } @@ -464,11 +467,13 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { if (cli.flags.help || cli.flags.h) { cli.showHelp(0); + return; } if (cli.flags.version || cli.flags.v) { cli.showVersion(); + return; } @@ -480,6 +485,7 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { files: cli.input }); } + return getStdin().then(stdin => Object.assign({}, optionsBase, { code: stdin @@ -497,6 +503,7 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { if (!options.files && !options.code) { cli.showHelp(); + return; } @@ -508,9 +515,11 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { ); process.stdout.write(report); + if (report) { process.exitCode = EXIT_CODE_ERROR; } + return; } @@ -540,5 +549,6 @@ module.exports = (argv /*: string[]*/) /*: Promise|void*/ => { function handleError(err /*: { stack: any, code: any }*/) /*: void */ { console.log(err.stack); // eslint-disable-line no-console const exitCode = typeof err.code === "number" ? err.code : 1; + process.exit(exitCode); // eslint-disable-line no-process-exit } diff --git a/lib/createStylelint.js b/lib/createStylelint.js index a6a7d50aba..7f1ed54b7b 100644 --- a/lib/createStylelint.js +++ b/lib/createStylelint.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); const augmentConfig = require("./augmentConfig"); const cosmiconfig = require("cosmiconfig"); diff --git a/lib/createStylelintResult.js b/lib/createStylelintResult.js index b710bcfe7f..98c14be4b8 100644 --- a/lib/createStylelintResult.js +++ b/lib/createStylelintResult.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); /*:: type messageType = { @@ -172,6 +173,7 @@ module.exports = function( // Result processors might just mutate the result object, // or might return a new one const returned = resultProcessor(stylelintResult, file); + if (returned) { stylelintResult = returned; } diff --git a/lib/formatters/__tests__/compactFormatter.test.js b/lib/formatters/__tests__/compactFormatter.test.js index 710ce72058..ae6a73b434 100644 --- a/lib/formatters/__tests__/compactFormatter.test.js +++ b/lib/formatters/__tests__/compactFormatter.test.js @@ -30,6 +30,7 @@ describe("compactFormatter", () => { ]; const output = compactFormatter(results); + expect(output).toBe(""); }); diff --git a/lib/formatters/__tests__/needlessDisablesStringFormatter.test.js b/lib/formatters/__tests__/needlessDisablesStringFormatter.test.js index 58fcdddf08..22de2e3c6c 100644 --- a/lib/formatters/__tests__/needlessDisablesStringFormatter.test.js +++ b/lib/formatters/__tests__/needlessDisablesStringFormatter.test.js @@ -31,6 +31,7 @@ describe("needlessDisablesStringFormatter", () => { bar start: 19, end: 33 start: 99, end: 102`; + expected = `\n${expected}\n`; expect(actual).toBe(expected); diff --git a/lib/formatters/__tests__/prepareFormatterOutput.js b/lib/formatters/__tests__/prepareFormatterOutput.js index 1b12890d8c..14c796f29b 100644 --- a/lib/formatters/__tests__/prepareFormatterOutput.js +++ b/lib/formatters/__tests__/prepareFormatterOutput.js @@ -3,6 +3,7 @@ const stripAnsi = require("strip-ansi"); const symbolConversions = new Map(); + symbolConversions.set("ℹ", "i"); symbolConversions.set("✔", "√"); symbolConversions.set("⚠", "‼"); diff --git a/lib/formatters/__tests__/stringFormatter.test.js b/lib/formatters/__tests__/stringFormatter.test.js index 9cf53bb3a7..37f73df3fa 100644 --- a/lib/formatters/__tests__/stringFormatter.test.js +++ b/lib/formatters/__tests__/stringFormatter.test.js @@ -30,6 +30,7 @@ describe("stringFormatter", () => { ]; const output = stringFormatter(results); + expect(output).toBe(""); }); diff --git a/lib/formatters/__tests__/unixFormatter.test.js b/lib/formatters/__tests__/unixFormatter.test.js index 3161f701e6..ca3c2639e4 100644 --- a/lib/formatters/__tests__/unixFormatter.test.js +++ b/lib/formatters/__tests__/unixFormatter.test.js @@ -30,6 +30,7 @@ describe("unixFormatter", () => { ]; const output = unixFormatter(results); + expect(output).toBe(""); }); @@ -60,6 +61,7 @@ describe("unixFormatter", () => { ]; const output = prepareFormatterOutput(results, unixFormatter); + expect(output).toBe(stripIndent` path/to/file.css:1:1: Unexpected foo [error] path/to/file.css:10:1: Unexpected foo 2 [error] @@ -90,6 +92,7 @@ describe("unixFormatter", () => { ]; const output = prepareFormatterOutput(results, unixFormatter); + expect(output).toBe(stripIndent` path/to/file.css:1:1: Unexpected foo [error] @@ -122,6 +125,7 @@ describe("unixFormatter", () => { ]; const output = prepareFormatterOutput(results, unixFormatter); + expect(output).toBe(stripIndent` path/to/file.css:1:1: Unexpected very very very very very very very very very very very very very long foo [error] @@ -141,6 +145,7 @@ describe("unixFormatter", () => { ]; const output = prepareFormatterOutput(results, unixFormatter); + expect(output).toBe(""); }); }); diff --git a/lib/formatters/__tests__/verboseFormatter.test.js b/lib/formatters/__tests__/verboseFormatter.test.js index 8741ba2924..34344cbfe0 100644 --- a/lib/formatters/__tests__/verboseFormatter.test.js +++ b/lib/formatters/__tests__/verboseFormatter.test.js @@ -62,6 +62,7 @@ describe("stringFormatter", () => { it("outputs 0 stdout column", () => { const stdoutColumn = process.stdout.columns; + process.stdout.columns = 0; const results = [ @@ -101,6 +102,7 @@ describe("stringFormatter", () => { it("outputs less than 80 stdout column", () => { const stdoutColumn = process.stdout.columns; + process.stdout.columns = 79; const results = [ diff --git a/lib/formatters/jsonFormatter.js b/lib/formatters/jsonFormatter.js index 844c191443..438bde7bc5 100644 --- a/lib/formatters/jsonFormatter.js +++ b/lib/formatters/jsonFormatter.js @@ -7,5 +7,6 @@ module.exports = function(results) { const cleanedResults = results.map(result => { return _.omitBy(result, (value, key) => key[0] === "_"); }); + return JSON.stringify(cleanedResults); }; diff --git a/lib/formatters/needlessDisablesStringFormatter.js b/lib/formatters/needlessDisablesStringFormatter.js index 528e99bd49..f1f094d13f 100644 --- a/lib/formatters/needlessDisablesStringFormatter.js +++ b/lib/formatters/needlessDisablesStringFormatter.js @@ -5,10 +5,9 @@ const chalk = require("chalk"); const path = require("path"); -function logFrom( - fromValue /*: string */ -) /*: string */ { +function logFrom(fromValue /*: string */) /*: string */ { if (fromValue.charAt(0) === "<") return fromValue; + return path .relative(process.cwd(), fromValue) .split(path.sep) @@ -26,13 +25,16 @@ module.exports = function( if (!sourceReport.ranges || sourceReport.ranges.length === 0) { return; } + output += "\n"; output += chalk.underline(logFrom(sourceReport.source)) + "\n"; sourceReport.ranges.forEach(range => { output += `start: ${range.start}`; + if (range.end !== undefined) { output += `, end: ${range.end}`; } + output += "\n"; }); }); diff --git a/lib/formatters/stringFormatter.js b/lib/formatters/stringFormatter.js index a6dca606c9..57bcf9e9c4 100644 --- a/lib/formatters/stringFormatter.js +++ b/lib/formatters/stringFormatter.js @@ -27,10 +27,12 @@ function deprecationsFormatter(results) { return uniqueDeprecationWarnings.reduce((output, warning) => { output += chalk.yellow("Deprecation Warning: "); output += warning.text; + if (warning.reference) { output += chalk.dim(" See: "); output += chalk.dim.underline(warning.reference); } + return output + "\n"; }, "\n"); } @@ -44,12 +46,14 @@ function invalidOptionsFormatter(results) { return uniqueInvalidOptionWarnings.reduce((output, warning) => { output += chalk.red("Invalid Option: "); output += warning; + return output + "\n"; }, "\n"); } function logFrom(fromValue) { if (fromValue.charAt(0) === "<") return fromValue; + return path .relative(process.cwd(), fromValue) .split(path.sep) @@ -90,6 +94,7 @@ function formatter(messages, source) { const calculateWidths = function(columns) { _.forOwn(columns, (value, key) => { const normalisedValue = value ? value.toString() : value; + columnWidths[key] = Math.max( columnWidths[key], stringWidth(normalisedValue) @@ -157,6 +162,7 @@ function formatter(messages, source) { module.exports = function(results) { let output = invalidOptionsFormatter(results); + output += deprecationsFormatter(results); output = results.reduce((output, result) => { @@ -172,7 +178,9 @@ module.exports = function(results) { }) ); } + output += formatter(result.warnings, result.source); + return output; }, output); diff --git a/lib/formatters/unixFormatter.js b/lib/formatters/unixFormatter.js index 1d3e2d30cc..e8fe676966 100644 --- a/lib/formatters/unixFormatter.js +++ b/lib/formatters/unixFormatter.js @@ -13,9 +13,11 @@ const unixFormatter = results => { ); const total = lines.length; let output = lines.join(""); + if (total > 0) { output += `\n${total} problem${total !== 1 ? "s" : ""}\n`; } + return output; }; diff --git a/lib/formatters/verboseFormatter.js b/lib/formatters/verboseFormatter.js index 5f25b973b6..145ba53057 100644 --- a/lib/formatters/verboseFormatter.js +++ b/lib/formatters/verboseFormatter.js @@ -16,9 +16,11 @@ module.exports = function(results) { const checkedDisplay = ignoredCount ? `${results.length - ignoredCount} of ${results.length}` : results.length; + output += chalk.underline(`${checkedDisplay} ${sourceWord} checked\n`); results.forEach(result => { let formatting = "green"; + if (result.errored) { formatting = "red"; } else if (result.warnings.length) { @@ -26,10 +28,13 @@ module.exports = function(results) { } else if (result.ignored) { formatting = "dim"; } + let sourceText = `${result.source}`; + if (result.ignored) { sourceText += " (ignored)"; } + output += _.get(chalk, formatting)(` ${sourceText}\n`); }); @@ -41,6 +46,7 @@ module.exports = function(results) { _.forOwn(warningsBySeverity, (warningList, severityLevel) => { const warningsByRule = _.groupBy(warningList, "rule"); + output += ` severity level "${severityLevel}": ${warningList.length}\n`; _.forOwn(warningsByRule, (list, rule) => { output += chalk.dim(` ${rule}: ${list.length}\n`); diff --git a/lib/getConfigForFile.js b/lib/getConfigForFile.js index 3adf5011a8..8240639310 100644 --- a/lib/getConfigForFile.js +++ b/lib/getConfigForFile.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const augmentConfigFull = require("./augmentConfig").augmentConfigFull; const configurationError = require("./utils/configurationError"); const path = require("path"); @@ -18,6 +19,7 @@ module.exports = function( const cached /*: configPromise*/ = stylelint._specifiedConfigCache.get( optionsConfig ); + if (cached) return cached; // stylelint._fullExplorer (cosmiconfig) is already configured to @@ -29,7 +31,9 @@ module.exports = function( // confused filepath: path.join(process.cwd(), "argument-config") }); + stylelint._specifiedConfigCache.set(optionsConfig, augmentedResult); + return augmentedResult; } @@ -41,13 +45,16 @@ module.exports = function( .then(config => { // If no config was found, try looking from process.cwd if (!config) return stylelint._fullExplorer.search(process.cwd()); + return config; }) .then(config => { if (!config) { const ending = searchPath ? ` for ${searchPath}` : ""; + throw configurationError(`No configuration provided${ending}`); } + return config; }); }; diff --git a/lib/getPostcssResult.js b/lib/getPostcssResult.js index 902a9d18f5..8e605bf989 100644 --- a/lib/getPostcssResult.js +++ b/lib/getPostcssResult.js @@ -16,6 +16,7 @@ const syntaxes /*: { stringify: postcss.stringify } }; + ["less", "sass", "scss", "markdown", "html", "styled", "jsx"].forEach(mod => { syntaxes[mod] = importLazy("postcss-" + mod); }); @@ -38,9 +39,11 @@ module.exports = function( const cached /*: ?postcss$result*/ = stylelint._postcssResultCache.get( options.filePath ); + if (cached) return Promise.resolve(cached); let getCode; + if (options.code !== undefined) { getCode = Promise.resolve(options.code); } else if (options.filePath) { @@ -64,6 +67,7 @@ module.exports = function( `Cannot resolve custom syntax module ${customSyntax}` ); } + /* * PostCSS allows for syntaxes that only contain a parser, however, * it then expects the syntax to be set as the `parser` option rather than `syntax. @@ -76,6 +80,7 @@ module.exports = function( } } else if (syntax) { syntax = syntaxes[syntax]; + if (!syntax) { throw new Error( "You must use a valid syntax option, either: scss, sass, less or sugarss" @@ -98,6 +103,7 @@ module.exports = function( const source = options.code ? options.codeFilename : options.filePath; let preProcessedCode = code; + if (options.codeProcessors) { options.codeProcessors.forEach(codeProcessor => { preProcessedCode = codeProcessor(preProcessedCode, source); @@ -114,6 +120,7 @@ module.exports = function( }) .then(postcssResult => { stylelint._postcssResultCache.set(options.filePath, postcssResult); + return postcssResult; }); }; @@ -124,6 +131,7 @@ function readFile(filePath /*: string*/) /*: Promise*/ { if (err) { return reject(err); } + resolve(content); }); }); diff --git a/lib/index.js b/lib/index.js index 842fb9e494..fea91a88ce 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,6 +17,7 @@ const api = postcssPlugin; const requiredRules = rules.reduce((acc, cur) => { acc[cur] = requireRule(cur); + return acc; }, {}); diff --git a/lib/isPathIgnored.js b/lib/isPathIgnored.js index eec6a6e3ee..8ea8746eac 100644 --- a/lib/isPathIgnored.js +++ b/lib/isPathIgnored.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const micromatch = require("micromatch"); const path = require("path"); @@ -11,6 +12,7 @@ module.exports = function( filePathArg /*:: ?: string*/ ) /*: Promise*/ { const filePath = filePathArg; // to please Flow + if (!filePath) { return Promise.resolve(false); } @@ -20,9 +22,11 @@ module.exports = function( const absoluteFilePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); + if (micromatch(absoluteFilePath, config.ignoreFiles).length) { return true; } + return false; }); }; diff --git a/lib/lintSource.js b/lib/lintSource.js index 5ea6962358..d56b07e4eb 100644 --- a/lib/lintSource.js +++ b/lib/lintSource.js @@ -93,6 +93,7 @@ module.exports = function lintSource( const isCodeNotFile = options.code !== undefined; const inputFilePath = isCodeNotFile ? options.codeFilename : options.filePath; + if (inputFilePath !== undefined && !path.isAbsolute(inputFilePath)) { if (isCodeNotFile) { return Promise.reject(new Error("codeFilename must be an absolute path")); @@ -103,6 +104,7 @@ module.exports = function lintSource( const getIsIgnored = stylelint.isPathIgnored(inputFilePath).catch(err => { if (isCodeNotFile && err.code === "ENOENT") return false; + throw err; }); @@ -111,9 +113,11 @@ module.exports = function lintSource( const postcssResult /*: Object*/ = options.existingPostcssResult || createEmptyPostcssResult(inputFilePath); + postcssResult.stylelint = postcssResult.stylelint || {}; postcssResult.stylelint.ignored = true; postcssResult.standaloneIgnored = true; // TODO: remove need for this + return postcssResult; } @@ -124,6 +128,7 @@ module.exports = function lintSource( .catch(err => { if (isCodeNotFile && err.code === "ENOENT") return stylelint.getConfigForFile(process.cwd()); + throw err; }); @@ -171,7 +176,9 @@ function lintPostcssResult( const newline = newlineMatch ? newlineMatch[0] : getOsEol(); const postcssRoot = postcssResult.root; + assignDisabledRanges(postcssRoot, postcssResult); + if ( stylelint._options.reportNeedlessDisables || stylelint._options.ignoreDisables @@ -195,6 +202,7 @@ function lintPostcssResult( } const ruleSettings = _.get(config, ["rules", ruleName]); + if (ruleSettings === null || ruleSettings[0] === null) { return; } @@ -204,6 +212,7 @@ function lintPostcssResult( // Log the rule's severity in the PostCSS result const defaultSeverity = config.defaultSeverity || "error"; + postcssResult.stylelint.ruleSeverities[ruleName] = _.get( secondaryOptions, "severity", @@ -220,6 +229,7 @@ function lintPostcssResult( newline })(postcssRoot, postcssResult); }); + performRules.push(performRule); }); diff --git a/lib/needlessDisables.js b/lib/needlessDisables.js index 3deb52f0a1..c340a53f34 100644 --- a/lib/needlessDisables.js +++ b/lib/needlessDisables.js @@ -43,11 +43,13 @@ module.exports = function( const rule /*: string*/ = warning.rule; const ruleRanges /*: Array*/ = rangeData[rule]; + if (ruleRanges) { // Back to front so we get the *last* range that applies to the warning for (const range of ruleRanges.reverse()) { if (isWarningInRange(warning, range)) { range.used = true; + return; } } @@ -56,6 +58,7 @@ module.exports = function( for (const range of rangeData.all.reverse()) { if (isWarningInRange(warning, range)) { range.used = true; + return; } } diff --git a/lib/normalizeRuleSettings.js b/lib/normalizeRuleSettings.js index 4ef0a90a83..c73cb5ebe4 100644 --- a/lib/normalizeRuleSettings.js +++ b/lib/normalizeRuleSettings.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); const requireRule = require("./requireRule"); @@ -37,6 +38,7 @@ module.exports = function( if (primaryOptionArray === undefined) { const rule /*: Function*/ = requireRule(ruleName); + primaryOptionArray = _.get(rule, "primaryOptionArray"); } diff --git a/lib/postcssPlugin.js b/lib/postcssPlugin.js index d071eea285..dee9764ca9 100644 --- a/lib/postcssPlugin.js +++ b/lib/postcssPlugin.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + /*:: type postcssType = { atRule: Function, comment: Function, @@ -113,6 +114,7 @@ module.exports = postcss.plugin("stylelint", function( // prettier-ignore return (root/*: rootParamT*/, result/*: resultParamT*/)/*: Promise*/ => { let filePath = options.from || _.get(root, "source.input.file"); + if (filePath !== undefined && !path.isAbsolute(filePath)) { filePath = path.join(process.cwd(), filePath); } diff --git a/lib/printConfig.js b/lib/printConfig.js index 941ac46091..ca0fde865f 100644 --- a/lib/printConfig.js +++ b/lib/printConfig.js @@ -18,6 +18,7 @@ module.exports = function( const files = options.files; const isCodeNotFile = code !== undefined; + if (!files || files.length !== 1 || isCodeNotFile) { return Promise.reject( new Error( diff --git a/lib/rules/at-rule-blacklist/index.js b/lib/rules/at-rule-blacklist/index.js index e0b1ab54f6..9c723b14ff 100644 --- a/lib/rules/at-rule-blacklist/index.js +++ b/lib/rules/at-rule-blacklist/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(blacklistInput) { // To allow for just a string as a parameter (not only arrays of strings) const blacklist = [].concat(blacklistInput); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/at-rule-empty-line-before/index.js b/lib/rules/at-rule-empty-line-before/index.js index 673ee16e16..1d8e62c688 100644 --- a/lib/rules/at-rule-empty-line-before/index.js +++ b/lib/rules/at-rule-empty-line-before/index.js @@ -53,12 +53,14 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } root.walkAtRules(atRule => { const isNested = atRule.parent.type !== "root"; + // Ignore the first node if (isFirstNodeOfRoot(atRule)) { return; @@ -160,6 +162,7 @@ const rule = function(expectation, options, context) { function isAtRuleAfterSameNameAtRule(atRule) { const previousNode = getPreviousNonSharedLineCommentNode(atRule); + return ( previousNode && previousNode.type === "atrule" && diff --git a/lib/rules/at-rule-name-case/index.js b/lib/rules/at-rule-name-case/index.js index 44adb20217..a830cd12dc 100644 --- a/lib/rules/at-rule-name-case/index.js +++ b/lib/rules/at-rule-name-case/index.js @@ -16,6 +16,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } @@ -32,6 +33,7 @@ const rule = function(expectation, options, context) { if (context.fix) { atRule.name = expectedName; + return; } diff --git a/lib/rules/at-rule-name-newline-after/index.js b/lib/rules/at-rule-name-newline-after/index.js index 4dd80fa6fd..a0531519da 100644 --- a/lib/rules/at-rule-name-newline-after/index.js +++ b/lib/rules/at-rule-name-newline-after/index.js @@ -13,11 +13,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line"] }); + if (!validOptions) { return; } diff --git a/lib/rules/at-rule-name-space-after/index.js b/lib/rules/at-rule-name-space-after/index.js index 8e923a9a52..7359ccd7a7 100644 --- a/lib/rules/at-rule-name-space-after/index.js +++ b/lib/rules/at-rule-name-space-after/index.js @@ -13,11 +13,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-single-line"] }); + if (!validOptions) { return; } diff --git a/lib/rules/at-rule-no-vendor-prefix/index.js b/lib/rules/at-rule-no-vendor-prefix/index.js index d327919320..d038912605 100644 --- a/lib/rules/at-rule-no-vendor-prefix/index.js +++ b/lib/rules/at-rule-no-vendor-prefix/index.js @@ -15,6 +15,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return function(root, result) { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/at-rule-semicolon-newline-after/index.js b/lib/rules/at-rule-semicolon-newline-after/index.js index 2aa93ef5b6..72b04dbf68 100644 --- a/lib/rules/at-rule-semicolon-newline-after/index.js +++ b/lib/rules/at-rule-semicolon-newline-after/index.js @@ -22,21 +22,25 @@ const rule = function(actual, secondary, context) { actual, possible: ["always"] }); + if (!validOptions) { return; } root.walkAtRules(atRule => { const nextNode = atRule.next(); + if (!nextNode) { return; } + if (hasBlock(atRule)) { return; } // Allow an end-of-line comment const nodeToCheck = nextNonCommentNode(nextNode); + if (!nodeToCheck) { return; } diff --git a/lib/rules/at-rule-semicolon-space-before/index.js b/lib/rules/at-rule-semicolon-space-before/index.js index 136e8a5f91..2dca1751c7 100644 --- a/lib/rules/at-rule-semicolon-space-before/index.js +++ b/lib/rules/at-rule-semicolon-space-before/index.js @@ -22,6 +22,7 @@ const rule = function(expectation) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -30,6 +31,7 @@ const rule = function(expectation) { if (hasBlock(atRule)) { return; } + const nodeString = rawNodeString(atRule); checker.before({ diff --git a/lib/rules/at-rule-whitelist/index.js b/lib/rules/at-rule-whitelist/index.js index e2ae6f1fdf..82cce114c8 100644 --- a/lib/rules/at-rule-whitelist/index.js +++ b/lib/rules/at-rule-whitelist/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(whitelistInput) { // To allow for just a string as a parameter (not only arrays of strings) const whitelist = [].concat(whitelistInput); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/atRuleNameSpaceChecker.js b/lib/rules/atRuleNameSpaceChecker.js index 5f17bb4997..7e1decef57 100644 --- a/lib/rules/atRuleNameSpaceChecker.js +++ b/lib/rules/atRuleNameSpaceChecker.js @@ -23,8 +23,10 @@ module.exports = function(options) { err: m => { if (options.fix) { options.fix(node); + return; } + report({ message: m, node, diff --git a/lib/rules/block-closing-brace-empty-line-before/index.js b/lib/rules/block-closing-brace-empty-line-before/index.js index 857435d57e..7846cc6b3d 100644 --- a/lib/rules/block-closing-brace-empty-line-before/index.js +++ b/lib/rules/block-closing-brace-empty-line-before/index.js @@ -36,6 +36,7 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -52,6 +53,7 @@ const rule = function(expectation, options, context) { // Get whitespace after ""}", ignoring extra semicolon const before = (statement.raws.after || "").replace(/;+/, ""); + if (before === undefined) { return; } @@ -59,6 +61,7 @@ const rule = function(expectation, options, context) { // Calculate index const statementString = statement.toString(); let index = statementString.length - 1; + if (statementString[index - 1] === "\r") { index -= 1; } @@ -95,6 +98,7 @@ const rule = function(expectation, options, context) { } else { removeEmptyLineAfter(statement, context.newline); } + return; } diff --git a/lib/rules/block-closing-brace-newline-after/index.js b/lib/rules/block-closing-brace-newline-after/index.js index 11994607a1..a9bc6cf86c 100644 --- a/lib/rules/block-closing-brace-newline-after/index.js +++ b/lib/rules/block-closing-brace-newline-after/index.js @@ -26,6 +26,7 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions( result, @@ -48,6 +49,7 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -60,11 +62,13 @@ const rule = function(expectation, options, context) { if (!hasBlock(statement)) { return; } + if (optionsMatches(options, "ignoreAtRules", statement.name)) { return; } const nextNode = statement.next(); + if (!nextNode) { return; } @@ -102,15 +106,18 @@ const rule = function(expectation, options, context) { if (context.fix) { if (expectation.indexOf("always") === 0) { const index = nodeToCheck.raws.before.search(/\r?\n/); + if (index >= 0) { nodeToCheck.raws.before = nodeToCheck.raws.before.slice(index); } else { nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before; } + return; } else if (expectation.indexOf("never") === 0) { nodeToCheck.raws.before = ""; + return; } } diff --git a/lib/rules/block-closing-brace-newline-before/index.js b/lib/rules/block-closing-brace-newline-before/index.js index 4ef77a26a9..06de886731 100644 --- a/lib/rules/block-closing-brace-newline-before/index.js +++ b/lib/rules/block-closing-brace-newline-before/index.js @@ -24,6 +24,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } @@ -40,6 +41,7 @@ const rule = function(expectation, options, context) { // Ignore extra semicolon const after = (statement.raws.after || "").replace(/;+/, ""); + if (after === undefined) { return; } @@ -48,6 +50,7 @@ const rule = function(expectation, options, context) { const statementString = statement.toString(); let index = statementString.length - 2; + if (statementString[index - 1] === "\r") { index -= 1; } @@ -86,6 +89,7 @@ const rule = function(expectation, options, context) { ? statement.raws.after.slice(firstWhitespaceIndex) : ""; const newlineIndex = newlineAfter.search(/\r?\n/); + if (newlineIndex >= 0) { statement.raws.after = newlineBefore + newlineAfter.slice(newlineIndex); @@ -93,9 +97,11 @@ const rule = function(expectation, options, context) { statement.raws.after = newlineBefore + context.newline + newlineAfter; } + return; } else if (expectation === "never-multi-line") { statement.raws.after = statement.raws.after.replace(/\s/g, ""); + return; } } diff --git a/lib/rules/block-closing-brace-space-after/index.js b/lib/rules/block-closing-brace-space-after/index.js index 976cf1b8b6..9fae46623b 100644 --- a/lib/rules/block-closing-brace-space-after/index.js +++ b/lib/rules/block-closing-brace-space-after/index.js @@ -38,6 +38,7 @@ const rule = function(expectation) { "never-multi-line" ] }); + if (!validOptions) { return; } @@ -48,9 +49,11 @@ const rule = function(expectation) { function check(statement) { const nextNode = statement.next(); + if (!nextNode) { return; } + if (!hasBlock(statement)) { return; } diff --git a/lib/rules/block-closing-brace-space-before/index.js b/lib/rules/block-closing-brace-space-before/index.js index 6f0588e863..b328ca7aff 100644 --- a/lib/rules/block-closing-brace-space-before/index.js +++ b/lib/rules/block-closing-brace-space-before/index.js @@ -38,6 +38,7 @@ const rule = function(expectation, options, context) { "never-multi-line" ] }); + if (!validOptions) { return; } @@ -56,6 +57,7 @@ const rule = function(expectation, options, context) { const statementString = statement.toString(); let index = statementString.length - 2; + if (statementString[index - 1] === "\r") { index -= 1; } @@ -67,12 +69,15 @@ const rule = function(expectation, options, context) { if (context.fix) { if (expectation.indexOf("always") === 0) { statement.raws.after = statement.raws.after.replace(/\s*$/, " "); + return; } else if (expectation.indexOf("never") === 0) { statement.raws.after = statement.raws.after.replace(/\s*$/, ""); + return; } } + report({ message: msg, node: statement, diff --git a/lib/rules/block-no-empty/index.js b/lib/rules/block-no-empty/index.js index f5df4c8680..46ad8775e4 100644 --- a/lib/rules/block-no-empty/index.js +++ b/lib/rules/block-no-empty/index.js @@ -15,6 +15,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/block-opening-brace-newline-after/index.js b/lib/rules/block-opening-brace-newline-after/index.js index 35934004d9..9c0e75d216 100644 --- a/lib/rules/block-opening-brace-newline-after/index.js +++ b/lib/rules/block-opening-brace-newline-after/index.js @@ -28,6 +28,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } @@ -53,10 +54,12 @@ const rule = function(expectation, options, context) { const newLineMatch = reNewLine.test(startNode.raws.before); const next = startNode.next(); + if (next && newLineMatch && !reNewLine.test(next.raws.before)) { backupCommentNextBefores.set(next, next.raws.before); next.raws.before = startNode.raws.before; } + return nextNode(next); } @@ -65,6 +68,7 @@ const rule = function(expectation, options, context) { // Allow an end-of-line comment const nodeToCheck = nextNode(statement.first); + if (!nodeToCheck) { return; } @@ -77,13 +81,16 @@ const rule = function(expectation, options, context) { if (context.fix) { if (expectation.indexOf("always") === 0) { const index = nodeToCheck.raws.before.search(/\r?\n/); + if (index >= 0) { nodeToCheck.raws.before = nodeToCheck.raws.before.slice(index); } else { nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before; } + backupCommentNextBefores.delete(nodeToCheck); + return; } else if (expectation === "never-multi-line") { // Restore the `before` of the node next to the comment node. @@ -95,6 +102,7 @@ const rule = function(expectation, options, context) { // Fix const reNewLine = /\r?\n/; let fixTarget = statement.first; + while (fixTarget) { if (reNewLine.test(fixTarget.raws.before)) { fixTarget.raws.before = fixTarget.raws.before.replace( @@ -102,15 +110,19 @@ const rule = function(expectation, options, context) { "" ); } + if (fixTarget.type !== "comment") { break; } + fixTarget = fixTarget.next(); } nodeToCheck.raws.before = ""; + return; } } + report({ message: m, node: statement, diff --git a/lib/rules/block-opening-brace-newline-before/index.js b/lib/rules/block-opening-brace-newline-before/index.js index e3d16b7c04..9cb3fb98e4 100644 --- a/lib/rules/block-opening-brace-newline-before/index.js +++ b/lib/rules/block-opening-brace-newline-before/index.js @@ -37,6 +37,7 @@ const rule = function(expectation, options, context) { "never-multi-line" ] }); + if (!validOptions) { return; } @@ -57,6 +58,7 @@ const rule = function(expectation, options, context) { }); let index = beforeBraceNoRaw.length - 1; + if (beforeBraceNoRaw[index - 1] === "\r") { index -= 1; } @@ -79,15 +81,18 @@ const rule = function(expectation, options, context) { statement.raws.between = statement.raws.between + context.newline; } + return; } else if (expectation.indexOf("never") === 0) { statement.raws.between = statement.raws.between.replace( /\s*$/, "" ); + return; } } + report({ message: m, node: statement, diff --git a/lib/rules/block-opening-brace-space-after/index.js b/lib/rules/block-opening-brace-space-after/index.js index 8fe54264ca..a8fd216d7b 100644 --- a/lib/rules/block-opening-brace-space-after/index.js +++ b/lib/rules/block-opening-brace-space-after/index.js @@ -26,6 +26,7 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, @@ -38,6 +39,7 @@ const rule = function(expectation, options, context) { "never-multi-line" ] }); + if (!validOptions) { return; } @@ -59,12 +61,15 @@ const rule = function(expectation, options, context) { if (context.fix) { if (expectation.indexOf("always") === 0) { statement.first.raws.before = " "; + return; } else if (expectation.indexOf("never") === 0) { statement.first.raws.before = ""; + return; } } + report({ message: m, node: statement, diff --git a/lib/rules/block-opening-brace-space-before/index.js b/lib/rules/block-opening-brace-space-before/index.js index 0469cb440e..3c2efa5949 100644 --- a/lib/rules/block-opening-brace-space-before/index.js +++ b/lib/rules/block-opening-brace-space-before/index.js @@ -28,6 +28,7 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions( result, @@ -51,6 +52,7 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -76,6 +78,7 @@ const rule = function(expectation, options, context) { }); let index = beforeBraceNoRaw.length - 1; + if (beforeBraceNoRaw[index - 1] === "\r") { index -= 1; } @@ -88,12 +91,15 @@ const rule = function(expectation, options, context) { if (context.fix) { if (expectation.indexOf("always") === 0) { statement.raws.between = " "; + return; } else if (expectation.indexOf("never") === 0) { statement.raws.between = ""; + return; } } + report({ message: m, node: statement, diff --git a/lib/rules/color-hex-case/index.js b/lib/rules/color-hex-case/index.js index e919178314..fb1a3d0399 100644 --- a/lib/rules/color-hex-case/index.js +++ b/lib/rules/color-hex-case/index.js @@ -18,6 +18,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } @@ -30,6 +31,7 @@ const rule = function(expectation, options, context) { const hexMatch = /^#[0-9A-Za-z]+/.exec( declString.substr(match.startIndex) ); + if (!hexMatch) { return; } diff --git a/lib/rules/color-hex-length/index.js b/lib/rules/color-hex-length/index.js index ce682220a7..97d1ffe587 100644 --- a/lib/rules/color-hex-length/index.js +++ b/lib/rules/color-hex-length/index.js @@ -18,6 +18,7 @@ const rule = function(expectation, _, context) { actual: expectation, possible: ["short", "long"] }); + if (!validOptions) { return; } @@ -30,6 +31,7 @@ const rule = function(expectation, _, context) { const hexMatch = /^#[0-9A-Za-z]+/.exec( declString.substr(match.startIndex) ); + if (!hexMatch) { return; } @@ -104,17 +106,21 @@ function canShrink(hex) { function shorter(hex) { let hexVariant = "#"; + for (let i = 1; i < hex.length; i = i + 2) { hexVariant += hex[i]; } + return hexVariant; } function longer(hex) { let hexVariant = "#"; + for (let i = 1; i < hex.length; i++) { hexVariant += hex[i] + hex[i]; } + return hexVariant; } diff --git a/lib/rules/color-named/generateColorFuncs.js b/lib/rules/color-named/generateColorFuncs.js index 8b95d95378..f95be3e8f8 100644 --- a/lib/rules/color-named/generateColorFuncs.js +++ b/lib/rules/color-named/generateColorFuncs.js @@ -83,6 +83,7 @@ function rgb2hsl(r, g, b) { const M = Math.max(r, g, b); const m = Math.min(r, g, b); const d = M - m; + if (d === 0) { h = 0; } else if (M === r) { @@ -150,10 +151,13 @@ function generateColorFuncs(hexString) { ") - expected 6 character hex string" ); } + const rgb = [0, 0, 0]; + for (let i = 0; i < 3; i += 1) { rgb[i] = parseInt(hexString.substr(2 * i + 1, 2), 16); } + const hsl = rgb2hsl(rgb[0], rgb[1], rgb[2]); const hwb = rgb2hwb(rgb[0], rgb[1], rgb[2]); const func = []; @@ -187,6 +191,7 @@ function generateColorFuncs(hexString) { if (lab[1] * lab[1] < 0.01 && lab[2] * lab[2] < 0.01) { // yay! gray! const grayStr = Math.round(lab[0]); + func.push("gray(" + grayStr + ")"); func.push("gray(" + grayStr + ",1)"); func.push("gray(" + grayStr + ",100%)"); @@ -194,6 +199,7 @@ function generateColorFuncs(hexString) { func.push("gray(" + grayStr + "%,1)"); func.push("gray(" + grayStr + "%,100%)"); } + return func; } diff --git a/lib/rules/color-named/index.js b/lib/rules/color-named/index.js index 3cc43c2619..2910097673 100644 --- a/lib/rules/color-named/index.js +++ b/lib/rules/color-named/index.js @@ -50,8 +50,10 @@ const rule = function(expectation, options) { const namedColors = Object.keys(namedColorDataHex); const namedColorData = {}; + namedColors.forEach(name => { const hex = namedColorDataHex[name]; + namedColorData[name] = { hex, func: generateColorFuncs(hex[0]) @@ -87,6 +89,7 @@ const rule = function(expectation, options) { if (!isStandardSyntaxValue(value)) { return; } + // Return early if neither a word nor a function if (NODE_TYPES.indexOf(type) === -1) { return; @@ -103,6 +106,7 @@ const rule = function(expectation, options) { decl, declarationValueIndex(decl) + sourceIndex ); + return; } @@ -121,8 +125,10 @@ const rule = function(expectation, options) { .stringify(node) .replace(/\s+/g, ""); let namedColor; + for (let i = 0, l = namedColors.length; i < l; i++) { namedColor = namedColors[i]; + if ( namedColorData[namedColor].func.indexOf( normalizedFunctionString.toLowerCase() @@ -133,16 +139,20 @@ const rule = function(expectation, options) { decl, declarationValueIndex(decl) + sourceIndex ); + return; // Exit as soon as a problem is found } } + return; } // Then by checking for alternative hex representations let namedColor; + for (let i = 0, l = namedColors.length; i < l; i++) { namedColor = namedColors[i]; + if ( namedColorData[namedColor].hex.indexOf(value.toLowerCase()) !== -1 ) { @@ -151,6 +161,7 @@ const rule = function(expectation, options) { decl, declarationValueIndex(decl) + sourceIndex ); + return; // Exit as soon as a problem is found } } diff --git a/lib/rules/color-no-hex/index.js b/lib/rules/color-no-hex/index.js index e08df8715e..66520c02c7 100644 --- a/lib/rules/color-no-hex/index.js +++ b/lib/rules/color-no-hex/index.js @@ -14,6 +14,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -32,9 +33,11 @@ const rule = function(actual) { const hexMatch = /^#[0-9A-Za-z]+/.exec( declString.substr(match.startIndex) ); + if (!hexMatch) { return; } + const hexValue = hexMatch[0]; report({ diff --git a/lib/rules/color-no-invalid-hex/index.js b/lib/rules/color-no-invalid-hex/index.js index 68cf6b659f..34eb6567cb 100644 --- a/lib/rules/color-no-invalid-hex/index.js +++ b/lib/rules/color-no-invalid-hex/index.js @@ -15,6 +15,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -33,11 +34,13 @@ const rule = function(actual) { const hexMatch = /^#[0-9A-Za-z]+/.exec( declString.substr(match.startIndex) ); + if (!hexMatch) { return; } const hexValue = hexMatch[0]; + if (isValidHex(hexValue)) { return; } diff --git a/lib/rules/comment-empty-line-before/index.js b/lib/rules/comment-empty-line-before/index.js index 6b0ed4ac45..9e0dfef876 100644 --- a/lib/rules/comment-empty-line-before/index.js +++ b/lib/rules/comment-empty-line-before/index.js @@ -39,6 +39,7 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -82,6 +83,7 @@ const rule = function(expectation, options, context) { ) { return false; } + return expectation === "always"; })(); diff --git a/lib/rules/comment-no-empty/index.js b/lib/rules/comment-no-empty/index.js index 6f8bd97d18..6edd166aa8 100644 --- a/lib/rules/comment-no-empty/index.js +++ b/lib/rules/comment-no-empty/index.js @@ -13,6 +13,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/comment-whitespace-inside/index.js b/lib/rules/comment-whitespace-inside/index.js index 0d08e4daff..1bf49b4bab 100755 --- a/lib/rules/comment-whitespace-inside/index.js +++ b/lib/rules/comment-whitespace-inside/index.js @@ -22,6 +22,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -101,6 +102,7 @@ const rule = function(expectation, options, context) { addWhitespaceAfter(comment); } } + return; } diff --git a/lib/rules/comment-word-blacklist/index.js b/lib/rules/comment-word-blacklist/index.js index 48d868e84c..8fed84088b 100644 --- a/lib/rules/comment-word-blacklist/index.js +++ b/lib/rules/comment-word-blacklist/index.js @@ -19,6 +19,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/custom-media-pattern/index.js b/lib/rules/custom-media-pattern/index.js index 49ad902192..24573b875e 100644 --- a/lib/rules/custom-media-pattern/index.js +++ b/lib/rules/custom-media-pattern/index.js @@ -18,6 +18,7 @@ const rule = function(pattern) { actual: pattern, possible: [_.isRegExp, _.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/custom-property-empty-line-before/index.js b/lib/rules/custom-property-empty-line-before/index.js index a15afea200..695f1e9465 100644 --- a/lib/rules/custom-property-empty-line-before/index.js +++ b/lib/rules/custom-property-empty-line-before/index.js @@ -40,6 +40,7 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -51,6 +52,7 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxDeclaration(decl)) { return; } + if (!isCustomProperty(prop)) { return; } @@ -114,6 +116,7 @@ const rule = function(expectation, options, context) { const message = expectEmptyLineBefore ? messages.expected : messages.rejected; + report({ message, node: decl, @@ -126,6 +129,7 @@ const rule = function(expectation, options, context) { function isAfterCustomProperty(decl) { const prevNode = getPreviousNonSharedLineCommentNode(decl); + return prevNode && prevNode.prop && isCustomProperty(prevNode.prop); } diff --git a/lib/rules/custom-property-pattern/index.js b/lib/rules/custom-property-pattern/index.js index d31fa6857d..8377aaa26a 100644 --- a/lib/rules/custom-property-pattern/index.js +++ b/lib/rules/custom-property-pattern/index.js @@ -18,6 +18,7 @@ const rule = function(pattern) { actual: pattern, possible: [_.isRegExp, _.isString] }); + if (!validOptions) { return; } @@ -30,6 +31,7 @@ const rule = function(pattern) { if (!isCustomProperty(prop)) { return; } + if (regexpPattern.test(prop.slice(2))) { return; } diff --git a/lib/rules/declaration-bang-space-after/index.js b/lib/rules/declaration-bang-space-after/index.js index 9a5c8d9868..0bdc2c5e26 100644 --- a/lib/rules/declaration-bang-space-after/index.js +++ b/lib/rules/declaration-bang-space-after/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -35,6 +37,7 @@ const rule = function(expectation, options, context) { const value = decl.raws.value ? decl.raws.value.raw : decl.value; let target; let setFixed; + if (bangIndex < value.length) { target = value; setFixed = value => { @@ -59,9 +62,11 @@ const rule = function(expectation, options, context) { if (expectation === "always") { setFixed(targetBefore + targetAfter.replace(/^\s*/, " ")); + return true; } else if (expectation === "never") { setFixed(targetBefore + targetAfter.replace(/^\s*/, "")); + return true; } } diff --git a/lib/rules/declaration-bang-space-before/index.js b/lib/rules/declaration-bang-space-before/index.js index fb2b959da7..88e579169f 100644 --- a/lib/rules/declaration-bang-space-before/index.js +++ b/lib/rules/declaration-bang-space-before/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -35,6 +37,7 @@ const rule = function(expectation, options, context) { const value = decl.raws.value ? decl.raws.value.raw : decl.value; let target; let setFixed; + if (bangIndex < value.length) { target = value; setFixed = value => { @@ -59,9 +62,11 @@ const rule = function(expectation, options, context) { if (expectation === "always") { setFixed(targetBefore.replace(/\s*$/, "") + " " + targetAfter); + return true; } else if (expectation === "never") { setFixed(targetBefore.replace(/\s*$/, "") + targetAfter); + return true; } } diff --git a/lib/rules/declaration-block-no-duplicate-properties/index.js b/lib/rules/declaration-block-no-duplicate-properties/index.js index 4cd5245732..c9f665f7d7 100644 --- a/lib/rules/declaration-block-no-duplicate-properties/index.js +++ b/lib/rules/declaration-block-no-duplicate-properties/index.js @@ -33,6 +33,7 @@ const rule = function(on, options) { optional: true } ); + if (!validOptions) { return; } @@ -40,6 +41,7 @@ const rule = function(on, options) { eachDeclarationBlock(root, eachDecl => { const decls = []; const values = []; + eachDecl(decl => { const prop = decl.prop; const value = decl.value; @@ -47,6 +49,7 @@ const rule = function(on, options) { if (!isStandardSyntaxProperty(prop)) { return; } + if (isCustomProperty(prop)) { return; } @@ -79,8 +82,10 @@ const rule = function(on, options) { result, ruleName }); + return; } + // if values of consecutive duplicates are equal if (value === values[indexDuplicate]) { report({ @@ -89,8 +94,10 @@ const rule = function(on, options) { result, ruleName }); + return; } + return; } diff --git a/lib/rules/declaration-block-no-redundant-longhand-properties/index.js b/lib/rules/declaration-block-no-redundant-longhand-properties/index.js index 8d0a6a1a10..96a1c3930e 100644 --- a/lib/rules/declaration-block-no-redundant-longhand-properties/index.js +++ b/lib/rules/declaration-block-no-redundant-longhand-properties/index.js @@ -29,6 +29,7 @@ const rule = function(actual, options) { optional: true } ); + if (!validOptions) { return; } @@ -48,6 +49,7 @@ const rule = function(actual, options) { eachDeclarationBlock(root, eachDecl => { const longhandDeclarations = {}; + eachDecl(decl => { const prop = decl.prop.toLowerCase(); const unprefixedProp = postcss.vendor.unprefixed(prop); diff --git a/lib/rules/declaration-block-no-shorthand-property-overrides/index.js b/lib/rules/declaration-block-no-shorthand-property-overrides/index.js index 06258c1381..c69dfb92c7 100644 --- a/lib/rules/declaration-block-no-shorthand-property-overrides/index.js +++ b/lib/rules/declaration-block-no-shorthand-property-overrides/index.js @@ -17,6 +17,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -30,14 +31,18 @@ const rule = function(actual) { const prefix = postcss.vendor.prefix(prop).toLowerCase(); const overrideables = shorthandData[unprefixedProp.toLowerCase()]; + if (!overrideables) { declarations[prop.toLowerCase()] = prop; + return; } + overrideables.forEach(longhandProp => { if (!declarations.hasOwnProperty(prefix + longhandProp)) { return; } + report({ ruleName, result, diff --git a/lib/rules/declaration-block-semicolon-newline-after/index.js b/lib/rules/declaration-block-semicolon-newline-after/index.js index 8ffc1a91a0..474216c7b1 100644 --- a/lib/rules/declaration-block-semicolon-newline-after/index.js +++ b/lib/rules/declaration-block-semicolon-newline-after/index.js @@ -26,6 +26,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } @@ -33,17 +34,20 @@ const rule = function(expectation, options, context) { root.walkDecls(decl => { // Ignore last declaration if there's no trailing semicolon const parentRule = decl.parent; + if (!parentRule.raws.semicolon && parentRule.last === decl) { return; } const nextNode = decl.next(); + if (!nextNode) { return; } // Allow end-of-line comment const nodeToCheck = nextNonCommentNode(nextNode); + if (!nodeToCheck) { return; } @@ -56,15 +60,18 @@ const rule = function(expectation, options, context) { if (context.fix) { if (expectation.indexOf("always") === 0) { const index = nodeToCheck.raws.before.search(/\r?\n/); + if (index >= 0) { nodeToCheck.raws.before = nodeToCheck.raws.before.slice(index); } else { nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before; } + return; } else if (expectation === "never-multi-line") { nodeToCheck.raws.before = ""; + return; } } diff --git a/lib/rules/declaration-block-semicolon-newline-before/index.js b/lib/rules/declaration-block-semicolon-newline-before/index.js index 7475e0cd58..67f928449b 100644 --- a/lib/rules/declaration-block-semicolon-newline-before/index.js +++ b/lib/rules/declaration-block-semicolon-newline-before/index.js @@ -24,12 +24,14 @@ const rule = function(expectation) { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } root.walkDecls(function(decl) { const parentRule = decl.parent; + if (!parentRule.raws.semicolon && parentRule.last === decl) { return; } diff --git a/lib/rules/declaration-block-semicolon-space-after/index.js b/lib/rules/declaration-block-semicolon-space-after/index.js index bec6e8a56a..2e78487e09 100644 --- a/lib/rules/declaration-block-semicolon-space-after/index.js +++ b/lib/rules/declaration-block-semicolon-space-after/index.js @@ -26,6 +26,7 @@ const rule = function(expectation) { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } @@ -33,11 +34,13 @@ const rule = function(expectation) { root.walkDecls(function(decl) { // Ignore last declaration if there's no trailing semicolon const parentRule = decl.parent; + if (!parentRule.raws.semicolon && parentRule.last === decl) { return; } const nextDecl = decl.next(); + if (!nextDecl) { return; } diff --git a/lib/rules/declaration-block-semicolon-space-before/index.js b/lib/rules/declaration-block-semicolon-space-before/index.js index b8354f8874..315658a54e 100644 --- a/lib/rules/declaration-block-semicolon-space-before/index.js +++ b/lib/rules/declaration-block-semicolon-space-before/index.js @@ -25,6 +25,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } @@ -32,6 +33,7 @@ const rule = function(expectation, options, context) { root.walkDecls(decl => { // Ignore last declaration if there's no trailing semicolon const parentRule = decl.parent; + if (!parentRule.raws.semicolon && parentRule.last === decl) { return; } @@ -45,24 +47,30 @@ const rule = function(expectation, options, context) { err: m => { if (context.fix) { const value = decl.raws.value ? decl.raws.value.raw : decl.value; + if (expectation.indexOf("always") === 0) { const fixedValue = value.replace(/\s*$/, " "); + if (decl.raws.value) { decl.raws.value.raw = fixedValue; } else { decl.value = fixedValue; } + return; } else if (expectation.indexOf("never") === 0) { const fixedValue = value.replace(/\s*$/, ""); + if (decl.raws.value) { decl.raws.value.raw = fixedValue; } else { decl.value = fixedValue; } + return; } } + report({ message: m, node: decl, diff --git a/lib/rules/declaration-block-single-line-max-declarations/index.js b/lib/rules/declaration-block-single-line-max-declarations/index.js index af7fcf329b..819ab2bd64 100644 --- a/lib/rules/declaration-block-single-line-max-declarations/index.js +++ b/lib/rules/declaration-block-single-line-max-declarations/index.js @@ -21,6 +21,7 @@ const rule = function(quantity) { actual: quantity, possible: [_.isNumber] }); + if (!validOptions) { return; } @@ -29,6 +30,7 @@ const rule = function(quantity) { if (!isSingleLineString(blockString(rule))) { return; } + if (!rule.nodes) { return; } diff --git a/lib/rules/declaration-block-trailing-semicolon/index.js b/lib/rules/declaration-block-trailing-semicolon/index.js index 61cf337701..ef1195f17f 100644 --- a/lib/rules/declaration-block-trailing-semicolon/index.js +++ b/lib/rules/declaration-block-trailing-semicolon/index.js @@ -18,6 +18,7 @@ const rule = function(expectation, _, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -26,12 +27,15 @@ const rule = function(expectation, _, context) { if (atRule.parent === root) { return; } + if (atRule !== atRule.parent.last) { return; } + if (hasBlock(atRule)) { return; } + checkLastNode(atRule); }); @@ -39,6 +43,7 @@ const rule = function(expectation, _, context) { if (decl !== decl.parent.last) { return; } + checkLastNode(decl); }); @@ -53,10 +58,12 @@ const rule = function(expectation, _, context) { // auto-fix if (context.fix) { node.parent.raws.semicolon = true; + if (node.type === "atrule") { node.raws.between = ""; node.parent.raws.after = " "; } + return; } @@ -69,6 +76,7 @@ const rule = function(expectation, _, context) { // auto-fix if (context.fix) { node.parent.raws.semicolon = false; + return; } diff --git a/lib/rules/declaration-colon-newline-after/index.js b/lib/rules/declaration-colon-newline-after/index.js index e06503a480..c2c1256bee 100644 --- a/lib/rules/declaration-colon-newline-after/index.js +++ b/lib/rules/declaration-colon-newline-after/index.js @@ -17,11 +17,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line"] }); + if (!validOptions) { return; } @@ -43,6 +45,7 @@ const rule = function(expectation, options, context) { if (propPlusColon[i] !== ":") { continue; } + const indexToCheck = /^[^\S\r\n]*\/\*/.test(propPlusColon.slice(i + 1)) ? propPlusColon.indexOf("*/", i) + 1 : i; @@ -58,6 +61,7 @@ const rule = function(expectation, options, context) { const sliceIndex = indexToCheck - betweenStart + 1; const betweenBefore = between.slice(0, sliceIndex); const betweenAfter = between.slice(sliceIndex); + if (/^\s*\r?\n/.test(betweenAfter)) { decl.raws.between = betweenBefore + betweenAfter.replace(/^[^\S\r\n]*/, ""); @@ -65,8 +69,10 @@ const rule = function(expectation, options, context) { decl.raws.between = betweenBefore + context.newline + betweenAfter; } + return; } + report({ message: m, node: decl, diff --git a/lib/rules/declaration-colon-space-after/index.js b/lib/rules/declaration-colon-space-after/index.js index 549dc01445..44b3ab15e8 100644 --- a/lib/rules/declaration-colon-space-after/index.js +++ b/lib/rules/declaration-colon-space-after/index.js @@ -17,11 +17,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line"] }); + if (!validOptions) { return; } @@ -35,15 +37,18 @@ const rule = function(expectation, options, context) { ? (decl, index) => { const colonIndex = index - declarationValueIndex(decl); const between = decl.raws.between; + if (expectation.indexOf("always") === 0) { decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/, ": "); + return true; } else if (expectation === "never") { decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/, ":"); + return true; } } diff --git a/lib/rules/declaration-colon-space-before/index.js b/lib/rules/declaration-colon-space-before/index.js index 57527520d1..5d8306a894 100644 --- a/lib/rules/declaration-colon-space-before/index.js +++ b/lib/rules/declaration-colon-space-before/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -33,15 +35,18 @@ const rule = function(expectation, options, context) { ? (decl, index) => { const colonIndex = index - declarationValueIndex(decl); const between = decl.raws.between; + if (expectation === "always") { decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, " ") + between.slice(colonIndex); + return true; } else if (expectation === "never") { decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, "") + between.slice(colonIndex); + return true; } } diff --git a/lib/rules/declaration-empty-line-before/index.js b/lib/rules/declaration-empty-line-before/index.js index 943a98f9e4..1cb914789e 100644 --- a/lib/rules/declaration-empty-line-before/index.js +++ b/lib/rules/declaration-empty-line-before/index.js @@ -45,6 +45,7 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -56,6 +57,7 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxDeclaration(decl)) { return; } + if (isCustomProperty(prop)) { return; } @@ -128,6 +130,7 @@ const rule = function(expectation, options, context) { const message = expectEmptyLineBefore ? messages.expected : messages.rejected; + report({ message, node: decl, result, ruleName }); }); }; diff --git a/lib/rules/declaration-no-important/index.js b/lib/rules/declaration-no-important/index.js index 986ca16b4a..0462903d1a 100644 --- a/lib/rules/declaration-no-important/index.js +++ b/lib/rules/declaration-no-important/index.js @@ -13,6 +13,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/declaration-property-unit-blacklist/index.js b/lib/rules/declaration-property-unit-blacklist/index.js index 3f2cd9c8c4..ac208940e1 100644 --- a/lib/rules/declaration-property-unit-blacklist/index.js +++ b/lib/rules/declaration-property-unit-blacklist/index.js @@ -23,6 +23,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isObject] }); + if (!validOptions) { return; } @@ -46,6 +47,7 @@ const rule = function(blacklist) { if (node.type === "function" && node.value.toLowerCase() === "url") { return false; } + if (node.type === "string") { return; } diff --git a/lib/rules/declaration-property-unit-whitelist/index.js b/lib/rules/declaration-property-unit-whitelist/index.js index 98c9c4f43f..acc924b538 100644 --- a/lib/rules/declaration-property-unit-whitelist/index.js +++ b/lib/rules/declaration-property-unit-whitelist/index.js @@ -23,6 +23,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isObject] }); + if (!validOptions) { return; } @@ -46,6 +47,7 @@ const rule = function(whitelist) { if (node.type === "function" && node.value.toLowerCase() === "url") { return false; } + if (node.type === "string") { return; } diff --git a/lib/rules/declaration-property-value-blacklist/index.js b/lib/rules/declaration-property-value-blacklist/index.js index 42eba1f6aa..5ef49711da 100644 --- a/lib/rules/declaration-property-value-blacklist/index.js +++ b/lib/rules/declaration-property-value-blacklist/index.js @@ -20,6 +20,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isObject] }); + if (!validOptions) { return; } diff --git a/lib/rules/declaration-property-value-whitelist/index.js b/lib/rules/declaration-property-value-whitelist/index.js index 87f601c23d..2a7fa81220 100644 --- a/lib/rules/declaration-property-value-whitelist/index.js +++ b/lib/rules/declaration-property-value-whitelist/index.js @@ -20,6 +20,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isObject] }); + if (!validOptions) { return; } diff --git a/lib/rules/declarationBangSpaceChecker.js b/lib/rules/declarationBangSpaceChecker.js index 351e366b23..b46769a35d 100644 --- a/lib/rules/declarationBangSpaceChecker.js +++ b/lib/rules/declarationBangSpaceChecker.js @@ -9,6 +9,7 @@ module.exports = function(opts) { const indexOffset = declarationValueIndex(decl); const declString = decl.toString(); const valueString = decl.toString().slice(indexOffset); + if (valueString.indexOf("!") === -1) { return; } @@ -26,6 +27,7 @@ module.exports = function(opts) { if (opts.fix && opts.fix(node, index)) { return; } + report({ message: m, node, diff --git a/lib/rules/declarationColonSpaceChecker.js b/lib/rules/declarationColonSpaceChecker.js index 1ec4115400..bbf84ec754 100644 --- a/lib/rules/declarationColonSpaceChecker.js +++ b/lib/rules/declarationColonSpaceChecker.js @@ -22,6 +22,7 @@ module.exports = function(opts) { if (propPlusColon[i] !== ":") { continue; } + opts.locationChecker({ source: propPlusColon, index: i, @@ -30,6 +31,7 @@ module.exports = function(opts) { if (opts.fix && opts.fix(decl, i)) { return; } + report({ message: m, node: decl, diff --git a/lib/rules/findMediaOperator.js b/lib/rules/findMediaOperator.js index 34c7b0135a..e67fa0f1a1 100644 --- a/lib/rules/findMediaOperator.js +++ b/lib/rules/findMediaOperator.js @@ -12,9 +12,11 @@ module.exports = function(atRule, cb) { styleSearch({ source: params, target: rangeOperators }, match => { const before = params[match.startIndex - 1]; + if (before === ">" || before === "<") { return; } + cb(match, params, atRule); }); }; diff --git a/lib/rules/font-family-name-quotes/index.js b/lib/rules/font-family-name-quotes/index.js index 6b8a9a4572..d41c5e752b 100644 --- a/lib/rules/font-family-name-quotes/index.js +++ b/lib/rules/font-family-name-quotes/index.js @@ -19,9 +19,11 @@ function isSystemFontKeyword(font) { if (font.indexOf("-apple-") === 0) { return true; } + if (font === "BlinkMacSystemFont") { return true; } + return false; } @@ -53,6 +55,7 @@ const rule = function(expectation) { "always-unless-keyword" ] }); + if (!validOptions) { return; } @@ -79,6 +82,7 @@ const rule = function(expectation) { if (!isStandardSyntaxValue(rawFamily)) { return; } + if (isVariable(rawFamily)) { return; } @@ -97,6 +101,7 @@ const rule = function(expectation) { if (hasQuotes) { return complain(messages.rejected(family), family, decl); } + return; } @@ -108,24 +113,29 @@ const rule = function(expectation) { if (!hasQuotes) { return complain(messages.expected(family), family, decl); } + return; case "always-where-recommended": if (!recommended && hasQuotes) { return complain(messages.rejected(family), family, decl); } + if (recommended && !hasQuotes) { return complain(messages.expected(family), family, decl); } + return; case "always-where-required": if (!required && hasQuotes) { return complain(messages.rejected(family), family, decl); } + if (required && !hasQuotes) { return complain(messages.expected(family), family, decl); } + return; } } diff --git a/lib/rules/font-family-no-duplicate-names/index.js b/lib/rules/font-family-no-duplicate-names/index.js index 23248c772b..3f6bbaa5d6 100644 --- a/lib/rules/font-family-no-duplicate-names/index.js +++ b/lib/rules/font-family-no-duplicate-names/index.js @@ -32,6 +32,7 @@ const rule = function(actual, options) { optional: true } ); + if (!validOptions) { return; } @@ -66,10 +67,12 @@ const rule = function(actual, options) { declarationValueIndex(decl) + fontFamilyNode.sourceIndex, decl ); + return; } keywords.add(family); + return; } @@ -79,6 +82,7 @@ const rule = function(actual, options) { declarationValueIndex(decl) + fontFamilyNode.sourceIndex, decl ); + return; } diff --git a/lib/rules/font-family-no-missing-generic-family-keyword/index.js b/lib/rules/font-family-no-missing-generic-family-keyword/index.js index 641d712b08..5196074fde 100644 --- a/lib/rules/font-family-no-missing-generic-family-keyword/index.js +++ b/lib/rules/font-family-no-missing-generic-family-keyword/index.js @@ -19,6 +19,7 @@ const isFamilyNameKeyword = node => const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/font-weight-notation/index.js b/lib/rules/font-weight-notation/index.js index c1512b0a8f..d680418090 100644 --- a/lib/rules/font-weight-notation/index.js +++ b/lib/rules/font-weight-notation/index.js @@ -41,6 +41,7 @@ const rule = function(expectation, options) { optional: true } ); + if (!validOptions) { return; } @@ -70,6 +71,7 @@ const rule = function(expectation, options) { keywordSets.fontWeightKeywords.has(value.toLowerCase())) ) { checkWeight(value, decl); + return; } } @@ -79,9 +81,11 @@ const rule = function(expectation, options) { if (!isStandardSyntaxValue(weightValue)) { return; } + if (isVariable(weightValue)) { return; } + if ( weightValue.toLowerCase() === INHERIT_KEYWORD || weightValue.toLowerCase() === INITIAL_KEYWORD @@ -109,14 +113,17 @@ const rule = function(expectation, options) { if (_.includes(WEIGHTS_WITH_KEYWORD_EQUIVALENTS, weightValue)) { complain(messages.expected("named")); } + return; } + if ( !keywordSets.fontWeightKeywords.has(weightValue.toLowerCase()) && weightValue.toLowerCase() !== NORMAL_KEYWORD ) { return complain(messages.invalidNamed(weightValue)); } + return; } diff --git a/lib/rules/function-blacklist/index.js b/lib/rules/function-blacklist/index.js index 72119eabd8..6afe914556 100644 --- a/lib/rules/function-blacklist/index.js +++ b/lib/rules/function-blacklist/index.js @@ -22,9 +22,11 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } + root.walkDecls(decl => { const value = decl.value; @@ -32,9 +34,11 @@ const rule = function(blacklist) { if (node.type !== "function") { return; } + if (!isStandardSyntaxFunction(node)) { return; } + if ( !matchesStringOrRegExp( postcss.vendor.unprefixed(node.value), diff --git a/lib/rules/function-calc-no-unspaced-operator/index.js b/lib/rules/function-calc-no-unspaced-operator/index.js index d6d65eb775..3ace1a1707 100644 --- a/lib/rules/function-calc-no-unspaced-operator/index.js +++ b/lib/rules/function-calc-no-unspaced-operator/index.js @@ -22,6 +22,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -97,6 +98,7 @@ const rule = function(actual) { decl, expressionIndex + index ); + return; } @@ -104,6 +106,7 @@ const rule = function(actual) { (expression[index - 1] === " " && !isWhitespace(expression[index - 2])) || newlineBefore(expression, index - 1); + if (!beforeOk) { complain( messages.expectedBefore(symbol), @@ -138,10 +141,13 @@ function blurVariables(source) { function newlineBefore(str, startIndex) { let index = startIndex; + while (index && isWhitespace(str[index])) { if (str[index] === "\n") return true; + index--; } + return false; } diff --git a/lib/rules/function-comma-newline-after/index.js b/lib/rules/function-comma-newline-after/index.js index 58bb7a4cfc..3d84bb2816 100644 --- a/lib/rules/function-comma-newline-after/index.js +++ b/lib/rules/function-comma-newline-after/index.js @@ -17,11 +17,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } diff --git a/lib/rules/function-comma-newline-before/index.js b/lib/rules/function-comma-newline-before/index.js index 5aa9a65ec4..3c4af0b3d2 100644 --- a/lib/rules/function-comma-newline-before/index.js +++ b/lib/rules/function-comma-newline-before/index.js @@ -17,11 +17,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } diff --git a/lib/rules/function-comma-space-after/index.js b/lib/rules/function-comma-space-after/index.js index 529dff8144..ad9df191fe 100644 --- a/lib/rules/function-comma-space-after/index.js +++ b/lib/rules/function-comma-space-after/index.js @@ -18,11 +18,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } @@ -36,20 +38,26 @@ const rule = function(expectation, options, context) { ? (div, index, nodes) => { if (expectation.indexOf("always") === 0) { div.after = " "; + return true; } else if (expectation.indexOf("never") === 0) { div.after = ""; + for (let i = index + 1; i < nodes.length; i++) { const node = nodes[i]; + if (node.type === "comment") { continue; } + if (node.type === "space") { node.value = ""; continue; } + break; } + return true; } } diff --git a/lib/rules/function-comma-space-before/index.js b/lib/rules/function-comma-space-before/index.js index f8e896fd14..5e4ad4150e 100644 --- a/lib/rules/function-comma-space-before/index.js +++ b/lib/rules/function-comma-space-before/index.js @@ -18,11 +18,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } @@ -36,9 +38,11 @@ const rule = function(expectation, options, context) { ? div => { if (expectation.indexOf("always") === 0) { div.before = " "; + return true; } else if (expectation.indexOf("never") === 0) { div.before = ""; + return true; } } diff --git a/lib/rules/function-linear-gradient-no-nonstandard-direction/index.js b/lib/rules/function-linear-gradient-no-nonstandard-direction/index.js index d350e300f3..6399ec26bd 100644 --- a/lib/rules/function-linear-gradient-no-nonstandard-direction/index.js +++ b/lib/rules/function-linear-gradient-no-nonstandard-direction/index.js @@ -20,22 +20,27 @@ function isStandardDirection(source, withToPrefix) { : /^(top|left|bottom|right)(?: (top|left|bottom|right))?$/; const matches = source.match(regexp); + if (!matches) { return false; } + if (matches.length === 2) { return true; } + // Cannot repeat side-or-corner, e.g. "to top top" if (matches.length === 3 && matches[1] !== matches[2]) { return true; } + return false; } const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -45,6 +50,7 @@ const rule = function(actual) { if (valueNode.type !== "function") { return; } + functionArgumentsSearch( valueParser.stringify(valueNode).toLowerCase(), "linear-gradient", @@ -56,7 +62,9 @@ const rule = function(actual) { if (/^[\d.]+(?:deg|grad|rad|turn)$/.test(firstArg)) { return; } + complain(); + return; } @@ -68,8 +76,10 @@ const rule = function(actual) { } const withToPrefix = !postcss.vendor.prefix(valueNode.value); + if (!isStandardDirection(firstArg, withToPrefix)) { complain(); + return; } diff --git a/lib/rules/function-max-empty-lines/index.js b/lib/rules/function-max-empty-lines/index.js index a77d5c0c0e..849f349ee4 100644 --- a/lib/rules/function-max-empty-lines/index.js +++ b/lib/rules/function-max-empty-lines/index.js @@ -22,6 +22,7 @@ const rule = function(max, options, context) { actual: max, possible: _.isNumber }); + if (!validOptions) { return; } @@ -51,6 +52,7 @@ const rule = function(max, options, context) { ) { // Put index at `\r` if it's CRLF, otherwise leave it at `\n` let index = match.startIndex; + if (declString[index - 1] === "\r") { index -= 1; } diff --git a/lib/rules/function-name-case/index.js b/lib/rules/function-name-case/index.js index e37cf38d06..637a921f68 100644 --- a/lib/rules/function-name-case/index.js +++ b/lib/rules/function-name-case/index.js @@ -17,6 +17,7 @@ const messages = ruleMessages(ruleName, { }); const mapLowercaseFunctionNamesToCamelCase = new Map(); + keywordSets.camelCaseFunctionNames.forEach(func => { mapLowercaseFunctionNamesToCamelCase.set(func.toLowerCase(), func); }); @@ -38,6 +39,7 @@ const rule = function(expectation, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -87,6 +89,7 @@ const rule = function(expectation, options, context) { if (context.fix) { needFix = true; node.value = expectedFunctionName; + return; } @@ -101,6 +104,7 @@ const rule = function(expectation, options, context) { if (context.fix && needFix) { const statement = parsed.toString(); + if (decl.raws.value) { decl.raws.value.raw = statement; } else { diff --git a/lib/rules/function-parentheses-newline-inside/index.js b/lib/rules/function-parentheses-newline-inside/index.js index a234e3a8fc..0bd83908c0 100644 --- a/lib/rules/function-parentheses-newline-inside/index.js +++ b/lib/rules/function-parentheses-newline-inside/index.js @@ -30,6 +30,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } @@ -42,6 +43,7 @@ const rule = function(expectation, options, context) { let hasFixed = false; const declValue = _.get(decl, "raws.value.raw", decl.value); const parsedValue = valueParser(declValue); + parsedValue.walk(valueNode => { if (valueNode.type !== "function") { return; @@ -53,6 +55,7 @@ const rule = function(expectation, options, context) { const functionString = valueParser.stringify(valueNode); const isMultiLine = !isSingleLineString(functionString); + function containsNewline(str) { return str.indexOf("\n") !== -1; } @@ -161,46 +164,60 @@ const rule = function(expectation, options, context) { function getCheckBefore(valueNode) { let before = valueNode.before; + for (const node of valueNode.nodes) { if (node.type === "comment") { continue; } + if (node.type === "space") { before += node.value; continue; } + break; } + return before; } + function getCheckAfter(valueNode) { let after = ""; + for (const node of valueNode.nodes.slice().reverse()) { if (node.type === "comment") { continue; } + if (node.type === "space") { after = node.value + after; continue; } + break; } + after += valueNode.after; + return after; } function fixBeforeForAlways(valueNode, newline) { let target; + for (const node of valueNode.nodes) { if (node.type === "comment") { continue; } + if (node.type === "space") { target = node; continue; } + break; } + if (target) { target.value = newline + target.value; } else { @@ -210,14 +227,17 @@ function fixBeforeForAlways(valueNode, newline) { function fixBeforeForNever(valueNode) { valueNode.before = ""; + for (const node of valueNode.nodes) { if (node.type === "comment") { continue; } + if (node.type === "space") { node.value = ""; continue; } + break; } } @@ -228,14 +248,17 @@ function fixAfterForAlways(valueNode, newline) { function fixAfterForNever(valueNode) { valueNode.after = ""; + for (const node of valueNode.nodes.slice().reverse()) { if (node.type === "comment") { continue; } + if (node.type === "space") { node.value = ""; continue; } + break; } } diff --git a/lib/rules/function-parentheses-space-inside/index.js b/lib/rules/function-parentheses-space-inside/index.js index ccf6cdb421..7bef9cde49 100644 --- a/lib/rules/function-parentheses-space-inside/index.js +++ b/lib/rules/function-parentheses-space-inside/index.js @@ -32,6 +32,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } @@ -40,9 +41,11 @@ const rule = function(expectation, options, context) { if (decl.value.indexOf("(") === -1) { return; } + let hasFixed = false; const declValue = _.get(decl, "raws.value.raw", decl.value); const parsedValue = valueParser(declValue); + parsedValue.walk(valueNode => { if (valueNode.type !== "function") { return; diff --git a/lib/rules/function-url-no-scheme-relative/index.js b/lib/rules/function-url-no-scheme-relative/index.js index e5548a9777..5d4b8c3f91 100644 --- a/lib/rules/function-url-no-scheme-relative/index.js +++ b/lib/rules/function-url-no-scheme-relative/index.js @@ -16,6 +16,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/function-url-quotes/index.js b/lib/rules/function-url-quotes/index.js index 32150b4aaa..48484209bd 100644 --- a/lib/rules/function-url-quotes/index.js +++ b/lib/rules/function-url-quotes/index.js @@ -33,6 +33,7 @@ const rule = function(expectation, options) { optional: true } ); + if (!validOptions) { return; } @@ -52,6 +53,7 @@ const rule = function(expectation, options) { function checkAtRuleParams(atRule) { const atRuleParamsLowerCase = atRule.params.toLowerCase(); + functionArgumentsSearch(atRuleParamsLowerCase, "url", (args, index) => { checkArgs(args, atRule, index + atRuleParamIndex(atRule), "url"); }); @@ -80,15 +82,18 @@ const rule = function(expectation, options) { let shouldHasQuotes = expectation === "always"; const leftTrimmedArgs = args.trimLeft(); + if (!isStandardSyntaxUrl(leftTrimmedArgs)) { return; } + const complaintIndex = index + args.length - leftTrimmedArgs.length; const hasQuotes = leftTrimmedArgs[0] === "'" || leftTrimmedArgs[0] === '"'; const trimmedArg = args.trim(); const isEmptyArgument = _.includes(["", "''", '""'], trimmedArg); + if (optionsMatches(options, "except", "empty") && isEmptyArgument) { shouldHasQuotes = !shouldHasQuotes; } @@ -97,11 +102,13 @@ const rule = function(expectation, options) { if (hasQuotes) { return; } + complain(messages.expected(functionName), node, complaintIndex); } else { if (!hasQuotes) { return; } + complain(messages.rejected(functionName), node, complaintIndex); } } diff --git a/lib/rules/function-url-scheme-blacklist/index.js b/lib/rules/function-url-scheme-blacklist/index.js index 12a5357951..10fe5b45be 100644 --- a/lib/rules/function-url-scheme-blacklist/index.js +++ b/lib/rules/function-url-scheme-blacklist/index.js @@ -21,6 +21,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -31,9 +32,11 @@ const rule = function(blacklist) { "url", (args, index) => { const unspacedUrlString = _.trim(args, " "); + if (!isStandardSyntaxUrl(unspacedUrlString)) { return; } + const urlString = _.trim(unspacedUrlString, "'\""); const scheme = getSchemeFromUrl(urlString); diff --git a/lib/rules/function-url-scheme-whitelist/index.js b/lib/rules/function-url-scheme-whitelist/index.js index 0b9b64cc5b..871bf57f72 100644 --- a/lib/rules/function-url-scheme-whitelist/index.js +++ b/lib/rules/function-url-scheme-whitelist/index.js @@ -21,6 +21,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -31,9 +32,11 @@ const rule = function(whitelist) { "url", (args, index) => { const unspacedUrlString = _.trim(args, " "); + if (!isStandardSyntaxUrl(unspacedUrlString)) { return; } + const urlString = _.trim(unspacedUrlString, "'\""); const scheme = getSchemeFromUrl(urlString); diff --git a/lib/rules/function-whitelist/index.js b/lib/rules/function-whitelist/index.js index 565fa4f182..f7ecf0ae6d 100644 --- a/lib/rules/function-whitelist/index.js +++ b/lib/rules/function-whitelist/index.js @@ -18,14 +18,17 @@ const messages = ruleMessages(ruleName, { const rule = function(whitelistInput) { const whitelist = [].concat(whitelistInput); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } + root.walkDecls(decl => { const value = decl.value; @@ -33,9 +36,11 @@ const rule = function(whitelistInput) { if (node.type !== "function") { return; } + if (!isStandardSyntaxFunction(node)) { return; } + if ( matchesStringOrRegExp( postcss.vendor.unprefixed(node.value), @@ -44,6 +49,7 @@ const rule = function(whitelistInput) { ) { return; } + report({ message: messages.rejected(node.value), node: decl, diff --git a/lib/rules/function-whitespace-after/index.js b/lib/rules/function-whitespace-after/index.js index 48f809d68b..79a3ebaf82 100644 --- a/lib/rules/function-whitespace-after/index.js +++ b/lib/rules/function-whitespace-after/index.js @@ -31,6 +31,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -50,25 +51,32 @@ const rule = function(expectation, options, context) { function checkClosingParen(source, index, node, getIndex, fix) { const nextChar = source[index]; + if (expectation === "always") { // Allow for the next character to be a single empty space, // another closing parenthesis, a comma, or the end of the value if (nextChar === " ") { return; } + if (nextChar === "\n") { return; } + if (source.substr(index, 2) === "\r\n") { return; } + if (ACCEPTABLE_AFTER_CLOSING_PAREN.has(nextChar)) { return; } + if (fix) { fix(index); + return; } + report({ message: messages.expected, node, @@ -80,8 +88,10 @@ const rule = function(expectation, options, context) { if (isWhitespace(nextChar)) { if (fix) { fix(index); + return; } + report({ message: messages.rejected, node, @@ -97,6 +107,7 @@ const rule = function(expectation, options, context) { let fixed = ""; let lastIndex = 0; let applyFix; + if (expectation === "always") { applyFix = index => { fixed += value.slice(lastIndex, index) + " "; @@ -105,6 +116,7 @@ const rule = function(expectation, options, context) { } else if (expectation === "never") { applyFix = index => { let whitespaceEndIndex = index + 1; + while ( whitespaceEndIndex < value.length && isWhitespace(value[whitespaceEndIndex]) @@ -115,6 +127,7 @@ const rule = function(expectation, options, context) { lastIndex = whitespaceEndIndex; }; } + return { applyFix, get hasFixed() { diff --git a/lib/rules/functionCommaSpaceChecker.js b/lib/rules/functionCommaSpaceChecker.js index 80b2300c95..f2cefd44cb 100644 --- a/lib/rules/functionCommaSpaceChecker.js +++ b/lib/rules/functionCommaSpaceChecker.js @@ -12,6 +12,7 @@ module.exports = function(opts) { let hasFixed; const parsedValue = valueParser(declValue); + parsedValue.walk(valueNode => { if (valueNode.type !== "function") { return; @@ -41,6 +42,7 @@ module.exports = function(opts) { /( *\/(\*.*\*\/(?!\S)|\/.*)|(\/(\*.*\*\/|\/.*)))/, "" ); + return result; })(); @@ -62,15 +64,19 @@ module.exports = function(opts) { /( *\/(\*.*\*\/(?!\S)|\/.*)|(\/(\*.*\*\/|\/.*)))/, "" ); + return commaBefore.length; } const commaDataList = []; + valueNode.nodes.forEach((node, nodeIndex) => { if (node.type !== "div" || node.value !== ",") { return; } + const checkIndex = getCommaCheckIndex(node, nodeIndex); + commaDataList.push({ commaNode: node, checkIndex, @@ -87,10 +93,13 @@ module.exports = function(opts) { declarationValueIndex(decl) + commaNode.sourceIndex + commaNode.before.length; + if (opts.fix && opts.fix(commaNode, nodeIndex, valueNode.nodes)) { hasFixed = true; + return; } + report({ index, message, diff --git a/lib/rules/indentation/index.js b/lib/rules/indentation/index.js index a3b2ca8645..f8f9740cc2 100644 --- a/lib/rules/indentation/index.js +++ b/lib/rules/indentation/index.js @@ -46,6 +46,7 @@ const rule = function(space, options, context) { optional: true } ); + if (!validOptions) { return; } @@ -68,6 +69,7 @@ const rule = function(space, options, context) { // there is no "indentation" to check.) const isFirstChild = parent.type === "root" && parent.first === node; const lastIndexOfNewline = before.lastIndexOf("\n"); + // Inspect whitespace in the `before` string that is // *after* the *last* newline character, // because anything besides that is not indentation for this node: @@ -86,6 +88,7 @@ const rule = function(space, options, context) { expectedOpeningBraceIndentation ); } + node.raws.before = fixIndentation( node.raws.before, expectedOpeningBraceIndentation @@ -111,6 +114,7 @@ const rule = function(space, options, context) { indentChar, closingBraceLevel ); + if ( hasBlock(node) && after && @@ -185,6 +189,7 @@ const rule = function(space, options, context) { if (decl.value.indexOf("\n") === -1) { return; } + if (optionsMatches(options, "ignore", "value")) { return; } @@ -267,6 +272,7 @@ const rule = function(space, options, context) { // Account for windows line endings let newlineIndex = match.startIndex; + if (source[match.startIndex - 1] === "\r") { newlineIndex--; } @@ -274,6 +280,7 @@ const rule = function(space, options, context) { const followsOpeningParenthesis = /\([ \t]*$/.test( source.slice(0, newlineIndex) ); + if (followsOpeningParenthesis) { parentheticalDepth += 1; } @@ -281,6 +288,7 @@ const rule = function(space, options, context) { const followsOpeningBrace = /\{[ \t]*$/.test( source.slice(0, newlineIndex) ); + if (followsOpeningBrace) { parentheticalDepth += 1; } @@ -288,6 +296,7 @@ const rule = function(space, options, context) { const startingClosingBrace = /^[ \t]*}/.test( source.slice(match.startIndex + 1) ); + if (startingClosingBrace) { parentheticalDepth -= 1; } @@ -305,6 +314,7 @@ const rule = function(space, options, context) { if (!precedesClosingParenthesis || options.indentClosingBrace) { expectedIndentLevel += 1; } + break; case "once-at-root-twice-in-block": if (node.parent === node.root()) { @@ -314,11 +324,14 @@ const rule = function(space, options, context) { ) { expectedIndentLevel -= 1; } + break; } + if (!precedesClosingParenthesis || options.indentClosingBrace) { expectedIndentLevel += 1; } + break; default: if (precedesClosingParenthesis && !options.indentClosingBrace) { @@ -443,26 +456,33 @@ const rule = function(space, options, context) { function getRootBaseIndentLevel(root, baseIndentLevel, space) { const document = root.document; + if (!document) { return 0; } + let indentLevel = root.source.baseIndentLevel; + if (!Number.isSafeInteger(indentLevel)) { indentLevel = inferRootIndentLevel(root, baseIndentLevel, () => inferDocIndentSize(document, space) ); root.source.baseIndentLevel = indentLevel; } + return indentLevel; } function inferDocIndentSize(document, space) { let indentSize = document.source.indentSize; + if (Number.isSafeInteger(indentSize)) { return indentSize; } + const source = document.source.input.css; const indents = source.match(/^ *(?=\S)/gm); + if (indents) { const scores = {}; let lastIndentSize = 0; @@ -472,6 +492,7 @@ function inferDocIndentSize(document, space) { lastIndentSize = Math.abs(leadingSpacesLength - lastLeadingSpacesLength) || lastIndentSize; + if (lastIndentSize > 1) { if (scores[lastIndentSize]) { scores[lastIndentSize]++; @@ -482,6 +503,7 @@ function inferDocIndentSize(document, space) { } else { lastIndentSize = 0; } + lastLeadingSpacesLength = leadingSpacesLength; }; @@ -493,6 +515,7 @@ function inferDocIndentSize(document, space) { for (const indentSizeDate in scores) { const score = scores[indentSizeDate]; + if (score > bestScore) { bestScore = score; indentSize = indentSizeDate; @@ -502,20 +525,25 @@ function inferDocIndentSize(document, space) { indentSize = +indentSize || (indents && indents[0].length) || +space || 2; document.source.indentSize = indentSize; + return indentSize; } function inferRootIndentLevel(root, baseIndentLevel, indentSize) { function getIndentLevel(indent) { let tabCount = indent.match(/\t/g); + tabCount = tabCount ? tabCount.length : 0; let spaceCount = indent.match(/ /g); + spaceCount = spaceCount ? Math.round(spaceCount.length / indentSize()) : 0; + return tabCount + spaceCount; } if (!Number.isSafeInteger(baseIndentLevel)) { let source = root.source.input.css; + source = source.replace( /^[^\r\n]+/, firstLine => @@ -525,6 +553,7 @@ function inferRootIndentLevel(root, baseIndentLevel, indentSize) { ); const indents = source.match(/^[ \t]*(?=\S)/gm); + if (indents) { return Math.min.apply(Math, indents.map(getIndentLevel)); } else { @@ -539,11 +568,15 @@ function inferRootIndentLevel(root, baseIndentLevel, indentSize) { } const after = root.raws.after; + if (after) { let afterEnd; + if (after.slice(-1) === "\n") { const document = root.document; + afterEnd = document.nodes[root.nodes.indexOf(root) + 1]; + if (afterEnd) { afterEnd = afterEnd.raws.beforeStart; } else { @@ -552,6 +585,7 @@ function inferRootIndentLevel(root, baseIndentLevel, indentSize) { } else { afterEnd = after; } + indents.push(afterEnd.match(/^[ \t]*/)[0]); } diff --git a/lib/rules/keyframe-declaration-no-important/index.js b/lib/rules/keyframe-declaration-no-important/index.js index adf3f7c46a..36f2409011 100644 --- a/lib/rules/keyframe-declaration-no-important/index.js +++ b/lib/rules/keyframe-declaration-no-important/index.js @@ -13,6 +13,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -22,6 +23,7 @@ const rule = function(actual) { if (!decl.important) { return; } + report({ message: messages.rejected, node: decl, diff --git a/lib/rules/keyframes-name-pattern/index.js b/lib/rules/keyframes-name-pattern/index.js index 5fbb2cfc8c..6d13e7f816 100644 --- a/lib/rules/keyframes-name-pattern/index.js +++ b/lib/rules/keyframes-name-pattern/index.js @@ -25,8 +25,10 @@ const rule = function(pattern) { } const regex = _.isString(pattern) ? new RegExp(pattern) : pattern; + root.walkAtRules(/keyframes/i, keyframesNode => { const value = keyframesNode.params; + if (value.match(regex)) { return; } diff --git a/lib/rules/length-zero-no-unit/index.js b/lib/rules/length-zero-no-unit/index.js index 70cbc70ca0..041e6c11c7 100644 --- a/lib/rules/length-zero-no-unit/index.js +++ b/lib/rules/length-zero-no-unit/index.js @@ -22,6 +22,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual, secondary, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -34,6 +35,7 @@ const rule = function(actual, secondary, context) { const source = hasBlock(atRule) ? beforeBlockString(atRule, { noRawBefore: true }) : atRule.toString(); + check(source, atRule); }); @@ -126,6 +128,7 @@ const rule = function(actual, secondary, context) { startIndex: valueWithZeroStart, length: valueWithZeroEnd - valueWithZeroStart }); + return; } @@ -170,6 +173,7 @@ const rule = function(actual, secondary, context) { function replaceZero(input, startIndex, length) { const stringStart = input.slice(0, startIndex); const stringEnd = input.slice(startIndex + length); + return stringStart + "0" + stringEnd; } diff --git a/lib/rules/linebreaks/index.js b/lib/rules/linebreaks/index.js index bb3656e6b8..cc64111167 100644 --- a/lib/rules/linebreaks/index.js +++ b/lib/rules/linebreaks/index.js @@ -37,6 +37,7 @@ const rule = function(actual, secondary, context) { root.walk(node => { propertiesToUpdate.forEach(property => { const fixedData = fixData(_.get(node, property), shouldHaveCR); + _.set(node, property, fixedData); }); }); @@ -47,6 +48,7 @@ const rule = function(actual, secondary, context) { for (let i = 0; i < lines.length; i++) { let line = lines[i]; + if (i < lines.length - 1 && line.indexOf("\r") === -1) { line += "\n"; } @@ -54,6 +56,7 @@ const rule = function(actual, secondary, context) { if (hasError(line, shouldHaveCR)) { const lineNum = i + 1; const colNum = line.length; + reportNewlineError(shouldHaveCR, lineNum, colNum, actual, result); } } @@ -69,12 +72,14 @@ const rule = function(actual, secondary, context) { function fixData(data, shouldHaveCR) { if (data) { let result = data.replace(/\r/g, ""); + if (shouldHaveCR) { result = result.replace(/\n/g, "\r\n"); } return result; } + return data; } diff --git a/lib/rules/max-empty-lines/index.js b/lib/rules/max-empty-lines/index.js index 7e8c8a94e3..cc6b9ec929 100644 --- a/lib/rules/max-empty-lines/index.js +++ b/lib/rules/max-empty-lines/index.js @@ -35,6 +35,7 @@ const rule = function(max, options) { optional: true } ); + if (!validOptions) { return; } @@ -42,12 +43,14 @@ const rule = function(max, options) { const ignoreComments = optionsMatches(options, "ignore", "comments"); const document = root.constructor.name === "Document" ? root : undefined; + eachRoot(root, checkRoot); function checkRoot(root) { emptyLines = 0; lastIndex = -1; const rootString = root.toString(); + styleSearch( { source: rootString, @@ -114,14 +117,18 @@ function isEofNode(document, root) { if (!document) { return true; } + // In the `postcss-html` and `postcss-styled` syntax, checks that there is text after the given node. let after; + if (root === document.last) { after = _.get(document, "raws.afterEnd"); } else { const rootIndex = document.index(root); + after = _.get(document.nodes[rootIndex + 1], "raws.beforeStart"); } + return !(after + "").trim(); } diff --git a/lib/rules/max-line-length/index.js b/lib/rules/max-line-length/index.js index 75b1ce8f0b..5a4044765b 100644 --- a/lib/rules/max-line-length/index.js +++ b/lib/rules/max-line-length/index.js @@ -36,6 +36,7 @@ const rule = function(maxLength, options) { optional: true } ); + if (!validOptions) { return; } @@ -47,6 +48,7 @@ const rule = function(maxLength, options) { function checkRoot(root) { const rootString = root.source.input.css; + // Check first line checkNewline(rootString, { endIndex: 0 }, root); // Check subsequent lines @@ -68,6 +70,7 @@ const rule = function(maxLength, options) { function checkNewline(rootString, match, root) { let nextNewlineIndex = rootString.indexOf("\n", match.endIndex); + if (rootString[nextNewlineIndex - 1] === "\r") { nextNewlineIndex -= 1; } @@ -121,6 +124,7 @@ const rule = function(maxLength, options) { .slice(match.endIndex) .trim() .slice(0, 2); + if (nextTwoChars === "/*" || nextTwoChars === "//") { return; } @@ -138,14 +142,17 @@ const rule = function(maxLength, options) { .slice(match.endIndex) .trim() .slice(0, 2); + if (nextTwoChars !== "/*" && nextTwoChars !== "//") { return; } + return complain(complaintIndex, root); } // If there are no spaces besides initial (indent) spaces, ignore it const lineString = rootString.slice(match.endIndex, nextNewlineIndex); + if (lineString.replace(/^\s+/, "").indexOf(" ") === -1) { return; } diff --git a/lib/rules/max-nesting-depth/index.js b/lib/rules/max-nesting-depth/index.js index 39ecdb02eb..1e4fa937d7 100644 --- a/lib/rules/max-nesting-depth/index.js +++ b/lib/rules/max-nesting-depth/index.js @@ -44,13 +44,17 @@ const rule = function(max, options) { if (isIgnoreAtRule(statement)) { return; } + if (!hasBlock(statement)) { return; } + if (statement.selector && !isStandardSyntaxRule(statement)) { return; } + const depth = nestingDepth(statement); + if (depth > max) { report({ ruleName, diff --git a/lib/rules/media-feature-colon-space-after/index.js b/lib/rules/media-feature-colon-space-after/index.js index d8fb8353ec..21b5d92148 100644 --- a/lib/rules/media-feature-colon-space-after/index.js +++ b/lib/rules/media-feature-colon-space-after/index.js @@ -15,16 +15,19 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } let fixData; + mediaFeatureColonSpaceChecker({ root, result, @@ -33,10 +36,13 @@ const rule = function(expectation, options, context) { fix: context.fix ? (atRule, index) => { const paramColonIndex = index - atRuleParamIndex(atRule); + fixData = fixData || new Map(); const colonIndices = fixData.get(atRule) || []; + colonIndices.push(paramColonIndex); fixData.set(atRule, colonIndices); + return true; } : null @@ -47,15 +53,18 @@ const rule = function(expectation, options, context) { let params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + colonIndices.sort((a, b) => b - a).forEach(index => { const beforeColon = params.slice(0, index + 1); const afterColon = params.slice(index + 1); + if (expectation === "always") { params = beforeColon + afterColon.replace(/^\s*/, " "); } else if (expectation === "never") { params = beforeColon + afterColon.replace(/^\s*/, ""); } }); + if (atRule.raws.params) { atRule.raws.params.raw = params; } else { diff --git a/lib/rules/media-feature-colon-space-before/index.js b/lib/rules/media-feature-colon-space-before/index.js index 96003d3664..4a6e8fca27 100644 --- a/lib/rules/media-feature-colon-space-before/index.js +++ b/lib/rules/media-feature-colon-space-before/index.js @@ -15,16 +15,19 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } let fixData; + mediaFeatureColonSpaceChecker({ root, result, @@ -33,10 +36,13 @@ const rule = function(expectation, options, context) { fix: context.fix ? (atRule, index) => { const paramColonIndex = index - atRuleParamIndex(atRule); + fixData = fixData || new Map(); const colonIndices = fixData.get(atRule) || []; + colonIndices.push(paramColonIndex); fixData.set(atRule, colonIndices); + return true; } : null @@ -47,15 +53,18 @@ const rule = function(expectation, options, context) { let params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + colonIndices.sort((a, b) => b - a).forEach(index => { const beforeColon = params.slice(0, index); const afterColon = params.slice(index); + if (expectation === "always") { params = beforeColon.replace(/\s*$/, " ") + afterColon; } else if (expectation === "never") { params = beforeColon.replace(/\s*$/, "") + afterColon; } }); + if (atRule.raws.params) { atRule.raws.params.raw = params; } else { diff --git a/lib/rules/media-feature-name-blacklist/index.js b/lib/rules/media-feature-name-blacklist/index.js index 790828a0f0..3042d38b3a 100644 --- a/lib/rules/media-feature-name-blacklist/index.js +++ b/lib/rules/media-feature-name-blacklist/index.js @@ -23,6 +23,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/media-feature-name-case/index.js b/lib/rules/media-feature-name-case/index.js index 7ac3fd359f..cbc1078b01 100644 --- a/lib/rules/media-feature-name-case/index.js +++ b/lib/rules/media-feature-name-case/index.js @@ -22,6 +22,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } @@ -63,6 +64,7 @@ const rule = function(expectation, options, context) { expectedFeatureName + atRule.params.slice(sourceIndex + expectedFeatureName.length); } + return; } diff --git a/lib/rules/media-feature-name-no-vendor-prefix/index.js b/lib/rules/media-feature-name-no-vendor-prefix/index.js index 670e76d40a..71a2a2a157 100644 --- a/lib/rules/media-feature-name-no-vendor-prefix/index.js +++ b/lib/rules/media-feature-name-no-vendor-prefix/index.js @@ -14,6 +14,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -24,6 +25,7 @@ const rule = function(actual) { if (!isAutoprefixable.mediaFeatureName(params)) { return; } + const matches = atRule.toString().match(/[a-z-]+device-pixel-ratio/gi); if (!matches) { diff --git a/lib/rules/media-feature-name-whitelist/index.js b/lib/rules/media-feature-name-whitelist/index.js index 808c37d86a..908d282705 100644 --- a/lib/rules/media-feature-name-whitelist/index.js +++ b/lib/rules/media-feature-name-whitelist/index.js @@ -23,6 +23,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/media-feature-parentheses-space-inside/index.js b/lib/rules/media-feature-parentheses-space-inside/index.js index 725ed71ce2..0b84881e20 100644 --- a/lib/rules/media-feature-parentheses-space-inside/index.js +++ b/lib/rules/media-feature-parentheses-space-inside/index.js @@ -22,6 +22,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -36,32 +37,38 @@ const rule = function(expectation, options, context) { const parsedParams = valueParser(params).walk(node => { if (node.type === "function") { const len = valueParser.stringify(node).length; + if (expectation === "never") { if (node.before === " ") { if (context.fix) node.before = ""; + violations.push({ message: messages.rejectedOpening, index: node.sourceIndex + 1 + indexBoost }); } + if (node.after === " ") { if (context.fix) node.after = ""; + violations.push({ message: messages.rejectedClosing, index: node.sourceIndex - 2 + len + indexBoost }); } - } - else if (expectation === "always") { + } else if (expectation === "always") { if (node.before === "") { if (context.fix) node.before = " "; + violations.push({ message: messages.expectedOpening, index: node.sourceIndex + 1 + indexBoost }); } + if (node.after === "") { if (context.fix) node.after = " "; + violations.push({ message: messages.expectedClosing, index: node.sourceIndex - 2 + len + indexBoost @@ -74,6 +81,7 @@ const rule = function(expectation, options, context) { if (violations.length) { if (context.fix) { atRule.params = parsedParams.toString(); + return; } diff --git a/lib/rules/media-feature-range-operator-space-after/index.js b/lib/rules/media-feature-range-operator-space-after/index.js index 6c107cb2b0..ec9bb6c956 100644 --- a/lib/rules/media-feature-range-operator-space-after/index.js +++ b/lib/rules/media-feature-range-operator-space-after/index.js @@ -16,11 +16,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -28,6 +30,7 @@ const rule = function(expectation, options, context) { root.walkAtRules(/^media$/i, atRule => { const fixOperatorIndices = []; const fix = context.fix ? index => fixOperatorIndices.push(index) : null; + findMediaOperator(atRule, (match, params, node) => { checkAfterOperator(match, params, node, fix); }); @@ -36,15 +39,18 @@ const rule = function(expectation, options, context) { let params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + fixOperatorIndices.sort((a, b) => b - a).forEach(index => { const beforeOperator = params.slice(0, index + 1); const afterOperator = params.slice(index + 1); + if (expectation === "always") { params = beforeOperator + afterOperator.replace(/^\s*/, " "); } else if (expectation === "never") { params = beforeOperator + afterOperator.replace(/^\s*/, ""); } }); + if (atRule.raws.params) { atRule.raws.params.raw = params; } else { @@ -62,8 +68,10 @@ const rule = function(expectation, options, context) { err: m => { if (fix) { fix(endIndex); + return; } + report({ message: m, node, diff --git a/lib/rules/media-feature-range-operator-space-before/index.js b/lib/rules/media-feature-range-operator-space-before/index.js index 366a061e56..a7d6c0fb6a 100644 --- a/lib/rules/media-feature-range-operator-space-before/index.js +++ b/lib/rules/media-feature-range-operator-space-before/index.js @@ -16,11 +16,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -28,6 +30,7 @@ const rule = function(expectation, options, context) { root.walkAtRules(/^media$/i, atRule => { const fixOperatorIndices = []; const fix = context.fix ? index => fixOperatorIndices.push(index) : null; + findMediaOperator(atRule, (match, params, node) => { checkBeforeOperator(match, params, node, fix); }); @@ -36,15 +39,18 @@ const rule = function(expectation, options, context) { let params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + fixOperatorIndices.sort((a, b) => b - a).forEach(index => { const beforeOperator = params.slice(0, index); const afterOperator = params.slice(index); + if (expectation === "always") { params = beforeOperator.replace(/\s*$/, " ") + afterOperator; } else if (expectation === "never") { params = beforeOperator.replace(/\s*$/, "") + afterOperator; } }); + if (atRule.raws.params) { atRule.raws.params.raw = params; } else { @@ -62,8 +68,10 @@ const rule = function(expectation, options, context) { err: m => { if (fix) { fix(match.startIndex); + return; } + report({ message: m, node, diff --git a/lib/rules/media-query-list-comma-newline-after/index.js b/lib/rules/media-query-list-comma-newline-after/index.js index 5b330208e6..edc5734616 100644 --- a/lib/rules/media-query-list-comma-newline-after/index.js +++ b/lib/rules/media-query-list-comma-newline-after/index.js @@ -24,6 +24,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } @@ -31,6 +32,7 @@ const rule = function(expectation, options, context) { // Only check for the newline after the comma, while allowing // arbitrary indentation after the newline let fixData; + mediaQueryListCommaWhitespaceChecker({ root, result, @@ -40,10 +42,13 @@ const rule = function(expectation, options, context) { fix: context.fix ? (atRule, index) => { const paramCommaIndex = index - atRuleParamIndex(atRule); + fixData = fixData || new Map(); const commaIndices = fixData.get(atRule) || []; + commaIndices.push(paramCommaIndex); fixData.set(atRule, commaIndices); + return true; } : null @@ -54,9 +59,11 @@ const rule = function(expectation, options, context) { let params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + commaIndices.sort((a, b) => b - a).forEach(index => { const beforeComma = params.slice(0, index + 1); const afterComma = params.slice(index + 1); + if (expectation.indexOf("always") === 0) { if (/^\s*\r?\n/.test(afterComma)) { params = beforeComma + afterComma.replace(/^[^\S\r\n]*/, ""); @@ -67,6 +74,7 @@ const rule = function(expectation, options, context) { params = beforeComma + afterComma.replace(/^\s*/, ""); } }); + if (atRule.raws.params) { atRule.raws.params.raw = params; } else { diff --git a/lib/rules/media-query-list-comma-newline-before/index.js b/lib/rules/media-query-list-comma-newline-before/index.js index 3a22727e3c..0c6d227cfc 100644 --- a/lib/rules/media-query-list-comma-newline-before/index.js +++ b/lib/rules/media-query-list-comma-newline-before/index.js @@ -17,14 +17,17 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } + mediaQueryListCommaWhitespaceChecker({ root, result, diff --git a/lib/rules/media-query-list-comma-space-after/index.js b/lib/rules/media-query-list-comma-space-after/index.js index abfd6fca10..9818560308 100644 --- a/lib/rules/media-query-list-comma-space-after/index.js +++ b/lib/rules/media-query-list-comma-space-after/index.js @@ -19,16 +19,19 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } let fixData; + mediaQueryListCommaWhitespaceChecker({ root, result, @@ -37,28 +40,35 @@ const rule = function(expectation, options, context) { fix: context.fix ? (atRule, index) => { const paramCommaIndex = index - atRuleParamIndex(atRule); + fixData = fixData || new Map(); const commaIndices = fixData.get(atRule) || []; + commaIndices.push(paramCommaIndex); fixData.set(atRule, commaIndices); + return true; } : null }); + if (fixData) { fixData.forEach((commaIndices, atRule) => { let params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + commaIndices.sort((a, b) => b - a).forEach(index => { const beforeComma = params.slice(0, index + 1); const afterComma = params.slice(index + 1); + if (expectation.indexOf("always") === 0) { params = beforeComma + afterComma.replace(/^\s*/, " "); } else if (expectation.indexOf("never") === 0) { params = beforeComma + afterComma.replace(/^\s*/, ""); } }); + if (atRule.raws.params) { atRule.raws.params.raw = params; } else { diff --git a/lib/rules/media-query-list-comma-space-before/index.js b/lib/rules/media-query-list-comma-space-before/index.js index 3f4bc79d71..07a5d0775a 100644 --- a/lib/rules/media-query-list-comma-space-before/index.js +++ b/lib/rules/media-query-list-comma-space-before/index.js @@ -19,16 +19,19 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } let fixData; + mediaQueryListCommaWhitespaceChecker({ root, result, @@ -37,10 +40,13 @@ const rule = function(expectation, options, context) { fix: context.fix ? (atRule, index) => { const paramCommaIndex = index - atRuleParamIndex(atRule); + fixData = fixData || new Map(); const commaIndices = fixData.get(atRule) || []; + commaIndices.push(paramCommaIndex); fixData.set(atRule, commaIndices); + return true; } : null @@ -51,15 +57,18 @@ const rule = function(expectation, options, context) { let params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + commaIndices.sort((a, b) => b - a).forEach(index => { const beforeComma = params.slice(0, index); const afterComma = params.slice(index); + if (expectation.indexOf("always") === 0) { params = beforeComma.replace(/\s*$/, " ") + afterComma; } else if (expectation.indexOf("never") === 0) { params = beforeComma.replace(/\s*$/, "") + afterComma; } }); + if (atRule.raws.params) { atRule.raws.params.raw = params; } else { diff --git a/lib/rules/mediaFeatureColonSpaceChecker.js b/lib/rules/mediaFeatureColonSpaceChecker.js index dc2d3fe20d..6489d7b6c3 100644 --- a/lib/rules/mediaFeatureColonSpaceChecker.js +++ b/lib/rules/mediaFeatureColonSpaceChecker.js @@ -19,9 +19,11 @@ module.exports = function(opts) { index, err: m => { const colonIndex = index + atRuleParamIndex(node); + if (opts.fix && opts.fix(node, colonIndex)) { return; } + report({ message: m, node, diff --git a/lib/rules/mediaQueryListCommaWhitespaceChecker.js b/lib/rules/mediaQueryListCommaWhitespaceChecker.js index 2a40341a1e..d45fbbab3e 100644 --- a/lib/rules/mediaQueryListCommaWhitespaceChecker.js +++ b/lib/rules/mediaQueryListCommaWhitespaceChecker.js @@ -7,11 +7,14 @@ const styleSearch = require("style-search"); module.exports = function(opts) { opts.root.walkAtRules(/^media$/i, atRule => { const params = atRule.raws.params ? atRule.raws.params.raw : atRule.params; + styleSearch({ source: params, target: "," }, match => { let index = match.startIndex; + if (opts.allowTrailingComments) { // if there is a comment on the same line at after the comma, check the space after the comment. let execResult; + while ( (execResult = /^[^\S\r\n]*\/\*([\s\S]*?)\*\//.exec( params.slice(index + 1) @@ -19,6 +22,7 @@ module.exports = function(opts) { ) { index += execResult[0].length; } + if ( (execResult = /^([^\S\r\n]*\/\/([\s\S]*?))\r?\n/.exec( params.slice(index + 1) @@ -27,6 +31,7 @@ module.exports = function(opts) { index += execResult[1].length; } } + checkComma(params, index, atRule); }); }); @@ -37,9 +42,11 @@ module.exports = function(opts) { index, err: m => { const commaIndex = index + atRuleParamIndex(node); + if (opts.fix && opts.fix(node, commaIndex)) { return; } + report({ message: m, node, diff --git a/lib/rules/no-descending-specificity/index.js b/lib/rules/no-descending-specificity/index.js index 87a7844840..945e9b8583 100644 --- a/lib/rules/no-descending-specificity/index.js +++ b/lib/rules/no-descending-specificity/index.js @@ -23,6 +23,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -47,6 +48,7 @@ const rule = function(actual) { rule.selectors.forEach(selector => { const trimSelector = selector.trim(); + // Ignore `.selector, { }` if (trimSelector === "") { return; @@ -54,12 +56,14 @@ const rule = function(actual) { // The edge-case of duplicate selectors will act acceptably const index = rule.selector.indexOf(trimSelector); + // Resolve any nested selectors before checking resolvedNestedSelector(selector, rule).forEach(resolvedSelector => { parseSelector(resolvedSelector, result, rule, s => { if (!isStandardSyntaxSelector(resolvedSelector)) { return; } + checkSelector(s, rule, index, comparisonContext); }); }); @@ -77,6 +81,7 @@ const rule = function(actual) { if (!comparisonContext.has(referenceSelectorNode)) { comparisonContext.set(referenceSelectorNode, [entry]); + return; } diff --git a/lib/rules/no-duplicate-at-import-rules/index.js b/lib/rules/no-duplicate-at-import-rules/index.js index 8b0f5f54bf..9a95761335 100644 --- a/lib/rules/no-duplicate-at-import-rules/index.js +++ b/lib/rules/no-duplicate-at-import-rules/index.js @@ -15,6 +15,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -23,6 +24,7 @@ const rule = function(actual) { root.walkAtRules(/^import$/i, atRule => { const params = valueParser(atRule.params).nodes; + if (!params.length) { return; } @@ -48,10 +50,12 @@ const rule = function(actual) { result, ruleName }); + return; } if (!imports[uri]) imports[uri] = []; + imports[uri] = imports[uri].concat(media); }); }; diff --git a/lib/rules/no-duplicate-selectors/index.js b/lib/rules/no-duplicate-selectors/index.js index 52ced1a85a..5846a4789f 100644 --- a/lib/rules/no-duplicate-selectors/index.js +++ b/lib/rules/no-duplicate-selectors/index.js @@ -20,6 +20,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -53,6 +54,7 @@ const rule = function(actual) { .slice() .sort() .join(","); + if (contextSelectorSet.has(sortedSelectorList)) { // If the selector isn't nested we can use its raw value; otherwise, // we have to approximate something for the message -- which is close enough diff --git a/lib/rules/no-empty-first-line/index.js b/lib/rules/no-empty-first-line/index.js index d3e64f8dfa..3e9eed98aa 100644 --- a/lib/rules/no-empty-first-line/index.js +++ b/lib/rules/no-empty-first-line/index.js @@ -14,6 +14,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual, _, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -22,7 +23,9 @@ const rule = function(actual, _, context) { if (root.source.inline || root.source.lang === "object-literal") { return; } + const rootString = root.source.input.css; + if (rootString.trim() === "") { return; } @@ -32,6 +35,7 @@ const rule = function(actual, _, context) { if (noEmptyFirstLineTest.test(rootString)) { if (context.fix) { root.nodes[0].raws.before = ""; + return; } diff --git a/lib/rules/no-empty-source/index.js b/lib/rules/no-empty-source/index.js index 73367c9e6f..1c2145e13e 100644 --- a/lib/rules/no-empty-source/index.js +++ b/lib/rules/no-empty-source/index.js @@ -14,6 +14,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -22,6 +23,7 @@ const rule = function(actual) { if (root.source.input.css.trim()) { return; } + report({ message: messages.rejected, node: root, diff --git a/lib/rules/no-eol-whitespace/index.js b/lib/rules/no-eol-whitespace/index.js index 0d899242f8..a05d6ae774 100644 --- a/lib/rules/no-eol-whitespace/index.js +++ b/lib/rules/no-eol-whitespace/index.js @@ -32,6 +32,7 @@ const rule = function(on, options, context) { } } ); + if (!validOptions) { return; } @@ -76,6 +77,7 @@ const rule = function(on, options, context) { }, match => { const eolWhitespaceIndex = match.startIndex - 1; + // If the character before newline is not whitespace, ignore if (!whitespacesToReject.has(string[eolWhitespaceIndex])) { return; @@ -88,6 +90,7 @@ const rule = function(on, options, context) { "\n", eolWhitespaceIndex ); + if (beforeNewlineIndex >= 0 || isRootFirst) { const line = string.substring( beforeNewlineIndex, @@ -99,6 +102,7 @@ const rule = function(on, options, context) { } } } + callback(eolWhitespaceIndex); } ); @@ -106,6 +110,7 @@ const rule = function(on, options, context) { function fix(root) { let isRootFirst = true; + root.walk(node => { fixText( node.raws.before, @@ -120,6 +125,7 @@ const rule = function(on, options, context) { fixText(node.raws.afterName, fixed => { node.raws.afterName = fixed; }); + if (node.raws.params) { fixText(node.raws.params.raw, fixed => { node.raws.params.raw = fixed; @@ -184,17 +190,21 @@ const rule = function(on, options, context) { if (!value) { return; } + let fixed = ""; let lastIndex = 0; + eachEolWhitespace( value, index => { const newlineIndex = index + 1; + fixed += value.slice(lastIndex, newlineIndex).replace(/[ \t]+$/, ""); lastIndex = newlineIndex; }, isRootFirst ); + if (lastIndex) { fixed += value.slice(lastIndex); fix(fixed); diff --git a/lib/rules/no-extra-semicolons/index.js b/lib/rules/no-extra-semicolons/index.js index 1506958217..8b0fcebf3f 100644 --- a/lib/rules/no-extra-semicolons/index.js +++ b/lib/rules/no-extra-semicolons/index.js @@ -19,6 +19,7 @@ function getOffsetByNode(node) { if (node.parent && node.parent.document) { return 0; } + const string = node.root().source.input.css; const nodeColumn = node.source.start.column; const nodeLine = node.source.start.line; @@ -46,6 +47,7 @@ function getOffsetByNode(node) { const rule = function(actual, options, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -57,11 +59,14 @@ const rule = function(actual, options, context) { if (rawAfterRoot && rawAfterRoot.trim().length !== 0) { const fixSemiIndices = []; + styleSearch({ source: rawAfterRoot, target: ";" }, match => { if (context.fix) { fixSemiIndices.push(match.startIndex); + return; } + complain( root.source.input.css.length - rawAfterRoot.length + @@ -122,14 +127,17 @@ const rule = function(actual, options, context) { } const fixSemiIndices = []; + styleSearch( { source: rawBeforeNode, target: ";" }, (match, count) => { if (count === allowedSemi) { return; } + if (context.fix) { fixSemiIndices.push(match.startIndex - rawBeforeIndexStart); + return; } @@ -163,11 +171,14 @@ const rule = function(actual, options, context) { } const fixSemiIndices = []; + styleSearch({ source: rawAfterNode, target: ";" }, match => { if (context.fix) { fixSemiIndices.push(match.startIndex); + return; } + const index = getOffsetByNode(node) + node.toString().length - @@ -194,14 +205,17 @@ const rule = function(actual, options, context) { } const fixSemiIndices = []; + styleSearch( { source: rawOwnSemicolon, target: ";" }, (match, count) => { if (count === allowedSemi) { return; } + if (context.fix) { fixSemiIndices.push(match.startIndex); + return; } @@ -210,6 +224,7 @@ const rule = function(actual, options, context) { node.toString().length - rawOwnSemicolon.length + match.startIndex; + complain(index); } ); @@ -238,6 +253,7 @@ const rule = function(actual, options, context) { indices.reverse().forEach(index => { str = str.slice(0, index) + str.slice(index + 1); }); + return str; } } diff --git a/lib/rules/no-invalid-double-slash-comments/index.js b/lib/rules/no-invalid-double-slash-comments/index.js index e231459fca..d29374dac3 100644 --- a/lib/rules/no-invalid-double-slash-comments/index.js +++ b/lib/rules/no-invalid-double-slash-comments/index.js @@ -13,6 +13,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/no-missing-end-of-source-newline/index.js b/lib/rules/no-missing-end-of-source-newline/index.js index d95de9d88d..ef22643a28 100644 --- a/lib/rules/no-missing-end-of-source-newline/index.js +++ b/lib/rules/no-missing-end-of-source-newline/index.js @@ -14,6 +14,7 @@ const messages = ruleMessages(ruleName, { const rule = function(primary, _, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { primary }); + if (!validOptions) { return; } @@ -24,7 +25,9 @@ const rule = function(primary, _, context) { if (root.source.inline || root.source.lang === "object-literal") { return; } + const rootString = root.source.input.css; + if (rootString.trim() === "" || rootString.slice(-1) === "\n") { return; } @@ -32,6 +35,7 @@ const rule = function(primary, _, context) { // Fix if (context.fix) { root.raws.after = context.newline; + return; } diff --git a/lib/rules/no-unknown-animations/index.js b/lib/rules/no-unknown-animations/index.js index 273415f328..0c9bf987f5 100644 --- a/lib/rules/no-unknown-animations/index.js +++ b/lib/rules/no-unknown-animations/index.js @@ -17,11 +17,13 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } const declaredAnimations = new Set(); + root.walkAtRules(/(-(moz|webkit)-)?keyframes/i, atRule => { declaredAnimations.add(atRule.params); }); @@ -45,6 +47,7 @@ const rule = function(actual) { ) { return; } + if (declaredAnimations.has(animationNameNode.value)) { return; } diff --git a/lib/rules/number-leading-zero/index.js b/lib/rules/number-leading-zero/index.js index daf4195b2c..a245c1d133 100644 --- a/lib/rules/number-leading-zero/index.js +++ b/lib/rules/number-leading-zero/index.js @@ -20,6 +20,7 @@ const rule = function(expectation, secondary, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -64,6 +65,7 @@ const rule = function(expectation, secondary, context) { if (match === null) { return; } + // The regexp above consists of 2 capturing groups (or capturing parentheses). // We need the index of the second group. This makes sanse when we have "-.5" as an input // for regex. And we need the index of ".5". @@ -76,6 +78,7 @@ const rule = function(expectation, secondary, context) { alwaysFixPositions.unshift({ index }); + return; } else { complain(messages.expected, node, getIndex(node) + index); @@ -104,6 +107,7 @@ const rule = function(expectation, secondary, context) { // match[1].length is the length of our matched zero(s) endIndex: index + match[1].length }); + return; } else { complain(messages.rejected, node, getIndex(node) + index); @@ -114,6 +118,7 @@ const rule = function(expectation, secondary, context) { if (alwaysFixPositions.length) { alwaysFixPositions.forEach(function(fixPosition) { const index = fixPosition.index; + if (node.type === "atrule") { node.params = addLeadingZero(node.params, index); } else { @@ -126,6 +131,7 @@ const rule = function(expectation, secondary, context) { neverFixPositions.forEach(function(fixPosition) { const startIndex = fixPosition.startIndex; const endIndex = fixPosition.endIndex; + if (node.type === "atrule") { node.params = removeLeadingZeros(node.params, startIndex, endIndex); } else { diff --git a/lib/rules/number-max-precision/index.js b/lib/rules/number-max-precision/index.js index fd157f14d7..f1fecb0ef2 100644 --- a/lib/rules/number-max-precision/index.js +++ b/lib/rules/number-max-precision/index.js @@ -35,6 +35,7 @@ const rule = function(precision, options) { } } ); + if (!validOptions) { return; } diff --git a/lib/rules/number-no-trailing-zeros/index.js b/lib/rules/number-no-trailing-zeros/index.js index 39f64ec3a5..c5c0cd3f5e 100644 --- a/lib/rules/number-no-trailing-zeros/index.js +++ b/lib/rules/number-no-trailing-zeros/index.js @@ -16,6 +16,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual, secondary, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -53,6 +54,7 @@ const rule = function(actual, secondary, context) { } const match = /\.(\d*?)(0+)(?:\D|$)/.exec(valueNode.value); + // match[1] is any numbers between the decimal and our trailing zero, could be empty // match[2] is our trailing zero(s) if (match === null) { @@ -79,6 +81,7 @@ const rule = function(actual, secondary, context) { startIndex, endIndex }); + return; } else { report({ @@ -96,6 +99,7 @@ const rule = function(actual, secondary, context) { fixPositions.forEach(function(fixPosition) { const startIndex = fixPosition.startIndex; const endIndex = fixPosition.endIndex; + if (node.type === "atrule") { node.params = removeTrailingZeros( node.params, diff --git a/lib/rules/property-blacklist/index.js b/lib/rules/property-blacklist/index.js index dd0200358a..420bd836d1 100644 --- a/lib/rules/property-blacklist/index.js +++ b/lib/rules/property-blacklist/index.js @@ -21,6 +21,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -31,9 +32,11 @@ const rule = function(blacklist) { if (!isStandardSyntaxProperty(prop)) { return; } + if (isCustomProperty(prop)) { return; } + if (!matchesStringOrRegExp(postcss.vendor.unprefixed(prop), blacklist)) { return; } diff --git a/lib/rules/property-case/index.js b/lib/rules/property-case/index.js index 832ce06ecd..5dc09c255a 100644 --- a/lib/rules/property-case/index.js +++ b/lib/rules/property-case/index.js @@ -18,6 +18,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } @@ -28,6 +29,7 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxProperty(prop)) { return; } + if (isCustomProperty(prop)) { return; } @@ -41,6 +43,7 @@ const rule = function(expectation, options, context) { if (context.fix) { decl.prop = expectedProp; + return; } diff --git a/lib/rules/property-no-unknown/index.js b/lib/rules/property-no-unknown/index.js index f119abddfd..ffea292fc3 100644 --- a/lib/rules/property-no-unknown/index.js +++ b/lib/rules/property-no-unknown/index.js @@ -45,9 +45,11 @@ const rule = function(actual, options) { if (!isStandardSyntaxProperty(prop)) { return; } + if (!isStandardSyntaxDeclaration(decl)) { return; } + if (isCustomProperty(prop)) { return; } diff --git a/lib/rules/property-no-vendor-prefix/index.js b/lib/rules/property-no-vendor-prefix/index.js index 56890d5ff2..55e1f94dc7 100644 --- a/lib/rules/property-no-vendor-prefix/index.js +++ b/lib/rules/property-no-vendor-prefix/index.js @@ -52,6 +52,7 @@ const rule = function(actual, options) { if (!isAutoprefixable.property(prop)) { return; } + report({ message: messages.rejected(prop), node: decl, diff --git a/lib/rules/property-whitelist/index.js b/lib/rules/property-whitelist/index.js index 7273353e96..607f23040d 100644 --- a/lib/rules/property-whitelist/index.js +++ b/lib/rules/property-whitelist/index.js @@ -21,6 +21,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -31,9 +32,11 @@ const rule = function(whitelist) { if (!isStandardSyntaxProperty(prop)) { return; } + if (isCustomProperty(prop)) { return; } + if (matchesStringOrRegExp(postcss.vendor.unprefixed(prop), whitelist)) { return; } diff --git a/lib/rules/rule-empty-line-before/index.js b/lib/rules/rule-empty-line-before/index.js index 66038fb2e3..90726fe532 100644 --- a/lib/rules/rule-empty-line-before/index.js +++ b/lib/rules/rule-empty-line-before/index.js @@ -145,6 +145,7 @@ const rule = function(expectation, options, context) { function isAfterRule(rule) { const prevNode = getPreviousNonSharedLineCommentNode(rule); + return prevNode && prevNode.type === "rule"; } diff --git a/lib/rules/selector-attribute-brackets-space-inside/index.js b/lib/rules/selector-attribute-brackets-space-inside/index.js index 33422a37d9..156892e140 100644 --- a/lib/rules/selector-attribute-brackets-space-inside/index.js +++ b/lib/rules/selector-attribute-brackets-space-inside/index.js @@ -23,6 +23,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -31,6 +32,7 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxRule(rule)) { return; } + if (rule.selector.indexOf("[") === -1) { return; } @@ -54,20 +56,26 @@ const rule = function(expectation, options, context) { const nextCharIsSpace = attributeSelectorString[match.startIndex + 1] === " "; const index = attributeNode.sourceIndex + match.startIndex + 1; + if (nextCharIsSpace && expectation === "never") { if (context.fix) { hasFixed = true; fixBefore(attributeNode); + return; } + complain(messages.rejectedOpening, index); } + if (!nextCharIsSpace && expectation === "always") { if (context.fix) { hasFixed = true; fixBefore(attributeNode); + return; } + complain(messages.expectedOpening, index); } } @@ -79,20 +87,26 @@ const rule = function(expectation, options, context) { const prevCharIsSpace = attributeSelectorString[match.startIndex - 1] === " "; const index = attributeNode.sourceIndex + match.startIndex - 1; + if (prevCharIsSpace && expectation === "never") { if (context.fix) { hasFixed = true; fixAfter(attributeNode); + return; } + complain(messages.rejectedClosing, index); } + if (!prevCharIsSpace && expectation === "always") { if (context.fix) { hasFixed = true; fixAfter(attributeNode); + return; } + complain(messages.expectedClosing, index); } } @@ -156,6 +170,7 @@ const rule = function(expectation, options, context) { } else { key = "attribute"; } + const rawAfter = _.get(attributeNode, `raws.spaces.${key}.after`); const { after, setAfter } = rawAfter ? { diff --git a/lib/rules/selector-attribute-operator-blacklist/index.js b/lib/rules/selector-attribute-operator-blacklist/index.js index a7615c4008..7c70fcb919 100644 --- a/lib/rules/selector-attribute-operator-blacklist/index.js +++ b/lib/rules/selector-attribute-operator-blacklist/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(blacklistInput) { const blacklist = [].concat(blacklistInput); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -28,6 +30,7 @@ const rule = function(blacklistInput) { if (!isStandardSyntaxRule(rule)) { return; } + if ( rule.selector.indexOf("[") === -1 || rule.selector.indexOf("=") === -1 diff --git a/lib/rules/selector-attribute-operator-space-after/index.js b/lib/rules/selector-attribute-operator-space-after/index.js index 99a1fdc73a..1889f4edfd 100644 --- a/lib/rules/selector-attribute-operator-space-after/index.js +++ b/lib/rules/selector-attribute-operator-space-after/index.js @@ -20,6 +20,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -34,10 +35,12 @@ const rule = function(expectation, options, context) { ? attributeNode => { const { operatorAfter, setOperatorAfter } = (() => { const rawOperator = _.get(attributeNode, "raws.operator"); + if (rawOperator) { const operatorAfter = rawOperator.slice( attributeNode.operator.length ); + return { operatorAfter, setOperatorAfter(fixed) { @@ -51,6 +54,7 @@ const rule = function(expectation, options, context) { attributeNode, "raws.spaces.operator.after" ); + if (rawOperatorAfter) { return { operatorAfter: rawOperatorAfter, @@ -59,6 +63,7 @@ const rule = function(expectation, options, context) { } }; } + return { operatorAfter: _.get( attributeNode, @@ -73,9 +78,11 @@ const rule = function(expectation, options, context) { if (expectation === "always") { setOperatorAfter(operatorAfter.replace(/^\s*/, " ")); + return true; } else if (expectation === "never") { setOperatorAfter(operatorAfter.replace(/^\s*/, "")); + return true; } } diff --git a/lib/rules/selector-attribute-operator-space-before/index.js b/lib/rules/selector-attribute-operator-space-before/index.js index 7f86c64d24..b789ac5bb8 100644 --- a/lib/rules/selector-attribute-operator-space-before/index.js +++ b/lib/rules/selector-attribute-operator-space-before/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -52,9 +54,11 @@ const rule = function(expectation, options, context) { if (expectation === "always") { setAttrAfter(attrAfter.replace(/\s*$/, " ")); + return true; } else if (expectation === "never") { setAttrAfter(attrAfter.replace(/\s*$/, "")); + return true; } } diff --git a/lib/rules/selector-attribute-operator-whitelist/index.js b/lib/rules/selector-attribute-operator-whitelist/index.js index dc51af44b1..b3ce197ad7 100644 --- a/lib/rules/selector-attribute-operator-whitelist/index.js +++ b/lib/rules/selector-attribute-operator-whitelist/index.js @@ -15,11 +15,13 @@ const messages = ruleMessages(ruleName, { const rule = function(whitelistInput) { const whitelist = [].concat(whitelistInput); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -28,6 +30,7 @@ const rule = function(whitelistInput) { if (!isStandardSyntaxRule(rule)) { return; } + if ( rule.selector.indexOf("[") === -1 || rule.selector.indexOf("=") === -1 diff --git a/lib/rules/selector-attribute-quotes/index.js b/lib/rules/selector-attribute-quotes/index.js index cb3beb6aee..ffafbbda6c 100644 --- a/lib/rules/selector-attribute-quotes/index.js +++ b/lib/rules/selector-attribute-quotes/index.js @@ -20,6 +20,7 @@ const rule = function(expectation) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -28,15 +29,18 @@ const rule = function(expectation) { if (!isStandardSyntaxRule(rule)) { return; } + if (!isStandardSyntaxSelector(rule.selector)) { return; } + if ( rule.selector.indexOf("[") === -1 || rule.selector.indexOf("=") === -1 ) { return; } + parseSelector(rule.selector, result, rule, selectorTree => { selectorTree.walkAttributes(attributeNode => { if (!attributeNode.operator) { diff --git a/lib/rules/selector-class-pattern/index.js b/lib/rules/selector-class-pattern/index.js index aac9db77a1..fcf31c3f6f 100644 --- a/lib/rules/selector-class-pattern/index.js +++ b/lib/rules/selector-class-pattern/index.js @@ -34,6 +34,7 @@ const rule = function(pattern, options) { optional: true } ); + if (!validOptions) { return; } @@ -53,9 +54,11 @@ const rule = function(pattern, options) { if (!isStandardSyntaxRule(rule)) { return; } + if (!isStandardSyntaxSelector(selector)) { return; } + if (selectors.some(s => isKeyframeSelector(s))) { return; } @@ -82,6 +85,7 @@ const rule = function(pattern, options) { if (normalizedPattern.test(value)) { return; } + report({ result, ruleName, @@ -102,13 +106,16 @@ function hasInterpolatingAmpersand(selector) { if (selector[i] !== "&") { continue; } + if (!_.isUndefined(selector[i - 1]) && !isCombinator(selector[i - 1])) { return true; } + if (!_.isUndefined(selector[i + 1]) && !isCombinator(selector[i + 1])) { return true; } } + return false; } diff --git a/lib/rules/selector-combinator-blacklist/index.js b/lib/rules/selector-combinator-blacklist/index.js index 449907290e..61f6f163ad 100644 --- a/lib/rules/selector-combinator-blacklist/index.js +++ b/lib/rules/selector-combinator-blacklist/index.js @@ -20,6 +20,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/selector-combinator-space-after/index.js b/lib/rules/selector-combinator-space-after/index.js index cc077f87d9..73a564ca02 100644 --- a/lib/rules/selector-combinator-space-after/index.js +++ b/lib/rules/selector-combinator-space-after/index.js @@ -14,11 +14,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -33,9 +35,11 @@ const rule = function(expectation, options, context) { ? combinator => { if (expectation === "always") { combinator.spaces.after = " "; + return true; } else if (expectation === "never") { combinator.spaces.after = ""; + return true; } } diff --git a/lib/rules/selector-combinator-space-before/index.js b/lib/rules/selector-combinator-space-before/index.js index 66c7f50601..d7bd63c3a1 100644 --- a/lib/rules/selector-combinator-space-before/index.js +++ b/lib/rules/selector-combinator-space-before/index.js @@ -14,11 +14,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -33,9 +35,11 @@ const rule = function(expectation, options, context) { ? combinator => { if (expectation === "always") { combinator.spaces.before = " "; + return true; } else if (expectation === "never") { combinator.spaces.before = ""; + return true; } } diff --git a/lib/rules/selector-combinator-whitelist/index.js b/lib/rules/selector-combinator-whitelist/index.js index 653902f28b..6a75ae3080 100644 --- a/lib/rules/selector-combinator-whitelist/index.js +++ b/lib/rules/selector-combinator-whitelist/index.js @@ -20,6 +20,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -36,6 +37,7 @@ const rule = function(whitelist) { if (!isStandardSyntaxCombinator(combinatorNode)) { return; } + const value = normalizeCombinator(combinatorNode.value); if (whitelist.indexOf(value) !== -1) { diff --git a/lib/rules/selector-descendant-combinator-no-non-space/index.js b/lib/rules/selector-descendant-combinator-no-non-space/index.js index 073f4b9a14..691bcbb6a6 100644 --- a/lib/rules/selector-descendant-combinator-no-non-space/index.js +++ b/lib/rules/selector-descendant-combinator-no-non-space/index.js @@ -18,6 +18,7 @@ const rule = function(expectation, options, context) { const validOptions = validateOptions(result, ruleName, { actual: expectation }); + if (!validOptions) { return; } @@ -57,6 +58,7 @@ const rule = function(expectation, options, context) { if (context.fix && /^\s+$/.test(value)) { hasFixed = true; combinatorNode.value = " "; + return; } @@ -101,17 +103,21 @@ function isActuallyCombinator(combinatorNode) { } let next = combinatorNode.next(); + while (skipTest(next)) { next = next.next(); } + if (isNonTarget(next)) { return false; } let prev = combinatorNode.prev(); + while (skipTest(prev)) { prev = prev.prev(); } + if (isNonTarget(prev)) { return false; } @@ -122,21 +128,27 @@ function isActuallyCombinator(combinatorNode) { if (!node) { return false; } + if (node.type === "comment") { return true; } + if (node.type === "combinator" && /^\s+$/.test(node.value)) { return true; } + return false; } + function isNonTarget(node) { if (!node) { return true; } + if (node.type === "combinator" && !/^\s+$/.test(node.value)) { return true; } + return false; } } diff --git a/lib/rules/selector-id-pattern/index.js b/lib/rules/selector-id-pattern/index.js index e5204b1917..109dd1393f 100644 --- a/lib/rules/selector-id-pattern/index.js +++ b/lib/rules/selector-id-pattern/index.js @@ -21,6 +21,7 @@ const rule = function(pattern) { actual: pattern, possible: [_.isRegExp, _.isString] }); + if (!validOptions) { return; } @@ -45,6 +46,7 @@ const rule = function(pattern) { if (selectorNode.type !== "id") { return; } + const value = selectorNode.value; const sourceIndex = selectorNode.sourceIndex; diff --git a/lib/rules/selector-list-comma-newline-after/index.js b/lib/rules/selector-list-comma-newline-after/index.js index eef1c2934e..9fe800b9f8 100644 --- a/lib/rules/selector-list-comma-newline-after/index.js +++ b/lib/rules/selector-list-comma-newline-after/index.js @@ -19,11 +19,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } @@ -32,12 +34,14 @@ const rule = function(expectation) { if (!isStandardSyntaxRule(rule)) { return; } + // Get raw selector so we can allow end-of-line comments, e.g. // a, /* comment */ // b {} const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector; + styleSearch( { source: selector, @@ -60,6 +64,7 @@ const rule = function(expectation) { const indextoCheckAfter = nextChars.match(/^\s+\/\*/) ? selector.indexOf("*/", match.endIndex) + 1 : match.startIndex; + checker.afterOneOnly({ source: selector, index: indextoCheckAfter, diff --git a/lib/rules/selector-list-comma-newline-before/index.js b/lib/rules/selector-list-comma-newline-before/index.js index e312a4c05e..5bd7e6931c 100644 --- a/lib/rules/selector-list-comma-newline-before/index.js +++ b/lib/rules/selector-list-comma-newline-before/index.js @@ -17,11 +17,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } @@ -37,22 +39,28 @@ const rule = function(expectation, options, context) { ? (ruleNode, index) => { fixData = fixData || new Map(); const commaIndices = fixData.get(ruleNode) || []; + commaIndices.push(index); fixData.set(ruleNode, commaIndices); + return true; } : null }); + if (fixData) { fixData.forEach((commaIndices, ruleNode) => { let selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector; + commaIndices.sort((a, b) => b - a).forEach(index => { let beforeSelector = selector.slice(0, index); const afterSelector = selector.slice(index); + if (expectation.indexOf("always") === 0) { const spaceIndex = beforeSelector.search(/\s+$/); + if (spaceIndex >= 0) { beforeSelector = beforeSelector.slice(0, spaceIndex) + @@ -64,8 +72,10 @@ const rule = function(expectation, options, context) { } else if (expectation === "never-multi-line") { beforeSelector = beforeSelector.replace(/\s*$/, ""); } + selector = beforeSelector + afterSelector; }); + if (ruleNode.raws.selector) { ruleNode.raws.selector.raw = selector; } else { diff --git a/lib/rules/selector-list-comma-space-after/index.js b/lib/rules/selector-list-comma-space-after/index.js index 42440f8731..dfbce9e5d2 100644 --- a/lib/rules/selector-list-comma-space-after/index.js +++ b/lib/rules/selector-list-comma-space-after/index.js @@ -18,11 +18,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } @@ -38,27 +40,34 @@ const rule = function(expectation, options, context) { ? (ruleNode, index) => { fixData = fixData || new Map(); const commaIndices = fixData.get(ruleNode) || []; + commaIndices.push(index); fixData.set(ruleNode, commaIndices); + return true; } : null }); + if (fixData) { fixData.forEach((commaIndices, ruleNode) => { let selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector; + commaIndices.sort((a, b) => b - a).forEach(index => { const beforeSelector = selector.slice(0, index + 1); let afterSelector = selector.slice(index + 1); + if (expectation.indexOf("always") === 0) { afterSelector = afterSelector.replace(/^\s*/, " "); } else if (expectation.indexOf("never") === 0) { afterSelector = afterSelector.replace(/^\s*/, ""); } + selector = beforeSelector + afterSelector; }); + if (ruleNode.raws.selector) { ruleNode.raws.selector.raw = selector; } else { diff --git a/lib/rules/selector-list-comma-space-before/index.js b/lib/rules/selector-list-comma-space-before/index.js index 2879525e6a..38d11448d8 100644 --- a/lib/rules/selector-list-comma-space-before/index.js +++ b/lib/rules/selector-list-comma-space-before/index.js @@ -18,11 +18,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } @@ -38,27 +40,34 @@ const rule = function(expectation, options, context) { ? (ruleNode, index) => { fixData = fixData || new Map(); const commaIndices = fixData.get(ruleNode) || []; + commaIndices.push(index); fixData.set(ruleNode, commaIndices); + return true; } : null }); + if (fixData) { fixData.forEach((commaIndices, ruleNode) => { let selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector; + commaIndices.sort((a, b) => b - a).forEach(index => { let beforeSelector = selector.slice(0, index); const afterSelector = selector.slice(index); + if (expectation.indexOf("always") >= 0) { beforeSelector = beforeSelector.replace(/\s*$/, " "); } else if (expectation.indexOf("never") >= 0) { beforeSelector = beforeSelector.replace(/\s*$/, ""); } + selector = beforeSelector + afterSelector; }); + if (ruleNode.raws.selector) { ruleNode.raws.selector.raw = selector; } else { diff --git a/lib/rules/selector-max-attribute/index.js b/lib/rules/selector-max-attribute/index.js index 42e3860386..df2d3358ac 100644 --- a/lib/rules/selector-max-attribute/index.js +++ b/lib/rules/selector-max-attribute/index.js @@ -40,6 +40,7 @@ function rule(max, options) { optional: true } ); + if (!validOptions) { return; } @@ -55,6 +56,7 @@ function rule(max, options) { // Not an attribute node -> ignore return total; } + if (optionsMatches(options, "ignoreAttributes", childNode.attribute)) { // it's an attribute that is supposed to be ignored return total; @@ -82,9 +84,11 @@ function rule(max, options) { if (!isStandardSyntaxRule(ruleNode)) { return; } + if (!isStandardSyntaxSelector(ruleNode.selector)) { return; } + if ( ruleNode.nodes.some( node => ["rule", "atrule"].indexOf(node.type) !== -1 diff --git a/lib/rules/selector-max-class/index.js b/lib/rules/selector-max-class/index.js index 6c1dd7d930..adc8492a91 100644 --- a/lib/rules/selector-max-class/index.js +++ b/lib/rules/selector-max-class/index.js @@ -27,6 +27,7 @@ function rule(max) { } ] }); + if (!validOptions) { return; } @@ -60,9 +61,11 @@ function rule(max) { if (!isStandardSyntaxRule(ruleNode)) { return; } + if (!isStandardSyntaxSelector(ruleNode.selector)) { return; } + if ( ruleNode.nodes.some( node => ["rule", "atrule"].indexOf(node.type) !== -1 diff --git a/lib/rules/selector-max-combinators/index.js b/lib/rules/selector-max-combinators/index.js index 5f6ead7243..30ebaf2b9f 100644 --- a/lib/rules/selector-max-combinators/index.js +++ b/lib/rules/selector-max-combinators/index.js @@ -27,6 +27,7 @@ function rule(max) { } ] }); + if (!validOptions) { return; } @@ -60,9 +61,11 @@ function rule(max) { if (!isStandardSyntaxRule(ruleNode)) { return; } + if (!isStandardSyntaxSelector(ruleNode.selector)) { return; } + if ( ruleNode.nodes.some( node => ["rule", "atrule"].indexOf(node.type) !== -1 diff --git a/lib/rules/selector-max-compound-selectors/index.js b/lib/rules/selector-max-compound-selectors/index.js index fcd64f89d8..383ee0ff40 100644 --- a/lib/rules/selector-max-compound-selectors/index.js +++ b/lib/rules/selector-max-compound-selectors/index.js @@ -27,6 +27,7 @@ const rule = function(max) { } ] }); + if (!validOptions) { return; } @@ -66,6 +67,7 @@ const rule = function(max) { if (!isStandardSyntaxRule(rule)) { return; } + if (!isStandardSyntaxSelector(rule.selector)) { return; } diff --git a/lib/rules/selector-max-empty-lines/index.js b/lib/rules/selector-max-empty-lines/index.js index a4082957a0..159d3ad567 100644 --- a/lib/rules/selector-max-empty-lines/index.js +++ b/lib/rules/selector-max-empty-lines/index.js @@ -21,6 +21,7 @@ const rule = function(max) { actual: max, possible: _.isNumber }); + if (!validOptions) { return; } @@ -41,6 +42,7 @@ const rule = function(max) { ) { // Put index at `\r` if it's CRLF, otherwise leave it at `\n` let index = match.startIndex; + if (selector[index - 1] === "\r") { index -= 1; } diff --git a/lib/rules/selector-max-id/index.js b/lib/rules/selector-max-id/index.js index c502e2ab22..161b1a9787 100644 --- a/lib/rules/selector-max-id/index.js +++ b/lib/rules/selector-max-id/index.js @@ -27,6 +27,7 @@ function rule(max) { } ] }); + if (!validOptions) { return; } @@ -60,6 +61,7 @@ function rule(max) { if (!isStandardSyntaxRule(ruleNode)) { return; } + if (!isStandardSyntaxSelector(ruleNode.selector)) { return; } diff --git a/lib/rules/selector-max-pseudo-class/index.js b/lib/rules/selector-max-pseudo-class/index.js index 96183cdb57..98c6ea5f81 100644 --- a/lib/rules/selector-max-pseudo-class/index.js +++ b/lib/rules/selector-max-pseudo-class/index.js @@ -28,6 +28,7 @@ function rule(max) { } ] }); + if (!validOptions) { return; } @@ -72,6 +73,7 @@ function rule(max) { if (!isStandardSyntaxRule(ruleNode)) { return; } + if (!isStandardSyntaxSelector(ruleNode.selector)) { return; } diff --git a/lib/rules/selector-max-specificity/index.js b/lib/rules/selector-max-specificity/index.js index 446e585c7f..6fa950ae43 100755 --- a/lib/rules/selector-max-specificity/index.js +++ b/lib/rules/selector-max-specificity/index.js @@ -24,11 +24,13 @@ const zeroSpecificity = () => [0, 0, 0, 0]; // Calculate the sum of given array of specificity arrays const specificitySum = specificities => { const sum = zeroSpecificity(); + specificities.forEach(specificityArray => { specificityArray.forEach((value, i) => { sum[i] += value; }); }); + return sum; }; @@ -43,6 +45,7 @@ const rule = function(max, options) { function(max) { // Check that the max specificity is in the form "a,b,c" const pattern = new RegExp("^\\d+,\\d+,\\d+$"); + return pattern.test(max); } ] @@ -55,6 +58,7 @@ const rule = function(max, options) { optional: true } ); + if (!validOptions) { return; } @@ -64,6 +68,7 @@ const rule = function(max, options) { if (optionsMatches(options, "ignoreSelectors", selector)) { return zeroSpecificity(); } + return specificity.calculate(selector)[0].specificityArray; }; @@ -71,6 +76,7 @@ const rule = function(max, options) { const maxChildSpecificity = node => node.reduce((max, child) => { const childSpecificity = nodeSpecificity(child); // eslint-disable-line no-use-before-define + return specificity.compare(childSpecificity, max) === 1 ? childSpecificity : max; @@ -109,13 +115,16 @@ const rule = function(max, options) { }; const maxSpecificityArray = ("0," + max).split(",").map(parseFloat); + root.walkRules(rule => { if (!isStandardSyntaxRule(rule)) { return; } + if (!isStandardSyntaxSelector(rule.selector)) { return; } + // Using rule.selectors gets us each selector in the eventuality we have a comma separated set rule.selectors.forEach(selector => { resolvedNestedSelector(selector, rule).forEach(resolvedSelector => { @@ -124,6 +133,7 @@ const rule = function(max, options) { if (!isStandardSyntaxSelector(resolvedSelector)) { return; } + parseSelector(resolvedSelector, result, rule, selectorTree => { // Check if the selector specificity exceeds the allowed maximum if ( diff --git a/lib/rules/selector-max-type/index.js b/lib/rules/selector-max-type/index.js index 13b38d22d9..3a34cfca3b 100644 --- a/lib/rules/selector-max-type/index.js +++ b/lib/rules/selector-max-type/index.js @@ -41,6 +41,7 @@ function rule(max, options) { optional: true } ); + if (!validOptions) { return; } @@ -97,12 +98,15 @@ function rule(max, options) { if (!isStandardSyntaxRule(ruleNode)) { return; } + if (!isStandardSyntaxSelector(selector)) { return; } + if (selectors.some(s => isKeyframeSelector(s))) { return; } + if ( ruleNode.nodes.some( node => ["rule", "atrule"].indexOf(node.type) !== -1 @@ -117,6 +121,7 @@ function rule(max, options) { if (!isStandardSyntaxSelector(resolvedSelector)) { return; } + parseSelector(resolvedSelector, result, ruleNode, container => checkSelector(container, ruleNode) ); @@ -128,11 +133,13 @@ function rule(max, options) { function hasDescendantCombinatorBefore(node) { const nodeIndex = node.parent.nodes.indexOf(node); + return node.parent.nodes.slice(0, nodeIndex).some(isDescendantCombinator); } function hasChildCombinatorBefore(node) { const nodeIndex = node.parent.nodes.indexOf(node); + return node.parent.nodes.slice(0, nodeIndex).some(isChildCombinator); } @@ -140,24 +147,29 @@ function hasCompoundSelector(node) { if (node.prev() && !isCombinator(node.prev())) { return true; } + if (node.next() && !isCombinator(node.next())) { return true; } + return false; } function isCombinator(node) { if (!node) return false; + return _.get(node, "type") === "combinator"; } function isDescendantCombinator(node) { if (!node) return false; + return isCombinator(node) && isOnlyWhitespace(node.value); } function isChildCombinator(node) { if (!node) return false; + return isCombinator(node) && node.value.includes(">"); } diff --git a/lib/rules/selector-max-universal/index.js b/lib/rules/selector-max-universal/index.js index 10af42edb5..ec4efaf26b 100644 --- a/lib/rules/selector-max-universal/index.js +++ b/lib/rules/selector-max-universal/index.js @@ -27,6 +27,7 @@ function rule(max) { } ] }); + if (!validOptions) { return; } @@ -60,9 +61,11 @@ function rule(max) { if (!isStandardSyntaxRule(ruleNode)) { return; } + if (!isStandardSyntaxSelector(ruleNode.selector)) { return; } + if ( ruleNode.nodes.some( node => ["rule", "atrule"].indexOf(node.type) !== -1 diff --git a/lib/rules/selector-nested-pattern/index.js b/lib/rules/selector-nested-pattern/index.js index 15bd9ac034..8847d181ca 100644 --- a/lib/rules/selector-nested-pattern/index.js +++ b/lib/rules/selector-nested-pattern/index.js @@ -20,6 +20,7 @@ const rule = function(pattern) { actual: pattern, possible: [_.isRegExp, _.isString] }); + if (!validOptions) { return; } @@ -32,6 +33,7 @@ const rule = function(pattern) { if (rule.parent.type !== "rule") { return; } + if (!isStandardSyntaxRule(rule)) { return; } diff --git a/lib/rules/selector-no-qualifying-type/index.js b/lib/rules/selector-no-qualifying-type/index.js index cc94dceca6..b35312b04a 100644 --- a/lib/rules/selector-no-qualifying-type/index.js +++ b/lib/rules/selector-no-qualifying-type/index.js @@ -30,6 +30,7 @@ function getRightNodes(node) { if (rightNode.type === "combinator") { break; } + if ( rightNode.type !== "id" && rightNode.type !== "class" && @@ -61,6 +62,7 @@ const rule = function(enabled, options) { optional: true } ); + if (!validOptions) { return; } @@ -69,13 +71,16 @@ const rule = function(enabled, options) { if (!isStandardSyntaxRule(rule)) { return; } + if (isKeyframeRule(rule)) { return; } + // Increasing performance if (!isStandardSyntaxSelector(rule.selector)) { return; } + if (!isSelectorCharacters(rule.selector)) { return; } diff --git a/lib/rules/selector-no-vendor-prefix/index.js b/lib/rules/selector-no-vendor-prefix/index.js index 7cc21bb2cd..1af88ae3b5 100644 --- a/lib/rules/selector-no-vendor-prefix/index.js +++ b/lib/rules/selector-no-vendor-prefix/index.js @@ -17,6 +17,7 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } @@ -25,11 +26,13 @@ const rule = function(actual) { if (!isStandardSyntaxRule(rule)) { return; } + const selector = rule.selector; if (!isStandardSyntaxSelector(selector)) { return; } + parseSelector(selector, result, rule, selectorTree => { selectorTree.walkPseudos(pseudoNode => { if (isAutoprefixable.selector(pseudoNode.value)) { diff --git a/lib/rules/selector-pseudo-class-blacklist/index.js b/lib/rules/selector-pseudo-class-blacklist/index.js index f92e35c429..af1908a81c 100644 --- a/lib/rules/selector-pseudo-class-blacklist/index.js +++ b/lib/rules/selector-pseudo-class-blacklist/index.js @@ -22,6 +22,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString] }); + if (!validOptions) { return; } diff --git a/lib/rules/selector-pseudo-class-case/index.js b/lib/rules/selector-pseudo-class-case/index.js index 3c2ab1b56c..05722d73a6 100644 --- a/lib/rules/selector-pseudo-class-case/index.js +++ b/lib/rules/selector-pseudo-class-case/index.js @@ -20,6 +20,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } @@ -28,6 +29,7 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxRule(rule)) { return; } + const selector = rule.selector; const startIndexPseudo = selector.indexOf(":"); @@ -67,6 +69,7 @@ const rule = function(expectation, options, context) { if (context.fix) { pseudoNode.value = expectedPseudo; + return; } diff --git a/lib/rules/selector-pseudo-class-no-unknown/index.js b/lib/rules/selector-pseudo-class-no-unknown/index.js index cc35227d63..0897d1fe1d 100644 --- a/lib/rules/selector-pseudo-class-no-unknown/index.js +++ b/lib/rules/selector-pseudo-class-no-unknown/index.js @@ -34,6 +34,7 @@ const rule = function(actual, options) { optional: true } ); + if (!validOptions) { return; } @@ -85,8 +86,10 @@ const rule = function(actual, options) { } let prevPseudoElement = pseudoNode; + do { prevPseudoElement = prevPseudoElement.prev(); + if ( prevPseudoElement && prevPseudoElement.value.slice(0, 2) === "::" diff --git a/lib/rules/selector-pseudo-class-parentheses-space-inside/index.js b/lib/rules/selector-pseudo-class-parentheses-space-inside/index.js index f6170dec89..12bc4905f8 100644 --- a/lib/rules/selector-pseudo-class-parentheses-space-inside/index.js +++ b/lib/rules/selector-pseudo-class-parentheses-space-inside/index.js @@ -21,6 +21,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["always", "never"] }); + if (!validOptions) { return; } @@ -29,6 +30,7 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxRule(rule)) { return; } + if (rule.selector.indexOf("(") === -1) { return; } @@ -46,12 +48,14 @@ const rule = function(expectation, options, context) { if (!pseudoNode.length) { return; } + const beforeString = (pseudoNode.rawSpaceBefore || "") + stringifyProperty(pseudoNode, "value"); const paramString = pseudoNode.map(String).join(","); const nextCharIsSpace = paramString.startsWith(" "); const openIndex = pseudoNode.sourceIndex + beforeString.length + 1; + if (nextCharIsSpace && expectation === "never") { if (context.fix) { hasFixed = true; @@ -60,6 +64,7 @@ const rule = function(expectation, options, context) { complain(messages.rejectedOpening, openIndex); } } + if (!nextCharIsSpace && expectation === "always") { if (context.fix) { hasFixed = true; @@ -71,6 +76,7 @@ const rule = function(expectation, options, context) { const prevCharIsSpace = paramString.endsWith(" "); const closeIndex = openIndex + paramString.length - 1; + if (prevCharIsSpace && expectation === "never") { if (context.fix) { hasFixed = true; @@ -79,6 +85,7 @@ const rule = function(expectation, options, context) { complain(messages.rejectedClosing, closeIndex); } } + if (!prevCharIsSpace && expectation === "always") { if (context.fix) { hasFixed = true; @@ -114,14 +121,17 @@ const rule = function(expectation, options, context) { function setFirstNodeSpaceBefore(node, value) { const target = node.first; + if (target.type === "selector") { setFirstNodeSpaceBefore(target, value); } else { target.spaces.before = value; } } + function setLastNodeSpaceAfter(node, value) { const target = node.last; + if (target.type === "selector") { setLastNodeSpaceAfter(target, value); } else { diff --git a/lib/rules/selector-pseudo-class-whitelist/index.js b/lib/rules/selector-pseudo-class-whitelist/index.js index 5fb76e5ca8..6819824566 100644 --- a/lib/rules/selector-pseudo-class-whitelist/index.js +++ b/lib/rules/selector-pseudo-class-whitelist/index.js @@ -22,6 +22,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isString] }); + if (!validOptions) { return; } @@ -36,6 +37,7 @@ const rule = function(whitelist) { if (!isStandardSyntaxSelector(selector)) { return; } + if (selector.indexOf(":") === -1) { return; } diff --git a/lib/rules/selector-pseudo-element-blacklist/index.js b/lib/rules/selector-pseudo-element-blacklist/index.js index bb6e3ae8ca..9b4992d1bc 100644 --- a/lib/rules/selector-pseudo-element-blacklist/index.js +++ b/lib/rules/selector-pseudo-element-blacklist/index.js @@ -22,6 +22,7 @@ const rule = function(blacklist) { actual: blacklist, possible: [_.isString, _.isRegExp] }); + if (!validOptions) { return; } diff --git a/lib/rules/selector-pseudo-element-case/index.js b/lib/rules/selector-pseudo-element-case/index.js index 8762e238b1..06c1f5a7cb 100644 --- a/lib/rules/selector-pseudo-element-case/index.js +++ b/lib/rules/selector-pseudo-element-case/index.js @@ -20,6 +20,7 @@ const rule = function(expectation) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } @@ -28,6 +29,7 @@ const rule = function(expectation) { if (!isStandardSyntaxRule(rule)) { return; } + const selector = rule.selector; const startIndexPseudoElement = selector.indexOf(":"); diff --git a/lib/rules/selector-pseudo-element-colon-notation/index.js b/lib/rules/selector-pseudo-element-colon-notation/index.js index 414f98024b..17305dbdbc 100644 --- a/lib/rules/selector-pseudo-element-colon-notation/index.js +++ b/lib/rules/selector-pseudo-element-colon-notation/index.js @@ -20,6 +20,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["single", "double"] }); + if (!validOptions) { return; } @@ -28,6 +29,7 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxRule(rule)) { return; } + const selector = rule.selector; // get out early if no pseudo elements or classes @@ -41,6 +43,7 @@ const rule = function(expectation, options, context) { const pseudoElementsWithColons = _.toArray( keywordSets.levelOneAndTwoPseudoElements ).map(x => `:${x}`); + styleSearch( { source: selector.toLowerCase(), target: pseudoElementsWithColons }, match => { @@ -49,12 +52,14 @@ const rule = function(expectation, options, context) { if (expectation === "single" && !prevCharIsColon) { return; } + if (expectation === "double" && prevCharIsColon) { return; } if (context.fix) { fixPositions.unshift({ rule, startIndex: match.startIndex }); + return; } diff --git a/lib/rules/selector-pseudo-element-no-unknown/index.js b/lib/rules/selector-pseudo-element-no-unknown/index.js index 8716c070f9..8164b56790 100644 --- a/lib/rules/selector-pseudo-element-no-unknown/index.js +++ b/lib/rules/selector-pseudo-element-no-unknown/index.js @@ -32,6 +32,7 @@ const rule = function(actual, options) { optional: true } ); + if (!validOptions) { return; } @@ -40,6 +41,7 @@ const rule = function(actual, options) { if (!isStandardSyntaxRule(rule)) { return; } + const selector = rule.selector; // Return early before parse if no pseudos for performance diff --git a/lib/rules/selector-pseudo-element-whitelist/index.js b/lib/rules/selector-pseudo-element-whitelist/index.js index f993d3e076..bc0a37a4c6 100644 --- a/lib/rules/selector-pseudo-element-whitelist/index.js +++ b/lib/rules/selector-pseudo-element-whitelist/index.js @@ -22,6 +22,7 @@ const rule = function(whitelist) { actual: whitelist, possible: [_.isString, _.isRegExp] }); + if (!validOptions) { return; } diff --git a/lib/rules/selector-type-case/index.js b/lib/rules/selector-type-case/index.js index 000c064cdd..c356263386 100644 --- a/lib/rules/selector-type-case/index.js +++ b/lib/rules/selector-type-case/index.js @@ -22,6 +22,7 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } @@ -34,9 +35,11 @@ const rule = function(expectation, options, context) { if (!isStandardSyntaxRule(rule)) { return; } + if (!isStandardSyntaxSelector(selector)) { return; } + if (selectors.some(s => isKeyframeSelector(s))) { return; } @@ -70,6 +73,7 @@ const rule = function(expectation, options, context) { expectedValue + rule.selector.slice(sourceIndex + value.length); } + return; } diff --git a/lib/rules/selector-type-no-unknown/index.js b/lib/rules/selector-type-no-unknown/index.js index c1ba480f87..54e7df0bd2 100644 --- a/lib/rules/selector-type-no-unknown/index.js +++ b/lib/rules/selector-type-no-unknown/index.js @@ -38,6 +38,7 @@ const rule = function(actual, options) { optional: true } ); + if (!validOptions) { return; } @@ -49,9 +50,11 @@ const rule = function(actual, options) { if (!isStandardSyntaxRule(rule)) { return; } + if (!isStandardSyntaxSelector(selector)) { return; } + if (selectors.some(s => isKeyframeSelector(s))) { return; } diff --git a/lib/rules/selectorAttributeOperatorSpaceChecker.js b/lib/rules/selectorAttributeOperatorSpaceChecker.js index e1db7e2d08..7949c8e325 100644 --- a/lib/rules/selectorAttributeOperatorSpaceChecker.js +++ b/lib/rules/selectorAttributeOperatorSpaceChecker.js @@ -10,12 +10,14 @@ module.exports = function(options) { if (!isStandardSyntaxRule(rule)) { return; } + if ( rule.selector.indexOf("[") === -1 || rule.selector.indexOf("=") === -1 ) { return; } + let hasFixed = false; const selector = rule.raws.selector ? rule.raws.selector.raw @@ -41,6 +43,7 @@ module.exports = function(options) { const index = options.checkBeforeOperator ? match.startIndex : match.endIndex - 1; + checkOperator( attributeNodeString, index, @@ -69,8 +72,10 @@ module.exports = function(options) { err: m => { if (options.fix && options.fix(attributeNode)) { hasFixed = true; + return; } + report({ message: m.replace( options.checkBeforeOperator diff --git a/lib/rules/selectorCombinatorSpaceChecker.js b/lib/rules/selectorCombinatorSpaceChecker.js index a35b0a6824..e5924e4ac1 100644 --- a/lib/rules/selectorCombinatorSpaceChecker.js +++ b/lib/rules/selectorCombinatorSpaceChecker.js @@ -6,10 +6,12 @@ const report = require("../utils/report"); module.exports = function(opts) { let hasFixed; + opts.root.walkRules(rule => { if (!isStandardSyntaxRule(rule)) { return; } + hasFixed = false; const selector = rule.raws.selector ? rule.raws.selector.raw @@ -25,6 +27,7 @@ module.exports = function(opts) { if (/\s/.test(node.value)) { return; } + // Check the exist of node in prev of the combinator. // in case some that aren't the first begin with combinators (nesting syntax) if (opts.locationType === "before" && !node.prev()) { @@ -48,6 +51,7 @@ module.exports = function(opts) { }); } ); + if (hasFixed) { if (!rule.raws.selector) { rule.selector = fixedSelector; @@ -65,8 +69,10 @@ module.exports = function(opts) { err: m => { if (opts.fix && opts.fix(combinator)) { hasFixed = true; + return; } + report({ message: m, node, diff --git a/lib/rules/selectorListCommaWhitespaceChecker.js b/lib/rules/selectorListCommaWhitespaceChecker.js index cc925ffd14..cebe2d3ed8 100644 --- a/lib/rules/selectorListCommaWhitespaceChecker.js +++ b/lib/rules/selectorListCommaWhitespaceChecker.js @@ -13,6 +13,7 @@ module.exports = function(opts) { const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector; + styleSearch( { source: selector, @@ -33,6 +34,7 @@ module.exports = function(opts) { if (opts.fix && opts.fix(node, index)) { return; } + report({ message: m, node, diff --git a/lib/rules/shorthand-property-no-redundant-values/index.js b/lib/rules/shorthand-property-no-redundant-values/index.js index 51f40ff6ba..4bdcffd1ea 100644 --- a/lib/rules/shorthand-property-no-redundant-values/index.js +++ b/lib/rules/shorthand-property-no-redundant-values/index.js @@ -91,6 +91,7 @@ function canCondenseToThreeValues(top, right, bottom, left) { const rule = function(actual, secondary, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } diff --git a/lib/rules/string-no-newline/index.js b/lib/rules/string-no-newline/index.js index a189f91002..6a49acd750 100644 --- a/lib/rules/string-no-newline/index.js +++ b/lib/rules/string-no-newline/index.js @@ -18,9 +18,11 @@ const messages = ruleMessages(ruleName, { const rule = function(actual) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual }); + if (!validOptions) { return; } + root.walk(node => { switch (node.type) { case "atrule": @@ -34,6 +36,7 @@ const rule = function(actual) { break; } }); + function checkRule(rule) { // Get out quickly if there are no new line if (!reNewLine.test(rule.selector)) { diff --git a/lib/rules/string-quotes/index.js b/lib/rules/string-quotes/index.js index 070956c35f..2d305f3170 100644 --- a/lib/rules/string-quotes/index.js +++ b/lib/rules/string-quotes/index.js @@ -64,6 +64,7 @@ const rule = function(expectation, secondary, context) { if (!isStandardSyntaxRule(rule)) { return; } + if ( rule.selector.indexOf("[") === -1 || rule.selector.indexOf("=") === -1 @@ -72,6 +73,7 @@ const rule = function(expectation, secondary, context) { } const fixPositions = []; + parseSelector(rule.selector, result, rule, selectorTree => { selectorTree.walkAttributes(attributeNode => { if ( @@ -80,6 +82,7 @@ const rule = function(expectation, secondary, context) { ) { const needsEscape = attributeNode.value.indexOf(correctQuote) !== -1; + if (avoidEscape && needsEscape) { // don't consider this an error return; @@ -104,6 +107,7 @@ const rule = function(expectation, secondary, context) { attributeNode.value.length - // with the length of our quote subtracted erroneousQuote.length; + fixPositions.push(openIndex, closeIndex); } else { report({ @@ -124,6 +128,7 @@ const rule = function(expectation, secondary, context) { function checkDeclOrAtRule(node, value, getIndex) { const fixPositions = []; + // Get out quickly if there are no erroneous quotes if (value.indexOf(erroneousQuote) === -1) { return; @@ -136,16 +141,19 @@ const rule = function(expectation, secondary, context) { valueParser(value).walk(valueNode => { if (valueNode.type === "string" && valueNode.quote === erroneousQuote) { const needsEscape = valueNode.value.indexOf(correctQuote) !== -1; + if (avoidEscape && needsEscape) { // don't consider this an error return; } + const openIndex = valueNode.sourceIndex; // we currently don't fix escapes if (context.fix && !needsEscape) { const closeIndex = openIndex + valueNode.value.length + erroneousQuote.length; + fixPositions.push(openIndex, closeIndex); } else { report({ @@ -177,6 +185,7 @@ function replaceQuote(string, index, replace) { string.substring(index + replace.length) ); } + rule.ruleName = ruleName; rule.messages = messages; module.exports = rule; diff --git a/lib/rules/time-min-milliseconds/index.js b/lib/rules/time-min-milliseconds/index.js index d208c982f0..876eb648bb 100644 --- a/lib/rules/time-min-milliseconds/index.js +++ b/lib/rules/time-min-milliseconds/index.js @@ -21,6 +21,7 @@ const rule = function(minimum) { actual: minimum, possible: _.isNumber }); + if (!validOptions) { return; } diff --git a/lib/rules/unit-blacklist/index.js b/lib/rules/unit-blacklist/index.js index 303ee493e1..af2bccef14 100644 --- a/lib/rules/unit-blacklist/index.js +++ b/lib/rules/unit-blacklist/index.js @@ -28,6 +28,7 @@ const getMediaFeatureName = mediaFeatureNode => { const rule = function(blacklistInput, options) { const blacklist = [].concat(blacklistInput); + return (root, result) => { const validOptions = validateOptions( result, @@ -45,6 +46,7 @@ const rule = function(blacklistInput, options) { } } ); + if (!validOptions) { return; } @@ -85,6 +87,7 @@ const rule = function(blacklistInput, options) { ) { return; } + check( node, getIndex(node), @@ -93,6 +96,7 @@ const rule = function(blacklistInput, options) { options ? options.ignoreMediaFeatureNames : {} ); }); + return; }); } @@ -110,6 +114,7 @@ const rule = function(blacklistInput, options) { ) { return false; } + check( node, getIndex(node), diff --git a/lib/rules/unit-case/index.js b/lib/rules/unit-case/index.js index de486feb4f..89397e1fb1 100644 --- a/lib/rules/unit-case/index.js +++ b/lib/rules/unit-case/index.js @@ -20,12 +20,14 @@ const rule = function(expectation, options, context) { actual: expectation, possible: ["lower", "upper"] }); + if (!validOptions) { return; } function check(node, value, getIndex) { const violations = []; + function processValue(valueNode) { const unit = getUnitFromValueNode(valueNode); @@ -47,10 +49,12 @@ const rule = function(expectation, options, context) { return true; } + const parsedValue = valueParser(value).walk(valueNode => { // Ignore wrong units within `url` function let needFix = false; const value = valueNode.value; + if (valueNode.type === "function" && value.toLowerCase() === "url") { return false; } diff --git a/lib/rules/unit-no-unknown/index.js b/lib/rules/unit-no-unknown/index.js index bdfeb848af..a36ab16d9b 100644 --- a/lib/rules/unit-no-unknown/index.js +++ b/lib/rules/unit-no-unknown/index.js @@ -50,6 +50,7 @@ const rule = function(actual, options) { } const unit = getUnitFromValueNode(valueNode); + if (!unit) { return; } diff --git a/lib/rules/unit-whitelist/index.js b/lib/rules/unit-whitelist/index.js index 2f10744ed3..076936de93 100644 --- a/lib/rules/unit-whitelist/index.js +++ b/lib/rules/unit-whitelist/index.js @@ -19,6 +19,7 @@ const messages = ruleMessages(ruleName, { const rule = function(whitelistInput, options) { const whitelist = [].concat(whitelistInput); + return (root, result) => { const validOptions = validateOptions( result, @@ -35,6 +36,7 @@ const rule = function(whitelistInput, options) { } } ); + if (!validOptions) { return; } diff --git a/lib/rules/value-keyword-case/index.js b/lib/rules/value-keyword-case/index.js index 3f9d97274d..62d9a095ec 100644 --- a/lib/rules/value-keyword-case/index.js +++ b/lib/rules/value-keyword-case/index.js @@ -23,6 +23,7 @@ const messages = ruleMessages(ruleName, { const ignoredCharacters = new Set(["+", "-", "/", "*", "%"]); const mapLowercaseKeywordsToCamelCase = new Map(); + keywordSets.camelCaseKeywords.forEach(func => { mapLowercaseKeywordsToCamelCase.set(func.toLowerCase(), func); }); @@ -45,6 +46,7 @@ const rule = function(expectation, options) { optional: true } ); + if (!validOptions) { return; } @@ -93,12 +95,14 @@ const rule = function(expectation, options) { ) { return; } + if ( prop === "animation-name" && !keywordSets.animationNameKeywords.has(valueLowerCase) ) { return; } + if ( prop === "font" && !keywordSets.fontShorthandKeywords.has(valueLowerCase) && @@ -106,42 +110,49 @@ const rule = function(expectation, options) { ) { return; } + if ( prop === "font-family" && !keywordSets.fontFamilyKeywords.has(valueLowerCase) ) { return; } + if ( prop === "counter-increment" && isCounterIncrementCustomIdentValue(valueLowerCase) ) { return; } + if ( prop === "counter-reset" && isCounterResetCustomIdentValue(valueLowerCase) ) { return; } + if ( prop === "grid-row" && !keywordSets.gridRowKeywords.has(valueLowerCase) ) { return; } + if ( prop === "grid-column" && !keywordSets.gridColumnKeywords.has(valueLowerCase) ) { return; } + if ( prop === "grid-area" && !keywordSets.gridAreaKeywords.has(valueLowerCase) ) { return; } + if ( prop === "list-style" && !keywordSets.listStyleShorthandKeywords.has(valueLowerCase) && @@ -149,6 +160,7 @@ const rule = function(expectation, options) { ) { return; } + if ( prop === "list-style-type" && !keywordSets.listStyleTypeKeywords.has(valueLowerCase) diff --git a/lib/rules/value-list-comma-newline-after/index.js b/lib/rules/value-list-comma-newline-after/index.js index a2f7c47de8..a517c2c992 100644 --- a/lib/rules/value-list-comma-newline-after/index.js +++ b/lib/rules/value-list-comma-newline-after/index.js @@ -18,16 +18,19 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } let fixData; + valueListCommaWhitespaceChecker({ root, result, @@ -36,17 +39,22 @@ const rule = function(expectation, options, context) { fix: context.fix ? (declNode, index) => { const valueIndex = declarationValueIndex(declNode); + if (index <= valueIndex) { return false; } + fixData = fixData || new Map(); const commaIndices = fixData.get(declNode) || []; + commaIndices.push(index); fixData.set(declNode, commaIndices); + return true; } : null }); + if (fixData) { fixData.forEach((commaIndices, decl) => { commaIndices @@ -57,11 +65,13 @@ const rule = function(expectation, options, context) { const valueIndex = index - declarationValueIndex(decl); const beforeValue = value.slice(0, valueIndex + 1); let afterValue = value.slice(valueIndex + 1); + if (expectation.indexOf("always") === 0) { afterValue = context.newline + afterValue; } else if (expectation.indexOf("never-multi-line") === 0) { afterValue = afterValue.replace(/^\s*/, ""); } + if (decl.raws.value) { decl.raws.value.raw = beforeValue + afterValue; } else { diff --git a/lib/rules/value-list-comma-newline-before/index.js b/lib/rules/value-list-comma-newline-before/index.js index 179eb0e25a..3223379e24 100644 --- a/lib/rules/value-list-comma-newline-before/index.js +++ b/lib/rules/value-list-comma-newline-before/index.js @@ -17,11 +17,13 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation) { const checker = whitespaceChecker("newline", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "always-multi-line", "never-multi-line"] }); + if (!validOptions) { return; } diff --git a/lib/rules/value-list-comma-space-after/index.js b/lib/rules/value-list-comma-space-after/index.js index c012ef23b0..dbab341576 100644 --- a/lib/rules/value-list-comma-space-after/index.js +++ b/lib/rules/value-list-comma-space-after/index.js @@ -19,16 +19,19 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } let fixData; + valueListCommaWhitespaceChecker({ root, result, @@ -37,17 +40,22 @@ const rule = function(expectation, options, context) { fix: context.fix ? (declNode, index) => { const valueIndex = declarationValueIndex(declNode); + if (index <= valueIndex) { return false; } + fixData = fixData || new Map(); const commaIndices = fixData.get(declNode) || []; + commaIndices.push(index); fixData.set(declNode, commaIndices); + return true; } : null }); + if (fixData) { fixData.forEach((commaIndices, decl) => { commaIndices.sort((a, b) => b - a).forEach(index => { @@ -55,11 +63,13 @@ const rule = function(expectation, options, context) { const valueIndex = index - declarationValueIndex(decl); const beforeValue = value.slice(0, valueIndex + 1); let afterValue = value.slice(valueIndex + 1); + if (expectation.indexOf("always") === 0) { afterValue = afterValue.replace(/^\s*/, " "); } else if (expectation.indexOf("never") === 0) { afterValue = afterValue.replace(/^\s*/, ""); } + if (decl.raws.value) { decl.raws.value.raw = beforeValue + afterValue; } else { diff --git a/lib/rules/value-list-comma-space-before/index.js b/lib/rules/value-list-comma-space-before/index.js index abd1d64f83..0a6b7c27c5 100644 --- a/lib/rules/value-list-comma-space-before/index.js +++ b/lib/rules/value-list-comma-space-before/index.js @@ -19,16 +19,19 @@ const messages = ruleMessages(ruleName, { const rule = function(expectation, options, context) { const checker = whitespaceChecker("space", expectation, messages); + return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ["always", "never", "always-single-line", "never-single-line"] }); + if (!validOptions) { return; } let fixData; + valueListCommaWhitespaceChecker({ root, result, @@ -37,17 +40,22 @@ const rule = function(expectation, options, context) { fix: context.fix ? (declNode, index) => { const valueIndex = declarationValueIndex(declNode); + if (index <= valueIndex) { return false; } + fixData = fixData || new Map(); const commaIndices = fixData.get(declNode) || []; + commaIndices.push(index); fixData.set(declNode, commaIndices); + return true; } : null }); + if (fixData) { fixData.forEach((commaIndices, decl) => { commaIndices.sort((a, b) => b - a).forEach(index => { @@ -55,11 +63,13 @@ const rule = function(expectation, options, context) { const valueIndex = index - declarationValueIndex(decl); let beforeValue = value.slice(0, valueIndex); const afterValue = value.slice(valueIndex); + if (expectation.indexOf("always") === 0) { beforeValue = beforeValue.replace(/\s*$/, " "); } else if (expectation.indexOf("never") === 0) { beforeValue = beforeValue.replace(/\s*$/, ""); } + if (decl.raws.value) { decl.raws.value.raw = beforeValue + afterValue; } else { diff --git a/lib/rules/value-list-max-empty-lines/index.js b/lib/rules/value-list-max-empty-lines/index.js index c007254dcf..95777f01ab 100644 --- a/lib/rules/value-list-max-empty-lines/index.js +++ b/lib/rules/value-list-max-empty-lines/index.js @@ -21,6 +21,7 @@ const rule = function(max) { actual: max, possible: _.isNumber }); + if (!validOptions) { return; } @@ -39,6 +40,7 @@ const rule = function(max) { ) { // Put index at `\r` if it's CRLF, otherwise leave it at `\n` let index = match.startIndex; + if (value[index - 1] === "\r") { index -= 1; } diff --git a/lib/rules/value-no-vendor-prefix/index.js b/lib/rules/value-no-vendor-prefix/index.js index bd93c3d4c1..75daca3bc0 100644 --- a/lib/rules/value-no-vendor-prefix/index.js +++ b/lib/rules/value-no-vendor-prefix/index.js @@ -64,6 +64,7 @@ const rule = function(actual, options) { const fullIdentifier = /^(-[a-z-]+)\b/i.exec( value.slice(match.startIndex) )[1]; + if (!isAutoprefixable.propertyValue(prop, fullIdentifier)) { return; } diff --git a/lib/rules/valueListCommaWhitespaceChecker.js b/lib/rules/valueListCommaWhitespaceChecker.js index 60e4dc6a30..3699c208c7 100644 --- a/lib/rules/valueListCommaWhitespaceChecker.js +++ b/lib/rules/valueListCommaWhitespaceChecker.js @@ -13,6 +13,7 @@ module.exports = function(opts) { ) { return; } + styleSearch( { source: decl.toString(), @@ -33,6 +34,7 @@ module.exports = function(opts) { if (opts.fix && opts.fix(node, index)) { return; } + report({ message: m, node, diff --git a/lib/standalone.js b/lib/standalone.js index 0dbc6b76b9..41863aecfd 100644 --- a/lib/standalone.js +++ b/lib/standalone.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); const createStylelint = require("./createStylelint"); const createStylelintResult = require("./createStylelintResult"); @@ -51,17 +52,20 @@ module.exports = function( ? ignoreFilePath : path.resolve(process.cwd(), ignoreFilePath); let ignoreText = ""; + try { ignoreText = fs.readFileSync(absoluteIgnoreFilePath, "utf8"); } catch (readError) { if (readError.code !== FILE_NOT_FOUND_ERROR_CODE) throw readError; } + const ignorePattern = options.ignorePattern || []; const ignorer = ignore() .add(ignoreText) .add(ignorePattern); const isValidCode = typeof code === "string"; + if ((!files && !isValidCode) || (files && (code || isValidCode))) { throw new Error( "You must pass stylelint a `files` glob or a `code` string, though not both" @@ -69,8 +73,10 @@ module.exports = function( } let formatterFunction; + if (typeof formatter === "string") { formatterFunction = formatters[formatter]; + if (formatterFunction === undefined) { return Promise.reject( new Error( @@ -101,6 +107,7 @@ module.exports = function( codeFilename !== undefined && !path.isAbsolute(codeFilename) ? path.join(process.cwd(), codeFilename) : codeFilename; + return stylelint ._lintSource({ code, @@ -111,6 +118,7 @@ module.exports = function( return new Promise((resolve, reject) => { if (!absoluteCodeFilename) { reject(); + return; } @@ -152,14 +160,17 @@ module.exports = function( postcssResult.opts.syntax ); } + return returnValue; }); } let fileList = files; + if (typeof fileList === "string") { fileList = [fileList]; } + if (!options.disableDefaultIgnores) { fileList = fileList.concat(ALWAYS_IGNORED_GLOBS.map(glob => "!" + glob)); } @@ -167,6 +178,7 @@ module.exports = function( if (useCache) { const stylelintVersion = pkg.version; const hashOfConfig = hash(`${stylelintVersion}_${JSON.stringify(config)}`); + fileCache = new FileCache(cacheLocation, hashOfConfig); } else { // No need to calculate hash here, we just want to delete cache file. @@ -191,6 +203,7 @@ module.exports = function( const absoluteFilepath = !path.isAbsolute(filePath) ? path.join(cwd, filePath) : path.normalize(filePath); + return absoluteFilepath; }); @@ -202,6 +215,7 @@ module.exports = function( const getStylelintResults = absoluteFilePaths.map(absoluteFilepath => { debug(`Processing ${absoluteFilepath}`); + return stylelint ._lintSource({ filePath: absoluteFilepath @@ -216,10 +230,12 @@ module.exports = function( // If we're fixing, save the file with changed code let fixFile = Promise.resolve(); + if (!postcssResult.stylelint.ignored && options.fix) { const fixedCss = postcssResult.root.toString( postcssResult.opts.syntax ); + if (postcssResult.root.source.input.css !== fixedCss) { fixFile = pify(writeFileAtomic)(absoluteFilepath, fixedCss); } @@ -232,6 +248,7 @@ module.exports = function( .catch(error => { // On any error, we should not cache the lint result fileCache.removeEntry(absoluteFilepath); + return handleError(stylelint, error); }); }); @@ -242,6 +259,7 @@ module.exports = function( if (useCache) { fileCache.reconcile(); } + return prepareReturnValue(stylelintResults); }); @@ -256,9 +274,11 @@ module.exports = function( output: formatterFunction(stylelintResults), results: stylelintResults }; + if (reportNeedlessDisables) { returnValue.needlessDisables = needlessDisables(stylelintResults); } + if (maxWarnings !== undefined) { const foundWarnings = stylelintResults.reduce((count, file) => { return count + file.warnings.length; @@ -268,7 +288,9 @@ module.exports = function( returnValue.maxWarningsExceeded = { maxWarnings, foundWarnings }; } } + debug(`Linting complete in ${Date.now() - startTime}ms`); + return returnValue; } }; diff --git a/lib/testUtils/__tests__/createRuleTester.test.js b/lib/testUtils/__tests__/createRuleTester.test.js index 175c8066f6..40b0284e1a 100644 --- a/lib/testUtils/__tests__/createRuleTester.test.js +++ b/lib/testUtils/__tests__/createRuleTester.test.js @@ -4,11 +4,13 @@ const createRuleTester = require("../createRuleTester"); function createRuleTesterPromise(rule, schema) { const contexts = []; + return new Promise((resolve, reject) => { const ruleTester = createRuleTester((promise, res) => { contexts.push(res); promise.then(resolve, reject); }); + ruleTester(rule, schema); }).then(() => contexts); } diff --git a/lib/testUtils/createRuleTester.js b/lib/testUtils/createRuleTester.js index a5c9161a7d..5f70981bb5 100644 --- a/lib/testUtils/createRuleTester.js +++ b/lib/testUtils/createRuleTester.js @@ -88,16 +88,19 @@ function checkCaseForOnly(caseType, testCase) { if (!testCase.only) { return; } + /* istanbul ignore next */ if (onlyTest) { throw new Error("Cannot use `only` on multiple test cases"); } + onlyTest = { case: testCase, type: caseType }; } module.exports = function(equalityCheck) { return function(rule, schema) { const alreadyHadOnlyTest = !!onlyTest; + if (schema.accept) { schema.accept.forEach(_.partial(checkCaseForOnly, "accept")); } @@ -131,14 +134,17 @@ function processGroup(rule, schema, equalityCheck) { let printableConfig = rulePrimaryOptions ? JSON.stringify(rulePrimaryOptions) : ""; + if (printableConfig && ruleSecondaryOptions) { printableConfig += ", " + JSON.stringify(ruleSecondaryOptions); } function createCaseDescription(code) { let text = `\n> rule: ${ruleName}\n`; + text += `> config: ${printableConfig}\n`; text += `> code: ${JSON.stringify(code)}\n`; + return text; } @@ -163,6 +169,7 @@ function processGroup(rule, schema, equalityCheck) { } const processor = postcss(); + processor.use(assignDisabledRanges); if (schema.preceedingPlugins) { @@ -185,6 +192,7 @@ function processGroup(rule, schema, equalityCheck) { if (!acceptedCase) { return; } + const assertionDescription = spaceJoin( acceptedCase.description, "should be accepted" @@ -192,6 +200,7 @@ function processGroup(rule, schema, equalityCheck) { const resultPromise = postcssProcess(acceptedCase.code) .then(postcssResult => { const warnings = postcssResult.warnings(); + return [ { expected: 0, @@ -214,14 +223,17 @@ function processGroup(rule, schema, equalityCheck) { schema.reject.forEach(rejectedCase => { let completeAssertionDescription = "should register one warning"; let comparisonCount = 1; + if (rejectedCase.line) { comparisonCount++; completeAssertionDescription += ` on line ${rejectedCase.line}`; } + if (rejectedCase.column !== undefined) { comparisonCount++; completeAssertionDescription += ` on column ${rejectedCase.column}`; } + if (rejectedCase.message) { comparisonCount++; completeAssertionDescription += ` with message "${ @@ -255,6 +267,7 @@ function processGroup(rule, schema, equalityCheck) { ) }); } + if (rejectedCase.column !== undefined) { comparisons.push({ expected: rejectedCase.column, @@ -265,6 +278,7 @@ function processGroup(rule, schema, equalityCheck) { ) }); } + if (rejectedCase.message) { comparisons.push({ expected: rejectedCase.message, @@ -275,6 +289,7 @@ function processGroup(rule, schema, equalityCheck) { ) }); } + return comparisons; }) .catch(err => console.log(err.stack)); // eslint-disable-line no-console diff --git a/lib/testUtils/mergeTestDescriptions.js b/lib/testUtils/mergeTestDescriptions.js index 52750fe8fe..709ec7aeee 100644 --- a/lib/testUtils/mergeTestDescriptions.js +++ b/lib/testUtils/mergeTestDescriptions.js @@ -4,6 +4,7 @@ const _ = require("lodash"); module.exports = function() { const mergeWithArgs = [{}]; + Array.from(arguments).forEach(arg => mergeWithArgs.push(arg)); mergeWithArgs.push(mergeCustomizer); diff --git a/lib/utils/FileCache.js b/lib/utils/FileCache.js index ee1dcc7e8f..d2e9d3557b 100644 --- a/lib/utils/FileCache.js +++ b/lib/utils/FileCache.js @@ -13,6 +13,7 @@ function FileCache(cacheLocation /*: ?string */, hashOfConfig /*: ?string */) { const cacheFile = path.resolve( getCacheFile(cacheLocation || DEFAULT_CACHE_LOCATION, process.cwd()) ); + debug(`Cache file is created at ${cacheFile}`); this._fileCache = fileEntryCache.create(cacheFile); this._hashOfConfig = hashOfConfig || DEFAULT_HASH; @@ -25,14 +26,17 @@ FileCache.prototype.hasFileChanged = function(absoluteFilepath) { const meta = descriptor.meta || {}; const changed = descriptor.changed || meta.hashOfConfig !== this._hashOfConfig; + if (!changed) { debug(`Skip linting ${absoluteFilepath}. File hasn't changed.`); } + // Mutate file descriptor object and store config hash to each file. // Running lint with different config should invalidate the cache. if (meta.hashOfConfig !== this._hashOfConfig) { meta.hashOfConfig = this._hashOfConfig; } + return changed; }; diff --git a/lib/utils/__tests__/beforeBlockString.test.js b/lib/utils/__tests__/beforeBlockString.test.js index 27ffe52773..526a9d1b2b 100644 --- a/lib/utils/__tests__/beforeBlockString.test.js +++ b/lib/utils/__tests__/beforeBlockString.test.js @@ -70,12 +70,16 @@ function postcssCheck(options, cssString, parser) { if (typeof options === "undefined") { options = {}; } + if (typeof parser === "undefined") { parser = postcss; } + if (typeof options === "string") { cssString = options; } + const root = parser.parse(cssString); + return beforeBlockString(root.first, options); } diff --git a/lib/utils/__tests__/blockString.test.js b/lib/utils/__tests__/blockString.test.js index ab46b222b8..d09da8130d 100644 --- a/lib/utils/__tests__/blockString.test.js +++ b/lib/utils/__tests__/blockString.test.js @@ -23,5 +23,6 @@ it("blockString at-rules", () => { function postcssCheck(cssString) { const root = postcss.parse(cssString); + return blockString(root.first); } diff --git a/lib/utils/__tests__/checkAgainstRule.test.js b/lib/utils/__tests__/checkAgainstRule.test.js index 02d4820fbd..9fa184f18a 100644 --- a/lib/utils/__tests__/checkAgainstRule.test.js +++ b/lib/utils/__tests__/checkAgainstRule.test.js @@ -9,6 +9,7 @@ describe("checkAgainstRule", () => { const root = postcss.parse("a {} @media {}"); const warnings = []; + checkAgainstRule( { ruleName: "at-rule-name-case", @@ -25,6 +26,7 @@ describe("checkAgainstRule", () => { const root = postcss.parse("a {} @media {}"); const warnings = []; + checkAgainstRule( { ruleName: "at-rule-name-case", @@ -46,6 +48,7 @@ describe("checkAgainstRule", () => { ); const warnings = []; + checkAgainstRule( { ruleName: "at-rule-empty-line-before", diff --git a/lib/utils/__tests__/checkInvalidCLIOptions.test.js b/lib/utils/__tests__/checkInvalidCLIOptions.test.js index 52eb4b90f0..d1a0d1f343 100644 --- a/lib/utils/__tests__/checkInvalidCLIOptions.test.js +++ b/lib/utils/__tests__/checkInvalidCLIOptions.test.js @@ -25,6 +25,7 @@ describe("checkInvalidCLIOptions", () => { o: true }; const { red: r, cyan: c } = chalk; + expect(checkInvalidCLIOptions(allowedOptions, inputOptions)).toBe( `Invalid option ${r('"--fis"')}. Did you mean ${c('"--fix"')}? Invalid option ${r('"--fi"')}. Did you mean ${c('"--fix"')}? diff --git a/lib/utils/__tests__/findAtRuleContext.test.js b/lib/utils/__tests__/findAtRuleContext.test.js index 18c7e058f9..31f4f4c233 100644 --- a/lib/utils/__tests__/findAtRuleContext.test.js +++ b/lib/utils/__tests__/findAtRuleContext.test.js @@ -18,7 +18,7 @@ it("findAtRuleContext", () => { postcss.parse(css).walkRules(rule => { switch (rule.selector) { case "a": - expect(findAtRuleContext(rule)).toBe(null); + expect(findAtRuleContext(rule)).toBeNull(); break; case "b": expect(findAtRuleContext(rule).params).toBe("print"); @@ -27,7 +27,7 @@ it("findAtRuleContext", () => { expect(findAtRuleContext(rule).params).toBe("(min-width: 900px)"); break; case "d": - expect(findAtRuleContext(rule)).toBe(null); + expect(findAtRuleContext(rule)).toBeNull(); break; default: } diff --git a/lib/utils/__tests__/functionArgumentsSearch.test.js b/lib/utils/__tests__/functionArgumentsSearch.test.js index a55a65c91a..261214318a 100644 --- a/lib/utils/__tests__/functionArgumentsSearch.test.js +++ b/lib/utils/__tests__/functionArgumentsSearch.test.js @@ -24,6 +24,7 @@ it("passes function arguments to callback", () => { it("works with nested functions", () => { const calcExpressions = []; const calcExpressionIndexes = []; + functionArgumentsSearch( "4px 5px calc(calc(1px + 2px) + 3px)", "calc", @@ -36,6 +37,7 @@ it("works with nested functions", () => { expect(calcExpressionIndexes).toEqual([13, 18]); const colorFuncValue = "color(red s(- 10%) s( - 10%))"; + functionArgumentsSearch( colorFuncValue, "color", @@ -46,6 +48,7 @@ it("works with nested functions", () => { ); const sExpressions = []; const sExpressionIndexes = []; + functionArgumentsSearch( colorFuncValue, "s", @@ -71,8 +74,8 @@ it("ignores strings", () => { '"calc(1px)"', "calc", (expression, expressionIndex) => { - expect(expression).toBe(null); - expect(expressionIndex).toBe(null); + expect(expression).toBeNull(); + expect(expressionIndex).toBeNull(); } ); }); diff --git a/lib/utils/__tests__/getNextNonSharedLineCommentNode.test.js b/lib/utils/__tests__/getNextNonSharedLineCommentNode.test.js index 84cb57d422..3ee5aee83a 100644 --- a/lib/utils/__tests__/getNextNonSharedLineCommentNode.test.js +++ b/lib/utils/__tests__/getNextNonSharedLineCommentNode.test.js @@ -9,14 +9,16 @@ describe("getNextNonSharedLineCommentNode", () => { const root = postcss.parse(` a {} `); - expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBe(undefined); + + expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBeUndefined(); }); it("returns undefined if there is no next node that's not a shared-line comment", () => { const root = postcss.parse(` a {} /* comment */ `); - expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBe(undefined); + + expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBeUndefined(); }); it("returns the next node if it is not a shared-line comment", () => { @@ -24,6 +26,7 @@ describe("getNextNonSharedLineCommentNode", () => { a {} b {} `); + expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBe(root.nodes[1]); }); @@ -32,6 +35,7 @@ describe("getNextNonSharedLineCommentNode", () => { a {} /* comment */ b {} `); + expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBe(root.nodes[2]); }); @@ -40,6 +44,7 @@ describe("getNextNonSharedLineCommentNode", () => { a {} /* comment */ b {} `); + expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBe(root.nodes[2]); }); @@ -48,6 +53,7 @@ describe("getNextNonSharedLineCommentNode", () => { a {} /* comment */ /* comment */ b {} `); + expect(getNextNonSharedLineCommentNode(root.nodes[0])).toBe(root.nodes[3]); }); }); diff --git a/lib/utils/__tests__/getPreviousNonSharedLineCommentNode.test.js b/lib/utils/__tests__/getPreviousNonSharedLineCommentNode.test.js index ec9d5447f6..1ceab4252e 100644 --- a/lib/utils/__tests__/getPreviousNonSharedLineCommentNode.test.js +++ b/lib/utils/__tests__/getPreviousNonSharedLineCommentNode.test.js @@ -9,14 +9,16 @@ describe("getPreviousNonSharedLineCommentNode", () => { const root = postcss.parse(` a {} `); - expect(getPreviousNonSharedLineCommentNode(root.nodes[0])).toBe(undefined); + + expect(getPreviousNonSharedLineCommentNode(root.nodes[0])).toBeUndefined(); }); it("returns undefined if there is no node before the prior shared-line comment", () => { const root = postcss.parse(` /* comment */ a {} `); - expect(getPreviousNonSharedLineCommentNode(root.nodes[0])).toBe(undefined); + + expect(getPreviousNonSharedLineCommentNode(root.nodes[0])).toBeUndefined(); }); it("returns the previous node if it is not a shared-line comment", () => { @@ -24,6 +26,7 @@ describe("getPreviousNonSharedLineCommentNode", () => { a {} b {} `); + expect(getPreviousNonSharedLineCommentNode(root.nodes[1])).toBe( root.nodes[0] ); @@ -34,6 +37,7 @@ describe("getPreviousNonSharedLineCommentNode", () => { a {} /* comment */ b {} `); + expect(getPreviousNonSharedLineCommentNode(root.nodes[2])).toBe( root.nodes[0] ); @@ -44,6 +48,7 @@ describe("getPreviousNonSharedLineCommentNode", () => { a {} /* comment */ b {} `); + expect(getPreviousNonSharedLineCommentNode(root.nodes[2])).toBe( root.nodes[0] ); @@ -54,6 +59,7 @@ describe("getPreviousNonSharedLineCommentNode", () => { a {} /* comment */ /* comment */ b {} `); + expect(getPreviousNonSharedLineCommentNode(root.nodes[3])).toBe( root.nodes[0] ); diff --git a/lib/utils/__tests__/getSchemeFromUrl.test.js b/lib/utils/__tests__/getSchemeFromUrl.test.js index b00edac787..fee99d8deb 100644 --- a/lib/utils/__tests__/getSchemeFromUrl.test.js +++ b/lib/utils/__tests__/getSchemeFromUrl.test.js @@ -3,12 +3,12 @@ const getSchemeFromUrl = require("../getSchemeFromUrl"); it("getSchemeFromUrl", () => { - expect(getSchemeFromUrl("")).toBe(null); - expect(getSchemeFromUrl(":")).toBe(null); - expect(getSchemeFromUrl("://")).toBe(null); - expect(getSchemeFromUrl("./file.jpg")).toBe(null); - expect(getSchemeFromUrl("/path/to/file.jpg")).toBe(null); - expect(getSchemeFromUrl("//www.example.com/file.jpg")).toBe(null); + expect(getSchemeFromUrl("")).toBeNull(); + expect(getSchemeFromUrl(":")).toBeNull(); + expect(getSchemeFromUrl("://")).toBeNull(); + expect(getSchemeFromUrl("./file.jpg")).toBeNull(); + expect(getSchemeFromUrl("/path/to/file.jpg")).toBeNull(); + expect(getSchemeFromUrl("//www.example.com/file.jpg")).toBeNull(); expect(getSchemeFromUrl("http://www.example.com/file.jpg")).toBe("http"); expect(getSchemeFromUrl("HTTPS://www.example.com/file.jpg")).toBe("https"); expect( diff --git a/lib/utils/__tests__/getUnitFromValueNode.test.js b/lib/utils/__tests__/getUnitFromValueNode.test.js index 9d7d353daa..7a155646d2 100644 --- a/lib/utils/__tests__/getUnitFromValueNode.test.js +++ b/lib/utils/__tests__/getUnitFromValueNode.test.js @@ -4,8 +4,8 @@ const getUnitFromValueNode = require("../getUnitFromValueNode"); const valueParser = require("postcss-value-parser"); it("getUnitFromValueNode", () => { - expect(getUnitFromValueNode()).toBe(null); - expect(getUnitFromValueNode({})).toBe(null); + expect(getUnitFromValueNode()).toBeNull(); + expect(getUnitFromValueNode({})).toBeNull(); expect(getUnitFromValueNode(valueParser("1px").nodes[0])).toBe("px"); expect(getUnitFromValueNode(valueParser("1pX").nodes[0])).toBe("pX"); expect(getUnitFromValueNode(valueParser("1PX").nodes[0])).toBe("PX"); @@ -14,16 +14,16 @@ it("getUnitFromValueNode", () => { expect(getUnitFromValueNode(valueParser("100").nodes[0])).toBe(""); expect(getUnitFromValueNode(valueParser("0\\0").nodes[0])).toBe(""); expect(getUnitFromValueNode(valueParser("10px\\9").nodes[0])).toBe("px"); - expect(getUnitFromValueNode(valueParser("#fff").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("#000").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser('"100"').nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser(" ").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("/").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("+").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("word").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("px").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("url()").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("$variable").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("${$variable}").nodes[0])).toBe(null); - expect(getUnitFromValueNode(valueParser("@variable").nodes[0])).toBe(null); + expect(getUnitFromValueNode(valueParser("#fff").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("#000").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser('"100"').nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser(" ").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("/").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("+").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("word").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("px").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("url()").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("$variable").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("${$variable}").nodes[0])).toBeNull(); + expect(getUnitFromValueNode(valueParser("@variable").nodes[0])).toBeNull(); }); diff --git a/lib/utils/__tests__/hasBlock.test.js b/lib/utils/__tests__/hasBlock.test.js index 09d25c3a00..55f8daa6bc 100644 --- a/lib/utils/__tests__/hasBlock.test.js +++ b/lib/utils/__tests__/hasBlock.test.js @@ -44,5 +44,6 @@ it("hasBlock", () => { function postcssCheck(cssString) { const root = postcss.parse(cssString); + return hasBlock(root.first); } diff --git a/lib/utils/__tests__/hasEmptyBlock.test.js b/lib/utils/__tests__/hasEmptyBlock.test.js index f73ffae1e4..a76109071a 100644 --- a/lib/utils/__tests__/hasEmptyBlock.test.js +++ b/lib/utils/__tests__/hasEmptyBlock.test.js @@ -43,5 +43,6 @@ it("hasEmptyBlock", () => { function postcssCheck(cssString) { const root = postcss.parse(cssString); + return hasEmptyBlock(root.first); } diff --git a/lib/utils/__tests__/isAfterComment.test.js b/lib/utils/__tests__/isAfterComment.test.js index 4c9b9d71c3..cc64bd1f19 100644 --- a/lib/utils/__tests__/isAfterComment.test.js +++ b/lib/utils/__tests__/isAfterComment.test.js @@ -10,6 +10,7 @@ describe("isAfterComment", () => { /* comment */ foo {} `); + expect(isAfterComment(root.nodes[1])).toBe(true); }); @@ -19,6 +20,7 @@ describe("isAfterComment", () => { and more comment */ foo {} `); + expect(isAfterComment(root.nodes[1])).toBe(true); }); @@ -27,6 +29,7 @@ describe("isAfterComment", () => { bar {} /* comment */ foo {} `); + expect(isAfterComment(root.nodes[2])).toBe(false); }); @@ -35,6 +38,7 @@ describe("isAfterComment", () => { bar {} foo {} `); + expect(isAfterComment(root.nodes[1])).toBe(false); }); @@ -42,6 +46,7 @@ describe("isAfterComment", () => { const root = postcss.parse(` foo {} `); + expect(isAfterComment(root.nodes[0])).toBe(false); }); }); diff --git a/lib/utils/__tests__/isAfterSingleLineComment.test.js b/lib/utils/__tests__/isAfterSingleLineComment.test.js index dbc6abd902..c5e2be0736 100644 --- a/lib/utils/__tests__/isAfterSingleLineComment.test.js +++ b/lib/utils/__tests__/isAfterSingleLineComment.test.js @@ -10,6 +10,7 @@ describe("isAfterSingleLineComment", () => { /* comment */ foo {} `); + expect(isAfterSingleLineComment(root.nodes[1])).toBe(true); }); @@ -19,6 +20,7 @@ describe("isAfterSingleLineComment", () => { and more comment */ foo {} `); + expect(isAfterSingleLineComment(root.nodes[1])).toBe(false); }); @@ -27,6 +29,7 @@ describe("isAfterSingleLineComment", () => { bar {} /* comment */ foo {} `); + expect(isAfterSingleLineComment(root.nodes[2])).toBe(false); }); @@ -36,6 +39,7 @@ describe("isAfterSingleLineComment", () => { foo {} } `); + expect(isAfterSingleLineComment(root.nodes[0].nodes[1])).toBe(false); }); @@ -44,6 +48,7 @@ describe("isAfterSingleLineComment", () => { bar {} foo {} `); + expect(isAfterSingleLineComment(root.nodes[1])).toBe(false); }); @@ -51,6 +56,7 @@ describe("isAfterSingleLineComment", () => { const root = postcss.parse(` foo {} `); + expect(isAfterSingleLineComment(root.nodes[0])).toBe(false); }); }); diff --git a/lib/utils/__tests__/isBlocklessAtRuleAfterBlocklessAtRule.test.js b/lib/utils/__tests__/isBlocklessAtRuleAfterBlocklessAtRule.test.js index e46357018a..be80b97484 100644 --- a/lib/utils/__tests__/isBlocklessAtRuleAfterBlocklessAtRule.test.js +++ b/lib/utils/__tests__/isBlocklessAtRuleAfterBlocklessAtRule.test.js @@ -9,6 +9,7 @@ describe("isBlocklessAtRuleAfterBlocklessAtRule", () => { const root = postcss.parse(` @import 'x.css' `); + expect(isBlocklessAtRuleAfterBlocklessAtRule(root.nodes[0])).toBe(false); }); @@ -16,6 +17,7 @@ describe("isBlocklessAtRuleAfterBlocklessAtRule", () => { const root = postcss.parse(` foo {} `); + expect(isBlocklessAtRuleAfterBlocklessAtRule(root.nodes[0])).toBe(false); }); @@ -24,6 +26,7 @@ describe("isBlocklessAtRuleAfterBlocklessAtRule", () => { foo {} @import 'x.css'; `); + expect(isBlocklessAtRuleAfterBlocklessAtRule(root.nodes[1])).toBe(false); }); @@ -32,6 +35,7 @@ describe("isBlocklessAtRuleAfterBlocklessAtRule", () => { @media {} @import 'x.css'; `); + expect(isBlocklessAtRuleAfterBlocklessAtRule(root.nodes[1])).toBe(false); }); @@ -40,6 +44,7 @@ describe("isBlocklessAtRuleAfterBlocklessAtRule", () => { @import 'y.css'; @media {} `); + expect(isBlocklessAtRuleAfterBlocklessAtRule(root.nodes[1])).toBe(false); }); @@ -48,6 +53,7 @@ describe("isBlocklessAtRuleAfterBlocklessAtRule", () => { @import 'y.css'; @import 'x.css'; `); + expect(isBlocklessAtRuleAfterBlocklessAtRule(root.nodes[1])).toBe(true); }); @@ -56,6 +62,7 @@ describe("isBlocklessAtRuleAfterBlocklessAtRule", () => { @import 'y.css'; /* comment */ @import 'x.css'; `); + expect(isBlocklessAtRuleAfterBlocklessAtRule(root.nodes[2])).toBe(true); }); }); diff --git a/lib/utils/__tests__/isBlocklessAtRuleAfterSameNameBlocklessAtRule.test.js b/lib/utils/__tests__/isBlocklessAtRuleAfterSameNameBlocklessAtRule.test.js index 4f455e89cb..906d0c444a 100644 --- a/lib/utils/__tests__/isBlocklessAtRuleAfterSameNameBlocklessAtRule.test.js +++ b/lib/utils/__tests__/isBlocklessAtRuleAfterSameNameBlocklessAtRule.test.js @@ -9,6 +9,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { const root = postcss.parse(` @import 'x.css' `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[0])).toBe( false ); @@ -18,6 +19,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { const root = postcss.parse(` foo {} `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[0])).toBe( false ); @@ -28,6 +30,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { foo {} @import 'x.css'; `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[1])).toBe( false ); @@ -38,6 +41,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { @media {} @import 'x.css'; `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[1])).toBe( false ); @@ -48,6 +52,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { @import 'y.css'; @media {} `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[1])).toBe( false ); @@ -58,6 +63,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { @extract 'y.css'; @import 'x.css'; `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[1])).toBe( false ); @@ -68,6 +74,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { @import 'y.css'; @import 'x.css'; `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[1])).toBe( true ); @@ -78,6 +85,7 @@ describe("isBlocklessAtRuleAfterSameNameBlocklessAtRule", () => { @import 'y.css'; /* comment */ @import 'x.css'; `); + expect(isBlocklessAtRuleAfterSameNameBlocklessAtRule(root.nodes[2])).toBe( true ); diff --git a/lib/utils/__tests__/isFirstNested.test.js b/lib/utils/__tests__/isFirstNested.test.js index 034c608aaa..ff8d27765b 100644 --- a/lib/utils/__tests__/isFirstNested.test.js +++ b/lib/utils/__tests__/isFirstNested.test.js @@ -9,6 +9,7 @@ describe("isFirstNested", () => { const root = postcss.parse(` a { color: 'pink'; } `); + expect(isFirstNested(root.nodes[0])).toBe(false); }); diff --git a/lib/utils/__tests__/isSharedLineComment.test.js b/lib/utils/__tests__/isSharedLineComment.test.js index 156d6212ed..6c3e7df095 100644 --- a/lib/utils/__tests__/isSharedLineComment.test.js +++ b/lib/utils/__tests__/isSharedLineComment.test.js @@ -9,6 +9,7 @@ describe("isSharedLineComment", () => { const root = postcss.parse(` /* comment */ `); + expect(isSharedLineComment(root.nodes[0])).toBe(false); }); @@ -17,6 +18,7 @@ describe("isSharedLineComment", () => { /* comment */ a {} `); + expect(isSharedLineComment(root.nodes[0])).toBe(false); }); @@ -25,6 +27,7 @@ describe("isSharedLineComment", () => { a {} /* comment */ `); + expect(isSharedLineComment(root.nodes[1])).toBe(false); }); @@ -32,6 +35,7 @@ describe("isSharedLineComment", () => { const root = postcss.parse(` /* comment */ a {} `); + expect(isSharedLineComment(root.nodes[0])).toBe(true); }); @@ -39,6 +43,7 @@ describe("isSharedLineComment", () => { const root = postcss.parse(` a {} /* comment */ `); + expect(isSharedLineComment(root.nodes[1])).toBe(true); }); @@ -46,6 +51,7 @@ describe("isSharedLineComment", () => { const root = postcss.parse(` a {} b {} `); + expect(isSharedLineComment(root.nodes[0])).toBe(false); expect(isSharedLineComment(root.nodes[1])).toBe(false); }); @@ -56,6 +62,7 @@ describe("isSharedLineComment", () => { color: pink; } `); + expect(isSharedLineComment(root.nodes[0])).toBe(true); }); @@ -65,6 +72,7 @@ describe("isSharedLineComment", () => { color: pink; } `); + root.walkComments(comment => { expect(isSharedLineComment(comment)).toBe(true); }); @@ -76,6 +84,7 @@ describe("isSharedLineComment", () => { a { color: pink; } } `); + expect(isSharedLineComment(root.nodes[0])).toBe(true); }); @@ -85,6 +94,7 @@ describe("isSharedLineComment", () => { a { color: pink; } } `); + root.walkComments(comment => { expect(isSharedLineComment(comment)).toBe(true); }); @@ -94,6 +104,7 @@ describe("isSharedLineComment", () => { const root = postcss.parse(` /* comment */ /* comment */ `); + expect(isSharedLineComment(root.nodes[0])).toBe(false); }); @@ -101,6 +112,7 @@ describe("isSharedLineComment", () => { const root = postcss.parse(` /* comment */ /* comment */ a {} `); + expect(isSharedLineComment(root.nodes[0])).toBe(true); }); @@ -110,6 +122,7 @@ describe("isSharedLineComment", () => { color: pink; } /* comment */ `); + root.walkComments(comment => { expect(isSharedLineComment(comment)).toBe(true); }); @@ -123,6 +136,7 @@ describe("isSharedLineComment", () => { 0; /* comment */ } `); + root.walkComments(comment => { expect(isSharedLineComment(comment)).toBe(true); }); @@ -136,6 +150,7 @@ describe("isSharedLineComment", () => { 0; } `); + root.walkComments(comment => { expect(isSharedLineComment(comment)).toBe(true); }); diff --git a/lib/utils/__tests__/isStandardSyntaxAtRule.test.js b/lib/utils/__tests__/isStandardSyntaxAtRule.test.js index 59c4377afe..4dd193c69c 100644 --- a/lib/utils/__tests__/isStandardSyntaxAtRule.test.js +++ b/lib/utils/__tests__/isStandardSyntaxAtRule.test.js @@ -69,20 +69,24 @@ describe("isStandardSyntaxAtRule", () => { it("ignore `@content` inside mixins newline", () => { const sass = "@mixin mixin()\n @content"; + sassAtRules(sass, atRule => { if (atRule.name === "mixin") { return; } + expect(isStandardSyntaxAtRule(atRule)).toBeFalsy(); }); }); it("ignore `@content` inside mixins space", () => { const scss = "@mixin mixin() { @content; };"; + scssAtRules(scss, atRule => { if (atRule.name === "mixin") { return; } + expect(isStandardSyntaxAtRule(atRule)).toBeFalsy(); }); }); @@ -90,6 +94,7 @@ describe("isStandardSyntaxAtRule", () => { it("ignore passing rulesets to mixins", () => { const less = "@detached-ruleset: { background: red; }; .top { @detached-ruleset(); }"; + lessAtRules(less, atRule => { expect(isStandardSyntaxAtRule(atRule)).toBeFalsy(); }); diff --git a/lib/utils/__tests__/isStandardSyntaxFunction.test.js b/lib/utils/__tests__/isStandardSyntaxFunction.test.js index f420469f42..08525b5048 100644 --- a/lib/utils/__tests__/isStandardSyntaxFunction.test.js +++ b/lib/utils/__tests__/isStandardSyntaxFunction.test.js @@ -36,6 +36,7 @@ function funcs(css, cb) { if (valueNode.type !== "function") { return; } + cb(valueNode); }); }); diff --git a/lib/utils/__tests__/isStandardSyntaxMediaFeature.test.js b/lib/utils/__tests__/isStandardSyntaxMediaFeature.test.js index f34874ec0d..db141dac89 100644 --- a/lib/utils/__tests__/isStandardSyntaxMediaFeature.test.js +++ b/lib/utils/__tests__/isStandardSyntaxMediaFeature.test.js @@ -5,34 +5,42 @@ const isStandardSyntaxMediaFeature = require("../isStandardSyntaxMediaFeature"); describe("isStandardSyntaxMediaFeature", () => { it("prefix on range features", () => { const css = "(min-width: 10px)"; + expect(isStandardSyntaxMediaFeature(css)).toBeTruthy(); }); it("range context", () => { const css = "(width <= 3rem)"; + expect(isStandardSyntaxMediaFeature(css)).toBeTruthy(); }); it("nested range context", () => { const css = "(400px < width < 1000px)"; + expect(isStandardSyntaxMediaFeature(css)).toBeTruthy(); }); it("boolean context", () => { const css = "(color)"; + expect(isStandardSyntaxMediaFeature(css)).toBeTruthy(); }); it("complex value", () => { const css = "(min-width: calc(100% - 20px))"; + expect(isStandardSyntaxMediaFeature(css)).toBeFalsy(); }); it("complex SCSS value", () => { const css = "(min-width: ($var - 10px))"; + expect(isStandardSyntaxMediaFeature(css)).toBeFalsy(); }); it("SCSS interpolation", () => { const css = "(min-width#{$value}: 10px)"; + expect(isStandardSyntaxMediaFeature(css)).toBeFalsy(); }); it("Less interpolation", () => { const css = "(@{value}min-width : 10px)"; + expect(isStandardSyntaxMediaFeature(css)).toBeFalsy(); }); }); diff --git a/lib/utils/__tests__/nextNonCommentNode.test.js b/lib/utils/__tests__/nextNonCommentNode.test.js index c7296dfef6..e3200872d0 100644 --- a/lib/utils/__tests__/nextNonCommentNode.test.js +++ b/lib/utils/__tests__/nextNonCommentNode.test.js @@ -23,6 +23,7 @@ describe("nextNonCommentNode", () => { if (rule.selector === "a") { aNode = rule; } + if (rule.selector === "b") { bNode = rule; } @@ -36,12 +37,13 @@ describe("nextNonCommentNode", () => { if (rule.selector === "a") { aNode = rule; } + if (rule.selector === "b") { bNode = rule; } }); - expect(nextNonCommentNode(bNode.next())).toBe(null); + expect(nextNonCommentNode(bNode.next())).toBeNull(); }); it("next node is a declaration preceded by a comment", () => { @@ -62,6 +64,6 @@ describe("nextNonCommentNode", () => { colorNode = rule; }); - expect(nextNonCommentNode(colorNode.next())).toBe(null); + expect(nextNonCommentNode(colorNode.next())).toBeNull(); }); }); diff --git a/lib/utils/__tests__/nodeContextLookup.test.js b/lib/utils/__tests__/nodeContextLookup.test.js index e6c68c61d1..86683aad87 100644 --- a/lib/utils/__tests__/nodeContextLookup.test.js +++ b/lib/utils/__tests__/nodeContextLookup.test.js @@ -14,6 +14,7 @@ it("nodeContextLookup checking media context", () => { }) .then(result => { const rulesBySelector = {}; + result.root.walkRules(rule => { rulesBySelector[rule.selector] = rule; }); diff --git a/lib/utils/__tests__/report.test.js b/lib/utils/__tests__/report.test.js index 8c93862e5d..a994b6516e 100644 --- a/lib/utils/__tests__/report.test.js +++ b/lib/utils/__tests__/report.test.js @@ -13,8 +13,10 @@ it("without disabledRanges", () => { positionBy: () => ({ line: 2 }) } }; + report(v); const spyArgs = v.result.warn.mock.calls[0]; + expect(spyArgs[0]).toBe("bar"); expect(spyArgs[1].node).toBe(v.node); }); @@ -35,8 +37,10 @@ it("with irrelevant general disabledRange", () => { positionBy: () => ({ line: 2 }) } }; + report(v); const spyArgs = v.result.warn.mock.calls[0]; + expect(spyArgs[0]).toBe("bar"); expect(spyArgs[1].node).toBe(v.node); }); @@ -57,6 +61,7 @@ it("with relevant general disabledRange", () => { positionBy: () => ({ line: 6 }) } }; + report(v); expect(v.result.warn).toHaveBeenCalledTimes(0); }); @@ -78,8 +83,10 @@ it("with irrelevant rule-specific disabledRange", () => { positionBy: () => ({ line: 6 }) } }; + report(v); const spyArgs = v.result.warn.mock.calls[0]; + expect(spyArgs[0]).toBe("bar"); expect(spyArgs[1].node).toBe(v.node); }); @@ -101,6 +108,7 @@ it("with relevant rule-specific disabledRange", () => { positionBy: () => ({ line: 6 }) } }; + report(v); expect(v.result.warn).toHaveBeenCalledTimes(0); }); @@ -121,6 +129,7 @@ it("with relevant general disabledRange, among others", () => { positionBy: () => ({ line: 6 }) } }; + report(v); expect(v.result.warn).toHaveBeenCalledTimes(0); }); @@ -145,6 +154,7 @@ it("with relevant rule-specific disabledRange, among others", () => { positionBy: () => ({ line: 6 }) } }; + report(v); expect(v.result.warn).toHaveBeenCalledTimes(0); }); @@ -166,6 +176,7 @@ it("with quiet mode on and rule severity of 'warning'", () => { positionBy: () => ({ line: 6 }) } }; + report(v); expect(v.result.warn).toHaveBeenCalledTimes(0); }); @@ -187,6 +198,7 @@ it("with quiet mode on and rule severity of 'error'", () => { positionBy: () => ({ line: 6 }) } }; + report(v); expect(v.result.warn).toHaveBeenCalledTimes(1); }); diff --git a/lib/utils/__tests__/validateOptions.test.js b/lib/utils/__tests__/validateOptions.test.js index 2e970f38d1..d8df260db1 100644 --- a/lib/utils/__tests__/validateOptions.test.js +++ b/lib/utils/__tests__/validateOptions.test.js @@ -233,6 +233,7 @@ it("validateOptions for multiple actual/possible pairs, checking return value", actual: "three" } ); + expect(validOptions).toBe(true); expect(result.warn).toHaveBeenCalledTimes(0); @@ -248,6 +249,7 @@ it("validateOptions for multiple actual/possible pairs, checking return value", actual: "threee" } ); + expect(invalidOptions).toBe(false); expect(result.warn.mock.calls[0]).toHaveLength(2); expect(result.warn.mock.calls[0][0]).toEqual( @@ -264,12 +266,15 @@ describe("validateOptions with a function for 'possible'", () => { if (x === "bar") { return true; } + if (!Array.isArray(x)) { return false; } + if (x.every(item => typeof item === "string" || !!item.properties)) { return true; } + return false; }; @@ -278,6 +283,7 @@ describe("validateOptions with a function for 'possible'", () => { possible: schema, actual: "bar" }); + expect(validExplicitlyNamedString).toBe(true); expect(result.warn).toHaveBeenCalledTimes(0); }); @@ -287,6 +293,7 @@ describe("validateOptions with a function for 'possible'", () => { possible: schema, actual: ["one", "two", "three"] }); + expect(validArrayOfStrings).toBe(true); expect(result.warn).toHaveBeenCalledTimes(0); }); @@ -296,6 +303,7 @@ describe("validateOptions with a function for 'possible'", () => { possible: schema, actual: [{ properties: ["one"] }, { properties: ["two", "three"] }] }); + expect(validArrayOfObjects).toBe(true); expect(result.warn).toHaveBeenCalledTimes(0); }); @@ -309,6 +317,7 @@ describe("validateOptions with a function for 'possible'", () => { "four" ] }); + expect(validArrayOfObjectsAndStrings).toBe(true); expect(result.warn).toHaveBeenCalledTimes(0); }); @@ -318,6 +327,7 @@ describe("validateOptions with a function for 'possible'", () => { possible: schema, actual: { properties: ["one"] } }); + expect(invalidObject).toBe(false); expect(result.warn.mock.calls[0]).toHaveLength(2); expect(result.warn.mock.calls[0][0]).toEqual( @@ -328,6 +338,7 @@ describe("validateOptions with a function for 'possible'", () => { it("validateOptions for null instead of array", () => { const result = mockResult(); + validateOptions(result, "no-dancing", { actual: null, possible: [v => typeof v === "string"] @@ -337,6 +348,7 @@ it("validateOptions for null instead of array", () => { it("validateOptions for arrayed null instead of array", () => { const result = mockResult(); + validateOptions(result, "no-dancing", { actual: [null], possible: [v => typeof v === "string"] diff --git a/lib/utils/addEmptyLineAfter.js b/lib/utils/addEmptyLineAfter.js index 4c1ed1b406..97a6bd3526 100644 --- a/lib/utils/addEmptyLineAfter.js +++ b/lib/utils/addEmptyLineAfter.js @@ -9,11 +9,13 @@ function addEmptyLineAfter( newline /*: '\n' | '\r\n'*/ ) /*: postcss$node*/ { const after = _.last(node.raws.after.split(";")); + if (!/\r?\n/.test(after)) { node.raws.after = node.raws.after + _.repeat(newline, 2); } else { node.raws.after = node.raws.after.replace(/(\r?\n)/, `${newline}$1`); } + return node; } diff --git a/lib/utils/addEmptyLineBefore.js b/lib/utils/addEmptyLineBefore.js index 69a7102854..5a782e8409 100644 --- a/lib/utils/addEmptyLineBefore.js +++ b/lib/utils/addEmptyLineBefore.js @@ -13,6 +13,7 @@ function addEmptyLineBefore( } else { node.raws.before = node.raws.before.replace(/(\r?\n)/, `${newline}$1`); } + return node; } diff --git a/lib/utils/atRuleParamIndex.js b/lib/utils/atRuleParamIndex.js index ff02dd8368..6480a23a5e 100644 --- a/lib/utils/atRuleParamIndex.js +++ b/lib/utils/atRuleParamIndex.js @@ -1,10 +1,13 @@ /* @flow */ "use strict"; + module.exports = function(atRule /*: postcss$atRule*/) /*: number*/ { // Initial 1 is for the `@` let index = 1 + atRule.name.length; + if (atRule.raws.afterName) { index += atRule.raws.afterName.length; } + return index; }; diff --git a/lib/utils/beforeBlockString.js b/lib/utils/beforeBlockString.js index 87ade7d8ac..b2f4e98cc1 100644 --- a/lib/utils/beforeBlockString.js +++ b/lib/utils/beforeBlockString.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + module.exports = function( statement /*: Object*/, options /*:: ?: Object*/ @@ -13,6 +14,7 @@ module.exports = function( if (statement.type === "rule") { rule = statement; } + if (statement.type === "atrule") { atRule = statement; } @@ -26,9 +28,11 @@ module.exports = function( if (!options.noRawBefore) { result += before; } + if (rule) { result += rule.selector; } + if (atRule) { result += "@" + atRule.name + (atRule.raws.afterName || "") + atRule.params; } diff --git a/lib/utils/blockString.js b/lib/utils/blockString.js index 19eddb20e3..7da86899f1 100644 --- a/lib/utils/blockString.js +++ b/lib/utils/blockString.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const beforeBlockString = require("./beforeBlockString"); const hasBlock = require("./hasBlock"); const rawNodeString = require("./rawNodeString"); @@ -19,5 +20,6 @@ module.exports = function( if (!hasBlock(statement)) { return false; } + return rawNodeString(statement).slice(beforeBlockString(statement).length); }; diff --git a/lib/utils/blurComments.js b/lib/utils/blurComments.js index f8b42e5a45..a37fe476af 100644 --- a/lib/utils/blurComments.js +++ b/lib/utils/blurComments.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + module.exports = function(source /*: string*/) /*: string*/ { const blurChar /*: string*/ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "`"; diff --git a/lib/utils/blurFunctionArguments.js b/lib/utils/blurFunctionArguments.js index cf7779ac68..e37d76e029 100644 --- a/lib/utils/blurFunctionArguments.js +++ b/lib/utils/blurFunctionArguments.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); const balancedMatch = require("balanced-match"); @@ -28,6 +29,7 @@ module.exports = function( const nameWithParen = `${functionName.toLowerCase()}(`; const lowerCaseSource = source.toLowerCase(); + if (!_.includes(lowerCaseSource, nameWithParen)) { return source; } @@ -36,6 +38,7 @@ module.exports = function( let result = source; let searchStartIndex = 0; + while (lowerCaseSource.indexOf(nameWithParen, searchStartIndex) !== -1) { const openingParenIndex = lowerCaseSource.indexOf(nameWithParen, searchStartIndex) + @@ -44,11 +47,13 @@ module.exports = function( balancedMatch("(", ")", lowerCaseSource.slice(openingParenIndex)).end + openingParenIndex; const argumentsLength = closingParenIndex - openingParenIndex - 1; + result = result.slice(0, openingParenIndex + 1) + _.repeat(blurChar, argumentsLength) + result.slice(closingParenIndex); searchStartIndex = closingParenIndex; } + return result; }; diff --git a/lib/utils/blurInterpolation.js b/lib/utils/blurInterpolation.js index 2f704be8ae..fc00d65dd8 100644 --- a/lib/utils/blurInterpolation.js +++ b/lib/utils/blurInterpolation.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + module.exports = function( source /*: string, optionalblurChar ?: string*/ ) /*: string*/ { diff --git a/lib/utils/checkAgainstRule.js b/lib/utils/checkAgainstRule.js index 62c42735ab..44bda6cf2d 100644 --- a/lib/utils/checkAgainstRule.js +++ b/lib/utils/checkAgainstRule.js @@ -20,13 +20,18 @@ module.exports = function( throw new Error( "checkAgainstRule requires an options object with 'ruleName', 'ruleSettings', and 'root' properties" ); + if (!callback) throw new Error("checkAgainstRule requires a callback"); + if (!options.ruleName) throw new Error("checkAgainstRule requires a 'ruleName' option"); + if (!rules.includes(options.ruleName)) throw new Error(`Rule '${options.ruleName}' does not exist`); + if (!options.ruleSettings) throw new Error("checkAgainstRule requires a 'ruleSettings' option"); + if (!options.root) throw new Error("checkAgainstRule requires a 'root' option"); @@ -34,11 +39,13 @@ module.exports = function( options.ruleSettings, options.ruleName ); + if (!settings) { return; } const tmpPostcssResult = new Result(); + requireRule(options.ruleName)(settings[0], settings[1], {})( options.root, tmpPostcssResult diff --git a/lib/utils/checkInvalidCLIOptions.js b/lib/utils/checkInvalidCLIOptions.js index 78cbe79d49..8ebe877835 100644 --- a/lib/utils/checkInvalidCLIOptions.js +++ b/lib/utils/checkInvalidCLIOptions.js @@ -10,25 +10,32 @@ const leven = require("leven"); const buildAllowedOptions = (allowedOptions /*: allowedOptionsType */) => { let options = Object.keys(allowedOptions); + options = options.reduce((opts, opt) => { const alias = allowedOptions[opt].alias; + if (alias) { opts.push(alias); } + return opts; }, options); options.sort(); + return options; }; const suggest = (all /*: string[]*/, invalid /*: string*/) => { const maxThreshold = 10; + for (let threshold = 1; threshold <= maxThreshold; threshold++) { const suggestion = all.find(option => leven(option, invalid) <= threshold); + if (suggestion) { return suggestion; } } + return null; }; @@ -37,9 +44,11 @@ const cliOption = (opt /*: string*/) => const buildMessageLine = (invalid /*: string*/, suggestion /*: ?string*/) => { let line = `Invalid option ${chalk.red(cliOption(invalid))}.`; + if (suggestion) { line += ` Did you mean ${chalk.cyan(cliOption(suggestion))}?`; } + return line + EOL; }; @@ -56,6 +65,7 @@ module.exports = function checkInvalidCLIOptions( // NOTE: No suggestion for shortcut options because it's too difficult const suggestion = invalid.length >= 2 ? suggest(allOptions, invalid) : null; + return msg + buildMessageLine(invalid, suggestion); }, ""); }; diff --git a/lib/utils/configurationError.js b/lib/utils/configurationError.js index 808b7e340e..97294bb877 100644 --- a/lib/utils/configurationError.js +++ b/lib/utils/configurationError.js @@ -6,6 +6,8 @@ */ module.exports = function(text /*: string */) /* Object */ { const err /*: Object*/ = new Error(text); + err.code = 78; + return err; }; diff --git a/lib/utils/containsString.js b/lib/utils/containsString.js index be502908b8..513a0eb1e4 100644 --- a/lib/utils/containsString.js +++ b/lib/utils/containsString.js @@ -18,15 +18,18 @@ module.exports = function containsString( for (const comparisonItem of comparison) { const testResult = testAgainstString(input, comparisonItem); + if (testResult) { return testResult; } } + return false; }; function testAgainstString(value, comparison) { if (!comparison) return false; + if (comparison[0] === "/" && comparison[comparison.length - 1] === "/") { return false; } diff --git a/lib/utils/declarationValueIndex.js b/lib/utils/declarationValueIndex.js index 3b1d99648e..975d639ac5 100644 --- a/lib/utils/declarationValueIndex.js +++ b/lib/utils/declarationValueIndex.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const _ = require("lodash"); /** diff --git a/lib/utils/eachDeclarationBlock.js b/lib/utils/eachDeclarationBlock.js index 07f8b71086..480201520f 100644 --- a/lib/utils/eachDeclarationBlock.js +++ b/lib/utils/eachDeclarationBlock.js @@ -1,4 +1,5 @@ "use strict"; + // In order to accommodate nested blocks (postcss-nested), // we need to run a shallow loop (instead of eachDecl() or eachRule(), // which loop recursively) and allow each nested block to accumulate @@ -19,10 +20,12 @@ module.exports = function(root /*: Object */, cb /* Function */) { each(node); } }); + if (decls.length) { cb(decls.forEach.bind(decls)); } } } + each(root); }; diff --git a/lib/utils/findAnimationName.js b/lib/utils/findAnimationName.js index 1285aad624..791b89f7e0 100644 --- a/lib/utils/findAnimationName.js +++ b/lib/utils/findAnimationName.js @@ -29,6 +29,7 @@ module.exports = function findAnimationName( if (valueNode.type === "function") { return false; } + if (valueNode.type !== "word") { return; } @@ -39,16 +40,20 @@ module.exports = function findAnimationName( if (!isStandardSyntaxValue(valueLowerCase)) { return; } + // Ignore variables if (isVariable(valueLowerCase)) { return; } + // Ignore keywords for other font parts if (keywordSets.animationShorthandKeywords.has(valueLowerCase)) { return; } + // Ignore numbers with units const unit = getUnitFromValueNode(valueNode); + if (unit || unit === "") { return; } diff --git a/lib/utils/findAtRuleContext.js b/lib/utils/findAtRuleContext.js index 455533db1c..53823bdbd4 100644 --- a/lib/utils/findAtRuleContext.js +++ b/lib/utils/findAtRuleContext.js @@ -14,8 +14,10 @@ module.exports = function findAtRuleContext( if (parent.type === "root") { return null; } + if (parent.type === "atrule") { return parent; } + return findAtRuleContext(parent); }; diff --git a/lib/utils/findFontFamily.js b/lib/utils/findFontFamily.js index 1e4f5bd33a..0394a25e97 100644 --- a/lib/utils/findFontFamily.js +++ b/lib/utils/findFontFamily.js @@ -44,6 +44,7 @@ module.exports = function findFontFamily( if (valueNode.type === "function") { return false; } + if (!nodeTypesToCheck.has(valueNode.type)) { return; } @@ -96,6 +97,7 @@ module.exports = function findFontFamily( ) { needMergeNodesByValue = true; mergeCharacters = valueNode.value; + return; } else if (valueNode.type === "space" || valueNode.type === "div") { return; diff --git a/lib/utils/findListStyleType.js b/lib/utils/findListStyleType.js index 2d03008fe4..71a8d438ae 100644 --- a/lib/utils/findListStyleType.js +++ b/lib/utils/findListStyleType.js @@ -30,6 +30,7 @@ module.exports = function findListStyleType( if (valueNode.type === "function") { return false; } + if (valueNode.type !== "word") { return; } @@ -40,10 +41,12 @@ module.exports = function findListStyleType( if (!isStandardSyntaxValue(valueLowerCase)) { return; } + // Ignore variables if (isVariable(valueLowerCase)) { return; } + // Ignore keywords for other font parts if ( keywordSets.listStylePositionKeywords.has(valueLowerCase) || diff --git a/lib/utils/functionArgumentsSearch.js b/lib/utils/functionArgumentsSearch.js index 6c120f3a9d..14681adb82 100644 --- a/lib/utils/functionArgumentsSearch.js +++ b/lib/utils/functionArgumentsSearch.js @@ -29,11 +29,13 @@ module.exports = function( if (source[match.endIndex] !== "(") { return; } + const parensMatch = balancedMatch( "(", ")", source.substr(match.startIndex) ); + callback(parensMatch.body, match.endIndex + 1); } ); diff --git a/lib/utils/getCacheFile.js b/lib/utils/getCacheFile.js index 026c133674..65fd33b2fb 100644 --- a/lib/utils/getCacheFile.js +++ b/lib/utils/getCacheFile.js @@ -5,6 +5,7 @@ const fs = require("fs"); const hash = require("./hash"); const path = require("path"); + /** * Return the cacheFile to be used by stylelint, based on whether the provided parameter is * a directory or looks like a directory (ends in `path.sep`), in which case the file diff --git a/lib/utils/getFormatterOptionsText.js b/lib/utils/getFormatterOptionsText.js index 7b2217a636..86917f4458 100644 --- a/lib/utils/getFormatterOptionsText.js +++ b/lib/utils/getFormatterOptionsText.js @@ -12,8 +12,10 @@ module.exports = function getFormatterOptionsText( .map(name => `"${name}"`) .join(", ") .value(); + if (options.useOr) { output = output.replace(/, ([a-z"]+)$/u, " or $1"); } + return output; }; diff --git a/lib/utils/getModulePath.js b/lib/utils/getModulePath.js index 4f65b2a47b..a05e47c031 100644 --- a/lib/utils/getModulePath.js +++ b/lib/utils/getModulePath.js @@ -11,13 +11,16 @@ module.exports = function( // First try to resolve from the provided directory, // then try to resolve from process.cwd. let path = resolveFrom.silent(basedir, lookup); + if (!path) { path = resolveFrom.silent(process.cwd(), lookup); } + if (!path) { throw configurationError( `Could not find "${lookup}". Do you need a \`configBasedir\`?` ); } + return path; }; diff --git a/lib/utils/getSchemeFromUrl.js b/lib/utils/getSchemeFromUrl.js index 0d8ecb6f94..d5fe6516ef 100644 --- a/lib/utils/getSchemeFromUrl.js +++ b/lib/utils/getSchemeFromUrl.js @@ -11,6 +11,7 @@ const parse = require("url").parse; module.exports = function(urlString /*: string*/) /*: ?string*/ { const url = parse(urlString); const protocol = url.protocol; + if (protocol === null || typeof protocol === "undefined") { return null; } diff --git a/lib/utils/hasInterpolation.js b/lib/utils/hasInterpolation.js index dfcd26839b..7432daed00 100644 --- a/lib/utils/hasInterpolation.js +++ b/lib/utils/hasInterpolation.js @@ -5,6 +5,7 @@ const hasLessInterpolation = require("../utils/hasLessInterpolation"); const hasPsvInterpolation = require("../utils/hasPsvInterpolation"); const hasScssInterpolation = require("../utils/hasScssInterpolation"); const hasTplInterpolation = require("../utils/hasTplInterpolation"); + /** * Check whether a string has interpolation * diff --git a/lib/utils/hash.js b/lib/utils/hash.js index 6afe5f7819..fe20e23c82 100644 --- a/lib/utils/hash.js +++ b/lib/utils/hash.js @@ -1,6 +1,7 @@ /* @flow */ "use strict"; + const murmur = require("imurmurhash"); /** @@ -8,9 +9,7 @@ const murmur = require("imurmurhash"); * @param {string} str the string to hash * @returns {string} the hash */ -module.exports = function hash( - str /*: string */ -) /*: string */ { +module.exports = function hash(str /*: string */) /*: string */ { return murmur(str) .result() .toString(36); diff --git a/lib/utils/isAutoprefixable.js b/lib/utils/isAutoprefixable.js index 5d53459c5d..6ce5427ba8 100644 --- a/lib/utils/isAutoprefixable.js +++ b/lib/utils/isAutoprefixable.js @@ -48,6 +48,7 @@ module.exports = { const possiblePrefixableValues = prefixes.remove[prop.toLowerCase()] && prefixes.remove[prop.toLowerCase()].values; + return ( possiblePrefixableValues && possiblePrefixableValues.some(valueObj => { diff --git a/lib/utils/isBlocklessAtRuleAfterBlocklessAtRule.js b/lib/utils/isBlocklessAtRuleAfterBlocklessAtRule.js index 9069cdb260..3049b42aba 100644 --- a/lib/utils/isBlocklessAtRuleAfterBlocklessAtRule.js +++ b/lib/utils/isBlocklessAtRuleAfterBlocklessAtRule.js @@ -10,6 +10,7 @@ module.exports = function(atRule /*: postcss$atRule*/) /*: boolean*/ { } const previousNode = getPreviousNonSharedLineCommentNode(atRule); + if (previousNode === undefined) { return false; } diff --git a/lib/utils/isCustomElement.js b/lib/utils/isCustomElement.js index e20b3df980..bace02d9a3 100644 --- a/lib/utils/isCustomElement.js +++ b/lib/utils/isCustomElement.js @@ -23,15 +23,19 @@ module.exports = function(selector /*: string*/) /*: boolean*/ { if (selectorLowerCase !== selector) { return false; } + if (svgTags.indexOf(selectorLowerCase) !== -1) { return false; } + if (htmlTags.indexOf(selectorLowerCase) !== -1) { return false; } + if (keywordSets.nonStandardHtmlTags.has(selectorLowerCase)) { return false; } + if (mathMLTags.indexOf(selectorLowerCase) !== -1) { return false; } diff --git a/lib/utils/isFirstNodeOfRoot.js b/lib/utils/isFirstNodeOfRoot.js index 00f422e566..10784b0665 100644 --- a/lib/utils/isFirstNodeOfRoot.js +++ b/lib/utils/isFirstNodeOfRoot.js @@ -3,5 +3,6 @@ module.exports = function(node /*: postcss$node*/) /*: boolean*/ { const parentNode = node.parent; + return parentNode.type === "root" && node === parentNode.first; }; diff --git a/lib/utils/isOnlyWhitespace.js b/lib/utils/isOnlyWhitespace.js index 5af4780f9d..60b551ef15 100644 --- a/lib/utils/isOnlyWhitespace.js +++ b/lib/utils/isOnlyWhitespace.js @@ -8,11 +8,13 @@ const isWhitespace = require("./isWhitespace"); */ module.exports = function(input /*: string*/) /*: boolean*/ { let isOnlyWhitespace = true; + for (let i = 0, l = input.length; i < l; i++) { if (!isWhitespace(input[i])) { isOnlyWhitespace = false; break; } } + return isOnlyWhitespace; }; diff --git a/lib/utils/isSharedLineComment.js b/lib/utils/isSharedLineComment.js index ae9e230607..ba1bb095d3 100644 --- a/lib/utils/isSharedLineComment.js +++ b/lib/utils/isSharedLineComment.js @@ -19,16 +19,19 @@ module.exports = function isSharedLineComment( const previousNonSharedLineCommentNode = getPreviousNonSharedLineCommentNode( node ); + if (nodesShareLines(previousNonSharedLineCommentNode, node)) { return true; } const nextNonSharedLineCommentNode = getNextNonSharedLineCommentNode(node); + if (nodesShareLines(node, nextNonSharedLineCommentNode)) { return true; } const parentNode = node.parent; + if ( parentNode !== undefined && parentNode.type !== "root" && diff --git a/lib/utils/isStandardSyntaxMediaFeature.js b/lib/utils/isStandardSyntaxMediaFeature.js index dbcfb9cc1d..af5a1616ee 100644 --- a/lib/utils/isStandardSyntaxMediaFeature.js +++ b/lib/utils/isStandardSyntaxMediaFeature.js @@ -2,6 +2,7 @@ "use strict"; const hasInterpolation = require("../utils/hasInterpolation"); + /** * Check whether a media feature is standard */ diff --git a/lib/utils/isStandardSyntaxProperty.js b/lib/utils/isStandardSyntaxProperty.js index c90bcfce30..e4a2c8340c 100644 --- a/lib/utils/isStandardSyntaxProperty.js +++ b/lib/utils/isStandardSyntaxProperty.js @@ -3,6 +3,7 @@ const _ = require("lodash"); const hasInterpolation = require("../utils/hasInterpolation"); + /** * Check whether a property is standard */ diff --git a/lib/utils/isStandardSyntaxSelector.js b/lib/utils/isStandardSyntaxSelector.js index c443c80f75..a60785b25b 100644 --- a/lib/utils/isStandardSyntaxSelector.js +++ b/lib/utils/isStandardSyntaxSelector.js @@ -2,6 +2,7 @@ "use strict"; const hasInterpolation = require("../utils/hasInterpolation"); + /** * Check whether a selector is standard */ diff --git a/lib/utils/isStandardSyntaxTypeSelector.js b/lib/utils/isStandardSyntaxTypeSelector.js index 12dce0872a..eac93208e2 100644 --- a/lib/utils/isStandardSyntaxTypeSelector.js +++ b/lib/utils/isStandardSyntaxTypeSelector.js @@ -22,6 +22,7 @@ module.exports = function(node /*: Object*/) /*: boolean*/ { if (parentValue) { const normalisedParentName = parentValue.toLowerCase().replace(/:+/, ""); + if ( parentType === "pseudo" && (keywordSets.aNPlusBNotationPseudoClasses.has(normalisedParentName) || diff --git a/lib/utils/isStandardSyntaxValue.js b/lib/utils/isStandardSyntaxValue.js index 63cf88c879..513afa5f03 100644 --- a/lib/utils/isStandardSyntaxValue.js +++ b/lib/utils/isStandardSyntaxValue.js @@ -2,6 +2,7 @@ "use strict"; const hasInterpolation = require("../utils/hasInterpolation"); + /** * Check whether a value is standard */ diff --git a/lib/utils/isValidFontSize.js b/lib/utils/isValidFontSize.js index 55cdba8b70..8b078062f4 100644 --- a/lib/utils/isValidFontSize.js +++ b/lib/utils/isValidFontSize.js @@ -17,6 +17,7 @@ module.exports = function(word /*: string*/) /*: boolean*/ { } const numberUnit = valueParser.unit(word); + if (!numberUnit) { return false; } @@ -26,6 +27,7 @@ module.exports = function(word /*: string*/) /*: boolean*/ { if (unit === "%") { return true; } + if (keywordSets.lengthUnits.has(unit.toLowerCase())) { return true; } diff --git a/lib/utils/matchesStringOrRegExp.js b/lib/utils/matchesStringOrRegExp.js index 29b1d56ae7..9baaa69a1d 100644 --- a/lib/utils/matchesStringOrRegExp.js +++ b/lib/utils/matchesStringOrRegExp.js @@ -19,6 +19,7 @@ module.exports = function matchesStringOrRegExp( for (const inputItem of input) { const testResult = testAgainstStringOrRegExpOrArray(inputItem, comparison); + if (testResult) { return testResult; } @@ -34,10 +35,12 @@ function testAgainstStringOrRegExpOrArray(value, comparison) { for (const comparisonItem of comparison) { const testResult = testAgainstStringOrRegExp(value, comparisonItem); + if (testResult) { return testResult; } } + return false; } @@ -67,6 +70,7 @@ function testAgainstStringOrRegExp(value, comparison) { const valueMatches = hasCaseInsensitiveFlag ? new RegExp(comparison.slice(1, -2), "i").test(value) : new RegExp(comparison.slice(1, -1)).test(value); + return valueMatches ? { match: value, pattern: comparison } : false; } diff --git a/lib/utils/nodeContextLookup.js b/lib/utils/nodeContextLookup.js index 68dce3a6a2..e3a3fa6a34 100644 --- a/lib/utils/nodeContextLookup.js +++ b/lib/utils/nodeContextLookup.js @@ -31,5 +31,6 @@ function creativeGetMap(someMap, someThing) { if (!someMap.has(someThing)) { someMap.set(someThing, new Map()); } + return someMap.get(someThing); } diff --git a/lib/utils/rawNodeString.js b/lib/utils/rawNodeString.js index 2018a157b6..e87e402c7d 100644 --- a/lib/utils/rawNodeString.js +++ b/lib/utils/rawNodeString.js @@ -6,9 +6,12 @@ */ module.exports = function(node /*: Object*/) /*: string*/ { let result = ""; + if (node.raws.before) { result += node.raws.before; } + result += node.toString(); + return result; }; diff --git a/lib/utils/report.js b/lib/utils/report.js index 04ca8d4cb2..f0763a23ff 100644 --- a/lib/utils/report.js +++ b/lib/utils/report.js @@ -52,6 +52,7 @@ module.exports = function( const ranges = result.stylelint.disabledRanges[ruleName] || result.stylelint.disabledRanges.all; + for (const range of ranges) { if ( // If the violation is within a disabledRange, @@ -80,12 +81,15 @@ module.exports = function( severity, rule: ruleName }; + if (node) { warningProperties.node = node; } + if (index) { warningProperties.index = index; } + if (word) { warningProperties.word = word; } @@ -95,5 +99,6 @@ module.exports = function( ["customMessages", ruleName], message ); + result.warn(warningMessage, warningProperties); }; diff --git a/lib/utils/ruleMessages.js b/lib/utils/ruleMessages.js index 24467df286..95dcf3c85d 100644 --- a/lib/utils/ruleMessages.js +++ b/lib/utils/ruleMessages.js @@ -17,6 +17,7 @@ module.exports = function( ) /*: Object*/ { return Object.keys(messages).reduce((newMessages, messageId) => { const messageText = messages[messageId]; + if (typeof messageText === "string") { newMessages[messageId] = `${messageText} (${ruleName})`; } else { @@ -24,6 +25,7 @@ module.exports = function( return `${messageText.apply(null, arguments)} (${ruleName})`; }; } + return newMessages; }, {}); }; diff --git a/lib/utils/validateOptions.js b/lib/utils/validateOptions.js index 4f8410a251..2560e6a4e3 100644 --- a/lib/utils/validateOptions.js +++ b/lib/utils/validateOptions.js @@ -69,7 +69,9 @@ function validate(opts, ruleName, complain) { if (nothingPossible || optional) { return; } + complain(`Expected option value for rule "${ruleName}"`); + return; } else if (nothingPossible) { if (optional) { @@ -81,6 +83,7 @@ function validate(opts, ruleName, complain) { } complain(`Unexpected option value "${actual}" for rule "${ruleName}"`); + return; } @@ -91,6 +94,7 @@ function validate(opts, ruleName, complain) { `Invalid option "${JSON.stringify(actual)}" for rule ${ruleName}` ); } + return; } @@ -100,8 +104,10 @@ function validate(opts, ruleName, complain) { if (isValid(possible, a)) { return; } + complain(`Invalid option value "${a}" for rule "${ruleName}"`); }); + return; } @@ -112,6 +118,7 @@ function validate(opts, ruleName, complain) { actual )} for rule "${ruleName}": ` + "should be an object" ); + return; } @@ -122,14 +129,17 @@ function validate(opts, ruleName, complain) { if (!possible[optionName]) { complain(`Invalid option name "${optionName}" for rule "${ruleName}"`); + return; } const actualOptionValue = actual[optionName]; + [].concat(actualOptionValue).forEach(a => { if (isValid(possible[optionName], a)) { return; } + complain( `Invalid value "${a}" for option "${optionName}" of rule "${ruleName}"` ); @@ -139,11 +149,14 @@ function validate(opts, ruleName, complain) { function isValid(possible, actual) { const possibleList = [].concat(possible); + for (let i = 0, l = possibleList.length; i < l; i++) { const possibility = possibleList[i]; + if (typeof possibility === "function" && possibility(actual)) { return true; } + if (actual === possibility) { return true; } diff --git a/lib/utils/whitespaceChecker.js b/lib/utils/whitespaceChecker.js index 7b62e2cc14..5480b5de99 100644 --- a/lib/utils/whitespaceChecker.js +++ b/lib/utils/whitespaceChecker.js @@ -94,6 +94,7 @@ module.exports = function( onlyOneChar, allowIndentation }; + switch (expectation) { case "always": expectBefore(); @@ -105,24 +106,28 @@ module.exports = function( if (!isSingleLineString(lineCheckStr || source)) { return; } + expectBefore(messages.expectedBeforeSingleLine); break; case "never-single-line": if (!isSingleLineString(lineCheckStr || source)) { return; } + rejectBefore(messages.rejectedBeforeSingleLine); break; case "always-multi-line": if (isSingleLineString(lineCheckStr || source)) { return; } + expectBefore(messages.expectedBeforeMultiLine); break; case "never-multi-line": if (isSingleLineString(lineCheckStr || source)) { return; } + rejectBefore(messages.rejectedBeforeMultiLine); break; default: @@ -146,6 +151,7 @@ module.exports = function( args.onlyOneChar === undefined ? false : args.onlyOneChar; activeArgs = { source, index, err, errTarget, onlyOneChar }; + switch (expectation) { case "always": expectAfter(); @@ -157,24 +163,28 @@ module.exports = function( if (!isSingleLineString(lineCheckStr || source)) { return; } + expectAfter(messages.expectedAfterSingleLine); break; case "never-single-line": if (!isSingleLineString(lineCheckStr || source)) { return; } + rejectAfter(messages.rejectedAfterSingleLine); break; case "always-multi-line": if (isSingleLineString(lineCheckStr || source)) { return; } + expectAfter(messages.expectedAfterMultiLine); break; case "never-multi-line": if (isSingleLineString(lineCheckStr || source)) { return; } + rejectAfter(messages.rejectedAfterMultiLine); break; default: @@ -186,13 +196,12 @@ module.exports = function( before(Object.assign({}, obj, { allowIndentation: true })); } - function expectBefore( - messageFunc /*:: ?: Function*/ - ) { + function expectBefore(messageFunc /*:: ?: Function*/) { messageFunc = messageFunc || messages.expectedBefore; if (activeArgs.allowIndentation) { expectBeforeAllowingIndentation(messageFunc); + return; } @@ -218,9 +227,7 @@ module.exports = function( ); } - function expectBeforeAllowingIndentation( - messageFunc /*:: ?: Function*/ - ) { + function expectBeforeAllowingIndentation(messageFunc /*:: ?: Function*/) { messageFunc = messageFunc || messages.expectedBefore; const _activeArgs2 = activeArgs; const source = _activeArgs2.source; @@ -233,21 +240,22 @@ module.exports = function( } })(); let i = index - 1; + while (source[i] !== expectedChar) { if (source[i] === "\t" || source[i] === " ") { i--; continue; } + err( messageFunc(activeArgs.errTarget ? activeArgs.errTarget : source[index]) ); + return; } } - function rejectBefore( - messageFunc /*:: ?: Function*/ - ) { + function rejectBefore(messageFunc /*:: ?: Function*/) { messageFunc = messageFunc || messages.rejectedBefore; const _activeArgs3 = activeArgs; const source = _activeArgs3.source; @@ -266,9 +274,7 @@ module.exports = function( after(Object.assign({}, obj, { onlyOneChar: true })); } - function expectAfter( - messageFunc /*:: ?: Function*/ - ) { + function expectAfter(messageFunc /*:: ?: Function*/) { messageFunc = messageFunc || messages.expectedAfter; const _activeArgs4 = activeArgs; const source = _activeArgs4.source; @@ -308,9 +314,7 @@ module.exports = function( ); } - function rejectAfter( - messageFunc /*:: ?: Function*/ - ) { + function rejectAfter(messageFunc /*:: ?: Function*/) { messageFunc = messageFunc || messages.rejectedAfter; const _activeArgs5 = activeArgs; const source = _activeArgs5.source; diff --git a/package.json b/package.json index e7e76a5943..63833f6474 100644 --- a/package.json +++ b/package.json @@ -93,8 +93,8 @@ "cp-file": "^6.0.0", "d3-queue": "^3.0.7", "del": "^3.0.0", - "eslint": "~5.1.0", - "eslint-config-stylelint": "~8.3.0", + "eslint": "~5.7.0", + "eslint-config-stylelint": "~9.0.0", "file-exists-promise": "^1.0.2", "flow-bin": "^0.83.0", "flow-typed": "^2.5.1", diff --git a/scripts/benchmark-rule.js b/scripts/benchmark-rule.js index e26fe0e90f..d19fe2d91b 100644 --- a/scripts/benchmark-rule.js +++ b/scripts/benchmark-rule.js @@ -19,6 +19,7 @@ const CSS_URL = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css"; let parsedOptions = ruleOptions; + /* eslint-disable eqeqeq */ if ( ruleOptions[0] === "[" || @@ -28,6 +29,7 @@ if ( ) { parsedOptions = JSON.parse(ruleOptions); } + /* eslint-enable eqeqeq */ const rule = rules[ruleName].apply(null, normalizeRuleSettings(parsedOptions)); const processor = postcss().use(rule); @@ -51,6 +53,7 @@ request(CSS_URL, (error, response, body) => { }); let firstTime = true; + function benchFn(css, done) { processor .process(css) @@ -64,6 +67,7 @@ function benchFn(css, done) { }); console.log(`${chalk.bold("Warnings")}: ${result.warnings().length}`); } + done(); }) .catch(err => { diff --git a/system-tests/004/004.test.js b/system-tests/004/004.test.js index 5cd2bd1d26..046b78f3d7 100644 --- a/system-tests/004/004.test.js +++ b/system-tests/004/004.test.js @@ -1,5 +1,6 @@ /* @flow */ "use strict"; + const path = require("path"); const spawn = require("child_process").spawn; @@ -20,6 +21,7 @@ it("004", done => { }); let stdout = ""; + cliProcess.stdout.on("data", data => (stdout += data)); cliProcess.on("close", function(code) { diff --git a/system-tests/004/stylelint.config.js b/system-tests/004/stylelint.config.js index 7c75214c48..af11f407e2 100644 --- a/system-tests/004/stylelint.config.js +++ b/system-tests/004/stylelint.config.js @@ -1,2 +1,3 @@ "use strict"; + module.exports = require("./someUnknownFile.js"); // eslint-disable-line node/no-missing-require diff --git a/system-tests/cli/cli.test.js b/system-tests/cli/cli.test.js index 68f40b8bd8..fcecc562b5 100644 --- a/system-tests/cli/cli.test.js +++ b/system-tests/cli/cli.test.js @@ -1,5 +1,6 @@ /* eslint no-console: off */ "use strict"; + const cli = require("../../lib/cli"); const path = require("path"); const pkg = require("../../package.json"); @@ -25,6 +26,7 @@ describe("CLI", () => { process.exitCode = undefined; console.log = jest.fn(); process.stdout.write = jest.fn(); + if (parseInt(process.versions.node) < 7) { // https://github.com/sindresorhus/get-stdin/issues/13 process.nextTick(() => { @@ -38,6 +40,7 @@ describe("CLI", () => { expect(process.exitCode).toBe(2); expect(console.log.mock.calls).toHaveLength(1); const lastCallArgs = console.log.mock.calls.pop(); + expect(lastCallArgs).toHaveLength(1); expect(lastCallArgs.pop()).toMatch("Usage: stylelint [input] [options]"); }); @@ -48,6 +51,7 @@ describe("CLI", () => { expect(process.exitCode).toBe(0); expect(console.log.mock.calls).toHaveLength(1); const lastCallArgs = console.log.mock.calls.pop(); + expect(lastCallArgs).toHaveLength(1); expect(lastCallArgs.pop()).toMatch("Usage: stylelint [input] [options]"); }); @@ -55,9 +59,10 @@ describe("CLI", () => { it("--version", () => { return Promise.resolve(cli(["--version"])).then(() => { - expect(process.exitCode).toBe(undefined); + expect(process.exitCode).toBeUndefined(); expect(console.log.mock.calls).toHaveLength(1); const lastCallArgs = console.log.mock.calls.pop(); + expect(lastCallArgs).toHaveLength(1); expect(lastCallArgs.pop()).toMatch(pkg.version); }); @@ -72,7 +77,7 @@ describe("CLI", () => { path.join(__dirname, "stylesheet.css") ]) ).then(() => { - expect(process.exitCode).toBe(undefined); + expect(process.exitCode).toBeUndefined(); expect(process.stdout.write).toHaveBeenCalledTimes(1); expect(process.stdout.write).toHaveBeenLastCalledWith( JSON.stringify( diff --git a/system-tests/fix/fix.test.js b/system-tests/fix/fix.test.js index b4329e89f9..775f6ff57d 100644 --- a/system-tests/fix/fix.test.js +++ b/system-tests/fix/fix.test.js @@ -17,6 +17,7 @@ describe("fix", () => { beforeEach(() => { tmpDir = os.tmpdir(); stylesheetPath = path.join(tmpDir, `stylesheet-${_.uniqueId()}.css`); + return cpFile(path.join(__dirname, "stylesheet.css"), stylesheetPath); }); @@ -36,6 +37,7 @@ describe("fix", () => { const cleanedResults = output.results.map(r => Object.assign({}, r, { source: "stylesheet.css" }) ); + expect(systemTestUtils.prepResults(cleanedResults)).toMatchSnapshot(); }); }); @@ -49,6 +51,7 @@ describe("fix", () => { }) .then(output => { const result = output.results[0]._postcssResult; + expect(result.root.toString(result.opts.syntax)).toMatchSnapshot(); }); }); @@ -62,6 +65,7 @@ describe("fix", () => { }) .then(output => { const result = output.results[0]._postcssResult; + return pify(fs.readFile)(stylesheetPath, "utf8").then(fileContent => { expect(fileContent).toBe(result.root.toString(result.opts.syntax)); }); diff --git a/system-tests/systemTestUtils.js b/system-tests/systemTestUtils.js index c4c4b06cf6..6a74a48eab 100644 --- a/system-tests/systemTestUtils.js +++ b/system-tests/systemTestUtils.js @@ -14,6 +14,7 @@ function caseStylesheetGlob(caseNumber) { function caseConfig(caseNumber, ext) { ext = ext || "json"; + return caseFilePath(caseNumber, `config.${ext}`); }