index.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. 'use strict';
  2. const blockString = require('../../utils/blockString');
  3. const getDeclarationValue = require('../../utils/getDeclarationValue');
  4. const report = require('../../utils/report');
  5. const ruleMessages = require('../../utils/ruleMessages');
  6. const setDeclarationValue = require('../../utils/setDeclarationValue');
  7. const validateOptions = require('../../utils/validateOptions');
  8. const whitespaceChecker = require('../../utils/whitespaceChecker');
  9. const { isAtRule, isRule } = require('../../utils/typeGuards');
  10. const ruleName = 'declaration-block-semicolon-space-before';
  11. const messages = ruleMessages(ruleName, {
  12. expectedBefore: () => 'Expected single space before ";"',
  13. rejectedBefore: () => 'Unexpected whitespace before ";"',
  14. expectedBeforeSingleLine: () =>
  15. 'Expected single space before ";" in a single-line declaration block',
  16. rejectedBeforeSingleLine: () =>
  17. 'Unexpected whitespace before ";" in a single-line declaration block',
  18. });
  19. const meta = {
  20. url: 'https://stylelint.io/user-guide/rules/list/declaration-block-semicolon-space-before',
  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 declString = decl.toString();
  44. checker.before({
  45. source: declString,
  46. index: declString.length,
  47. lineCheckStr: blockString(parentRule),
  48. err: (m) => {
  49. if (context.fix) {
  50. const value = getDeclarationValue(decl);
  51. if (primary.startsWith('always')) {
  52. if (decl.important) {
  53. decl.raws.important = ' !important ';
  54. } else {
  55. setDeclarationValue(decl, value.replace(/\s*$/, ' '));
  56. }
  57. return;
  58. }
  59. if (primary.startsWith('never')) {
  60. if (decl.raws.important) {
  61. decl.raws.important = decl.raws.important.replace(/\s*$/, '');
  62. } else {
  63. setDeclarationValue(decl, value.replace(/\s*$/, ''));
  64. }
  65. return;
  66. }
  67. }
  68. report({
  69. message: m,
  70. node: decl,
  71. index: decl.toString().length - 1,
  72. result,
  73. ruleName,
  74. });
  75. },
  76. });
  77. });
  78. };
  79. };
  80. rule.ruleName = ruleName;
  81. rule.messages = messages;
  82. rule.meta = meta;
  83. module.exports = rule;