UNPKG

9.92 kBJavaScriptView Raw
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
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19 meta: {
20 type: "layout",
21
22 docs: {
23 description: "require or disallow spacing between function identifiers and their invocations",
24 category: "Stylistic Issues",
25 recommended: false,
26 url: "https://eslint.org/docs/rules/func-call-spacing"
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: {
66 unexpectedWhitespace: "Unexpected whitespace between function name and paren.",
67 unexpectedNewline: "Unexpected newline between function name and paren.",
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;
76 const sourceCode = context.getSourceCode();
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,
119 loc: {
120 start: leftToken.loc.end,
121 end: {
122 line: rightToken.loc.start.line,
123 column: rightToken.loc.start.column - 1
124 }
125 },
126 messageId: "unexpectedWhitespace",
127 fix(fixer) {
128
129 // Don't remove comments.
130 if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
131 return null;
132 }
133
134 // If `?.` exists, it doesn't hide no-unexpected-multiline errors
135 if (node.optional) {
136 return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?.");
137 }
138
139 /*
140 * Only autofix if there is no newline
141 * https://github.com/eslint/eslint/issues/7787
142 */
143 if (hasNewline) {
144 return null;
145 }
146 return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
147 }
148 });
149 } else if (!never && !hasWhitespace) {
150 context.report({
151 node,
152 loc: {
153 start: {
154 line: leftToken.loc.end.line,
155 column: leftToken.loc.end.column - 1
156 },
157 end: rightToken.loc.start
158 },
159 messageId: "missing",
160 fix(fixer) {
161 if (node.optional) {
162 return null; // Not sure if inserting a space to either before/after `?.` token.
163 }
164 return fixer.insertTextBefore(rightToken, " ");
165 }
166 });
167 } else if (!never && !allowNewlines && hasNewline) {
168 context.report({
169 node,
170 loc: {
171 start: leftToken.loc.end,
172 end: rightToken.loc.start
173 },
174 messageId: "unexpectedNewline",
175 fix(fixer) {
176
177 /*
178 * Only autofix if there is no newline
179 * https://github.com/eslint/eslint/issues/7787
180 * But if `?.` exists, it doesn't hide no-unexpected-multiline errors
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, " ?. ");
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);
211 const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken);
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};