UNPKG

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