UNPKG

3.08 kBJavaScriptView Raw
1'use strict';
2
3const beforeBlockString = require('../../utils/beforeBlockString');
4const blockString = require('../../utils/blockString');
5const hasBlock = require('../../utils/hasBlock');
6const hasEmptyBlock = require('../../utils/hasEmptyBlock');
7const report = require('../../utils/report');
8const ruleMessages = require('../../utils/ruleMessages');
9const validateOptions = require('../../utils/validateOptions');
10const whitespaceChecker = require('../../utils/whitespaceChecker');
11
12const ruleName = 'block-opening-brace-newline-before';
13
14const messages = ruleMessages(ruleName, {
15 expectedBefore: () => 'Expected newline before "{"',
16 expectedBeforeSingleLine: () => 'Expected newline before "{" of a single-line block',
17 rejectedBeforeSingleLine: () => 'Unexpected whitespace before "{" of a single-line block',
18 expectedBeforeMultiLine: () => 'Expected newline 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('newline', primary, messages);
25
26 return (root, result) => {
27 const validOptions = validateOptions(result, ruleName, {
28 actual: primary,
29 possible: [
30 'always',
31 'always-single-line',
32 'never-single-line',
33 'always-multi-line',
34 'never-multi-line',
35 ],
36 });
37
38 if (!validOptions) {
39 return;
40 }
41
42 // Check both kinds of statement: rules and at-rules
43 root.walkRules(check);
44 root.walkAtRules(check);
45
46 /**
47 * @param {import('postcss').Rule | import('postcss').AtRule} statement
48 */
49 function check(statement) {
50 // Return early if blockless or has an empty block
51 if (!hasBlock(statement) || hasEmptyBlock(statement)) {
52 return;
53 }
54
55 const source = beforeBlockString(statement);
56 const beforeBraceNoRaw = beforeBlockString(statement, {
57 noRawBefore: true,
58 });
59
60 let index = beforeBraceNoRaw.length - 1;
61
62 if (beforeBraceNoRaw[index - 1] === '\r') {
63 index -= 1;
64 }
65
66 checker.beforeAllowingIndentation({
67 lineCheckStr: blockString(statement),
68 source,
69 index: source.length,
70 err: (m) => {
71 if (context.fix) {
72 const statementRaws = statement.raws;
73
74 if (typeof statementRaws.between !== 'string') return;
75
76 if (primary.startsWith('always')) {
77 const spaceIndex = statementRaws.between.search(/\s+$/);
78
79 if (spaceIndex >= 0) {
80 statement.raws.between =
81 statementRaws.between.slice(0, spaceIndex) +
82 context.newline +
83 statementRaws.between.slice(spaceIndex);
84 } else {
85 statementRaws.between += context.newline;
86 }
87
88 return;
89 }
90
91 if (primary.startsWith('never')) {
92 statementRaws.between = statementRaws.between.replace(/\s*$/, '');
93
94 return;
95 }
96 }
97
98 report({
99 message: m,
100 node: statement,
101 index,
102 result,
103 ruleName,
104 });
105 },
106 });
107 }
108 };
109};
110
111rule.ruleName = ruleName;
112rule.messages = messages;
113module.exports = rule;