index.js 2.8 KB

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