UNPKG

3.41 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5const blockString = require('../../utils/blockString');
6const hasBlock = require('../../utils/hasBlock');
7const hasEmptyBlock = require('../../utils/hasEmptyBlock');
8const isSingleLineString = require('../../utils/isSingleLineString');
9const report = require('../../utils/report');
10const ruleMessages = require('../../utils/ruleMessages');
11const validateOptions = require('../../utils/validateOptions');
12
13const ruleName = 'block-closing-brace-newline-before';
14
15const messages = ruleMessages(ruleName, {
16 expectedBefore: 'Expected newline before "}"',
17 expectedBeforeMultiLine: 'Expected newline before "}" of a multi-line block',
18 rejectedBeforeMultiLine: 'Unexpected whitespace before "}" of a multi-line block',
19});
20
21function rule(expectation, options, context) {
22 return (root, result) => {
23 const validOptions = validateOptions(result, ruleName, {
24 actual: expectation,
25 possible: ['always', 'always-multi-line', 'never-multi-line'],
26 });
27
28 if (!validOptions) {
29 return;
30 }
31
32 // Check both kinds of statements: rules and at-rules
33 root.walkRules(check);
34 root.walkAtRules(check);
35
36 function check(statement) {
37 // Return early if blockless or has empty block
38 if (!hasBlock(statement) || hasEmptyBlock(statement)) {
39 return;
40 }
41
42 // Ignore extra semicolon
43 const after = (statement.raws.after || '').replace(/;+/, '');
44
45 if (after === undefined) {
46 return;
47 }
48
49 const blockIsMultiLine = !isSingleLineString(blockString(statement));
50 const statementString = statement.toString();
51
52 let index = statementString.length - 2;
53
54 if (statementString[index - 1] === '\r') {
55 index -= 1;
56 }
57
58 // We're really just checking whether a
59 // newline *starts* the block's final space -- between
60 // the last declaration and the closing brace. We can
61 // ignore any other whitespace between them, because that
62 // will be checked by the indentation rule.
63 if (!after.startsWith('\n') && !after.startsWith('\r\n')) {
64 if (expectation === 'always') {
65 complain(messages.expectedBefore);
66 } else if (blockIsMultiLine && expectation === 'always-multi-line') {
67 complain(messages.expectedBeforeMultiLine);
68 }
69 }
70
71 if (after !== '' && blockIsMultiLine && expectation === 'never-multi-line') {
72 complain(messages.rejectedBeforeMultiLine);
73 }
74
75 function complain(message) {
76 if (context.fix) {
77 if (expectation.startsWith('always')) {
78 const firstWhitespaceIndex = statement.raws.after.search(/\s/);
79 const newlineBefore =
80 firstWhitespaceIndex >= 0
81 ? statement.raws.after.slice(0, firstWhitespaceIndex)
82 : statement.raws.after;
83 const newlineAfter =
84 firstWhitespaceIndex >= 0 ? statement.raws.after.slice(firstWhitespaceIndex) : '';
85 const newlineIndex = newlineAfter.search(/\r?\n/);
86
87 if (newlineIndex >= 0) {
88 statement.raws.after = newlineBefore + newlineAfter.slice(newlineIndex);
89 } else {
90 statement.raws.after = newlineBefore + context.newline + newlineAfter;
91 }
92
93 return;
94 }
95
96 if (expectation === 'never-multi-line') {
97 statement.raws.after = statement.raws.after.replace(/\s/g, '');
98
99 return;
100 }
101 }
102
103 report({
104 message,
105 result,
106 ruleName,
107 node: statement,
108 index,
109 });
110 }
111 }
112 };
113}
114
115rule.ruleName = ruleName;
116rule.messages = messages;
117module.exports = rule;