You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
8.0 KiB
194 lines
8.0 KiB
/** |
|
* @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 |
|
//------------------------------------------------------------------------------ |
|
|
|
/** @type {import('../shared/types').Rule} */ |
|
module.exports = { |
|
meta: { |
|
type: "layout", |
|
|
|
docs: { |
|
description: "Enforce consistent brace style for blocks", |
|
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)); |
|
} |
|
}, |
|
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)); |
|
} |
|
} |
|
}; |
|
} |
|
};
|
|
|