|  | /** | 
|  | * @fileoverview Rule to enforce spacing before and after keywords. | 
|  | * @author Toru Nagashima | 
|  | */ | 
|  |  | 
|  | "use strict"; | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Requirements | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | const astUtils = require("./utils/ast-utils"), | 
|  | keywords = require("./utils/keywords"); | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Constants | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | const PREV_TOKEN = /^[)\]}>]$/u; | 
|  | const NEXT_TOKEN = /^(?:[([{<~!]|\+\+?|--?)$/u; | 
|  | const PREV_TOKEN_M = /^[)\]}>*]$/u; | 
|  | const NEXT_TOKEN_M = /^[{*]$/u; | 
|  | const TEMPLATE_OPEN_PAREN = /\$\{$/u; | 
|  | const TEMPLATE_CLOSE_PAREN = /^\}/u; | 
|  | const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u; | 
|  | const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]); | 
|  |  | 
|  | // check duplications. | 
|  | (function() { | 
|  | KEYS.sort(); | 
|  | for (let i = 1; i < KEYS.length; ++i) { | 
|  | if (KEYS[i] === KEYS[i - 1]) { | 
|  | throw new Error(`Duplication was found in the keyword list: ${KEYS[i]}`); | 
|  | } | 
|  | } | 
|  | }()); | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Helpers | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | /** | 
|  | * Checks whether or not a given token is a "Template" token ends with "${". | 
|  | * | 
|  | * @param {Token} token - A token to check. | 
|  | * @returns {boolean} `true` if the token is a "Template" token ends with "${". | 
|  | */ | 
|  | function isOpenParenOfTemplate(token) { | 
|  | return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Checks whether or not a given token is a "Template" token starts with "}". | 
|  | * | 
|  | * @param {Token} token - A token to check. | 
|  | * @returns {boolean} `true` if the token is a "Template" token starts with "}". | 
|  | */ | 
|  | function isCloseParenOfTemplate(token) { | 
|  | return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Rule Definition | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | module.exports = { | 
|  | meta: { | 
|  | type: "layout", | 
|  |  | 
|  | docs: { | 
|  | description: "enforce consistent spacing before and after keywords", | 
|  | category: "Stylistic Issues", | 
|  | recommended: false, | 
|  | url: "https://eslint.org/docs/rules/keyword-spacing" | 
|  | }, | 
|  |  | 
|  | fixable: "whitespace", | 
|  |  | 
|  | schema: [ | 
|  | { | 
|  | type: "object", | 
|  | properties: { | 
|  | before: { type: "boolean", default: true }, | 
|  | after: { type: "boolean", default: true }, | 
|  | overrides: { | 
|  | type: "object", | 
|  | properties: KEYS.reduce((retv, key) => { | 
|  | retv[key] = { | 
|  | type: "object", | 
|  | properties: { | 
|  | before: { type: "boolean", default: true }, | 
|  | after: { type: "boolean", default: true } | 
|  | }, | 
|  | additionalProperties: false | 
|  | }; | 
|  | return retv; | 
|  | }, {}), | 
|  | additionalProperties: false | 
|  | } | 
|  | }, | 
|  | additionalProperties: false | 
|  | } | 
|  | ], | 
|  | messages: { | 
|  | expectedBefore: "Expected space(s) before \"{{value}}\".", | 
|  | expectedAfter: "Expected space(s) after \"{{value}}\".", | 
|  | unexpectedBefore: "Unexpected space(s) before \"{{value}}\".", | 
|  | unexpectedAfter: "Unexpected space(s) after \"{{value}}\"." | 
|  | } | 
|  | }, | 
|  |  | 
|  | create(context) { | 
|  | const sourceCode = context.getSourceCode(); | 
|  |  | 
|  | /** | 
|  | * Reports a given token if there are not space(s) before the token. | 
|  | * | 
|  | * @param {Token} token - A token to report. | 
|  | * @param {RegExp} pattern - A pattern of the previous token to check. | 
|  | * @returns {void} | 
|  | */ | 
|  | function expectSpaceBefore(token, pattern) { | 
|  | const prevToken = sourceCode.getTokenBefore(token); | 
|  |  | 
|  | if (prevToken && | 
|  | (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && | 
|  | !isOpenParenOfTemplate(prevToken) && | 
|  | astUtils.isTokenOnSameLine(prevToken, token) && | 
|  | !sourceCode.isSpaceBetweenTokens(prevToken, token) | 
|  | ) { | 
|  | context.report({ | 
|  | loc: token.loc.start, | 
|  | messageId: "expectedBefore", | 
|  | data: token, | 
|  | fix(fixer) { | 
|  | return fixer.insertTextBefore(token, " "); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports a given token if there are space(s) before the token. | 
|  | * | 
|  | * @param {Token} token - A token to report. | 
|  | * @param {RegExp} pattern - A pattern of the previous token to check. | 
|  | * @returns {void} | 
|  | */ | 
|  | function unexpectSpaceBefore(token, pattern) { | 
|  | const prevToken = sourceCode.getTokenBefore(token); | 
|  |  | 
|  | if (prevToken && | 
|  | (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && | 
|  | !isOpenParenOfTemplate(prevToken) && | 
|  | astUtils.isTokenOnSameLine(prevToken, token) && | 
|  | sourceCode.isSpaceBetweenTokens(prevToken, token) | 
|  | ) { | 
|  | context.report({ | 
|  | loc: token.loc.start, | 
|  | messageId: "unexpectedBefore", | 
|  | data: token, | 
|  | fix(fixer) { | 
|  | return fixer.removeRange([prevToken.range[1], token.range[0]]); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports a given token if there are not space(s) after the token. | 
|  | * | 
|  | * @param {Token} token - A token to report. | 
|  | * @param {RegExp} pattern - A pattern of the next token to check. | 
|  | * @returns {void} | 
|  | */ | 
|  | function expectSpaceAfter(token, pattern) { | 
|  | const nextToken = sourceCode.getTokenAfter(token); | 
|  |  | 
|  | if (nextToken && | 
|  | (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && | 
|  | !isCloseParenOfTemplate(nextToken) && | 
|  | astUtils.isTokenOnSameLine(token, nextToken) && | 
|  | !sourceCode.isSpaceBetweenTokens(token, nextToken) | 
|  | ) { | 
|  | context.report({ | 
|  | loc: token.loc.start, | 
|  | messageId: "expectedAfter", | 
|  | data: token, | 
|  | fix(fixer) { | 
|  | return fixer.insertTextAfter(token, " "); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports a given token if there are space(s) after the token. | 
|  | * | 
|  | * @param {Token} token - A token to report. | 
|  | * @param {RegExp} pattern - A pattern of the next token to check. | 
|  | * @returns {void} | 
|  | */ | 
|  | function unexpectSpaceAfter(token, pattern) { | 
|  | const nextToken = sourceCode.getTokenAfter(token); | 
|  |  | 
|  | if (nextToken && | 
|  | (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && | 
|  | !isCloseParenOfTemplate(nextToken) && | 
|  | astUtils.isTokenOnSameLine(token, nextToken) && | 
|  | sourceCode.isSpaceBetweenTokens(token, nextToken) | 
|  | ) { | 
|  | context.report({ | 
|  | loc: token.loc.start, | 
|  | messageId: "unexpectedAfter", | 
|  | data: token, | 
|  | fix(fixer) { | 
|  | return fixer.removeRange([token.range[1], nextToken.range[0]]); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Parses the option object and determines check methods for each keyword. | 
|  | * | 
|  | * @param {Object|undefined} options - The option object to parse. | 
|  | * @returns {Object} - Normalized option object. | 
|  | *      Keys are keywords (there are for every keyword). | 
|  | *      Values are instances of `{"before": function, "after": function}`. | 
|  | */ | 
|  | function parseOptions(options = {}) { | 
|  | const before = options.before !== false; | 
|  | const after = options.after !== false; | 
|  | const defaultValue = { | 
|  | before: before ? expectSpaceBefore : unexpectSpaceBefore, | 
|  | after: after ? expectSpaceAfter : unexpectSpaceAfter | 
|  | }; | 
|  | const overrides = (options && options.overrides) || {}; | 
|  | const retv = Object.create(null); | 
|  |  | 
|  | for (let i = 0; i < KEYS.length; ++i) { | 
|  | const key = KEYS[i]; | 
|  | const override = overrides[key]; | 
|  |  | 
|  | if (override) { | 
|  | const thisBefore = ("before" in override) ? override.before : before; | 
|  | const thisAfter = ("after" in override) ? override.after : after; | 
|  |  | 
|  | retv[key] = { | 
|  | before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore, | 
|  | after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter | 
|  | }; | 
|  | } else { | 
|  | retv[key] = defaultValue; | 
|  | } | 
|  | } | 
|  |  | 
|  | return retv; | 
|  | } | 
|  |  | 
|  | const checkMethodMap = parseOptions(context.options[0]); | 
|  |  | 
|  | /** | 
|  | * Reports a given token if usage of spacing followed by the token is | 
|  | * invalid. | 
|  | * | 
|  | * @param {Token} token - A token to report. | 
|  | * @param {RegExp|undefined} pattern - Optional. A pattern of the previous | 
|  | *      token to check. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingBefore(token, pattern) { | 
|  | checkMethodMap[token.value].before(token, pattern || PREV_TOKEN); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports a given token if usage of spacing preceded by the token is | 
|  | * invalid. | 
|  | * | 
|  | * @param {Token} token - A token to report. | 
|  | * @param {RegExp|undefined} pattern - Optional. A pattern of the next | 
|  | *      token to check. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingAfter(token, pattern) { | 
|  | checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports a given token if usage of spacing around the token is invalid. | 
|  | * | 
|  | * @param {Token} token - A token to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingAround(token) { | 
|  | checkSpacingBefore(token); | 
|  | checkSpacingAfter(token); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports the first token of a given node if the first token is a keyword | 
|  | * and usage of spacing around the token is invalid. | 
|  | * | 
|  | * @param {ASTNode|null} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingAroundFirstToken(node) { | 
|  | const firstToken = node && sourceCode.getFirstToken(node); | 
|  |  | 
|  | if (firstToken && firstToken.type === "Keyword") { | 
|  | checkSpacingAround(firstToken); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports the first token of a given node if the first token is a keyword | 
|  | * and usage of spacing followed by the token is invalid. | 
|  | * | 
|  | * This is used for unary operators (e.g. `typeof`), `function`, and `super`. | 
|  | * Other rules are handling usage of spacing preceded by those keywords. | 
|  | * | 
|  | * @param {ASTNode|null} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingBeforeFirstToken(node) { | 
|  | const firstToken = node && sourceCode.getFirstToken(node); | 
|  |  | 
|  | if (firstToken && firstToken.type === "Keyword") { | 
|  | checkSpacingBefore(firstToken); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports the previous token of a given node if the token is a keyword and | 
|  | * usage of spacing around the token is invalid. | 
|  | * | 
|  | * @param {ASTNode|null} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingAroundTokenBefore(node) { | 
|  | if (node) { | 
|  | const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken); | 
|  |  | 
|  | checkSpacingAround(token); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `async` or `function` keywords of a given node if usage of | 
|  | * spacing around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForFunction(node) { | 
|  | const firstToken = node && sourceCode.getFirstToken(node); | 
|  |  | 
|  | if (firstToken && | 
|  | ((firstToken.type === "Keyword" && firstToken.value === "function") || | 
|  | firstToken.value === "async") | 
|  | ) { | 
|  | checkSpacingBefore(firstToken); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `class` and `extends` keywords of a given node if usage of | 
|  | * spacing around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForClass(node) { | 
|  | checkSpacingAroundFirstToken(node); | 
|  | checkSpacingAroundTokenBefore(node.superClass); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `if` and `else` keywords of a given node if usage of spacing | 
|  | * around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForIfStatement(node) { | 
|  | checkSpacingAroundFirstToken(node); | 
|  | checkSpacingAroundTokenBefore(node.alternate); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `try`, `catch`, and `finally` keywords of a given node if usage | 
|  | * of spacing around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForTryStatement(node) { | 
|  | checkSpacingAroundFirstToken(node); | 
|  | checkSpacingAroundFirstToken(node.handler); | 
|  | checkSpacingAroundTokenBefore(node.finalizer); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `do` and `while` keywords of a given node if usage of spacing | 
|  | * around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForDoWhileStatement(node) { | 
|  | checkSpacingAroundFirstToken(node); | 
|  | checkSpacingAroundTokenBefore(node.test); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `for` and `in` keywords of a given node if usage of spacing | 
|  | * around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForForInStatement(node) { | 
|  | checkSpacingAroundFirstToken(node); | 
|  | checkSpacingAroundTokenBefore(node.right); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `for` and `of` keywords of a given node if usage of spacing | 
|  | * around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForForOfStatement(node) { | 
|  | if (node.await) { | 
|  | checkSpacingBefore(sourceCode.getFirstToken(node, 0)); | 
|  | checkSpacingAfter(sourceCode.getFirstToken(node, 1)); | 
|  | } else { | 
|  | checkSpacingAroundFirstToken(node); | 
|  | } | 
|  | checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `import`, `export`, `as`, and `from` keywords of a given node if | 
|  | * usage of spacing around those keywords is invalid. | 
|  | * | 
|  | * This rule handles the `*` token in module declarations. | 
|  | * | 
|  | *     import*as A from "./a"; /*error Expected space(s) after "import". | 
|  | *                               error Expected space(s) before "as". | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForModuleDeclaration(node) { | 
|  | const firstToken = sourceCode.getFirstToken(node); | 
|  |  | 
|  | checkSpacingBefore(firstToken, PREV_TOKEN_M); | 
|  | checkSpacingAfter(firstToken, NEXT_TOKEN_M); | 
|  |  | 
|  | if (node.type === "ExportDefaultDeclaration") { | 
|  | checkSpacingAround(sourceCode.getTokenAfter(firstToken)); | 
|  | } | 
|  |  | 
|  | if (node.source) { | 
|  | const fromToken = sourceCode.getTokenBefore(node.source); | 
|  |  | 
|  | checkSpacingBefore(fromToken, PREV_TOKEN_M); | 
|  | checkSpacingAfter(fromToken, NEXT_TOKEN_M); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `as` keyword of a given node if usage of spacing around this | 
|  | * keyword is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForImportNamespaceSpecifier(node) { | 
|  | const asToken = sourceCode.getFirstToken(node, 1); | 
|  |  | 
|  | checkSpacingBefore(asToken, PREV_TOKEN_M); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `static`, `get`, and `set` keywords of a given node if usage of | 
|  | * spacing around those keywords is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForProperty(node) { | 
|  | if (node.static) { | 
|  | checkSpacingAroundFirstToken(node); | 
|  | } | 
|  | if (node.kind === "get" || | 
|  | node.kind === "set" || | 
|  | ( | 
|  | (node.method || node.type === "MethodDefinition") && | 
|  | node.value.async | 
|  | ) | 
|  | ) { | 
|  | const token = sourceCode.getTokenBefore( | 
|  | node.key, | 
|  | tok => { | 
|  | switch (tok.value) { | 
|  | case "get": | 
|  | case "set": | 
|  | case "async": | 
|  | return true; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  | ); | 
|  |  | 
|  | if (!token) { | 
|  | throw new Error("Failed to find token get, set, or async beside method name"); | 
|  | } | 
|  |  | 
|  |  | 
|  | checkSpacingAround(token); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Reports `await` keyword of a given node if usage of spacing before | 
|  | * this keyword is invalid. | 
|  | * | 
|  | * @param {ASTNode} node - A node to report. | 
|  | * @returns {void} | 
|  | */ | 
|  | function checkSpacingForAwaitExpression(node) { | 
|  | checkSpacingBefore(sourceCode.getFirstToken(node)); | 
|  | } | 
|  |  | 
|  | return { | 
|  |  | 
|  | // Statements | 
|  | DebuggerStatement: checkSpacingAroundFirstToken, | 
|  | WithStatement: checkSpacingAroundFirstToken, | 
|  |  | 
|  | // Statements - Control flow | 
|  | BreakStatement: checkSpacingAroundFirstToken, | 
|  | ContinueStatement: checkSpacingAroundFirstToken, | 
|  | ReturnStatement: checkSpacingAroundFirstToken, | 
|  | ThrowStatement: checkSpacingAroundFirstToken, | 
|  | TryStatement: checkSpacingForTryStatement, | 
|  |  | 
|  | // Statements - Choice | 
|  | IfStatement: checkSpacingForIfStatement, | 
|  | SwitchStatement: checkSpacingAroundFirstToken, | 
|  | SwitchCase: checkSpacingAroundFirstToken, | 
|  |  | 
|  | // Statements - Loops | 
|  | DoWhileStatement: checkSpacingForDoWhileStatement, | 
|  | ForInStatement: checkSpacingForForInStatement, | 
|  | ForOfStatement: checkSpacingForForOfStatement, | 
|  | ForStatement: checkSpacingAroundFirstToken, | 
|  | WhileStatement: checkSpacingAroundFirstToken, | 
|  |  | 
|  | // Statements - Declarations | 
|  | ClassDeclaration: checkSpacingForClass, | 
|  | ExportNamedDeclaration: checkSpacingForModuleDeclaration, | 
|  | ExportDefaultDeclaration: checkSpacingForModuleDeclaration, | 
|  | ExportAllDeclaration: checkSpacingForModuleDeclaration, | 
|  | FunctionDeclaration: checkSpacingForFunction, | 
|  | ImportDeclaration: checkSpacingForModuleDeclaration, | 
|  | VariableDeclaration: checkSpacingAroundFirstToken, | 
|  |  | 
|  | // Expressions | 
|  | ArrowFunctionExpression: checkSpacingForFunction, | 
|  | AwaitExpression: checkSpacingForAwaitExpression, | 
|  | ClassExpression: checkSpacingForClass, | 
|  | FunctionExpression: checkSpacingForFunction, | 
|  | NewExpression: checkSpacingBeforeFirstToken, | 
|  | Super: checkSpacingBeforeFirstToken, | 
|  | ThisExpression: checkSpacingBeforeFirstToken, | 
|  | UnaryExpression: checkSpacingBeforeFirstToken, | 
|  | YieldExpression: checkSpacingBeforeFirstToken, | 
|  |  | 
|  | // Others | 
|  | ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, | 
|  | MethodDefinition: checkSpacingForProperty, | 
|  | Property: checkSpacingForProperty | 
|  | }; | 
|  | } | 
|  | }; |