index.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. 'use strict';
  2. const declarationValueIndex = require('../../utils/declarationValueIndex');
  3. const functionArgumentsSearch = require('../../utils/functionArgumentsSearch');
  4. const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
  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 ruleName = 'function-linear-gradient-no-nonstandard-direction';
  11. const messages = ruleMessages(ruleName, {
  12. rejected: 'Unexpected nonstandard direction',
  13. });
  14. const meta = {
  15. url: 'https://stylelint.io/user-guide/rules/function-linear-gradient-no-nonstandard-direction',
  16. };
  17. /**
  18. * @param {string} source
  19. * @param {boolean} withToPrefix
  20. */
  21. function isStandardDirection(source, withToPrefix) {
  22. const regexp = withToPrefix
  23. ? /^to (top|left|bottom|right)(?: (top|left|bottom|right))?$/
  24. : /^(top|left|bottom|right)(?: (top|left|bottom|right))?$/;
  25. const matches = source.match(regexp);
  26. if (!matches) {
  27. return false;
  28. }
  29. if (matches.length === 2) {
  30. return true;
  31. }
  32. // Cannot repeat side-or-corner, e.g. "to top top"
  33. if (matches.length === 3 && matches[1] !== matches[2]) {
  34. return true;
  35. }
  36. return false;
  37. }
  38. /** @type {import('stylelint').Rule} */
  39. const rule = (primary) => {
  40. return (root, result) => {
  41. const validOptions = validateOptions(result, ruleName, { actual: primary });
  42. if (!validOptions) {
  43. return;
  44. }
  45. root.walkDecls((decl) => {
  46. valueParser(decl.value).walk((valueNode) => {
  47. if (valueNode.type !== 'function') {
  48. return;
  49. }
  50. functionArgumentsSearch(
  51. valueParser.stringify(valueNode).toLowerCase(),
  52. /^(-webkit-|-moz-|-o-|-ms-)?linear-gradient$/i,
  53. (expression, expressionIndex) => {
  54. const args = expression.split(',');
  55. const firstArg = (args[0] || '').trim();
  56. // If the first arg is not standard, return early
  57. if (!isStandardSyntaxValue(firstArg)) {
  58. return;
  59. }
  60. // If the first character is a number, we can assume the user intends an angle
  61. if (/[\d.]/.test(firstArg.charAt(0))) {
  62. if (/^[\d.]+(?:deg|grad|rad|turn)$/.test(firstArg)) {
  63. return;
  64. }
  65. complain();
  66. return;
  67. }
  68. // The first argument may not be a direction: it may be an angle,
  69. // or a color stop (in which case user gets default direction, "to bottom")
  70. // cf. https://drafts.csswg.org/css-images-3/#linear-gradient-syntax
  71. if (!/left|right|top|bottom/.test(firstArg)) {
  72. return;
  73. }
  74. const withToPrefix = !vendor.prefix(valueNode.value);
  75. if (!isStandardDirection(firstArg, withToPrefix)) {
  76. complain();
  77. }
  78. function complain() {
  79. const index = declarationValueIndex(decl) + valueNode.sourceIndex + expressionIndex;
  80. const endIndex = index + (args[0] || '').trimEnd().length;
  81. report({
  82. message: messages.rejected,
  83. node: decl,
  84. index,
  85. endIndex,
  86. result,
  87. ruleName,
  88. });
  89. }
  90. },
  91. );
  92. });
  93. });
  94. };
  95. };
  96. rule.ruleName = ruleName;
  97. rule.messages = messages;
  98. rule.meta = meta;
  99. module.exports = rule;