prettier-plugin-solidity
Version:
A Prettier Plugin for automatically formatting your Solidity code.
831 lines (770 loc) • 20.4 kB
JavaScript
import { util } from "prettier";
const {
addLeadingComment,
addTrailingComment,
addDanglingComment,
hasNewline,
hasNewlineInRange,
getNextNonSpaceNonCommentCharacter,
getNextNonSpaceNonCommentCharacterIndex
} = util
export function handleOwnLineComment(comment, text, options, ast, isLastComment) {
const { precedingNode, enclosingNode, followingNode } = comment;
if (
handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleMemberExpressionComments(enclosingNode, followingNode, comment) ||
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleWhileComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleTryStatementComments(
enclosingNode,
precedingNode,
followingNode,
comment
) ||
handleClassComments(enclosingNode, precedingNode, followingNode, comment) ||
handleImportSpecifierComments(enclosingNode, comment) ||
handleForComments(enclosingNode, precedingNode, comment) ||
handleUnionTypeComments(
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleImportDeclarationComments(
text,
enclosingNode,
precedingNode,
comment,
options
) ||
handleAssignmentPatternComments(enclosingNode, comment) ||
handleMethodNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
)
) {
return true;
}
return false;
}
export function handleEndOfLineComment(comment, text, options, ast, isLastComment) {
const { precedingNode, enclosingNode, followingNode } = comment;
if (
handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleConditionalExpressionComments(
enclosingNode,
precedingNode,
followingNode,
comment,
text,
options
) ||
handleImportSpecifierComments(enclosingNode, comment) ||
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleWhileComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleTryStatementComments(
enclosingNode,
precedingNode,
followingNode,
comment
) ||
handleClassComments(enclosingNode, precedingNode, followingNode, comment) ||
handleLabeledStatementComments(enclosingNode, comment) ||
handleCallExpressionComments(precedingNode, enclosingNode, comment) ||
handlePropertyComments(enclosingNode, comment) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleTypeAliasComments(enclosingNode, followingNode, comment) ||
handleVariableDeclaratorComments(enclosingNode, followingNode, comment)
) {
return true;
}
return false;
}
export function handleRemainingComment(comment, text, options, ast, isLastComment) {
const { precedingNode, enclosingNode, followingNode } = comment;
if (
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleWhileComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) ||
handleCommentInEmptyParens(text, enclosingNode, comment, options) ||
handleMethodNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleCommentAfterArrowParams(text, enclosingNode, comment, options) ||
handleFunctionNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) ||
handleTSMappedTypeComments(
text,
enclosingNode,
precedingNode,
followingNode,
comment
) ||
handleBreakAndContinueStatementComments(enclosingNode, comment)
) {
return true;
}
return false;
}
function addBlockStatementFirstComment(node, comment) {
if (!node.body) {
addDanglingComment(node, comment);
return;
}
const body = node.body.filter(n => n.type !== "EmptyStatement");
if (body.length === 0) {
addDanglingComment(node, comment);
} else {
addLeadingComment(body[0], comment);
}
}
function addBlockOrNotComment(node, comment) {
if (node.type === "BlockStatement") {
addBlockStatementFirstComment(node, comment);
} else {
addLeadingComment(node, comment);
}
}
// There are often comments before the else clause of if statements like
//
// if (1) { ... }
// // comment
// else { ... }
//
// They are being attached as leading comments of the BlockExpression which
// is not well printed. What we want is to instead move the comment inside
// of the block and make it leadingComment of the first element of the block
// or dangling comment of the block if there is nothing inside
//
// if (1) { ... }
// else {
// // comment
// ...
// }
function handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) {
if (
!enclosingNode ||
enclosingNode.type !== "IfStatement" ||
!followingNode
) {
return false;
}
// We unfortunately have no way using the AST or location of nodes to know
// if the comment is positioned before the condition parenthesis:
// if (a /* comment */) {}
// The only workaround I found is to look at the next character to see if
// it is a ).
const nextCharacter = getNextNonSpaceNonCommentCharacter(
text,
options.locEnd(comment)
);
if (nextCharacter === ")") {
addTrailingComment(precedingNode, comment);
return true;
}
// Comments before `else`:
// - treat as trailing comments of the trueBody, if it's a ExpressionStatement
// - treat as a dangling comment otherwise
if (
precedingNode === enclosingNode.trueBody &&
followingNode === enclosingNode.falseBody
) {
if (precedingNode.type === "ExpressionStatement") {
addTrailingComment(precedingNode, comment);
} else {
addDanglingComment(enclosingNode, comment);
}
return true;
}
if (followingNode.type === "ExpressionStatement") {
addBlockStatementFirstComment(followingNode, comment);
return true;
}
if (followingNode.type === "IfStatement") {
addBlockOrNotComment(followingNode.trueBody, comment);
return true;
}
// For comments positioned after the condition parenthesis in an if statement
// before the trueBody without brackets on, such as
// if (a) /* comment */ true,
// we look at the next character to see if the following node
// is the trueBody for the if statement
if (enclosingNode.trueBody === followingNode) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
function handleWhileComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) {
if (
!enclosingNode ||
enclosingNode.type !== "WhileStatement" ||
!followingNode
) {
return false;
}
// We unfortunately have no way using the AST or location of nodes to know
// if the comment is positioned before the condition parenthesis:
// while (a /* comment */) {}
// The only workaround I found is to look at the next character to see if
// it is a ).
const nextCharacter = getNextNonSpaceNonCommentCharacter(
text,
options.locEnd(comment)
);
if (nextCharacter === ")") {
addTrailingComment(precedingNode, comment);
return true;
}
if (followingNode.type === "BlockStatement") {
addBlockStatementFirstComment(followingNode, comment);
return true;
}
return false;
}
// Same as IfStatement but for TryStatement
function handleTryStatementComments(
enclosingNode,
precedingNode,
followingNode,
comment
) {
if (
!enclosingNode ||
(enclosingNode.type !== "TryStatement" &&
enclosingNode.type !== "CatchClause") ||
!followingNode
) {
return false;
}
if (enclosingNode.type === "CatchClause" && precedingNode) {
addTrailingComment(precedingNode, comment);
return true;
}
if (followingNode.type === "BlockStatement") {
addBlockStatementFirstComment(followingNode, comment);
return true;
}
if (followingNode.type === "TryStatement") {
addBlockOrNotComment(followingNode.finalizer, comment);
return true;
}
if (followingNode.type === "CatchClause") {
addBlockOrNotComment(followingNode.body, comment);
return true;
}
return false;
}
function handleMemberExpressionComments(enclosingNode, followingNode, comment) {
if (
enclosingNode &&
enclosingNode.type === "MemberExpression" &&
followingNode &&
followingNode.type === "Identifier"
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleConditionalExpressionComments(
enclosingNode,
precedingNode,
followingNode,
comment,
text,
options
) {
const isSameLineAsPrecedingNode =
precedingNode &&
!hasNewlineInRange(
text,
options.locEnd(precedingNode),
options.locStart(comment)
);
if (
(!precedingNode || !isSameLineAsPrecedingNode) &&
enclosingNode &&
enclosingNode.type === "ConditionalExpression" &&
followingNode
) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
function handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ObjectProperty" ||
enclosingNode.type === "Property") &&
enclosingNode.shorthand &&
enclosingNode.key === precedingNode &&
enclosingNode.value.type === "AssignmentPattern"
) {
addTrailingComment(enclosingNode.value.left, comment);
return true;
}
return false;
}
function handleClassComments(
enclosingNode,
precedingNode,
followingNode,
comment
) {
if (
enclosingNode &&
(enclosingNode.type === "ClassDeclaration" ||
enclosingNode.type === "ClassExpression") &&
(enclosingNode.decorators && enclosingNode.decorators.length > 0) &&
!(followingNode && followingNode.type === "Decorator")
) {
if (!enclosingNode.decorators || enclosingNode.decorators.length === 0) {
addLeadingComment(enclosingNode, comment);
} else {
addTrailingComment(
enclosingNode.decorators[enclosingNode.decorators.length - 1],
comment
);
}
return true;
}
return false;
}
function handleMethodNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) {
// This is only needed for estree parsers (flow, typescript) to attach
// after a method name:
// obj = { fn /*comment*/() {} };
if (
enclosingNode &&
precedingNode &&
(enclosingNode.type === "Property" ||
enclosingNode.type === "MethodDefinition") &&
precedingNode.type === "Identifier" &&
enclosingNode.key === precedingNode &&
// special Property case: { key: /*comment*/(value) };
// comment should be attached to value instead of key
getNextNonSpaceNonCommentCharacter(
text,
options.locEnd(precedingNode)
) !== ":"
) {
addTrailingComment(precedingNode, comment);
return true;
}
// Print comments between decorators and class methods as a trailing comment
// on the decorator node instead of the method node
if (
precedingNode &&
enclosingNode &&
precedingNode.type === "Decorator" &&
(enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "ClassProperty" ||
enclosingNode.type === "TSAbstractClassProperty" ||
enclosingNode.type === "TSAbstractMethodDefinition" ||
enclosingNode.type === "MethodDefinition")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleFunctionNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) {
if (
getNextNonSpaceNonCommentCharacter(
text,
options.locEnd(comment)
) !== "("
) {
return false;
}
if (
precedingNode &&
enclosingNode &&
(enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "FunctionExpression" ||
enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "MethodDefinition" ||
enclosingNode.type === "ObjectMethod")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleCommentAfterArrowParams(text, enclosingNode, comment, options) {
if (!(enclosingNode && enclosingNode.type === "ArrowFunctionExpression")) {
return false;
}
const index = getNextNonSpaceNonCommentCharacterIndex(
text,
options.locEnd(comment)
);
if (text.substr(index, 2) === "=>") {
addDanglingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleCommentInEmptyParens(text, enclosingNode, comment, options) {
if (
getNextNonSpaceNonCommentCharacter(
text,
options.locEnd(comment)
) !== ")"
) {
return false;
}
// Only add dangling comments to fix the case when no params are present,
// i.e. a function without any argument.
if (
enclosingNode &&
(((enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "FunctionExpression" ||
(enclosingNode.type === "ArrowFunctionExpression" &&
(enclosingNode.body.type !== "CallExpression" ||
enclosingNode.body.arguments.length === 0)) ||
enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "ObjectMethod") &&
enclosingNode.params.length === 0) ||
((enclosingNode.type === "CallExpression" ||
enclosingNode.type === "NewExpression") &&
enclosingNode.arguments.length === 0))
) {
addDanglingComment(enclosingNode, comment);
return true;
}
if (
enclosingNode &&
(enclosingNode.type === "MethodDefinition" &&
enclosingNode.value.params.length === 0)
) {
addDanglingComment(enclosingNode.value, comment);
return true;
}
return false;
}
function handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) {
// Type definitions functions
if (
precedingNode &&
precedingNode.type === "FunctionTypeParam" &&
enclosingNode &&
enclosingNode.type === "FunctionTypeAnnotation" &&
followingNode &&
followingNode.type !== "FunctionTypeParam"
) {
addTrailingComment(precedingNode, comment);
return true;
}
// Real functions
if (
precedingNode &&
(precedingNode.type === "Identifier" ||
precedingNode.type === "AssignmentPattern") &&
enclosingNode &&
(enclosingNode.type === "ArrowFunctionExpression" ||
enclosingNode.type === "FunctionExpression" ||
enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "ObjectMethod" ||
enclosingNode.type === "ClassMethod") &&
getNextNonSpaceNonCommentCharacter(
text,
options.locEnd(comment)
) === ")"
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleImportSpecifierComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "ImportSpecifier") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleLabeledStatementComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "LabeledStatement") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleBreakAndContinueStatementComments(enclosingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ContinueStatement" ||
enclosingNode.type === "BreakStatement") &&
!enclosingNode.label
) {
addTrailingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleCallExpressionComments(precedingNode, enclosingNode, comment) {
if (
enclosingNode &&
enclosingNode.type === "CallExpression" &&
precedingNode &&
enclosingNode.callee === precedingNode &&
enclosingNode.arguments.length > 0
) {
addLeadingComment(enclosingNode.arguments[0], comment);
return true;
}
return false;
}
function handleUnionTypeComments(
precedingNode,
enclosingNode,
followingNode,
comment
) {
if (
enclosingNode &&
(enclosingNode.type === "UnionTypeAnnotation" ||
enclosingNode.type === "TSUnionType")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handlePropertyComments(enclosingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "Property" ||
enclosingNode.type === "ObjectProperty")
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleOnlyComments(enclosingNode, ast, comment, isLastComment) {
// With Flow the enclosingNode is undefined so use the AST instead.
if (ast && ast.body && ast.body.length === 0) {
if (isLastComment) {
addDanglingComment(ast, comment);
} else {
addLeadingComment(ast, comment);
}
return true;
} else if (
enclosingNode &&
enclosingNode.type === "Program" &&
enclosingNode.body.length === 0 &&
enclosingNode.directives &&
enclosingNode.directives.length === 0
) {
if (isLastComment) {
addDanglingComment(enclosingNode, comment);
} else {
addLeadingComment(enclosingNode, comment);
}
return true;
}
return false;
}
function handleForComments(enclosingNode, precedingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ForInStatement" ||
enclosingNode.type === "ForOfStatement")
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleImportDeclarationComments(
text,
enclosingNode,
precedingNode,
comment,
options
) {
if (
precedingNode &&
precedingNode.type === "ImportSpecifier" &&
enclosingNode &&
enclosingNode.type === "ImportDeclaration" &&
hasNewline(text, options.locEnd(comment))
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleAssignmentPatternComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "AssignmentPattern") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleTypeAliasComments(enclosingNode, followingNode, comment) {
if (enclosingNode && enclosingNode.type === "TypeAlias") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleVariableDeclaratorComments(
enclosingNode,
followingNode,
comment
) {
if (
enclosingNode &&
(enclosingNode.type === "VariableDeclarator" ||
enclosingNode.type === "AssignmentExpression") &&
followingNode &&
(followingNode.type === "ObjectExpression" ||
followingNode.type === "ArrayExpression" ||
followingNode.type === "TemplateLiteral" ||
followingNode.type === "TaggedTemplateExpression")
) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
function handleTSMappedTypeComments(
text,
enclosingNode,
precedingNode,
followingNode,
comment
) {
if (!enclosingNode || enclosingNode.type !== "TSMappedType") {
return false;
}
if (
followingNode &&
followingNode.type === "TSTypeParameter" &&
followingNode.name
) {
addLeadingComment(followingNode.name, comment);
return true;
}
if (
precedingNode &&
precedingNode.type === "TSTypeParameter" &&
precedingNode.constraint
) {
addTrailingComment(precedingNode.constraint, comment);
return true;
}
return false;
}
export function isBlockComment(comment) {
return comment.type === "Block" || comment.type === "CommentBlock";
}