From 710e567ba5cbca2fd61dda6863e1c9497258c327 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 3 Nov 2025 15:55:02 +0000 Subject: [PATCH] Check javadoc macros in adoc files Closes gh-48298 --- .../boot/build/AntoraConventions.java | 25 + .../boot/build/antora/CheckJavadocMacros.java | 446 ++++++++++++++++++ .../spring-boot-docs/build.gradle | 62 +++ .../modules/reference/pages/web/servlet.adoc | 2 +- .../reference/pages/web/spring-security.adoc | 3 +- .../pages/packaging-oci-image.adoc | 2 +- .../maven-plugin/pages/build-image.adoc | 2 +- 7 files changed, 538 insertions(+), 4 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/antora/CheckJavadocMacros.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java index 4942e0c6a2a..0219a16f4f1 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java @@ -40,12 +40,16 @@ import org.gradle.api.file.Directory; import org.gradle.api.file.FileCollection; import org.gradle.api.logging.LogLevel; import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Copy; +import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.build.antora.AntoraAsciidocAttributes; +import org.springframework.boot.build.antora.CheckJavadocMacros; import org.springframework.boot.build.antora.GenerateAntoraPlaybook; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.ResolvedBom; @@ -97,6 +101,27 @@ public class AntoraConventions { (antoraTask) -> configureAntoraTask(project, antoraTask, npmInstallTask, generateAntoraPlaybookTask)); project.getExtensions() .configure(NodeExtension.class, (nodeExtension) -> configureNodeExtension(project, nodeExtension)); + TaskProvider checkAntoraJavadocMacros = tasks.register("checkAntoraJavadocMacros", + CheckJavadocMacros.class, (task) -> { + task.setSource(project.files(ANTORA_SOURCE_DIR)); + task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(task.getName())); + }); + project.getPlugins().withType(JavaPlugin.class, (java) -> { + String runtimeClasspathConfigurationName = project.getExtensions() + .getByType(JavaPluginExtension.class) + .getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME) + .getRuntimeClasspathConfigurationName(); + Configuration javadocMacros = project.getConfigurations().create("javadocMacros", (configuration) -> { + configuration.extendsFrom(project.getConfigurations().getByName(runtimeClasspathConfigurationName)); + configuration.setDescription( + "Dependencies referenced in javadoc macros. Extends from " + runtimeClasspathConfigurationName); + configuration.setCanBeResolved(true); + configuration.setCanBeDeclared(true); + configuration.setCanBeConsumed(false); + }); + checkAntoraJavadocMacros.configure((macrosTask) -> macrosTask.setClasspath(javadocMacros)); + }); } private void configureGenerateAntoraPlaybookTask(Project project, diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/CheckJavadocMacros.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/CheckJavadocMacros.java new file mode 100644 index 00000000000..8c7cb35c981 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/CheckJavadocMacros.java @@ -0,0 +1,446 @@ +/* + * Copyright 2025 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.build.antora; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import java.util.zip.ZipEntry; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.VerificationException; + +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.FieldVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; +import org.springframework.util.function.ThrowingConsumer; + +/** + * A task to check {@code javadoc:[]} macros in Antora source files. + * + * @author Andy Wilkinson + */ +public abstract class CheckJavadocMacros extends DefaultTask { + + private static final Pattern JAVADOC_MACRO_PATTERN = Pattern.compile("javadoc:(.*?)\\[(.*?)\\]"); + + private final Path projectRoot; + + private FileCollection source; + + private FileCollection classpath; + + public CheckJavadocMacros() { + this.projectRoot = getProject().getRootDir().toPath(); + } + + @InputFiles + public FileCollection getSource() { + return this.source; + } + + public void setSource(FileCollection source) { + this.source = source; + } + + @Optional + @Classpath + public FileCollection getClasspath() { + return this.classpath; + } + + public void setClasspath(FileCollection classpath) { + this.classpath = classpath; + } + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + + @TaskAction + void checkJavadocMacros() { + Set availableClasses = indexClasspath(); + List problems = new ArrayList<>(); + this.source.getAsFileTree() + .filter((file) -> file.getName().endsWith(".adoc")) + .forEach((file) -> problems.addAll(checkJavadocMacros(file, availableClasses))); + File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); + writeReport(problems, outputFile); + if (!problems.isEmpty()) { + throw new VerificationException("Javadoc macro check failed. See '%s' for details".formatted(outputFile)); + } + } + + private Set indexClasspath() { + Set availableClasses = StreamSupport.stream(this.classpath.spliterator(), false).flatMap((root) -> { + if (root.isFile()) { + try (JarFile jar = new JarFile(root)) { + return jar.stream() + .map(JarEntry::getName) + .filter((entryName) -> entryName.endsWith(".class")) + .map((className) -> { + if (className.startsWith("META-INF/versions/")) { + className = className.substring("META-INF/versions/".length()); + } + className = className.substring(0, className.length() - ".class".length()); + className = className.replace('/', '.'); + return className; + }) + .toList() + .stream(); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + return Stream.empty(); + }).collect(Collectors.toSet()); + return availableClasses; + } + + private List checkJavadocMacros(File adocFile, Set availableClasses) { + List problems = new ArrayList<>(); + List macros = JavadocMacro.parse(adocFile); + for (JavadocMacro macro : macros) { + if (!classIsAvailable(macro.className.name, availableClasses)) { + problems.add(this.projectRoot.relativize(macro.className.origin.file.toPath()) + ":" + + macro.className.origin.line + ":" + macro.className.origin.column + ": class " + + macro.className.name + " does not exist."); + } + else { + JavadocAnchor anchor = macro.anchor; + if (anchor != null) { + if (anchor instanceof MethodAnchor methodAnchor) { + MethodMatcher methodMatcher = new MethodMatcher(methodAnchor); + inputStreamOf(macro.className.name, (stream) -> { + ClassReader reader = new ClassReader(stream); + reader.accept(methodMatcher, 0); + }); + if (!methodMatcher.matched) { + problems.add(this.projectRoot.relativize(macro.anchor.origin.file.toPath()) + ":" + + macro.anchor.origin.line + ":" + methodAnchor.origin().column + ": method " + + methodAnchor + " does not exist"); + } + } + else if (anchor instanceof FieldAnchor fieldAnchor) { + FieldMatcher fieldMatcher = new FieldMatcher(fieldAnchor); + inputStreamOf(macro.className.name, (stream) -> { + ClassReader reader = new ClassReader(stream); + reader.accept(fieldMatcher, 0); + }); + if (!fieldMatcher.matched) { + problems.add(this.projectRoot.relativize(macro.anchor.origin.file.toPath()) + ":" + + macro.anchor.origin.line + ":" + fieldAnchor.origin().column + ": field " + + fieldAnchor.name + " does not exist"); + } + } + } + } + } + return problems; + } + + private boolean classIsAvailable(String className, Set availableClasses) { + if (availableClasses.contains(className)) { + return true; + } + if (className.startsWith("java.") || className.startsWith("javax.")) { + return jdkResourceForClass(className) != null; + } + return false; + } + + private URL jdkResourceForClass(String className) { + return getClass().getClassLoader().getResource(className.replace(".", "/") + ".class"); + } + + private void inputStreamOf(String className, ThrowingConsumer streamHandler) { + for (File root : this.classpath) { + if (root.isFile()) { + try (JarFile jar = new JarFile(root)) { + ZipEntry entry = jar.getEntry(className.replace(".", "/") + ".class"); + if (entry != null) { + try (InputStream stream = jar.getInputStream(entry)) { + streamHandler.accept(stream); + } + return; + } + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + URL resource = jdkResourceForClass(className); + if (resource != null) { + try (InputStream stream = resource.openStream()) { + streamHandler.accept(stream); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + + private void writeReport(List problems, File outputFile) { + outputFile.getParentFile().mkdirs(); + StringBuilder report = new StringBuilder(); + if (!problems.isEmpty()) { + if (problems.size() == 1) { + report.append("Found 1 javadoc macro problem:%n".formatted()); + } + else { + report.append("Found %d javadoc macro problems:%n".formatted(problems.size())); + } + problems.forEach((problem) -> report.append("%s%n".formatted(problem))); + } + try { + Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static final class JavadocMacro { + + private final ClassName className; + + private final JavadocAnchor anchor; + + private JavadocMacro(ClassName className, JavadocAnchor anchor) { + this.className = className; + this.anchor = anchor; + } + + private static List parse(File adocFile) { + List macros = new ArrayList<>(); + try { + Path adocFilePath = adocFile.toPath(); + List lines = Files.readAllLines(adocFilePath); + for (int i = 0; i < lines.size(); i++) { + Matcher matcher = JAVADOC_MACRO_PATTERN.matcher(lines.get(i)); + while (matcher.find()) { + Origin classNameOrigin = new Origin(adocFile, i + 1, matcher.start(1) + 1); + String target = matcher.group(1); + String className = target; + int endOfUrlAttribute = className.indexOf("}/"); + if (endOfUrlAttribute != -1) { + className = className.substring(endOfUrlAttribute + 2); + } + JavadocAnchor anchor = null; + int anchorIndex = className.indexOf("#"); + if (anchorIndex != -1) { + anchor = JavadocAnchor.of(className.substring(anchorIndex + 1), new Origin(adocFile, + classNameOrigin.line(), classNameOrigin.column + anchorIndex + 1)); + className = className.substring(0, anchorIndex); + } + macros.add(new JavadocMacro(new ClassName(classNameOrigin, className), anchor)); + } + } + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + return macros; + } + + } + + private static final class ClassName { + + private final Origin origin; + + private final String name; + + private ClassName(Origin origin, String name) { + this.origin = origin; + this.name = name; + } + + } + + private record Origin(File file, int line, int column) { + + } + + private abstract static class JavadocAnchor { + + private final Origin origin; + + protected JavadocAnchor(Origin origin) { + this.origin = origin; + } + + Origin origin() { + return this.origin; + } + + private static JavadocAnchor of(String anchor, Origin origin) { + JavadocAnchor javadocAnchor = WellKnownAnchor.of(anchor, origin); + if (javadocAnchor == null) { + javadocAnchor = MethodAnchor.of(anchor, origin); + } + if (javadocAnchor == null) { + javadocAnchor = FieldAnchor.of(anchor, origin); + } + return javadocAnchor; + } + + } + + private static final class WellKnownAnchor extends JavadocAnchor { + + private WellKnownAnchor(Origin origin) { + super(origin); + } + + private static WellKnownAnchor of(String anchor, Origin origin) { + if (anchor.equals("enum-constant-summary")) { + return new WellKnownAnchor(origin); + } + return null; + } + + } + + private static final class MethodAnchor extends JavadocAnchor { + + private final String name; + + private final List arguments; + + private MethodAnchor(String name, List arguments, Origin origin) { + super(origin); + this.name = name; + this.arguments = arguments; + } + + @Override + public String toString() { + return this.name + "(" + String.join(", ", this.arguments + ")"); + } + + static MethodAnchor of(String anchor, Origin origin) { + if (!anchor.contains("(")) { + return null; + } + int openingIndex = anchor.indexOf('('); + String name = anchor.substring(0, openingIndex); + List arguments = Stream.of(anchor.substring(openingIndex + 1, anchor.length() - 1).split(",")) + .map(String::trim) + .map((argument) -> argument.endsWith("...") ? argument.replace("...", "[]") : argument) + .toList(); + return new MethodAnchor(name, arguments, origin); + } + + } + + private static final class FieldAnchor extends JavadocAnchor { + + private final String name; + + private FieldAnchor(String name, Origin origin) { + super(origin); + this.name = name; + } + + static FieldAnchor of(String anchor, Origin origin) { + return new FieldAnchor(anchor, origin); + } + + } + + private static final class MethodMatcher extends ClassVisitor { + + private final MethodAnchor methodAnchor; + + private boolean matched = false; + + private MethodMatcher(MethodAnchor methodAnchor) { + super(SpringAsmInfo.ASM_VERSION); + this.methodAnchor = methodAnchor; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + if (!this.matched && name.equals(this.methodAnchor.name)) { + Type type = Type.getType(descriptor); + if (type.getArgumentCount() == this.methodAnchor.arguments.size()) { + List argumentTypeNames = Arrays.asList(type.getArgumentTypes()) + .stream() + .map(Type::getClassName) + .toList(); + if (argumentTypeNames.equals(this.methodAnchor.arguments)) { + this.matched = true; + } + } + } + return null; + } + + } + + private static final class FieldMatcher extends ClassVisitor { + + private final FieldAnchor fieldAnchor; + + private boolean matched = false; + + private FieldMatcher(FieldAnchor fieldAnchor) { + super(SpringAsmInfo.ASM_VERSION); + this.fieldAnchor = fieldAnchor; + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + if (!this.matched && name.equals(this.fieldAnchor.name)) { + this.matched = true; + } + return null; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 9abb6601dff..05bdbca8609 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -181,6 +181,68 @@ dependencies { implementation("org.junit.jupiter:junit-jupiter") implementation("org.yaml:snakeyaml") + javadocMacros(project(":spring-boot-project:spring-boot-tools:spring-boot-loader")) + javadocMacros("com.fasterxml.jackson.dataformat:jackson-dataformat-xml") + javadocMacros("com.google.code.gson:gson") + javadocMacros("com.h2database:h2") + javadocMacros("com.hazelcast:hazelcast") + javadocMacros("com.hazelcast:hazelcast-spring") + javadocMacros("com.redis:testcontainers-redis") + javadocMacros("io.lettuce:lettuce-core") + javadocMacros("io.micrometer:micrometer-registry-new-relic") + javadocMacros("io.opentelemetry:opentelemetry-sdk-logs") + javadocMacros("io.opentelemetry:opentelemetry-sdk-metrics") + javadocMacros("io.opentelemetry:opentelemetry-sdk-trace") + javadocMacros("io.prometheus:prometheus-metrics-tracer-common") + javadocMacros("io.prometheus:simpleclient_tracer_common") + javadocMacros("io.rsocket:rsocket-core") + javadocMacros("jakarta.json.bind:jakarta.json.bind-api") + javadocMacros("jakarta.mail:jakarta.mail-api") + javadocMacros("jakarta.websocket:jakarta.websocket-api") + javadocMacros("org.apache.logging.log4j:log4j-core") + javadocMacros("org.apache.activemq:artemis-jakarta-server") + javadocMacros("org.eclipse.jetty:jetty-client") + javadocMacros("org.eclipse.jetty:jetty-server") + javadocMacros("org.elasticsearch.client:elasticsearch-rest-client-sniffer") + javadocMacros("org.flywaydb:flyway-core") + javadocMacros("org.infinispan:infinispan-core") + javadocMacros("org.liquibase:liquibase-core") + javadocMacros("org.messaginghub:pooled-jms") + javadocMacros("org.postgresql:postgresql") + javadocMacros("org.projectlombok:lombok") + javadocMacros("org.seleniumhq.selenium:selenium-api") + javadocMacros("org.springframework.amqp:spring-rabbit-stream") + javadocMacros("org.springframework.data:spring-data-jdbc") + javadocMacros("org.springframework.data:spring-data-rest-webmvc") + javadocMacros("org.springframework.hateoas:spring-hateoas") + javadocMacros("org.springframework.integration:spring-integration-core") + javadocMacros("org.springframework.integration:spring-integration-rsocket") + javadocMacros("org.springframework.security:spring-security-oauth2-authorization-server") + javadocMacros("org.springframework.security:spring-security-oauth2-jose") + javadocMacros("org.springframework.security:spring-security-oauth2-resource-server") + javadocMacros("org.springframework.security:spring-security-saml2-service-provider") { + exclude group: "org.opensaml" + } + javadocMacros("org.springframework.session:spring-session-core") + javadocMacros("org.testcontainers:activemq") + javadocMacros("org.testcontainers:cassandra") + javadocMacros("org.testcontainers:clickhouse") + javadocMacros("org.testcontainers:couchbase") + javadocMacros("org.testcontainers:elasticsearch") + javadocMacros("org.testcontainers:grafana") + javadocMacros("org.testcontainers:jdbc") + javadocMacros("org.testcontainers:kafka") + javadocMacros("org.testcontainers:mariadb") + javadocMacros("org.testcontainers:mssqlserver") + javadocMacros("org.testcontainers:mysql") + javadocMacros("org.testcontainers:oracle-free") + javadocMacros("org.testcontainers:oracle-xe") + javadocMacros("org.testcontainers:postgresql") + javadocMacros("org.testcontainers:pulsar") + javadocMacros("org.testcontainers:rabbitmq") + javadocMacros("org.testcontainers:redpanda") + javadocMacros("org.thymeleaf:thymeleaf-spring6") + remoteSpringApplicationExample(platform(project(":spring-boot-project:spring-boot-dependencies"))) remoteSpringApplicationExample(project(":spring-boot-project:spring-boot-devtools")) remoteSpringApplicationExample(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-logging")) diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc index 81230ad4ff4..415f2c47b9f 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/servlet.adoc @@ -110,7 +110,7 @@ This can be useful when you want to re-order or remove some of the converters th === MessageCodesResolver Spring MVC has a strategy for generating error codes for rendering error messages from binding errors: javadoc:org.springframework.validation.MessageCodesResolver[]. -If you set the configprop:spring.mvc.message-codes-resolver-format[] property `PREFIX_ERROR_CODE` or `POSTFIX_ERROR_CODE`, Spring Boot creates one for you (see the enumeration in javadoc:org.springframework.validation.DefaultMessageCodesResolver#Format[]). +If you set the configprop:spring.mvc.message-codes-resolver-format[] property `PREFIX_ERROR_CODE` or `POSTFIX_ERROR_CODE`, Spring Boot creates one for you (see the enumeration in javadoc:org.springframework.validation.DefaultMessageCodesResolver$Format[]). diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-security.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-security.adoc index 859cee78a19..57695d4566c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-security.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-security.adoc @@ -61,8 +61,9 @@ javadoc:org.springframework.boot.autoconfigure.security.servlet.PathRequest[] ca Similar to Spring MVC applications, you can secure your WebFlux applications by adding the `spring-boot-starter-security` dependency. The default security configuration is implemented in javadoc:org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration[] and javadoc:org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration[]. -javadoc:org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration[] imports `WebFluxSecurityConfiguration` for web security and javadoc:org.springframework.boot.autoconfigure.security.reactive.UserDetailsServiceAutoConfiguration[] for authentication. In addition to reactive web applications, the latter is also auto-configured when RSocket is in use. +javadoc:org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration[] imports `WebFluxSecurityConfiguration` for web security. +javadoc:org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration[] auto-configures authentication. To completely switch off the default web application security configuration, including Actuator security, add a bean of type javadoc:org.springframework.security.web.server.WebFilterChainProxy[] (doing so does not disable the javadoc:org.springframework.security.core.userdetails.ReactiveUserDetailsService[] configuration). To also switch off the javadoc:org.springframework.security.core.userdetails.ReactiveUserDetailsService[] configuration, add a bean of type javadoc:org.springframework.security.core.userdetails.ReactiveUserDetailsService[] or javadoc:org.springframework.security.authentication.ReactiveAuthenticationManager[]. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc index a3da1f3a025..3eee118fb10 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc @@ -140,7 +140,7 @@ Refer to documentation of the builder being used to determine the image OS and a | `imageName` | `--imageName` -| javadoc:org.springframework.boot.buildpack.platform.docker.type.ImageReference#of-java.lang.String-[Image name] for the generated image. +| javadoc:org.springframework.boot.buildpack.platform.docker.type.ImageReference#of(java.lang.String)[Image name] for the generated image. | `docker.io/library/${project.name}:${project.version}` | `pullPolicy` diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/build-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/build-image.adoc index ec1df154e03..9888b0a83ac 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/build-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/antora/modules/maven-plugin/pages/build-image.adoc @@ -156,7 +156,7 @@ Refer to documentation of the builder being used to determine the image OS and a | `name` + (`spring-boot.build-image.imageName`) -| javadoc:org.springframework.boot.buildpack.platform.docker.type.ImageName#of-java.lang.String-[Image name] for the generated image. +| javadoc:org.springframework.boot.buildpack.platform.docker.type.ImageName#of(java.lang.String)[Image name] for the generated image. | `docker.io/library/` + `${project.artifactId}:${project.version}`