index.mjs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { isAtRule, isRoot, isRule } from '../../utils/typeGuards.mjs';
  2. import blockString from '../../utils/blockString.mjs';
  3. import fixEmptyLinesBefore from '../../utils/fixEmptyLinesBefore.mjs';
  4. import hasEmptyLine from '../../utils/hasEmptyLine.mjs';
  5. import isAfterComment from '../../utils/isAfterComment.mjs';
  6. import isAfterStandardPropertyDeclaration from '../../utils/isAfterStandardPropertyDeclaration.mjs';
  7. import isCustomProperty from '../../utils/isCustomProperty.mjs';
  8. import isFirstNested from '../../utils/isFirstNested.mjs';
  9. import isFirstNodeOfRoot from '../../utils/isFirstNodeOfRoot.mjs';
  10. import isSingleLineString from '../../utils/isSingleLineString.mjs';
  11. import isStandardSyntaxDeclaration from '../../utils/isStandardSyntaxDeclaration.mjs';
  12. import optionsMatches from '../../utils/optionsMatches.mjs';
  13. import report from '../../utils/report.mjs';
  14. import ruleMessages from '../../utils/ruleMessages.mjs';
  15. import validateOptions from '../../utils/validateOptions.mjs';
  16. const ruleName = 'declaration-empty-line-before';
  17. const messages = ruleMessages(ruleName, {
  18. expected: 'Expected empty line before declaration',
  19. rejected: 'Unexpected empty line before declaration',
  20. });
  21. const meta = {
  22. url: 'https://stylelint.io/user-guide/rules/declaration-empty-line-before',
  23. fixable: true,
  24. };
  25. /** @type {import('stylelint').CoreRules[ruleName]} */
  26. const rule = (primary, secondaryOptions, context) => {
  27. return (root, result) => {
  28. const validOptions = validateOptions(
  29. result,
  30. ruleName,
  31. {
  32. actual: primary,
  33. possible: ['always', 'never'],
  34. },
  35. {
  36. actual: secondaryOptions,
  37. possible: {
  38. except: ['first-nested', 'after-comment', 'after-declaration'],
  39. ignore: [
  40. 'after-comment',
  41. 'after-declaration',
  42. 'first-nested',
  43. 'inside-single-line-block',
  44. ],
  45. },
  46. optional: true,
  47. },
  48. );
  49. if (!validOptions) {
  50. return;
  51. }
  52. root.walkDecls((decl) => {
  53. const prop = decl.prop;
  54. const parent = decl.parent;
  55. if (parent == null) {
  56. return;
  57. }
  58. // Ignore the first node
  59. if (isFirstNodeOfRoot(decl)) {
  60. return;
  61. }
  62. if (!isAtRule(parent) && !isRule(parent) && !isRoot(parent)) {
  63. return;
  64. }
  65. if (!isStandardSyntaxDeclaration(decl)) {
  66. return;
  67. }
  68. if (isCustomProperty(prop)) {
  69. return;
  70. }
  71. // Optionally ignore the node if a comment precedes it
  72. if (optionsMatches(secondaryOptions, 'ignore', 'after-comment') && isAfterComment(decl)) {
  73. return;
  74. }
  75. // Optionally ignore the node if a declaration precedes it
  76. if (
  77. optionsMatches(secondaryOptions, 'ignore', 'after-declaration') &&
  78. isAfterStandardPropertyDeclaration(decl)
  79. ) {
  80. return;
  81. }
  82. // Optionally ignore the node if it is the first nested
  83. if (optionsMatches(secondaryOptions, 'ignore', 'first-nested') && isFirstNested(decl)) {
  84. return;
  85. }
  86. // Optionally ignore nodes inside single-line blocks
  87. if (
  88. optionsMatches(secondaryOptions, 'ignore', 'inside-single-line-block') &&
  89. isSingleLineString(blockString(parent))
  90. ) {
  91. return;
  92. }
  93. let expectEmptyLineBefore = primary === 'always';
  94. // Optionally reverse the expectation if any exceptions apply
  95. if (
  96. (optionsMatches(secondaryOptions, 'except', 'first-nested') && isFirstNested(decl)) ||
  97. (optionsMatches(secondaryOptions, 'except', 'after-comment') && isAfterComment(decl)) ||
  98. (optionsMatches(secondaryOptions, 'except', 'after-declaration') &&
  99. isAfterStandardPropertyDeclaration(decl))
  100. ) {
  101. expectEmptyLineBefore = !expectEmptyLineBefore;
  102. }
  103. // Check for at least one empty line
  104. const hasEmptyLineBefore = hasEmptyLine(decl.raws.before);
  105. // Return if the expectation is met
  106. if (expectEmptyLineBefore === hasEmptyLineBefore) {
  107. return;
  108. }
  109. const message = expectEmptyLineBefore ? messages.expected : messages.rejected;
  110. const action = expectEmptyLineBefore ? 'add' : 'remove';
  111. // Fix
  112. const fix = () =>
  113. fixEmptyLinesBefore({
  114. node: decl,
  115. newline: context.newline,
  116. action,
  117. });
  118. report({
  119. message,
  120. messageArgs: [],
  121. node: decl,
  122. result,
  123. ruleName,
  124. fix: {
  125. apply: fix,
  126. node: decl.parent,
  127. },
  128. });
  129. });
  130. };
  131. };
  132. rule.ruleName = ruleName;
  133. rule.messages = messages;
  134. rule.meta = meta;
  135. export default rule;