From ea90004cbbc31b6b8fd63c498d2853430c034d71 Mon Sep 17 00:00:00 2001 From: Jordan Kiesel Date: Wed, 10 Jan 2024 00:04:57 -0700 Subject: [PATCH] fix: improve throws formatting --- .../dist/base-cst-printer.js | 55 ++ .../prettier-plugin-java/dist/cst-printer.js | 29 + packages/prettier-plugin-java/dist/index.js | 66 ++ packages/prettier-plugin-java/dist/options.js | 242 ++++++ packages/prettier-plugin-java/dist/parser.js | 4 + packages/prettier-plugin-java/dist/printer.js | 6 + .../dist/printers/arrays.js | 25 + .../dist/printers/blocks-and-statements.js | 435 +++++++++++ .../dist/printers/classes.js | 693 ++++++++++++++++++ .../dist/printers/comments/comments-utils.js | 21 + .../dist/printers/comments/format-comments.js | 168 +++++ .../dist/printers/comments/handle-comments.js | 73 ++ .../dist/printers/expressions.js | 495 +++++++++++++ .../dist/printers/interfaces.js | 218 ++++++ .../dist/printers/lexical-structure.js | 31 + .../dist/printers/names.js | 29 + .../dist/printers/packages-and-modules.js | 171 +++++ .../dist/printers/prettier-builder.js | 45 ++ .../dist/printers/printer-utils.js | 586 +++++++++++++++ .../printers/types-values-and-variables.js | 153 ++++ .../prettier-plugin-java/dist/types/utils.js | 20 + .../dist/utils/expressions-utils.js | 24 + .../prettier-plugin-java/dist/utils/index.js | 3 + .../dist/utils/isEmptyDoc.js | 4 + .../dist/utils/printArgumentListWithBraces.js | 15 + .../dist/utils/printSingleLambdaInvocation.js | 17 + .../src/printers/classes.ts | 36 +- .../src/printers/prettier-builder.ts | 6 +- .../test/unit-test/throws/_output.java | 92 ++- 29 files changed, 3718 insertions(+), 44 deletions(-) create mode 100644 packages/prettier-plugin-java/dist/base-cst-printer.js create mode 100644 packages/prettier-plugin-java/dist/cst-printer.js create mode 100644 packages/prettier-plugin-java/dist/index.js create mode 100644 packages/prettier-plugin-java/dist/options.js create mode 100644 packages/prettier-plugin-java/dist/parser.js create mode 100644 packages/prettier-plugin-java/dist/printer.js create mode 100644 packages/prettier-plugin-java/dist/printers/arrays.js create mode 100644 packages/prettier-plugin-java/dist/printers/blocks-and-statements.js create mode 100644 packages/prettier-plugin-java/dist/printers/classes.js create mode 100644 packages/prettier-plugin-java/dist/printers/comments/comments-utils.js create mode 100644 packages/prettier-plugin-java/dist/printers/comments/format-comments.js create mode 100644 packages/prettier-plugin-java/dist/printers/comments/handle-comments.js create mode 100644 packages/prettier-plugin-java/dist/printers/expressions.js create mode 100644 packages/prettier-plugin-java/dist/printers/interfaces.js create mode 100644 packages/prettier-plugin-java/dist/printers/lexical-structure.js create mode 100644 packages/prettier-plugin-java/dist/printers/names.js create mode 100644 packages/prettier-plugin-java/dist/printers/packages-and-modules.js create mode 100644 packages/prettier-plugin-java/dist/printers/prettier-builder.js create mode 100644 packages/prettier-plugin-java/dist/printers/printer-utils.js create mode 100644 packages/prettier-plugin-java/dist/printers/types-values-and-variables.js create mode 100644 packages/prettier-plugin-java/dist/types/utils.js create mode 100644 packages/prettier-plugin-java/dist/utils/expressions-utils.js create mode 100644 packages/prettier-plugin-java/dist/utils/index.js create mode 100644 packages/prettier-plugin-java/dist/utils/isEmptyDoc.js create mode 100644 packages/prettier-plugin-java/dist/utils/printArgumentListWithBraces.js create mode 100644 packages/prettier-plugin-java/dist/utils/printSingleLambdaInvocation.js diff --git a/packages/prettier-plugin-java/dist/base-cst-printer.js b/packages/prettier-plugin-java/dist/base-cst-printer.js new file mode 100644 index 00000000..2a1aa42e --- /dev/null +++ b/packages/prettier-plugin-java/dist/base-cst-printer.js @@ -0,0 +1,55 @@ +import { BaseJavaCstVisitor } from "java-parser"; +import { printNodeWithComments } from "./printers/comments/format-comments.js"; +export class BaseCstPrettierPrinter extends BaseJavaCstVisitor { + constructor() { + super(); + this.mapVisit = (elements, params) => { + if (elements === undefined) { + // TODO: can optimize this by returning an immutable empty array singleton. + return []; + } + return elements.map(element => this.visit(element, params)); + }; + this.getSingle = (ctx) => { + const ctxKeys = Object.keys(ctx); + if (ctxKeys.length !== 1) { + throw Error(`Expecting single key CST ctx but found: <${ctxKeys.length}> keys`); + } + const singleElementKey = ctxKeys[0]; + const singleElementValues = ctx[singleElementKey]; + if ((singleElementValues === null || singleElementValues === void 0 ? void 0 : singleElementValues.length) !== 1) { + throw Error(`Expecting single item in CST ctx key but found: <${singleElementValues === null || singleElementValues === void 0 ? void 0 : singleElementValues.length}> items`); + } + return singleElementValues[0]; + }; + // @ts-ignore + this.orgVisit = this.visit; + this.visit = function (ctx, inParam) { + if (ctx === undefined) { + // empty Doc + return ""; + } + const node = Array.isArray(ctx) ? ctx[0] : ctx; + if (node.ignore) { + try { + const startOffset = node.leadingComments !== undefined + ? node.leadingComments[0].startOffset + : node.location.startOffset; + const endOffset = (node.trailingComments !== undefined + ? node.trailingComments[node.trailingComments.length - 1].endOffset + : node.location.endOffset); + return this.prettierOptions.originalText.substring(startOffset, endOffset + 1); + } + catch (e) { + throw Error(e + + "\nThere might be a problem with prettier-ignore, please report an issue on https://github.com/jhipster/prettier-java/issues"); + } + } + return printNodeWithComments(node, this.orgVisit.call(this, node, inParam)); + }; + this.visitSingle = function (ctx, params) { + const singleElement = this.getSingle(ctx); + return this.visit(singleElement, params); + }; + } +} diff --git a/packages/prettier-plugin-java/dist/cst-printer.js b/packages/prettier-plugin-java/dist/cst-printer.js new file mode 100644 index 00000000..ca486265 --- /dev/null +++ b/packages/prettier-plugin-java/dist/cst-printer.js @@ -0,0 +1,29 @@ +import { BaseCstPrettierPrinter } from "./base-cst-printer.js"; +import { ArraysPrettierVisitor } from "./printers/arrays.js"; +import { BlocksAndStatementPrettierVisitor } from "./printers/blocks-and-statements.js"; +import { ClassesPrettierVisitor } from "./printers/classes.js"; +import { ExpressionsPrettierVisitor } from "./printers/expressions.js"; +import { InterfacesPrettierVisitor } from "./printers/interfaces.js"; +import { LexicalStructurePrettierVisitor } from "./printers/lexical-structure.js"; +import { NamesPrettierVisitor } from "./printers/names.js"; +import { TypesValuesAndVariablesPrettierVisitor } from "./printers/types-values-and-variables.js"; +import { PackagesAndModulesPrettierVisitor } from "./printers/packages-and-modules.js"; +// Mixins for the win +mixInMethods(ArraysPrettierVisitor, BlocksAndStatementPrettierVisitor, ClassesPrettierVisitor, ExpressionsPrettierVisitor, InterfacesPrettierVisitor, LexicalStructurePrettierVisitor, NamesPrettierVisitor, TypesValuesAndVariablesPrettierVisitor, PackagesAndModulesPrettierVisitor); +function mixInMethods(...classesToMix) { + classesToMix.forEach(from => { + const fromMethodsNames = Object.getOwnPropertyNames(from.prototype); + const fromPureMethodsName = fromMethodsNames.filter(methodName => methodName !== "constructor"); + fromPureMethodsName.forEach(methodName => { + // @ts-ignore + BaseCstPrettierPrinter.prototype[methodName] = from.prototype[methodName]; + }); + }); +} +const prettyPrinter = new BaseCstPrettierPrinter(); +// TODO: do we need the "path" and "print" arguments passed by prettier +// see https://github.com/prettier/prettier/issues/5747 +export function createPrettierDoc(cstNode, options) { + prettyPrinter.prettierOptions = options; + return prettyPrinter.visit(cstNode); +} diff --git a/packages/prettier-plugin-java/dist/index.js b/packages/prettier-plugin-java/dist/index.js new file mode 100644 index 00000000..d1441af2 --- /dev/null +++ b/packages/prettier-plugin-java/dist/index.js @@ -0,0 +1,66 @@ +import parse from "./parser.js"; +import print from "./printer.js"; +import options from "./options.js"; +const languages = [ + { + name: "Java", + parsers: ["java"], + group: "Java", + tmScope: "text.html.vue", + aceMode: "html", + codemirrorMode: "clike", + codemirrorMimeType: "text/x-java", + extensions: [".java"], + linguistLanguageId: 181, + vscodeLanguageIds: ["java"] + } +]; +function locStart( /* node */) { + return -1; +} +function locEnd( /* node */) { + return -1; +} +function hasPragma(text) { + return /^\/\*\*[\n][\t\s]+\*\s@(prettier|format)[\n][\t\s]+\*\//.test(text); +} +const parsers = { + java: { + parse, + astFormat: "java", + locStart, + locEnd, + hasPragma + } +}; +function canAttachComment(node) { + return node.ast_type && node.ast_type !== "comment"; +} +function printComment(commentPath) { + const comment = commentPath.getValue(); + switch (comment.ast_type) { + case "comment": + return comment.value; + default: + throw new Error("Not a comment: " + JSON.stringify(comment)); + } +} +function clean(ast, newObj) { + delete newObj.lineno; + delete newObj.col_offset; +} +const printers = { + java: { + print, + // hasPrettierIgnore, + printComment, + canAttachComment, + massageAstNode: clean + } +}; +export default { + languages, + printers, + parsers, + options +}; diff --git a/packages/prettier-plugin-java/dist/options.js b/packages/prettier-plugin-java/dist/options.js new file mode 100644 index 00000000..95603332 --- /dev/null +++ b/packages/prettier-plugin-java/dist/options.js @@ -0,0 +1,242 @@ +export default { + entrypoint: { + type: "choice", + category: "Global", + default: "compilationUnit", + // sed -nr 's/.*\.RULE\(([^,]+),.*/\1/p' $(ls path/to/java-parser/rules/folder/*) + choices: [ + { value: "arrayInitializer" }, + { value: "variableInitializerList" }, + { value: "block" }, + { value: "blockStatements" }, + { value: "blockStatement" }, + { value: "localVariableDeclarationStatement" }, + { value: "localVariableDeclaration" }, + { value: "localVariableType" }, + { value: "statement" }, + { value: "statementWithoutTrailingSubstatement" }, + { value: "emptyStatement" }, + { value: "labeledStatement" }, + { value: "expressionStatement" }, + { value: "statementExpression" }, + { value: "ifStatement" }, + { value: "assertStatement" }, + { value: "switchStatement" }, + { value: "switchBlock" }, + { value: "switchBlockStatementGroup" }, + { value: "switchLabel" }, + { value: "switchRule" }, + { value: "caseConstant" }, + { value: "whileStatement" }, + { value: "doStatement" }, + { value: "forStatement" }, + { value: "basicForStatement" }, + { value: "forInit" }, + { value: "forUpdate" }, + { value: "statementExpressionList" }, + { value: "enhancedForStatement" }, + { value: "breakStatement" }, + { value: "continueStatement" }, + { value: "returnStatement" }, + { value: "throwStatement" }, + { value: "synchronizedStatement" }, + { value: "tryStatement" }, + { value: "catches" }, + { value: "catchClause" }, + { value: "catchFormalParameter" }, + { value: "catchType" }, + { value: "finally" }, + { value: "tryWithResourcesStatement" }, + { value: "resourceSpecification" }, + { value: "resourceList" }, + { value: "resource" }, + { value: "yieldStatement" }, + { value: "variableAccess" }, + { value: "classDeclaration" }, + { value: "normalClassDeclaration" }, + { value: "classModifier" }, + { value: "typeParameters" }, + { value: "typeParameterList" }, + { value: "superclass" }, + { value: "superinterfaces" }, + { value: "interfaceTypeList" }, + { value: "classPermits" }, + { value: "classBody" }, + { value: "classBodyDeclaration" }, + { value: "classMemberDeclaration" }, + { value: "fieldDeclaration" }, + { value: "fieldModifier" }, + { value: "variableDeclaratorList" }, + { value: "variableDeclarator" }, + { value: "variableDeclaratorId" }, + { value: "variableInitializer" }, + { value: "unannType" }, + { value: "unannPrimitiveTypeWithOptionalDimsSuffix" }, + { value: "unannPrimitiveType" }, + { value: "unannReferenceType" }, + { value: "unannClassOrInterfaceType" }, + { value: "unannClassType" }, + { value: "unannInterfaceType" }, + { value: "unannTypeVariable" }, + { value: "methodDeclaration" }, + { value: "methodModifier" }, + { value: "methodHeader" }, + { value: "result" }, + { value: "methodDeclarator" }, + { value: "receiverParameter" }, + { value: "formalParameterList" }, + { value: "formalParameter" }, + { value: "variableParaRegularParameter" }, + { value: "variableArityParameter" }, + { value: "variableModifier" }, + { value: "throws" }, + { value: "exceptionTypeList" }, + { value: "exceptionType" }, + { value: "methodBody" }, + { value: "instanceInitializer" }, + { value: "staticInitializer" }, + { value: "constructorDeclaration" }, + { value: "constructorModifier" }, + { value: "constructorDeclarator" }, + { value: "simpleTypeName" }, + { value: "constructorBody" }, + { value: "explicitConstructorInvocation" }, + { value: "unqualifiedExplicitConstructorInvocation" }, + { value: "qualifiedExplicitConstructorInvocation" }, + { value: "enumDeclaration" }, + { value: "enumBody" }, + { value: "enumConstantList" }, + { value: "enumConstant" }, + { value: "enumConstantModifier" }, + { value: "enumBodyDeclarations" }, + { value: "recordDeclaration" }, + { value: "recordHeader" }, + { value: "recordComponentList" }, + { value: "recordComponent" }, + { value: "variableArityRecordComponent" }, + { value: "recordComponentModifier" }, + { value: "recordBody" }, + { value: "recordBodyDeclaration" }, + { value: "compactConstructorDeclaration" }, + { value: "isDims" }, + { value: "expression" }, + { value: "lambdaExpression" }, + { value: "lambdaParameters" }, + { value: "lambdaParametersWithBraces" }, + { value: "lambdaParameterList" }, + { value: "inferredLambdaParameterList" }, + { value: "explicitLambdaParameterList" }, + { value: "lambdaParameter" }, + { value: "regularLambdaParameter" }, + { value: "lambdaParameterType" }, + { value: "lambdaBody" }, + { value: "ternaryExpression" }, + { value: "binaryExpression" }, + { value: "unaryExpression" }, + { value: "unaryExpressionNotPlusMinus" }, + { value: "primary" }, + { value: "primaryPrefix" }, + { value: "primarySuffix" }, + { value: "fqnOrRefType" }, + { value: "fqnOrRefTypePartRest" }, + { value: "fqnOrRefTypePartCommon" }, + { value: "fqnOrRefTypePartFirst" }, + { value: "parenthesisExpression" }, + { value: "castExpression" }, + { value: "primitiveCastExpression" }, + { value: "referenceTypeCastExpression" }, + { value: "newExpression" }, + { value: "unqualifiedClassInstanceCreationExpression" }, + { value: "classOrInterfaceTypeToInstantiate" }, + { value: "typeArgumentsOrDiamond" }, + { value: "diamond" }, + { value: "methodInvocationSuffix" }, + { value: "argumentList" }, + { value: "arrayCreationExpression" }, + { value: "arrayCreationDefaultInitSuffix" }, + { value: "arrayCreationExplicitInitSuffix" }, + { value: "dimExprs" }, + { value: "dimExpr" }, + { value: "classLiteralSuffix" }, + { value: "arrayAccessSuffix" }, + { value: "methodReferenceSuffix" }, + { value: "pattern" }, + { value: "typePattern" }, + { value: "recordPattern" }, + { value: "componentPatternList" }, + { value: "componentPattern" }, + { value: "unnamedPattern" }, + { value: "guard" }, + { value: "isRefTypeInMethodRef" }, + { value: "interfaceDeclaration" }, + { value: "normalInterfaceDeclaration" }, + { value: "interfaceModifier" }, + { value: "extendsInterfaces" }, + { value: "interfacePermits" }, + { value: "interfaceBody" }, + { value: "interfaceMemberDeclaration" }, + { value: "constantDeclaration" }, + { value: "constantModifier" }, + { value: "interfaceMethodDeclaration" }, + { value: "interfaceMethodModifier" }, + { value: "annotationTypeDeclaration" }, + { value: "annotationTypeBody" }, + { value: "annotationTypeMemberDeclaration" }, + { value: "annotationTypeElementDeclaration" }, + { value: "annotationTypeElementModifier" }, + { value: "defaultValue" }, + { value: "annotation" }, + { value: "elementValuePairList" }, + { value: "elementValuePair" }, + { value: "elementValue" }, + { value: "elementValueArrayInitializer" }, + { value: "elementValueList" }, + { value: "literal" }, + { value: "integerLiteral" }, + { value: "floatingPointLiteral" }, + { value: "booleanLiteral" }, + { value: "moduleName" }, + { value: "packageName" }, + { value: "typeName" }, + { value: "expressionName" }, + { value: "methodName" }, + { value: "packageOrTypeName" }, + { value: "ambiguousName" }, + { value: "compilationUnit" }, + { value: "ordinaryCompilationUnit" }, + { value: "modularCompilationUnit" }, + { value: "packageDeclaration" }, + { value: "packageModifier" }, + { value: "importDeclaration" }, + { value: "typeDeclaration" }, + { value: "moduleDeclaration" }, + { value: "moduleDirective" }, + { value: "requiresModuleDirective" }, + { value: "exportsModuleDirective" }, + { value: "opensModuleDirective" }, + { value: "usesModuleDirective" }, + { value: "providesModuleDirective" }, + { value: "requiresModifier" }, + { value: "primitiveType" }, + { value: "numericType" }, + { value: "integralType" }, + { value: "floatingPointType" }, + { value: "referenceType" }, + { value: "classOrInterfaceType" }, + { value: "classType" }, + { value: "interfaceType" }, + { value: "typeVariable" }, + { value: "dims" }, + { value: "typeParameter" }, + { value: "typeParameterModifier" }, + { value: "typeBound" }, + { value: "additionalBound" }, + { value: "typeArguments" }, + { value: "typeArgumentList" }, + { value: "typeArgument" }, + { value: "wildcard" }, + { value: "wildcardBounds" } + ], + description: "Prettify from the entrypoint, allowing to use prettier on snippet." + } +}; diff --git a/packages/prettier-plugin-java/dist/parser.js b/packages/prettier-plugin-java/dist/parser.js new file mode 100644 index 00000000..95713771 --- /dev/null +++ b/packages/prettier-plugin-java/dist/parser.js @@ -0,0 +1,4 @@ +import javaParser from "java-parser"; +export default function parse(text, parsers, opts) { + return javaParser.parse(text, opts.entrypoint); +} diff --git a/packages/prettier-plugin-java/dist/printer.js b/packages/prettier-plugin-java/dist/printer.js new file mode 100644 index 00000000..44ee6da3 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printer.js @@ -0,0 +1,6 @@ +import { createPrettierDoc } from "./cst-printer.js"; +// eslint-disable-next-line no-unused-vars +export default function genericPrint(path, options, print) { + const node = path.getValue(); + return createPrettierDoc(node, options); +} diff --git a/packages/prettier-plugin-java/dist/printers/arrays.js b/packages/prettier-plugin-java/dist/printers/arrays.js new file mode 100644 index 00000000..ac8eadcf --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/arrays.js @@ -0,0 +1,25 @@ +import { printArrayList, rejectAndConcat, rejectAndJoinSeps } from "./printer-utils.js"; +import { builders } from "prettier/doc"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +const { line } = builders; +export class ArraysPrettierVisitor extends BaseCstPrettierPrinter { + arrayInitializer(ctx) { + const optionalVariableInitializerList = this.visit(ctx.variableInitializerList); + return printArrayList({ + list: optionalVariableInitializerList, + extraComma: ctx.Comma, + LCurly: ctx.LCurly[0], + RCurly: ctx.RCurly[0], + trailingComma: this.prettierOptions.trailingComma + }); + } + variableInitializerList(ctx) { + const variableInitializers = this.mapVisit(ctx.variableInitializer); + const commas = ctx.Comma + ? ctx.Comma.map(comma => { + return rejectAndConcat([comma, line]); + }) + : []; + return rejectAndJoinSeps(commas, variableInitializers); + } +} diff --git a/packages/prettier-plugin-java/dist/printers/blocks-and-statements.js b/packages/prettier-plugin-java/dist/printers/blocks-and-statements.js new file mode 100644 index 00000000..e55640c2 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/blocks-and-statements.js @@ -0,0 +1,435 @@ +import { builders } from "prettier/doc"; +import { concat, group, indent, join } from "./prettier-builder.js"; +import { printTokenWithComments } from "./comments/format-comments.js"; +import { hasLeadingLineComments, hasTrailingLineComments } from "./comments/comments-utils.js"; +import { displaySemicolon, getBlankLinesSeparator, isStatementEmptyStatement, putIntoBraces, rejectAndConcat, rejectAndJoin, rejectAndJoinSeps, rejectSeparators, sortModifiers } from "./printer-utils.js"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +const { line, softline, hardline } = builders; +export class BlocksAndStatementPrettierVisitor extends BaseCstPrettierPrinter { + block(ctx) { + const blockStatements = this.visit(ctx.blockStatements); + return putIntoBraces(blockStatements, hardline, ctx.LCurly[0], ctx.RCurly[0]); + } + blockStatements(ctx) { + const blockStatement = this.mapVisit(ctx.blockStatement); + const separators = rejectSeparators(getBlankLinesSeparator(ctx.blockStatement), blockStatement); + return rejectAndJoinSeps(separators, blockStatement); + } + blockStatement(ctx) { + return this.visitSingle(ctx); + } + localVariableDeclarationStatement(ctx) { + const localVariableDeclaration = this.visit(ctx.localVariableDeclaration); + return rejectAndConcat([localVariableDeclaration, ctx.Semicolon[0]]); + } + localVariableDeclaration(ctx) { + const modifiers = sortModifiers(ctx.variableModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const finalModifiers = this.mapVisit(modifiers[1]); + const localVariableType = this.visit(ctx.localVariableType); + const variableDeclaratorList = this.visit(ctx.variableDeclaratorList); + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + rejectAndJoin(" ", finalModifiers), + localVariableType, + variableDeclaratorList + ]) + ]); + } + localVariableType(ctx) { + if (ctx.unannType) { + return this.visitSingle(ctx); + } + return printTokenWithComments(this.getSingle(ctx)); + } + statement(ctx, params) { + // handling Labeled statements comments + if (ctx.labeledStatement !== undefined) { + const newLabelStatement = Object.assign({}, ctx.labeledStatement[0]); + const newColon = Object.assign({}, ctx.labeledStatement[0].children.Colon[0]); + const newStatement = Object.assign({}, ctx.labeledStatement[0].children.statement[0]); + const labeledStatementLeadingComments = []; + if (newColon.trailingComments !== undefined) { + labeledStatementLeadingComments.push(...newColon.trailingComments); + delete newColon.trailingComments; + } + if (newStatement.leadingComments !== undefined) { + labeledStatementLeadingComments.push(...newStatement.leadingComments); + delete newStatement.leadingComments; + } + if (labeledStatementLeadingComments.length !== 0) { + newLabelStatement.leadingComments = labeledStatementLeadingComments; + } + newLabelStatement.children.Colon[0] = newColon; + newLabelStatement.children.statement[0] = newStatement; + return this.visit([newLabelStatement]); + } + return this.visitSingle(ctx, params); + } + statementWithoutTrailingSubstatement(ctx, params) { + return this.visitSingle(ctx, params); + } + emptyStatement(ctx, params) { + return displaySemicolon(ctx.Semicolon[0], params); + } + labeledStatement(ctx) { + const identifier = ctx.Identifier[0]; + const statement = this.visit(ctx.statement); + return rejectAndJoin(ctx.Colon[0], [identifier, statement]); + } + expressionStatement(ctx) { + const statementExpression = this.visit(ctx.statementExpression); + return rejectAndConcat([statementExpression, ctx.Semicolon[0]]); + } + statementExpression(ctx) { + return this.visitSingle(ctx); + } + ifStatement(ctx) { + var _a; + const expression = this.visit(ctx.expression); + const ifStatement = this.visit(ctx.statement[0], { + allowEmptyStatement: true + }); + const ifSeparator = isStatementEmptyStatement(ifStatement) ? "" : " "; + let elsePart = ""; + if (ctx.Else !== undefined) { + const elseStatement = this.visit(ctx.statement[1], { + allowEmptyStatement: true + }); + const elseSeparator = isStatementEmptyStatement(elseStatement) ? "" : " "; + const elseOnSameLine = hasTrailingLineComments(ctx.statement[0]) || + hasLeadingLineComments(ctx.Else[0]) || + !((_a = ctx.statement[0].children.statementWithoutTrailingSubstatement) === null || _a === void 0 ? void 0 : _a[0].children.block) + ? hardline + : " "; + elsePart = rejectAndJoin(elseSeparator, [ + concat([elseOnSameLine, ctx.Else[0]]), + elseStatement + ]); + } + return rejectAndConcat([ + rejectAndJoin(" ", [ + ctx.If[0], + concat([ + putIntoBraces(expression, softline, ctx.LBrace[0], ctx.RBrace[0]), + ifSeparator + ]) + ]), + ifStatement, + elsePart + ]); + } + assertStatement(ctx) { + const expressions = this.mapVisit(ctx.expression); + const colon = ctx.Colon ? ctx.Colon[0] : ":"; + return rejectAndConcat([ + concat([ctx.Assert[0], " "]), + rejectAndJoin(concat([" ", colon, " "]), expressions), + ctx.Semicolon[0] + ]); + } + switchStatement(ctx) { + const expression = this.visit(ctx.expression); + const switchBlock = this.visit(ctx.switchBlock); + return rejectAndJoin(" ", [ + ctx.Switch[0], + putIntoBraces(expression, softline, ctx.LBrace[0], ctx.RBrace[0]), + switchBlock + ]); + } + switchBlock(ctx) { + const switchCases = ctx.switchBlockStatementGroup !== undefined + ? this.mapVisit(ctx.switchBlockStatementGroup) + : this.mapVisit(ctx.switchRule); + return putIntoBraces(rejectAndJoin(hardline, switchCases), hardline, ctx.LCurly[0], ctx.RCurly[0]); + } + switchBlockStatementGroup(ctx) { + var _a, _b, _c; + const switchLabel = this.visit(ctx.switchLabel); + const blockStatements = this.visit(ctx.blockStatements); + const statements = (_a = ctx.blockStatements) === null || _a === void 0 ? void 0 : _a[0].children.blockStatement; + const hasSingleStatementBlock = (statements === null || statements === void 0 ? void 0 : statements.length) === 1 && + ((_c = (_b = statements[0].children.statement) === null || _b === void 0 ? void 0 : _b[0].children.statementWithoutTrailingSubstatement) === null || _c === void 0 ? void 0 : _c[0].children.block) !== undefined; + return concat([ + switchLabel, + ctx.Colon[0], + hasSingleStatementBlock + ? concat([" ", blockStatements]) + : blockStatements && indent([hardline, blockStatements]) + ]); + } + switchLabel(ctx) { + var _a, _b, _c; + const Case = (_a = ctx.Case) === null || _a === void 0 ? void 0 : _a[0]; + const commas = (_b = ctx.Comma) === null || _b === void 0 ? void 0 : _b.map(elt => concat([elt, line])); + if (ctx.caseConstant || ctx.Null) { + const caseConstants = ctx.Null + ? [ctx.Null[0], (_c = ctx.Default) === null || _c === void 0 ? void 0 : _c[0]] + : this.mapVisit(ctx.caseConstant); + return group(indent(join(" ", [Case, rejectAndJoinSeps(commas, caseConstants)]))); + } + else if (ctx.pattern) { + const patterns = this.mapVisit(ctx.pattern); + const guard = this.visit(ctx.guard); + const multiplePatterns = ctx.pattern.length > 1; + const separator = multiplePatterns ? line : " "; + const contents = join(separator, [ + Case, + rejectAndJoinSeps(commas, patterns) + ]); + return group(rejectAndJoin(separator, [ + multiplePatterns ? indent(contents) : contents, + guard + ])); + } + return printTokenWithComments(ctx.Default[0]); + } + switchRule(ctx) { + const switchLabel = this.visit(ctx.switchLabel); + let caseInstruction; + if (ctx.throwStatement !== undefined) { + caseInstruction = this.visit(ctx.throwStatement); + } + else if (ctx.block !== undefined) { + caseInstruction = this.visit(ctx.block); + } + else { + caseInstruction = concat([this.visit(ctx.expression), ctx.Semicolon[0]]); + } + return concat([switchLabel, " ", ctx.Arrow[0], " ", caseInstruction]); + } + caseConstant(ctx) { + return this.visitSingle(ctx); + } + whileStatement(ctx) { + const expression = this.visit(ctx.expression); + const statement = this.visit(ctx.statement[0], { + allowEmptyStatement: true + }); + const statementSeparator = isStatementEmptyStatement(statement) ? "" : " "; + return rejectAndJoin(" ", [ + ctx.While[0], + rejectAndJoin(statementSeparator, [ + putIntoBraces(expression, softline, ctx.LBrace[0], ctx.RBrace[0]), + statement + ]) + ]); + } + doStatement(ctx) { + const statement = this.visit(ctx.statement[0], { + allowEmptyStatement: true + }); + const statementSeparator = isStatementEmptyStatement(statement) ? "" : " "; + const expression = this.visit(ctx.expression); + return rejectAndJoin(" ", [ + rejectAndJoin(statementSeparator, [ctx.Do[0], statement]), + ctx.While[0], + rejectAndConcat([ + putIntoBraces(expression, softline, ctx.LBrace[0], ctx.RBrace[0]), + ctx.Semicolon[0] + ]) + ]); + } + forStatement(ctx) { + return this.visitSingle(ctx); + } + basicForStatement(ctx) { + const forInit = this.visit(ctx.forInit); + const expression = this.visit(ctx.expression); + const forUpdate = this.visit(ctx.forUpdate); + const statement = this.visit(ctx.statement[0], { + allowEmptyStatement: true + }); + const statementSeparator = isStatementEmptyStatement(statement) ? "" : " "; + return rejectAndConcat([ + rejectAndJoin(" ", [ + ctx.For[0], + putIntoBraces(rejectAndConcat([ + forInit, + rejectAndJoin(line, [ctx.Semicolon[0], expression]), + rejectAndJoin(line, [ctx.Semicolon[1], forUpdate]) + ]), softline, ctx.LBrace[0], ctx.RBrace[0]) + ]), + statementSeparator, + statement + ]); + } + forInit(ctx) { + return this.visitSingle(ctx); + } + forUpdate(ctx) { + return this.visitSingle(ctx); + } + statementExpressionList(ctx) { + const statementExpressions = this.mapVisit(ctx.statementExpression); + const commas = ctx.Comma + ? ctx.Comma.map(elt => { + return concat([printTokenWithComments(elt), " "]); + }) + : []; + return rejectAndJoinSeps(commas, statementExpressions); + } + enhancedForStatement(ctx) { + const variableModifiers = this.mapVisit(ctx.variableModifier); + const localVariableType = this.visit(ctx.localVariableType); + const variableDeclaratorId = this.visit(ctx.variableDeclaratorId); + const expression = this.visit(ctx.expression); + const statement = this.visit(ctx.statement[0], { + allowEmptyStatement: true + }); + const statementSeparator = isStatementEmptyStatement(statement) ? "" : " "; + return rejectAndConcat([ + rejectAndJoin(" ", [ctx.For[0], ctx.LBrace[0]]), + rejectAndJoin(" ", [ + rejectAndJoin(" ", variableModifiers), + localVariableType, + variableDeclaratorId + ]), + concat([" ", ctx.Colon[0], " "]), + expression, + concat([ctx.RBrace[0], statementSeparator]), + statement + ]); + } + breakStatement(ctx) { + if (ctx.Identifier) { + const identifier = ctx.Identifier[0]; + return rejectAndConcat([ + concat([ctx.Break[0], " "]), + identifier, + ctx.Semicolon[0] + ]); + } + return concat([ctx.Break[0], ctx.Semicolon[0]]); + } + continueStatement(ctx) { + if (ctx.Identifier) { + const identifier = ctx.Identifier[0]; + return rejectAndConcat([ + concat([ctx.Continue[0], " "]), + identifier, + ctx.Semicolon[0] + ]); + } + return rejectAndConcat([ctx.Continue[0], ctx.Semicolon[0]]); + } + returnStatement(ctx) { + if (ctx.expression) { + const expression = this.visit(ctx.expression, { + addParenthesisToWrapStatement: true + }); + return rejectAndConcat([ + concat([ctx.Return[0], " "]), + expression, + ctx.Semicolon[0] + ]); + } + return rejectAndConcat([ctx.Return[0], ctx.Semicolon[0]]); + } + throwStatement(ctx) { + const expression = this.visit(ctx.expression); + return rejectAndConcat([ + concat([ctx.Throw[0], " "]), + expression, + ctx.Semicolon[0] + ]); + } + synchronizedStatement(ctx) { + const expression = this.visit(ctx.expression); + const block = this.visit(ctx.block); + return rejectAndConcat([ + join(" ", [ + ctx.Synchronized[0], + concat([ + putIntoBraces(expression, softline, ctx.LBrace[0], ctx.RBrace[0]), + " " + ]) + ]), + block + ]); + } + tryStatement(ctx) { + if (ctx.tryWithResourcesStatement) { + return this.visit(ctx.tryWithResourcesStatement); + } + const block = this.visit(ctx.block); + const catches = this.visit(ctx.catches); + const finallyBlock = this.visit(ctx.finally); + return rejectAndJoin(" ", [ctx.Try[0], block, catches, finallyBlock]); + } + catches(ctx) { + const catchClauses = this.mapVisit(ctx.catchClause); + return rejectAndJoin(" ", catchClauses); + } + catchClause(ctx) { + const catchFormalParameter = this.visit(ctx.catchFormalParameter); + const block = this.visit(ctx.block); + return rejectAndConcat([ + group(rejectAndConcat([ + rejectAndJoin(" ", [ctx.Catch[0], ctx.LBrace[0]]), + indent(rejectAndConcat([softline, catchFormalParameter])), + softline, + concat([ctx.RBrace[0], " "]) + ])), + block + ]); + } + catchFormalParameter(ctx) { + const variableModifiers = this.mapVisit(ctx.variableModifier); + const catchType = this.visit(ctx.catchType); + const variableDeclaratorId = this.visit(ctx.variableDeclaratorId); + return rejectAndJoin(" ", [ + rejectAndJoin(" ", variableModifiers), + catchType, + variableDeclaratorId + ]); + } + catchType(ctx) { + const unannClassType = this.visit(ctx.unannClassType); + const classTypes = this.mapVisit(ctx.classType); + const ors = ctx.Or ? ctx.Or.map(elt => concat([line, elt, " "])) : []; + return group(rejectAndJoinSeps(ors, [unannClassType, ...classTypes])); + } + finally(ctx) { + const block = this.visit(ctx.block); + return rejectAndJoin(" ", [ctx.Finally[0], block]); + } + tryWithResourcesStatement(ctx) { + const resourceSpecification = this.visit(ctx.resourceSpecification); + const block = this.visit(ctx.block); + const catches = this.visit(ctx.catches); + const finallyBlock = this.visit(ctx.finally); + return rejectAndJoin(" ", [ + ctx.Try[0], + resourceSpecification, + block, + catches, + finallyBlock + ]); + } + resourceSpecification(ctx) { + const resourceList = this.visit(ctx.resourceList); + const optionalSemicolon = ctx.Semicolon ? ctx.Semicolon[0] : ""; + return putIntoBraces(rejectAndConcat([resourceList, optionalSemicolon]), softline, ctx.LBrace[0], ctx.RBrace[0]); + } + resourceList(ctx) { + const resources = this.mapVisit(ctx.resource); + const semicolons = ctx.Semicolon + ? ctx.Semicolon.map(elt => { + return concat([elt, line]); + }) + : [""]; + return rejectAndJoinSeps(semicolons, resources); + } + resource(ctx) { + return this.visitSingle(ctx); + } + yieldStatement(ctx) { + const expression = this.visit(ctx.expression); + return join(" ", [ctx.Yield[0], concat([expression, ctx.Semicolon[0]])]); + } + variableAccess(ctx) { + return this.visitSingle(ctx); + } +} diff --git a/packages/prettier-plugin-java/dist/printers/classes.js b/packages/prettier-plugin-java/dist/printers/classes.js new file mode 100644 index 00000000..9df59085 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/classes.js @@ -0,0 +1,693 @@ +import forEach from "lodash/forEach.js"; +import { displaySemicolon, getBlankLinesSeparator, getClassBodyDeclarationsSeparator, isStatementEmptyStatement, putIntoBraces, reject, rejectAndConcat, rejectAndJoin, rejectAndJoinSeps, sortClassTypeChildren, sortModifiers } from "./printer-utils.js"; +import { concat, group, indent, join, indentIfBreak } from "./prettier-builder.js"; +import { printTokenWithComments } from "./comments/format-comments.js"; +import { hasLeadingComments, hasLeadingLineComments } from "./comments/comments-utils.js"; +import { builders } from "prettier/doc"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +import { isAnnotationCstNode, isTypeArgumentsCstNode } from "../types/utils.js"; +import { printArgumentListWithBraces } from "../utils/index.js"; +const { line, softline, hardline, lineSuffixBoundary } = builders; +export class ClassesPrettierVisitor extends BaseCstPrettierPrinter { + classDeclaration(ctx) { + const modifiers = sortModifiers(ctx.classModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + let classCST; + if (ctx.normalClassDeclaration !== undefined) { + classCST = ctx.normalClassDeclaration; + } + else if (ctx.enumDeclaration !== undefined) { + classCST = ctx.enumDeclaration; + } + else { + classCST = ctx.recordDeclaration; + } + const classDoc = this.visit(classCST); + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [join(" ", otherModifiers), classDoc]) + ]); + } + normalClassDeclaration(ctx) { + const name = this.visit(ctx.typeIdentifier); + const optionalTypeParams = this.visit(ctx.typeParameters); + const optionalSuperClasses = this.visit(ctx.superclass); + const optionalSuperInterfaces = this.visit(ctx.superinterfaces); + const optionalClassPermits = this.visit(ctx.classPermits); + const body = this.visit(ctx.classBody, { isNormalClassDeclaration: true }); + let superClassesPart = ""; + if (optionalSuperClasses) { + superClassesPart = indent(rejectAndConcat([line, optionalSuperClasses])); + } + let superInterfacesPart = ""; + if (optionalSuperInterfaces) { + superInterfacesPart = indent(rejectAndConcat([line, optionalSuperInterfaces])); + } + let classPermits = ""; + if (optionalClassPermits) { + classPermits = indent(rejectAndConcat([line, optionalClassPermits])); + } + return rejectAndJoin(" ", [ + group(rejectAndConcat([ + rejectAndJoin(" ", [ctx.Class[0], name]), + optionalTypeParams, + superClassesPart, + superInterfacesPart, + classPermits + ])), + body + ]); + } + classModifier(ctx) { + if (ctx.annotation) { + return this.visit(ctx.annotation); + } + // public | protected | private | ... + return printTokenWithComments(this.getSingle(ctx)); + } + typeParameters(ctx) { + const typeParameterList = this.visit(ctx.typeParameterList); + return putIntoBraces(typeParameterList, softline, ctx.Less[0], ctx.Greater[0]); + } + typeParameterList(ctx) { + const typeParameter = this.mapVisit(ctx.typeParameter); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return group(rejectAndJoinSeps(commas, typeParameter)); + } + superclass(ctx) { + return join(" ", [ctx.Extends[0], this.visit(ctx.classType)]); + } + superinterfaces(ctx) { + const interfaceTypeList = this.visit(ctx.interfaceTypeList); + return group(rejectAndConcat([ + ctx.Implements[0], + indent(rejectAndConcat([line, interfaceTypeList])) + ])); + } + classPermits(ctx) { + const typeNames = this.mapVisit(ctx.typeName); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return group(rejectAndConcat([ + ctx.Permits[0], + indent(rejectAndConcat([line, group(rejectAndJoinSeps(commas, typeNames))])) + ])); + } + interfaceTypeList(ctx) { + const interfaceType = this.mapVisit(ctx.interfaceType); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return group(rejectAndJoinSeps(commas, interfaceType)); + } + classBody(ctx, param) { + let content = ""; + if (ctx.classBodyDeclaration !== undefined) { + const classBodyDeclsVisited = reject(this.mapVisit(ctx.classBodyDeclaration)); + const separators = getClassBodyDeclarationsSeparator(ctx.classBodyDeclaration); + content = rejectAndJoinSeps(separators, classBodyDeclsVisited); + // edge case when we have SemiColons + let shouldHardline = false; + ctx.classBodyDeclaration.forEach(elt => { + if ((elt.children.classMemberDeclaration && + !elt.children.classMemberDeclaration[0].children.Semicolon) || + elt.children.constructorDeclaration) { + shouldHardline = true; + } + }); + if ((ctx.classBodyDeclaration[0].children.classMemberDeclaration || + ctx.classBodyDeclaration[0].children.constructorDeclaration) && + shouldHardline && + param && + param.isNormalClassDeclaration) { + content = rejectAndConcat([hardline, content]); + } + } + return putIntoBraces(content, hardline, ctx.LCurly[0], ctx.RCurly[0]); + } + classBodyDeclaration(ctx) { + return this.visitSingle(ctx); + } + classMemberDeclaration(ctx) { + if (ctx.Semicolon) { + return displaySemicolon(ctx.Semicolon[0]); + } + return this.visitSingle(ctx); + } + fieldDeclaration(ctx) { + const modifiers = sortModifiers(ctx.fieldModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const unannType = this.visit(ctx.unannType); + const variableDeclaratorList = this.visit(ctx.variableDeclaratorList); + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + rejectAndJoin(" ", otherModifiers), + unannType, + concat([variableDeclaratorList, ctx.Semicolon[0]]) + ]) + ]); + } + fieldModifier(ctx) { + if (ctx.annotation) { + return this.visit(ctx.annotation); + } + // public | protected | private | ... + return printTokenWithComments(this.getSingle(ctx)); + } + variableDeclaratorList(ctx) { + const variableDeclarators = this.mapVisit(ctx.variableDeclarator); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, " "])) : []; + return rejectAndJoinSeps(commas, variableDeclarators); + } + variableDeclarator(ctx) { + const variableDeclaratorId = this.visit(ctx.variableDeclaratorId); + if (ctx.Equals) { + const variableInitializer = this.visit(ctx.variableInitializer); + if (hasLeadingLineComments(ctx.variableInitializer[0])) { + return group(indent(rejectAndJoin(hardline, [ + rejectAndJoin(" ", [variableDeclaratorId, ctx.Equals[0]]), + variableInitializer + ]))); + } + if ( + // Array Initialisation + ctx.variableInitializer[0].children.arrayInitializer !== undefined || + // Lambda expression + ctx.variableInitializer[0].children.expression[0].children + .lambdaExpression !== undefined || + // Ternary Expression + (ctx.variableInitializer[0].children.expression[0].children + .ternaryExpression !== undefined && + ctx.variableInitializer[0].children.expression[0].children + .ternaryExpression[0].children.QuestionMark !== undefined)) { + const groupId = Symbol("assignment"); + return group([ + group(variableDeclaratorId), + " ", + ctx.Equals[0], + group(indent(line), { id: groupId }), + lineSuffixBoundary, + indentIfBreak(variableInitializer, { groupId }) + ]); + } + if (ctx.variableInitializer[0].children.expression[0].children + .ternaryExpression !== undefined) { + const unaryExpressions = ctx.variableInitializer[0].children.expression[0].children + .ternaryExpression[0].children.binaryExpression[0].children + .unaryExpression; + const firstPrimary = unaryExpressions[0].children.primary[0]; + // Cast Expression + if (firstPrimary.children.primaryPrefix[0].children.castExpression !== + undefined && + unaryExpressions.length === 1) { + const groupId = Symbol("assignment"); + return group([ + group(variableDeclaratorId), + " ", + ctx.Equals[0], + group(indent(line), { id: groupId }), + lineSuffixBoundary, + indentIfBreak(variableInitializer, { groupId }) + ]); + } + // New Expression + if (firstPrimary.children.primaryPrefix[0].children.newExpression !== + undefined) { + const groupId = Symbol("assignment"); + return group([ + group(variableDeclaratorId), + " ", + ctx.Equals[0], + group(indent(line), { id: groupId }), + lineSuffixBoundary, + indentIfBreak(variableInitializer, { groupId }) + ]); + } + // Method Invocation + const isMethodInvocation = firstPrimary.children.primarySuffix !== undefined && + firstPrimary.children.primarySuffix[0].children + .methodInvocationSuffix !== undefined; + const isUniqueUnaryExpression = ctx.variableInitializer[0].children.expression[0].children + .ternaryExpression[0].children.binaryExpression[0].children + .unaryExpression.length === 1; + const isUniqueMethodInvocation = isMethodInvocation && isUniqueUnaryExpression; + if (isUniqueMethodInvocation) { + const groupId = Symbol("assignment"); + return group([ + group(variableDeclaratorId), + " ", + ctx.Equals[0], + group(indent(line), { id: groupId }), + lineSuffixBoundary, + indentIfBreak(variableInitializer, { groupId }) + ]); + } + } + return group(indent(rejectAndJoin(line, [ + rejectAndJoin(" ", [variableDeclaratorId, ctx.Equals[0]]), + variableInitializer + ]))); + } + return variableDeclaratorId; + } + variableDeclaratorId(ctx) { + if (ctx.Underscore) { + return printTokenWithComments(ctx.Underscore[0]); + } + const identifier = ctx.Identifier[0]; + const dims = this.visit(ctx.dims); + return rejectAndConcat([identifier, dims]); + } + variableInitializer(ctx) { + return this.visitSingle(ctx); + } + unannType(ctx) { + return this.visitSingle(ctx); + } + unannPrimitiveTypeWithOptionalDimsSuffix(ctx) { + const unannPrimitiveType = this.visit(ctx.unannPrimitiveType); + const dims = this.visit(ctx.dims); + return rejectAndConcat([unannPrimitiveType, dims]); + } + unannPrimitiveType(ctx) { + if (ctx.numericType) { + return this.visitSingle(ctx); + } + return printTokenWithComments(this.getSingle(ctx)); + } + unannReferenceType(ctx) { + const unannClassOrInterfaceType = this.visit(ctx.unannClassOrInterfaceType); + const dims = this.visit(ctx.dims); + return rejectAndConcat([unannClassOrInterfaceType, dims]); + } + unannClassOrInterfaceType(ctx) { + return this.visit(ctx.unannClassType); + } + unannClassType(ctx) { + const tokens = sortClassTypeChildren(ctx.annotation, ctx.typeArguments, ctx.Identifier); + const segments = []; + let currentSegment = []; + forEach(tokens, (token, i) => { + if (isTypeArgumentsCstNode(token)) { + currentSegment.push(this.visit([token])); + segments.push(rejectAndConcat(currentSegment)); + currentSegment = []; + } + else if (isAnnotationCstNode(token)) { + currentSegment.push(this.visit([token])); + currentSegment.push(" "); + } + else { + currentSegment.push(token); + if ((i + 1 < tokens.length && !isTypeArgumentsCstNode(tokens[i + 1])) || + i + 1 === tokens.length) { + segments.push(rejectAndConcat(currentSegment)); + currentSegment = []; + } + } + }); + return rejectAndJoinSeps(ctx.Dot, segments); + } + unannInterfaceType(ctx) { + return this.visit(ctx.unannClassType); + } + unannTypeVariable(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } + methodDeclaration(ctx) { + const modifiers = sortModifiers(ctx.methodModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const header = this.visit(ctx.methodHeader); + const body = this.visit(ctx.methodBody); + const headerBodySeparator = isStatementEmptyStatement(body) ? "" : " "; + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + rejectAndJoin(" ", otherModifiers), + rejectAndJoin(headerBodySeparator, [header, body]) + ]) + ]); + } + methodModifier(ctx) { + if (ctx.annotation) { + return this.visit(ctx.annotation); + } + // public | protected | private | Synchronized | ... + return printTokenWithComments(this.getSingle(ctx)); + } + methodHeader(ctx) { + const typeParameters = this.visit(ctx.typeParameters); + const annotations = this.mapVisit(ctx.annotation); + const result = this.visit(ctx.result); + const declarator = this.visit(ctx.methodDeclarator); + const throws = this.visit(ctx.throws); + return group(concat([ + rejectAndJoin(" ", [ + typeParameters, + rejectAndJoin(line, annotations), + result, + declarator, + throws + ]) + ])); + } + result(ctx) { + if (ctx.unannType) { + return this.visit(ctx.unannType); + } + // void + return printTokenWithComments(this.getSingle(ctx)); + } + methodDeclarator(ctx) { + const identifier = printTokenWithComments(ctx.Identifier[0]); + const formalParameterList = this.visit(ctx.formalParameterList); + const dims = this.visit(ctx.dims); + return rejectAndConcat([ + identifier, + putIntoBraces(formalParameterList, softline, ctx.LBrace[0], ctx.RBrace[0]), + dims + ]); + } + receiverParameter(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const unannType = this.visit(ctx.unannType); + const identifier = ctx.Identifier + ? concat([ctx.Identifier[0], ctx.Dot[0]]) + : ""; + return rejectAndJoin("", [ + rejectAndJoin(" ", annotations), + unannType, + identifier, + ctx.This[0] + ]); + } + formalParameterList(ctx) { + const formalParameter = this.mapVisit(ctx.formalParameter); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return rejectAndJoinSeps(commas, formalParameter); + } + formalParameter(ctx) { + return this.visitSingle(ctx); + } + variableParaRegularParameter(ctx) { + const variableModifier = this.mapVisit(ctx.variableModifier); + const unannType = this.visit(ctx.unannType); + const variableDeclaratorId = this.visit(ctx.variableDeclaratorId); + return rejectAndJoin(" ", [ + rejectAndJoin(" ", variableModifier), + unannType, + variableDeclaratorId + ]); + } + variableArityParameter(ctx) { + const variableModifier = this.mapVisit(ctx.variableModifier); + const unannType = this.visit(ctx.unannType); + const annotations = this.mapVisit(ctx.annotation); + const identifier = ctx.Identifier[0]; + const unannTypePrinted = ctx.annotation === undefined + ? concat([unannType, ctx.DotDotDot[0]]) + : unannType; + const annotationsPrinted = ctx.annotation === undefined + ? annotations + : concat([rejectAndJoin(" ", annotations), ctx.DotDotDot[0]]); + return rejectAndJoin(" ", [ + join(" ", variableModifier), + unannTypePrinted, + annotationsPrinted, + identifier + ]); + } + variableModifier(ctx) { + if (ctx.annotation) { + return this.visit(ctx.annotation); + } + return printTokenWithComments(this.getSingle(ctx)); + } + throws(ctx) { + const exceptionTypeList = this.visit(ctx.exceptionTypeList); + const throwsDeclaration = join(" ", [ctx.Throws[0], exceptionTypeList]); + return group(indent(rejectAndConcat([softline, throwsDeclaration]))); + } + exceptionTypeList(ctx) { + const exceptionTypes = this.mapVisit(ctx.exceptionType); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, " "])) : []; + return rejectAndJoinSeps(commas, exceptionTypes); + } + exceptionType(ctx) { + return this.visitSingle(ctx); + } + methodBody(ctx) { + if (ctx.block) { + return this.visit(ctx.block); + } + return printTokenWithComments(this.getSingle(ctx)); + } + instanceInitializer(ctx) { + return this.visitSingle(ctx); + } + staticInitializer(ctx) { + const block = this.visit(ctx.block); + return join(" ", [ctx.Static[0], block]); + } + constructorDeclaration(ctx) { + const modifiers = sortModifiers(ctx.constructorModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const constructorDeclarator = this.visit(ctx.constructorDeclarator); + const throws = this.visit(ctx.throws); + const constructorBody = this.visit(ctx.constructorBody); + return rejectAndJoin(" ", [ + group(rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + join(" ", otherModifiers), + constructorDeclarator, + throws + ]) + ])), + constructorBody + ]); + } + constructorModifier(ctx) { + if (ctx.annotation) { + return this.visit(ctx.annotation); + } + // public | protected | private | Synchronized | ... + return printTokenWithComments(this.getSingle(ctx)); + } + constructorDeclarator(ctx) { + const typeParameters = this.visit(ctx.typeParameters); + const simpleTypeName = this.visit(ctx.simpleTypeName); + const receiverParameter = this.visit(ctx.receiverParameter); + const formalParameterList = this.visit(ctx.formalParameterList); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, " "])) : []; + return rejectAndJoin(" ", [ + typeParameters, + concat([ + simpleTypeName, + putIntoBraces(rejectAndJoinSeps(commas, [receiverParameter, formalParameterList]), softline, ctx.LBrace[0], ctx.RBrace[0]) + ]) + ]); + } + simpleTypeName(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } + constructorBody(ctx) { + const explicitConstructorInvocation = this.visit(ctx.explicitConstructorInvocation); + const blockStatements = this.visit(ctx.blockStatements); + return putIntoBraces(rejectAndJoin(hardline, [explicitConstructorInvocation, blockStatements]), hardline, ctx.LCurly[0], ctx.RCurly[0]); + } + explicitConstructorInvocation(ctx) { + return this.visitSingle(ctx); + } + unqualifiedExplicitConstructorInvocation(ctx) { + const typeArguments = this.visit(ctx.typeArguments); + const keyWord = ctx.This ? ctx.This[0] : ctx.Super[0]; + const argumentList = printArgumentListWithBraces.call(this, ctx.argumentList, ctx.RBrace[0], ctx.LBrace[0]); + return rejectAndConcat([ + typeArguments, + keyWord, + group(rejectAndConcat([argumentList, ctx.Semicolon[0]])) + ]); + } + qualifiedExplicitConstructorInvocation(ctx) { + const expressionName = this.visit(ctx.expressionName); + const typeArguments = this.visit(ctx.typeArguments); + const argumentList = printArgumentListWithBraces.call(this, ctx.argumentList, ctx.RBrace[0], ctx.LBrace[0]); + return rejectAndConcat([ + expressionName, + ctx.Dot[0], + typeArguments, + ctx.Super[0], + group(rejectAndConcat([argumentList, ctx.Semicolon[0]])) + ]); + } + enumDeclaration(ctx) { + const classModifier = this.mapVisit(ctx.classModifier); + const typeIdentifier = this.visit(ctx.typeIdentifier); + const superinterfaces = this.visit(ctx.superinterfaces); + const enumBody = this.visit(ctx.enumBody); + return rejectAndJoin(" ", [ + join(" ", classModifier), + ctx.Enum[0], + typeIdentifier, + superinterfaces, + enumBody + ]); + } + enumBody(ctx) { + const enumConstantList = this.visit(ctx.enumConstantList); + const enumBodyDeclarations = this.visit(ctx.enumBodyDeclarations); + const hasEnumConstants = ctx.enumConstantList !== undefined; + const hasNoClassBodyDeclarations = ctx.enumBodyDeclarations === undefined || + ctx.enumBodyDeclarations[0].children.classBodyDeclaration === undefined; + // edge case: https://github.com/jhipster/prettier-java/issues/383 + const handleEnumBodyDeclarationsLeadingComments = !hasNoClassBodyDeclarations && + hasLeadingComments(ctx.enumBodyDeclarations[0]) + ? hardline + : ""; + let optionalComma; + if (hasEnumConstants && + hasNoClassBodyDeclarations && + this.prettierOptions.trailingComma !== "none") { + optionalComma = ctx.Comma ? ctx.Comma[0] : ","; + } + else { + optionalComma = ctx.Comma ? Object.assign(Object.assign({}, ctx.Comma[0]), { image: "" }) : ""; + } + return putIntoBraces(rejectAndConcat([ + enumConstantList, + optionalComma, + handleEnumBodyDeclarationsLeadingComments, + enumBodyDeclarations + ]), hardline, ctx.LCurly[0], ctx.RCurly[0]); + } + enumConstantList(ctx) { + const enumConstants = this.mapVisit(ctx.enumConstant); + const blankLineSeparators = getBlankLinesSeparator(ctx.enumConstant); + const commas = ctx.Comma + ? ctx.Comma.map((elt, index) => concat([elt, blankLineSeparators[index]])) + : []; + return group(rejectAndJoinSeps(commas, enumConstants)); + } + enumConstant(ctx) { + const modifiers = sortModifiers(ctx.enumConstantModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const identifier = ctx.Identifier[0]; + const classBody = this.visit(ctx.classBody); + const optionalBracesAndArgumentList = ctx.LBrace + ? printArgumentListWithBraces.call(this, ctx.argumentList, ctx.RBrace[0], ctx.LBrace[0]) + : ""; + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + rejectAndJoin(" ", otherModifiers), + rejectAndConcat([identifier, optionalBracesAndArgumentList]), + classBody + ]) + ]); + } + enumConstantModifier(ctx) { + return this.visitSingle(ctx); + } + enumBodyDeclarations(ctx) { + if (ctx.classBodyDeclaration !== undefined) { + const classBodyDeclaration = this.mapVisit(ctx.classBodyDeclaration); + const separators = getClassBodyDeclarationsSeparator(ctx.classBodyDeclaration); + return rejectAndJoin(concat([hardline, hardline]), [ + ctx.Semicolon[0], + rejectAndJoinSeps(separators, classBodyDeclaration) + ]); + } + return printTokenWithComments(Object.assign(Object.assign({}, ctx.Semicolon[0]), { image: "" })); + } + recordDeclaration(ctx) { + const name = this.visit(ctx.typeIdentifier); + const optionalTypeParams = this.visit(ctx.typeParameters); + const recordHeader = this.visit(ctx.recordHeader); + let superInterfacesPart = ""; + const optionalSuperInterfaces = this.visit(ctx.superinterfaces); + if (optionalSuperInterfaces) { + superInterfacesPart = indent(rejectAndConcat([line, optionalSuperInterfaces])); + } + const body = this.visit(ctx.recordBody); + return rejectAndJoin(" ", [ + group(rejectAndConcat([ + rejectAndJoin(" ", [ctx.Record[0], name]), + optionalTypeParams, + recordHeader, + superInterfacesPart + ])), + body + ]); + } + recordHeader(ctx) { + const recordComponentList = this.visit(ctx.recordComponentList); + return putIntoBraces(recordComponentList, softline, ctx.LBrace[0], ctx.RBrace[0]); + } + recordComponentList(ctx) { + const recordComponents = this.mapVisit(ctx.recordComponent); + const blankLineSeparators = getBlankLinesSeparator(ctx.recordComponent, line); + const commas = ctx.Comma + ? ctx.Comma.map((elt, index) => concat([elt, blankLineSeparators[index]])) + : []; + return rejectAndJoinSeps(commas, recordComponents); + } + recordComponent(ctx) { + const modifiers = this.mapVisit(ctx.recordComponentModifier); + const unannType = this.visit(ctx.unannType); + if (ctx.Identifier !== undefined) { + return group(rejectAndJoin(line, [ + join(line, modifiers), + join(" ", [unannType, ctx.Identifier[0]]) + ])); + } + const variableArityRecordComponent = this.visit(ctx.variableArityRecordComponent); + if (ctx.variableArityRecordComponent[0].children.annotation !== undefined) { + return group(rejectAndJoin(line, [ + join(line, modifiers), + join(" ", [unannType, variableArityRecordComponent]) + ])); + } + return group(rejectAndJoin(line, [ + join(line, modifiers), + concat([unannType, variableArityRecordComponent]) + ])); + } + variableArityRecordComponent(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const identifier = ctx.Identifier[0]; + return rejectAndJoin(" ", [ + rejectAndConcat([rejectAndJoin(" ", annotations), ctx.DotDotDot[0]]), + identifier + ]); + } + recordComponentModifier(ctx) { + return this.visitSingle(ctx); + } + recordBody(ctx) { + return putIntoBraces(rejectAndJoinSeps(getBlankLinesSeparator(ctx.recordBodyDeclaration), this.mapVisit(ctx.recordBodyDeclaration)), hardline, ctx.LCurly[0], ctx.RCurly[0]); + } + recordBodyDeclaration(ctx) { + return this.visitSingle(ctx); + } + compactConstructorDeclaration(ctx) { + const modifiers = sortModifiers(ctx.constructorModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const name = this.visit(ctx.simpleTypeName); + const constructorBody = this.visit(ctx.constructorBody); + return rejectAndJoin(" ", [ + group(rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [join(" ", otherModifiers), name]) + ])), + constructorBody + ]); + } + isDims() { + return "isDims"; + } +} diff --git a/packages/prettier-plugin-java/dist/printers/comments/comments-utils.js b/packages/prettier-plugin-java/dist/printers/comments/comments-utils.js new file mode 100644 index 00000000..b3740201 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/comments/comments-utils.js @@ -0,0 +1,21 @@ +export function hasLeadingComments(token) { + return token.leadingComments !== undefined; +} +export function hasTrailingComments(token) { + return token.trailingComments !== undefined; +} +export function hasLeadingLineComments(token) { + return (token.leadingComments !== undefined && + token.leadingComments.length !== 0 && + token.leadingComments[token.leadingComments.length - 1].tokenType.name === + "LineComment"); +} +export function hasTrailingLineComments(token) { + return (token.trailingComments !== undefined && + token.trailingComments.length !== 0 && + token.trailingComments[token.trailingComments.length - 1].tokenType.name === + "LineComment"); +} +export function hasComments(token) { + return hasLeadingComments(token) || hasTrailingComments(token); +} diff --git a/packages/prettier-plugin-java/dist/printers/comments/format-comments.js b/packages/prettier-plugin-java/dist/printers/comments/format-comments.js new file mode 100644 index 00000000..15789624 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/comments/format-comments.js @@ -0,0 +1,168 @@ +import { builders } from "prettier/doc"; +import { isCstElementOrUndefinedIToken } from "../../types/utils.js"; +import isEmptyDoc from "../../utils/isEmptyDoc.js"; +const { hardline, lineSuffix, breakParent, literalline } = builders; +/** + * Takes a token and return a doc with: + * - concatenated leading comments + * - the token image + * - concatenated trailing comments + * + * @param {IToken} token + * @return a doc with the token and its comments + */ +export function printTokenWithComments(token) { + return printWithComments(token, token.image, getTokenLeadingComments, getTokenTrailingComments); +} +/** + * Takes a node and return a doc with: + * - concatenated leading comments + * - the node doc value + * - concatenated trailing comments + * + * @param {CstNode} node + * @param {Doc} value - the converted node value + * @return a doc with the token and its comments + */ +export function printNodeWithComments(node, value) { + return printWithComments(node, value, getNodeLeadingComments, getNodeTrailingComments); +} +function printWithComments(nodeOrToken, value, getLeadingComments, getTrailingComments) { + const leadingComments = getLeadingComments(nodeOrToken); + const trailingComments = getTrailingComments(nodeOrToken, value); + return leadingComments.length === 0 && trailingComments.length === 0 + ? value + : [...leadingComments, value, ...trailingComments]; +} +/** + * @param {IToken} token + * @return an array containing processed leading comments and separators + */ +export function getTokenLeadingComments(token) { + return getLeadingComments(token, token); +} +/** + * @param {CstNode} node + * @return an array containing processed leading comments and separators + */ +function getNodeLeadingComments(node) { + return getLeadingComments(node, node.location); +} +function getLeadingComments(nodeOrToken, location) { + const arr = []; + if (nodeOrToken.leadingComments !== undefined) { + let previousEndLine = nodeOrToken.leadingComments[0].endLine; + let step; + arr.push(formatComment(nodeOrToken.leadingComments[0])); + for (let i = 1; i < nodeOrToken.leadingComments.length; i++) { + step = nodeOrToken.leadingComments[i].startLine - previousEndLine; + if (step === 1 || + nodeOrToken.leadingComments[i].startOffset > location.startOffset) { + arr.push(hardline); + } + else if (step > 1) { + arr.push(hardline, hardline); + } + arr.push(formatComment(nodeOrToken.leadingComments[i])); + previousEndLine = nodeOrToken.leadingComments[i].endLine; + } + step = location.startLine - previousEndLine; + if (step === 1 || + nodeOrToken.leadingComments[nodeOrToken.leadingComments.length - 1] + .startOffset > location.startOffset) { + arr.push(hardline); + } + else if (step > 1) { + arr.push(hardline, hardline); + } + } + return arr; +} +/** + * @param {IToken} token + * @return an array containing processed trailing comments and separators + */ +function getTokenTrailingComments(token) { + return getTrailingComments(token, token.image, token); +} +/** + * @param {CstNode} node + * @param {string} value + * @return an array containing processed trailing comments and separators + */ +function getNodeTrailingComments(node, value) { + return getTrailingComments(node, value, node.location); +} +function getTrailingComments(nodeOrToken, value, location) { + const arr = []; + let previousEndLine = location.endLine; + if (nodeOrToken.trailingComments !== undefined) { + nodeOrToken.trailingComments.forEach((comment, idx) => { + let separator = ""; + if (comment.startLine !== previousEndLine) { + arr.push(hardline); + } + else if (!isEmptyDoc(value) && idx === 0) { + separator = " "; + } + if (comment.tokenType.name === "LineComment") { + arr.push(lineSuffix([separator, formatComment(comment), breakParent])); + } + else { + arr.push(formatComment(comment)); + } + previousEndLine = comment.endLine; + }); + } + return arr; +} +function isJavaDoc(comment, lines) { + let isJavaDoc = true; + if (comment.tokenType.name === "TraditionalComment" && lines.length > 1) { + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim().charAt(0) !== "*") { + isJavaDoc = false; + break; + } + } + } + else { + isJavaDoc = false; + } + return isJavaDoc; +} +function formatJavaDoc(lines) { + const res = [lines[0].trim()]; + for (let i = 1; i < lines.length; i++) { + res.push(hardline); + res.push(" " + lines[i].trim()); + } + return res; +} +function formatComment(comment) { + const res = []; + const lines = comment.image.split("\n"); + if (isJavaDoc(comment, lines)) { + return formatJavaDoc(lines); + } + lines.forEach(line => { + res.push(line); + res.push(literalline); + }); + res.pop(); + return res; +} +export function processComments(docs) { + if (!Array.isArray(docs)) { + if (isCstElementOrUndefinedIToken(docs)) { + return printTokenWithComments(docs); + } + return docs; + } + return docs.map(elt => { + if (isCstElementOrUndefinedIToken(elt)) { + return printTokenWithComments(elt); + } + return elt; + }); +} diff --git a/packages/prettier-plugin-java/dist/printers/comments/handle-comments.js b/packages/prettier-plugin-java/dist/printers/comments/handle-comments.js new file mode 100644 index 00000000..0eeb345d --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/comments/handle-comments.js @@ -0,0 +1,73 @@ +import { hasLeadingComments, hasTrailingComments } from "./comments-utils.js"; +export function handleCommentsBinaryExpression(ctx) { + moveOperatorLeadingCommentsToNextExpression(ctx); + moveExpressionTrailingCommentsToNextOperator(ctx); +} +function moveOperatorLeadingCommentsToNextExpression(ctx) { + var _a; + let unaryExpressionIndex = 1; + (_a = ctx.BinaryOperator) === null || _a === void 0 ? void 0 : _a.forEach(binaryOperator => { + if (hasLeadingComments(binaryOperator)) { + while (ctx.unaryExpression[unaryExpressionIndex].location.startOffset < + binaryOperator.endOffset) { + unaryExpressionIndex++; + } + // Adapt the position of the operator and its leading comments + const shiftUp = binaryOperator.leadingComments[0].startLine - + 1 - + binaryOperator.startLine; + if (binaryOperator.startLine !== + ctx.unaryExpression[unaryExpressionIndex].location.startLine) { + binaryOperator.leadingComments.forEach(comment => { + comment.startLine += 1; + comment.endLine += 1; + }); + } + binaryOperator.startLine += shiftUp; + binaryOperator.endLine += shiftUp; + // Move binaryOperator's leading comments to the following + // unaryExpression + ctx.unaryExpression[unaryExpressionIndex].leadingComments = + ctx.unaryExpression[unaryExpressionIndex].leadingComments || []; + ctx.unaryExpression[unaryExpressionIndex].leadingComments.unshift(...binaryOperator.leadingComments); + delete binaryOperator.leadingComments; + } + }); +} +function moveExpressionTrailingCommentsToNextOperator(ctx) { + const binaryOperators = ctx.BinaryOperator; + let binaryOperatorIndex = 1; + if (binaryOperators === null || binaryOperators === void 0 ? void 0 : binaryOperators.length) { + ctx.unaryExpression.forEach(unaryExpression => { + var _a; + if (hasTrailingComments(unaryExpression)) { + while (binaryOperatorIndex < binaryOperators.length && + unaryExpression.location.endOffset && + binaryOperators[binaryOperatorIndex].startOffset < + unaryExpression.location.endOffset) { + binaryOperatorIndex++; + } + const binaryOperator = binaryOperators[binaryOperatorIndex]; + // Adapt the position of the expression and its trailing comments + const shiftUp = unaryExpression.trailingComments[0].startLine - + 1 - + unaryExpression.location.startLine; + if (unaryExpression.location.startLine !== binaryOperator.startLine) { + unaryExpression.trailingComments.forEach(comment => { + comment.startLine += 1; + comment.endLine += 1; + }); + } + unaryExpression.location.startLine += shiftUp; + if (unaryExpression.location.endLine !== undefined) { + unaryExpression.location.endLine += shiftUp; + } + // Move unaryExpression's trailing comments to the following + // binaryOperator + binaryOperator.trailingComments = (_a = binaryOperator.trailingComments) !== null && _a !== void 0 ? _a : []; + binaryOperator.trailingComments.unshift(...unaryExpression.trailingComments); + delete unaryExpression.trailingComments; + } + }); + } +} diff --git a/packages/prettier-plugin-java/dist/printers/expressions.js b/packages/prettier-plugin-java/dist/printers/expressions.js new file mode 100644 index 00000000..cab3e8fa --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/expressions.js @@ -0,0 +1,495 @@ +import forEach from "lodash/forEach.js"; +import { builders } from "prettier/doc"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +import { isAnnotationCstNode } from "../types/utils.js"; +import { isArgumentListSingleLambda } from "../utils/expressions-utils.js"; +import { printSingleLambdaInvocation, printArgumentListWithBraces } from "../utils/index.js"; +import { printTokenWithComments } from "./comments/format-comments.js"; +import { handleCommentsBinaryExpression } from "./comments/handle-comments.js"; +import { concat, dedent, group, indent } from "./prettier-builder.js"; +import { binary, findDeepElementInPartsArray, isExplicitLambdaParameter, isUniqueMethodInvocation, putIntoBraces, rejectAndConcat, rejectAndJoin, rejectAndJoinSeps, sortAnnotationIdentifier, sortNodes, sortTokens } from "./printer-utils.js"; +const { ifBreak, line, softline, indentIfBreak } = builders; +export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter { + expression(ctx, params) { + return this.visitSingle(ctx, params); + } + lambdaExpression(ctx, params) { + const lambdaParameters = group(this.visit(ctx.lambdaParameters, params), params ? { id: params.lambdaParametersGroupId } : undefined); + const lambdaBody = this.visit(ctx.lambdaBody); + const isLambdaBodyABlock = ctx.lambdaBody[0].children.block !== undefined; + if (isLambdaBodyABlock) { + return rejectAndJoin(" ", [ + lambdaParameters, + ctx.Arrow[0], + (params === null || params === void 0 ? void 0 : params.lambdaParametersGroupId) !== undefined + ? indentIfBreak(lambdaBody, { + groupId: params.lambdaParametersGroupId + }) + : lambdaBody + ]); + } + return group(indent(rejectAndJoin(line, [ + rejectAndJoin(" ", [lambdaParameters, ctx.Arrow[0]]), + lambdaBody + ]))); + } + lambdaParameters(ctx, params) { + if (ctx.lambdaParametersWithBraces) { + return this.visitSingle(ctx, params); + } + return printTokenWithComments(this.getSingle(ctx)); + } + lambdaParametersWithBraces(ctx, params) { + const lambdaParameterList = this.visit(ctx.lambdaParameterList); + if (findDeepElementInPartsArray(lambdaParameterList, ",")) { + const content = putIntoBraces(lambdaParameterList, softline, ctx.LBrace[0], ctx.RBrace[0]); + if ((params === null || params === void 0 ? void 0 : params.isInsideMethodInvocationSuffix) === true) { + return indent(concat([softline, content])); + } + return content; + } + // removing braces when only no comments attached + if ((ctx.LBrace && + ctx.RBrace && + (!lambdaParameterList || isExplicitLambdaParameter(ctx))) || + ctx.LBrace[0].leadingComments || + ctx.LBrace[0].trailingComments || + ctx.RBrace[0].leadingComments || + ctx.RBrace[0].trailingComments) { + return rejectAndConcat([ + ctx.LBrace[0], + lambdaParameterList, + ctx.RBrace[0] + ]); + } + return lambdaParameterList; + } + lambdaParameterList(ctx) { + return this.visitSingle(ctx); + } + inferredLambdaParameterList(ctx) { + const commas = ctx.Comma + ? ctx.Comma.map(elt => { + return concat([elt, line]); + }) + : []; + return rejectAndJoinSeps(commas, ctx.Identifier); + } + explicitLambdaParameterList(ctx) { + const lambdaParameter = this.mapVisit(ctx.lambdaParameter); + const commas = ctx.Comma + ? ctx.Comma.map(elt => { + return concat([elt, line]); + }) + : []; + return rejectAndJoinSeps(commas, lambdaParameter); + } + lambdaParameter(ctx) { + return this.visitSingle(ctx); + } + regularLambdaParameter(ctx) { + const variableModifier = this.mapVisit(ctx.variableModifier); + const lambdaParameterType = this.visit(ctx.lambdaParameterType); + const variableDeclaratorId = this.visit(ctx.variableDeclaratorId); + return rejectAndJoin(" ", [ + rejectAndJoin(" ", variableModifier), + lambdaParameterType, + variableDeclaratorId + ]); + } + lambdaParameterType(ctx) { + if (ctx.unannType) { + return this.visitSingle(ctx); + } + return printTokenWithComments(this.getSingle(ctx)); + } + lambdaBody(ctx) { + return this.visitSingle(ctx); + } + ternaryExpression(ctx, params) { + const binaryExpression = this.visit(ctx.binaryExpression, params); + if (ctx.QuestionMark) { + const expression1 = this.visit(ctx.expression[0]); + const expression2 = this.visit(ctx.expression[1]); + return indent(group(rejectAndConcat([ + rejectAndJoin(line, [ + binaryExpression, + rejectAndJoin(" ", [ctx.QuestionMark[0], expression1]), + rejectAndJoin(" ", [ctx.Colon[0], expression2]) + ]) + ]))); + } + return binaryExpression; + } + binaryExpression(ctx, params) { + handleCommentsBinaryExpression(ctx); + const sortedNodes = sortNodes([ + ctx.pattern, + ctx.referenceType, + ctx.expression, + ctx.unaryExpression + ]); + const nodes = this.mapVisit(sortedNodes, sortedNodes.length === 1 ? params : undefined); + const tokens = sortTokens([ + ctx.Instanceof, + ctx.AssignmentOperator, + ctx.Less, + ctx.Greater, + ctx.BinaryOperator + ]); + const hasTokens = tokens.length > 0; + const content = binary(nodes, tokens, true); + return hasTokens && (params === null || params === void 0 ? void 0 : params.addParenthesisToWrapStatement) + ? group(concat([ + ifBreak("("), + indent(concat([softline, content])), + softline, + ifBreak(")") + ])) + : content; + } + unaryExpression(ctx, params) { + const unaryPrefixOperator = ctx.UnaryPrefixOperator + ? ctx.UnaryPrefixOperator + : []; + const primary = this.visit(ctx.primary, params); + const unarySuffixOperator = ctx.UnarySuffixOperator + ? ctx.UnarySuffixOperator + : []; + return rejectAndConcat([ + rejectAndConcat(unaryPrefixOperator), + primary, + rejectAndConcat(unarySuffixOperator) + ]); + } + unaryExpressionNotPlusMinus(ctx) { + const unaryPrefixOperatorNotPlusMinus = ctx.UnaryPrefixOperatorNotPlusMinus // changed when moved to TS + ? rejectAndJoin(" ", ctx.UnaryPrefixOperatorNotPlusMinus) // changed when moved to TS + : ""; + const primary = this.visit(ctx.primary); + const unarySuffixOperator = ctx.UnarySuffixOperator // changed when moved to TS + ? rejectAndJoin(" ", ctx.UnarySuffixOperator) // changed when moved to TS + : ""; + return rejectAndJoin(" ", [ + unaryPrefixOperatorNotPlusMinus, + primary, + unarySuffixOperator + ]); + } + primary(ctx, params) { + const countMethodInvocation = isUniqueMethodInvocation(ctx.primarySuffix); + const primaryPrefix = this.visit(ctx.primaryPrefix, Object.assign(Object.assign({}, params), { shouldBreakBeforeFirstMethodInvocation: countMethodInvocation > 1 })); + const suffixes = []; + if (ctx.primarySuffix !== undefined) { + // edge case: https://github.com/jhipster/prettier-java/issues/381 + let hasFirstInvocationArg = true; + if (ctx.primarySuffix.length > 1 && + ctx.primarySuffix[1].children.methodInvocationSuffix && + Object.keys(ctx.primarySuffix[1].children.methodInvocationSuffix[0].children).length === 2) { + hasFirstInvocationArg = false; + } + if (ctx.primarySuffix[0].children.Dot !== undefined && + ctx.primaryPrefix[0].children.newExpression !== undefined) { + suffixes.push(softline); + } + suffixes.push(this.visit(ctx.primarySuffix[0], { + shouldDedent: + // dedent when simple method invocation + countMethodInvocation !== 1 && + // dedent when (chain) method invocation + ctx.primaryPrefix[0] && + ctx.primaryPrefix[0].children.fqnOrRefType && + !(ctx.primaryPrefix[0].children.fqnOrRefType[0].children.Dot !== + undefined) && + // indent when lambdaExpression + ctx.primarySuffix[0].children.methodInvocationSuffix && + ctx.primarySuffix[0].children.methodInvocationSuffix[0].children + .argumentList && + ctx.primarySuffix[0].children.methodInvocationSuffix[0].children + .argumentList[0].children.expression && + ctx.primarySuffix[0].children.methodInvocationSuffix[0].children + .argumentList[0].children.expression[0].children + .lambdaExpression === undefined + })); + for (let i = 1; i < ctx.primarySuffix.length; i++) { + if (ctx.primarySuffix[i].children.Dot !== undefined && + ctx.primarySuffix[i - 1].children.methodInvocationSuffix !== undefined) { + suffixes.push(softline); + } + suffixes.push(this.visit(ctx.primarySuffix[i])); + } + if (countMethodInvocation === 1 && + ctx.primaryPrefix[0].children.newExpression === undefined) { + return group(rejectAndConcat([ + primaryPrefix, + hasFirstInvocationArg ? suffixes[0] : indent(suffixes[0]), + indent(rejectAndConcat(suffixes.slice(1))) + ])); + } + } + return group(rejectAndConcat([primaryPrefix, indent(rejectAndConcat(suffixes))])); + } + primaryPrefix(ctx, params) { + if (ctx.This || ctx.Void) { + return printTokenWithComments(this.getSingle(ctx)); + } + return this.visitSingle(ctx, params); + } + primarySuffix(ctx, params) { + if (ctx.Dot) { + if (ctx.This) { + return rejectAndConcat([ctx.Dot[0], ctx.This[0]]); + } + else if (ctx.Identifier) { + const typeArguments = this.visit(ctx.typeArguments); + return rejectAndConcat([ctx.Dot[0], typeArguments, ctx.Identifier[0]]); + } + const unqualifiedClassInstanceCreationExpression = this.visit(ctx.unqualifiedClassInstanceCreationExpression); + return rejectAndConcat([ + ctx.Dot[0], + unqualifiedClassInstanceCreationExpression + ]); + } + return this.visitSingle(ctx, params); + } + fqnOrRefType(ctx, params) { + const fqnOrRefTypePartFirst = this.visit(ctx.fqnOrRefTypePartFirst); + const fqnOrRefTypePartRest = this.mapVisit(ctx.fqnOrRefTypePartRest); + const dims = this.visit(ctx.dims); + const dots = ctx.Dot ? ctx.Dot : []; + const isMethodInvocation = ctx.Dot && ctx.Dot.length === 1; + if (params !== undefined && + params.shouldBreakBeforeFirstMethodInvocation === true) { + // when fqnOrRefType is a method call from an object + if (isMethodInvocation) { + return rejectAndConcat([ + indent(rejectAndJoin(concat([softline, dots[0]]), [ + fqnOrRefTypePartFirst, + rejectAndJoinSeps(dots.slice(1), fqnOrRefTypePartRest), + dims + ])) + ]); + // otherwise it is a fully qualified name but we need to exclude when it is just a method call + } + else if (ctx.Dot) { + return indent(rejectAndConcat([ + rejectAndJoinSeps(dots.slice(0, dots.length - 1), [ + fqnOrRefTypePartFirst, + ...fqnOrRefTypePartRest.slice(0, fqnOrRefTypePartRest.length - 1) + ]), + softline, + rejectAndConcat([ + dots[dots.length - 1], + fqnOrRefTypePartRest[fqnOrRefTypePartRest.length - 1] + ]), + dims + ])); + } + } + return rejectAndConcat([ + rejectAndJoinSeps(dots, [fqnOrRefTypePartFirst, ...fqnOrRefTypePartRest]), + dims + ]); + } + fqnOrRefTypePartFirst(ctx) { + const annotation = this.mapVisit(ctx.annotation); + const fqnOrRefTypeCommon = this.visit(ctx.fqnOrRefTypePartCommon); + return rejectAndJoin(" ", [ + rejectAndJoin(" ", annotation), + fqnOrRefTypeCommon + ]); + } + fqnOrRefTypePartRest(ctx) { + const annotation = this.mapVisit(ctx.annotation); + const fqnOrRefTypeCommon = this.visit(ctx.fqnOrRefTypePartCommon); + const typeArguments = this.visit(ctx.typeArguments); + return rejectAndJoin(" ", [ + rejectAndJoin(" ", annotation), + rejectAndConcat([typeArguments, fqnOrRefTypeCommon]) + ]); + } + fqnOrRefTypePartCommon(ctx) { + let keyWord = null; + if (ctx.Identifier) { + keyWord = ctx.Identifier[0]; + } + else { + keyWord = ctx.Super[0]; + } + const typeArguments = this.visit(ctx.typeArguments); + return rejectAndConcat([keyWord, typeArguments]); + } + parenthesisExpression(ctx, params) { + const expression = this.visit(ctx.expression); + const separator = (params === null || params === void 0 ? void 0 : params.addParenthesisToWrapStatement) ? softline : ""; + return putIntoBraces(expression, separator, ctx.LBrace[0], ctx.RBrace[0]); + } + castExpression(ctx) { + return this.visitSingle(ctx); + } + primitiveCastExpression(ctx) { + const primitiveType = this.visit(ctx.primitiveType); + const unaryExpression = this.visit(ctx.unaryExpression); + return rejectAndJoin(" ", [ + rejectAndConcat([ctx.LBrace[0], primitiveType, ctx.RBrace[0]]), + unaryExpression + ]); + } + referenceTypeCastExpression(ctx) { + const referenceType = this.visit(ctx.referenceType); + const hasAdditionalBounds = ctx.additionalBound !== undefined; + const additionalBounds = rejectAndJoin(line, this.mapVisit(ctx.additionalBound)); + const expression = ctx.lambdaExpression + ? this.visit(ctx.lambdaExpression) + : this.visit(ctx.unaryExpressionNotPlusMinus); + return rejectAndJoin(" ", [ + putIntoBraces(rejectAndJoin(line, [referenceType, additionalBounds]), hasAdditionalBounds ? softline : "", ctx.LBrace[0], ctx.RBrace[0]), + expression + ]); + } + newExpression(ctx) { + return this.visitSingle(ctx); + } + unqualifiedClassInstanceCreationExpression(ctx) { + const typeArguments = this.visit(ctx.typeArguments); + const classOrInterfaceTypeToInstantiate = this.visit(ctx.classOrInterfaceTypeToInstantiate); + let content = printArgumentListWithBraces.call(this, ctx.argumentList, ctx.RBrace[0], ctx.LBrace[0]); + const classBody = this.visit(ctx.classBody); + return rejectAndJoin(" ", [ + ctx.New[0], + rejectAndConcat([ + typeArguments, + classOrInterfaceTypeToInstantiate, + content + ]), + classBody + ]); + } + classOrInterfaceTypeToInstantiate(ctx) { + const tokens = sortAnnotationIdentifier(ctx.annotation, ctx.Identifier); + const segments = []; + let currentSegment = []; + forEach(tokens, token => { + if (isAnnotationCstNode(token)) { + currentSegment.push(this.visit([token])); + } + else { + currentSegment.push(token); + segments.push(rejectAndJoin(" ", currentSegment)); + currentSegment = []; + } + }); + const typeArgumentsOrDiamond = this.visit(ctx.typeArgumentsOrDiamond); + const dots = ctx.Dot ? ctx.Dot : []; + return rejectAndConcat([ + rejectAndJoinSeps(dots, segments), + typeArgumentsOrDiamond + ]); + } + typeArgumentsOrDiamond(ctx) { + return this.visitSingle(ctx); + } + diamond(ctx) { + return concat([ctx.Less[0], ctx.Greater[0]]); + } + methodInvocationSuffix(ctx, params) { + const isSingleLambda = isArgumentListSingleLambda(ctx.argumentList); + if (isSingleLambda) { + return printSingleLambdaInvocation.call(this, ctx.argumentList, ctx.RBrace[0], ctx.LBrace[0]); + } + const argumentList = this.visit(ctx.argumentList); + if (params && params.shouldDedent) { + return dedent(putIntoBraces(argumentList, softline, ctx.LBrace[0], ctx.RBrace[0])); + } + return putIntoBraces(argumentList, softline, ctx.LBrace[0], ctx.RBrace[0]); + } + argumentList(ctx, params) { + const expressions = this.mapVisit(ctx.expression, params); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return rejectAndJoinSeps(commas, expressions); + } + arrayCreationExpression(ctx) { + const type = ctx.primitiveType + ? this.visit(ctx.primitiveType) + : this.visit(ctx.classOrInterfaceType); + const suffix = ctx.arrayCreationDefaultInitSuffix + ? this.visit(ctx.arrayCreationDefaultInitSuffix) + : this.visit(ctx.arrayCreationExplicitInitSuffix); + return rejectAndConcat([concat([ctx.New[0], " "]), type, suffix]); + } + arrayCreationDefaultInitSuffix(ctx) { + const dimExprs = this.visit(ctx.dimExprs); + const dims = this.visit(ctx.dims); + return rejectAndConcat([dimExprs, dims]); + } + arrayCreationExplicitInitSuffix(ctx) { + const dims = this.visit(ctx.dims); + const arrayInitializer = this.visit(ctx.arrayInitializer); + return rejectAndJoin(" ", [dims, arrayInitializer]); + } + dimExprs(ctx) { + const dimExpr = this.mapVisit(ctx.dimExpr); + return rejectAndConcat(dimExpr); + } + dimExpr(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const expression = this.visit(ctx.expression); + return rejectAndJoin(" ", [ + rejectAndJoin(" ", annotations), + rejectAndConcat([ctx.LSquare[0], expression, ctx.RSquare[0]]) + ]); + } + classLiteralSuffix(ctx) { + const squares = []; + if (ctx.LSquare) { + for (let i = 0; i < ctx.LSquare.length; i++) { + squares.push(concat([ctx.LSquare[i], ctx.RSquare[i]])); + } + } + return rejectAndConcat([...squares, ctx.Dot[0], ctx.Class[0]]); + } + arrayAccessSuffix(ctx) { + const expression = this.visit(ctx.expression); + return rejectAndConcat([ctx.LSquare[0], expression, ctx.RSquare[0]]); + } + methodReferenceSuffix(ctx) { + const typeArguments = this.visit(ctx.typeArguments); + const identifierOrNew = ctx.New ? ctx.New[0] : ctx.Identifier[0]; + return rejectAndConcat([ctx.ColonColon[0], typeArguments, identifierOrNew]); + } + pattern(ctx) { + return this.visitSingle(ctx); + } + typePattern(ctx) { + return this.visitSingle(ctx); + } + recordPattern(ctx) { + const referenceType = this.visit(ctx.referenceType); + const componentPatternList = this.visit(ctx.componentPatternList); + return concat([ + referenceType, + putIntoBraces(componentPatternList, softline, ctx.LBrace[0], ctx.RBrace[0]) + ]); + } + componentPatternList(ctx) { + var _a, _b; + const componentPatterns = this.mapVisit(ctx.componentPattern); + const commas = (_b = (_a = ctx.Comma) === null || _a === void 0 ? void 0 : _a.map(elt => concat([elt, line]))) !== null && _b !== void 0 ? _b : []; + return rejectAndJoinSeps(commas, componentPatterns); + } + componentPattern(ctx) { + return this.visitSingle(ctx); + } + unnamedPattern(ctx) { + return printTokenWithComments(ctx.Underscore[0]); + } + guard(ctx) { + const expression = this.visit(ctx.expression, { + addParenthesisToWrapStatement: true + }); + return concat([ctx.When[0], " ", expression]); + } + isRefTypeInMethodRef() { + return "isRefTypeInMethodRef"; + } +} diff --git a/packages/prettier-plugin-java/dist/printers/interfaces.js b/packages/prettier-plugin-java/dist/printers/interfaces.js new file mode 100644 index 00000000..04568250 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/interfaces.js @@ -0,0 +1,218 @@ +import { concat, group, indent } from "./prettier-builder.js"; +import { printTokenWithComments } from "./comments/format-comments.js"; +import { displaySemicolon, getInterfaceBodyDeclarationsSeparator, isStatementEmptyStatement, printArrayList, putIntoBraces, rejectAndConcat, rejectAndJoin, rejectAndJoinSeps, sortModifiers } from "./printer-utils.js"; +import { builders } from "prettier/doc"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +const { line, softline, hardline } = builders; +export class InterfacesPrettierVisitor extends BaseCstPrettierPrinter { + interfaceDeclaration(ctx) { + const modifiers = sortModifiers(ctx.interfaceModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const declaration = ctx.normalInterfaceDeclaration + ? this.visit(ctx.normalInterfaceDeclaration) + : this.visit(ctx.annotationTypeDeclaration); + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [rejectAndJoin(" ", otherModifiers), declaration]) + ]); + } + normalInterfaceDeclaration(ctx) { + const typeIdentifier = this.visit(ctx.typeIdentifier); + const typeParameters = this.visit(ctx.typeParameters); + const extendsInterfaces = this.visit(ctx.extendsInterfaces); + const optionalInterfacePermits = this.visit(ctx.interfacePermits); + const interfaceBody = this.visit(ctx.interfaceBody); + let extendsInterfacesPart = ""; + if (extendsInterfaces) { + extendsInterfacesPart = indent(rejectAndConcat([softline, extendsInterfaces])); + } + let interfacePermits = ""; + if (optionalInterfacePermits) { + interfacePermits = indent(rejectAndConcat([softline, optionalInterfacePermits])); + } + return rejectAndJoin(" ", [ + group(rejectAndJoin(" ", [ + ctx.Interface[0], + concat([typeIdentifier, typeParameters]), + extendsInterfacesPart, + interfacePermits + ])), + interfaceBody + ]); + } + interfaceModifier(ctx) { + if (ctx.annotation) { + return this.visitSingle(ctx); + } + return printTokenWithComments(this.getSingle(ctx)); + } + extendsInterfaces(ctx) { + const interfaceTypeList = this.visit(ctx.interfaceTypeList); + return group(rejectAndConcat([ + ctx.Extends[0], + indent(rejectAndConcat([line, interfaceTypeList])) + ])); + } + interfacePermits(ctx) { + return this.classPermits(ctx); + } + interfaceBody(ctx) { + let joinedInterfaceMemberDeclaration = ""; + if (ctx.interfaceMemberDeclaration !== undefined) { + const interfaceMemberDeclaration = this.mapVisit(ctx.interfaceMemberDeclaration); + const separators = getInterfaceBodyDeclarationsSeparator(ctx.interfaceMemberDeclaration); + joinedInterfaceMemberDeclaration = rejectAndJoinSeps(separators, interfaceMemberDeclaration); + } + return putIntoBraces(joinedInterfaceMemberDeclaration, hardline, ctx.LCurly[0], ctx.RCurly[0]); + } + interfaceMemberDeclaration(ctx) { + if (ctx.Semicolon) { + return displaySemicolon(ctx.Semicolon[0]); + } + return this.visitSingle(ctx); + } + constantDeclaration(ctx) { + const modifiers = sortModifiers(ctx.constantModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const unannType = this.visit(ctx.unannType); + const variableDeclaratorList = this.visit(ctx.variableDeclaratorList); + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + rejectAndJoin(" ", otherModifiers), + unannType, + rejectAndConcat([variableDeclaratorList, ctx.Semicolon[0]]) + ]) + ]); + } + constantModifier(ctx) { + if (ctx.annotation) { + return this.visitSingle(ctx); + } + return printTokenWithComments(this.getSingle(ctx)); + } + interfaceMethodDeclaration(ctx) { + const modifiers = sortModifiers(ctx.interfaceMethodModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const methodHeader = this.visit(ctx.methodHeader); + const methodBody = this.visit(ctx.methodBody); + const separator = isStatementEmptyStatement(methodBody) ? "" : " "; + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + rejectAndJoin(" ", otherModifiers), + rejectAndJoin(separator, [methodHeader, methodBody]) + ]) + ]); + } + interfaceMethodModifier(ctx) { + if (ctx.annotation) { + return this.visitSingle(ctx); + } + return printTokenWithComments(this.getSingle(ctx)); + } + annotationTypeDeclaration(ctx) { + const typeIdentifier = this.visit(ctx.typeIdentifier); + const annotationTypeBody = this.visit(ctx.annotationTypeBody); + return rejectAndJoin(" ", [ + concat([ctx.At[0], ctx.Interface[0]]), + typeIdentifier, + annotationTypeBody + ]); + } + annotationTypeBody(ctx) { + const annotationTypeMemberDeclaration = this.mapVisit(ctx.annotationTypeMemberDeclaration); + return rejectAndJoin(line, [ + indent(rejectAndJoin(line, [ + ctx.LCurly[0], + rejectAndJoin(concat([line, line]), annotationTypeMemberDeclaration) + ])), + ctx.RCurly[0] + ]); + } + annotationTypeMemberDeclaration(ctx) { + if (ctx.Semicolon) { + return printTokenWithComments(this.getSingle(ctx)); + } + return this.visitSingle(ctx); + } + annotationTypeElementDeclaration(ctx) { + const modifiers = sortModifiers(ctx.annotationTypeElementModifier); + const firstAnnotations = this.mapVisit(modifiers[0]); + const otherModifiers = this.mapVisit(modifiers[1]); + const unannType = this.visit(ctx.unannType); + const identifier = ctx.Identifier[0]; + const dims = this.visit(ctx.dims); + const defaultValue = ctx.defaultValue + ? concat([" ", this.visit(ctx.defaultValue)]) + : ""; + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, firstAnnotations), + rejectAndJoin(" ", [ + rejectAndJoin(" ", otherModifiers), + unannType, + rejectAndConcat([ + identifier, + concat([ctx.LBrace[0], ctx.RBrace[0]]), + dims, + defaultValue, + ctx.Semicolon[0] + ]) + ]) + ]); + } + annotationTypeElementModifier(ctx) { + if (ctx.annotation) { + return this.visitSingle(ctx); + } + return printTokenWithComments(this.getSingle(ctx)); + } + defaultValue(ctx) { + const elementValue = this.visit(ctx.elementValue); + return rejectAndJoin(" ", [ctx.Default[0], elementValue]); + } + annotation(ctx) { + const fqn = this.visit(ctx.typeName); + let annoArgs = ""; + if (ctx.LBrace) { + if (ctx.elementValuePairList) { + annoArgs = putIntoBraces(this.visit(ctx.elementValuePairList), softline, ctx.LBrace[0], ctx.RBrace[0]); + } + else if (ctx.elementValue) { + annoArgs = putIntoBraces(this.visit(ctx.elementValue), softline, ctx.LBrace[0], ctx.RBrace[0]); + } + } + return group(rejectAndConcat([ctx.At[0], fqn, annoArgs])); + } + elementValuePairList(ctx) { + const elementValuePairs = this.mapVisit(ctx.elementValuePair); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return rejectAndJoinSeps(commas, elementValuePairs); + } + elementValuePair(ctx) { + const identifier = ctx.Identifier[0]; + const elementValue = this.visit(ctx.elementValue); + return rejectAndJoin(" ", [identifier, ctx.Equals[0], elementValue]); + } + elementValue(ctx) { + return this.visitSingle(ctx); + } + elementValueArrayInitializer(ctx) { + const elementValueList = this.visit(ctx.elementValueList); + return printArrayList({ + list: elementValueList, + extraComma: ctx.Comma, + LCurly: ctx.LCurly[0], + RCurly: ctx.RCurly[0], + trailingComma: this.prettierOptions.trailingComma + }); + } + elementValueList(ctx) { + const elementValues = this.mapVisit(ctx.elementValue); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return group(rejectAndConcat([rejectAndJoinSeps(commas, elementValues)])); + } +} diff --git a/packages/prettier-plugin-java/dist/printers/lexical-structure.js b/packages/prettier-plugin-java/dist/printers/lexical-structure.js new file mode 100644 index 00000000..10154c1b --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/lexical-structure.js @@ -0,0 +1,31 @@ +import { printTokenWithComments } from "./comments/format-comments.js"; +import { join } from "./prettier-builder.js"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +import { builders } from "prettier/doc"; +const { hardline } = builders; +export class LexicalStructurePrettierVisitor extends BaseCstPrettierPrinter { + literal(ctx) { + if (ctx.TextBlock) { + const lines = ctx.TextBlock[0].image.split("\n"); + const open = lines.shift(); + const baseIndent = Math.min(...lines.map(line => line.search(/\S/)).filter(indent => indent >= 0)); + return join(hardline, [ + open, + ...lines.map(line => line.slice(baseIndent)) + ]); + } + if (ctx.CharLiteral || ctx.StringLiteral || ctx.Null) { + return printTokenWithComments(this.getSingle(ctx)); + } + return this.visitSingle(ctx); + } + integerLiteral(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } + floatingPointLiteral(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } + booleanLiteral(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } +} diff --git a/packages/prettier-plugin-java/dist/printers/names.js b/packages/prettier-plugin-java/dist/printers/names.js new file mode 100644 index 00000000..7d860b47 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/names.js @@ -0,0 +1,29 @@ +import { buildFqn } from "./printer-utils.js"; +import { printTokenWithComments } from "./comments/format-comments.js"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +export class NamesPrettierVisitor extends BaseCstPrettierPrinter { + typeIdentifier(ctx) { + return printTokenWithComments(ctx.Identifier[0]); + } + moduleName(ctx) { + return buildFqn(ctx.Identifier, ctx.Dot); + } + packageName(ctx) { + return buildFqn(ctx.Identifier, ctx.Dot); + } + typeName(ctx) { + return buildFqn(ctx.Identifier, ctx.Dot); + } + expressionName(ctx) { + return buildFqn(ctx.Identifier, ctx.Dot); + } + methodName(ctx) { + return printTokenWithComments(ctx.Identifier[0]); + } + packageOrTypeName(ctx) { + return buildFqn(ctx.Identifier, ctx.Dot); + } + ambiguousName(ctx) { + return buildFqn(ctx.Identifier, ctx.Dot); + } +} diff --git a/packages/prettier-plugin-java/dist/printers/packages-and-modules.js b/packages/prettier-plugin-java/dist/printers/packages-and-modules.js new file mode 100644 index 00000000..9560f9f5 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/packages-and-modules.js @@ -0,0 +1,171 @@ +import { concat, join } from "./prettier-builder.js"; +import { printTokenWithComments } from "./comments/format-comments.js"; +import { buildFqn, displaySemicolon, getBlankLinesSeparator, putIntoBraces, rejectAndConcat, rejectAndJoin, rejectAndJoinSeps, sortImports } from "./printer-utils.js"; +import { builders } from "prettier/doc"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +import { isOrdinaryCompilationUnitCtx } from "../types/utils.js"; +const { line, hardline, indent, group } = builders; +export class PackagesAndModulesPrettierVisitor extends BaseCstPrettierPrinter { + compilationUnit(ctx) { + const compilationUnit = isOrdinaryCompilationUnitCtx(ctx) + ? ctx.ordinaryCompilationUnit + : ctx.modularCompilationUnit; + return concat([this.visit(compilationUnit[0]), line]); + } + ordinaryCompilationUnit(ctx) { + const packageDecl = this.visit(ctx.packageDeclaration); + const sortedImportsDecl = sortImports(ctx.importDeclaration); + const nonStaticImports = this.mapVisit(sortedImportsDecl.nonStaticImports); + const staticImports = this.mapVisit(sortedImportsDecl.staticImports); + const typesDecl = this.mapVisit(ctx.typeDeclaration); + // TODO: utility to add item+line (or multiple lines) but only if an item exists + return rejectAndConcat([ + rejectAndJoin(concat([hardline, hardline]), [ + packageDecl, + rejectAndJoin(hardline, staticImports), + rejectAndJoin(hardline, nonStaticImports), + rejectAndJoin(concat([hardline, hardline]), typesDecl) + ]) + ]); + } + modularCompilationUnit(ctx) { + const sortedImportsDecl = sortImports(ctx.importDeclaration); + const nonStaticImports = this.mapVisit(sortedImportsDecl.nonStaticImports); + const staticImports = this.mapVisit(sortedImportsDecl.staticImports); + const moduleDeclaration = this.visit(ctx.moduleDeclaration); + return rejectAndConcat([ + rejectAndJoin(concat([hardline, hardline]), [ + rejectAndJoin(hardline, staticImports), + rejectAndJoin(hardline, nonStaticImports), + moduleDeclaration + ]) + ]); + } + packageDeclaration(ctx) { + const modifiers = this.mapVisit(ctx.packageModifier); + const name = buildFqn(ctx.Identifier, ctx.Dot); + return rejectAndJoin(hardline, [ + rejectAndJoin(hardline, modifiers), + concat([ctx.Package[0], " ", name, ctx.Semicolon[0]]) + ]); + } + packageModifier(ctx) { + return this.visitSingle(ctx); + } + importDeclaration(ctx) { + if (ctx.emptyStatement !== undefined) { + return this.visit(ctx.emptyStatement); + } + const optionalStatic = ctx.Static ? ctx.Static[0] : ""; + const packageOrTypeName = this.visit(ctx.packageOrTypeName); + const optionalDotStar = ctx.Dot ? concat([ctx.Dot[0], ctx.Star[0]]) : ""; + return rejectAndJoin(" ", [ + ctx.Import[0], + optionalStatic, + rejectAndConcat([packageOrTypeName, optionalDotStar, ctx.Semicolon[0]]) + ]); + } + typeDeclaration(ctx) { + if (ctx.Semicolon) { + return displaySemicolon(ctx.Semicolon[0]); + } + return this.visitSingle(ctx); + } + moduleDeclaration(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const optionalOpen = ctx.Open ? ctx.Open[0] : ""; + const name = buildFqn(ctx.Identifier, ctx.Dot); + const moduleDirectives = this.mapVisit(ctx.moduleDirective); + const content = rejectAndJoinSeps(getBlankLinesSeparator(ctx.moduleDirective), moduleDirectives); + return rejectAndJoin(" ", [ + join(" ", annotations), + optionalOpen, + ctx.Module[0], + name, + putIntoBraces(content, hardline, ctx.LCurly[0], ctx.RCurly[0]) + ]); + } + moduleDirective(ctx) { + return this.visitSingle(ctx); + } + requiresModuleDirective(ctx) { + const modifiers = this.mapVisit(ctx.requiresModifier); + const moduleName = this.visit(ctx.moduleName); + return rejectAndJoin(" ", [ + ctx.Requires[0], + join(" ", modifiers), + concat([moduleName, ctx.Semicolon[0]]) + ]); + } + exportsModuleDirective(ctx) { + const packageName = this.visit(ctx.packageName); + const moduleNames = this.mapVisit(ctx.moduleName); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + if (ctx.To) { + return group(rejectAndConcat([ + indent(rejectAndJoin(line, [ + rejectAndJoin(" ", [ctx.Exports[0], packageName]), + group(indent(rejectAndJoin(line, [ + ctx.To[0], + rejectAndJoinSeps(commas, moduleNames) + ]))) + ])), + ctx.Semicolon[0] + ])); + } + return rejectAndConcat([ + concat([ctx.Exports[0], " "]), + packageName, + ctx.Semicolon[0] + ]); + } + opensModuleDirective(ctx) { + const packageName = this.visit(ctx.packageName); + const to = ctx.To ? ctx.To[0] : ""; + const moduleNames = this.mapVisit(ctx.moduleName); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + if (ctx.To) { + return group(rejectAndConcat([ + indent(rejectAndJoin(line, [ + rejectAndJoin(" ", [ctx.Opens[0], packageName]), + group(indent(rejectAndJoin(line, [ + ctx.To[0], + rejectAndJoinSeps(commas, moduleNames) + ]))) + ])), + ctx.Semicolon[0] + ])); + } + return rejectAndConcat([ + concat([ctx.Opens[0], " "]), + packageName, + ctx.Semicolon[0] + ]); + } + usesModuleDirective(ctx) { + const typeName = this.visit(ctx.typeName); + return rejectAndConcat([ + concat([ctx.Uses[0], " "]), + typeName, + ctx.Semicolon[0] + ]); + } + providesModuleDirective(ctx) { + const firstTypeName = this.visit(ctx.typeName[0]); + const otherTypeNames = this.mapVisit(ctx.typeName.slice(1)); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return group(rejectAndConcat([ + indent(rejectAndJoin(line, [ + rejectAndJoin(" ", [ctx.Provides[0], firstTypeName]), + group(indent(rejectAndJoin(line, [ + ctx.With[0], + rejectAndJoinSeps(commas, otherTypeNames) + ]))) + ])), + ctx.Semicolon[0] + ])); + } + requiresModifier(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } +} diff --git a/packages/prettier-plugin-java/dist/printers/prettier-builder.js b/packages/prettier-plugin-java/dist/printers/prettier-builder.js new file mode 100644 index 00000000..9a88ab76 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/prettier-builder.js @@ -0,0 +1,45 @@ +import { builders } from "prettier/doc"; +import * as formatComments from "./comments/format-comments.js"; +const processComments = formatComments.processComments; +/* + * ------------------------------------------------------------------ + * Wraps the Prettier builder functions to print tokens with comments + * ------------------------------------------------------------------ + */ +export function concat(docs) { + const concatenation = processComments(docs); + if (!Array.isArray(docs)) { + return ""; + } + return concatenation; +} +export function join(sep, docs) { + return builders.join(processComments(sep), processComments(docs)); +} +export function group(docs, opts) { + const group = builders.group(processComments(docs), opts); + return group.contents === undefined ? "" : group; +} +export function fill(docs) { + return builders.fill(processComments(docs)); +} +export function indent(doc) { + const processedDoc = processComments(doc); + if (processedDoc.length === 0) { + return ""; + } + return builders.indent(processedDoc); +} +export function dedent(doc) { + const processedDoc = processComments(doc); + if (processedDoc.length === 0) { + return ""; + } + return builders.dedent(processComments(doc)); +} +export function ifBreak(breakContents, flatContents) { + return builders.ifBreak(processComments(breakContents), processComments(flatContents)); +} +export function indentIfBreak(contents, opts) { + return builders.indentIfBreak(processComments(contents), opts); +} diff --git a/packages/prettier-plugin-java/dist/printers/printer-utils.js b/packages/prettier-plugin-java/dist/printers/printer-utils.js new file mode 100644 index 00000000..1ce8b057 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/printer-utils.js @@ -0,0 +1,586 @@ +import findIndex from "lodash/findIndex.js"; +import findLastIndex from "lodash/findLastIndex.js"; +import forEach from "lodash/forEach.js"; +import forEachRight from "lodash/forEachRight.js"; +import includes from "lodash/includes.js"; +import { builders } from "prettier/doc"; +import { isCstNode } from "../types/utils.js"; +import { isEmptyDoc } from "../utils/index.js"; +import { hasComments, hasLeadingComments, hasTrailingComments } from "./comments/comments-utils.js"; +import { getTokenLeadingComments, printTokenWithComments } from "./comments/format-comments.js"; +import { concat, group, ifBreak, join } from "./prettier-builder.js"; +const { indent, hardline, line } = builders; +const orderedModifiers = [ + "Public", + "Protected", + "Private", + "Abstract", + "Default", + "Static", + "Final", + "Transient", + "Volatile", + "Synchronized", + "Native", + "Sealed", + "NonSealed", + "Strictfp" +]; +export function buildFqn(tokens, dots) { + return rejectAndJoinSeps(dots ? dots : [], tokens); +} +export function rejectAndJoinSeps(sepTokens, elems, sep) { + if (!Array.isArray(sepTokens)) { + return rejectAndJoin(sepTokens, elems); + } + const actualElements = reject(elems); + const res = []; + for (let i = 0; i < sepTokens.length; i++) { + res.push(actualElements[i], sepTokens[i]); + if (sep) { + res.push(sep); + } + } + res.push(...actualElements.slice(sepTokens.length)); + return concat(res); +} +export function reject(elems) { + return elems.filter(item => { + if (typeof item === "string") { + return item !== ""; + } + // eslint-ignore next - We want the conversion to boolean! + // @ts-ignore + return item != false && item !== undefined; + }); +} +export function rejectSeparators(separators, elems) { + const realElements = reject(elems); + const realSeparators = []; + for (let i = 0; i < realElements.length - 1; i++) { + if (realElements[i] !== "") { + realSeparators.push(separators[i]); + } + } + return realSeparators; +} +export function rejectAndJoin(sep, elems) { + const actualElements = reject(elems); + return join(sep, actualElements); +} +export function rejectAndConcat(elems) { + const actualElements = reject(elems); + return concat(actualElements); +} +export function sortAnnotationIdentifier(annotations, identifiers) { + let tokens = [...identifiers]; + if (annotations && annotations.length > 0) { + tokens = [...tokens, ...annotations]; + } + return tokens.sort((a, b) => { + const startOffset1 = isCstNode(a) + ? a.children.At[0].startOffset + : a.startOffset; + const startOffset2 = isCstNode(b) + ? b.children.At[0].startOffset + : b.startOffset; + return startOffset1 - startOffset2; + }); +} +export function sortTokens(values) { + let tokens = []; + forEach(values, argument => { + if (argument) { + tokens = tokens.concat(argument); + } + }); + return tokens.sort((a, b) => { + return a.startOffset - b.startOffset; + }); +} +export function sortNodes(values) { + let nodes = []; + forEach(values, argument => { + if (argument) { + nodes = nodes.concat(argument); + } + }); + return nodes.sort((a, b) => { + const aOffset = a.location.startOffset; + const bOffset = b.location.startOffset; + return aOffset - bOffset; + }); +} +export function matchCategory(token, categoryName) { + const labels = (token.tokenType.CATEGORIES || []).map(category => { + return category.LABEL; + }); + return labels.indexOf(categoryName) !== -1; +} +export function sortClassTypeChildren(annotations, typeArguments, identifiers, dots) { + let tokens = [...identifiers]; + if (annotations && annotations.length > 0) { + tokens = [...tokens, ...annotations]; + } + if (typeArguments && typeArguments.length > 0) { + tokens = [...tokens, ...typeArguments]; + } + if (dots && dots.length > 0) { + tokens = [...tokens, ...dots]; + } + return tokens.sort((a, b) => { + const startOffsetA = isCstNode(a) + ? a.children.At + ? a.children.At[0].startOffset + : a.children.Less[0].startOffset + : a.startOffset; + const startOffsetB = isCstNode(b) + ? b.children.At + ? b.children.At[0].startOffset + : b.children.Less[0].startOffset + : b.startOffset; + return startOffsetA - startOffsetB; + }); +} +export function sortModifiers(modifiers) { + let firstAnnotations = []; + const otherModifiers = []; + let lastAnnotations = []; + let hasOtherModifier = false; + /** + * iterate in reverse order because we special-case + * type annotations which come after all other + * modifiers + */ + forEachRight(modifiers, modifier => { + const isAnnotation = modifier.children.annotation !== undefined; + const isTypeAnnotation = isAnnotation && + (modifier.name === "methodModifier" || + modifier.name === "interfaceMethodModifier" || + modifier.name === "fieldModifier"); + if (isAnnotation) { + if (isTypeAnnotation && !hasOtherModifier) { + lastAnnotations.unshift(modifier); + } + else { + firstAnnotations.unshift(modifier); + } + } + else { + otherModifiers.unshift(modifier); + hasOtherModifier = true; + } + }); + /** + * if there are only annotations, move everything from + * lastAnnotations to firstAnnotations + */ + if (!hasOtherModifier) { + firstAnnotations = firstAnnotations.concat(lastAnnotations); + lastAnnotations = []; + } + otherModifiers.sort((a, b) => { + const modifierIndexA = orderedModifiers.indexOf(Object.keys(a.children)[0]); + const modifierIndexB = orderedModifiers.indexOf(Object.keys(b.children)[0]); + return modifierIndexA - modifierIndexB; + }); + return [firstAnnotations, otherModifiers.concat(lastAnnotations)]; +} +export function findDeepElementInPartsArray(item, elt) { + if (Array.isArray(item)) { + if (includes(item, elt)) { + return true; + } + for (let i = 0; i < item.length; i++) { + if (findDeepElementInPartsArray(item[i], elt)) { + return true; + } + } + } + else { + for (const key in item) { + if (typeof item[key] === "object" && + findDeepElementInPartsArray(item[key], elt)) { + return true; + } + } + } + return false; +} +export function displaySemicolon(token, params) { + if (params !== undefined && params.allowEmptyStatement) { + return printTokenWithComments(token); + } + if (!hasComments(token)) { + return ""; + } + token.image = ""; + return printTokenWithComments(token); +} +export function isExplicitLambdaParameter(ctx) { + return (ctx && + ctx.lambdaParameterList && + ctx.lambdaParameterList[0] && + ctx.lambdaParameterList[0].children && + ctx.lambdaParameterList[0].children.explicitLambdaParameterList); +} +export function getBlankLinesSeparator(ctx, separator = hardline) { + if (ctx === undefined) { + return []; + } + const separators = []; + for (let i = 0; i < ctx.length - 1; i++) { + const node = ctx[i]; + const previousRuleEndLineWithComment = hasTrailingComments(node) + ? node.trailingComments[node.trailingComments.length - 1].endLine + : node.location.endLine; + const nextNode = ctx[i + 1]; + const nextRuleStartLineWithComment = hasLeadingComments(nextNode) + ? nextNode.leadingComments[0].startLine + : nextNode.location.startLine; + if (nextRuleStartLineWithComment - previousRuleEndLineWithComment > 1) { + separators.push([hardline, hardline]); + } + else { + separators.push(separator); + } + } + return separators; +} +const isTwoHardLine = (userBlankLinesSeparator) => { + if (!Array.isArray(userBlankLinesSeparator)) { + return false; + } + return (userBlankLinesSeparator.length === 2 && + userBlankLinesSeparator[0] === hardline && + userBlankLinesSeparator[1] === hardline); +}; +function getDeclarationsSeparator(declarations, needLineDeclaration, isSemicolon) { + const declarationsWithoutEmptyStatements = declarations.filter(declaration => !isSemicolon(declaration)); + const userBlankLinesSeparators = getBlankLinesSeparator(declarationsWithoutEmptyStatements); + const additionalBlankLines = declarationsWithoutEmptyStatements.map(needLineDeclaration); + const separators = []; + let indexNextNotEmptyDeclaration = 0; + for (let i = 0; i < declarations.length - 1; i++) { + // if the empty statement has comments + // we want to print them on their own line + if (isSemicolon(declarations[i])) { + if (hasComments(declarations[i])) { + separators.push(hardline); + } + } + else if (indexNextNotEmptyDeclaration < + declarationsWithoutEmptyStatements.length - 1) { + const isNextSeparatorTwoHardLine = isTwoHardLine(userBlankLinesSeparators[indexNextNotEmptyDeclaration]); + const additionalSep = !isNextSeparatorTwoHardLine && + (additionalBlankLines[indexNextNotEmptyDeclaration + 1] || + additionalBlankLines[indexNextNotEmptyDeclaration]) + ? hardline + : ""; + separators.push(concat([ + userBlankLinesSeparators[indexNextNotEmptyDeclaration], + additionalSep + ])); + indexNextNotEmptyDeclaration += 1; + } + } + return separators; +} +function needLineClassBodyDeclaration(declaration) { + if (declaration.children.classMemberDeclaration === undefined) { + return true; + } + const classMemberDeclaration = declaration.children.classMemberDeclaration[0]; + if (classMemberDeclaration.children.fieldDeclaration !== undefined) { + const fieldDeclaration = classMemberDeclaration.children.fieldDeclaration[0]; + if (fieldDeclaration.children.fieldModifier !== undefined && + hasAnnotation(fieldDeclaration.children.fieldModifier) && + hasNonTrailingAnnotation(fieldDeclaration.children.fieldModifier)) { + return true; + } + return false; + } + else if (classMemberDeclaration.children.Semicolon !== undefined) { + return false; + } + return true; +} +function needLineInterfaceMemberDeclaration(declaration) { + if (declaration.children.constantDeclaration !== undefined) { + const constantDeclaration = declaration.children.constantDeclaration[0]; + if (constantDeclaration.children.constantModifier !== undefined && + hasAnnotation(constantDeclaration.children.constantModifier) && + hasNonTrailingAnnotation(constantDeclaration.children.constantModifier)) { + return true; + } + return false; + } + else if (declaration.children.interfaceMethodDeclaration !== undefined) { + const interfaceMethodDeclaration = declaration.children.interfaceMethodDeclaration[0]; + if (interfaceMethodDeclaration.children.interfaceMethodModifier !== + undefined && + hasNonTrailingAnnotation(interfaceMethodDeclaration.children.interfaceMethodModifier)) { + return true; + } + return false; + } + return true; +} +function isClassBodyDeclarationASemicolon(classBodyDeclaration) { + if (classBodyDeclaration.children.classMemberDeclaration) { + if (classBodyDeclaration.children.classMemberDeclaration[0].children + .Semicolon !== undefined) { + return true; + } + } + return false; +} +function isInterfaceMemberASemicolon(interfaceMemberDeclaration) { + return interfaceMemberDeclaration.children.Semicolon !== undefined; +} +function hasAnnotation(modifiers) { + return modifiers.some(modifier => modifier.children.annotation !== undefined); +} +/** + * Return true if there is a modifier that does not come after all other modifiers + * It is useful to know if sortModifiers will add an annotation before other modifiers + * + * @param modifiers + * @returns {boolean} + */ +function hasNonTrailingAnnotation(modifiers) { + const firstAnnotationIndex = findIndex(modifiers, modifier => modifier.children.annotation !== undefined); + const lastNonAnnotationIndex = findLastIndex(modifiers, modifier => modifier.children.annotation === undefined); + return (firstAnnotationIndex < lastNonAnnotationIndex || + lastNonAnnotationIndex === -1); +} +export function getClassBodyDeclarationsSeparator(classBodyDeclarationContext) { + return getDeclarationsSeparator(classBodyDeclarationContext, needLineClassBodyDeclaration, isClassBodyDeclarationASemicolon); +} +export function getInterfaceBodyDeclarationsSeparator(interfaceMemberDeclarationContext) { + return getDeclarationsSeparator(interfaceMemberDeclarationContext, needLineInterfaceMemberDeclaration, isInterfaceMemberASemicolon); +} +function getAndRemoveLeadingComment(doc) { + const isTokenWithLeadingComment = typeof doc !== "string" && "leadingComments" in doc; + if (!isTokenWithLeadingComment) { + return []; + } + const leadingComments = getTokenLeadingComments(doc); + delete doc.leadingComments; + return leadingComments; +} +export function putIntoBraces(argument, separator, LBrace, RBrace) { + const rightBraceLeadingComments = getAndRemoveLeadingComment(RBrace); + const lastBreakLine = + // check if last element of the array is a line + rightBraceLeadingComments.length !== 0 && + rightBraceLeadingComments[rightBraceLeadingComments.length - 1] === hardline + ? rightBraceLeadingComments.pop() + : separator; + let contentInsideBraces; + if (isEmptyDoc(argument)) { + if (rightBraceLeadingComments.length === 0) { + return concat([LBrace, RBrace]); + } + contentInsideBraces = [separator, ...rightBraceLeadingComments]; + } + else if (rightBraceLeadingComments.length !== 0) { + contentInsideBraces = [ + separator, + argument, + separator, + ...rightBraceLeadingComments + ]; + } + else { + contentInsideBraces = [separator, argument]; + } + return group(rejectAndConcat([ + LBrace, + indent(concat(contentInsideBraces)), + lastBreakLine, + RBrace + ])); +} +export function binary(nodes, tokens, isRoot = false) { + let levelOperator; + let levelPrecedence; + let level = []; + while (tokens.length) { + const nextOperator = getOperator(tokens); + const nextPrecedence = getOperatorPrecedence(nextOperator); + if (levelPrecedence === undefined || nextPrecedence === levelPrecedence) { + const tokenLength = ["<<", ">>", ">>>"].includes(nextOperator) + ? nextOperator.length + : 1; + const operator = concat(tokens.splice(0, tokenLength)); + if (levelOperator !== undefined && + needsParentheses(levelOperator, nextOperator)) { + level.push(nodes.shift()); + level = [ + concat(["(", group(indent(join(line, level))), ") ", operator]) + ]; + } + else { + level.push(join(" ", [nodes.shift(), operator])); + } + levelOperator = nextOperator; + levelPrecedence = nextPrecedence; + } + else if (nextPrecedence < levelPrecedence) { + level.push(nodes.shift()); + if (isRoot) { + const content = group(indent(join(line, level))); + nodes.unshift(levelOperator !== undefined && + needsParentheses(levelOperator, nextOperator) + ? concat(["(", content, ")"]) + : content); + level = []; + levelOperator = undefined; + levelPrecedence = undefined; + } + else { + return group(join(line, level)); + } + } + else { + const content = indent(binary(nodes, tokens)); + nodes.unshift(levelOperator !== undefined && + needsParentheses(nextOperator, levelOperator) + ? concat(["(", content, ")"]) + : content); + } + } + level.push(nodes.shift()); + const content = group(join(line, level)); + return levelOperator === "=" ? indent(content) : content; +} +function getOperator(tokens) { + if (!tokens.length) { + return ""; + } + const [{ image, startOffset }] = tokens; + if (!["<", ">"].includes(image)) { + return image; + } + let repeatedTokenCount = 1; + for (let i = 1; i < Math.min(3, tokens.length); i++) { + const token = tokens[i]; + if (token.image !== image || token.startOffset !== startOffset + i) { + break; + } + repeatedTokenCount++; + } + if (repeatedTokenCount === 1) { + return image; + } + if (image === "<") { + return "<<"; + } + else if (repeatedTokenCount == 2) { + return ">>"; + } + else { + return ">>>"; + } +} +const PRECEDENCES_BY_OPERATOR = new Map([ + ["||"], + ["&&"], + ["|"], + ["^"], + ["&"], + ["==", "!="], + ["<", ">", "<=", ">=", "instanceof"], + ["<<", ">>", ">>>"], + ["+", "-"], + ["*", "/", "%"] +].flatMap((operators, index) => operators.map(operator => [operator, index]))); +function getOperatorPrecedence(operator) { + var _a; + return (_a = PRECEDENCES_BY_OPERATOR.get(operator)) !== null && _a !== void 0 ? _a : -1; +} +function needsParentheses(operator, parentOperator) { + return ((operator === "&&" && parentOperator === "||") || + (["|", "^", "&", "<<", ">>", ">>>"].includes(parentOperator) && + getOperatorPrecedence(operator) > + getOperatorPrecedence(parentOperator)) || + [operator, parentOperator].every(o => ["==", "!="].includes(o)) || + [operator, parentOperator].every(o => ["<<", ">>", ">>>"].includes(o)) || + (operator === "*" && parentOperator === "/") || + (operator === "/" && parentOperator === "*") || + (operator === "%" && ["+", "-", "*", "/"].includes(parentOperator)) || + (["*", "/"].includes(operator) && parentOperator === "%")); +} +export function isStatementEmptyStatement(statement) { + return (statement === ";" || (Array.isArray(statement) && statement[0] === ";")); +} +export function sortImports(imports) { + const staticImports = []; + const nonStaticImports = []; + if (imports !== undefined) { + for (let i = 0; i < imports.length; i++) { + if (imports[i].children.Static !== undefined) { + staticImports.push(imports[i]); + } + else if (imports[i].children.emptyStatement === undefined) { + nonStaticImports.push(imports[i]); + } + } + // TODO: Could be optimized as we could expect that the array is already almost sorted + const comparator = (first, second) => compareFqn(first.children.packageOrTypeName[0], second.children.packageOrTypeName[0]); + staticImports.sort(comparator); + nonStaticImports.sort(comparator); + } + return { + staticImports, + nonStaticImports + }; +} +function compareFqn(packageOrTypeNameFirst, packageOrTypeNameSecond) { + const identifiersFirst = packageOrTypeNameFirst.children.Identifier; + const identifiersSecond = packageOrTypeNameSecond.children.Identifier; + const minParts = Math.min(identifiersFirst.length, identifiersSecond.length); + for (let i = 0; i < minParts; i++) { + if (identifiersFirst[i].image < identifiersSecond[i].image) { + return -1; + } + else if (identifiersFirst[i].image > identifiersSecond[i].image) { + return 1; + } + } + if (identifiersFirst.length < identifiersSecond.length) { + return -1; + } + else if (identifiersFirst.length > identifiersSecond.length) { + return 1; + } + return 0; +} +export function isUniqueMethodInvocation(primarySuffixes) { + if (primarySuffixes === undefined) { + return 0; + } + let count = 0; + primarySuffixes.forEach(primarySuffix => { + if (primarySuffix.children.methodInvocationSuffix !== undefined) { + count++; + if (count > 1) { + return 2; + } + } + }); + return count; +} +export function printArrayList({ list, extraComma, LCurly, RCurly, trailingComma }) { + let optionalComma; + if (trailingComma !== "none" && list !== "") { + optionalComma = extraComma + ? ifBreak(extraComma[0], Object.assign(Object.assign({}, extraComma[0]), { image: "" })) + : ifBreak(",", ""); + } + else { + optionalComma = extraComma ? Object.assign(Object.assign({}, extraComma[0]), { image: "" }) : ""; + } + return putIntoBraces(rejectAndConcat([list, optionalComma]), line, LCurly, RCurly); +} diff --git a/packages/prettier-plugin-java/dist/printers/types-values-and-variables.js b/packages/prettier-plugin-java/dist/printers/types-values-and-variables.js new file mode 100644 index 00000000..055ec065 --- /dev/null +++ b/packages/prettier-plugin-java/dist/printers/types-values-and-variables.js @@ -0,0 +1,153 @@ +import forEach from "lodash/forEach.js"; +import { builders } from "prettier/doc"; +import { concat, group, indent, join } from "./prettier-builder.js"; +import { printTokenWithComments } from "./comments/format-comments.js"; +import { putIntoBraces, rejectAndConcat, rejectAndJoin, rejectAndJoinSeps, sortClassTypeChildren } from "./printer-utils.js"; +import { BaseCstPrettierPrinter } from "../base-cst-printer.js"; +import { isAnnotationCstNode, isCstNode, isTypeArgumentsCstNode } from "../types/utils.js"; +const { line, softline } = builders; +export class TypesValuesAndVariablesPrettierVisitor extends BaseCstPrettierPrinter { + primitiveType(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const type = ctx.numericType + ? this.visit(ctx.numericType) + : this.getSingle(ctx); + return rejectAndJoin(" ", [join(" ", annotations), type]); + } + numericType(ctx) { + return this.visitSingle(ctx); + } + integralType(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } + floatingPointType(ctx) { + return printTokenWithComments(this.getSingle(ctx)); + } + referenceType(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const type = ctx.primitiveType + ? this.visit(ctx.primitiveType) + : this.visit(ctx.classOrInterfaceType); + const dims = this.visit(ctx.dims); + return rejectAndJoin(" ", [join(" ", annotations), concat([type, dims])]); + } + classOrInterfaceType(ctx) { + return this.visitSingle(ctx); + } + classType(ctx) { + const tokens = sortClassTypeChildren(ctx.annotation, ctx.typeArguments, ctx.Identifier); + const segments = []; + let currentSegment = []; + forEach(tokens, (token, i) => { + if (isTypeArgumentsCstNode(token)) { + currentSegment.push(this.visit([token])); + segments.push(rejectAndConcat(currentSegment)); + currentSegment = []; + } + else if (isAnnotationCstNode(token)) { + currentSegment.push(this.visit([token]), " "); + } + else { + currentSegment.push(token); + if ((i + 1 < tokens.length && !isTypeArgumentsCstNode(tokens[i + 1])) || + i + 1 === tokens.length) { + segments.push(rejectAndConcat(currentSegment)); + currentSegment = []; + } + } + }); + return rejectAndJoinSeps(ctx.Dot, segments); + } + interfaceType(ctx) { + return this.visitSingle(ctx); + } + typeVariable(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const identifier = this.getSingle(ctx); + return rejectAndJoin(" ", [join(" ", annotations), identifier]); + } + dims(ctx) { + let tokens = [...ctx.LSquare]; + if (ctx.annotation) { + tokens = [...tokens, ...ctx.annotation]; + } + tokens = tokens.sort((a, b) => { + const startOffset1 = isCstNode(a) + ? a.children.At[0].startOffset + : a.startOffset; + const startOffset2 = isCstNode(b) + ? b.children.At[0].startOffset + : b.startOffset; + return startOffset1 - startOffset2; + }); + const segments = []; + let currentSegment = []; + forEach(tokens, token => { + if (isCstNode(token)) { + currentSegment.push(this.visit([token])); + } + else { + segments.push(rejectAndConcat([ + rejectAndJoin(" ", currentSegment), + concat([ctx.LSquare[0], ctx.RSquare[0]]) + ])); + currentSegment = []; + } + }); + return rejectAndConcat(segments); + } + typeParameter(ctx) { + const typeParameterModifiers = this.mapVisit(ctx.typeParameterModifier); + const typeIdentifier = this.visit(ctx.typeIdentifier); + const typeBound = this.visit(ctx.typeBound); + return rejectAndJoin(" ", [ + join(" ", typeParameterModifiers), + typeIdentifier, + typeBound + ]); + } + typeParameterModifier(ctx) { + return this.visitSingle(ctx); + } + typeBound(ctx) { + const classOrInterfaceType = this.visit(ctx.classOrInterfaceType); + const additionalBound = this.mapVisit(ctx.additionalBound); + return concat([ + rejectAndJoin(" ", [ctx.Extends[0], classOrInterfaceType]), + indent(group(concat([ + additionalBound.length ? line : "", + rejectAndJoin(line, additionalBound) + ]))) + ]); + } + additionalBound(ctx) { + const interfaceType = this.visit(ctx.interfaceType); + return join(" ", [ctx.And[0], interfaceType]); + } + typeArguments(ctx) { + const typeArgumentList = this.visit(ctx.typeArgumentList); + return putIntoBraces(typeArgumentList, softline, ctx.Less[0], ctx.Greater[0]); + } + typeArgumentList(ctx) { + const typeArguments = this.mapVisit(ctx.typeArgument); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + return rejectAndJoinSeps(commas, typeArguments); + } + typeArgument(ctx) { + return this.visitSingle(ctx); + } + wildcard(ctx) { + const annotations = this.mapVisit(ctx.annotation); + const wildcardBounds = this.visit(ctx.wildcardBounds); + return rejectAndJoin(" ", [ + join(" ", annotations), + ctx.QuestionMark[0], + wildcardBounds + ]); + } + wildcardBounds(ctx) { + const keyWord = ctx.Extends ? ctx.Extends[0] : ctx.Super[0]; + const referenceType = this.visit(ctx.referenceType); + return join(" ", [keyWord, referenceType]); + } +} diff --git a/packages/prettier-plugin-java/dist/types/utils.js b/packages/prettier-plugin-java/dist/types/utils.js new file mode 100644 index 00000000..815f9b10 --- /dev/null +++ b/packages/prettier-plugin-java/dist/types/utils.js @@ -0,0 +1,20 @@ +export function isCstNode(tokenOrNode) { + return !isIToken(tokenOrNode); +} +export function isIToken(tokenOrNode) { + return (tokenOrNode.tokenType !== undefined && + tokenOrNode.image !== undefined); +} +export function isCstElementOrUndefinedIToken(tokenOrNode) { + return tokenOrNode !== undefined && isIToken(tokenOrNode); +} +export const isTypeArgumentsCstNode = (cstElement) => { + return cstElement.name === "typeArguments"; +}; +export const isAnnotationCstNode = (cstElement) => { + return cstElement.name === "annotation"; +}; +export const isOrdinaryCompilationUnitCtx = (ctx) => { + return (ctx.ordinaryCompilationUnit !== + undefined); +}; diff --git a/packages/prettier-plugin-java/dist/utils/expressions-utils.js b/packages/prettier-plugin-java/dist/utils/expressions-utils.js new file mode 100644 index 00000000..b827a8cf --- /dev/null +++ b/packages/prettier-plugin-java/dist/utils/expressions-utils.js @@ -0,0 +1,24 @@ +export function isArgumentListSingleLambda(argumentList) { + if (argumentList === undefined) { + return false; + } + const args = argumentList[0].children.expression; + if (args.length !== 1) { + return false; + } + const argument = args[0]; + return argument.children.lambdaExpression !== undefined; +} +export const isSingleArgumentLambdaExpressionWithBlock = (argumentList) => { + if (argumentList === undefined) { + return false; + } + const args = argumentList[0].children.expression; + if (args.length !== 1) { + return false; + } + const argument = args[0]; + return (argument.children.lambdaExpression !== undefined && + argument.children.lambdaExpression[0].children.lambdaBody[0].children + .block !== undefined); +}; diff --git a/packages/prettier-plugin-java/dist/utils/index.js b/packages/prettier-plugin-java/dist/utils/index.js new file mode 100644 index 00000000..ff2a3ebc --- /dev/null +++ b/packages/prettier-plugin-java/dist/utils/index.js @@ -0,0 +1,3 @@ +export { default as printArgumentListWithBraces } from "./printArgumentListWithBraces.js"; +export { default as printSingleLambdaInvocation } from "./printSingleLambdaInvocation.js"; +export { default as isEmptyDoc } from "./isEmptyDoc.js"; diff --git a/packages/prettier-plugin-java/dist/utils/isEmptyDoc.js b/packages/prettier-plugin-java/dist/utils/isEmptyDoc.js new file mode 100644 index 00000000..7277adbf --- /dev/null +++ b/packages/prettier-plugin-java/dist/utils/isEmptyDoc.js @@ -0,0 +1,4 @@ +const isEmptyDoc = (argument) => { + return argument === "" || (Array.isArray(argument) && argument.length) === 0; +}; +export default isEmptyDoc; diff --git a/packages/prettier-plugin-java/dist/utils/printArgumentListWithBraces.js b/packages/prettier-plugin-java/dist/utils/printArgumentListWithBraces.js new file mode 100644 index 00000000..078d3a3e --- /dev/null +++ b/packages/prettier-plugin-java/dist/utils/printArgumentListWithBraces.js @@ -0,0 +1,15 @@ +import { builders } from "prettier/doc"; +import { isArgumentListSingleLambda } from "./expressions-utils.js"; +import { putIntoBraces } from "../printers/printer-utils.js"; +import printSingleLambdaInvocation from "./printSingleLambdaInvocation.js"; +const { softline } = builders; +export default function printArgumentListWithBraces(argumentListCtx, rBrace, lBrace) { + const isSingleLambda = isArgumentListSingleLambda(argumentListCtx); + if (isSingleLambda) { + return printSingleLambdaInvocation.call(this, argumentListCtx, rBrace, lBrace); + } + const argumentList = this.visit(argumentListCtx, { + isInsideMethodInvocationSuffix: true + }); + return putIntoBraces(argumentList, softline, lBrace, rBrace); +} diff --git a/packages/prettier-plugin-java/dist/utils/printSingleLambdaInvocation.js b/packages/prettier-plugin-java/dist/utils/printSingleLambdaInvocation.js new file mode 100644 index 00000000..e7aaf811 --- /dev/null +++ b/packages/prettier-plugin-java/dist/utils/printSingleLambdaInvocation.js @@ -0,0 +1,17 @@ +import { builders } from "prettier/doc"; +import { isSingleArgumentLambdaExpressionWithBlock } from "./expressions-utils.js"; +import { printTokenWithComments } from "../printers/comments/format-comments.js"; +import { concat, dedent, indent } from "../printers/prettier-builder.js"; +import { putIntoBraces } from "../printers/printer-utils.js"; +const { softline, ifBreak } = builders; +export default function printSingleLambdaInvocation(argumentListCtx, rBrace, lBrace) { + const lambdaParametersGroupId = Symbol("lambdaParameters"); + const argumentList = this.visit(argumentListCtx, { + lambdaParametersGroupId, + isInsideMethodInvocationSuffix: true + }); + const formattedRBrace = isSingleArgumentLambdaExpressionWithBlock(argumentListCtx) + ? ifBreak(indent(concat([softline, rBrace])), printTokenWithComments(rBrace), { groupId: lambdaParametersGroupId }) + : indent(concat([softline, rBrace])); + return dedent(putIntoBraces(argumentList, "", lBrace, formattedRBrace)); +} diff --git a/packages/prettier-plugin-java/src/printers/classes.ts b/packages/prettier-plugin-java/src/printers/classes.ts index 8e19acb8..c03c85d8 100644 --- a/packages/prettier-plugin-java/src/printers/classes.ts +++ b/packages/prettier-plugin-java/src/printers/classes.ts @@ -15,6 +15,7 @@ import { import { concat, group, + ifBreak, indent, join, indentIfBreak @@ -515,18 +516,23 @@ export class ClassesPrettierVisitor extends BaseCstPrettierPrinter { methodDeclaration(ctx: MethodDeclarationCtx) { const modifiers = sortModifiers(ctx.methodModifier); + const throwsGroupId = Symbol("throws"); const firstAnnotations = this.mapVisit(modifiers[0]); const otherModifiers = this.mapVisit(modifiers[1]); - const header = this.visit(ctx.methodHeader); + const header = this.visit(ctx.methodHeader, { throwsGroupId }); const body = this.visit(ctx.methodBody); - const headerBodySeparator = isStatementEmptyStatement(body) ? "" : " "; + const headerBodySeparator = isStatementEmptyStatement(body) + ? "" + : ctx.methodHeader[0].children.throws + ? ifBreak(hardline, " ", { groupId: throwsGroupId }) + : " "; return rejectAndJoin(hardline, [ - rejectAndJoin(hardline, firstAnnotations), + ...firstAnnotations, rejectAndJoin(" ", [ - rejectAndJoin(" ", otherModifiers), + ...otherModifiers, rejectAndJoin(headerBodySeparator, [header, body]) ]) ]); @@ -540,12 +546,12 @@ export class ClassesPrettierVisitor extends BaseCstPrettierPrinter { return printTokenWithComments(this.getSingle(ctx) as IToken); } - methodHeader(ctx: MethodHeaderCtx) { + methodHeader(ctx: MethodHeaderCtx, opts?: { throwsGroupId?: symbol }) { const typeParameters = this.visit(ctx.typeParameters); const annotations = this.mapVisit(ctx.annotation); const result = this.visit(ctx.result); const declarator = this.visit(ctx.methodDeclarator); - const throws = this.visit(ctx.throws); + const throws = this.visit(ctx.throws, opts); return group( concat([ @@ -652,15 +658,16 @@ export class ClassesPrettierVisitor extends BaseCstPrettierPrinter { return printTokenWithComments(this.getSingle(ctx) as IToken); } - throws(ctx: ThrowsCtx) { + throws(ctx: ThrowsCtx, opts?: { throwsGroupId?: symbol }) { const exceptionTypeList = this.visit(ctx.exceptionTypeList); - const throwsDeclaration = join(" ", [ctx.Throws[0], exceptionTypeList]); - return group(indent(rejectAndConcat([softline, throwsDeclaration]))); + return group(indent(join(line, [ctx.Throws[0], exceptionTypeList])), { + id: opts?.throwsGroupId + }); } exceptionTypeList(ctx: ExceptionTypeListCtx) { const exceptionTypes = this.mapVisit(ctx.exceptionType); - const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, " "])) : []; + const commas = ctx.Comma?.map(comma => concat([comma, line])); return rejectAndJoinSeps(commas, exceptionTypes); } @@ -687,25 +694,26 @@ export class ClassesPrettierVisitor extends BaseCstPrettierPrinter { } constructorDeclaration(ctx: ConstructorDeclarationCtx) { + const throwsGroupId = Symbol("throws"); const modifiers = sortModifiers(ctx.constructorModifier); const firstAnnotations = this.mapVisit(modifiers[0]); const otherModifiers = this.mapVisit(modifiers[1]); - const constructorDeclarator = this.visit(ctx.constructorDeclarator); - const throws = this.visit(ctx.throws); + const throws = this.visit(ctx.throws, { throwsGroupId }); const constructorBody = this.visit(ctx.constructorBody); - return rejectAndJoin(" ", [ + return concat([ group( rejectAndJoin(hardline, [ rejectAndJoin(hardline, firstAnnotations), rejectAndJoin(" ", [ - join(" ", otherModifiers), + rejectAndJoin(" ", otherModifiers), constructorDeclarator, throws ]) ]) ), + ctx.throws ? ifBreak(hardline, " ", { groupId: throwsGroupId }) : " ", constructorBody ]); } diff --git a/packages/prettier-plugin-java/src/printers/prettier-builder.ts b/packages/prettier-plugin-java/src/printers/prettier-builder.ts index 613c2dee..7bde7bbc 100644 --- a/packages/prettier-plugin-java/src/printers/prettier-builder.ts +++ b/packages/prettier-plugin-java/src/printers/prettier-builder.ts @@ -53,11 +53,13 @@ export function dedent(doc: Doc | IToken) { export function ifBreak( breakContents: Doc | IToken, - flatContents: Doc | IToken + flatContents: Doc | IToken, + options?: { groupId?: symbol | undefined } ) { return builders.ifBreak( processComments(breakContents), - processComments(flatContents) + processComments(flatContents), + options ); } diff --git a/packages/prettier-plugin-java/test/unit-test/throws/_output.java b/packages/prettier-plugin-java/test/unit-test/throws/_output.java index 2fa9b037..904f1340 100644 --- a/packages/prettier-plugin-java/test/unit-test/throws/_output.java +++ b/packages/prettier-plugin-java/test/unit-test/throws/_output.java @@ -8,23 +8,33 @@ void throwException2(String string) throws RuntimeException { throw new RuntimeException(); } - void throwException3(String string1, String string2, String string3) - throws RuntimeException { + void throwException3(String string1, String string2, String string3) throws + RuntimeException + { throw new RuntimeException(); } - void throwException4() - throws RuntimeException, RuntimeException, RuntimeException { + void throwException4() throws + RuntimeException, + RuntimeException, + RuntimeException + { throw new RuntimeException(); } - void throwException5(String string) - throws RuntimeException, RuntimeException, RuntimeException { + void throwException5(String string) throws + RuntimeException, + RuntimeException, + RuntimeException + { throw new RuntimeException(); } - void throwException6(String string1, String string2, String string3) - throws RuntimeException, RuntimeException, RuntimeException { + void throwException6(String string1, String string2, String string3) throws + RuntimeException, + RuntimeException, + RuntimeException + { throw new RuntimeException(); } @@ -51,19 +61,33 @@ void throwException9( String string2, String string3, String string4 - ) - throws RuntimeException, RuntimeException, RuntimeException, RuntimeException { + ) throws + RuntimeException, + RuntimeException, + RuntimeException, + RuntimeException + { throw new RuntimeException(); } - void aVeryLongNameForAMethodWichShouldBreakTheExpression() - throws aVeryLongException {} - - void aVeryLongNameForAMethodWichShouldBreakTheExpression() - throws aVeryLongException, aVeryLongException {} - - void aVeryLongNameForAMethodWichShouldBreakTheExpression() - throws Exception, Exception, Exception, Exception, Exception, Exception, Exception {} + void aVeryLongNameForAMethodWichShouldBreakTheExpression() throws + aVeryLongException + {} + + void aVeryLongNameForAMethodWichShouldBreakTheExpression() throws + aVeryLongException, + aVeryLongException + {} + + void aVeryLongNameForAMethodWichShouldBreakTheExpression() throws + Exception, + Exception, + Exception, + Exception, + Exception, + Exception, + Exception + {} abstract void absThrowException1() throws RuntimeException; @@ -75,11 +99,15 @@ abstract void absThrowException3( String string3 ) throws RuntimeException; - abstract void absThrowException4() - throws RuntimeException, RuntimeException, RuntimeException; + abstract void absThrowException4() throws + RuntimeException, + RuntimeException, + RuntimeException; - abstract void absThrowException5(String string) - throws RuntimeException, RuntimeException, RuntimeException; + abstract void absThrowException5(String string) throws + RuntimeException, + RuntimeException, + RuntimeException; abstract void absThrowException6( String string1, @@ -99,15 +127,19 @@ abstract void absThrowException8( String string2, String string3, String string4 - ) - throws RuntimeException, RuntimeException, RuntimeException, RuntimeException; + ) throws + RuntimeException, + RuntimeException, + RuntimeException, + RuntimeException; public Throws(String string1) throws RuntimeException { System.out.println("Constructor with throws that should not wrap"); } - public Throws(String string1, String string2, String string3) - throws RuntimeException { + public Throws(String string1, String string2, String string3) throws + RuntimeException + { System.out.println("Constructor with throws that should wrap"); } @@ -127,8 +159,12 @@ public Throws( String string3, String string4, String string5 - ) - throws RuntimeException, RuntimeException, RuntimeException, RuntimeException { + ) throws + RuntimeException, + RuntimeException, + RuntimeException, + RuntimeException + { System.out.println("Constructor with throws that should wrap"); } }