index.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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/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. /** @type {Map<string, string[]>} */
  37. const longhandToShorthands = new Map();
  38. for (const [shorthand, longhandProps] of longhandSubPropertiesOfShorthandProperties.entries()) {
  39. if (optionsMatches(secondaryOptions, 'ignoreShorthands', shorthand)) {
  40. continue;
  41. }
  42. for (const longhand of longhandProps) {
  43. const shorthands = longhandToShorthands.get(longhand) || [];
  44. shorthands.push(shorthand);
  45. longhandToShorthands.set(longhand, shorthands);
  46. }
  47. }
  48. eachDeclarationBlock(root, (eachDecl) => {
  49. /** @type {Map<string, string[]>} */
  50. const longhandDeclarations = new Map();
  51. eachDecl((decl) => {
  52. const prop = decl.prop.toLowerCase();
  53. const unprefixedProp = vendor.unprefixed(prop);
  54. const prefix = vendor.prefix(prop);
  55. const shorthandProperties = longhandToShorthands.get(unprefixedProp);
  56. if (!shorthandProperties) {
  57. return;
  58. }
  59. for (const shorthandProperty of shorthandProperties) {
  60. const prefixedShorthandProperty = prefix + shorthandProperty;
  61. const longhandDeclaration = longhandDeclarations.get(prefixedShorthandProperty) || [];
  62. longhandDeclaration.push(prop);
  63. longhandDeclarations.set(prefixedShorthandProperty, longhandDeclaration);
  64. const shorthandProps = /** @type {Map<string, Set<string>>} */ (
  65. longhandSubPropertiesOfShorthandProperties
  66. ).get(shorthandProperty);
  67. const prefixedShorthandData = Array.from(shorthandProps || []).map(
  68. (item) => prefix + item,
  69. );
  70. if (!arrayEqual(prefixedShorthandData.sort(), longhandDeclaration.sort())) {
  71. continue;
  72. }
  73. report({
  74. ruleName,
  75. result,
  76. node: decl,
  77. word: decl.prop,
  78. message: messages.expected(prefixedShorthandProperty),
  79. });
  80. }
  81. });
  82. });
  83. };
  84. };
  85. rule.ruleName = ruleName;
  86. rule.messages = messages;
  87. rule.meta = meta;
  88. module.exports = rule;