"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculationOperatorSpaceChecker = calculationOperatorSpaceChecker; exports["default"] = rule; exports.ruleName = exports.meta = exports.messages = void 0; var _postcssMediaQueryParser = _interopRequireDefault(require("postcss-media-query-parser")); var _stylelint = require("stylelint"); var _utils = require("../../utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } var ruleName = (0, _utils.namespace)("operator-no-unspaced"); exports.ruleName = ruleName; var messages = _stylelint.utils.ruleMessages(ruleName, { expectedAfter: function expectedAfter(operator) { return "Expected single space after \"".concat(operator, "\""); }, expectedBefore: function expectedBefore(operator) { return "Expected single space before \"".concat(operator, "\""); } }); exports.messages = messages; var meta = { url: (0, _utils.ruleUrl)(ruleName) }; /** * The actual check for are there (un)necessary whitespaces */ exports.meta = meta; function checkSpaces(_ref) { var string = _ref.string, globalIndex = _ref.globalIndex, startIndex = _ref.startIndex, endIndex = _ref.endIndex, node = _ref.node, result = _ref.result; var symbol = string.substring(startIndex, endIndex + 1); var beforeOk = string[startIndex - 1] === " " && !(0, _utils.isWhitespace)(string[startIndex - 2]) || newlineBefore(string, startIndex - 1); if (!beforeOk) { _stylelint.utils.report({ ruleName: ruleName, result: result, node: node, message: messages.expectedBefore(symbol), index: startIndex + globalIndex }); } var afterOk = string[endIndex + 1] === " " && !(0, _utils.isWhitespace)(string[endIndex + 2]) || string[endIndex + 1] === "\n" || string.substr(endIndex + 1, 2) === "\r\n"; if (!afterOk) { _stylelint.utils.report({ ruleName: ruleName, result: result, node: node, message: messages.expectedAfter(symbol), index: endIndex + globalIndex }); } } function newlineBefore(str, startIndex) { var index = startIndex; while (index && (0, _utils.isWhitespace)(str[index])) { if (str[index] === "\n") return true; index--; } return false; } function rule(expectation) { return function (root, result) { var validOptions = _stylelint.utils.validateOptions(result, ruleName, { actual: expectation }); if (!validOptions) { return; } (0, _utils.eachRoot)(root, checkRoot); function checkRoot(root) { var rootString = root.source.input.css; if (rootString.trim() === "") { return; } calculationOperatorSpaceChecker({ root: root, result: result, checker: checkSpaces }); } }; } rule.ruleName = ruleName; rule.messages = messages; rule.meta = meta; /** * The core rule logic function. This one can be imported by some other rules * that work with Sass operators * * @param {Object} args -- Named arguments object * @param {PostCSS Root} args.root * @param {PostCSS Result} args.result * @param {function} args.checker -- the function that is run against all the * operators found in the input. Takes these arguments: * {Object} cbArgs -- Named arguments object * {string} cbArgs.string -- the input string (suspected operation) * {number} cbArgs.globalIndex -- the string's index in a global input * {number} cbArgs.startIndex -- the start index of a symbol to inspect * {number} cbArgs.endIndex -- the end index of a symbol to inspect * (two indexes needed to allow for `==`, `!=`, etc.) * {PostCSS Node} cbArgs.node -- for stylelint.utils.report * {PostCSS Result} cbArgs.result -- for stylelint.utils.report */ function calculationOperatorSpaceChecker(_ref2) { var root = _ref2.root, result = _ref2.result, checker = _ref2.checker; /** * Takes a string, finds all occurrences of Sass interpolation in it, then * finds all operators inside that interpolation * * @return {array} An array of objects { string, operators } - effectively, * a list of operators for each Sass interpolation occurrence */ function findInterpolation(string, startIndex) { var interpolationRegex = /#{(.*?)}/g; var results = []; // Searching for interpolation var match = interpolationRegex.exec(string); startIndex = !isNaN(startIndex) ? Number(startIndex) : 0; while (match !== null) { results.push({ source: match[0], operators: (0, _utils.findOperators)({ string: match[0], globalIndex: match.index + startIndex }) }); match = interpolationRegex.exec(string); } return results; } var dataURIRegex = /^url\(\s*['"]?data:.+['"]?\s*\)/; root.walk(function (item) { if (item.prop === "unicode-range") { return; } var results = []; // Check a value (`10px` in `width: 10px;`) if (item.value !== undefined) { if (dataURIRegex.test(item.value)) { return results; } results.push({ source: item.value, operators: (0, _utils.findOperators)({ string: item.value, globalIndex: (0, _utils.declarationValueIndex)(item), // For Sass variable values some special rules apply isAfterColon: item.prop[0] === "$" }) }); } // Property name if (item.prop !== undefined) { results = results.concat(findInterpolation(item.prop)); } // Selector if (item.selector !== undefined) { results = results.concat(findInterpolation(item.selector)); } if (item.type === "atrule") { // @forward, @use and @at-root if (item.name === "forward" || item.name === "use" || item.name === "at-root") { return; } // Media queries if (item.name === "media" || item.name === "import") { (0, _postcssMediaQueryParser["default"])(item.params).walk(function (node) { var type = node.type; if (["keyword", "media-type", "media-feature"].includes(type)) { results = results.concat(findInterpolation(node.value, (0, _utils.atRuleParamIndex)(item) + node.sourceIndex)); } else if (type === "value") { results.push({ source: node.value, operators: (0, _utils.findOperators)({ string: node.value, globalIndex: (0, _utils.atRuleParamIndex)(item) + node.sourceIndex, isAfterColon: true }) }); } else if (type === "url") { var isQuoted = node.value[0] === '"' || node.value[0] === "'"; var containsWhitespace = node.value.search(/\s/) > -1; if (isQuoted || containsWhitespace) { // The argument to the url function is only parsed as SassScript if it is a quoted // string, or a _valid_ unquoted URL [1]. // // [1] https://sass-lang.com/documentation/syntax/special-functions#url results.push({ source: node.value, operators: (0, _utils.findOperators)({ string: node.value, globalIndex: (0, _utils.atRuleParamIndex)(item) + node.sourceIndex, isAfterColon: true }) }); } } }); } else { // Function and mixin definitions and other rules results.push({ source: item.params, operators: (0, _utils.findOperators)({ string: item.params, globalIndex: (0, _utils.atRuleParamIndex)(item), isAfterColon: true }) }); } } // All the strings have been parsed, now run whitespace checking results.forEach(function (el) { // Only if there are operators within a string if (el.operators && el.operators.length > 0) { el.operators.forEach(function (operator) { checker({ string: el.source, globalIndex: operator.globalIndex, startIndex: operator.startIndex, endIndex: operator.endIndex, node: item, result: result }); }); } }); }); // Checking interpolation inside comments // We have to give up on PostCSS here because it skips some inline comments (0, _utils.findCommentsInRaws)(root.source.input.css).forEach(function (comment) { var startIndex = comment.source.start + comment.raws.startToken.length + comment.raws.left.length; if (comment.type !== "css") { return; } findInterpolation(comment.text).forEach(function (el) { // Only if there are operators within a string if (el.operators && el.operators.length > 0) { el.operators.forEach(function (operator) { checker({ string: el.source, globalIndex: operator.globalIndex + startIndex, startIndex: operator.startIndex, endIndex: operator.endIndex, node: root, result: result }); }); } }); }); }