index.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. // @ts-nocheck
  2. 'use strict';
  3. const _ = require('lodash');
  4. const declarationValueIndex = require('../../utils/declarationValueIndex');
  5. const keywordSets = require('../../reference/keywordSets');
  6. const optionsMatches = require('../../utils/optionsMatches');
  7. const postcss = require('postcss');
  8. const report = require('../../utils/report');
  9. const ruleMessages = require('../../utils/ruleMessages');
  10. const validateOptions = require('../../utils/validateOptions');
  11. const valueParser = require('postcss-value-parser');
  12. const ruleName = 'time-min-milliseconds';
  13. const messages = ruleMessages(ruleName, {
  14. expected: (time) => `Expected a minimum of ${time} milliseconds`,
  15. });
  16. const DELAY_PROPERTIES = ['animation-delay', 'transition-delay'];
  17. function rule(minimum, options) {
  18. return (root, result) => {
  19. const validOptions = validateOptions(
  20. result,
  21. ruleName,
  22. {
  23. actual: minimum,
  24. possible: _.isNumber,
  25. },
  26. {
  27. actual: options,
  28. possible: {
  29. ignore: ['delay'],
  30. },
  31. optional: true,
  32. },
  33. );
  34. if (!validOptions) {
  35. return;
  36. }
  37. root.walkDecls((decl) => {
  38. const propertyName = postcss.vendor.unprefixed(decl.prop.toLowerCase());
  39. if (
  40. keywordSets.longhandTimeProperties.has(propertyName) &&
  41. !isIgnoredProperty(propertyName) &&
  42. !isAcceptableTime(decl.value)
  43. ) {
  44. complain(decl);
  45. }
  46. if (keywordSets.shorthandTimeProperties.has(propertyName)) {
  47. const valueListList = postcss.list.comma(decl.value);
  48. for (const valueListString of valueListList) {
  49. const valueList = postcss.list.space(valueListString);
  50. if (optionsMatches(options, 'ignore', 'delay')) {
  51. // Check only duration time values
  52. const duration = getDuration(valueList);
  53. if (duration && !isAcceptableTime(duration)) {
  54. complain(decl, decl.value.indexOf(duration));
  55. }
  56. } else {
  57. // Check all time values
  58. for (const value of valueList) {
  59. if (!isAcceptableTime(value)) {
  60. complain(decl, decl.value.indexOf(value));
  61. }
  62. }
  63. }
  64. }
  65. }
  66. });
  67. /**
  68. * Get the duration within an `animation` or `transition` shorthand property value.
  69. *
  70. * @param {Node[]} valueList
  71. *
  72. * @returns {Node}
  73. */
  74. function getDuration(valueList) {
  75. for (const value of valueList) {
  76. const parsedTime = valueParser.unit(value);
  77. if (!parsedTime) continue;
  78. // The first numeric value in an animation shorthand is the duration.
  79. return value;
  80. }
  81. }
  82. function isIgnoredProperty(propertyName) {
  83. if (optionsMatches(options, 'ignore', 'delay') && DELAY_PROPERTIES.includes(propertyName)) {
  84. return true;
  85. }
  86. return false;
  87. }
  88. function isAcceptableTime(time) {
  89. const parsedTime = valueParser.unit(time);
  90. if (!parsedTime) return true;
  91. if (parsedTime.number <= 0) {
  92. return true;
  93. }
  94. if (parsedTime.unit.toLowerCase() === 'ms' && parsedTime.number < minimum) {
  95. return false;
  96. }
  97. if (parsedTime.unit.toLowerCase() === 's' && parsedTime.number * 1000 < minimum) {
  98. return false;
  99. }
  100. return true;
  101. }
  102. function complain(decl, offset = 0) {
  103. report({
  104. result,
  105. ruleName,
  106. message: messages.expected(minimum),
  107. index: declarationValueIndex(decl) + offset,
  108. node: decl,
  109. });
  110. }
  111. };
  112. }
  113. rule.ruleName = ruleName;
  114. rule.messages = messages;
  115. module.exports = rule;