index.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. 'use strict';
  2. const arrayEqual = require('../../utils/arrayEqual');
  3. const eachDeclarationBlock = require('../../utils/eachDeclarationBlock');
  4. const optionsMatches = require('../../utils/optionsMatches');
  5. const report = require('../../utils/report');
  6. const ruleMessages = require('../../utils/ruleMessages');
  7. const shorthandData = require('../../reference/shorthandData');
  8. const validateOptions = require('../../utils/validateOptions');
  9. const vendor = require('../../utils/vendor');
  10. const { isRegExp, isString } = require('../../utils/validateTypes');
  11. const ruleName = 'declaration-block-no-redundant-longhand-properties';
  12. const messages = ruleMessages(ruleName, {
  13. expected: (props) => `Expected shorthand property "${props}"`,
  14. });
  15. const meta = {
  16. url: 'https://stylelint.io/user-guide/rules/list/declaration-block-no-redundant-longhand-properties',
  17. };
  18. /** @type {import('stylelint').Rule} */
  19. const rule = (primary, secondaryOptions) => {
  20. return (root, result) => {
  21. const validOptions = validateOptions(
  22. result,
  23. ruleName,
  24. { actual: primary },
  25. {
  26. actual: secondaryOptions,
  27. possible: {
  28. ignoreShorthands: [isString, isRegExp],
  29. },
  30. optional: true,
  31. },
  32. );
  33. if (!validOptions) {
  34. return;
  35. }
  36. const longhandProperties = Object.entries(shorthandData).reduce(
  37. (/** @type {Record<string, string[]>} */ longhandProps, [key, values]) => {
  38. if (optionsMatches(secondaryOptions, 'ignoreShorthands', key)) {
  39. return longhandProps;
  40. }
  41. for (const value of values) {
  42. (longhandProps[value] || (longhandProps[value] = [])).push(key);
  43. }
  44. return longhandProps;
  45. },
  46. {},
  47. );
  48. eachDeclarationBlock(root, (eachDecl) => {
  49. /** @type {Record<string, string[]>} */
  50. const longhandDeclarations = {};
  51. eachDecl((decl) => {
  52. const prop = decl.prop.toLowerCase();
  53. const unprefixedProp = vendor.unprefixed(prop);
  54. const prefix = vendor.prefix(prop);
  55. const shorthandProperties = longhandProperties[unprefixedProp];
  56. if (!shorthandProperties) {
  57. return;
  58. }
  59. for (const shorthandProperty of shorthandProperties) {
  60. const prefixedShorthandProperty = prefix + shorthandProperty;
  61. if (!longhandDeclarations[prefixedShorthandProperty]) {
  62. longhandDeclarations[prefixedShorthandProperty] = [];
  63. }
  64. longhandDeclarations[prefixedShorthandProperty].push(prop);
  65. const prefixedShorthandData = shorthandData[shorthandProperty].map(
  66. (item) => prefix + item,
  67. );
  68. if (
  69. !arrayEqual(
  70. prefixedShorthandData.sort(),
  71. longhandDeclarations[prefixedShorthandProperty].sort(),
  72. )
  73. ) {
  74. continue;
  75. }
  76. report({
  77. ruleName,
  78. result,
  79. node: decl,
  80. message: messages.expected(prefixedShorthandProperty),
  81. });
  82. }
  83. });
  84. });
  85. };
  86. };
  87. rule.ruleName = ruleName;
  88. rule.messages = messages;
  89. rule.meta = meta;
  90. module.exports = rule;