123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- 'use strict';
- const isStandardSyntaxDeclaration = require('../../utils/isStandardSyntaxDeclaration');
- const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty');
- const report = require('../../utils/report');
- const ruleMessages = require('../../utils/ruleMessages');
- const validateOptions = require('../../utils/validateOptions');
- const valueParser = require('postcss-value-parser');
- const vendor = require('../../utils/vendor');
- const ruleName = 'shorthand-property-no-redundant-values';
- const messages = ruleMessages(ruleName, {
- rejected: (unexpected, expected) =>
- `Unexpected longhand value "${unexpected}" instead of "${expected}"`,
- });
- const meta = {
- url: 'https://stylelint.io/user-guide/rules/list/shorthand-property-no-redundant-values',
- fixable: true,
- };
- const propertiesWithShorthandNotation = new Set([
- 'margin',
- 'padding',
- 'border-color',
- 'border-radius',
- 'border-style',
- 'border-width',
- 'grid-gap',
- ]);
- const ignoredCharacters = ['+', '*', '/', '(', ')', '$', '@', '--', 'var('];
- /**
- * @param {string} value
- * @returns {boolean}
- */
- function hasIgnoredCharacters(value) {
- return ignoredCharacters.some((char) => value.includes(char));
- }
- /**
- * @param {string} property
- * @returns {boolean}
- */
- function isShorthandProperty(property) {
- return propertiesWithShorthandNotation.has(property);
- }
- /**
- * @param {string} top
- * @param {string} right
- * @param {string} bottom
- * @param {string} left
- * @returns {string[]}
- */
- function canCondense(top, right, bottom, left) {
- 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];
- }
- if (canCondenseToTwoValues(lowerTop, lowerRight, lowerBottom, lowerLeft)) {
- return [top, right];
- }
- if (canCondenseToThreeValues(lowerTop, lowerRight, lowerBottom, lowerLeft)) {
- return [top, right, bottom];
- }
- return [top, right, bottom, left];
- }
- /**
- * @param {string} top
- * @param {string} right
- * @param {string} bottom
- * @param {string} left
- * @returns {boolean}
- */
- function canCondenseToOneValue(top, right, bottom, left) {
- if (top !== right) {
- return false;
- }
- return (top === bottom && (bottom === left || !left)) || (!bottom && !left);
- }
- /**
- * @param {string} top
- * @param {string} right
- * @param {string} bottom
- * @param {string} left
- * @returns {boolean}
- */
- function canCondenseToTwoValues(top, right, bottom, left) {
- return (top === bottom && right === left) || (top === bottom && !left && top !== right);
- }
- /**
- * @param {string} _top
- * @param {string} right
- * @param {string} _bottom
- * @param {string} left
- * @returns {boolean}
- */
- function canCondenseToThreeValues(_top, right, _bottom, left) {
- return right === left;
- }
- /** @type {import('stylelint').Rule} */
- const rule = (primary, _secondaryOptions, context) => {
- return (root, result) => {
- const validOptions = validateOptions(result, ruleName, { actual: primary });
- if (!validOptions) {
- return;
- }
- root.walkDecls((decl) => {
- if (!isStandardSyntaxDeclaration(decl) || !isStandardSyntaxProperty(decl.prop)) {
- return;
- }
- const prop = decl.prop;
- const value = decl.value;
- const normalizedProp = vendor.unprefixed(prop.toLowerCase());
- if (hasIgnoredCharacters(value) || !isShorthandProperty(normalizedProp)) {
- return;
- }
- /** @type {string[]} */
- 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(
- valuesToShorthand[0] || '',
- valuesToShorthand[1] || '',
- valuesToShorthand[2] || '',
- valuesToShorthand[3] || '',
- );
- const shortestFormString = shortestForm.filter(Boolean).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;
- rule.meta = meta;
- module.exports = rule;
|