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: 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)
207 loc: token.loc.start,
208 messageId: "unexpectedAfter",
211 return fixer.removeRange([token.range[1], nextToken.range[0]]);
218 * Parses the option object and determines check methods for each keyword.
219 * @param {Object|undefined} options The option object to parse.
220 * @returns {Object} - Normalized option object.
221 * Keys are keywords (there are for every keyword).
222 * Values are instances of `{"before": function, "after": function}`.
224 function parseOptions(options = {}) {
225 const before = options.before !== false;
226 const after = options.after !== false;
227 const defaultValue = {
228 before: before ? expectSpaceBefore : unexpectSpaceBefore,
229 after: after ? expectSpaceAfter : unexpectSpaceAfter
231 const overrides = (options && options.overrides) || {};
232 const retv = Object.create(null);
234 for (let i = 0; i < KEYS.length; ++i) {
236 const override = overrides[key];
239 const thisBefore = ("before" in override) ? override.before : before;
240 const thisAfter = ("after" in override) ? override.after : after;
243 before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
244 after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
247 retv[key] = defaultValue;
254 const checkMethodMap = parseOptions(context.options[0]);
257 * Reports a given token if usage of spacing followed by the token is
259 * @param {Token} token A token to report.
260 * @param {RegExp} [pattern] Optional. A pattern of the previous
264 function checkSpacingBefore(token, pattern) {
265 checkMethodMap[token.value].before(token, pattern || PREV_TOKEN);
269 * Reports a given token if usage of spacing preceded by the token is
271 * @param {Token} token A token to report.
272 * @param {RegExp} [pattern] Optional. A pattern of the next
276 function checkSpacingAfter(token, pattern) {
277 checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN);
281 * Reports a given token if usage of spacing around the token is invalid.
282 * @param {Token} token A token to report.
285 function checkSpacingAround(token) {
286 checkSpacingBefore(token);
287 checkSpacingAfter(token);
291 * Reports the first token of a given node if the first token is a keyword
292 * and usage of spacing around the token is invalid.
293 * @param {ASTNode|null} node A node to report.
296 function checkSpacingAroundFirstToken(node) {
297 const firstToken = node && sourceCode.getFirstToken(node);
299 if (firstToken && firstToken.type === "Keyword") {
300 checkSpacingAround(firstToken);
305 * Reports the first token of a given node if the first token is a keyword
306 * and usage of spacing followed by the token is invalid.
308 * This is used for unary operators (e.g. `typeof`), `function`, and `super`.
309 * Other rules are handling usage of spacing preceded by those keywords.
310 * @param {ASTNode|null} node A node to report.
313 function checkSpacingBeforeFirstToken(node) {
314 const firstToken = node && sourceCode.getFirstToken(node);
316 if (firstToken && firstToken.type === "Keyword") {
317 checkSpacingBefore(firstToken);
322 * Reports the previous token of a given node if the token is a keyword and
323 * usage of spacing around the token is invalid.
324 * @param {ASTNode|null} node A node to report.
327 function checkSpacingAroundTokenBefore(node) {
329 const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken);
331 checkSpacingAround(token);
336 * Reports `async` or `function` keywords of a given node if usage of
337 * spacing around those keywords is invalid.
338 * @param {ASTNode} node A node to report.
341 function checkSpacingForFunction(node) {
342 const firstToken = node && sourceCode.getFirstToken(node);
345 ((firstToken.type === "Keyword" && firstToken.value === "function") ||
346 firstToken.value === "async")
348 checkSpacingBefore(firstToken);
353 * Reports `class` and `extends` keywords of a given node if usage of
354 * spacing around those keywords is invalid.
355 * @param {ASTNode} node A node to report.
358 function checkSpacingForClass(node) {
359 checkSpacingAroundFirstToken(node);
360 checkSpacingAroundTokenBefore(node.superClass);
364 * Reports `if` and `else` keywords of a given node if usage of spacing
365 * around those keywords is invalid.
366 * @param {ASTNode} node A node to report.
369 function checkSpacingForIfStatement(node) {
370 checkSpacingAroundFirstToken(node);
371 checkSpacingAroundTokenBefore(node.alternate);
375 * Reports `try`, `catch`, and `finally` keywords of a given node if usage
376 * of spacing around those keywords is invalid.
377 * @param {ASTNode} node A node to report.
380 function checkSpacingForTryStatement(node) {
381 checkSpacingAroundFirstToken(node);
382 checkSpacingAroundFirstToken(node.handler);
383 checkSpacingAroundTokenBefore(node.finalizer);
387 * Reports `do` and `while` keywords of a given node if usage of spacing
388 * around those keywords is invalid.
389 * @param {ASTNode} node A node to report.
392 function checkSpacingForDoWhileStatement(node) {
393 checkSpacingAroundFirstToken(node);
394 checkSpacingAroundTokenBefore(node.test);
398 * Reports `for` and `in` keywords of a given node if usage of spacing
399 * around those keywords is invalid.
400 * @param {ASTNode} node A node to report.
403 function checkSpacingForForInStatement(node) {
404 checkSpacingAroundFirstToken(node);
405 checkSpacingAroundTokenBefore(node.right);
409 * Reports `for` and `of` keywords of a given node if usage of spacing
410 * around those keywords is invalid.
411 * @param {ASTNode} node A node to report.
414 function checkSpacingForForOfStatement(node) {
416 checkSpacingBefore(sourceCode.getFirstToken(node, 0));
417 checkSpacingAfter(sourceCode.getFirstToken(node, 1));
419 checkSpacingAroundFirstToken(node);
421 checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
425 * Reports `import`, `export`, `as`, and `from` keywords of a given node if
426 * usage of spacing around those keywords is invalid.
428 * This rule handles the `*` token in module declarations.
430 * import*as A from "./a"; /*error Expected space(s) after "import".
431 * error Expected space(s) before "as".
432 * @param {ASTNode} node A node to report.
435 function checkSpacingForModuleDeclaration(node) {
436 const firstToken = sourceCode.getFirstToken(node);
438 checkSpacingBefore(firstToken, PREV_TOKEN_M);
439 checkSpacingAfter(firstToken, NEXT_TOKEN_M);
441 if (node.type === "ExportDefaultDeclaration") {
442 checkSpacingAround(sourceCode.getTokenAfter(firstToken));
446 const fromToken = sourceCode.getTokenBefore(node.source);
448 checkSpacingBefore(fromToken, PREV_TOKEN_M);
449 checkSpacingAfter(fromToken, NEXT_TOKEN_M);
454 * Reports `as` keyword of a given node if usage of spacing around this
455 * keyword is invalid.
456 * @param {ASTNode} node A node to report.
459 function checkSpacingForImportNamespaceSpecifier(node) {
460 const asToken = sourceCode.getFirstToken(node, 1);
462 checkSpacingBefore(asToken, PREV_TOKEN_M);
466 * Reports `static`, `get`, and `set` keywords of a given node if usage of
467 * spacing around those keywords is invalid.
468 * @param {ASTNode} node A node to report.
471 function checkSpacingForProperty(node) {
473 checkSpacingAroundFirstToken(node);
475 if (node.kind === "get" ||
476 node.kind === "set" ||
478 (node.method || node.type === "MethodDefinition") &&
482 const token = sourceCode.getTokenBefore(
497 throw new Error("Failed to find token get, set, or async beside method name");
501 checkSpacingAround(token);
506 * Reports `await` keyword of a given node if usage of spacing before
507 * this keyword is invalid.
508 * @param {ASTNode} node A node to report.
511 function checkSpacingForAwaitExpression(node) {
512 checkSpacingBefore(sourceCode.getFirstToken(node));
518 DebuggerStatement: checkSpacingAroundFirstToken,
519 WithStatement: checkSpacingAroundFirstToken,
521 // Statements - Control flow
522 BreakStatement: checkSpacingAroundFirstToken,
523 ContinueStatement: checkSpacingAroundFirstToken,
524 ReturnStatement: checkSpacingAroundFirstToken,
525 ThrowStatement: checkSpacingAroundFirstToken,
526 TryStatement: checkSpacingForTryStatement,
528 // Statements - Choice
529 IfStatement: checkSpacingForIfStatement,
530 SwitchStatement: checkSpacingAroundFirstToken,
531 SwitchCase: checkSpacingAroundFirstToken,
533 // Statements - Loops
534 DoWhileStatement: checkSpacingForDoWhileStatement,
535 ForInStatement: checkSpacingForForInStatement,
536 ForOfStatement: checkSpacingForForOfStatement,
537 ForStatement: checkSpacingAroundFirstToken,
538 WhileStatement: checkSpacingAroundFirstToken,
540 // Statements - Declarations
541 ClassDeclaration: checkSpacingForClass,
542 ExportNamedDeclaration: checkSpacingForModuleDeclaration,
543 ExportDefaultDeclaration: checkSpacingForModuleDeclaration,
544 ExportAllDeclaration: checkSpacingForModuleDeclaration,
545 FunctionDeclaration: checkSpacingForFunction,
546 ImportDeclaration: checkSpacingForModuleDeclaration,
547 VariableDeclaration: checkSpacingAroundFirstToken,
550 ArrowFunctionExpression: checkSpacingForFunction,
551 AwaitExpression: checkSpacingForAwaitExpression,
552 ClassExpression: checkSpacingForClass,
553 FunctionExpression: checkSpacingForFunction,
554 NewExpression: checkSpacingBeforeFirstToken,
555 Super: checkSpacingBeforeFirstToken,
556 ThisExpression: checkSpacingBeforeFirstToken,
557 UnaryExpression: checkSpacingBeforeFirstToken,
558 YieldExpression: checkSpacingBeforeFirstToken,
561 ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
562 MethodDefinition: checkSpacingForProperty,
563 Property: checkSpacingForProperty