index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. 'use strict';
  2. const eachDeclarationBlock = require('../../utils/eachDeclarationBlock');
  3. const isCustomProperty = require('../../utils/isCustomProperty');
  4. const isStandardSyntaxProperty = require('../../utils/isStandardSyntaxProperty');
  5. const optionsMatches = require('../../utils/optionsMatches');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const validateOptions = require('../../utils/validateOptions');
  9. const { isString } = require('../../utils/validateTypes');
  10. const vendor = require('../../utils/vendor');
  11. const ruleName = 'declaration-block-no-duplicate-properties';
  12. const messages = ruleMessages(ruleName, {
  13. rejected: (property) => `Unexpected duplicate "${property}"`,
  14. });
  15. const meta = {
  16. url: 'https://stylelint.io/user-guide/rules/list/declaration-block-no-duplicate-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. ignore: [
  29. 'consecutive-duplicates',
  30. 'consecutive-duplicates-with-different-values',
  31. 'consecutive-duplicates-with-same-prefixless-values',
  32. ],
  33. ignoreProperties: [isString],
  34. },
  35. optional: true,
  36. },
  37. );
  38. if (!validOptions) {
  39. return;
  40. }
  41. const ignoreDuplicates = optionsMatches(secondaryOptions, 'ignore', 'consecutive-duplicates');
  42. const ignoreDiffValues = optionsMatches(
  43. secondaryOptions,
  44. 'ignore',
  45. 'consecutive-duplicates-with-different-values',
  46. );
  47. const ignorePrefixlessSameValues = optionsMatches(
  48. secondaryOptions,
  49. 'ignore',
  50. 'consecutive-duplicates-with-same-prefixless-values',
  51. );
  52. eachDeclarationBlock(root, (eachDecl) => {
  53. /** @type {string[]} */
  54. const decls = [];
  55. /** @type {string[]} */
  56. const values = [];
  57. eachDecl((decl) => {
  58. const prop = decl.prop;
  59. const value = decl.value;
  60. if (!isStandardSyntaxProperty(prop)) {
  61. return;
  62. }
  63. if (isCustomProperty(prop)) {
  64. return;
  65. }
  66. // Return early if the property is to be ignored
  67. if (optionsMatches(secondaryOptions, 'ignoreProperties', prop)) {
  68. return;
  69. }
  70. // Ignore the src property as commonly duplicated in at-fontface
  71. if (prop.toLowerCase() === 'src') {
  72. return;
  73. }
  74. const indexDuplicate = decls.indexOf(prop.toLowerCase());
  75. if (indexDuplicate !== -1) {
  76. if (ignoreDiffValues || ignorePrefixlessSameValues) {
  77. // fails if duplicates are not consecutive
  78. if (indexDuplicate !== decls.length - 1) {
  79. report({
  80. message: messages.rejected(prop),
  81. node: decl,
  82. result,
  83. ruleName,
  84. });
  85. return;
  86. }
  87. const duplicateValue = values[indexDuplicate];
  88. if (ignorePrefixlessSameValues) {
  89. // fails if values of consecutive, unprefixed duplicates are equal
  90. if (vendor.unprefixed(value) !== vendor.unprefixed(duplicateValue)) {
  91. report({
  92. message: messages.rejected(prop),
  93. node: decl,
  94. result,
  95. ruleName,
  96. });
  97. return;
  98. }
  99. }
  100. // fails if values of consecutive duplicates are equal
  101. if (value === duplicateValue) {
  102. report({
  103. message: messages.rejected(prop),
  104. node: decl,
  105. result,
  106. ruleName,
  107. });
  108. return;
  109. }
  110. return;
  111. }
  112. if (ignoreDuplicates && indexDuplicate === decls.length - 1) {
  113. return;
  114. }
  115. report({
  116. message: messages.rejected(prop),
  117. node: decl,
  118. result,
  119. ruleName,
  120. });
  121. }
  122. decls.push(prop.toLowerCase());
  123. values.push(value.toLowerCase());
  124. });
  125. });
  126. };
  127. };
  128. rule.ruleName = ruleName;
  129. rule.messages = messages;
  130. rule.meta = meta;
  131. module.exports = rule;