index.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. 'use strict';
  2. const getDeclarationValue = require('../../utils/getDeclarationValue');
  3. const report = require('../../utils/report');
  4. const ruleMessages = require('../../utils/ruleMessages');
  5. const setDeclarationValue = require('../../utils/setDeclarationValue');
  6. const validateOptions = require('../../utils/validateOptions');
  7. const valueParser = require('postcss-value-parser');
  8. const { isNumber } = require('../../utils/validateTypes');
  9. const ruleName = 'function-max-empty-lines';
  10. const messages = ruleMessages(ruleName, {
  11. expected: (max) => `Expected no more than ${max} empty ${max === 1 ? 'line' : 'lines'}`,
  12. });
  13. const meta = {
  14. url: 'https://stylelint.io/user-guide/rules/function-max-empty-lines',
  15. fixable: true,
  16. deprecated: true,
  17. };
  18. /**
  19. * @param {import('postcss').Declaration} decl
  20. */
  21. function placeIndexOnValueStart(decl) {
  22. if (decl.raws.between == null) throw new Error('`between` must be present');
  23. return decl.prop.length + decl.raws.between.length - 1;
  24. }
  25. /** @type {import('stylelint').Rule} */
  26. const rule = (primary, _secondaryOptions, context) => {
  27. const maxAdjacentNewlines = primary + 1;
  28. return (root, result) => {
  29. const validOptions = validateOptions(result, ruleName, {
  30. actual: primary,
  31. possible: isNumber,
  32. });
  33. if (!validOptions) {
  34. return;
  35. }
  36. const violatedCRLFNewLinesRegex = new RegExp(`(?:\r\n){${maxAdjacentNewlines + 1},}`);
  37. const violatedLFNewLinesRegex = new RegExp(`\n{${maxAdjacentNewlines + 1},}`);
  38. const allowedLFNewLinesString = context.fix ? '\n'.repeat(maxAdjacentNewlines) : '';
  39. const allowedCRLFNewLinesString = context.fix ? '\r\n'.repeat(maxAdjacentNewlines) : '';
  40. root.walkDecls((decl) => {
  41. if (!decl.value.includes('(')) {
  42. return;
  43. }
  44. const stringValue = getDeclarationValue(decl);
  45. /** @type {Array<[string, string]>} */
  46. const splittedValue = [];
  47. let sourceIndexStart = 0;
  48. valueParser(stringValue).walk((node) => {
  49. if (
  50. node.type !== 'function' /* ignore non functions */ ||
  51. node.value.length === 0 /* ignore sass lists */
  52. ) {
  53. return;
  54. }
  55. const stringifiedNode = valueParser.stringify(node);
  56. if (
  57. !violatedLFNewLinesRegex.test(stringifiedNode) &&
  58. !violatedCRLFNewLinesRegex.test(stringifiedNode)
  59. ) {
  60. return;
  61. }
  62. if (context.fix) {
  63. const newNodeString = stringifiedNode
  64. .replace(new RegExp(violatedLFNewLinesRegex, 'gm'), allowedLFNewLinesString)
  65. .replace(new RegExp(violatedCRLFNewLinesRegex, 'gm'), allowedCRLFNewLinesString);
  66. splittedValue.push([
  67. stringValue.slice(sourceIndexStart, node.sourceIndex),
  68. newNodeString,
  69. ]);
  70. sourceIndexStart = node.sourceIndex + stringifiedNode.length;
  71. } else {
  72. report({
  73. message: messages.expected(primary),
  74. node: decl,
  75. index: placeIndexOnValueStart(decl) + node.sourceIndex,
  76. result,
  77. ruleName,
  78. });
  79. }
  80. });
  81. if (context.fix && splittedValue.length > 0) {
  82. const updatedValue =
  83. splittedValue.reduce((acc, curr) => acc + curr[0] + curr[1], '') +
  84. stringValue.slice(sourceIndexStart);
  85. setDeclarationValue(decl, updatedValue);
  86. }
  87. });
  88. };
  89. };
  90. rule.ruleName = ruleName;
  91. rule.messages = messages;
  92. rule.meta = meta;
  93. module.exports = rule;