index.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. 'use strict';
  2. const blockString = require('../../utils/blockString');
  3. const hasBlock = require('../../utils/hasBlock');
  4. const optionsMatches = require('../../utils/optionsMatches');
  5. const rawNodeString = require('../../utils/rawNodeString');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const validateOptions = require('../../utils/validateOptions');
  9. const whitespaceChecker = require('../../utils/whitespaceChecker');
  10. const { isString } = require('../../utils/validateTypes');
  11. const ruleName = 'block-closing-brace-newline-after';
  12. const messages = ruleMessages(ruleName, {
  13. expectedAfter: () => 'Expected newline after "}"',
  14. expectedAfterSingleLine: () => 'Expected newline after "}" of a single-line block',
  15. rejectedAfterSingleLine: () => 'Unexpected whitespace after "}" of a single-line block',
  16. expectedAfterMultiLine: () => 'Expected newline after "}" of a multi-line block',
  17. rejectedAfterMultiLine: () => 'Unexpected whitespace after "}" of a multi-line block',
  18. });
  19. const meta = {
  20. url: 'https://stylelint.io/user-guide/rules/block-closing-brace-newline-after',
  21. fixable: true,
  22. };
  23. /** @type {import('stylelint').Rule} */
  24. const rule = (primary, secondaryOptions, context) => {
  25. const checker = whitespaceChecker('newline', primary, messages);
  26. return (root, result) => {
  27. const validOptions = validateOptions(
  28. result,
  29. ruleName,
  30. {
  31. actual: primary,
  32. possible: [
  33. 'always',
  34. 'always-single-line',
  35. 'never-single-line',
  36. 'always-multi-line',
  37. 'never-multi-line',
  38. ],
  39. },
  40. {
  41. actual: secondaryOptions,
  42. possible: {
  43. ignoreAtRules: [isString],
  44. },
  45. optional: true,
  46. },
  47. );
  48. if (!validOptions) {
  49. return;
  50. }
  51. // Check both kinds of statements: rules and at-rules
  52. root.walkRules(check);
  53. root.walkAtRules(check);
  54. /**
  55. * @param {import('postcss').Rule | import('postcss').AtRule} statement
  56. */
  57. function check(statement) {
  58. if (!hasBlock(statement)) {
  59. return;
  60. }
  61. if (
  62. statement.type === 'atrule' &&
  63. optionsMatches(secondaryOptions, 'ignoreAtRules', statement.name)
  64. ) {
  65. return;
  66. }
  67. const nextNode = statement.next();
  68. if (!nextNode) {
  69. return;
  70. }
  71. // Allow an end-of-line comment x spaces after the brace
  72. const nextNodeIsSingleLineComment =
  73. nextNode.type === 'comment' &&
  74. !/[^ ]/.test(nextNode.raws.before || '') &&
  75. !nextNode.toString().includes('\n');
  76. const nodeToCheck = nextNodeIsSingleLineComment ? nextNode.next() : nextNode;
  77. if (!nodeToCheck) {
  78. return;
  79. }
  80. let reportIndex = statement.toString().length;
  81. let source = rawNodeString(nodeToCheck);
  82. // Skip a semicolon at the beginning, if any
  83. if (source && source.startsWith(';')) {
  84. source = source.slice(1);
  85. reportIndex++;
  86. }
  87. // Only check one after, because there might be other
  88. // spaces handled by the indentation rule
  89. checker.afterOneOnly({
  90. source,
  91. index: -1,
  92. lineCheckStr: blockString(statement),
  93. err: (msg) => {
  94. if (context.fix) {
  95. const nodeToCheckRaws = nodeToCheck.raws;
  96. if (typeof nodeToCheckRaws.before !== 'string') return;
  97. if (primary.startsWith('always')) {
  98. const index = nodeToCheckRaws.before.search(/\r?\n/);
  99. nodeToCheckRaws.before =
  100. index >= 0
  101. ? nodeToCheckRaws.before.slice(index)
  102. : context.newline + nodeToCheckRaws.before;
  103. return;
  104. }
  105. if (primary.startsWith('never')) {
  106. nodeToCheckRaws.before = '';
  107. return;
  108. }
  109. }
  110. report({
  111. message: msg,
  112. node: statement,
  113. index: reportIndex,
  114. result,
  115. ruleName,
  116. });
  117. },
  118. });
  119. }
  120. };
  121. };
  122. rule.ruleName = ruleName;
  123. rule.messages = messages;
  124. rule.meta = meta;
  125. module.exports = rule;