UNPKG

3.91 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 rawNodeString = require('../../utils/rawNodeString');
8const report = require('../../utils/report');
9const ruleMessages = require('../../utils/ruleMessages');
10const validateOptions = require('../../utils/validateOptions');
11const whitespaceChecker = require('../../utils/whitespaceChecker');
12
13const ruleName = 'block-opening-brace-newline-after';
14
15const messages = ruleMessages(ruleName, {
16 expectedAfter: () => 'Expected newline after "{"',
17 expectedAfterMultiLine: () => 'Expected newline after "{" of a multi-line block',
18 rejectedAfterMultiLine: () => 'Unexpected whitespace after "{" of a multi-line block',
19});
20
21function rule(expectation, options, context) {
22 const checker = whitespaceChecker('newline', expectation, messages);
23
24 return (root, result) => {
25 const validOptions = validateOptions(result, ruleName, {
26 actual: expectation,
27 possible: ['always', 'always-multi-line', 'never-multi-line'],
28 });
29
30 if (!validOptions) {
31 return;
32 }
33
34 // Check both kinds of statement: rules and at-rules
35 root.walkRules(check);
36 root.walkAtRules(check);
37
38 function check(statement) {
39 // Return early if blockless or has an empty block
40 if (!hasBlock(statement) || hasEmptyBlock(statement)) {
41 return;
42 }
43
44 const backupCommentNextBefores = new Map();
45
46 // next node with checking newlines after comment
47 function nextNode(startNode) {
48 if (!startNode || !startNode.next) return null;
49
50 if (startNode.type === 'comment') {
51 const reNewLine = /\r?\n/;
52 const newLineMatch = reNewLine.test(startNode.raws.before);
53
54 const next = startNode.next();
55
56 if (next && newLineMatch && !reNewLine.test(next.raws.before)) {
57 backupCommentNextBefores.set(next, next.raws.before);
58 next.raws.before = startNode.raws.before;
59 }
60
61 return nextNode(next);
62 }
63
64 return startNode;
65 }
66
67 // Allow an end-of-line comment
68 const nodeToCheck = nextNode(statement.first);
69
70 if (!nodeToCheck) {
71 return;
72 }
73
74 checker.afterOneOnly({
75 source: rawNodeString(nodeToCheck),
76 index: -1,
77 lineCheckStr: blockString(statement),
78 err: (m) => {
79 if (context.fix) {
80 if (expectation.startsWith('always')) {
81 const index = nodeToCheck.raws.before.search(/\r?\n/);
82
83 if (index >= 0) {
84 nodeToCheck.raws.before = nodeToCheck.raws.before.slice(index);
85 } else {
86 nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before;
87 }
88
89 backupCommentNextBefores.delete(nodeToCheck);
90
91 return;
92 }
93
94 if (expectation === 'never-multi-line') {
95 // Restore the `before` of the node next to the comment node.
96 backupCommentNextBefores.forEach((before, node) => {
97 node.raws.before = before;
98 });
99 backupCommentNextBefores.clear();
100
101 // Fix
102 const reNewLine = /\r?\n/;
103 let fixTarget = statement.first;
104
105 while (fixTarget) {
106 if (reNewLine.test(fixTarget.raws.before)) {
107 fixTarget.raws.before = fixTarget.raws.before.replace(/\r?\n/g, '');
108 }
109
110 if (fixTarget.type !== 'comment') {
111 break;
112 }
113
114 fixTarget = fixTarget.next();
115 }
116 nodeToCheck.raws.before = '';
117
118 return;
119 }
120 }
121
122 report({
123 message: m,
124 node: statement,
125 index: beforeBlockString(statement, { noRawBefore: true }).length + 1,
126 result,
127 ruleName,
128 });
129 },
130 });
131
132 // Restore the `before` of the node next to the comment node.
133 backupCommentNextBefores.forEach((before, node) => {
134 node.raws.before = before;
135 });
136 }
137 };
138}
139
140rule.ruleName = ruleName;
141rule.messages = messages;
142module.exports = rule;