UNPKG

2.72 kBJavaScriptView Raw
1'use strict';
2
3const blockString = require('../../utils/blockString');
4const hasBlock = require('../../utils/hasBlock');
5const hasEmptyBlock = require('../../utils/hasEmptyBlock');
6const report = require('../../utils/report');
7const ruleMessages = require('../../utils/ruleMessages');
8const validateOptions = require('../../utils/validateOptions');
9const whitespaceChecker = require('../../utils/whitespaceChecker');
10
11const ruleName = 'block-closing-brace-space-before';
12
13const messages = ruleMessages(ruleName, {
14 expectedBefore: () => 'Expected single space before "}"',
15 rejectedBefore: () => 'Unexpected whitespace before "}"',
16 expectedBeforeSingleLine: () => 'Expected single space before "}" of a single-line block',
17 rejectedBeforeSingleLine: () => 'Unexpected whitespace before "}" of a single-line block',
18 expectedBeforeMultiLine: () => 'Expected single space before "}" of a multi-line block',
19 rejectedBeforeMultiLine: () => 'Unexpected whitespace before "}" of a multi-line block',
20});
21
22/** @type {import('stylelint').Rule} */
23const rule = (primary, _secondaryOptions, context) => {
24 const checker = whitespaceChecker('space', primary, messages);
25
26 return (root, result) => {
27 const validOptions = validateOptions(result, ruleName, {
28 actual: primary,
29 possible: [
30 'always',
31 'never',
32 'always-single-line',
33 'never-single-line',
34 'always-multi-line',
35 'never-multi-line',
36 ],
37 });
38
39 if (!validOptions) {
40 return;
41 }
42
43 // Check both kinds of statement: rules and at-rules
44 root.walkRules(check);
45 root.walkAtRules(check);
46
47 /**
48 * @param {import('postcss').Rule | import('postcss').AtRule} statement
49 */
50 function check(statement) {
51 // Return early if blockless or has empty block
52 if (!hasBlock(statement) || hasEmptyBlock(statement)) {
53 return;
54 }
55
56 const source = blockString(statement);
57 const statementString = statement.toString();
58
59 let index = statementString.length - 2;
60
61 if (statementString[index - 1] === '\r') {
62 index -= 1;
63 }
64
65 checker.before({
66 source,
67 index: source.length - 1,
68 err: (msg) => {
69 if (context.fix) {
70 const statementRaws = statement.raws;
71
72 if (typeof statementRaws.after !== 'string') return;
73
74 if (primary.startsWith('always')) {
75 statementRaws.after = statementRaws.after.replace(/\s*$/, ' ');
76
77 return;
78 }
79
80 if (primary.startsWith('never')) {
81 statementRaws.after = statementRaws.after.replace(/\s*$/, '');
82
83 return;
84 }
85 }
86
87 report({
88 message: msg,
89 node: statement,
90 index,
91 result,
92 ruleName,
93 });
94 },
95 });
96 }
97 };
98};
99
100rule.ruleName = ruleName;
101rule.messages = messages;
102module.exports = rule;