]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/keyword-spacing.js
913cf4682f903acde73b0f1075a96cbd395cdbce
[pve-eslint.git] / eslint / lib / rules / keyword-spacing.js
1 /**
2 * @fileoverview Rule to enforce spacing before and after keywords.
3 * @author Toru Nagashima
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils"),
13 keywords = require("./utils/keywords");
14
15 //------------------------------------------------------------------------------
16 // Constants
17 //------------------------------------------------------------------------------
18
19 const PREV_TOKEN = /^[)\]}>]$/u;
20 const NEXT_TOKEN = /^(?:[([{<~!]|\+\+?|--?)$/u;
21 const PREV_TOKEN_M = /^[)\]}>*]$/u;
22 const NEXT_TOKEN_M = /^[{*]$/u;
23 const TEMPLATE_OPEN_PAREN = /\$\{$/u;
24 const TEMPLATE_CLOSE_PAREN = /^\}/u;
25 const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u;
26 const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]);
27
28 // check duplications.
29 (function() {
30 KEYS.sort();
31 for (let i = 1; i < KEYS.length; ++i) {
32 if (KEYS[i] === KEYS[i - 1]) {
33 throw new Error(`Duplication was found in the keyword list: ${KEYS[i]}`);
34 }
35 }
36 }());
37
38 //------------------------------------------------------------------------------
39 // Helpers
40 //------------------------------------------------------------------------------
41
42 /**
43 * Checks whether or not a given token is a "Template" token ends with "${".
44 * @param {Token} token A token to check.
45 * @returns {boolean} `true` if the token is a "Template" token ends with "${".
46 */
47 function isOpenParenOfTemplate(token) {
48 return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value);
49 }
50
51 /**
52 * Checks whether or not a given token is a "Template" token starts with "}".
53 * @param {Token} token A token to check.
54 * @returns {boolean} `true` if the token is a "Template" token starts with "}".
55 */
56 function isCloseParenOfTemplate(token) {
57 return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value);
58 }
59
60 //------------------------------------------------------------------------------
61 // Rule Definition
62 //------------------------------------------------------------------------------
63
64 module.exports = {
65 meta: {
66 type: "layout",
67
68 docs: {
69 description: "enforce consistent spacing before and after keywords",
70 category: "Stylistic Issues",
71 recommended: false,
72 url: "https://eslint.org/docs/rules/keyword-spacing"
73 },
74
75 fixable: "whitespace",
76
77 schema: [
78 {
79 type: "object",
80 properties: {
81 before: { type: "boolean", default: true },
82 after: { type: "boolean", default: true },
83 overrides: {
84 type: "object",
85 properties: KEYS.reduce((retv, key) => {
86 retv[key] = {
87 type: "object",
88 properties: {
89 before: { type: "boolean" },
90 after: { type: "boolean" }
91 },
92 additionalProperties: false
93 };
94 return retv;
95 }, {}),
96 additionalProperties: false
97 }
98 },
99 additionalProperties: false
100 }
101 ],
102 messages: {
103 expectedBefore: "Expected space(s) before \"{{value}}\".",
104 expectedAfter: "Expected space(s) after \"{{value}}\".",
105 unexpectedBefore: "Unexpected space(s) before \"{{value}}\".",
106 unexpectedAfter: "Unexpected space(s) after \"{{value}}\"."
107 }
108 },
109
110 create(context) {
111 const sourceCode = context.getSourceCode();
112
113 /**
114 * Reports a given token if there are not space(s) before the token.
115 * @param {Token} token A token to report.
116 * @param {RegExp} pattern A pattern of the previous token to check.
117 * @returns {void}
118 */
119 function expectSpaceBefore(token, pattern) {
120 const prevToken = sourceCode.getTokenBefore(token);
121
122 if (prevToken &&
123 (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
124 !isOpenParenOfTemplate(prevToken) &&
125 astUtils.isTokenOnSameLine(prevToken, token) &&
126 !sourceCode.isSpaceBetweenTokens(prevToken, token)
127 ) {
128 context.report({
129 loc: token.loc,
130 messageId: "expectedBefore",
131 data: token,
132 fix(fixer) {
133 return fixer.insertTextBefore(token, " ");
134 }
135 });
136 }
137 }
138
139 /**
140 * Reports a given token if there are space(s) before the token.
141 * @param {Token} token A token to report.
142 * @param {RegExp} pattern A pattern of the previous token to check.
143 * @returns {void}
144 */
145 function unexpectSpaceBefore(token, pattern) {
146 const prevToken = sourceCode.getTokenBefore(token);
147
148 if (prevToken &&
149 (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
150 !isOpenParenOfTemplate(prevToken) &&
151 astUtils.isTokenOnSameLine(prevToken, token) &&
152 sourceCode.isSpaceBetweenTokens(prevToken, token)
153 ) {
154 context.report({
155 loc: { start: prevToken.loc.end, end: token.loc.start },
156 messageId: "unexpectedBefore",
157 data: token,
158 fix(fixer) {
159 return fixer.removeRange([prevToken.range[1], token.range[0]]);
160 }
161 });
162 }
163 }
164
165 /**
166 * Reports a given token if there are not space(s) after the token.
167 * @param {Token} token A token to report.
168 * @param {RegExp} pattern A pattern of the next token to check.
169 * @returns {void}
170 */
171 function expectSpaceAfter(token, pattern) {
172 const nextToken = sourceCode.getTokenAfter(token);
173
174 if (nextToken &&
175 (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
176 !isCloseParenOfTemplate(nextToken) &&
177 astUtils.isTokenOnSameLine(token, nextToken) &&
178 !sourceCode.isSpaceBetweenTokens(token, nextToken)
179 ) {
180 context.report({
181 loc: token.loc,
182 messageId: "expectedAfter",
183 data: token,
184 fix(fixer) {
185 return fixer.insertTextAfter(token, " ");
186 }
187 });
188 }
189 }
190
191 /**
192 * Reports a given token if there are space(s) after the token.
193 * @param {Token} token A token to report.
194 * @param {RegExp} pattern A pattern of the next token to check.
195 * @returns {void}
196 */
197 function unexpectSpaceAfter(token, pattern) {
198 const nextToken = sourceCode.getTokenAfter(token);
199
200 if (nextToken &&
201 (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
202 !isCloseParenOfTemplate(nextToken) &&
203 astUtils.isTokenOnSameLine(token, nextToken) &&
204 sourceCode.isSpaceBetweenTokens(token, nextToken)
205 ) {
206
207 context.report({
208 loc: { start: token.loc.end, end: nextToken.loc.start },
209 messageId: "unexpectedAfter",
210 data: token,
211 fix(fixer) {
212 return fixer.removeRange([token.range[1], nextToken.range[0]]);
213 }
214 });
215 }
216 }
217
218 /**
219 * Parses the option object and determines check methods for each keyword.
220 * @param {Object|undefined} options The option object to parse.
221 * @returns {Object} - Normalized option object.
222 * Keys are keywords (there are for every keyword).
223 * Values are instances of `{"before": function, "after": function}`.
224 */
225 function parseOptions(options = {}) {
226 const before = options.before !== false;
227 const after = options.after !== false;
228 const defaultValue = {
229 before: before ? expectSpaceBefore : unexpectSpaceBefore,
230 after: after ? expectSpaceAfter : unexpectSpaceAfter
231 };
232 const overrides = (options && options.overrides) || {};
233 const retv = Object.create(null);
234
235 for (let i = 0; i < KEYS.length; ++i) {
236 const key = KEYS[i];
237 const override = overrides[key];
238
239 if (override) {
240 const thisBefore = ("before" in override) ? override.before : before;
241 const thisAfter = ("after" in override) ? override.after : after;
242
243 retv[key] = {
244 before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
245 after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
246 };
247 } else {
248 retv[key] = defaultValue;
249 }
250 }
251
252 return retv;
253 }
254
255 const checkMethodMap = parseOptions(context.options[0]);
256
257 /**
258 * Reports a given token if usage of spacing followed by the token is
259 * invalid.
260 * @param {Token} token A token to report.
261 * @param {RegExp} [pattern] Optional. A pattern of the previous
262 * token to check.
263 * @returns {void}
264 */
265 function checkSpacingBefore(token, pattern) {
266 checkMethodMap[token.value].before(token, pattern || PREV_TOKEN);
267 }
268
269 /**
270 * Reports a given token if usage of spacing preceded by the token is
271 * invalid.
272 * @param {Token} token A token to report.
273 * @param {RegExp} [pattern] Optional. A pattern of the next
274 * token to check.
275 * @returns {void}
276 */
277 function checkSpacingAfter(token, pattern) {
278 checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN);
279 }
280
281 /**
282 * Reports a given token if usage of spacing around the token is invalid.
283 * @param {Token} token A token to report.
284 * @returns {void}
285 */
286 function checkSpacingAround(token) {
287 checkSpacingBefore(token);
288 checkSpacingAfter(token);
289 }
290
291 /**
292 * Reports the first token of a given node if the first token is a keyword
293 * and usage of spacing around the token is invalid.
294 * @param {ASTNode|null} node A node to report.
295 * @returns {void}
296 */
297 function checkSpacingAroundFirstToken(node) {
298 const firstToken = node && sourceCode.getFirstToken(node);
299
300 if (firstToken && firstToken.type === "Keyword") {
301 checkSpacingAround(firstToken);
302 }
303 }
304
305 /**
306 * Reports the first token of a given node if the first token is a keyword
307 * and usage of spacing followed by the token is invalid.
308 *
309 * This is used for unary operators (e.g. `typeof`), `function`, and `super`.
310 * Other rules are handling usage of spacing preceded by those keywords.
311 * @param {ASTNode|null} node A node to report.
312 * @returns {void}
313 */
314 function checkSpacingBeforeFirstToken(node) {
315 const firstToken = node && sourceCode.getFirstToken(node);
316
317 if (firstToken && firstToken.type === "Keyword") {
318 checkSpacingBefore(firstToken);
319 }
320 }
321
322 /**
323 * Reports the previous token of a given node if the token is a keyword and
324 * usage of spacing around the token is invalid.
325 * @param {ASTNode|null} node A node to report.
326 * @returns {void}
327 */
328 function checkSpacingAroundTokenBefore(node) {
329 if (node) {
330 const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken);
331
332 checkSpacingAround(token);
333 }
334 }
335
336 /**
337 * Reports `async` or `function` keywords of a given node if usage of
338 * spacing around those keywords is invalid.
339 * @param {ASTNode} node A node to report.
340 * @returns {void}
341 */
342 function checkSpacingForFunction(node) {
343 const firstToken = node && sourceCode.getFirstToken(node);
344
345 if (firstToken &&
346 ((firstToken.type === "Keyword" && firstToken.value === "function") ||
347 firstToken.value === "async")
348 ) {
349 checkSpacingBefore(firstToken);
350 }
351 }
352
353 /**
354 * Reports `class` and `extends` keywords of a given node if usage of
355 * spacing around those keywords is invalid.
356 * @param {ASTNode} node A node to report.
357 * @returns {void}
358 */
359 function checkSpacingForClass(node) {
360 checkSpacingAroundFirstToken(node);
361 checkSpacingAroundTokenBefore(node.superClass);
362 }
363
364 /**
365 * Reports `if` and `else` keywords of a given node if usage of spacing
366 * around those keywords is invalid.
367 * @param {ASTNode} node A node to report.
368 * @returns {void}
369 */
370 function checkSpacingForIfStatement(node) {
371 checkSpacingAroundFirstToken(node);
372 checkSpacingAroundTokenBefore(node.alternate);
373 }
374
375 /**
376 * Reports `try`, `catch`, and `finally` keywords of a given node if usage
377 * of spacing around those keywords is invalid.
378 * @param {ASTNode} node A node to report.
379 * @returns {void}
380 */
381 function checkSpacingForTryStatement(node) {
382 checkSpacingAroundFirstToken(node);
383 checkSpacingAroundFirstToken(node.handler);
384 checkSpacingAroundTokenBefore(node.finalizer);
385 }
386
387 /**
388 * Reports `do` and `while` keywords of a given node if usage of spacing
389 * around those keywords is invalid.
390 * @param {ASTNode} node A node to report.
391 * @returns {void}
392 */
393 function checkSpacingForDoWhileStatement(node) {
394 checkSpacingAroundFirstToken(node);
395 checkSpacingAroundTokenBefore(node.test);
396 }
397
398 /**
399 * Reports `for` and `in` keywords of a given node if usage of spacing
400 * around those keywords is invalid.
401 * @param {ASTNode} node A node to report.
402 * @returns {void}
403 */
404 function checkSpacingForForInStatement(node) {
405 checkSpacingAroundFirstToken(node);
406 checkSpacingAroundTokenBefore(node.right);
407 }
408
409 /**
410 * Reports `for` and `of` keywords of a given node if usage of spacing
411 * around those keywords is invalid.
412 * @param {ASTNode} node A node to report.
413 * @returns {void}
414 */
415 function checkSpacingForForOfStatement(node) {
416 if (node.await) {
417 checkSpacingBefore(sourceCode.getFirstToken(node, 0));
418 checkSpacingAfter(sourceCode.getFirstToken(node, 1));
419 } else {
420 checkSpacingAroundFirstToken(node);
421 }
422 checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
423 }
424
425 /**
426 * Reports `import`, `export`, `as`, and `from` keywords of a given node if
427 * usage of spacing around those keywords is invalid.
428 *
429 * This rule handles the `*` token in module declarations.
430 *
431 * import*as A from "./a"; /*error Expected space(s) after "import".
432 * error Expected space(s) before "as".
433 * @param {ASTNode} node A node to report.
434 * @returns {void}
435 */
436 function checkSpacingForModuleDeclaration(node) {
437 const firstToken = sourceCode.getFirstToken(node);
438
439 checkSpacingBefore(firstToken, PREV_TOKEN_M);
440 checkSpacingAfter(firstToken, NEXT_TOKEN_M);
441
442 if (node.type === "ExportDefaultDeclaration") {
443 checkSpacingAround(sourceCode.getTokenAfter(firstToken));
444 }
445
446 if (node.type === "ExportAllDeclaration" && node.exported) {
447 const asToken = sourceCode.getTokenBefore(node.exported);
448
449 checkSpacingBefore(asToken, PREV_TOKEN_M);
450 }
451
452 if (node.source) {
453 const fromToken = sourceCode.getTokenBefore(node.source);
454
455 checkSpacingBefore(fromToken, PREV_TOKEN_M);
456 checkSpacingAfter(fromToken, NEXT_TOKEN_M);
457 }
458 }
459
460 /**
461 * Reports `as` keyword of a given node if usage of spacing around this
462 * keyword is invalid.
463 * @param {ASTNode} node A node to report.
464 * @returns {void}
465 */
466 function checkSpacingForImportNamespaceSpecifier(node) {
467 const asToken = sourceCode.getFirstToken(node, 1);
468
469 checkSpacingBefore(asToken, PREV_TOKEN_M);
470 }
471
472 /**
473 * Reports `static`, `get`, and `set` keywords of a given node if usage of
474 * spacing around those keywords is invalid.
475 * @param {ASTNode} node A node to report.
476 * @returns {void}
477 */
478 function checkSpacingForProperty(node) {
479 if (node.static) {
480 checkSpacingAroundFirstToken(node);
481 }
482 if (node.kind === "get" ||
483 node.kind === "set" ||
484 (
485 (node.method || node.type === "MethodDefinition") &&
486 node.value.async
487 )
488 ) {
489 const token = sourceCode.getTokenBefore(
490 node.key,
491 tok => {
492 switch (tok.value) {
493 case "get":
494 case "set":
495 case "async":
496 return true;
497 default:
498 return false;
499 }
500 }
501 );
502
503 if (!token) {
504 throw new Error("Failed to find token get, set, or async beside method name");
505 }
506
507
508 checkSpacingAround(token);
509 }
510 }
511
512 /**
513 * Reports `await` keyword of a given node if usage of spacing before
514 * this keyword is invalid.
515 * @param {ASTNode} node A node to report.
516 * @returns {void}
517 */
518 function checkSpacingForAwaitExpression(node) {
519 checkSpacingBefore(sourceCode.getFirstToken(node));
520 }
521
522 return {
523
524 // Statements
525 DebuggerStatement: checkSpacingAroundFirstToken,
526 WithStatement: checkSpacingAroundFirstToken,
527
528 // Statements - Control flow
529 BreakStatement: checkSpacingAroundFirstToken,
530 ContinueStatement: checkSpacingAroundFirstToken,
531 ReturnStatement: checkSpacingAroundFirstToken,
532 ThrowStatement: checkSpacingAroundFirstToken,
533 TryStatement: checkSpacingForTryStatement,
534
535 // Statements - Choice
536 IfStatement: checkSpacingForIfStatement,
537 SwitchStatement: checkSpacingAroundFirstToken,
538 SwitchCase: checkSpacingAroundFirstToken,
539
540 // Statements - Loops
541 DoWhileStatement: checkSpacingForDoWhileStatement,
542 ForInStatement: checkSpacingForForInStatement,
543 ForOfStatement: checkSpacingForForOfStatement,
544 ForStatement: checkSpacingAroundFirstToken,
545 WhileStatement: checkSpacingAroundFirstToken,
546
547 // Statements - Declarations
548 ClassDeclaration: checkSpacingForClass,
549 ExportNamedDeclaration: checkSpacingForModuleDeclaration,
550 ExportDefaultDeclaration: checkSpacingForModuleDeclaration,
551 ExportAllDeclaration: checkSpacingForModuleDeclaration,
552 FunctionDeclaration: checkSpacingForFunction,
553 ImportDeclaration: checkSpacingForModuleDeclaration,
554 VariableDeclaration: checkSpacingAroundFirstToken,
555
556 // Expressions
557 ArrowFunctionExpression: checkSpacingForFunction,
558 AwaitExpression: checkSpacingForAwaitExpression,
559 ClassExpression: checkSpacingForClass,
560 FunctionExpression: checkSpacingForFunction,
561 NewExpression: checkSpacingBeforeFirstToken,
562 Super: checkSpacingBeforeFirstToken,
563 ThisExpression: checkSpacingBeforeFirstToken,
564 UnaryExpression: checkSpacingBeforeFirstToken,
565 YieldExpression: checkSpacingBeforeFirstToken,
566
567 // Others
568 ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
569 MethodDefinition: checkSpacingForProperty,
570 Property: checkSpacingForProperty
571 };
572 }
573 };