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 }); - } - ); + }); + } }; };