UNPKG

@stylistic/eslint-plugin-js

Version:

JavaScript stylistic rules for ESLint, migrated from [eslint core](https://github.com/eslint/eslint).

338 lines (335 loc) 14.1 kB
import { K as KEYWORDS_JS, c as createRule, n as isNotOpeningParenToken, i as isTokenOnSameLine, E as isKeywordToken } from '../utils.js'; import 'eslint-visitor-keys'; import 'espree'; 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|PrivateIdentifier)$/u; const KEYS = KEYWORDS_JS.concat(["as", "async", "await", "from", "get", "let", "of", "satisfies", "set", "yield"]); (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]}`); } })(); function isOpenParenOfTemplate(token) { return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value); } function isCloseParenOfTemplate(token) { return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value); } var keywordSpacing = createRule({ name: "keyword-spacing", package: "js", meta: { type: "layout", docs: { description: "Enforce consistent spacing before and after keywords" }, 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" }, after: { type: "boolean" } }, 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.sourceCode; const tokensToIgnore = /* @__PURE__ */ new WeakSet(); function expectSpaceBefore(token, pattern) { const prevToken = sourceCode.getTokenBefore(token); if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && !tokensToIgnore.has(prevToken) && isTokenOnSameLine(prevToken, token) && !sourceCode.isSpaceBetween(prevToken, token)) { context.report({ loc: token.loc, messageId: "expectedBefore", // @ts-expect-error index signature for string is missing data: token, fix(fixer) { return fixer.insertTextBefore(token, " "); } }); } } function unexpectSpaceBefore(token, pattern) { const prevToken = sourceCode.getTokenBefore(token); if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && !tokensToIgnore.has(prevToken) && isTokenOnSameLine(prevToken, token) && sourceCode.isSpaceBetween(prevToken, token)) { context.report({ loc: { start: prevToken.loc.end, end: token.loc.start }, messageId: "unexpectedBefore", // @ts-expect-error index signature for string is missing data: token, fix(fixer) { return fixer.removeRange([prevToken.range[1], token.range[0]]); } }); } } function expectSpaceAfter(token, pattern) { const nextToken = sourceCode.getTokenAfter(token); if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && !tokensToIgnore.has(nextToken) && isTokenOnSameLine(token, nextToken) && !sourceCode.isSpaceBetween(token, nextToken)) { context.report({ loc: token.loc, messageId: "expectedAfter", // @ts-expect-error index signature for string is missing data: token, fix(fixer) { return fixer.insertTextAfter(token, " "); } }); } } function unexpectSpaceAfter(token, pattern) { const nextToken = sourceCode.getTokenAfter(token); if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && !tokensToIgnore.has(nextToken) && isTokenOnSameLine(token, nextToken) && sourceCode.isSpaceBetween(token, nextToken)) { context.report({ loc: { start: token.loc.end, end: nextToken.loc.start }, messageId: "unexpectedAfter", // @ts-expect-error index signature for string is missing data: token, fix(fixer) { return fixer.removeRange([token.range[1], nextToken.range[0]]); } }); } } 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 = /* @__PURE__ */ 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]); function checkSpacingBefore(token, pattern) { checkMethodMap[token.value].before(token, pattern || PREV_TOKEN); } function checkSpacingAfter(token, pattern) { checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN); } function checkSpacingAround(token) { checkSpacingBefore(token); checkSpacingAfter(token); } function checkSpacingAroundFirstToken(node) { const firstToken = node && sourceCode.getFirstToken(node); if (firstToken && firstToken.type === "Keyword") checkSpacingAround(firstToken); } function checkSpacingBeforeFirstToken(node) { const firstToken = node && sourceCode.getFirstToken(node); if (firstToken && firstToken.type === "Keyword") checkSpacingBefore(firstToken); } function checkSpacingAroundTokenBefore(node) { if (node) { const token = sourceCode.getTokenBefore(node, isKeywordToken); if (token) checkSpacingAround(token); } } function checkSpacingForFunction(node) { const firstToken = node && sourceCode.getFirstToken(node); if (firstToken && (firstToken.type === "Keyword" && firstToken.value === "function" || firstToken.value === "async")) { checkSpacingBefore(firstToken); } } function checkSpacingForClass(node) { checkSpacingAroundFirstToken(node); checkSpacingAroundTokenBefore(node.superClass); } function checkSpacingForIfStatement(node) { checkSpacingAroundFirstToken(node); checkSpacingAroundTokenBefore(node.alternate); } function checkSpacingForTryStatement(node) { checkSpacingAroundFirstToken(node); checkSpacingAroundFirstToken(node.handler); checkSpacingAroundTokenBefore(node.finalizer); } function checkSpacingForDoWhileStatement(node) { checkSpacingAroundFirstToken(node); checkSpacingAroundTokenBefore(node.test); } function checkSpacingForForInStatement(node) { checkSpacingAroundFirstToken(node); const inToken = sourceCode.getTokenBefore(node.right, isNotOpeningParenToken); const previousToken = sourceCode.getTokenBefore(inToken); if (previousToken.type !== "PrivateIdentifier") checkSpacingBefore(inToken); checkSpacingAfter(inToken); } function checkSpacingForForOfStatement(node) { if (node.await) { checkSpacingBefore(sourceCode.getFirstToken(node, 0)); checkSpacingAfter(sourceCode.getFirstToken(node, 1)); } else { checkSpacingAroundFirstToken(node); } const ofToken = sourceCode.getTokenBefore(node.right, isNotOpeningParenToken); const previousToken = sourceCode.getTokenBefore(ofToken); if (previousToken.type !== "PrivateIdentifier") checkSpacingBefore(ofToken); checkSpacingAfter(ofToken); } 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.type === "ExportAllDeclaration" && node.exported) { const asToken = sourceCode.getTokenBefore(node.exported); checkSpacingBefore(asToken, PREV_TOKEN_M); checkSpacingAfter(asToken, NEXT_TOKEN_M); } if ("source" in node && node.source) { const fromToken = sourceCode.getTokenBefore(node.source); checkSpacingBefore(fromToken, PREV_TOKEN_M); checkSpacingAfter(fromToken, NEXT_TOKEN_M); } } function checkSpacingForImportSpecifier(node) { if (node.imported.range[0] !== node.local.range[0]) { const asToken = sourceCode.getTokenBefore(node.local); checkSpacingBefore(asToken, PREV_TOKEN_M); } } function checkSpacingForExportSpecifier(node) { if (node.local.range[0] !== node.exported.range[0]) { const asToken = sourceCode.getTokenBefore(node.exported); checkSpacingBefore(asToken, PREV_TOKEN_M); checkSpacingAfter(asToken, NEXT_TOKEN_M); } } function checkSpacingForImportNamespaceSpecifier(node) { const asToken = sourceCode.getFirstToken(node, 1); checkSpacingBefore(asToken, PREV_TOKEN_M); } function checkSpacingForProperty(node) { if ("static" in node && node.static) checkSpacingAroundFirstToken(node); if (node.kind === "get" || node.kind === "set" || ("method" in node && node.method || node.type === "MethodDefinition") && "async" in node.value && 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); } } 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 "ImportSpecifier": checkSpacingForImportSpecifier, "ExportSpecifier": checkSpacingForExportSpecifier, "ImportNamespaceSpecifier": checkSpacingForImportNamespaceSpecifier, "MethodDefinition": checkSpacingForProperty, "PropertyDefinition": checkSpacingForProperty, "StaticBlock": checkSpacingAroundFirstToken, "Property": checkSpacingForProperty, // To avoid conflicts with `space-infix-ops`, e.g. `a > this.b` "BinaryExpression[operator='>']": function(node) { const operatorToken = sourceCode.getTokenBefore(node.right, isNotOpeningParenToken); tokensToIgnore.add(operatorToken); } }; } }); export { keywordSpacing as default };