UNPKG

3.4 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const blockString = require('../../utils/blockString');
5const hasBlock = require('../../utils/hasBlock');
6const optionsMatches = require('../../utils/optionsMatches');
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-closing-brace-newline-after';
14
15const messages = ruleMessages(ruleName, {
16 expectedAfter: () => 'Expected newline after "}"',
17 expectedAfterSingleLine: () => 'Expected newline after "}" of a single-line block',
18 rejectedAfterSingleLine: () => 'Unexpected whitespace after "}" of a single-line block',
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(
28 result,
29 ruleName,
30 {
31 actual: expectation,
32 possible: [
33 'always',
34 'always-single-line',
35 'never-single-line',
36 'always-multi-line',
37 'never-multi-line',
38 ],
39 },
40 {
41 actual: options,
42 possible: {
43 ignoreAtRules: [_.isString],
44 },
45 optional: true,
46 },
47 );
48
49 if (!validOptions) {
50 return;
51 }
52
53 // Check both kinds of statements: rules and at-rules
54 root.walkRules(check);
55 root.walkAtRules(check);
56
57 function check(statement) {
58 if (!hasBlock(statement)) {
59 return;
60 }
61
62 if (optionsMatches(options, 'ignoreAtRules', statement.name)) {
63 return;
64 }
65
66 const nextNode = statement.next();
67
68 if (!nextNode) {
69 return;
70 }
71
72 // Allow an end-of-line comment x spaces after the brace
73 const nextNodeIsSingleLineComment =
74 nextNode.type === 'comment' &&
75 !/[^ ]/.test(nextNode.raws.before || '') &&
76 !nextNode.toString().includes('\n');
77
78 const nodeToCheck = nextNodeIsSingleLineComment ? nextNode.next() : nextNode;
79
80 if (!nodeToCheck) {
81 return;
82 }
83
84 let reportIndex = statement.toString().length;
85 let source = rawNodeString(nodeToCheck);
86
87 // Skip a semicolon at the beginning, if any
88 if (source && source.startsWith(';')) {
89 source = source.slice(1);
90 reportIndex++;
91 }
92
93 // Only check one after, because there might be other
94 // spaces handled by the indentation rule
95 checker.afterOneOnly({
96 source,
97 index: -1,
98 lineCheckStr: blockString(statement),
99 err: (msg) => {
100 if (context.fix) {
101 if (expectation.startsWith('always')) {
102 const index = nodeToCheck.raws.before.search(/\r?\n/);
103
104 if (index >= 0) {
105 nodeToCheck.raws.before = nodeToCheck.raws.before.slice(index);
106 } else {
107 nodeToCheck.raws.before = context.newline + nodeToCheck.raws.before;
108 }
109
110 return;
111 }
112
113 if (expectation.startsWith('never')) {
114 nodeToCheck.raws.before = '';
115
116 return;
117 }
118 }
119
120 report({
121 message: msg,
122 node: statement,
123 index: reportIndex,
124 result,
125 ruleName,
126 });
127 },
128 });
129 }
130 };
131}
132
133rule.ruleName = ruleName;
134rule.messages = messages;
135module.exports = rule;