UNPKG

stylelint

Version:
298 lines (244 loc) 8.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.messages = exports.ruleName = undefined; var _utils = require("../../utils"); var _lodash = require("lodash"); var _lodash2 = _interopRequireDefault(_lodash); var _postcss = require("postcss"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var ruleName = exports.ruleName = "declaration-block-properties-order"; var messages = exports.messages = (0, _utils.ruleMessages)(ruleName, { expected: function expected(first, second) { return "Expected \"" + first + "\" to come before \"" + second + "\""; } }); function rule(expectation, options) { return function (root, result) { var validOptions = (0, _utils.validateOptions)(result, ruleName, { actual: expectation, possible: validatePrimaryOption }, { actual: options, possible: { unspecified: ["top", "bottom", "ignore", "bottomAlphabetical"] }, optional: true }); if (!validOptions) { return; } var alphabetical = expectation === "alphabetical"; var expectedOrder = alphabetical ? null : createExpectedOrder(expectation); // By default, ignore unspecified properties var unspecified = _lodash2.default.get(options, ["unspecified"], "ignore"); // Shallow loop root.each(function (node) { if (node.type === "rule" || node.type === "atrule") { checkNode(node); } }); function checkNode(node) { var allPropData = []; node.each(function (child) { // If the child has nested nodes with child // (e.g. a rule nested within a rule), make // sure to check the children if (child.nodes && child.nodes.length) { checkNode(child); } if (child.type !== "decl") { return; } var prop = child.prop; if (!(0, _utils.isStandardSyntaxProperty)(prop)) { return; } if ((0, _utils.isCustomProperty)(prop)) { return; } var unprefixedPropName = _postcss.vendor.unprefixed(prop); // Hack to allow -moz-osx-font-smoothing to be understood // just like -webkit-font-smoothing if (unprefixedPropName.indexOf("osx-") === 0) { unprefixedPropName = unprefixedPropName.slice(4); } var propData = { name: prop, unprefixedName: unprefixedPropName, orderData: alphabetical ? null : getOrderData(expectedOrder, unprefixedPropName), before: child.raw("before"), index: allPropData.length, node: child }; var previousPropData = _lodash2.default.last(allPropData); allPropData.push(propData); // Skip first decl if (!previousPropData) { return; } var isCorrectOrder = alphabetical ? checkAlpabeticalOrder(previousPropData, propData) : checkOrder(previousPropData, propData); if (isCorrectOrder) { return; } complain({ message: messages.expected(propData.name, previousPropData.name), node: child }); }); function checkOrder(firstPropData, secondPropData) { // If the unprefixed property names are the same, resort to alphabetical ordering if (firstPropData.unprefixedName === secondPropData.unprefixedName) { return firstPropData.name <= secondPropData.name; } var firstPropIsUnspecified = !firstPropData.orderData; var secondPropIsUnspecified = !secondPropData.orderData; // Now check actual known properties ... if (!firstPropIsUnspecified && !secondPropIsUnspecified) { return firstPropData.orderData.expectedPosition <= secondPropData.orderData.expectedPosition; } if (firstPropIsUnspecified && !secondPropIsUnspecified) { // If first prop is unspecified, look for a specified prop before it to // compare to the current prop var priorSpecifiedPropData = _lodash2.default.findLast(allPropData.slice(0, -1), function (d) { return !!d.orderData; }); if (priorSpecifiedPropData && priorSpecifiedPropData.orderData && priorSpecifiedPropData.orderData.expectedPosition > secondPropData.orderData.expectedPosition) { complain({ message: messages.expected(secondPropData.name, priorSpecifiedPropData.name), node: secondPropData.node }); return true; // avoid logging another warning } } // Now deal with unspecified props ... // Starting with bottomAlphabetical as it requires more specific conditionals if (unspecified === "bottomAlphabetical" && !firstPropIsUnspecified && secondPropIsUnspecified) { return true; } if (unspecified === "bottomAlphabetical" && secondPropIsUnspecified && firstPropIsUnspecified) { if (checkAlpabeticalOrder(firstPropData, secondPropData)) { return true; } else { return false; } } if (unspecified === "bottomAlphabetical" && firstPropIsUnspecified) { return false; } if (firstPropIsUnspecified && secondPropIsUnspecified) { return true; } if (unspecified === "ignore" && (firstPropIsUnspecified || secondPropIsUnspecified)) { return true; } if (unspecified === "top" && firstPropIsUnspecified) { return true; } if (unspecified === "top" && secondPropIsUnspecified) { return false; } if (unspecified === "bottom" && secondPropIsUnspecified) { return true; } if (unspecified === "bottom" && firstPropIsUnspecified) { return false; } } } function complain(_ref) { var message = _ref.message; var node = _ref.node; (0, _utils.report)({ message: message, node: node, result: result, ruleName: ruleName }); } }; } rule.primaryOptionArray = true; exports.default = rule; function createExpectedOrder(input) { var order = {}; var expectedPosition = 0; appendGroup(input); function appendGroup(items) { items.forEach(function (item) { return appendItem(item, false); }); } function appendItem(item, inFlexibleGroup) { if (_lodash2.default.isString(item)) { // In flexible groups, the expectedPosition does not ascend // to make that flexibility work; // otherwise, it will always ascend if (!inFlexibleGroup) { expectedPosition += 1; } order[item] = { expectedPosition: expectedPosition }; return; } if (!item.order || item.order === "strict") { appendGroup(item.properties); return; } else if (item.order === "flexible") { expectedPosition += 1; item.properties.forEach(function (property) { appendItem(property, true); }); } } return order; } function getOrderData(expectedOrder, propName) { var orderData = expectedOrder[propName]; // If prop was not specified but has a hyphen // (e.g. `padding-top`), try looking for the segment preceding the hyphen // and use that index if (!orderData && propName.lastIndexOf("-") !== -1) { var propNamePreHyphen = propName.slice(0, propName.lastIndexOf("-")); orderData = getOrderData(expectedOrder, propNamePreHyphen); } return orderData; } function checkAlpabeticalOrder(firstPropData, secondPropData) { // If unprefixed prop names are the same, compare the prefixed versions if (firstPropData.unprefixedName === secondPropData.unprefixedName) { return firstPropData.name <= secondPropData.name; } return firstPropData.unprefixedName < secondPropData.unprefixedName; } function validatePrimaryOption(actualOptions) { // Return true early if alphabetical if (actualOptions === "alphabetical") { return true; } // Otherwise, begin checking array options if (!Array.isArray(actualOptions)) { return false; } // Every item in the array must be a string or an object // with a "properties" property if (!actualOptions.every(function (item) { if (_lodash2.default.isString(item)) { return true; } return _lodash2.default.isPlainObject(item) && !_lodash2.default.isUndefined(item.properties); })) { return false; } var objectItems = actualOptions.filter(_lodash2.default.isPlainObject); // Every object-item's "order" property must be "strict" or "flexible" if (!objectItems.every(function (item) { if (_lodash2.default.isUndefined(item.order)) { return true; } return _lodash2.default.includes(["strict", "flexible"], item.order); })) { return false; } return true; }