UNPKG

3.86 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5const _ = require('lodash');
6const atRuleParamIndex = require('../../utils/atRuleParamIndex');
7const declarationValueIndex = require('../../utils/declarationValueIndex');
8const isWhitespace = require('../../utils/isWhitespace');
9const report = require('../../utils/report');
10const ruleMessages = require('../../utils/ruleMessages');
11const styleSearch = require('style-search');
12const validateOptions = require('../../utils/validateOptions');
13
14const ruleName = 'function-whitespace-after';
15
16const messages = ruleMessages(ruleName, {
17 expected: 'Expected whitespace after ")"',
18 rejected: 'Unexpected whitespace after ")"',
19});
20
21const ACCEPTABLE_AFTER_CLOSING_PAREN = new Set([')', ',', '}', ':', '/', undefined]);
22
23function 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 // Allow for the next character to be a single empty space,
52 // another closing parenthesis, a comma, or the end of the value
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 // eslint-disable-next-line prefer-template
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
166rule.ruleName = ruleName;
167rule.messages = messages;
168module.exports = rule;