diff --git a/src/printers/classes.ts b/src/printers/classes.ts index e908bd18..1ea328e2 100644 --- a/src/printers/classes.ts +++ b/src/printers/classes.ts @@ -9,16 +9,15 @@ import { printBlock, printBlockStatements, printBodyDeclarations, - printDanglingComments, + printDanglingCommentsInList, printModifiers, printTypeParameters, printValue, printVariableDeclaration, - type NamedNodePath, type NamedNodePrinters } from "./helpers.ts"; -const { group, hardline, indent, join, line, softline } = builders; +const { group, hardline, indent, join, line } = builders; export default { class_declaration(path, print) { @@ -471,19 +470,3 @@ const indexByModifier = [ "non-sealed", "strictfp" ].reduce((map, name, index) => map.set(name, index), new Map()); - -function printDanglingCommentsInList(path: NamedNodePath) { - const { node } = path; - - return node.comments?.some(comment => !(comment.leading || comment.trailing)) - ? [ - indent([softline, printDanglingComments(path)]), - node.comments.some( - ({ type, leading, trailing }) => - !(leading || trailing) && type === SyntaxType.LineComment - ) - ? hardline - : softline - ] - : ""; -} diff --git a/src/printers/expressions.ts b/src/printers/expressions.ts index 24b1bf7d..37a7c0ee 100644 --- a/src/printers/expressions.ts +++ b/src/printers/expressions.ts @@ -12,9 +12,11 @@ import { hasDanglingComments, hasType, indentInParentheses, + isConciselyPrintedArray, isReturnOrThrowStatement, printAssignment, printDanglingComments, + printDanglingCommentsInList, shouldFlatten, type JavaParserOptions, type NamedNodePath, @@ -521,7 +523,7 @@ export default { ({ type, leading, trailing }) => !leading && !trailing && type === SyntaxType.LineComment ); - return group(indentInParentheses(printDanglingComments(path)), { + return group(["(", printDanglingCommentsInList(path), ")"], { shouldBreak }); } @@ -597,7 +599,7 @@ export default { ]); } - if (shouldExpandLastArg(args)) { + if (shouldExpandLastArg(args, options)) { const headArgs = printedArguments.slice(0, -1); if (headArgs.some(willBreak)) { return allArgsBrokenOut(); @@ -1504,17 +1506,25 @@ function couldExpandArg(arg: NamedNode, lambdaChainRecursion = false) { return false; } -function shouldExpandLastArg(args: NamedNode[]) { +function shouldExpandLastArg(args: NamedNode[], options: JavaParserOptions) { const lastArg = args.at(-1)!; const penultimateArg = args.at(-2); return ( - !lastArg.comments?.some(({ leading }) => leading) && - !lastArg.comments?.some(({ trailing }) => trailing) && + !lastArg.comments?.some(({ leading, trailing }) => leading || trailing) && couldExpandArg(lastArg) && // If the last two arguments are of the same type, // disable last element expansion. (!penultimateArg || penultimateArg.type !== lastArg.type) && - (args.length !== 2 || penultimateArg!.type !== SyntaxType.LambdaExpression) + // useMemo(() -> func(), [foo, bar, baz]) + (args.length !== 2 || + penultimateArg!.type !== SyntaxType.LambdaExpression || + lastArg.type !== SyntaxType.ArrayCreationExpression) && + !( + args.length > 1 && + lastArg.type === SyntaxType.ArrayCreationExpression && + lastArg.valueNode && + isConciselyPrintedArray(lastArg.valueNode, options) + ) ); } diff --git a/src/printers/helpers.ts b/src/printers/helpers.ts index 973b83e1..938a4952 100644 --- a/src/printers/helpers.ts +++ b/src/printers/helpers.ts @@ -1,24 +1,31 @@ -import type { - AstPath, - Doc, - Options, - ParserOptions, - Plugin, - SupportLanguage +import { + util, + type AstPath, + type Doc, + type Options, + type ParserOptions, + type Plugin, + type SupportLanguage } from "prettier"; import { builders, utils } from "prettier/doc"; import { SyntaxType, type ArrayInitializerNode, type CommentNode, + type ElementValueArrayInitializerNode, type ExpressionNode, type NamedNode, type NamedType, type SyntaxNode, - type TypeString + type TypeString, + type UnaryExpressionNode } from "../node-types.ts"; +import parser from "../parser.ts"; +const { hasNewline, isNextLineEmpty, skipInlineComment, skipTrailingComment } = + util; const { + fill, group, hardline, ifBreak, @@ -103,7 +110,7 @@ export function lineEndWithComments(node: SyntaxNode) { } export function printDanglingComments( - path: NamedNodePath, + path: AstPath, danglingCommentsPrintOptions: { indent?: boolean } = {} ): Doc { const { indent: shouldIndent = false } = danglingCommentsPrintOptions; @@ -129,6 +136,22 @@ export function printDanglingComments( return shouldIndent ? indent([hardline, doc]) : doc; } +export function printDanglingCommentsInList(path: AstPath) { + const { node } = path; + + return node.comments?.some(({ leading, trailing }) => !leading && !trailing) + ? [ + indent([softline, printDanglingComments(path)]), + node.comments?.some( + ({ type, leading, trailing }) => + !leading && !trailing && type === SyntaxType.LineComment + ) + ? hardline + : softline + ] + : ""; +} + export function printComment(comment: CommentNode) { comment.printed = true; const lines = comment.value.split("\n").map(line => line.trim()); @@ -167,18 +190,179 @@ export function printArrayInitializer( print: PrintFunction, options: JavaParserOptions ) { - if (!path.node.namedChildren.length) { - const danglingComments = printDanglingComments(path, { indent: true }); - return danglingComments ? ["{", danglingComments, hardline, "}"] : "{}"; + const { node } = path; + const parts: Doc[] = []; + + const elements = node.namedChildren; + if (elements.length === 0) { + parts.push(group(["{", printDanglingCommentsInList(path), "}"])); + } else { + const groupId = Symbol("array"); + + const shouldBreak = + (elements.length > 1 && + elements.every((element, i, elements) => { + if (element.type !== SyntaxType.ArrayInitializer) { + return false; + } + + const elementType = element.type; + const nextElement = elements[i + 1]; + if (nextElement && elementType !== nextElement.type) { + return false; + } + + return element.namedChildren.length > 1; + })) || + node.comments?.some( + ({ type, leading, trailing }) => + !leading && !trailing && type === SyntaxType.LineComment + ); + + const shouldUseConciseFormatting = isConciselyPrintedArray(node, options); + + const trailingComma = + options.trailingComma === "none" + ? "" + : shouldUseConciseFormatting + ? ifBreak(",", "", { groupId }) + : ifBreak(","); + + parts.push( + group( + [ + "{", + indent([ + line, + shouldUseConciseFormatting + ? printArrayElementsConcisely(path, options, print, trailingComma) + : [printArrayElements(path, options, print), trailingComma], + printDanglingComments(path) + ]), + line, + "}" + ], + { shouldBreak, id: groupId } + ) + ); + } + + return parts; +} + +export function isConciselyPrintedArray( + node: ArrayInitializerNode | ElementValueArrayInitializerNode, + options: JavaParserOptions +) { + return ( + node.namedChildren.length > 0 && + node.namedChildren.every( + element => + element && + (isNumericLiteral(element) || + (isSignedNumericLiteral(element) && !element.operandNode.comments)) && + !element.comments?.some( + comment => + comment.trailing && + comment.type === SyntaxType.LineComment && + !hasNewline(options.originalText, parser.locStart(comment), { + backwards: true + }) + ) + ) + ); +} + +const isNumericLiteral = createTypeCheckFunction([ + SyntaxType.BinaryIntegerLiteral, + SyntaxType.DecimalFloatingPointLiteral, + SyntaxType.DecimalIntegerLiteral, + SyntaxType.HexFloatingPointLiteral, + SyntaxType.HexIntegerLiteral, + SyntaxType.OctalIntegerLiteral +]); + +function isSignedNumericLiteral(node: SyntaxNode): node is UnaryExpressionNode { + return ( + node.type === SyntaxType.UnaryExpression && + (node.operatorNode.type === "+" || node.operatorNode.type === "-") && + isNumericLiteral(node.operandNode) + ); +} + +function isLineAfterElementEmpty( + { node }: AstPath, + { originalText: text }: JavaParserOptions +) { + let currentIdx = parser.locEnd(node); + if (currentIdx === parser.locStart(node)) { + return false; } - const list = join([",", line], path.map(print, "namedChildren")); + const { length } = text; + while (currentIdx < length) { + if (text[currentIdx] === ",") { + break; + } - if (list.length && options.trailingComma !== "none") { - list.push(ifBreak(",")); + currentIdx = skipInlineComment( + text, + skipTrailingComment(text, currentIdx + 1) + ) as number; } - return group(["{", indent([line, ...list]), line, "}"]); + return isNextLineEmpty(text, currentIdx); +} + +function printArrayElements( + path: AstPath, + options: JavaParserOptions, + print: PrintFunction +) { + const parts: Doc[] = []; + + path.each(element => { + parts.push(group(print(element))); + + if (!element.isLast) { + parts.push([ + ",", + line, + isLineAfterElementEmpty(path, options) ? softline : "" + ]); + } + }, "namedChildren"); + + return parts; +} + +function printArrayElementsConcisely( + path: AstPath, + options: JavaParserOptions, + print: PrintFunction, + trailingComma: Doc +) { + const parts: Doc[] = []; + + path.each(element => { + const { next } = element; + parts.push([print(element), !next ? trailingComma : ","]); + + if (next) { + parts.push( + isLineAfterElementEmpty(path, options) + ? [hardline, hardline] + : next.comments?.some( + ({ type, leading }) => + leading && type === SyntaxType.LineComment + ) + ? hardline + : line + ); + } + }, "namedChildren"); + + return fill(parts); } export function printBlock(path: NamedNodePath, contents: Doc[]) { diff --git a/test/unit-test/arrays/_input.java b/test/unit-test/arrays/_input.java index 8c29c8a0..915ee2de 100644 --- a/test/unit-test/arrays/_input.java +++ b/test/unit-test/arrays/_input.java @@ -12,4 +12,54 @@ class Array { Weather currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; int @Nullable [] array; + + int[] empty = {}; + + int[] emptyWithComments = { + // dangling comment + }; + + int[] shortArray = { 1, 2, 3, 4, 5 }; + + int[] longArray = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }; + + int[] variousBases = { 0b1010, 0xFF, 0123, 3.14159f, 2.71828d, 100L, 0b1101, 0xABC, 0456, 1.41421f, 1.61803d, 200L, 0b1111, 0x7F, 0777, 9.80665f, 6.62607d, 300L }; + + int[] signedNumbers = { -1, +2, -42, +100, -3.14, -500, +600, -7000, +80, -90, +101, -202, +303, -404, +505, -606 }; + + int[] signedWithComment = { -1, -/* negative two */ 2, +3, -4, +5, -6, +7, -8, +9, -10 }; + + int[] withBlankLines = { + 1, 2, 3, 4, 5, + + 6, 7, 8, 9, 10, + + 11, 12, 13, 14, 15 + }; + + int[] withTrailingComments = { + 1, // comment on one + 2, // comment on two + 3 + }; + + int[] withLeadingComments = { + 1, + // comment before 3 + 3, + 4 + }; + + Object[] mixedArray = { 1, "hello", true, null, 2, "world", false, 3, "foo", true, 4, "bar", false }; + + int[] expressionArray = { 1 + 1, 2 * 3, getValue(), 4 + 4, 5 * 5, getData(), 6 + 6, 7 * 7, compute() }; + + int[][] nestedSingular = { { 1 }, { 2 }, { 3 } }; + + int[][] nestedMultiple = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; + + int[] arrayWithInternalNodeComment = { + 1, 2, 3 + // internal node comment + }; } diff --git a/test/unit-test/arrays/_output.java b/test/unit-test/arrays/_output.java index a3164a08..9491ce20 100644 --- a/test/unit-test/arrays/_output.java +++ b/test/unit-test/arrays/_output.java @@ -19,4 +19,101 @@ class Array { ]; int @Nullable [] array; + + int[] empty = {}; + + int[] emptyWithComments = { + // dangling comment + }; + + int[] shortArray = { 1, 2, 3, 4, 5 }; + + int[] longArray = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, + }; + + int[] variousBases = { + 0b1010, 0xFF, 0123, 3.14159f, 2.71828d, 100L, 0b1101, 0xABC, 0456, 1.41421f, + 1.61803d, 200L, 0b1111, 0x7F, 0777, 9.80665f, 6.62607d, 300L, + }; + + int[] signedNumbers = { + -1, +2, -42, +100, -3.14, -500, +600, -7000, +80, -90, +101, -202, +303, + -404, +505, -606, + }; + + int[] signedWithComment = { + -1, + -(/* negative two */ 2), + +3, + -4, + +5, + -6, + +7, + -8, + +9, + -10, + }; + + int[] withBlankLines = { + 1, 2, 3, 4, 5, + + 6, 7, 8, 9, 10, + + 11, 12, 13, 14, 15, + }; + + int[] withTrailingComments = { + 1, // comment on one + 2, // comment on two + 3, + }; + + int[] withLeadingComments = { + 1, + // comment before 3 + 3, 4, + }; + + Object[] mixedArray = { + 1, + "hello", + true, + null, + 2, + "world", + false, + 3, + "foo", + true, + 4, + "bar", + false, + }; + + int[] expressionArray = { + 1 + 1, + 2 * 3, + getValue(), + 4 + 4, + 5 * 5, + getData(), + 6 + 6, + 7 * 7, + compute(), + }; + + int[][] nestedSingular = { { 1 }, { 2 }, { 3 } }; + + int[][] nestedMultiple = { + { 1, 2 }, + { 3, 4 }, + { 5, 6 }, + }; + + int[] arrayWithInternalNodeComment = { + 1, 2, 3, + // internal node comment + }; } diff --git a/test/unit-test/comments/comments-blocks-and-statements/complex/_output.java b/test/unit-test/comments/comments-blocks-and-statements/complex/_output.java index 24538f82..f1f8e4c7 100644 --- a/test/unit-test/comments/comments-blocks-and-statements/complex/_output.java +++ b/test/unit-test/comments/comments-blocks-and-statements/complex/_output.java @@ -34,12 +34,7 @@ private void myFunction( System.out.println("The value is 0"); } else /*false*/ { int[] arr = { - /*One*/ 1, - /*Two */ 2, - /*zero*/ 0, - /*One again*/ 1, - -1 /*Minus One*/, - 0, + /*One*/ 1, /*Two */ 2, /*zero*/ 0, /*One again*/ 1, -1 /*Minus One*/, 0, 2, }; diff --git a/test/unit-test/member_chain/_input.java b/test/unit-test/member_chain/_input.java index 1e8e2d46..9921db54 100644 --- a/test/unit-test/member_chain/_input.java +++ b/test/unit-test/member_chain/_input.java @@ -266,4 +266,9 @@ void typeArguments() { a().b().iiiiiiiiii("jjjjjjjjjj", "kkkkkkkkkk", "llllllllll", "mmmmmmmmmm", "nnnnnnnnnn"); } + + void arrayInitializer() { + assertEquals(Bytes.fromInt(0x01020304), new byte[] { 0x01, 0x02, 0x03, 0x04 }); + community = fromExtended(0x02, new byte[] { 0x00, 0x00, 0x00, 0x64, 0x19, 0x00, 0x00, 0x00, 0x00, 0x64, 0x19, 0x00, 0x00, 0x00, 0x00, 0x64 }); + } } diff --git a/test/unit-test/member_chain/_output.java b/test/unit-test/member_chain/_output.java index d568e8f2..e0c87158 100644 --- a/test/unit-test/member_chain/_output.java +++ b/test/unit-test/member_chain/_output.java @@ -351,4 +351,18 @@ void typeArguments() { "nnnnnnnnnn" ); } + + void arrayInitializer() { + assertEquals( + Bytes.fromInt(0x01020304), + new byte[] { 0x01, 0x02, 0x03, 0x04 } + ); + community = fromExtended( + 0x02, + new byte[] { + 0x00, 0x00, 0x00, 0x64, 0x19, 0x00, 0x00, 0x00, 0x00, 0x64, 0x19, 0x00, + 0x00, 0x00, 0x00, 0x64, + } + ); + } }