Skip to content

Commit

Permalink
Run rules for each root of document (#3875)
Browse files Browse the repository at this point in the history
* Run rules for each root of document

* Add tests for no-duplicate-selectors and no-descending-specificity
  • Loading branch information
gucong3000 authored and jeddy3 committed Jan 12, 2019
1 parent 762df47 commit e57e893
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 306 deletions.
34 changes: 18 additions & 16 deletions lib/lintSource.js
Expand Up @@ -168,16 +168,11 @@ function lintPostcssResult(
postcssResult.stylelint.customMessages = {};
postcssResult.stylelint.quiet = config.quiet;

const newlineMatch = postcssResult.root
.toResult({
stringifier: postcssResult.opts.syntax
})
.css.match(/\r?\n/);
const postcssDoc = postcssResult.root;
const newlineMatch = postcssDoc.source.input.css.match(/\r?\n/);
const newline = newlineMatch ? newlineMatch[0] : getOsEol();

const postcssRoot = postcssResult.root;

assignDisabledRanges(postcssRoot, postcssResult);
assignDisabledRanges(postcssDoc, postcssResult);

if (
stylelint._options.reportNeedlessDisables ||
Expand All @@ -186,6 +181,11 @@ function lintPostcssResult(
postcssResult.stylelint.ignoreDisables = true;
}

const postcssRoots =
postcssDoc.constructor.name === "Document"
? postcssDoc.nodes
: [postcssDoc];

// Promises for the rules. Although the rule code runs synchronously now,
// the use of Promises makes it compatible with the possibility of async
// rules down the line.
Expand Down Expand Up @@ -223,14 +223,16 @@ function lintPostcssResult(
"message"
);

const performRule = Promise.resolve().then(() => {
return ruleFunction(primaryOption, secondaryOptions, {
fix: stylelint._options.fix,
newline
})(postcssRoot, postcssResult);
});

performRules.push(performRule);
performRules.push(
Promise.all(
postcssRoots.map(postcssRoot =>
ruleFunction(primaryOption, secondaryOptions, {
fix: stylelint._options.fix,
newline
})(postcssRoot, postcssResult)
)
)
);
});

return Promise.all(performRules);
Expand Down
37 changes: 15 additions & 22 deletions lib/rules/max-empty-lines/index.js
@@ -1,7 +1,6 @@
"use strict";

const _ = require("lodash");
const eachRoot = require("../../utils/eachRoot");
const optionsMatches = require("../../utils/optionsMatches");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
Expand Down Expand Up @@ -42,26 +41,20 @@ const rule = function(max, options) {

const ignoreComments = optionsMatches(options, "ignore", "comments");

const document = root.constructor.name === "Document" ? root : undefined;
emptyLines = 0;
lastIndex = -1;
const rootString = root.toString();

eachRoot(root, checkRoot);

function checkRoot(root) {
emptyLines = 0;
lastIndex = -1;
const rootString = root.toString();

styleSearch(
{
source: rootString,
target: /\r\n/.test(rootString) ? "\r\n" : "\n",
comments: ignoreComments ? "skip" : "check"
},
match => {
checkMatch(rootString, match.startIndex, match.endIndex, root);
}
);
}
styleSearch(
{
source: rootString,
target: /\r\n/.test(rootString) ? "\r\n" : "\n",
comments: ignoreComments ? "skip" : "check"
},
match => {
checkMatch(rootString, match.startIndex, match.endIndex, root);
}
);

function checkMatch(source, matchStartIndex, matchEndIndex, node) {
const eof = matchEndIndex === source.length ? true : false;
Expand Down Expand Up @@ -94,7 +87,7 @@ const rule = function(max, options) {
if (eof && max) {
emptyLines++;

if (emptyLines > max && isEofNode(document, node)) {
if (emptyLines > max && isEofNode(result.root, node)) {
report({
message: messages.expected(max),
node,
Expand All @@ -114,7 +107,7 @@ const rule = function(max, options) {
* @param {Root} root the root node of css
*/
function isEofNode(document, root) {
if (!document) {
if (!document || document.constructor.name !== "Document") {
return true;
}

Expand Down
26 changes: 10 additions & 16 deletions lib/rules/max-line-length/index.js
@@ -1,7 +1,6 @@
"use strict";

const _ = require("lodash");
const eachRoot = require("../../utils/eachRoot");
const execall = require("execall");
const optionsMatches = require("../../utils/optionsMatches");
const report = require("../../utils/report");
Expand All @@ -18,7 +17,7 @@ const messages = ruleMessages(ruleName, {
}`
});

const rule = function(maxLength, options) {
const rule = function(maxLength, options, context) {
return (root, result) => {
const validOptions = validateOptions(
result,
Expand All @@ -43,20 +42,15 @@ const rule = function(maxLength, options) {

const ignoreNonComments = optionsMatches(options, "ignore", "non-comments");
const ignoreComments = optionsMatches(options, "ignore", "comments");

eachRoot(root, checkRoot);

function checkRoot(root) {
const rootString = root.source.input.css;

// Check first line
checkNewline(rootString, { endIndex: 0 }, root);
// Check subsequent lines
styleSearch(
{ source: rootString, target: ["\n"], comments: "check" },
match => checkNewline(rootString, match, root)
);
}
const rootString = context.fix ? root.toString() : root.source.input.css;

// Check first line
checkNewline(rootString, { endIndex: 0 }, root);
// Check subsequent lines
styleSearch(
{ source: rootString, target: ["\n"], comments: "check" },
match => checkNewline(rootString, match, root)
);

function complain(index, root) {
report({
Expand Down
70 changes: 70 additions & 0 deletions lib/rules/no-descending-specificity/__tests__/index.js
Expand Up @@ -163,3 +163,73 @@ testRule(rule, {
}
]
});

testRule(rule, {
ruleName,
syntax: "css-in-js",
config: [true],

accept: [
{
description: "css-in-js",
code: `
export const a = styled.div\`
&:hover {
svg { }
}
\`;
export const b = styled.div\`
svg { }
\`;
`
},
{
description: "css-in-js",
code: `
export const a = styled.div\`
&.active {
svg { }
}
\`;
export const b = styled.div\`
svg { }
\`;
`
}
],

reject: [
{
description: "css-in-js",
code: `
export const a = styled.div\`
&:hover {
svg { }
}
svg { }
\`;
`,
message: messages.rejected("svg", "&:hover svg"),
line: 7,
column: 11
},
{
description: "css-in-js",
code: `
export const a = styled.div\`
&.active {
svg { }
}
svg { }
\`;
`,
message: messages.rejected("svg", "&.active svg"),
line: 7,
column: 11
}
]
});
60 changes: 60 additions & 0 deletions lib/rules/no-duplicate-selectors/__tests__/index.js
Expand Up @@ -154,3 +154,63 @@ it("with postcss-import and duplicates across files, no warnings", () => {
expect(result.warnings()).toHaveLength(0);
});
});

testRule(rule, {
ruleName,
syntax: "css-in-js",
config: [true],

accept: [
{
description: "css-in-js",
code: `
export const a = styled.div\`
a {}
\`;
export const b = styled.div\`
a {}
\`;
`
},
{
description: "css-in-js",
code: `
export const a = styled.div\`
\${ButtonStyled} {}
\`;
export const b = styled.div\`
\${ButtonStyled} {}
\`;
`
}
],

reject: [
{
description: "css-in-js",
code: `
export const a = styled.div\`
a {}
a {}
\`;
`,
message: messages.rejected("a", 3),
line: 4,
column: 11
},
{
description: "css-in-js",
code: `
export const a = styled.div\`
\${ButtonStyled} {}
\${ButtonStyled} {}
\`;
`,
message: messages.rejected("${ButtonStyled}", 3),
line: 4,
column: 11
}
]
});
46 changes: 21 additions & 25 deletions lib/rules/no-empty-first-line/index.js
@@ -1,11 +1,11 @@
"use strict";

const eachRoot = require("../../utils/eachRoot");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const validateOptions = require("../../utils/validateOptions");

const ruleName = "no-empty-first-line";
const noEmptyFirstLineTest = /^\s*[\r\n]/;

const messages = ruleMessages(ruleName, {
rejected: "Unexpected empty line"
Expand All @@ -15,38 +15,34 @@ const rule = function(actual, _, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual });

if (!validOptions) {
if (
!validOptions ||
root.source.inline ||
root.source.lang === "object-literal"
) {
return;
}

eachRoot(root, function(root) {
if (root.source.inline || root.source.lang === "object-literal") {
return;
}
const rootString = context.fix ? root.toString() : root.source.input.css;

if (!rootString.trim()) {
return;
}

const rootString = root.source.input.css;
if (noEmptyFirstLineTest.test(rootString)) {
if (context.fix) {
root.nodes[0].raws.before = root.first.raws.before.trimLeft();

if (rootString.trim() === "") {
return;
}

const noEmptyFirstLineTest = RegExp(/^(\s*\r?\n)+/g);

if (noEmptyFirstLineTest.test(rootString)) {
if (context.fix) {
root.nodes[0].raws.before = "";

return;
}

report({
message: messages.rejected,
node: root,
result,
ruleName
});
}
});
report({
message: messages.rejected,
node: root,
result,
ruleName
});
}
};
};

Expand Down

0 comments on commit e57e893

Please sign in to comment.