index.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. 'use strict';
  2. const addEmptyLineAfter = require('../../utils/addEmptyLineAfter');
  3. const blockString = require('../../utils/blockString');
  4. const hasBlock = require('../../utils/hasBlock');
  5. const hasEmptyBlock = require('../../utils/hasEmptyBlock');
  6. const hasEmptyLine = require('../../utils/hasEmptyLine');
  7. const isSingleLineString = require('../../utils/isSingleLineString');
  8. const optionsMatches = require('../../utils/optionsMatches');
  9. const removeEmptyLinesAfter = require('../../utils/removeEmptyLinesAfter');
  10. const report = require('../../utils/report');
  11. const ruleMessages = require('../../utils/ruleMessages');
  12. const validateOptions = require('../../utils/validateOptions');
  13. const ruleName = 'block-closing-brace-empty-line-before';
  14. const messages = ruleMessages(ruleName, {
  15. expected: 'Expected empty line before closing brace',
  16. rejected: 'Unexpected empty line before closing brace',
  17. });
  18. const meta = {
  19. url: 'https://stylelint.io/user-guide/rules/block-closing-brace-empty-line-before',
  20. fixable: true,
  21. deprecated: true,
  22. };
  23. /** @type {import('stylelint').Rule} */
  24. const rule = (primary, secondaryOptions, context) => {
  25. return (root, result) => {
  26. const validOptions = validateOptions(
  27. result,
  28. ruleName,
  29. {
  30. actual: primary,
  31. possible: ['always-multi-line', 'never'],
  32. },
  33. {
  34. actual: secondaryOptions,
  35. possible: {
  36. except: ['after-closing-brace'],
  37. },
  38. optional: true,
  39. },
  40. );
  41. if (!validOptions) {
  42. return;
  43. }
  44. // Check both kinds of statements: rules and at-rules
  45. root.walkRules(check);
  46. root.walkAtRules(check);
  47. /**
  48. * @param {import('postcss').Rule | import('postcss').AtRule} statement
  49. */
  50. function check(statement) {
  51. // Return early if blockless or has empty block
  52. if (!hasBlock(statement) || hasEmptyBlock(statement)) {
  53. return;
  54. }
  55. // Get whitespace after ""}", ignoring extra semicolon
  56. const before = (statement.raws.after || '').replace(/;+/, '');
  57. // Calculate index
  58. const statementString = statement.toString();
  59. let index = statementString.length - 1;
  60. if (statementString[index - 1] === '\r') {
  61. index -= 1;
  62. }
  63. // Set expectation
  64. const expectEmptyLineBefore = (() => {
  65. const childNodeTypes = statement.nodes.map((item) => item.type);
  66. // Reverse the primary options if `after-closing-brace` is set
  67. if (
  68. optionsMatches(secondaryOptions, 'except', 'after-closing-brace') &&
  69. statement.type === 'atrule' &&
  70. !childNodeTypes.includes('decl')
  71. ) {
  72. return primary === 'never';
  73. }
  74. return primary === 'always-multi-line' && !isSingleLineString(blockString(statement));
  75. })();
  76. // Check for at least one empty line
  77. const hasEmptyLineBefore = hasEmptyLine(before);
  78. // Return if the expectation is met
  79. if (expectEmptyLineBefore === hasEmptyLineBefore) {
  80. return;
  81. }
  82. if (context.fix) {
  83. const { newline } = context;
  84. if (typeof newline !== 'string') return;
  85. if (expectEmptyLineBefore) {
  86. addEmptyLineAfter(statement, newline);
  87. } else {
  88. removeEmptyLinesAfter(statement, newline);
  89. }
  90. return;
  91. }
  92. const message = expectEmptyLineBefore ? messages.expected : messages.rejected;
  93. report({
  94. message,
  95. result,
  96. ruleName,
  97. node: statement,
  98. index,
  99. });
  100. }
  101. };
  102. };
  103. rule.ruleName = ruleName;
  104. rule.messages = messages;
  105. rule.meta = meta;
  106. module.exports = rule;