/** * @fileoverview Validates spacing before and after semicolon * @author Mathias Schreck * @deprecated in ESLint v8.53.0 */ 'use strict'; const astUtils = require('./utils/ast-utils'); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../types').Rule.RuleModule} */ module.exports = { meta: { deprecated: { message: 'Formatting rules are being moved out of ESLint core.', url: 'https://eslint.org/blog/2023/10/deprecating-formatting-rules/', deprecatedSince: '8.53.0', availableUntil: '10.0.0', replacedBy: [ { message: 'ESLint Stylistic now maintains deprecated stylistic core rules.', url: 'https://eslint.style/guide/migration', plugin: { name: '@stylistic/eslint-plugin-js', url: 'https://eslint.style/packages/js', }, rule: { name: 'semi-spacing', url: 'https://eslint.style/rules/js/semi-spacing', }, }, ], }, type: 'layout', docs: { description: 'Enforce consistent spacing before and after semicolons', recommended: false, url: 'https://eslint.org/docs/latest/rules/semi-spacing', }, fixable: 'whitespace', schema: [ { type: 'object', properties: { before: { type: 'boolean', default: false, }, after: { type: 'boolean', default: true, }, }, additionalProperties: false, }, ], messages: { unexpectedWhitespaceBefore: 'Unexpected whitespace before semicolon.', unexpectedWhitespaceAfter: 'Unexpected whitespace after semicolon.', missingWhitespaceBefore: 'Missing whitespace before semicolon.', missingWhitespaceAfter: 'Missing whitespace after semicolon.', }, }, create(context) { const config = context.options[0], sourceCode = context.sourceCode; let requireSpaceBefore = false, requireSpaceAfter = true; if (typeof config === 'object') { requireSpaceBefore = config.before; requireSpaceAfter = config.after; } /** * Checks if a given token has leading whitespace. * @param {Object} token The token to check. * @returns {boolean} True if the given token has leading space, false if not. */ function hasLeadingSpace(token) { const tokenBefore = sourceCode.getTokenBefore(token); return ( tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token) ); } /** * Checks if a given token has trailing whitespace. * @param {Object} token The token to check. * @returns {boolean} True if the given token has trailing space, false if not. */ function hasTrailingSpace(token) { const tokenAfter = sourceCode.getTokenAfter(token); return ( tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter) ); } /** * Checks if the given token is the last token in its line. * @param {Token} token The token to check. * @returns {boolean} Whether or not the token is the last in its line. */ function isLastTokenInCurrentLine(token) { const tokenAfter = sourceCode.getTokenAfter(token); return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)); } /** * Checks if the given token is the first token in its line * @param {Token} token The token to check. * @returns {boolean} Whether or not the token is the first in its line. */ function isFirstTokenInCurrentLine(token) { const tokenBefore = sourceCode.getTokenBefore(token); return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)); } /** * Checks if the next token of a given token is a closing parenthesis. * @param {Token} token The token to check. * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis. */ function isBeforeClosingParen(token) { const nextToken = sourceCode.getTokenAfter(token); return ( (nextToken && astUtils.isClosingBraceToken(nextToken)) || astUtils.isClosingParenToken(nextToken) ); } /** * Report location example : * * for unexpected space `before` * * var a = 'b' ; * ^^^ * * for unexpected space `after` * * var a = 'b'; c = 10; * ^^ * * Reports if the given token has invalid spacing. * @param {Token} token The semicolon token to check. * @param {ASTNode} node The corresponding node of the token. * @returns {void} */ function checkSemicolonSpacing(token, node) { if (astUtils.isSemicolonToken(token)) { if (hasLeadingSpace(token)) { if (!requireSpaceBefore) { const tokenBefore = sourceCode.getTokenBefore(token); const loc = { start: tokenBefore.loc.end, end: token.loc.start, }; context.report({ node, loc, messageId: 'unexpectedWhitespaceBefore', fix(fixer) { return fixer.removeRange([ tokenBefore.range[1], token.range[0], ]); }, }); } } else { if (requireSpaceBefore) { const loc = token.loc; context.report({ node, loc, messageId: 'missingWhitespaceBefore', fix(fixer) { return fixer.insertTextBefore(token, ' '); }, }); } } if ( !isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token) ) { if (hasTrailingSpace(token)) { if (!requireSpaceAfter) { const tokenAfter = sourceCode.getTokenAfter(token); const loc = { start: token.loc.end, end: tokenAfter.loc.start, }; context.report({ node, loc, messageId: 'unexpectedWhitespaceAfter', fix(fixer) { return fixer.removeRange([ token.range[1], tokenAfter.range[0], ]); }, }); } } else { if (requireSpaceAfter) { const loc = token.loc; context.report({ node, loc, messageId: 'missingWhitespaceAfter', fix(fixer) { return fixer.insertTextAfter(token, ' '); }, }); } } } } } /** * Checks the spacing of the semicolon with the assumption that the last token is the semicolon. * @param {ASTNode} node The node to check. * @returns {void} */ function checkNode(node) { const token = sourceCode.getLastToken(node); checkSemicolonSpacing(token, node); } return { VariableDeclaration: checkNode, ExpressionStatement: checkNode, BreakStatement: checkNode, ContinueStatement: checkNode, DebuggerStatement: checkNode, DoWhileStatement: checkNode, ReturnStatement: checkNode, ThrowStatement: checkNode, ImportDeclaration: checkNode, ExportNamedDeclaration: checkNode, ExportAllDeclaration: checkNode, ExportDefaultDeclaration: checkNode, ForStatement(node) { if (node.init) { checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node); } if (node.test) { checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node); } }, PropertyDefinition: checkNode, }; }, };