2 * @fileoverview Rule to enforce spacing before and after keywords.
3 * @author Toru Nagashima
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils
= require("./utils/ast-utils"),
13 keywords
= require("./utils/keywords");
15 //------------------------------------------------------------------------------
17 //------------------------------------------------------------------------------
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"]);
28 // check duplications.
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]}`);
38 //------------------------------------------------------------------------------
40 //------------------------------------------------------------------------------
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 "${".
47 function isOpenParenOfTemplate(token
) {
48 return token
.type
=== "Template" && TEMPLATE_OPEN_PAREN
.test(token
.value
);
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 "}".
56 function isCloseParenOfTemplate(token
) {
57 return token
.type
=== "Template" && TEMPLATE_CLOSE_PAREN
.test(token
.value
);
60 //------------------------------------------------------------------------------
62 //------------------------------------------------------------------------------
69 description
: "enforce consistent spacing before and after keywords",
70 category
: "Stylistic Issues",
72 url
: "https://eslint.org/docs/rules/keyword-spacing"
75 fixable
: "whitespace",
81 before
: { type
: "boolean", default: true },
82 after
: { type
: "boolean", default: true },
85 properties
: KEYS
.reduce((retv
, key
) => {
89 before
: { type
: "boolean" },
90 after
: { type
: "boolean" }
92 additionalProperties
: false
96 additionalProperties
: false
99 additionalProperties
: false
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}}\"."
111 const sourceCode
= context
.getSourceCode();
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.
119 function expectSpaceBefore(token
, pattern
) {
120 const prevToken
= sourceCode
.getTokenBefore(token
);
123 (CHECK_TYPE
.test(prevToken
.type
) || pattern
.test(prevToken
.value
)) &&
124 !isOpenParenOfTemplate(prevToken
) &&
125 astUtils
.isTokenOnSameLine(prevToken
, token
) &&
126 !sourceCode
.isSpaceBetweenTokens(prevToken
, token
)
129 loc
: token
.loc
.start
,
130 messageId
: "expectedBefore",
133 return fixer
.insertTextBefore(token
, " ");
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.
145 function unexpectSpaceBefore(token
, pattern
) {
146 const prevToken
= sourceCode
.getTokenBefore(token
);
149 (CHECK_TYPE
.test(prevToken
.type
) || pattern
.test(prevToken
.value
)) &&
150 !isOpenParenOfTemplate(prevToken
) &&
151 astUtils
.isTokenOnSameLine(prevToken
, token
) &&
152 sourceCode
.isSpaceBetweenTokens(prevToken
, token
)
155 loc
: { start
: prevToken
.loc
.end
, end
: token
.loc
.start
},
156 messageId
: "unexpectedBefore",
159 return fixer
.removeRange([prevToken
.range
[1], token
.range
[0]]);
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.
171 function expectSpaceAfter(token
, pattern
) {
172 const nextToken
= sourceCode
.getTokenAfter(token
);
175 (CHECK_TYPE
.test(nextToken
.type
) || pattern
.test(nextToken
.value
)) &&
176 !isCloseParenOfTemplate(nextToken
) &&
177 astUtils
.isTokenOnSameLine(token
, nextToken
) &&
178 !sourceCode
.isSpaceBetweenTokens(token
, nextToken
)
181 loc
: token
.loc
.start
,
182 messageId
: "expectedAfter",
185 return fixer
.insertTextAfter(token
, " ");
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.
197 function unexpectSpaceAfter(token
, pattern
) {
198 const nextToken
= sourceCode
.getTokenAfter(token
);
201 (CHECK_TYPE
.test(nextToken
.type
) || pattern
.test(nextToken
.value
)) &&
202 !isCloseParenOfTemplate(nextToken
) &&
203 astUtils
.isTokenOnSameLine(token
, nextToken
) &&
204 sourceCode
.isSpaceBetweenTokens(token
, nextToken
)
208 loc
: { start
: token
.loc
.end
, end
: nextToken
.loc
.start
},
209 messageId
: "unexpectedAfter",
212 return fixer
.removeRange([token
.range
[1], nextToken
.range
[0]]);
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}`.
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
232 const overrides
= (options
&& options
.overrides
) || {};
233 const retv
= Object
.create(null);
235 for (let i
= 0; i
< KEYS
.length
; ++i
) {
237 const override
= overrides
[key
];
240 const thisBefore
= ("before" in override
) ? override
.before
: before
;
241 const thisAfter
= ("after" in override
) ? override
.after
: after
;
244 before
: thisBefore
? expectSpaceBefore
: unexpectSpaceBefore
,
245 after
: thisAfter
? expectSpaceAfter
: unexpectSpaceAfter
248 retv
[key
] = defaultValue
;
255 const checkMethodMap
= parseOptions(context
.options
[0]);
258 * Reports a given token if usage of spacing followed by the token is
260 * @param {Token} token A token to report.
261 * @param {RegExp} [pattern] Optional. A pattern of the previous
265 function checkSpacingBefore(token
, pattern
) {
266 checkMethodMap
[token
.value
].before(token
, pattern
|| PREV_TOKEN
);
270 * Reports a given token if usage of spacing preceded by the token is
272 * @param {Token} token A token to report.
273 * @param {RegExp} [pattern] Optional. A pattern of the next
277 function checkSpacingAfter(token
, pattern
) {
278 checkMethodMap
[token
.value
].after(token
, pattern
|| NEXT_TOKEN
);
282 * Reports a given token if usage of spacing around the token is invalid.
283 * @param {Token} token A token to report.
286 function checkSpacingAround(token
) {
287 checkSpacingBefore(token
);
288 checkSpacingAfter(token
);
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.
297 function checkSpacingAroundFirstToken(node
) {
298 const firstToken
= node
&& sourceCode
.getFirstToken(node
);
300 if (firstToken
&& firstToken
.type
=== "Keyword") {
301 checkSpacingAround(firstToken
);
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.
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.
314 function checkSpacingBeforeFirstToken(node
) {
315 const firstToken
= node
&& sourceCode
.getFirstToken(node
);
317 if (firstToken
&& firstToken
.type
=== "Keyword") {
318 checkSpacingBefore(firstToken
);
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.
328 function checkSpacingAroundTokenBefore(node
) {
330 const token
= sourceCode
.getTokenBefore(node
, astUtils
.isKeywordToken
);
332 checkSpacingAround(token
);
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.
342 function checkSpacingForFunction(node
) {
343 const firstToken
= node
&& sourceCode
.getFirstToken(node
);
346 ((firstToken
.type
=== "Keyword" && firstToken
.value
=== "function") ||
347 firstToken
.value
=== "async")
349 checkSpacingBefore(firstToken
);
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.
359 function checkSpacingForClass(node
) {
360 checkSpacingAroundFirstToken(node
);
361 checkSpacingAroundTokenBefore(node
.superClass
);
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.
370 function checkSpacingForIfStatement(node
) {
371 checkSpacingAroundFirstToken(node
);
372 checkSpacingAroundTokenBefore(node
.alternate
);
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.
381 function checkSpacingForTryStatement(node
) {
382 checkSpacingAroundFirstToken(node
);
383 checkSpacingAroundFirstToken(node
.handler
);
384 checkSpacingAroundTokenBefore(node
.finalizer
);
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.
393 function checkSpacingForDoWhileStatement(node
) {
394 checkSpacingAroundFirstToken(node
);
395 checkSpacingAroundTokenBefore(node
.test
);
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.
404 function checkSpacingForForInStatement(node
) {
405 checkSpacingAroundFirstToken(node
);
406 checkSpacingAroundTokenBefore(node
.right
);
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.
415 function checkSpacingForForOfStatement(node
) {
417 checkSpacingBefore(sourceCode
.getFirstToken(node
, 0));
418 checkSpacingAfter(sourceCode
.getFirstToken(node
, 1));
420 checkSpacingAroundFirstToken(node
);
422 checkSpacingAround(sourceCode
.getTokenBefore(node
.right
, astUtils
.isNotOpeningParenToken
));
426 * Reports `import`, `export`, `as`, and `from` keywords of a given node if
427 * usage of spacing around those keywords is invalid.
429 * This rule handles the `*` token in module declarations.
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.
436 function checkSpacingForModuleDeclaration(node
) {
437 const firstToken
= sourceCode
.getFirstToken(node
);
439 checkSpacingBefore(firstToken
, PREV_TOKEN_M
);
440 checkSpacingAfter(firstToken
, NEXT_TOKEN_M
);
442 if (node
.type
=== "ExportDefaultDeclaration") {
443 checkSpacingAround(sourceCode
.getTokenAfter(firstToken
));
446 if (node
.type
=== "ExportAllDeclaration" && node
.exported
) {
447 const asToken
= sourceCode
.getTokenBefore(node
.exported
);
449 checkSpacingBefore(asToken
, PREV_TOKEN_M
);
453 const fromToken
= sourceCode
.getTokenBefore(node
.source
);
455 checkSpacingBefore(fromToken
, PREV_TOKEN_M
);
456 checkSpacingAfter(fromToken
, NEXT_TOKEN_M
);
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.
466 function checkSpacingForImportNamespaceSpecifier(node
) {
467 const asToken
= sourceCode
.getFirstToken(node
, 1);
469 checkSpacingBefore(asToken
, PREV_TOKEN_M
);
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.
478 function checkSpacingForProperty(node
) {
480 checkSpacingAroundFirstToken(node
);
482 if (node
.kind
=== "get" ||
483 node
.kind
=== "set" ||
485 (node
.method
|| node
.type
=== "MethodDefinition") &&
489 const token
= sourceCode
.getTokenBefore(
504 throw new Error("Failed to find token get, set, or async beside method name");
508 checkSpacingAround(token
);
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.
518 function checkSpacingForAwaitExpression(node
) {
519 checkSpacingBefore(sourceCode
.getFirstToken(node
));
525 DebuggerStatement
: checkSpacingAroundFirstToken
,
526 WithStatement
: checkSpacingAroundFirstToken
,
528 // Statements - Control flow
529 BreakStatement
: checkSpacingAroundFirstToken
,
530 ContinueStatement
: checkSpacingAroundFirstToken
,
531 ReturnStatement
: checkSpacingAroundFirstToken
,
532 ThrowStatement
: checkSpacingAroundFirstToken
,
533 TryStatement
: checkSpacingForTryStatement
,
535 // Statements - Choice
536 IfStatement
: checkSpacingForIfStatement
,
537 SwitchStatement
: checkSpacingAroundFirstToken
,
538 SwitchCase
: checkSpacingAroundFirstToken
,
540 // Statements - Loops
541 DoWhileStatement
: checkSpacingForDoWhileStatement
,
542 ForInStatement
: checkSpacingForForInStatement
,
543 ForOfStatement
: checkSpacingForForOfStatement
,
544 ForStatement
: checkSpacingAroundFirstToken
,
545 WhileStatement
: checkSpacingAroundFirstToken
,
547 // Statements - Declarations
548 ClassDeclaration
: checkSpacingForClass
,
549 ExportNamedDeclaration
: checkSpacingForModuleDeclaration
,
550 ExportDefaultDeclaration
: checkSpacingForModuleDeclaration
,
551 ExportAllDeclaration
: checkSpacingForModuleDeclaration
,
552 FunctionDeclaration
: checkSpacingForFunction
,
553 ImportDeclaration
: checkSpacingForModuleDeclaration
,
554 VariableDeclaration
: checkSpacingAroundFirstToken
,
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
,
568 ImportNamespaceSpecifier
: checkSpacingForImportNamespaceSpecifier
,
569 MethodDefinition
: checkSpacingForProperty
,
570 Property
: checkSpacingForProperty