/** * @fileoverview Rule to flag block statements that do not use the one true brace style * @author Ian Christian Myers * @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: 'brace-style', url: 'https://eslint.style/rules/js/brace-style', }, }, ], }, type: 'layout', docs: { description: 'Enforce consistent brace style for blocks', recommended: false, url: 'https://eslint.org/docs/latest/rules/brace-style', }, schema: [ { enum: ['1tbs', 'stroustrup', 'allman'], }, { type: 'object', properties: { allowSingleLine: { type: 'boolean', default: false, }, }, additionalProperties: false, }, ], fixable: 'whitespace', messages: { nextLineOpen: 'Opening curly brace does not appear on the same line as controlling statement.', sameLineOpen: 'Opening curly brace appears on the same line as controlling statement.', blockSameLine: 'Statement inside of curly braces should be on next line.', nextLineClose: 'Closing curly brace does not appear on the same line as the subsequent block.', singleLineClose: 'Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.', sameLineClose: 'Closing curly brace appears on the same line as the subsequent block.', }, }, create(context) { const style = context.options[0] || '1tbs', params = context.options[1] || {}, sourceCode = context.sourceCode; //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- /** * Fixes a place where a newline unexpectedly appears * @param {Token} firstToken The token before the unexpected newline * @param {Token} secondToken The token after the unexpected newline * @returns {Function} A fixer function to remove the newlines between the tokens */ function removeNewlineBetween(firstToken, secondToken) { const textRange = [firstToken.range[1], secondToken.range[0]]; const textBetween = sourceCode.text.slice(textRange[0], textRange[1]); // Don't do a fix if there is a comment between the tokens if (textBetween.trim()) { return null; } return (fixer) => fixer.replaceTextRange(textRange, ' '); } /** * Validates a pair of curly brackets based on the user's config * @param {Token} openingCurly The opening curly bracket * @param {Token} closingCurly The closing curly bracket * @returns {void} */ function validateCurlyPair(openingCurly, closingCurly) { const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly); const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly); const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly); const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly); if ( style !== 'allman' && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) ) { context.report({ node: openingCurly, messageId: 'nextLineOpen', fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly), }); } if ( style === 'allman' && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException ) { context.report({ node: openingCurly, messageId: 'sameLineOpen', fix: (fixer) => fixer.insertTextBefore(openingCurly, '\n'), }); } if ( astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException ) { context.report({ node: openingCurly, messageId: 'blockSameLine', fix: (fixer) => fixer.insertTextAfter(openingCurly, '\n'), }); } if ( tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly) ) { context.report({ node: closingCurly, messageId: 'singleLineClose', fix: (fixer) => fixer.insertTextBefore(closingCurly, '\n'), }); } } /** * Validates the location of a token that appears before a keyword (e.g. a newline before `else`) * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`). * @returns {void} */ function validateCurlyBeforeKeyword(curlyToken) { const keywordToken = sourceCode.getTokenAfter(curlyToken); if ( style === '1tbs' && !astUtils.isTokenOnSameLine(curlyToken, keywordToken) ) { context.report({ node: curlyToken, messageId: 'nextLineClose', fix: removeNewlineBetween(curlyToken, keywordToken), }); } if ( style !== '1tbs' && astUtils.isTokenOnSameLine(curlyToken, keywordToken) ) { context.report({ node: curlyToken, messageId: 'sameLineClose', fix: (fixer) => fixer.insertTextAfter(curlyToken, '\n'), }); } } //-------------------------------------------------------------------------- // Public API //-------------------------------------------------------------------------- return { BlockStatement(node) { if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { validateCurlyPair( sourceCode.getFirstToken(node), sourceCode.getLastToken(node) ); } }, StaticBlock(node) { validateCurlyPair( sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token sourceCode.getLastToken(node) ); }, ClassBody(node) { validateCurlyPair( sourceCode.getFirstToken(node), sourceCode.getLastToken(node) ); }, SwitchStatement(node) { const closingCurly = sourceCode.getLastToken(node); const openingCurly = sourceCode.getTokenBefore( node.cases.length ? node.cases[0] : closingCurly ); validateCurlyPair(openingCurly, closingCurly); }, IfStatement(node) { if (node.consequent.type === 'BlockStatement' && node.alternate) { // Handle the keyword after the `if` block (before `else`) validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent)); } }, TryStatement(node) { // Handle the keyword after the `try` block (before `catch` or `finally`) validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block)); if (node.handler && node.finalizer) { // Handle the keyword after the `catch` block (before `finally`) validateCurlyBeforeKeyword( sourceCode.getLastToken(node.handler.body) ); } }, }; }, };