--- /dev/null
+"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}'`
+});
+
+// Only these shorthand properties can have values, that can be written in shorter form (remove repetitions)
+const shorthandableProperties = new Set([
+ "margin",
+ "padding",
+ "border-color",
+ "border-radius",
+ "border-style",
+ "border-width",
+ "grid-gap"
+]);
+
+const ignoredCharacters = [
+ "+",
+ "-",
+ "*",
+ "/",
+ "(",
+ ")",
+ "$",
+ "@",
+ "--",
+ "var("
+];
+
+function isIgnoredCharacters(value) {
+ return ignoredCharacters.some(char => value.indexOf(char) !== -1);
+}
+
+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,
+ value = decl.value;
+
+ const normalizedProp = postcss.vendor.unprefixed(prop.toLowerCase());
+
+ // Ignore not shorthandable properties, and math operations
+ if (
+ isIgnoredCharacters(value) ||
+ !shorthandableProperties.has(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 => {
+ return 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;