From 9c9597904e21d4a1d1a8c6dab11e749305e73b95 Mon Sep 17 00:00:00 2001 From: Fred Bricon Date: Fri, 15 Sep 2023 12:07:29 +0200 Subject: [PATCH] feat: validate BuildItem classes Signed-off-by: Fred Bricon --- .../maven/quarkus-builditems/.gitignore | 43 +++++++ .../projects/maven/quarkus-builditems/pom.xml | 116 ++++++++++++++++++ .../org/acme/builditems/BadBuildItem.java | 6 + .../org/acme/builditems/GoodBuildItem.java | 6 + .../src/main/resources/application.properties | 0 .../psi/core/utils/PositionUtils.java | 15 ++- .../intellij/quarkus/QuarkusConstants.java | 3 + .../builditems/QuarkusBuildItemErrorCode.java | 30 +++++ ...uarkusBuildItemDiagnosticsParticipant.java | 99 +++++++++++++++ src/main/resources/META-INF/plugin.xml | 1 + .../validation/BuildItemDiagnosticsTest.java | 62 ++++++++++ 11 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 projects/quarkus/projects/maven/quarkus-builditems/.gitignore create mode 100644 projects/quarkus/projects/maven/quarkus-builditems/pom.xml create mode 100644 projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/BadBuildItem.java create mode 100644 projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/GoodBuildItem.java create mode 100644 projects/quarkus/projects/maven/quarkus-builditems/src/main/resources/application.properties create mode 100644 src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/builditems/QuarkusBuildItemErrorCode.java create mode 100644 src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/validators/QuarkusBuildItemDiagnosticsParticipant.java create mode 100644 src/test/java/com/redhat/devtools/intellij/quarkus/validation/BuildItemDiagnosticsTest.java diff --git a/projects/quarkus/projects/maven/quarkus-builditems/.gitignore b/projects/quarkus/projects/maven/quarkus-builditems/.gitignore new file mode 100644 index 000000000..8c7863e7b --- /dev/null +++ b/projects/quarkus/projects/maven/quarkus-builditems/.gitignore @@ -0,0 +1,43 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/projects/quarkus/projects/maven/quarkus-builditems/pom.xml b/projects/quarkus/projects/maven/quarkus-builditems/pom.xml new file mode 100644 index 000000000..cd6dfe7dd --- /dev/null +++ b/projects/quarkus/projects/maven/quarkus-builditems/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + org.acme + quarkus-builditems + 1.0.0-SNAPSHOT + + 3.11.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.3.1 + true + 3.1.2 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-junit5 + test + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + native + + + native + + + + false + native + + + + diff --git a/projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/BadBuildItem.java b/projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/BadBuildItem.java new file mode 100644 index 000000000..85960e7bb --- /dev/null +++ b/projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/BadBuildItem.java @@ -0,0 +1,6 @@ +package org.acme.builditems; + +import io.quarkus.builder.item.MultiBuildItem; + +public class BadBuildItem extends MultiBuildItem { +} \ No newline at end of file diff --git a/projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/GoodBuildItem.java b/projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/GoodBuildItem.java new file mode 100644 index 000000000..8cf3a6604 --- /dev/null +++ b/projects/quarkus/projects/maven/quarkus-builditems/src/main/java/org/acme/builditems/GoodBuildItem.java @@ -0,0 +1,6 @@ +package org.acme.builditems; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class GoodBuildItem extends SimpleBuildItem { +} \ No newline at end of file diff --git a/projects/quarkus/projects/maven/quarkus-builditems/src/main/resources/application.properties b/projects/quarkus/projects/maven/quarkus-builditems/src/main/resources/application.properties new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PositionUtils.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PositionUtils.java index 8903088a2..c6ab47943 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PositionUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PositionUtils.java @@ -9,6 +9,7 @@ *******************************************************************************/ package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightNamesUtil; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiField; @@ -16,6 +17,7 @@ import com.intellij.psi.PsiMethod; import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.IPsiUtils; import org.eclipse.lsp4j.Range; +import org.jetbrains.annotations.NotNull; /** * Position utilities. @@ -58,7 +60,6 @@ public static Range toNameRange(PsiClass type, IPsiUtils utils) { * @param method the java type. * @param utils the JDT utilities. * @return the LSP range for the given method name. - * @throws JavaModelException */ public static Range toNameRange(PsiMethod method, IPsiUtils utils) { PsiFile openable = method.getContainingFile(); @@ -66,4 +67,16 @@ public static Range toNameRange(PsiMethod method, IPsiUtils utils) { return utils.toRange(openable, sourceRange.getStartOffset(), sourceRange.getLength()); } + /** + * Returns the LSP Range for the class declaration of the given type + * + * @param type the java type. + * @param utils the JDT utilities. + * @return the LSP range the class declaration of the given type. + */ + public static Range toClassDeclarationRange(@NotNull PsiClass type, @NotNull IPsiUtils utils) { + PsiFile openable = type.getContainingFile(); + TextRange sourceRange = HighlightNamesUtil.getClassDeclarationTextRange(type); + return utils.toRange(openable, sourceRange.getStartOffset(), sourceRange.getLength()); + } } diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java index 53384576e..4b3227b3e 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java @@ -29,6 +29,8 @@ public class QuarkusConstants { public static final String CONFIG_GROUP_ANNOTATION = "io.quarkus.runtime.annotations.ConfigGroup"; public static final String CONFIG_ITEM_ANNOTATION = "io.quarkus.runtime.annotations.ConfigItem"; public static final String CONFIG_PROPERTIES_ANNOTATION = "io.quarkus.arc.config.ConfigProperties"; + public static final String QUARKUS_DIAGNOSTIC_SOURCE = "quarkus"; + /** * As ConfigProperties is not part anymore of Quarkus since 3.1.2.Final, @@ -116,5 +118,6 @@ public class QuarkusConstants { public static final String QUARKUS_CODE_URL = System.getProperty(QUARKUS_CODE_URL_PROPERTY_NAME, QUARKUS_CODE_URL_PRODUCTION); public static final String QUARKUS_RUNTIME_CLASS_NAME = "io.quarkus.runtime.LaunchMode"; + public static final String QUARKUS_BUILD_ITEM_CLASS_NAME = "io.quarkus.builder.item.BuildItem"; public static final String QUARKUS_RUN_CONTEXT_KEY = QuarkusConstants.class.getName() + ".quarkusContext"; } diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/builditems/QuarkusBuildItemErrorCode.java b/src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/builditems/QuarkusBuildItemErrorCode.java new file mode 100644 index 000000000..857e67667 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/builditems/QuarkusBuildItemErrorCode.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.quarkus.psi.internal.builditems; + +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.java.diagnostics.IJavaErrorCode; + +/** + * Represents error codes for validation issues in classes inheriting io.quarkus.builder.item.BuildItem. + */ +public enum QuarkusBuildItemErrorCode implements IJavaErrorCode { + + InvalidModifierBuildItem; + + @Override + public String getCode() { + return name(); + } +} + diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/validators/QuarkusBuildItemDiagnosticsParticipant.java b/src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/validators/QuarkusBuildItemDiagnosticsParticipant.java new file mode 100644 index 000000000..b467e94e0 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/psi/internal/validators/QuarkusBuildItemDiagnosticsParticipant.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.quarkus.psi.internal.validators; + +import com.intellij.openapi.module.Module; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiModifier; +import com.intellij.psi.util.InheritanceUtil; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.java.diagnostics.IJavaDiagnosticsParticipant; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.java.diagnostics.JavaDiagnosticsContext; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.PositionUtils; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.PsiTypeUtils; +import com.redhat.devtools.intellij.quarkus.QuarkusConstants; +import com.redhat.devtools.intellij.quarkus.psi.internal.builditems.QuarkusBuildItemErrorCode; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4mp.commons.DocumentFormat; + +import java.util.ArrayList; +import java.util.List; + +/** + * Validates io.quarkus.builder.item.BuildItem subclasses. + * + */ +public class QuarkusBuildItemDiagnosticsParticipant implements IJavaDiagnosticsParticipant { + + private static final String INVALID_MODIFIER = "BuildItem class %2$s%1$s%2$s must either be declared final or abstract"; + + @Override + public boolean isAdaptedForDiagnostics(JavaDiagnosticsContext context) { + // Collection of diagnostics for Quarkus Build Items is done only if + // io.quarkus.builder.item.BuildItem is on the classpath + Module javaProject = context.getJavaProject(); + return PsiTypeUtils.findType(javaProject, QuarkusConstants.QUARKUS_BUILD_ITEM_CLASS_NAME) != null; + } + + @Override + public List collectDiagnostics(JavaDiagnosticsContext context) { + PsiFile typeRoot = context.getTypeRoot(); + PsiElement[] elements = typeRoot.getChildren(); + List diagnostics = new ArrayList<>(); + collectDiagnostics(elements, diagnostics, context); + return diagnostics; + } + + private static void collectDiagnostics(PsiElement[] elements, List diagnostics, + JavaDiagnosticsContext context) { + for (PsiElement element : elements) { + if (element instanceof PsiClass) { + PsiClass psiClass = (PsiClass) element; + if (isBuildItem(psiClass)) { + validateBuildItem(psiClass, diagnostics, context); + } + } + } + } + + private static boolean isBuildItem(PsiClass type) { + return InheritanceUtil.isInheritor(type, QuarkusConstants.QUARKUS_BUILD_ITEM_CLASS_NAME); + } + + private static void validateBuildItem(PsiClass psiClass, List diagnostics, JavaDiagnosticsContext context) { + if (psiClass.hasModifierProperty(PsiModifier.FINAL) + || psiClass.hasModifierProperty(PsiModifier.ABSTRACT) + ) { + return; + } + Range range = PositionUtils.toClassDeclarationRange(psiClass, context.getUtils()); + Diagnostic d = context.createDiagnostic(context.getUri(), + createDiagnosticMessage(psiClass, context.getDocumentFormat()), + range, QuarkusConstants.QUARKUS_DIAGNOSTIC_SOURCE, + QuarkusBuildItemErrorCode.InvalidModifierBuildItem, + DiagnosticSeverity.Error + ); + diagnostics.add(d); + } + + private static String createDiagnosticMessage(PsiClass classType, DocumentFormat documentFormat) { + String quote = DocumentFormat.Markdown.equals(documentFormat)?"`":"'"; + return String.format(INVALID_MODIFIER, classType.getQualifiedName(), quote); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index aefda9165..cc3157114 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -417,6 +417,7 @@ + diff --git a/src/test/java/com/redhat/devtools/intellij/quarkus/validation/BuildItemDiagnosticsTest.java b/src/test/java/com/redhat/devtools/intellij/quarkus/validation/BuildItemDiagnosticsTest.java new file mode 100644 index 000000000..c6767a489 --- /dev/null +++ b/src/test/java/com/redhat/devtools/intellij/quarkus/validation/BuildItemDiagnosticsTest.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.quarkus.validation; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.redhat.devtools.intellij.MavenModuleImportingTestCase; +import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.IPsiUtils; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.health.java.MicroProfileHealthErrorCode; +import com.redhat.devtools.intellij.quarkus.QuarkusConstants; +import com.redhat.devtools.intellij.quarkus.psi.internal.builditems.QuarkusBuildItemErrorCode; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4mp.commons.DocumentFormat; +import org.eclipse.lsp4mp.commons.MicroProfileJavaDiagnosticsParams; + +import java.io.File; +import java.util.Arrays; + +import static com.redhat.devtools.intellij.lsp4mp4ij.psi.core.MicroProfileForJavaAssert.*; + +public class BuildItemDiagnosticsTest extends MavenModuleImportingTestCase { + + public void testBuildItemClassifier() throws Exception { + Module module = createMavenModule(new File("projects/quarkus/projects/maven/quarkus-builditems")); + + { // Bad BuildItem shows error + MicroProfileJavaDiagnosticsParams diagnosticsParams = new MicroProfileJavaDiagnosticsParams(); + String uri = LSPIJUtils.toUri(module).resolve("src/main/java/org/acme/builditems/BadBuildItem.java").toASCIIString(); + diagnosticsParams.setUris(Arrays.asList(uri)); + diagnosticsParams.setDocumentFormat(DocumentFormat.Markdown); + + Diagnostic d = d(4, 0, 49, + "BuildItem class `org.acme.builditems.BadBuildItem` must either be declared final or abstract", + DiagnosticSeverity.Error, QuarkusConstants.QUARKUS_DIAGNOSTIC_SOURCE, + QuarkusBuildItemErrorCode.InvalidModifierBuildItem); + assertJavaDiagnostics(diagnosticsParams, PsiUtilsLSImpl.getInstance(myProject), d); + } + + + { // Good BuildItem shows no error + MicroProfileJavaDiagnosticsParams diagnosticsParams = new MicroProfileJavaDiagnosticsParams(); + String uri = LSPIJUtils.toUri(module).resolve("src/main/java/org/acme/builditems/GoodBuildItem.java").toASCIIString(); + diagnosticsParams.setUris(Arrays.asList(uri)); + diagnosticsParams.setDocumentFormat(DocumentFormat.Markdown); + assertJavaDiagnostics(diagnosticsParams, PsiUtilsLSImpl.getInstance(myProject)); + } + + } +}