index.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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 { longhandSubPropertiesOfShorthandProperties } = require('../../reference/properties');
  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/declaration-block-no-redundant-longhand-properties',
  17. };
  18. const IGNORED_VALUES = new Set(['inherit']);
  19. /** @type {import('stylelint').Rule} */
  20. const rule = (primary, secondaryOptions) => {
  21. return (root, result) => {
  22. const validOptions = validateOptions(
  23. result,
  24. ruleName,
  25. { actual: primary },
  26. {
  27. actual: secondaryOptions,
  28. possible: {
  29. ignoreShorthands: [isString, isRegExp],
  30. },
  31. optional: true,
  32. },
  33. );
  34. if (!validOptions) {
  35. return;
  36. }
  37. /** @type {Map<string, string[]>} */
  38. const longhandToShorthands = new Map();
  39. for (const [shorthand, longhandProps] of longhandSubPropertiesOfShorthandProperties.entries()) {
  40. if (optionsMatches(secondaryOptions, 'ignoreShorthands', shorthand)) {
  41. continue;
  42. }
  43. for (const longhand of longhandProps) {
  44. const shorthands = longhandToShorthands.get(longhand) || [];
  45. shorthands.push(shorthand);
  46. longhandToShorthands.set(longhand, shorthands);
  47. }
  48. }
  49. eachDeclarationBlock(root, (eachDecl) => {
  50. /** @type {Map<string, string[]>} */
  51. const longhandDeclarations = new Map();
  52. eachDecl((decl) => {
  53. if (IGNORED_VALUES.has(decl.value)) {
  54. return;
  55. }
  56. const prop = decl.prop.toLowerCase();
  57. const unprefixedProp = vendor.unprefixed(prop);
  58. const prefix = vendor.prefix(prop);
  59. const shorthandProperties = longhandToShorthands.get(unprefixedProp);
  60. if (!shorthandProperties) {
  61. return;
  62. }
  63. for (const shorthandProperty of shorthandProperties) {
  64. const prefixedShorthandProperty = prefix + shorthandProperty;
  65. const longhandDeclaration = longhandDeclarations.get(prefixedShorthandProperty) || [];
  66. longhandDeclaration.push(prop);
  67. longhandDeclarations.set(prefixedShorthandProperty, longhandDeclaration);
  68. const shorthandProps = /** @type {Map<string, Set<string>>} */ (
  69. longhandSubPropertiesOfShorthandProperties
  70. ).get(shorthandProperty);
  71. const prefixedShorthandData = Array.from(shorthandProps || []).map(
  72. (item) => prefix + item,
  73. );
  74. if (!arrayEqual(prefixedShorthandData.sort(), longhandDeclaration.sort())) {
  75. continue;
  76. }
  77. report({
  78. ruleName,
  79. result,
  80. node: decl,
  81. word: decl.prop,
  82. message: messages.expected(prefixedShorthandProperty),
  83. });
  84. }
  85. });
  86. });
  87. };
  88. };
  89. rule.ruleName = ruleName;
  90. rule.messages = messages;
  91. rule.meta = meta;
  92. module.exports = rule;