From ff5c2a2351163f024f38144f75ba26fff6afeb7e Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 1 May 2024 22:35:28 -0700 Subject: [PATCH] Improve javadoc cleanup to remove duplicate spaces Improve `TypeUtils` so that repeated space chars are removed. Fixes gh-40593 --- .../configurationprocessor/TypeUtils.java | 24 +++++-- ...ationMetadataAnnotationProcessorTests.java | 2 +- .../record/ExampleRecord.java | 7 +- .../boot/context/properties/TempTests.java | 70 +++++++++++++++++++ 4 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/TempTests.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java index 568b9e6e249..bf9d39afb87 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,8 +67,6 @@ class TypeUtils { private static final Map WRAPPER_TO_PRIMITIVE; - private static final Pattern NEW_LINE_PATTERN = Pattern.compile("[\r\n]+"); - static { Map primitives = new HashMap<>(); PRIMITIVE_WRAPPERS.forEach((kind, wrapperClass) -> primitives.put(wrapperClass.getName(), kind)); @@ -183,9 +181,7 @@ class TypeUtils { return getJavaDoc((RecordComponentElement) element); } String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null; - if (javadoc != null) { - javadoc = NEW_LINE_PATTERN.matcher(javadoc).replaceAll("").trim(); - } + javadoc = (javadoc != null) ? cleanupJavaDoc(javadoc) : null; return (javadoc == null || javadoc.isEmpty()) ? null : javadoc; } @@ -259,7 +255,7 @@ class TypeUtils { Pattern paramJavadocPattern = paramJavadocPattern(recordComponent.getSimpleName().toString()); Matcher paramJavadocMatcher = paramJavadocPattern.matcher(recordJavadoc); if (paramJavadocMatcher.find()) { - String paramJavadoc = NEW_LINE_PATTERN.matcher(paramJavadocMatcher.group()).replaceAll("").trim(); + String paramJavadoc = cleanupJavaDoc(paramJavadocMatcher.group()); return paramJavadoc.isEmpty() ? null : paramJavadoc; } } @@ -271,6 +267,20 @@ class TypeUtils { return Pattern.compile(pattern, Pattern.DOTALL); } + private String cleanupJavaDoc(String javadoc) { + StringBuilder result = new StringBuilder(javadoc.length()); + char lastChar = '.'; + for (int i = 0; i < javadoc.length(); i++) { + char ch = javadoc.charAt(i); + boolean repeatedSpace = ch == ' ' && lastChar == ' '; + if (ch != '\r' && ch != '\n' && !repeatedSpace) { + result.append(ch); + lastChar = ch; + } + } + return result.toString().trim(); + } + /** * A visitor that extracts the fully qualified name of a type, including generic * information. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 8aaa49d316b..b0807905214 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -521,7 +521,7 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene void recordPropertiesWithDescriptions() { ConfigurationMetadata metadata = compile(ExampleRecord.class); assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-string", String.class) - .withDescription("very long description that doesn't fit single line")); + .withDescription("very long description that doesn't fit single line and is indented")); assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-integer", Integer.class) .withDescription("description with @param and @ pitfalls")); assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-boolean", Boolean.class) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/ExampleRecord.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/ExampleRecord.java index fdffebfbcbf..41814a40749 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/ExampleRecord.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/record/ExampleRecord.java @@ -16,10 +16,13 @@ package org.springframework.boot.configurationsample.record; +// @formatter:off + /** * Example Record Javadoc sample * - * @param someString very long description that doesn't fit single line + * @param someString very long description that + * doesn't fit single line and is indented * @param someInteger description with @param and @ pitfalls * @param someBoolean description with extra spaces * @param someLong description without space after asterisk @@ -30,3 +33,5 @@ package org.springframework.boot.configurationsample.record; @org.springframework.boot.configurationsample.ConfigurationProperties("record.descriptions") public record ExampleRecord(String someString, Integer someInteger, Boolean someBoolean, Long someLong, Byte someByte) { } + +//@formatter:on diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/TempTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/TempTests.java new file mode 100644 index 00000000000..9a85331cfea --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/TempTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties; + +/** + * @author pwebb + */ + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.context.properties.bind.DefaultValue; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.support.TestPropertySourceUtils; + +public class TempTests { + + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @Test + void testName() { + load(MyConfig.class, "foo.bar.baz=hello"); + System.out.println(this.context.getBean(Foo.class)); + } + + @Test + void testName2() { + load(MyConfig.class); + System.out.println(this.context.getBean(Foo.class)); + } + + private AnnotationConfigApplicationContext load(Class configuration, String... inlinedProperties) { + return load(new Class[] { configuration }, inlinedProperties); + } + + private AnnotationConfigApplicationContext load(Class[] configuration, String... inlinedProperties) { + this.context.register(configuration); + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, inlinedProperties); + this.context.refresh(); + return this.context; + } + + @Configuration + @EnableConfigurationProperties(Foo.class) + static class MyConfig { + + } + + @ConfigurationProperties("foo") + record Foo(@DefaultValue Bar bar) { + } + + record Bar(@DefaultValue("hello") String baz) { + } + +}