diff --git a/lib/rules/string-no-newline/__tests__/index.js b/lib/rules/string-no-newline/__tests__/index.js
index 8596dd002c..cfcad2d9e1 100644
--- a/lib/rules/string-no-newline/__tests__/index.js
+++ b/lib/rules/string-no-newline/__tests__/index.js
@@ -37,22 +37,57 @@ testRule(rule, {
}`,
description: "attribute containing double-slash"
+ },
+ {
+ code: `
+`,
+ description: "newline in html"
+ },
+ {
+ code: `
this is fine.
+ this isn't.
+ this line is still affected.
+ actual issues are still reported
`,
+ description: "single quotes"
}
],
reject: [
{
code: "a::before { content: 'one\ntwo'; }",
+ description: "content LF",
message: messages.rejected,
line: 1,
column: 26
},
{
code: "a::before { content: 'one\r\ntwo'; }",
- description: "CRLF",
+ description: "content CRLF",
message: messages.rejected,
line: 1,
column: 26
+ },
+ {
+ code: "a[href^='one\r\ntwo'] { display: block; }",
+ description: "attribute CRLF",
+ message: messages.rejected,
+ line: 1,
+ column: 12
+ },
+ {
+ code: "@charset 'utf-8\n';",
+ description: "atRule CRLF",
+ message: messages.rejected,
+ line: 1,
+ column: 16
}
]
});
diff --git a/lib/rules/string-no-newline/index.js b/lib/rules/string-no-newline/index.js
index 545ebe1615..a189f91002 100644
--- a/lib/rules/string-no-newline/index.js
+++ b/lib/rules/string-no-newline/index.js
@@ -1,11 +1,15 @@
"use strict";
+const atRuleParamIndex = require("../../utils/atRuleParamIndex");
+const declarationValueIndex = require("../../utils/declarationValueIndex");
+const parseSelector = require("../../utils/parseSelector");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
-const styleSearch = require("style-search");
const validateOptions = require("../../utils/validateOptions");
+const valueParser = require("postcss-value-parser");
const ruleName = "string-no-newline";
+const reNewLine = /(\r?\n)/;
const messages = ruleMessages(ruleName, {
rejected: "Unexpected newline in string"
@@ -17,30 +21,82 @@ const rule = function(actual) {
if (!validOptions) {
return;
}
+ root.walk(node => {
+ switch (node.type) {
+ case "atrule":
+ checkDeclOrAtRule(node, node.params, atRuleParamIndex);
+ break;
+ case "decl":
+ checkDeclOrAtRule(node, node.value, declarationValueIndex);
+ break;
+ case "rule":
+ checkRule(node);
+ break;
+ }
+ });
+ function checkRule(rule) {
+ // Get out quickly if there are no new line
+ if (!reNewLine.test(rule.selector)) {
+ return;
+ }
+
+ parseSelector(rule.selector, result, rule, selectorTree => {
+ selectorTree.walkAttributes(attributeNode => {
+ if (!reNewLine.test(attributeNode.value)) {
+ return;
+ }
+
+ const openIndex = [
+ // length of our attribute
+ attributeNode.attribute,
+ // length of our operator , ie '='
+ attributeNode.operator,
+ // length of the contents before newline
+ RegExp.leftContext
+ ].reduce(
+ (index, str) => index + str.length,
+ // index of the start of our attribute node in our source
+ attributeNode.sourceIndex
+ );
+
+ report({
+ message: messages.rejected,
+ node: rule,
+ index: openIndex,
+ result,
+ ruleName
+ });
+ });
+ });
+ }
- const cssString = root.toString();
- styleSearch(
- {
- source: cssString,
- target: "\n",
- strings: "only"
- },
- match => {
- const charBefore = cssString[match.startIndex - 1];
- let index = match.startIndex;
- if (charBefore === "\\") {
+ function checkDeclOrAtRule(node, value, getIndex) {
+ // Get out quickly if there are no new line
+ if (!reNewLine.test(value)) {
+ return;
+ }
+
+ valueParser(value).walk(valueNode => {
+ if (valueNode.type !== "string" || !reNewLine.test(valueNode.value)) {
return;
}
- if (charBefore === "\r") index -= 1;
+
+ const openIndex = [
+ // length of the quote
+ valueNode.quote,
+ // length of the contents before newline
+ RegExp.leftContext
+ ].reduce((index, str) => index + str.length, valueNode.sourceIndex);
+
report({
message: messages.rejected,
- node: root,
- index,
+ node,
+ index: getIndex(node) + openIndex,
result,
ruleName
});
- }
- );
+ });
+ }
};
};