blob: df04890bfa3828b694d5d580cf8eba9ebea4c86a [file] [log] [blame]
"use strict";
const isStandardSyntaxDeclaration = require("../../utils/isStandardSyntaxDeclaration");
const isStandardSyntaxProperty = require("../../utils/isStandardSyntaxProperty");
const postcss = require("postcss");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const validateOptions = require("../../utils/validateOptions");
const valueParser = require("postcss-value-parser");
const ruleName = "shorthand-property-no-redundant-values";
const messages = ruleMessages(ruleName, {
rejected: (unexpected, expected) =>
`Unexpected longhand value '${unexpected}' instead of '${expected}'`
});
const propertiesWithShorthandNotation = new Set([
"margin",
"padding",
"border-color",
"border-radius",
"border-style",
"border-width",
"grid-gap"
]);
const ignoredCharacters = ["+", "*", "/", "(", ")", "$", "@", "--", "var("];
function hasIgnoredCharacters(value) {
return ignoredCharacters.some(char => value.indexOf(char) !== -1);
}
function isShorthandProperty(property) {
return propertiesWithShorthandNotation.has(property);
}
function canCondense(top, right) {
const bottom =
arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
const left =
arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
const lowerTop = top.toLowerCase();
const lowerRight = right.toLowerCase();
const lowerBottom = bottom && bottom.toLowerCase();
const lowerLeft = left && left.toLowerCase();
if (canCondenseToOneValue(lowerTop, lowerRight, lowerBottom, lowerLeft)) {
return [top];
} else if (
canCondenseToTwoValues(lowerTop, lowerRight, lowerBottom, lowerLeft)
) {
return [top, right];
} else if (
canCondenseToThreeValues(lowerTop, lowerRight, lowerBottom, lowerLeft)
) {
return [top, right, bottom];
} else {
return [top, right, bottom, left];
}
}
function canCondenseToOneValue(top, right, bottom, left) {
if (top !== right) {
return false;
}
return (top === bottom && (bottom === left || !left)) || (!bottom && !left);
}
function canCondenseToTwoValues(top, right, bottom, left) {
return (
(top === bottom && right === left) ||
(top === bottom && !left && top !== right)
);
}
function canCondenseToThreeValues(top, right, bottom, left) {
return right === left;
}
const rule = function(actual, secondary, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual });
if (!validOptions) {
return;
}
root.walkDecls(decl => {
if (
!isStandardSyntaxDeclaration(decl) ||
!isStandardSyntaxProperty(decl.prop)
) {
return;
}
const prop = decl.prop;
const value = decl.value;
const normalizedProp = postcss.vendor.unprefixed(prop.toLowerCase());
if (hasIgnoredCharacters(value) || !isShorthandProperty(normalizedProp)) {
return;
}
const valuesToShorthand = [];
valueParser(value).walk(valueNode => {
if (valueNode.type !== "word") {
return;
}
valuesToShorthand.push(valueParser.stringify(valueNode));
});
if (valuesToShorthand.length <= 1 || valuesToShorthand.length > 4) {
return;
}
const shortestForm = canCondense.apply(undefined, valuesToShorthand);
const shortestFormString = shortestForm.filter(value => value).join(" ");
const valuesFormString = valuesToShorthand.join(" ");
if (shortestFormString.toLowerCase() === valuesFormString.toLowerCase()) {
return;
}
if (context.fix) {
decl.value = decl.value.replace(value, shortestFormString);
} else {
report({
message: messages.rejected(value, shortestFormString),
node: decl,
result,
ruleName
});
}
});
};
};
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;