| /** | |
| * @fileoverview Rule to flag block statements that do not use the one true brace style | |
| * @author Ian Christian Myers | |
| */ | |
| "use strict"; | |
| const astUtils = require("./utils/ast-utils"); | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| module.exports = { | |
| meta: { | |
| type: "layout", | |
| docs: { | |
| description: "enforce consistent brace style for blocks", | |
| category: "Stylistic Issues", | |
| recommended: false, | |
| url: "https://eslint.org/docs/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.getSourceCode(); | |
| //-------------------------------------------------------------------------- | |
| // 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)); | |
| } | |
| }, | |
| 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)); | |
| } | |
| } | |
| }; | |
| } | |
| }; |