diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java index cd04d09ab..98bac7796 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java @@ -157,6 +157,12 @@ public class JavaInputAstVisitor extends TreePathScanner { */ private static final int METHOD_CHAIN_COLUMN_LIMIT = 80; + /** + * Maximum column at which the annotated parameter of a record should start. This exists in particular to improve + * readability when one or multiple annotations are added to the parameters of a record. + */ + private static final int ANNOTATION_RECORD_PARAMETER_COLUMN_LIMIT = 60; + /** Direction for Annotations (usually VERTICAL). */ protected enum Direction { VERTICAL, @@ -2210,13 +2216,23 @@ protected List visitModifiers( ModifiersTree modifiersTree, Direction annotationsDirection, Optional declarationAnnotationBreak) { - return visitModifiers(modifiersTree.getAnnotations(), annotationsDirection, declarationAnnotationBreak); + boolean isRecordParameter = modifiersTree instanceof JCTree.JCModifiers && isInRecord(modifiersTree); + return visitModifiers( + modifiersTree.getAnnotations(), annotationsDirection, declarationAnnotationBreak, isRecordParameter); } private List visitModifiers( List annotationTrees, Direction annotationsDirection, Optional declarationAnnotationBreak) { + return visitModifiers(annotationTrees, annotationsDirection, declarationAnnotationBreak, false); + } + + private List visitModifiers( + List annotationTrees, + Direction annotationsDirection, + Optional declarationAnnotationBreak, + boolean isRecordParameter) { if (annotationTrees.isEmpty() && !nextIsModifier()) { return EMPTY_LIST; } @@ -2239,9 +2255,19 @@ private List visitModifiers( lastWasAnnotation = true; } builder.close(); - ImmutableList trailingBreak = annotationsDirection.isVertical() - ? forceBreakList(declarationAnnotationBreak) - : breakList(declarationAnnotationBreak); + + // pjf specific: record params should take into consideration the columnLimit + ImmutableList trailingBreak = isRecordParameter + ? ImmutableList.of(Break.builder() + .fillMode(FillMode.UNIFIED) + .flat(" ") + .plusIndent(ZERO) + .hasColumnLimit(true) + .optTag(declarationAnnotationBreak) + .build()) + : annotationsDirection.isVertical() + ? forceBreakList(declarationAnnotationBreak) + : breakList(declarationAnnotationBreak); if (annotations.isEmpty() && !nextIsModifier()) { return trailingBreak; } @@ -2385,6 +2411,11 @@ protected void visitFormals(Optional receiver, List receiver, List initializer, String equals, Optional trailing) { + visitToDeclare(kind, annotationsDirection, node, initializer, equals, trailing, Optional.empty()); + } + + private void visitToDeclare( + DeclarationKind kind, + Direction annotationsDirection, + VariableTree node, + Optional initializer, + String equals, + Optional trailing, + Optional annotationBreakForRecords) { sync(node); declareOne( kind, @@ -2596,7 +2651,8 @@ private void visitToDeclare( initializer, trailing, /* receiverExpression= */ Optional.empty(), - /* typeWithDims= */ Optional.empty()); + /* typeWithDims= */ Optional.empty(), + annotationBreakForRecords); } /** Does not omit the leading '<', which should be associated with the type name. */ @@ -3400,9 +3456,39 @@ int declareOne( Optional trailing, Optional receiverExpression, Optional typeWithDims) { + return declareOne( + kind, + annotationsDirection, + modifiers, + type, + name, + op, + equals, + initializer, + trailing, + receiverExpression, + typeWithDims, + Optional.empty()); + } + + /** Declare one variable or variable-like thing. */ + @SuppressWarnings("TooManyArguments") + int declareOne( + DeclarationKind kind, + Direction annotationsDirection, + Optional modifiers, + Tree type, + Name name, + String op, + String equals, + Optional initializer, + Optional trailing, + Optional receiverExpression, + Optional typeWithDims, + Optional verticalAnnotationBreakForRecords) { BreakTag typeBreak = new BreakTag(); - BreakTag verticalAnnotationBreak = new BreakTag(); + BreakTag verticalAnnotationBreak = verticalAnnotationBreakForRecords.orElseGet(BreakTag::new); // If the node is a field declaration, try to output any declaration // annotations in-line. If the entire declaration doesn't fit on a single @@ -3417,12 +3503,22 @@ int declareOne( new ArrayDeque<>(typeWithDims.isPresent() ? typeWithDims.get().dims : Collections.emptyList()); int baseDims = 0; - builder.open( - kind == DeclarationKind.PARAMETER - && (modifiers.isPresent() - && !modifiers.get().getAnnotations().isEmpty()) - ? plusFour - : ZERO); + if (kind == DeclarationKind.PARAMETER + && modifiers.isPresent() + && !modifiers.get().getAnnotations().isEmpty()) { + if (!isInRecord(modifiers.get())) { + builder.open(plusFour); + } else { + // pjf specific: we are enforcing the column limit for annotated parameters of records + builder.open(OpenOp.builder() + .debugName("visitParam") + .plusIndent(ZERO) + .columnLimitBeforeLastBreak(ANNOTATION_RECORD_PARAMETER_COLUMN_LIMIT) + .build()); + } + } else { + builder.open(ZERO); + } { if (modifiers.isPresent()) { visitAndBreakModifiers(modifiers.get(), annotationsDirection, Optional.of(verticalAnnotationBreak)); @@ -3792,6 +3888,13 @@ private Direction inlineAnnotationDirection(ModifiersTree modifiers) { return Direction.HORIZONTAL; } + /** + * Checks if the modifiers are parameters of a record definition. + */ + private boolean isInRecord(ModifiersTree modifiers) { + return (((JCTree.JCModifiers) modifiers).flags & RECORD) == RECORD; + } + /** * Emit a {@link Token}. * diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/AnnotationFields.input b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/AnnotationFields.input new file mode 100644 index 000000000..84a47945c --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/AnnotationFields.input @@ -0,0 +1,110 @@ +@Schema(description = "Type of quote being requested", example = "NEW_BUSINESS", somethingElse= "new", other="my other long string") +public record QuoteRequest( + @Schema(description = "US state of the product being quoted", example = "TX") + RegulatoryState regulatoryState, + @Schema(description = "Reason for a quote", example = "New Business") String amendmentReason, + @Schema(description = "Type of quote being requested", example = "NEW_BUSINESS", somethingElse= "new", other="my other long string") QuoteType quoteType, + @Schema(description = "Date the quoted changes become active", example = "2023-06-25") + LocalDate quoteEffectiveDate +) {} + +public record QuoteRequest( + + int value, + + @SomeInput RegulatoryState regulatoryState, + + @SomeInput + @NotNull + @Deprecated + @JsonValue(name = "something") @Schema(description = "US state of the product being quoted", example = "TX") RegulatoryState regulatoryState, + @Schema(description = "Reason for a quote", example = "New Business") String amendmentReason, + int x, + int j) {} + +public record Url( + int var1, + @JsonValue("value") String var2, + @SomeInput @JsonValue String var3, + int var3, + int var4, + int var5, + int var6) {} + +public record Url( + @NotNull + @JsonValue + @Deprecated + @Annotation1 + @Annotation2 + @Annotation3 + @Annotation4 + @Annotation5 + @Annotation6 + @Annotation7 + @Annotation8 + @Annotation9 + @Annotation10 + @Annotation11 + @Annotation12 + String value) {} + +public record Url(@NotNull String value, int number) {} + +public record Url( + int number, + @JsonValue("value") String value) {} + +public record Url(@New @JsonValue("value") String value) {} + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public record Game( + @JsonProperty("accounting_group") @JsonPropertyDescription("The accounting group of the game.") + String accountingGroup, + @JsonProperty("accumulating") + @JsonPropertyDescription("Marks which games with accumulating bonuses.") Boolean accumulating, + @JsonProperty("bonus_buy") @JsonPropertyDescription("Games with purchasable bonuses.") Boolean bonusBuy, + @JsonProperty("category") + @JsonPropertyDescription( + "Game's category. Allowed values: slots, roulette, card, " + + "casual, lottery, poker, craps, video_poker") + String category, + @JsonProperty("hd") @JsonPropertyDescription("HD format games.") Boolean hd, + + @JsonProperty("new") @JsonPropertyDescription("New format games.") Boolean assa, + @JsonProperty("hit_rate") @JsonPropertyDescription( + "Frequency of wins per 100 bets. The higher, the hit rate the lower the volatility " + + "rating, and vice versa. Positive value. For slots only. " + + "Match pattern: ^\\d{1,18}(\\.\\d{1,12})?$") String hitRate, + @JsonProperty("params") @JsonPropertyDescription("Game's custom parameters.") + Map params) { + + private void get(@JsonNode("value") String value) {} + + private void get2(@JsonNode(value="value") String value) {} + } + +record ApiEndpoint(@JsonValue("name") String name, HttpMethod method, String url) { + ApiEndpoint { + name = name.toUpperCase(); + } +} + +record ApiEndpoint(@JsonProperty("bonus_buy") @JsonValue(some_other_name_that_is_here="value", name_which_is_super_long_that_goes_beyond_the_limit="name", other="other") String name, HttpMethod method, String url) { + ApiEndpoint { + name = name.toUpperCase(); + } +} + +record ApiEndpoint(@JsonValue(name="name") String name, HttpMethod method, String url) { + ApiEndpoint { + name = name.toUpperCase(); + } +} + +record ApiEndpoint(@JsonValue(name="name") String name, HttpMethod method, String url, @JsonValue(name="name") String name) { + ApiEndpoint { + name = name.toUpperCase(); + } +} \ No newline at end of file diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/AnnotationFields.output b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/AnnotationFields.output new file mode 100644 index 000000000..8124ce30c --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/AnnotationFields.output @@ -0,0 +1,146 @@ +@Schema( + description = "Type of quote being requested", + example = "NEW_BUSINESS", + somethingElse = "new", + other = "my other long string") +public record QuoteRequest( + @Schema(description = "US state of the product being quoted", example = "TX") + RegulatoryState regulatoryState, + + @Schema(description = "Reason for a quote", example = "New Business") + String amendmentReason, + + @Schema( + description = "Type of quote being requested", + example = "NEW_BUSINESS", + somethingElse = "new", + other = "my other long string") + QuoteType quoteType, + + @Schema(description = "Date the quoted changes become active", example = "2023-06-25") + LocalDate quoteEffectiveDate) {} + +public record QuoteRequest( + int value, + + @SomeInput RegulatoryState regulatoryState, + + @SomeInput + @NotNull + @Deprecated + @JsonValue(name = "something") + @Schema(description = "US state of the product being quoted", example = "TX") + RegulatoryState regulatoryState, + + @Schema(description = "Reason for a quote", example = "New Business") + String amendmentReason, + + int x, + int j) {} + +public record Url( + int var1, + @JsonValue("value") String var2, + @SomeInput @JsonValue String var3, + int var3, + int var4, + int var5, + int var6) {} + +public record Url( + @NotNull + @JsonValue + @Deprecated + @Annotation1 + @Annotation2 + @Annotation3 + @Annotation4 + @Annotation5 + @Annotation6 + @Annotation7 + @Annotation8 + @Annotation9 + @Annotation10 + @Annotation11 + @Annotation12 + String value) {} + +public record Url(@NotNull String value, int number) {} + +public record Url(int number, @JsonValue("value") String value) {} + +public record Url(@New @JsonValue("value") String value) {} + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public record Game( + @JsonProperty("accounting_group") @JsonPropertyDescription("The accounting group of the game.") + String accountingGroup, + + @JsonProperty("accumulating") @JsonPropertyDescription("Marks which games with accumulating bonuses.") + Boolean accumulating, + + @JsonProperty("bonus_buy") @JsonPropertyDescription("Games with purchasable bonuses.") + Boolean bonusBuy, + + @JsonProperty("category") + @JsonPropertyDescription("Game's category. Allowed values: slots, roulette, card, " + + "casual, lottery, poker, craps, video_poker") + String category, + + @JsonProperty("hd") @JsonPropertyDescription("HD format games.") + Boolean hd, + + @JsonProperty("new") @JsonPropertyDescription("New format games.") + Boolean assa, + + @JsonProperty("hit_rate") + @JsonPropertyDescription("Frequency of wins per 100 bets. The higher, the hit rate the lower the volatility " + + "rating, and vice versa. Positive value. For slots only. " + + "Match pattern: ^\\d{1,18}(\\.\\d{1,12})?$") + String hitRate, + + @JsonProperty("params") @JsonPropertyDescription("Game's custom parameters.") + Map params) { + + private void get(@JsonNode("value") String value) {} + + private void get2(@JsonNode(value = "value") String value) {} +} + +record ApiEndpoint(@JsonValue("name") String name, HttpMethod method, String url) { + ApiEndpoint { + name = name.toUpperCase(); + } +} + +record ApiEndpoint( + @JsonProperty("bonus_buy") + @JsonValue( + some_other_name_that_is_here = "value", + name_which_is_super_long_that_goes_beyond_the_limit = "name", + other = "other") + String name, + + HttpMethod method, + String url) { + ApiEndpoint { + name = name.toUpperCase(); + } +} + +record ApiEndpoint(@JsonValue(name = "name") String name, HttpMethod method, String url) { + ApiEndpoint { + name = name.toUpperCase(); + } +} + +record ApiEndpoint( + @JsonValue(name = "name") String name, + HttpMethod method, + String url, + @JsonValue(name = "name") String name) { + ApiEndpoint { + name = name.toUpperCase(); + } +}