index.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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. fixable: true,
  22. };
  23. /** @type {import('stylelint').Rule} */
  24. const rule = (primary, _secondaryOptions, context) => {
  25. const checker = whitespaceChecker('space', primary, messages);
  26. return (root, result) => {
  27. const validOptions = validateOptions(result, ruleName, {
  28. actual: primary,
  29. possible: ['always', 'never', 'always-single-line', 'never-single-line'],
  30. });
  31. if (!validOptions) {
  32. return;
  33. }
  34. root.walkDecls((decl) => {
  35. // Ignore last declaration if there's no trailing semicolon
  36. const parentRule = decl.parent;
  37. if (!parentRule) throw new Error('A parent node must be present');
  38. if (!isAtRule(parentRule) && !isRule(parentRule)) {
  39. return;
  40. }
  41. if (!parentRule.raws.semicolon && parentRule.last === decl) {
  42. return;
  43. }
  44. const declString = decl.toString();
  45. checker.before({
  46. source: declString,
  47. index: declString.length,
  48. lineCheckStr: blockString(parentRule),
  49. err: (m) => {
  50. if (context.fix) {
  51. const value = getDeclarationValue(decl);
  52. if (primary.startsWith('always')) {
  53. if (decl.important) {
  54. decl.raws.important = ' !important ';
  55. } else {
  56. setDeclarationValue(decl, value.replace(/\s*$/, ' '));
  57. }
  58. return;
  59. }
  60. if (primary.startsWith('never')) {
  61. if (decl.raws.important) {
  62. decl.raws.important = decl.raws.important.replace(/\s*$/, '');
  63. } else {
  64. setDeclarationValue(decl, value.replace(/\s*$/, ''));
  65. }
  66. return;
  67. }
  68. }
  69. report({
  70. message: m,
  71. node: decl,
  72. index: decl.toString().length - 1,
  73. result,
  74. ruleName,
  75. });
  76. },
  77. });
  78. });
  79. };
  80. };
  81. rule.ruleName = ruleName;
  82. rule.messages = messages;
  83. rule.meta = meta;
  84. module.exports = rule;