'use strict'; const _ = require('lodash'); const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule'); 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 ruleName = 'selector-attribute-brackets-space-inside'; const messages = ruleMessages(ruleName, { expectedOpening: 'Expected single space after "["', rejectedOpening: 'Unexpected whitespace after "["', expectedClosing: 'Expected single space before "]"', rejectedClosing: 'Unexpected whitespace before "]"', }); function rule(expectation, options, context) { return (root, result) => { const validOptions = validateOptions(result, ruleName, { actual: expectation, possible: ['always', 'never'], }); if (!validOptions) { return; } root.walkRules((rule) => { if (!isStandardSyntaxRule(rule)) { return; } if (!rule.selector.includes('[')) { return; } const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector; let hasFixed; const fixedSelector = parseSelector(selector, result, rule, (selectorTree) => { selectorTree.walkAttributes((attributeNode) => { const attributeSelectorString = attributeNode.toString(); styleSearch({ source: attributeSelectorString, target: '[' }, (match) => { 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); } }); styleSearch({ source: attributeSelectorString, target: ']' }, (match) => { 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); } }); }); }); if (hasFixed) { if (!rule.raws.selector) { rule.selector = fixedSelector; } else { rule.raws.selector.raw = fixedSelector; } } function complain(message, index) { report({ message, index, result, ruleName, node: rule, }); } }); }; function fixBefore(attributeNode) { const rawAttrBefore = _.get(attributeNode, 'raws.spaces.attribute.before'); const { attrBefore, setAttrBefore } = rawAttrBefore ? { attrBefore: rawAttrBefore, setAttrBefore(fixed) { attributeNode.raws.spaces.attribute.before = fixed; }, } : { attrBefore: _.get(attributeNode, 'spaces.attribute.before', ''), setAttrBefore(fixed) { _.set(attributeNode, 'spaces.attribute.before', fixed); }, }; if (expectation === 'always') { setAttrBefore(attrBefore.replace(/^\s*/, ' ')); } else if (expectation === 'never') { setAttrBefore(attrBefore.replace(/^\s*/, '')); } } function fixAfter(attributeNode) { let key; if (attributeNode.operator) { if (attributeNode.insensitive) { key = 'insensitive'; } else { key = 'value'; } } else { key = 'attribute'; } const rawAfter = _.get(attributeNode, `raws.spaces.${key}.after`); const { after, setAfter } = rawAfter ? { after: rawAfter, setAfter(fixed) { attributeNode.raws.spaces[key].after = fixed; }, } : { after: _.get(attributeNode, `spaces.${key}.after`, ''), setAfter(fixed) { _.set(attributeNode, `spaces.${key}.after`, fixed); }, }; if (expectation === 'always') { setAfter(after.replace(/\s*$/, ' ')); } else if (expectation === 'never') { setAfter(after.replace(/\s*$/, '')); } } } rule.ruleName = ruleName; rule.messages = messages; module.exports = rule;