3 const _ = require("lodash");
4 const atRuleParamIndex = require("../../utils/atRuleParamIndex");
5 const declarationValueIndex = require("../../utils/declarationValueIndex");
6 const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule");
7 const parseSelector = require("../../utils/parseSelector");
8 const report = require("../../utils/report");
9 const ruleMessages = require("../../utils/ruleMessages");
10 const validateOptions = require("../../utils/validateOptions");
11 const valueParser = require("postcss-value-parser");
13 const ruleName = "string-quotes";
15 const messages = ruleMessages(ruleName, {
16 expected: q => `Expected ${q} quotes`
19 const singleQuote = `'`;
20 const doubleQuote = `"`;
22 const rule = function(expectation, secondary, context) {
23 const correctQuote = expectation === "single" ? singleQuote : doubleQuote;
24 const erroneousQuote = expectation === "single" ? doubleQuote : singleQuote;
26 return (root, result) => {
27 const validOptions = validateOptions(
32 possible: ["single", "double"]
37 avoidEscape: _.isBoolean
47 const avoidEscape = _.get(secondary, "avoidEscape", true);
52 checkDeclOrAtRule(node, node.params, atRuleParamIndex);
55 checkDeclOrAtRule(node, node.value, declarationValueIndex);
63 function checkRule(rule) {
64 if (!isStandardSyntaxRule(rule)) {
68 rule.selector.indexOf("[") === -1 ||
69 rule.selector.indexOf("=") === -1
74 const fixPositions = [];
75 parseSelector(rule.selector, result, rule, selectorTree => {
76 selectorTree.walkAttributes(attributeNode => {
78 attributeNode.quoted &&
79 attributeNode.value.indexOf(erroneousQuote) !== -1
82 attributeNode.value.indexOf(correctQuote) !== -1;
83 if (avoidEscape && needsEscape) {
84 // don't consider this an error
89 // index of the start of our attribute node in our source
90 attributeNode.sourceIndex +
91 // length of our attribute
92 attributeNode.attribute.length +
93 // length of our operator , ie '='
94 attributeNode.operator.length +
95 // and the length of the quote
96 erroneousQuote.length;
98 // we currently don't fix escapes
99 if (context.fix && !needsEscape) {
103 // the length of our value
104 attributeNode.value.length -
105 // with the length of our quote subtracted
106 erroneousQuote.length;
107 fixPositions.push(openIndex, closeIndex);
110 message: messages.expected(expectation),
120 fixPositions.forEach(fixIndex => {
121 rule.selector = replaceQuote(rule.selector, fixIndex, correctQuote);
125 function checkDeclOrAtRule(node, value, getIndex) {
126 const fixPositions = [];
127 // Get out quickly if there are no erroneous quotes
128 if (value.indexOf(erroneousQuote) === -1) {
130 } else if (node.type === "atrule" && node.name === "charset") {
131 // allow @charset rules to have double quotes, in spite of the configuration
132 // TODO: @charset should always use double-quotes, see https://github.com/stylelint/stylelint/issues/2788
136 valueParser(value).walk(valueNode => {
137 if (valueNode.type === "string" && valueNode.quote === erroneousQuote) {
138 const needsEscape = valueNode.value.indexOf(correctQuote) !== -1;
139 if (avoidEscape && needsEscape) {
140 // don't consider this an error
143 const openIndex = valueNode.sourceIndex;
145 // we currently don't fix escapes
146 if (context.fix && !needsEscape) {
148 openIndex + valueNode.value.length + erroneousQuote.length;
149 fixPositions.push(openIndex, closeIndex);
152 message: messages.expected(expectation),
154 index: getIndex(node) + openIndex,
162 fixPositions.forEach(fixIndex => {
163 if (node.type === "atrule") {
164 node.params = replaceQuote(node.params, fixIndex, correctQuote);
166 node.value = replaceQuote(node.value, fixIndex, correctQuote);
173 function replaceQuote(string, index, replace) {
175 string.substring(0, index) +
177 string.substring(index + replace.length)
180 rule.ruleName = ruleName;
181 rule.messages = messages;
182 module.exports = rule;