UNPKG

3.05 kBJavaScriptView Raw
1'use strict';
2
3const addEmptyLineAfter = require('../../utils/addEmptyLineAfter');
4const blockString = require('../../utils/blockString');
5const hasBlock = require('../../utils/hasBlock');
6const hasEmptyBlock = require('../../utils/hasEmptyBlock');
7const hasEmptyLine = require('../../utils/hasEmptyLine');
8const isSingleLineString = require('../../utils/isSingleLineString');
9const optionsMatches = require('../../utils/optionsMatches');
10const removeEmptyLineAfter = require('../../utils/removeEmptyLinesAfter');
11const report = require('../../utils/report');
12const ruleMessages = require('../../utils/ruleMessages');
13const validateOptions = require('../../utils/validateOptions');
14
15const ruleName = 'block-closing-brace-empty-line-before';
16
17const messages = ruleMessages(ruleName, {
18 expected: 'Expected empty line before closing brace',
19 rejected: 'Unexpected empty line before closing brace',
20});
21
22function rule(expectation, options, context) {
23 return (root, result) => {
24 const validOptions = validateOptions(
25 result,
26 ruleName,
27 {
28 actual: expectation,
29 possible: ['always-multi-line', 'never'],
30 },
31 {
32 actual: options,
33 possible: {
34 except: ['after-closing-brace'],
35 },
36 optional: true,
37 },
38 );
39
40 if (!validOptions) {
41 return;
42 }
43
44 // Check both kinds of statements: rules and at-rules
45 root.walkRules(check);
46 root.walkAtRules(check);
47
48 function check(statement) {
49 // Return early if blockless or has empty block
50 if (!hasBlock(statement) || hasEmptyBlock(statement)) {
51 return;
52 }
53
54 // Get whitespace after ""}", ignoring extra semicolon
55 const before = (statement.raws.after || '').replace(/;+/, '');
56
57 // Calculate index
58 const statementString = statement.toString();
59 let index = statementString.length - 1;
60
61 if (statementString[index - 1] === '\r') {
62 index -= 1;
63 }
64
65 // Set expectation
66 const expectEmptyLineBefore = (() => {
67 const childNodeTypes = statement.nodes.map((item) => item.type);
68
69 // Reverse the primary options if `after-closing-brace` is set
70 if (
71 optionsMatches(options, 'except', 'after-closing-brace') &&
72 statement.type === 'atrule' &&
73 !childNodeTypes.includes('decl')
74 ) {
75 return expectation === 'never';
76 }
77
78 return Boolean(
79 expectation === 'always-multi-line' && !isSingleLineString(blockString(statement)),
80 );
81 })();
82
83 // Check for at least one empty line
84 const hasEmptyLineBefore = hasEmptyLine(before);
85
86 // Return if the expectation is met
87 if (expectEmptyLineBefore === hasEmptyLineBefore) {
88 return;
89 }
90
91 if (context.fix) {
92 if (expectEmptyLineBefore) {
93 addEmptyLineAfter(statement, context.newline);
94 } else {
95 removeEmptyLineAfter(statement, context.newline);
96 }
97
98 return;
99 }
100
101 const message = expectEmptyLineBefore ? messages.expected : messages.rejected;
102
103 report({
104 message,
105 result,
106 ruleName,
107 node: statement,
108 index,
109 });
110 }
111 };
112}
113
114rule.ruleName = ruleName;
115rule.messages = messages;
116module.exports = rule;