1 |
|
2 |
|
3 | 'use strict';
|
4 |
|
5 | const _ = require('lodash');
|
6 | const atRuleParamIndex = require('../../utils/atRuleParamIndex');
|
7 | const declarationValueIndex = require('../../utils/declarationValueIndex');
|
8 | const isWhitespace = require('../../utils/isWhitespace');
|
9 | const report = require('../../utils/report');
|
10 | const ruleMessages = require('../../utils/ruleMessages');
|
11 | const styleSearch = require('style-search');
|
12 | const validateOptions = require('../../utils/validateOptions');
|
13 |
|
14 | const ruleName = 'function-whitespace-after';
|
15 |
|
16 | const messages = ruleMessages(ruleName, {
|
17 | expected: 'Expected whitespace after ")"',
|
18 | rejected: 'Unexpected whitespace after ")"',
|
19 | });
|
20 |
|
21 | const ACCEPTABLE_AFTER_CLOSING_PAREN = new Set([')', ',', '}', ':', '/', undefined]);
|
22 |
|
23 | function rule(expectation, options, context) {
|
24 | return (root, result) => {
|
25 | const validOptions = validateOptions(result, ruleName, {
|
26 | actual: expectation,
|
27 | possible: ['always', 'never'],
|
28 | });
|
29 |
|
30 | if (!validOptions) {
|
31 | return;
|
32 | }
|
33 |
|
34 | function check(node, value, getIndex, fix) {
|
35 | styleSearch(
|
36 | {
|
37 | source: value,
|
38 | target: ')',
|
39 | functionArguments: 'only',
|
40 | },
|
41 | (match) => {
|
42 | checkClosingParen(value, match.startIndex + 1, node, getIndex, fix);
|
43 | },
|
44 | );
|
45 | }
|
46 |
|
47 | function checkClosingParen(source, index, node, getIndex, fix) {
|
48 | const nextChar = source[index];
|
49 |
|
50 | if (expectation === 'always') {
|
51 |
|
52 |
|
53 | if (nextChar === ' ') {
|
54 | return;
|
55 | }
|
56 |
|
57 | if (nextChar === '\n') {
|
58 | return;
|
59 | }
|
60 |
|
61 | if (source.substr(index, 2) === '\r\n') {
|
62 | return;
|
63 | }
|
64 |
|
65 | if (ACCEPTABLE_AFTER_CLOSING_PAREN.has(nextChar)) {
|
66 | return;
|
67 | }
|
68 |
|
69 | if (fix) {
|
70 | fix(index);
|
71 |
|
72 | return;
|
73 | }
|
74 |
|
75 | report({
|
76 | message: messages.expected,
|
77 | node,
|
78 | index: getIndex(node) + index,
|
79 | result,
|
80 | ruleName,
|
81 | });
|
82 | } else if (expectation === 'never') {
|
83 | if (isWhitespace(nextChar)) {
|
84 | if (fix) {
|
85 | fix(index);
|
86 |
|
87 | return;
|
88 | }
|
89 |
|
90 | report({
|
91 | message: messages.rejected,
|
92 | node,
|
93 | index: getIndex(node) + index,
|
94 | result,
|
95 | ruleName,
|
96 | });
|
97 | }
|
98 | }
|
99 | }
|
100 |
|
101 | function createFixer(value) {
|
102 | let fixed = '';
|
103 | let lastIndex = 0;
|
104 | let applyFix;
|
105 |
|
106 | if (expectation === 'always') {
|
107 | applyFix = (index) => {
|
108 |
|
109 | fixed += value.slice(lastIndex, index) + ' ';
|
110 | lastIndex = index;
|
111 | };
|
112 | } else if (expectation === 'never') {
|
113 | applyFix = (index) => {
|
114 | let whitespaceEndIndex = index + 1;
|
115 |
|
116 | while (whitespaceEndIndex < value.length && isWhitespace(value[whitespaceEndIndex])) {
|
117 | whitespaceEndIndex++;
|
118 | }
|
119 | fixed += value.slice(lastIndex, index);
|
120 | lastIndex = whitespaceEndIndex;
|
121 | };
|
122 | }
|
123 |
|
124 | return {
|
125 | applyFix,
|
126 | get hasFixed() {
|
127 | return Boolean(lastIndex);
|
128 | },
|
129 | get fixed() {
|
130 | return fixed + value.slice(lastIndex);
|
131 | },
|
132 | };
|
133 | }
|
134 |
|
135 | root.walkAtRules(/^import$/i, (atRule) => {
|
136 | const param = _.get(atRule, 'raws.params.raw', atRule.params);
|
137 | const fixer = context.fix && createFixer(param);
|
138 |
|
139 | check(atRule, param, atRuleParamIndex, fixer && fixer.applyFix);
|
140 |
|
141 | if (fixer && fixer.hasFixed) {
|
142 | if (atRule.raws.params) {
|
143 | atRule.raws.params.raw = fixer.fixed;
|
144 | } else {
|
145 | atRule.params = fixer.fixed;
|
146 | }
|
147 | }
|
148 | });
|
149 | root.walkDecls((decl) => {
|
150 | const value = _.get(decl, 'raws.value.raw', decl.value);
|
151 | const fixer = context.fix && createFixer(value);
|
152 |
|
153 | check(decl, value, declarationValueIndex, fixer && fixer.applyFix);
|
154 |
|
155 | if (fixer && fixer.hasFixed) {
|
156 | if (decl.raws.value) {
|
157 | decl.raws.value.raw = fixer.fixed;
|
158 | } else {
|
159 | decl.value = fixer.fixed;
|
160 | }
|
161 | }
|
162 | });
|
163 | };
|
164 | }
|
165 |
|
166 | rule.ruleName = ruleName;
|
167 | rule.messages = messages;
|
168 | module.exports = rule;
|