index.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. 'use strict';
  2. const blockString = require('../../utils/blockString');
  3. const rawNodeString = require('../../utils/rawNodeString');
  4. const report = require('../../utils/report');
  5. const ruleMessages = require('../../utils/ruleMessages');
  6. const validateOptions = require('../../utils/validateOptions');
  7. const whitespaceChecker = require('../../utils/whitespaceChecker');
  8. const { isAtRule, isRule } = require('../../utils/typeGuards');
  9. const ruleName = 'declaration-block-semicolon-space-after';
  10. const messages = ruleMessages(ruleName, {
  11. expectedAfter: () => 'Expected single space after ";"',
  12. rejectedAfter: () => 'Unexpected whitespace after ";"',
  13. expectedAfterSingleLine: () =>
  14. 'Expected single space after ";" in a single-line declaration block',
  15. rejectedAfterSingleLine: () =>
  16. 'Unexpected whitespace after ";" in a single-line declaration block',
  17. });
  18. const meta = {
  19. url: 'https://stylelint.io/user-guide/rules/declaration-block-semicolon-space-after',
  20. fixable: true,
  21. };
  22. /** @type {import('stylelint').Rule} */
  23. const rule = (primary, _secondaryOptions, context) => {
  24. const checker = whitespaceChecker('space', primary, messages);
  25. return (root, result) => {
  26. const validOptions = validateOptions(result, ruleName, {
  27. actual: primary,
  28. possible: ['always', 'never', 'always-single-line', 'never-single-line'],
  29. });
  30. if (!validOptions) {
  31. return;
  32. }
  33. root.walkDecls((decl) => {
  34. // Ignore last declaration if there's no trailing semicolon
  35. const parentRule = decl.parent;
  36. if (!parentRule) throw new Error('A parent node must be present');
  37. if (!isAtRule(parentRule) && !isRule(parentRule)) {
  38. return;
  39. }
  40. if (!parentRule.raws.semicolon && parentRule.last === decl) {
  41. return;
  42. }
  43. const nextDecl = decl.next();
  44. if (!nextDecl) {
  45. return;
  46. }
  47. checker.after({
  48. source: rawNodeString(nextDecl),
  49. index: -1,
  50. lineCheckStr: blockString(parentRule),
  51. err: (m) => {
  52. if (context.fix) {
  53. if (primary.startsWith('always')) {
  54. nextDecl.raws.before = ' ';
  55. return;
  56. }
  57. if (primary.startsWith('never')) {
  58. nextDecl.raws.before = '';
  59. return;
  60. }
  61. }
  62. report({
  63. message: m,
  64. node: decl,
  65. index: decl.toString().length + 1,
  66. result,
  67. ruleName,
  68. });
  69. },
  70. });
  71. });
  72. };
  73. };
  74. rule.ruleName = ruleName;
  75. rule.messages = messages;
  76. rule.meta = meta;
  77. module.exports = rule;