index.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. 'use strict';
  2. const declarationValueIndex = require('../../utils/declarationValueIndex');
  3. const { longhandTimeProperties, shorthandTimeProperties } = require('../../reference/properties');
  4. const optionsMatches = require('../../utils/optionsMatches');
  5. const report = require('../../utils/report');
  6. const ruleMessages = require('../../utils/ruleMessages');
  7. const validateOptions = require('../../utils/validateOptions');
  8. const valueParser = require('postcss-value-parser');
  9. const vendor = require('../../utils/vendor');
  10. const { isNumber } = require('../../utils/validateTypes');
  11. const getDeclarationValue = require('../../utils/getDeclarationValue');
  12. const getDimension = require('../../utils/getDimension');
  13. const ruleName = 'time-min-milliseconds';
  14. const messages = ruleMessages(ruleName, {
  15. expected: (time) => `Expected a minimum of ${time} milliseconds`,
  16. });
  17. const meta = {
  18. url: 'https://stylelint.io/user-guide/rules/time-min-milliseconds',
  19. };
  20. const DELAY_PROPERTIES = new Set(['animation-delay', 'transition-delay']);
  21. /** @type {import('stylelint').Rule<number>} */
  22. const rule = (primary, secondaryOptions) => {
  23. return (root, result) => {
  24. const validOptions = validateOptions(
  25. result,
  26. ruleName,
  27. {
  28. actual: primary,
  29. possible: isNumber,
  30. },
  31. {
  32. actual: secondaryOptions,
  33. possible: {
  34. ignore: ['delay'],
  35. },
  36. optional: true,
  37. },
  38. );
  39. if (!validOptions) {
  40. return;
  41. }
  42. const minimum = primary;
  43. const ignoreDelay = optionsMatches(secondaryOptions, 'ignore', 'delay');
  44. root.walkDecls((decl) => {
  45. const propertyName = vendor.unprefixed(decl.prop.toLowerCase());
  46. const propertyValue = decl.value;
  47. const parsedValue = valueParser(getDeclarationValue(decl));
  48. let timeValueCount = 0;
  49. parsedValue.walk((node) => {
  50. const { value, sourceIndex } = node;
  51. const dimension = getDimension(node);
  52. if (
  53. longhandTimeProperties.has(propertyName) &&
  54. !isIgnoredProperty(propertyName) &&
  55. !isAcceptableTime(dimension)
  56. ) {
  57. complain(decl, 0, propertyValue.length);
  58. }
  59. if (!shorthandTimeProperties.has(propertyName)) return;
  60. timeValueCount = calcTimeValueCount(dimension, value, timeValueCount);
  61. if (isAcceptableTime(dimension) || (ignoreDelay && timeValueCount !== 1)) return;
  62. complain(decl, sourceIndex, value.length);
  63. });
  64. });
  65. /**
  66. * @param {{unit: string | null, number: string | null}} dimension
  67. * @param {string} value
  68. * @param {number} valueTimeCount
  69. * @returns {number}
  70. */
  71. function calcTimeValueCount(dimension, value, valueTimeCount) {
  72. const { unit } = dimension;
  73. if (unit !== null) valueTimeCount++;
  74. if (value === ',') valueTimeCount = 0;
  75. return valueTimeCount;
  76. }
  77. /**
  78. * @param {string} propertyName
  79. * @returns {boolean}
  80. */
  81. function isIgnoredProperty(propertyName) {
  82. if (ignoreDelay && DELAY_PROPERTIES.has(propertyName)) {
  83. return true;
  84. }
  85. return false;
  86. }
  87. /**
  88. * @param {import('postcss-value-parser').Dimension | {unit: null, number: null}} dimension
  89. * @returns {boolean}
  90. */
  91. function isAcceptableTime(dimension) {
  92. const { unit, number } = dimension;
  93. if (unit === null || number === null) return true;
  94. const numTime = Number(number);
  95. if (numTime <= 0) {
  96. return true;
  97. }
  98. const timeUnit = unit.toLowerCase();
  99. if (timeUnit === 'ms' && numTime < minimum) {
  100. return false;
  101. }
  102. if (timeUnit === 's' && numTime * 1000 < minimum) {
  103. return false;
  104. }
  105. return true;
  106. }
  107. /**
  108. * @param {import('postcss').Declaration} decl
  109. * @param {number} offset
  110. * @param {number} length
  111. * @returns {void}
  112. */
  113. function complain(decl, offset, length) {
  114. const index = declarationValueIndex(decl) + offset;
  115. const endIndex = index + length;
  116. report({
  117. result,
  118. ruleName,
  119. message: messages.expected(minimum),
  120. index,
  121. endIndex,
  122. node: decl,
  123. });
  124. }
  125. };
  126. };
  127. rule.ruleName = ruleName;
  128. rule.messages = messages;
  129. rule.meta = meta;
  130. module.exports = rule;