]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule to control spacing within function calls | |
3 | * @author Matt DuVall <http://www.mattduvall.com> | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Requirements | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | const astUtils = require("./utils/ast-utils"); | |
13 | ||
14 | //------------------------------------------------------------------------------ | |
15 | // Rule Definition | |
16 | //------------------------------------------------------------------------------ | |
17 | ||
34eeec05 | 18 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
19 | module.exports = { |
20 | meta: { | |
21 | type: "layout", | |
22 | ||
23 | docs: { | |
8f9d1d4d | 24 | description: "Require or disallow spacing between function identifiers and their invocations", |
eb39fafa | 25 | recommended: false, |
f2a92ac6 | 26 | url: "https://eslint.org/docs/latest/rules/func-call-spacing" |
eb39fafa DC |
27 | }, |
28 | ||
29 | fixable: "whitespace", | |
30 | ||
31 | schema: { | |
32 | anyOf: [ | |
33 | { | |
34 | type: "array", | |
35 | items: [ | |
36 | { | |
37 | enum: ["never"] | |
38 | } | |
39 | ], | |
40 | minItems: 0, | |
41 | maxItems: 1 | |
42 | }, | |
43 | { | |
44 | type: "array", | |
45 | items: [ | |
46 | { | |
47 | enum: ["always"] | |
48 | }, | |
49 | { | |
50 | type: "object", | |
51 | properties: { | |
52 | allowNewlines: { | |
53 | type: "boolean" | |
54 | } | |
55 | }, | |
56 | additionalProperties: false | |
57 | } | |
58 | ], | |
59 | minItems: 0, | |
60 | maxItems: 2 | |
61 | } | |
62 | ] | |
63 | }, | |
64 | ||
65 | messages: { | |
56c4a2cb DC |
66 | unexpectedWhitespace: "Unexpected whitespace between function name and paren.", |
67 | unexpectedNewline: "Unexpected newline between function name and paren.", | |
eb39fafa DC |
68 | missing: "Missing space between function name and paren." |
69 | } | |
70 | }, | |
71 | ||
72 | create(context) { | |
73 | ||
74 | const never = context.options[0] !== "always"; | |
75 | const allowNewlines = !never && context.options[1] && context.options[1].allowNewlines; | |
f2a92ac6 | 76 | const sourceCode = context.sourceCode; |
eb39fafa DC |
77 | const text = sourceCode.getText(); |
78 | ||
79 | /** | |
80 | * Check if open space is present in a function name | |
81 | * @param {ASTNode} node node to evaluate | |
82 | * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee. | |
83 | * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments. | |
84 | * @returns {void} | |
85 | * @private | |
86 | */ | |
87 | function checkSpacing(node, leftToken, rightToken) { | |
88 | const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, ""); | |
89 | const hasWhitespace = /\s/u.test(textBetweenTokens); | |
90 | const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens); | |
91 | ||
92 | /* | |
93 | * never allowNewlines hasWhitespace hasNewline message | |
94 | * F F F F Missing space between function name and paren. | |
95 | * F F F T (Invalid `!hasWhitespace && hasNewline`) | |
96 | * F F T T Unexpected newline between function name and paren. | |
97 | * F F T F (OK) | |
98 | * F T T F (OK) | |
99 | * F T T T (OK) | |
100 | * F T F T (Invalid `!hasWhitespace && hasNewline`) | |
101 | * F T F F Missing space between function name and paren. | |
102 | * T T F F (Invalid `never && allowNewlines`) | |
103 | * T T F T (Invalid `!hasWhitespace && hasNewline`) | |
104 | * T T T T (Invalid `never && allowNewlines`) | |
105 | * T T T F (Invalid `never && allowNewlines`) | |
106 | * T F T F Unexpected space between function name and paren. | |
107 | * T F T T Unexpected space between function name and paren. | |
108 | * T F F T (Invalid `!hasWhitespace && hasNewline`) | |
109 | * T F F F (OK) | |
110 | * | |
111 | * T T Unexpected space between function name and paren. | |
112 | * F F Missing space between function name and paren. | |
113 | * F F T Unexpected newline between function name and paren. | |
114 | */ | |
115 | ||
116 | if (never && hasWhitespace) { | |
117 | context.report({ | |
118 | node, | |
d3726936 TL |
119 | loc: { |
120 | start: leftToken.loc.end, | |
121 | end: { | |
122 | line: rightToken.loc.start.line, | |
123 | column: rightToken.loc.start.column - 1 | |
124 | } | |
125 | }, | |
56c4a2cb | 126 | messageId: "unexpectedWhitespace", |
eb39fafa DC |
127 | fix(fixer) { |
128 | ||
6f036462 TL |
129 | // Don't remove comments. |
130 | if (sourceCode.commentsExistBetween(leftToken, rightToken)) { | |
131 | return null; | |
132 | } | |
133 | ||
456be15e | 134 | // If `?.` exists, it doesn't hide no-unexpected-multiline errors |
6f036462 TL |
135 | if (node.optional) { |
136 | return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?."); | |
137 | } | |
138 | ||
eb39fafa DC |
139 | /* |
140 | * Only autofix if there is no newline | |
141 | * https://github.com/eslint/eslint/issues/7787 | |
142 | */ | |
6f036462 TL |
143 | if (hasNewline) { |
144 | return null; | |
eb39fafa | 145 | } |
6f036462 | 146 | return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); |
eb39fafa DC |
147 | } |
148 | }); | |
149 | } else if (!never && !hasWhitespace) { | |
150 | context.report({ | |
151 | node, | |
d3726936 TL |
152 | loc: { |
153 | start: { | |
154 | line: leftToken.loc.end.line, | |
155 | column: leftToken.loc.end.column - 1 | |
156 | }, | |
157 | end: rightToken.loc.start | |
158 | }, | |
eb39fafa DC |
159 | messageId: "missing", |
160 | fix(fixer) { | |
6f036462 TL |
161 | if (node.optional) { |
162 | return null; // Not sure if inserting a space to either before/after `?.` token. | |
163 | } | |
eb39fafa DC |
164 | return fixer.insertTextBefore(rightToken, " "); |
165 | } | |
166 | }); | |
167 | } else if (!never && !allowNewlines && hasNewline) { | |
168 | context.report({ | |
169 | node, | |
d3726936 TL |
170 | loc: { |
171 | start: leftToken.loc.end, | |
172 | end: rightToken.loc.start | |
173 | }, | |
56c4a2cb | 174 | messageId: "unexpectedNewline", |
eb39fafa | 175 | fix(fixer) { |
6f036462 TL |
176 | |
177 | /* | |
178 | * Only autofix if there is no newline | |
179 | * https://github.com/eslint/eslint/issues/7787 | |
456be15e | 180 | * But if `?.` exists, it doesn't hide no-unexpected-multiline errors |
6f036462 TL |
181 | */ |
182 | if (!node.optional) { | |
183 | return null; | |
184 | } | |
185 | ||
186 | // Don't remove comments. | |
187 | if (sourceCode.commentsExistBetween(leftToken, rightToken)) { | |
188 | return null; | |
189 | } | |
190 | ||
191 | const range = [leftToken.range[1], rightToken.range[0]]; | |
192 | const qdToken = sourceCode.getTokenAfter(leftToken); | |
193 | ||
194 | if (qdToken.range[0] === leftToken.range[1]) { | |
195 | return fixer.replaceTextRange(range, "?. "); | |
196 | } | |
197 | if (qdToken.range[1] === rightToken.range[0]) { | |
198 | return fixer.replaceTextRange(range, " ?."); | |
199 | } | |
200 | return fixer.replaceTextRange(range, " ?. "); | |
eb39fafa DC |
201 | } |
202 | }); | |
203 | } | |
204 | } | |
205 | ||
206 | return { | |
207 | "CallExpression, NewExpression"(node) { | |
208 | const lastToken = sourceCode.getLastToken(node); | |
209 | const lastCalleeToken = sourceCode.getLastToken(node.callee); | |
210 | const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken); | |
6f036462 | 211 | const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken); |
eb39fafa DC |
212 | |
213 | // Parens in NewExpression are optional | |
214 | if (!(parenToken && parenToken.range[1] < node.range[1])) { | |
215 | return; | |
216 | } | |
217 | ||
218 | checkSpacing(node, prevToken, parenToken); | |
219 | }, | |
220 | ||
221 | ImportExpression(node) { | |
222 | const leftToken = sourceCode.getFirstToken(node); | |
223 | const rightToken = sourceCode.getTokenAfter(leftToken); | |
224 | ||
225 | checkSpacing(node, leftToken, rightToken); | |
226 | } | |
227 | }; | |
228 | ||
229 | } | |
230 | }; |