index.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use strict';
  2. const blockString = require('../../utils/blockString');
  3. const hasBlock = require('../../utils/hasBlock');
  4. const hasEmptyBlock = require('../../utils/hasEmptyBlock');
  5. const isSingleLineString = require('../../utils/isSingleLineString');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const validateOptions = require('../../utils/validateOptions');
  9. const ruleName = 'block-closing-brace-newline-before';
  10. const messages = ruleMessages(ruleName, {
  11. expectedBefore: 'Expected newline before "}"',
  12. expectedBeforeMultiLine: 'Expected newline before "}" of a multi-line block',
  13. rejectedBeforeMultiLine: 'Unexpected whitespace before "}" of a multi-line block',
  14. });
  15. const meta = {
  16. url: 'https://stylelint.io/user-guide/rules/list/block-closing-brace-newline-before',
  17. fixable: true,
  18. };
  19. /** @type {import('stylelint').Rule} */
  20. const rule = (primary, _secondaryOptions, context) => {
  21. return (root, result) => {
  22. const validOptions = validateOptions(result, ruleName, {
  23. actual: primary,
  24. possible: ['always', 'always-multi-line', 'never-multi-line'],
  25. });
  26. if (!validOptions) {
  27. return;
  28. }
  29. // Check both kinds of statements: rules and at-rules
  30. root.walkRules(check);
  31. root.walkAtRules(check);
  32. /**
  33. * @param {import('postcss').Rule | import('postcss').AtRule} statement
  34. */
  35. function check(statement) {
  36. // Return early if blockless or has empty block
  37. if (!hasBlock(statement) || hasEmptyBlock(statement)) {
  38. return;
  39. }
  40. // Ignore extra semicolon
  41. const after = (statement.raws.after || '').replace(/;+/, '');
  42. if (after === undefined) {
  43. return;
  44. }
  45. const blockIsMultiLine = !isSingleLineString(blockString(statement));
  46. const statementString = statement.toString();
  47. let index = statementString.length - 2;
  48. if (statementString[index - 1] === '\r') {
  49. index -= 1;
  50. }
  51. // We're really just checking whether a
  52. // newline *starts* the block's final space -- between
  53. // the last declaration and the closing brace. We can
  54. // ignore any other whitespace between them, because that
  55. // will be checked by the indentation rule.
  56. if (!after.startsWith('\n') && !after.startsWith('\r\n')) {
  57. if (primary === 'always') {
  58. complain(messages.expectedBefore);
  59. } else if (blockIsMultiLine && primary === 'always-multi-line') {
  60. complain(messages.expectedBeforeMultiLine);
  61. }
  62. }
  63. if (after !== '' && blockIsMultiLine && primary === 'never-multi-line') {
  64. complain(messages.rejectedBeforeMultiLine);
  65. }
  66. /**
  67. * @param {string} message
  68. */
  69. function complain(message) {
  70. if (context.fix) {
  71. const statementRaws = statement.raws;
  72. if (typeof statementRaws.after !== 'string') return;
  73. if (primary.startsWith('always')) {
  74. const firstWhitespaceIndex = statementRaws.after.search(/\s/);
  75. const newlineBefore =
  76. firstWhitespaceIndex >= 0
  77. ? statementRaws.after.slice(0, firstWhitespaceIndex)
  78. : statementRaws.after;
  79. const newlineAfter =
  80. firstWhitespaceIndex >= 0 ? statementRaws.after.slice(firstWhitespaceIndex) : '';
  81. const newlineIndex = newlineAfter.search(/\r?\n/);
  82. statementRaws.after =
  83. newlineIndex >= 0
  84. ? newlineBefore + newlineAfter.slice(newlineIndex)
  85. : newlineBefore + context.newline + newlineAfter;
  86. return;
  87. }
  88. if (primary === 'never-multi-line') {
  89. statementRaws.after = statementRaws.after.replace(/\s/g, '');
  90. return;
  91. }
  92. }
  93. report({
  94. message,
  95. result,
  96. ruleName,
  97. node: statement,
  98. index,
  99. });
  100. }
  101. }
  102. };
  103. };
  104. rule.ruleName = ruleName;
  105. rule.messages = messages;
  106. rule.meta = meta;
  107. module.exports = rule;