From 2277abb40c5a2318f24d00d999ddfddcba952533 Mon Sep 17 00:00:00 2001 From: Rejoy Date: Mon, 8 Jun 2026 09:58:09 +0530 Subject: [PATCH 1/9] Initial commit --- .../lsp4ij/cdi/ManagedBeanConstants.java | 9 +++++++++ .../cdi/ManagedBeanDiagnosticsCollector.java | 14 ++++++++++++++ .../lsp4jakarta/messages/messages.properties | 1 + 3 files changed, 24 insertions(+) diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java index 1dea29280..783c0a840 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java @@ -48,6 +48,7 @@ public class ManagedBeanConstants { public static final String DIAGNOSTIC_CODE_INTERCEPTOR_DECORATOR_OBSERVER = "InvalidInterceptorOrDecoratorWithObserverMethod"; public static final String DIAGNOSTIC_CODE_DEPENDENT_CONDITIONAL_OBSERVER = "InvalidDependentScopeWithConditionalObserver"; public static final String DIAGNOSTIC_MULTIPLE_OBSERVER_PARAMS = "InvalidMultipleObserverParams"; + public static final String DIAGNOSTIC_CODE_INTERCEPTOR_DECORATOR_ILLEGAL_SCOPE = "InvalidInterceptorOrDecorator"; public static final String DIAGNOSTIC_CODE_REDUNDANT_DISPOSES = "RemoveExtraDisposes"; //Added as part of fix that adds two quick fixes which are mutually exclusive issue #540 public static final String[] INVALID_DISPOSER_FQ_PARAMS = { DISPOSES_FQ_NAME }; @@ -63,5 +64,13 @@ public class ManagedBeanConstants { "jakarta.enterprise.context.SessionScoped", "jakarta.enterprise.context.NormalScope", "jakarta.Interceptor", "jakarta.Decorator", "jakarta.enterprise.inject.Stereotype")); + // Scopes that are invalid for interceptors and decorators (they must use @Dependent only) + public static final String[] INVALID_INTERCEPTOR_DECORATOR_SCOPES = { + "jakarta.enterprise.context.ApplicationScoped", + "jakarta.enterprise.context.SessionScoped", + "jakarta.enterprise.context.ConversationScoped", + "jakarta.enterprise.context.RequestScoped" + }; + public static final Set INVALID_OBSERVES_OBSERVES_ASYNC_CONFLICTED_PARAMS = Set.of(OBSERVES_FQ_NAME, OBSERVES_ASYNC_FQ_NAME); } \ No newline at end of file diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java index d2f5a3bce..6ecba4c80 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java @@ -302,6 +302,20 @@ else if (INJECT_FQ_NAME.equals(annotation)) DIAGNOSTIC_CODE_SCOPEDECL, new Gson().toJsonTree(managedBeanAnnotations), DiagnosticSeverity.Error)); } + + // Interceptors and decorators must not have normal scopes (ApplicationScoped, SessionScoped, etc.) + // They should only use @Dependent scope + if (interceptorOrDecorator) { + List foundInvalidScopes = getMatchedJavaElementNames(type, + Stream.of(typeAnnotations).map(PsiAnnotation::getQualifiedName).toArray(String[]::new), + INVALID_INTERCEPTOR_DECORATOR_SCOPES); + if (!foundInvalidScopes.isEmpty()) { + diagnostics.add(createDiagnostic(type, unit, + Messages.getMessage("InterceptorOrDecoratorWithIllegalScope"), + DIAGNOSTIC_CODE_INTERCEPTOR_DECORATOR_ILLEGAL_SCOPE, null, + DiagnosticSeverity.Error)); + } + } } /* diff --git a/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties b/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties index fe4503500..5fa9c0264 100644 --- a/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties +++ b/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties @@ -125,6 +125,7 @@ ManagedBeanDependentScopeConditionalObserver = Beans with scope @Dependent may n SingletonSessionBeanInvalidScope = A singleton session bean must be annotated with either @ApplicationScoped or @Dependent. ManagedBeanMultipleObserverParams = Parameters {0} are annotated with @Observes or @ObservesAsync, but a method cannot contain more than one such parameter. StatelessSessionBeanWithIllegalScope = A stateless session bean belongs to the @Dependent scope. Any other scope is invalid. +InterceptorOrDecoratorWithIllegalScope = Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid. # ManagedBeanNoArgConstructorQuickFix AddProtectedConstructor = Add a no-arg protected constructor to this class From fbdf0c45de5998827a86c3ab90d1a05ea0a6dabf Mon Sep 17 00:00:00 2001 From: Rejoy Date: Mon, 8 Jun 2026 09:58:22 +0530 Subject: [PATCH 2/9] adding quickfix --- src/main/resources/META-INF/plugin.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5f0981655..0bb9b40d4 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -574,6 +574,10 @@ group="jakarta" targetDiagnostic="jakarta-cdi#InvalidSingletonSessionBeanScope" implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.cdi.ManagedBeanQuickFix"/> + From e75c89e23d76225d275225dff35c8de3e8a94a74 Mon Sep 17 00:00:00 2001 From: Rejoy Date: Mon, 8 Jun 2026 09:58:48 +0530 Subject: [PATCH 3/9] tests --- .../cdi/InterceptorDecoratorScopesTest.java | 138 ++++++++++++++++++ .../cdi/InterceptorDecoratorScopes.java | 49 +++++++ 2 files changed, 187 insertions(+) create mode 100644 src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java create mode 100644 src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java diff --git a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java new file mode 100644 index 000000000..3ed0950e3 --- /dev/null +++ b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package io.openliberty.tools.intellij.lsp4jakarta.it.cdi; + +import com.google.gson.JsonArray; +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 io.openliberty.tools.intellij.lsp4jakarta.it.core.BaseJakartaTest; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.utils.IPsiUtils; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4jakarta.commons.JakartaJavaCodeActionParams; +import org.eclipse.lsp4jakarta.commons.JakartaJavaDiagnosticsParams; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.util.Arrays; + +import static io.openliberty.tools.intellij.lsp4jakarta.it.core.JakartaForJavaAssert.*; + +/** + * Tests for CDI Interceptor and Decorator scope validation. + */ +@RunWith(JUnit4.class) +public class InterceptorDecoratorScopesTest extends BaseJakartaTest { + + @Test + public void interceptorDecoratorScopes() throws Exception { + Module module = createMavenModule(new File("src/test/resources/projects/maven/jakarta-sample")); + IPsiUtils utils = PsiUtilsLSImpl.getInstance(getProject()); + + VirtualFile javaFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(ModuleUtilCore.getModuleDirPath(module) + + "/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java"); + String uri = VfsUtilCore.virtualToIoFile(javaFile).toURI().toString(); + + JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams(); + diagnosticsParams.setUris(Arrays.asList(uri)); + + // Invalid: @Interceptor with @ApplicationScoped + Diagnostic interceptorWithAppScoped = d(12, 13, 39, + "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + + // Invalid: @Decorator with @RequestScoped + Diagnostic decoratorWithReqScoped = d(18, 6, 39, + "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + + // Invalid: @Interceptor with @Dependent and @SessionScoped (has multiple scopes) + JsonArray data = new JsonArray(); + data.add("jakarta.enterprise.context.Dependent"); + data.add("jakarta.enterprise.context.SessionScoped"); + Diagnostic interceptorMultipleScopesDecl = d(37, 6, 42, + "Scope type annotations must be specified by a managed bean class at most once.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", data); + + // Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope) + Diagnostic interceptorWithMultipleScopes = d(37, 6, 42, + "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + + assertJavaDiagnostics(diagnosticsParams, utils, interceptorMultipleScopesDecl, interceptorWithMultipleScopes, decoratorWithReqScoped, interceptorWithAppScoped); + + // Test quick fix for @Interceptor with @ApplicationScoped - replace with @Dependent + JakartaJavaCodeActionParams interceptorAppScopedParams = createCodeActionParams(uri, interceptorWithAppScoped); + String newText1 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.interceptor.Interceptor;\n" + + "import jakarta.decorator.Decorator;\nimport jakarta.enterprise.context.RequestScoped;\n" + + "import jakarta.enterprise.context.SessionScoped;\nimport jakarta.enterprise.context.Dependent;\n\n" + + "// Invalid: @Interceptor with @ApplicationScoped\n@Dependent\n@Interceptor\npublic class InterceptorDecoratorScopes {\n}\n\n" + + "// Invalid: @Decorator with @RequestScoped\n@Decorator\n@RequestScoped\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + + "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + + "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + + "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + + "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + + "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + + "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; + TextEdit interceptorAppScopedEdit = te(0, 0, 49, 0, newText1); + CodeAction interceptorAppScopedAction = ca(uri, "Replace current scope with @Dependent", + interceptorWithAppScoped, interceptorAppScopedEdit); + assertJavaCodeAction(interceptorAppScopedParams, utils, interceptorAppScopedAction); + + // Test quick fix for @Decorator with @RequestScoped - replace with @Dependent + JakartaJavaCodeActionParams decoratorReqScopedParams = createCodeActionParams(uri, decoratorWithReqScoped); + String newText2 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.interceptor.Interceptor;\n" + + "import jakarta.decorator.Decorator;\nimport jakarta.enterprise.context.ApplicationScoped;\n" + + "import jakarta.enterprise.context.SessionScoped;\nimport jakarta.enterprise.context.Dependent;\n\n" + + "// Invalid: @Interceptor with @ApplicationScoped\n@Interceptor\n@ApplicationScoped\npublic class InterceptorDecoratorScopes {\n}\n\n" + + "// Invalid: @Decorator with @RequestScoped\n@Dependent\n@Decorator\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + + "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + + "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + + "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + + "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + + "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + + "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; + TextEdit decoratorReqScopedEdit = te(0, 0, 49, 0, newText2); + CodeAction decoratorReqScopedAction = ca(uri, "Replace current scope with @Dependent", decoratorWithReqScoped, + decoratorReqScopedEdit); + assertJavaCodeAction(decoratorReqScopedParams, utils, decoratorReqScopedAction); + + // Test quick fix for @Interceptor with multiple scopes - replace with @Dependent + JakartaJavaCodeActionParams interceptorMultiScopesParams = createCodeActionParams(uri, + interceptorWithMultipleScopes); + String newText3 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.enterprise.context.Dependent;\n" + + "import jakarta.interceptor.Interceptor;\nimport jakarta.decorator.Decorator;\n" + + "import jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.context.RequestScoped;\n\n" + + "// Invalid: @Interceptor with @ApplicationScoped\n@Interceptor\n@ApplicationScoped\npublic class InterceptorDecoratorScopes {\n}\n\n" + + "// Invalid: @Decorator with @RequestScoped\n@Decorator\n@RequestScoped\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + + "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + + "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + + "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Dependent\n@Interceptor\n" + + "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + + "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + + "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; + TextEdit interceptorMultiScopesEdit = te(0, 0, 49, 0, newText3); + CodeAction interceptorMultiScopesAction = ca(uri, "Replace current scope with @Dependent", + interceptorWithMultipleScopes, interceptorMultiScopesEdit); + assertJavaCodeAction(interceptorMultiScopesParams, utils, interceptorMultiScopesAction); + } +} diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java new file mode 100644 index 000000000..c2792966a --- /dev/null +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java @@ -0,0 +1,49 @@ +package io.openliberty.sample.jakarta.cdi; + +import jakarta.interceptor.Interceptor; +import jakarta.decorator.Decorator; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.context.SessionScoped; +import jakarta.enterprise.context.Dependent; + +// Invalid: @Interceptor with @ApplicationScoped +@Interceptor +@ApplicationScoped +public class InterceptorDecoratorScopes { +} + +// Invalid: @Decorator with @RequestScoped +@Decorator +@RequestScoped +class InvalidDecoratorWithRequestScoped { +} + +// Valid: @Interceptor with @Dependent +@Interceptor +@Dependent +class ValidInterceptorWithDependent { +} + +// Valid: @Decorator with @Dependent +@Decorator +@Dependent +class ValidDecoratorWithDependent { +} + +// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope) +@Interceptor +@Dependent +@SessionScoped +class InvalidInterceptorWithMultipleScopes { +} + +// Valid: @Decorator with no scope annotation (defaults to @Dependent) +@Decorator +class ValidDecoratorWithNoScope { +} + +// Valid: @Interceptor with no scope annotation (defaults to @Dependent) +@Interceptor +class ValidInterceptorWithNoScope { +} From 95a11ab92532af5eb73f34200c042bdfc62124a1 Mon Sep 17 00:00:00 2001 From: Rejoy Date: Tue, 9 Jun 2026 10:33:22 +0530 Subject: [PATCH 4/9] added multiple scope for decorator --- .../cdi/InterceptorDecoratorScopesTest.java | 55 ++++++++++++++++--- .../cdi/InterceptorDecoratorScopes.java | 7 +++ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java index 3ed0950e3..db7801071 100644 --- a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java +++ b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java @@ -78,7 +78,21 @@ public void interceptorDecoratorScopes() throws Exception { "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); - assertJavaDiagnostics(diagnosticsParams, utils, interceptorMultipleScopesDecl, interceptorWithMultipleScopes, decoratorWithReqScoped, interceptorWithAppScoped); + // Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple scopes) + JsonArray decoratorData = new JsonArray(); + decoratorData.add("jakarta.enterprise.context.Dependent"); + decoratorData.add("jakarta.enterprise.context.ApplicationScoped"); + Diagnostic decoratorMultipleScopesDecl = d(44, 6, 40, + "Scope type annotations must be specified by a managed bean class at most once.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", decoratorData); + + // Invalid: @Decorator with @Dependent and @ApplicationScoped (has invalid scope) + Diagnostic decoratorWithMultipleScopes = d(44, 6, 40, + "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + + assertJavaDiagnostics(diagnosticsParams, utils, interceptorMultipleScopesDecl, interceptorWithMultipleScopes, + decoratorMultipleScopesDecl, decoratorWithMultipleScopes, decoratorWithReqScoped, interceptorWithAppScoped); // Test quick fix for @Interceptor with @ApplicationScoped - replace with @Dependent JakartaJavaCodeActionParams interceptorAppScopedParams = createCodeActionParams(uri, interceptorWithAppScoped); @@ -90,10 +104,12 @@ public void interceptorDecoratorScopes() throws Exception { "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + - "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + + "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + + "@Decorator\n@Dependent\n@ApplicationScoped\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + + "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; - TextEdit interceptorAppScopedEdit = te(0, 0, 49, 0, newText1); + TextEdit interceptorAppScopedEdit = te(0, 0, 56, 0, newText1); CodeAction interceptorAppScopedAction = ca(uri, "Replace current scope with @Dependent", interceptorWithAppScoped, interceptorAppScopedEdit); assertJavaCodeAction(interceptorAppScopedParams, utils, interceptorAppScopedAction); @@ -108,10 +124,12 @@ public void interceptorDecoratorScopes() throws Exception { "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + - "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + + "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + + "@Decorator\n@Dependent\n@ApplicationScoped\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + + "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; - TextEdit decoratorReqScopedEdit = te(0, 0, 49, 0, newText2); + TextEdit decoratorReqScopedEdit = te(0, 0, 56, 0, newText2); CodeAction decoratorReqScopedAction = ca(uri, "Replace current scope with @Dependent", decoratorWithReqScoped, decoratorReqScopedEdit); assertJavaCodeAction(decoratorReqScopedParams, utils, decoratorReqScopedAction); @@ -127,12 +145,35 @@ public void interceptorDecoratorScopes() throws Exception { "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Dependent\n@Interceptor\n" + - "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + + "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + + "@Decorator\n@Dependent\n@ApplicationScoped\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + + "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; - TextEdit interceptorMultiScopesEdit = te(0, 0, 49, 0, newText3); + TextEdit interceptorMultiScopesEdit = te(0, 0, 56, 0, newText3); CodeAction interceptorMultiScopesAction = ca(uri, "Replace current scope with @Dependent", interceptorWithMultipleScopes, interceptorMultiScopesEdit); assertJavaCodeAction(interceptorMultiScopesParams, utils, interceptorMultiScopesAction); + + // Test quick fix for @Decorator with multiple scopes - replace with @Dependent + JakartaJavaCodeActionParams decoratorMultiScopesParams = createCodeActionParams(uri, + decoratorWithMultipleScopes); + String newText4 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.enterprise.context.Dependent;\n" + + "import jakarta.interceptor.Interceptor;\nimport jakarta.decorator.Decorator;\n" + + "import jakarta.enterprise.context.RequestScoped;\nimport jakarta.enterprise.context.SessionScoped;\n\n" + + "// Invalid: @Interceptor with @ApplicationScoped\n@Interceptor\n@ApplicationScoped\npublic class InterceptorDecoratorScopes {\n}\n\n" + + "// Invalid: @Decorator with @RequestScoped\n@Decorator\n@RequestScoped\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + + "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + + "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + + "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + + "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + + "@Dependent\n@Decorator\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + + "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + + "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + + "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; + TextEdit decoratorMultiScopesEdit = te(0, 0, 56, 0, newText4); + CodeAction decoratorMultiScopesAction = ca(uri, "Replace current scope with @Dependent", + decoratorWithMultipleScopes, decoratorMultiScopesEdit); + assertJavaCodeAction(decoratorMultiScopesParams, utils, decoratorMultiScopesAction); } } diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java index c2792966a..4d3d93871 100644 --- a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java @@ -38,6 +38,13 @@ class ValidDecoratorWithDependent { class InvalidInterceptorWithMultipleScopes { } +// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes) +@Decorator +@Dependent +@ApplicationScoped +class InvalidDecoratorWithMultipleScopes { +} + // Valid: @Decorator with no scope annotation (defaults to @Dependent) @Decorator class ValidDecoratorWithNoScope { From 5b0643427378e08822b97c8aa975690ade3b7f5d Mon Sep 17 00:00:00 2001 From: Rejoy Date: Thu, 11 Jun 2026 19:57:09 +0530 Subject: [PATCH 5/9] pr coments --- .../lsp4ij/cdi/ManagedBeanConstants.java | 1 + .../cdi/ManagedBeanDiagnosticsCollector.java | 73 +++++++++- src/main/resources/META-INF/plugin.xml | 4 - .../cdi/InterceptorDecoratorScopesTest.java | 134 ++++++------------ .../sample/jakarta/cdi/CustomNormalScope.java | 19 +++ .../cdi/InterceptorDecoratorCustomScopes.java | 45 ++++++ 6 files changed, 180 insertions(+), 96 deletions(-) create mode 100644 src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/CustomNormalScope.java create mode 100644 src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java index 783c0a840..05d101a64 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanConstants.java @@ -30,6 +30,7 @@ public class ManagedBeanConstants { public static final String SINGLETON_FQ_NAME = "jakarta.ejb.Singleton"; public static final String APPLICATION_SCOPED_FQ_NAME = "jakarta.enterprise.context.ApplicationScoped"; public static final String STATELESS_FQ_NAME = "jakarta.ejb.Stateless"; + public static final String NORMAL_SCOPE_FQ_NAME = "jakarta.enterprise.context.NormalScope"; public static final String DIAGNOSTIC_SOURCE = "jakarta-cdi"; public static final String DIAGNOSTIC_CODE = "InvalidManagedBeanAnnotation"; diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java index 6ecba4c80..6f429299d 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java @@ -306,13 +306,12 @@ DIAGNOSTIC_CODE_SCOPEDECL, new Gson().toJsonTree(managedBeanAnnotations), // Interceptors and decorators must not have normal scopes (ApplicationScoped, SessionScoped, etc.) // They should only use @Dependent scope if (interceptorOrDecorator) { - List foundInvalidScopes = getMatchedJavaElementNames(type, - Stream.of(typeAnnotations).map(PsiAnnotation::getQualifiedName).toArray(String[]::new), - INVALID_INTERCEPTOR_DECORATOR_SCOPES); + List foundInvalidScopes = validateInterceptorDecoratorScopes(type, typeAnnotations); if (!foundInvalidScopes.isEmpty()) { diagnostics.add(createDiagnostic(type, unit, Messages.getMessage("InterceptorOrDecoratorWithIllegalScope"), - DIAGNOSTIC_CODE_INTERCEPTOR_DECORATOR_ILLEGAL_SCOPE, null, + DIAGNOSTIC_CODE_INTERCEPTOR_DECORATOR_ILLEGAL_SCOPE, + new Gson().toJsonTree(foundInvalidScopes), DiagnosticSeverity.Error)); } } @@ -524,4 +523,70 @@ private boolean hasConditionalObserverAnnotation(PsiClass type, PsiMethod method .flatMap(param -> Stream.of(param.getAnnotations())) .anyMatch(annotation -> isConditionalObserver(type, annotation)); } + + /** + * validateInterceptorDecoratorScopes + * Validates that interceptors and decorators do not declare invalid scope annotations. + * Interceptors and decorators must not have normal scopes (ApplicationScoped, SessionScoped, etc.) + * and should only use @Dependent scope. Detects both built-in CDI scopes and custom @NormalScope annotations. + * + * @param type the Java type being validated + * @param typeAnnotations the annotations on the type + * @return list of invalid scope annotation fully qualified names + */ + private List validateInterceptorDecoratorScopes(PsiClass type, PsiAnnotation[] typeAnnotations) { + List foundInvalidScopes = new ArrayList<>(); + + // Check each annotation to see if it's an invalid scope + for (PsiAnnotation annotation : typeAnnotations) { + String annotationName = annotation.getQualifiedName(); + if (annotationName == null) { + continue; + } + + // Skip @Interceptor, @Decorator, and @Dependent annotations - these are not scopes we're checking + String matchedSkip = getMatchedJavaElementName(type, annotationName, + new String[]{ + INTERCEPTOR_FQ_NAME, + DECORATOR_FQ_NAME, + DEPENDENT_FQ_NAME + }); + if (matchedSkip != null) { + continue; + } + + // Check if it's a built-in invalid scope + String matchedBuiltInScope = getMatchedJavaElementName(type, annotationName, + INVALID_INTERCEPTOR_DECORATOR_SCOPES); + if (matchedBuiltInScope != null) { + foundInvalidScopes.add(matchedBuiltInScope); + } else { + // Check if it's a custom @NormalScope annotation + try { + PsiClass annotationType = JavaPsiFacade.getInstance(type.getProject()) + .findClass(annotationName, type.getResolveScope()); + if (annotationType != null && isAnnotatedWith(annotationType, NORMAL_SCOPE_FQ_NAME)) { + foundInvalidScopes.add(annotationName); + } + } catch (Exception e) { + // Ignore exceptions during annotation type resolution + } + } + } + + return foundInvalidScopes; + } + + /** + * isAnnotatedWith + * Checks if a class is annotated with a specific annotation. + * + * @param psiClass the class to check + * @param annotationFQN the fully qualified name of the annotation + * @return true if the class is annotated with the specified annotation + */ + private boolean isAnnotatedWith(PsiClass psiClass, String annotationFQN) { + return Stream.of(psiClass.getAnnotations()) + .anyMatch(annotation -> annotationFQN.equals(annotation.getQualifiedName())); + } } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 0bb9b40d4..5f0981655 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -574,10 +574,6 @@ group="jakarta" targetDiagnostic="jakarta-cdi#InvalidSingletonSessionBeanScope" implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.cdi.ManagedBeanQuickFix"/> - diff --git a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java index db7801071..2ebc8f89d 100644 --- a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java +++ b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java @@ -22,11 +22,8 @@ import io.openliberty.tools.intellij.lsp4jakarta.it.core.BaseJakartaTest; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.utils.IPsiUtils; import io.openliberty.tools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl; -import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; -import org.eclipse.lsp4j.TextEdit; -import org.eclipse.lsp4jakarta.commons.JakartaJavaCodeActionParams; import org.eclipse.lsp4jakarta.commons.JakartaJavaDiagnosticsParams; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,14 +53,18 @@ public void interceptorDecoratorScopes() throws Exception { diagnosticsParams.setUris(Arrays.asList(uri)); // Invalid: @Interceptor with @ApplicationScoped + JsonArray interceptorAppScopedData = new JsonArray(); + interceptorAppScopedData.add("jakarta.enterprise.context.ApplicationScoped"); Diagnostic interceptorWithAppScoped = d(12, 13, 39, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", interceptorAppScopedData); // Invalid: @Decorator with @RequestScoped + JsonArray decoratorReqScopedData = new JsonArray(); + decoratorReqScopedData.add("jakarta.enterprise.context.RequestScoped"); Diagnostic decoratorWithReqScoped = d(18, 6, 39, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", decoratorReqScopedData); // Invalid: @Interceptor with @Dependent and @SessionScoped (has multiple scopes) JsonArray data = new JsonArray(); @@ -74,9 +75,11 @@ public void interceptorDecoratorScopes() throws Exception { DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", data); // Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope) + JsonArray interceptorInvalidScopeData = new JsonArray(); + interceptorInvalidScopeData.add("jakarta.enterprise.context.SessionScoped"); Diagnostic interceptorWithMultipleScopes = d(37, 6, 42, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", interceptorInvalidScopeData); // Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple scopes) JsonArray decoratorData = new JsonArray(); @@ -87,93 +90,48 @@ public void interceptorDecoratorScopes() throws Exception { DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", decoratorData); // Invalid: @Decorator with @Dependent and @ApplicationScoped (has invalid scope) + JsonArray decoratorInvalidScopeData = new JsonArray(); + decoratorInvalidScopeData.add("jakarta.enterprise.context.ApplicationScoped"); Diagnostic decoratorWithMultipleScopes = d(44, 6, 40, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator"); + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", decoratorInvalidScopeData); assertJavaDiagnostics(diagnosticsParams, utils, interceptorMultipleScopesDecl, interceptorWithMultipleScopes, decoratorMultipleScopesDecl, decoratorWithMultipleScopes, decoratorWithReqScoped, interceptorWithAppScoped); + } + + @Test + public void interceptorDecoratorCustomScopes() throws Exception { + Module module = createMavenModule(new File("src/test/resources/projects/maven/jakarta-sample")); + IPsiUtils utils = PsiUtilsLSImpl.getInstance(getProject()); + + VirtualFile javaFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(ModuleUtilCore.getModuleDirPath(module) + + "/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java"); + String uri = VfsUtilCore.virtualToIoFile(javaFile).toURI().toString(); + + JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams(); + diagnosticsParams.setUris(Arrays.asList(uri)); + + // Invalid: @Interceptor with mixed scopes (built-in and custom) - Invalid scope diagnostic (line 36) + // Note: Classes with only @CustomNormalScope are not being detected in tests due to annotation resolution timing issues + // Only testing mixed scope cases (built-in + custom) which are reliably detected + JsonArray interceptorMixedScopesInvalid = new JsonArray(); + interceptorMixedScopesInvalid.add("jakarta.enterprise.context.ApplicationScoped"); + interceptorMixedScopesInvalid.add("io.openliberty.sample.jakarta.cdi.CustomNormalScope"); + Diagnostic interceptorWithMixedScopes = d(35, 6, 39, + "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", interceptorMixedScopesInvalid); + + // Invalid: @Decorator with mixed scopes (built-in and custom) - Invalid scope diagnostic (line 43) + JsonArray decoratorMixedScopesInvalid = new JsonArray(); + decoratorMixedScopesInvalid.add("jakarta.enterprise.context.ApplicationScoped"); + decoratorMixedScopesInvalid.add("io.openliberty.sample.jakarta.cdi.CustomNormalScope"); + Diagnostic decoratorWithMixedScopes = d(42, 6, 37, + "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", decoratorMixedScopesInvalid); - // Test quick fix for @Interceptor with @ApplicationScoped - replace with @Dependent - JakartaJavaCodeActionParams interceptorAppScopedParams = createCodeActionParams(uri, interceptorWithAppScoped); - String newText1 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.interceptor.Interceptor;\n" + - "import jakarta.decorator.Decorator;\nimport jakarta.enterprise.context.RequestScoped;\n" + - "import jakarta.enterprise.context.SessionScoped;\nimport jakarta.enterprise.context.Dependent;\n\n" + - "// Invalid: @Interceptor with @ApplicationScoped\n@Dependent\n@Interceptor\npublic class InterceptorDecoratorScopes {\n}\n\n" + - "// Invalid: @Decorator with @RequestScoped\n@Decorator\n@RequestScoped\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + - "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + - "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + - "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + - "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + - "@Decorator\n@Dependent\n@ApplicationScoped\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + - "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + - "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + - "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; - TextEdit interceptorAppScopedEdit = te(0, 0, 56, 0, newText1); - CodeAction interceptorAppScopedAction = ca(uri, "Replace current scope with @Dependent", - interceptorWithAppScoped, interceptorAppScopedEdit); - assertJavaCodeAction(interceptorAppScopedParams, utils, interceptorAppScopedAction); - - // Test quick fix for @Decorator with @RequestScoped - replace with @Dependent - JakartaJavaCodeActionParams decoratorReqScopedParams = createCodeActionParams(uri, decoratorWithReqScoped); - String newText2 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.interceptor.Interceptor;\n" + - "import jakarta.decorator.Decorator;\nimport jakarta.enterprise.context.ApplicationScoped;\n" + - "import jakarta.enterprise.context.SessionScoped;\nimport jakarta.enterprise.context.Dependent;\n\n" + - "// Invalid: @Interceptor with @ApplicationScoped\n@Interceptor\n@ApplicationScoped\npublic class InterceptorDecoratorScopes {\n}\n\n" + - "// Invalid: @Decorator with @RequestScoped\n@Dependent\n@Decorator\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + - "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + - "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + - "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + - "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + - "@Decorator\n@Dependent\n@ApplicationScoped\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + - "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + - "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + - "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; - TextEdit decoratorReqScopedEdit = te(0, 0, 56, 0, newText2); - CodeAction decoratorReqScopedAction = ca(uri, "Replace current scope with @Dependent", decoratorWithReqScoped, - decoratorReqScopedEdit); - assertJavaCodeAction(decoratorReqScopedParams, utils, decoratorReqScopedAction); - - // Test quick fix for @Interceptor with multiple scopes - replace with @Dependent - JakartaJavaCodeActionParams interceptorMultiScopesParams = createCodeActionParams(uri, - interceptorWithMultipleScopes); - String newText3 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.enterprise.context.Dependent;\n" + - "import jakarta.interceptor.Interceptor;\nimport jakarta.decorator.Decorator;\n" + - "import jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.context.RequestScoped;\n\n" + - "// Invalid: @Interceptor with @ApplicationScoped\n@Interceptor\n@ApplicationScoped\npublic class InterceptorDecoratorScopes {\n}\n\n" + - "// Invalid: @Decorator with @RequestScoped\n@Decorator\n@RequestScoped\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + - "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + - "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + - "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Dependent\n@Interceptor\n" + - "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + - "@Decorator\n@Dependent\n@ApplicationScoped\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + - "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + - "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + - "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; - TextEdit interceptorMultiScopesEdit = te(0, 0, 56, 0, newText3); - CodeAction interceptorMultiScopesAction = ca(uri, "Replace current scope with @Dependent", - interceptorWithMultipleScopes, interceptorMultiScopesEdit); - assertJavaCodeAction(interceptorMultiScopesParams, utils, interceptorMultiScopesAction); - - // Test quick fix for @Decorator with multiple scopes - replace with @Dependent - JakartaJavaCodeActionParams decoratorMultiScopesParams = createCodeActionParams(uri, - decoratorWithMultipleScopes); - String newText4 = "package io.openliberty.sample.jakarta.cdi;\n\nimport jakarta.enterprise.context.Dependent;\n" + - "import jakarta.interceptor.Interceptor;\nimport jakarta.decorator.Decorator;\n" + - "import jakarta.enterprise.context.RequestScoped;\nimport jakarta.enterprise.context.SessionScoped;\n\n" + - "// Invalid: @Interceptor with @ApplicationScoped\n@Interceptor\n@ApplicationScoped\npublic class InterceptorDecoratorScopes {\n}\n\n" + - "// Invalid: @Decorator with @RequestScoped\n@Decorator\n@RequestScoped\nclass InvalidDecoratorWithRequestScoped {\n}\n\n" + - "// Valid: @Interceptor with @Dependent\n@Interceptor\n@Dependent\nclass ValidInterceptorWithDependent {\n}\n\n" + - "// Valid: @Decorator with @Dependent\n@Decorator\n@Dependent\nclass ValidDecoratorWithDependent {\n}\n\n" + - "// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope)\n@Interceptor\n@Dependent\n@SessionScoped\n" + - "class InvalidInterceptorWithMultipleScopes {\n}\n\n// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes)\n" + - "@Dependent\n@Decorator\nclass InvalidDecoratorWithMultipleScopes {\n}\n\n" + - "// Valid: @Decorator with no scope annotation (defaults to @Dependent)\n" + - "@Decorator\nclass ValidDecoratorWithNoScope {\n}\n\n// Valid: @Interceptor with no scope annotation (defaults to @Dependent)\n" + - "@Interceptor\nclass ValidInterceptorWithNoScope {\n}\n"; - TextEdit decoratorMultiScopesEdit = te(0, 0, 56, 0, newText4); - CodeAction decoratorMultiScopesAction = ca(uri, "Replace current scope with @Dependent", - decoratorWithMultipleScopes, decoratorMultiScopesEdit); - assertJavaCodeAction(decoratorMultiScopesParams, utils, decoratorMultiScopesAction); + assertJavaDiagnostics(diagnosticsParams, utils, + decoratorWithMixedScopes, + interceptorWithMixedScopes); } } diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/CustomNormalScope.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/CustomNormalScope.java new file mode 100644 index 000000000..50e503396 --- /dev/null +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/CustomNormalScope.java @@ -0,0 +1,19 @@ +package io.openliberty.sample.jakarta.cdi; + +import jakarta.enterprise.context.NormalScope; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Custom scope annotation meta-annotated with @NormalScope. + * This is used to test detection of custom normal scopes on interceptors and decorators. + */ +@NormalScope +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD}) +public @interface CustomNormalScope { +} + diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java new file mode 100644 index 000000000..ca8712b46 --- /dev/null +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java @@ -0,0 +1,45 @@ +package io.openliberty.sample.jakarta.cdi; + +import jakarta.interceptor.Interceptor; +import jakarta.decorator.Decorator; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; + +// Invalid: @Interceptor with custom @NormalScope +@Interceptor +@CustomNormalScope +public class InterceptorDecoratorCustomScopes { +} + +// Invalid: @Decorator with custom @NormalScope +@Decorator +@CustomNormalScope +class InvalidDecoratorWithCustomScope { +} + +// Valid: @Interceptor with @Dependent +@Interceptor +@Dependent +class ValidInterceptorWithDependent { +} + +// Valid: @Decorator with @Dependent +@Decorator +@Dependent +class ValidDecoratorWithDependent { +} + +// Invalid: @Interceptor with mixed scopes (built-in and custom) +@Interceptor +@ApplicationScoped +@CustomNormalScope +class InvalidInterceptorWithMixedScopes { +} + +// Invalid: @Decorator with mixed scopes (built-in and custom) +@Decorator +@ApplicationScoped +@CustomNormalScope +class InvalidDecoratorWithMixedScopes { +} + From d518e4a529e023c29e93ab2cb9332a4cfe2ce57a Mon Sep 17 00:00:00 2001 From: Rejoy Date: Thu, 11 Jun 2026 20:13:33 +0530 Subject: [PATCH 6/9] Update ManagedBeanDiagnosticsCollector.java --- .../cdi/ManagedBeanDiagnosticsCollector.java | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java index 6f429299d..c3a6adf9f 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java @@ -16,6 +16,7 @@ import java.util.*; import java.util.stream.Stream; +import com.intellij.codeInsight.AnnotationUtil; import com.intellij.psi.*; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.AbstractDiagnosticsCollector; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages; @@ -540,10 +541,6 @@ private List validateInterceptorDecoratorScopes(PsiClass type, PsiAnnota // Check each annotation to see if it's an invalid scope for (PsiAnnotation annotation : typeAnnotations) { String annotationName = annotation.getQualifiedName(); - if (annotationName == null) { - continue; - } - // Skip @Interceptor, @Decorator, and @Dependent annotations - these are not scopes we're checking String matchedSkip = getMatchedJavaElementName(type, annotationName, new String[]{ @@ -554,18 +551,17 @@ private List validateInterceptorDecoratorScopes(PsiClass type, PsiAnnota if (matchedSkip != null) { continue; } - // Check if it's a built-in invalid scope String matchedBuiltInScope = getMatchedJavaElementName(type, annotationName, INVALID_INTERCEPTOR_DECORATOR_SCOPES); if (matchedBuiltInScope != null) { foundInvalidScopes.add(matchedBuiltInScope); } else { - // Check if it's a custom @NormalScope annotation + // Check if it's a custom @NormalScope annotation using AnnotationUtil try { PsiClass annotationType = JavaPsiFacade.getInstance(type.getProject()) .findClass(annotationName, type.getResolveScope()); - if (annotationType != null && isAnnotatedWith(annotationType, NORMAL_SCOPE_FQ_NAME)) { + if (annotationType != null && AnnotationUtil.isAnnotated(annotationType, NORMAL_SCOPE_FQ_NAME, 0)) { foundInvalidScopes.add(annotationName); } } catch (Exception e) { @@ -573,20 +569,6 @@ private List validateInterceptorDecoratorScopes(PsiClass type, PsiAnnota } } } - return foundInvalidScopes; } - - /** - * isAnnotatedWith - * Checks if a class is annotated with a specific annotation. - * - * @param psiClass the class to check - * @param annotationFQN the fully qualified name of the annotation - * @return true if the class is annotated with the specified annotation - */ - private boolean isAnnotatedWith(PsiClass psiClass, String annotationFQN) { - return Stream.of(psiClass.getAnnotations()) - .anyMatch(annotation -> annotationFQN.equals(annotation.getQualifiedName())); - } } \ No newline at end of file From 82b1184ae40947895512a551732f78a9c8c31c19 Mon Sep 17 00:00:00 2001 From: Rejoy Date: Fri, 12 Jun 2026 10:18:09 +0530 Subject: [PATCH 7/9] refracted the code and test cases --- .../cdi/ManagedBeanDiagnosticsCollector.java | 26 ++- .../cdi/InterceptorDecoratorScopesTest.java | 149 ++++++++---------- .../cdi/InterceptorDecoratorCustomScopes.java | 45 ------ .../cdi/InterceptorDecoratorScopes.java | 97 +++++++++--- 4 files changed, 155 insertions(+), 162 deletions(-) delete mode 100644 src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java index c3a6adf9f..6fca8449c 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ManagedBeanDiagnosticsCollector.java @@ -303,19 +303,6 @@ else if (INJECT_FQ_NAME.equals(annotation)) DIAGNOSTIC_CODE_SCOPEDECL, new Gson().toJsonTree(managedBeanAnnotations), DiagnosticSeverity.Error)); } - - // Interceptors and decorators must not have normal scopes (ApplicationScoped, SessionScoped, etc.) - // They should only use @Dependent scope - if (interceptorOrDecorator) { - List foundInvalidScopes = validateInterceptorDecoratorScopes(type, typeAnnotations); - if (!foundInvalidScopes.isEmpty()) { - diagnostics.add(createDiagnostic(type, unit, - Messages.getMessage("InterceptorOrDecoratorWithIllegalScope"), - DIAGNOSTIC_CODE_INTERCEPTOR_DECORATOR_ILLEGAL_SCOPE, - new Gson().toJsonTree(foundInvalidScopes), - DiagnosticSeverity.Error)); - } - } } /* @@ -331,7 +318,18 @@ DIAGNOSTIC_CODE_SCOPEDECL, new Gson().toJsonTree(managedBeanAnnotations), */ invalidParamsCheck(unit, diagnostics, type, INJECT_FQ_NAME, ManagedBeanConstants.DIAGNOSTIC_CODE_INVALID_INJECT_PARAM); - + // Interceptors and decorators must not have normal scopes (ApplicationScoped, SessionScoped, etc.) + // They should only use @Dependent scope + if (interceptorOrDecorator) { + List foundInvalidScopes = validateInterceptorDecoratorScopes(type, typeAnnotations); + if (!foundInvalidScopes.isEmpty()) { + diagnostics.add(createDiagnostic(type, unit, + Messages.getMessage("InterceptorOrDecoratorWithIllegalScope"), + DIAGNOSTIC_CODE_INTERCEPTOR_DECORATOR_ILLEGAL_SCOPE, + new Gson().toJsonTree(foundInvalidScopes), + DiagnosticSeverity.Error)); + } + } if (isManagedBean) { /* * ========= Produces and Disposes, Observes, ObservesAsync Annotations Checks========= diff --git a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java index 2ebc8f89d..27e18ca2a 100644 --- a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java +++ b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java @@ -52,86 +52,77 @@ public void interceptorDecoratorScopes() throws Exception { JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams(); diagnosticsParams.setUris(Arrays.asList(uri)); - // Invalid: @Interceptor with @ApplicationScoped - JsonArray interceptorAppScopedData = new JsonArray(); - interceptorAppScopedData.add("jakarta.enterprise.context.ApplicationScoped"); - Diagnostic interceptorWithAppScoped = d(12, 13, 39, - "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", interceptorAppScopedData); - - // Invalid: @Decorator with @RequestScoped - JsonArray decoratorReqScopedData = new JsonArray(); - decoratorReqScopedData.add("jakarta.enterprise.context.RequestScoped"); - Diagnostic decoratorWithReqScoped = d(18, 6, 39, - "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", decoratorReqScopedData); - - // Invalid: @Interceptor with @Dependent and @SessionScoped (has multiple scopes) - JsonArray data = new JsonArray(); - data.add("jakarta.enterprise.context.Dependent"); - data.add("jakarta.enterprise.context.SessionScoped"); - Diagnostic interceptorMultipleScopesDecl = d(37, 6, 42, - "Scope type annotations must be specified by a managed bean class at most once.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", data); - - // Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope) - JsonArray interceptorInvalidScopeData = new JsonArray(); - interceptorInvalidScopeData.add("jakarta.enterprise.context.SessionScoped"); - Diagnostic interceptorWithMultipleScopes = d(37, 6, 42, - "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", interceptorInvalidScopeData); - - // Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple scopes) - JsonArray decoratorData = new JsonArray(); - decoratorData.add("jakarta.enterprise.context.Dependent"); - decoratorData.add("jakarta.enterprise.context.ApplicationScoped"); - Diagnostic decoratorMultipleScopesDecl = d(44, 6, 40, - "Scope type annotations must be specified by a managed bean class at most once.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", decoratorData); - - // Invalid: @Decorator with @Dependent and @ApplicationScoped (has invalid scope) - JsonArray decoratorInvalidScopeData = new JsonArray(); - decoratorInvalidScopeData.add("jakarta.enterprise.context.ApplicationScoped"); - Diagnostic decoratorWithMultipleScopes = d(44, 6, 40, - "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", decoratorInvalidScopeData); - - assertJavaDiagnostics(diagnosticsParams, utils, interceptorMultipleScopesDecl, interceptorWithMultipleScopes, - decoratorMultipleScopesDecl, decoratorWithMultipleScopes, decoratorWithReqScoped, interceptorWithAppScoped); + // Line 41: Invalid interceptor with @ApplicationScoped + Diagnostic interceptorAppScoped = d(41, 6, 38, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.ApplicationScoped")); + + // Line 47: Invalid interceptor with @SessionScoped + Diagnostic interceptorSessionScoped = d(47, 6, 34, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.SessionScoped")); + + // Line 54: Invalid interceptor with multiple scopes - InvalidScopeDecl + Diagnostic interceptorMultiScopeDecl = d(54, 6, 42, "Scope type annotations must be specified by a managed bean class at most once.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", + createJsonArray("jakarta.enterprise.context.SessionScoped", "jakarta.enterprise.context.ApplicationScoped")); + + // Line 54: Invalid interceptor with multiple scopes - InvalidInterceptorOrDecorator + Diagnostic interceptorMultiScope = d(54, 6, 42, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.ApplicationScoped", "jakarta.enterprise.context.SessionScoped")); + + // Line 62: Invalid decorator with @ApplicationScoped + Diagnostic decoratorAppScoped = d(62, 6, 36, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.ApplicationScoped")); + + // Line 68: Invalid decorator with @SessionScoped + Diagnostic decoratorSessionScoped = d(68, 6, 32, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.SessionScoped")); + + // Line 75: Invalid decorator with multiple scopes - InvalidScopeDecl + Diagnostic decoratorMultiScopeDecl = d(75, 6, 40, "Scope type annotations must be specified by a managed bean class at most once.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidScopeDecl", + createJsonArray("jakarta.enterprise.context.ConversationScoped", "jakarta.enterprise.context.RequestScoped")); + + // Line 75: Invalid decorator with multiple scopes - InvalidInterceptorOrDecorator + Diagnostic decoratorMultiScope = d(75, 6, 40, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.RequestScoped", "jakarta.enterprise.context.ConversationScoped")); + + // Line 83: Invalid interceptor with custom normal scope + Diagnostic interceptorCustomScope = d(83, 6, 38, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("io.openliberty.sample.jakarta.cdi.CustomNormalScope")); + + // Line 89: Invalid decorator with custom normal scope + Diagnostic decoratorCustomScope = d(89, 6, 36, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("io.openliberty.sample.jakarta.cdi.CustomNormalScope")); + + // Line 96: Invalid interceptor with mixed scopes + Diagnostic interceptorMixedScopes = d(96, 6, 32, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.ApplicationScoped", "io.openliberty.sample.jakarta.cdi.CustomNormalScope")); + + // Line 103: Invalid decorator with mixed scopes + Diagnostic decoratorMixedScopes = d(103, 6, 30, "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", + DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", + createJsonArray("jakarta.enterprise.context.ApplicationScoped", "io.openliberty.sample.jakarta.cdi.CustomNormalScope")); + + assertJavaDiagnostics(diagnosticsParams, utils, interceptorAppScoped, interceptorSessionScoped, + interceptorMultiScopeDecl, interceptorMultiScope, decoratorAppScoped, decoratorSessionScoped, + decoratorMultiScopeDecl, decoratorMultiScope, interceptorCustomScope, decoratorCustomScope, + interceptorMixedScopes, decoratorMixedScopes); } - @Test - public void interceptorDecoratorCustomScopes() throws Exception { - Module module = createMavenModule(new File("src/test/resources/projects/maven/jakarta-sample")); - IPsiUtils utils = PsiUtilsLSImpl.getInstance(getProject()); - - VirtualFile javaFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(ModuleUtilCore.getModuleDirPath(module) - + "/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java"); - String uri = VfsUtilCore.virtualToIoFile(javaFile).toURI().toString(); - - JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams(); - diagnosticsParams.setUris(Arrays.asList(uri)); - - // Invalid: @Interceptor with mixed scopes (built-in and custom) - Invalid scope diagnostic (line 36) - // Note: Classes with only @CustomNormalScope are not being detected in tests due to annotation resolution timing issues - // Only testing mixed scope cases (built-in + custom) which are reliably detected - JsonArray interceptorMixedScopesInvalid = new JsonArray(); - interceptorMixedScopesInvalid.add("jakarta.enterprise.context.ApplicationScoped"); - interceptorMixedScopesInvalid.add("io.openliberty.sample.jakarta.cdi.CustomNormalScope"); - Diagnostic interceptorWithMixedScopes = d(35, 6, 39, - "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", interceptorMixedScopesInvalid); - - // Invalid: @Decorator with mixed scopes (built-in and custom) - Invalid scope diagnostic (line 43) - JsonArray decoratorMixedScopesInvalid = new JsonArray(); - decoratorMixedScopesInvalid.add("jakarta.enterprise.context.ApplicationScoped"); - decoratorMixedScopesInvalid.add("io.openliberty.sample.jakarta.cdi.CustomNormalScope"); - Diagnostic decoratorWithMixedScopes = d(42, 6, 37, - "Interceptors and decorators must be annotated with the @Dependent scope. Any other scope is invalid.", - DiagnosticSeverity.Error, "jakarta-cdi", "InvalidInterceptorOrDecorator", decoratorMixedScopesInvalid); - - assertJavaDiagnostics(diagnosticsParams, utils, - decoratorWithMixedScopes, - interceptorWithMixedScopes); + private JsonArray createJsonArray(String... values) { + JsonArray array = new JsonArray(); + for (String value : values) { + array.add(value); + } + return array; } } diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java deleted file mode 100644 index ca8712b46..000000000 --- a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorCustomScopes.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.openliberty.sample.jakarta.cdi; - -import jakarta.interceptor.Interceptor; -import jakarta.decorator.Decorator; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Dependent; - -// Invalid: @Interceptor with custom @NormalScope -@Interceptor -@CustomNormalScope -public class InterceptorDecoratorCustomScopes { -} - -// Invalid: @Decorator with custom @NormalScope -@Decorator -@CustomNormalScope -class InvalidDecoratorWithCustomScope { -} - -// Valid: @Interceptor with @Dependent -@Interceptor -@Dependent -class ValidInterceptorWithDependent { -} - -// Valid: @Decorator with @Dependent -@Decorator -@Dependent -class ValidDecoratorWithDependent { -} - -// Invalid: @Interceptor with mixed scopes (built-in and custom) -@Interceptor -@ApplicationScoped -@CustomNormalScope -class InvalidInterceptorWithMixedScopes { -} - -// Invalid: @Decorator with mixed scopes (built-in and custom) -@Decorator -@ApplicationScoped -@CustomNormalScope -class InvalidDecoratorWithMixedScopes { -} - diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java index 4d3d93871..ce56b225d 100644 --- a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/cdi/InterceptorDecoratorScopes.java @@ -3,54 +3,103 @@ import jakarta.interceptor.Interceptor; import jakarta.decorator.Decorator; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.context.SessionScoped; +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.context.ConversationScoped; import jakarta.enterprise.context.Dependent; -// Invalid: @Interceptor with @ApplicationScoped -@Interceptor -@ApplicationScoped -public class InterceptorDecoratorScopes { -} +// ========== Valid Interceptors ========== -// Invalid: @Decorator with @RequestScoped -@Decorator -@RequestScoped -class InvalidDecoratorWithRequestScoped { -} - -// Valid: @Interceptor with @Dependent +// Valid interceptor with explicit @Dependent scope @Interceptor @Dependent class ValidInterceptorWithDependent { } -// Valid: @Decorator with @Dependent +// Valid interceptor with no scope (defaults to @Dependent) +@Interceptor +class ValidInterceptorWithNoScope { +} + +// ========== Valid Decorators ========== + +// Valid decorator with explicit @Dependent scope @Decorator @Dependent class ValidDecoratorWithDependent { } -// Invalid: @Interceptor with @Dependent and @SessionScoped (has invalid scope) +// Valid decorator with no scope (defaults to @Dependent) +@Decorator +class ValidDecoratorWithNoScope { +} + +// ========== Invalid Interceptors with Built-in Normal Scopes ========== + +// Invalid interceptor with @ApplicationScoped +@Interceptor +@ApplicationScoped +class InterceptorWithApplicationScoped { +} + +// Invalid interceptor with @SessionScoped @Interceptor -@Dependent @SessionScoped -class InvalidInterceptorWithMultipleScopes { +class InterceptorWithSessionScoped { +} + +// Invalid interceptor with multiple scopes including illegal ones +@Interceptor +@ApplicationScoped +@SessionScoped +class InterceptorWithMultipleIllegalScopes { } -// Invalid: @Decorator with @Dependent and @ApplicationScoped (has multiple invalid scopes) +// ========== Invalid Decorators with Built-in Normal Scopes ========== + +// Invalid decorator with @ApplicationScoped @Decorator -@Dependent @ApplicationScoped -class InvalidDecoratorWithMultipleScopes { +class DecoratorWithApplicationScoped { } -// Valid: @Decorator with no scope annotation (defaults to @Dependent) +// Invalid decorator with @SessionScoped @Decorator -class ValidDecoratorWithNoScope { +@SessionScoped +class DecoratorWithSessionScoped { +} + +// Invalid decorator with multiple scopes including illegal ones +@Decorator +@RequestScoped +@ConversationScoped +class DecoratorWithMultipleIllegalScopes { } -// Valid: @Interceptor with no scope annotation (defaults to @Dependent) +// ========== Invalid Interceptors/Decorators with Custom Normal Scopes ========== + +// Invalid interceptor with custom normal scope @Interceptor -class ValidInterceptorWithNoScope { +@CustomNormalScope +class InterceptorWithCustomNormalScope { +} + +// Invalid decorator with custom normal scope +@Decorator +@CustomNormalScope +class DecoratorWithCustomNormalScope { +} + +// Invalid interceptor with both built-in and custom normal scopes +@Interceptor +@ApplicationScoped +@CustomNormalScope +class InterceptorWithMixedScopes { +} + +// Invalid decorator with both built-in and custom normal scopes +@Decorator +@ApplicationScoped +@CustomNormalScope +class DecoratorWithMixedScopes { } From e9162d5a8ff0007e7a7f14ea4e82483376d7a261 Mon Sep 17 00:00:00 2001 From: Rejoy Date: Mon, 15 Jun 2026 17:38:35 +0530 Subject: [PATCH 8/9] Initial commit Added quickfixes for Interceptor-and-decorator-must-use-dependant-scope-quickfix --- ...aceInvalidScopesWithDependentQuickFix.java | 66 +++++++++ .../quickfix/ReplaceAnnotationsQuickFix.java | 131 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 4 + .../lsp4jakarta/messages/messages.properties | 3 + 4 files changed, 204 insertions(+) create mode 100644 src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ReplaceInvalidScopesWithDependentQuickFix.java create mode 100644 src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/proposal/quickfix/ReplaceAnnotationsQuickFix.java diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ReplaceInvalidScopesWithDependentQuickFix.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ReplaceInvalidScopesWithDependentQuickFix.java new file mode 100644 index 000000000..670fac355 --- /dev/null +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/cdi/ReplaceInvalidScopesWithDependentQuickFix.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial implementation + *******************************************************************************/ +package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.cdi; + +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.ReplaceAnnotationsQuickFix; + +import java.util.List; +import java.util.stream.Collectors; + +import static io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils.getSimpleName; + +/** + * Quickfix for InvalidInterceptorOrDecorator diagnostic. + * Replaces all invalid scope annotations with @Dependent. + */ +public class ReplaceInvalidScopesWithDependentQuickFix extends ReplaceAnnotationsQuickFix { + + /** + * Constructor. + */ + public ReplaceInvalidScopesWithDependentQuickFix() { + super(ManagedBeanConstants.DEPENDENT_FQ_NAME); + } + + /** + * {@inheritDoc} + */ + @Override + public String getParticipantId() { + return ReplaceInvalidScopesWithDependentQuickFix.class.getName(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getCodeActionLabel(List annotationsToRemove) { + String formattedNames = formatAnnotationNames(annotationsToRemove); + return Messages.getMessage("ReplaceInvalidScopesWithDependent", formattedNames); + } + + /** + * Formats a list of fully qualified annotation names for display. + * Extracts simple names and joins them with commas, prefixed with @. + * + * @param annotationFqNames List of fully qualified annotation names + * @return Formatted string (e.g., "@ApplicationScoped, @RequestScoped") + */ + private String formatAnnotationNames(List annotationFqNames) { + return annotationFqNames.stream() + .map(fqName -> "@" + getSimpleName(fqName)) + .collect(Collectors.joining(", ")); + } +} + diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/proposal/quickfix/ReplaceAnnotationsQuickFix.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/proposal/quickfix/ReplaceAnnotationsQuickFix.java new file mode 100644 index 000000000..58962f725 --- /dev/null +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/proposal/quickfix/ReplaceAnnotationsQuickFix.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation 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. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial implementation + *******************************************************************************/ +package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix; + +import com.google.gson.JsonArray; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiModifierListOwner; +import com.intellij.psi.util.PsiTreeUtil; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionContext; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionResolveContext; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ChangeCorrectionProposal; +import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ReplaceAnnotationProposal; +import io.openliberty.tools.intellij.util.ExceptionUtil; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.Diagnostic; + +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils.getSimpleName; + +/** + * Abstract base class for quickfixes that replace annotations. + * Provides common functionality for extracting annotation data from diagnostics + * and creating code actions that replace multiple annotations with a single annotation. + */ +public abstract class ReplaceAnnotationsQuickFix extends InsertAnnotationMissingQuickFix { + + /** Logger object to record events for this class. */ + private static final Logger LOGGER = Logger.getLogger(ReplaceAnnotationsQuickFix.class.getName()); + + /** + * Constructor. + * + * @param annotation The fully qualified name of the annotation to insert + */ + public ReplaceAnnotationsQuickFix(String annotation) { + super(annotation); + } + + /** + * Extracts the list of annotation fully qualified names from diagnostic data. + * + * @param diagnostic The diagnostic containing annotation data + * @return List of fully qualified annotation names, or null if data is invalid + */ + private List extractAnnotationsFromDiagnostic(Diagnostic diagnostic) { + JsonArray diagnosticData = (JsonArray) diagnostic.getData(); + if (diagnosticData == null || diagnosticData.size() == 0) { + return null; + } + + return IntStream.range(0, diagnosticData.size()) + .mapToObj(idx -> diagnosticData.get(idx).getAsString()) + .collect(Collectors.toList()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void insertAnnotations(Diagnostic diagnostic, JavaCodeActionContext context, + List codeActions) { + List annotationsToRemove = extractAnnotationsFromDiagnostic(diagnostic); + if (annotationsToRemove == null) { + return; + } + + // Get the code action label from subclass + String name = getCodeActionLabel(annotationsToRemove); + + // Create code action + codeActions.add(JDTUtils.createCodeAction(context, diagnostic, name, getParticipantId())); + } + + /** + * {@inheritDoc} + */ + @Override + public CodeAction resolveCodeAction(JavaCodeActionResolveContext context) { + final CodeAction toResolve = context.getUnresolved(); + String name = toResolve.getTitle(); + + // Get the diagnostic to extract annotations to remove + Diagnostic diagnostic = toResolve.getDiagnostics().get(0); + List annotationsToRemove = extractAnnotationsFromDiagnostic(diagnostic); + + if (annotationsToRemove == null) { + return toResolve; + } + + // Convert to array of fully qualified names for ReplaceAnnotationProposal + String[] fqNames = annotationsToRemove.toArray(new String[0]); + + PsiElement node = context.getCoveringNode(); + PsiModifierListOwner parentType = PsiTreeUtil.getParentOfType(node, PsiClass.class); + + // Create a proposal that replaces all annotations + ChangeCorrectionProposal proposal = new ReplaceAnnotationProposal(name, context.getCompilationUnit(), + context.getASTRoot(), parentType, 0, getAnnotations()[0], + context.getSource().getCompilationUnit(), fqNames); + + ExceptionUtil.executeWithWorkspaceEditHandling(context, proposal, toResolve, LOGGER, + "Unable to create workspace edit for code action to replace annotations"); + + return toResolve; + } + + /** + * Returns the code action label for the given list of annotation fully qualified names. + * Subclasses should override this to provide custom labels based on the annotations to remove. + * + * @param annotationsToRemove List of fully qualified annotation names to be removed + * @return The code action label + */ + protected abstract String getCodeActionLabel(List annotationsToRemove); +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 200ec3236..2a215b358 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -558,6 +558,10 @@ group="jakarta" targetDiagnostic="jakarta-cdi#InvalidDependentScopeWithConditionalObserver" implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.cdi.RemoveObserverAnnotationQuickFix"/> + Date: Mon, 15 Jun 2026 17:38:44 +0530 Subject: [PATCH 9/9] Added tests --- .../cdi/InterceptorDecoratorScopesTest.java | 357 ++++++++++++++++++ 1 file changed, 357 insertions(+) diff --git a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java index 27e18ca2a..cfe0ef0a4 100644 --- a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java +++ b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/cdi/InterceptorDecoratorScopesTest.java @@ -22,8 +22,10 @@ import io.openliberty.tools.intellij.lsp4jakarta.it.core.BaseJakartaTest; import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.utils.IPsiUtils; import io.openliberty.tools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl; +import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4jakarta.commons.JakartaJavaDiagnosticsParams; import org.junit.Test; import org.junit.runner.RunWith; @@ -116,6 +118,361 @@ public void interceptorDecoratorScopes() throws Exception { interceptorMultiScopeDecl, interceptorMultiScope, decoratorAppScoped, decoratorSessionScoped, decoratorMultiScopeDecl, decoratorMultiScope, interceptorCustomScope, decoratorCustomScope, interceptorMixedScopes, decoratorMixedScopes); + + // Test quickfix for interceptor with @ApplicationScoped (line 41) + String newText1 = "package io.openliberty.sample.jakarta.cdi;\n\n" + + "import jakarta.interceptor.Interceptor;\n" + + "import jakarta.decorator.Decorator;\n" + + "import jakarta.enterprise.context.SessionScoped;\n" + + "import jakarta.enterprise.context.RequestScoped;\n" + + "import jakarta.enterprise.context.ConversationScoped;\n" + + "import jakarta.enterprise.context.Dependent;\n\n" + + "// ========== Valid Interceptors ==========\n\n" + + "// Valid interceptor with explicit @Dependent scope\n" + + "@Interceptor\n" + + "@Dependent\n" + + "class ValidInterceptorWithDependent {\n" + + "}\n\n" + + "// Valid interceptor with no scope (defaults to @Dependent)\n" + + "@Interceptor\n" + + "class ValidInterceptorWithNoScope {\n" + + "}\n\n" + + "// ========== Valid Decorators ==========\n\n" + + "// Valid decorator with explicit @Dependent scope\n" + + "@Decorator\n" + + "@Dependent\n" + + "class ValidDecoratorWithDependent {\n" + + "}\n\n" + + "// Valid decorator with no scope (defaults to @Dependent)\n" + + "@Decorator\n" + + "class ValidDecoratorWithNoScope {\n" + + "}\n\n" + + "// ========== Invalid Interceptors with Built-in Normal Scopes ==========\n\n" + + "// Invalid interceptor with @ApplicationScoped\n" + + "@Dependent\n" + + "@Interceptor\n" + + "class InterceptorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid interceptor with @SessionScoped\n" + + "@Interceptor\n" + + "@SessionScoped\n" + + "class InterceptorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid interceptor with multiple scopes including illegal ones\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "@SessionScoped\n" + + "class InterceptorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Decorators with Built-in Normal Scopes ==========\n\n" + + "// Invalid decorator with @ApplicationScoped\n" + + "@Decorator\n" + + "@ApplicationScoped\n" + + "class DecoratorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid decorator with @SessionScoped\n" + + "@Decorator\n" + + "@SessionScoped\n" + + "class DecoratorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid decorator with multiple scopes including illegal ones\n" + + "@Decorator\n" + + "@RequestScoped\n" + + "@ConversationScoped\n" + + "class DecoratorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Interceptors/Decorators with Custom Normal Scopes ==========\n\n" + + "// Invalid interceptor with custom normal scope\n" + + "@Interceptor\n" + + "@CustomNormalScope\n" + + "class InterceptorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid decorator with custom normal scope\n" + + "@Decorator\n" + + "@CustomNormalScope\n" + + "class DecoratorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid interceptor with both built-in and custom normal scopes\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class InterceptorWithMixedScopes {\n" + + "}\n\n" + + "// Invalid decorator with both built-in and custom normal scopes\n" + + "@Decorator\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class DecoratorWithMixedScopes {\n" + + "}\n"; + TextEdit replaceAppScopedEdit = te(0, 0, 105, 0, newText1); + CodeAction replaceAppScopedAction = ca(uri, "Replace @ApplicationScoped with @Dependent", interceptorAppScoped, replaceAppScopedEdit); + assertJavaCodeAction(createCodeActionParams(uri, interceptorAppScoped), utils, replaceAppScopedAction); + + // Test quickfix for interceptor with multiple scopes (line 54) + String newText2 = "package io.openliberty.sample.jakarta.cdi;\n\n" + + "import jakarta.interceptor.Interceptor;\n" + + "import jakarta.decorator.Decorator;\n" + + "import jakarta.enterprise.context.RequestScoped;\n" + + "import jakarta.enterprise.context.ConversationScoped;\n" + + "import jakarta.enterprise.context.Dependent;\n\n" + + "// ========== Valid Interceptors ==========\n\n" + + "// Valid interceptor with explicit @Dependent scope\n" + + "@Interceptor\n" + + "@Dependent\n" + + "class ValidInterceptorWithDependent {\n" + + "}\n\n" + + "// Valid interceptor with no scope (defaults to @Dependent)\n" + + "@Interceptor\n" + + "class ValidInterceptorWithNoScope {\n" + + "}\n\n" + + "// ========== Valid Decorators ==========\n\n" + + "// Valid decorator with explicit @Dependent scope\n" + + "@Decorator\n" + + "@Dependent\n" + + "class ValidDecoratorWithDependent {\n" + + "}\n\n" + + "// Valid decorator with no scope (defaults to @Dependent)\n" + + "@Decorator\n" + + "class ValidDecoratorWithNoScope {\n" + + "}\n\n" + + "// ========== Invalid Interceptors with Built-in Normal Scopes ==========\n\n" + + "// Invalid interceptor with @ApplicationScoped\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "class InterceptorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid interceptor with @SessionScoped\n" + + "@Interceptor\n" + + "@SessionScoped\n" + + "class InterceptorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid interceptor with multiple scopes including illegal ones\n" + + "@Dependent\n" + + "@Interceptor\n" + + "class InterceptorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Decorators with Built-in Normal Scopes ==========\n\n" + + "// Invalid decorator with @ApplicationScoped\n" + + "@Decorator\n" + + "@ApplicationScoped\n" + + "class DecoratorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid decorator with @SessionScoped\n" + + "@Decorator\n" + + "@SessionScoped\n" + + "class DecoratorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid decorator with multiple scopes including illegal ones\n" + + "@Decorator\n" + + "@RequestScoped\n" + + "@ConversationScoped\n" + + "class DecoratorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Interceptors/Decorators with Custom Normal Scopes ==========\n\n" + + "// Invalid interceptor with custom normal scope\n" + + "@Interceptor\n" + + "@CustomNormalScope\n" + + "class InterceptorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid decorator with custom normal scope\n" + + "@Decorator\n" + + "@CustomNormalScope\n" + + "class DecoratorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid interceptor with both built-in and custom normal scopes\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class InterceptorWithMixedScopes {\n" + + "}\n\n" + + "// Invalid decorator with both built-in and custom normal scopes\n" + + "@Decorator\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class DecoratorWithMixedScopes {\n" + + "}\n"; + TextEdit replaceMultipleScopesEdit = te(0, 0, 105, 0, newText2); + CodeAction replaceMultipleScopesAction = ca(uri, "Replace @ApplicationScoped, @SessionScoped with @Dependent", interceptorMultiScope, replaceMultipleScopesEdit); + assertJavaCodeAction(createCodeActionParams(uri, interceptorMultiScope), utils, replaceMultipleScopesAction); + + // Test quickfix for decorator with @ApplicationScoped (line 62) + String newText3 = "package io.openliberty.sample.jakarta.cdi;\n\n" + + "import jakarta.interceptor.Interceptor;\n" + + "import jakarta.decorator.Decorator;\n" + + "import jakarta.enterprise.context.SessionScoped;\n" + + "import jakarta.enterprise.context.RequestScoped;\n" + + "import jakarta.enterprise.context.ConversationScoped;\n" + + "import jakarta.enterprise.context.Dependent;\n\n" + + "// ========== Valid Interceptors ==========\n\n" + + "// Valid interceptor with explicit @Dependent scope\n" + + "@Interceptor\n" + + "@Dependent\n" + + "class ValidInterceptorWithDependent {\n" + + "}\n\n" + + "// Valid interceptor with no scope (defaults to @Dependent)\n" + + "@Interceptor\n" + + "class ValidInterceptorWithNoScope {\n" + + "}\n\n" + + "// ========== Valid Decorators ==========\n\n" + + "// Valid decorator with explicit @Dependent scope\n" + + "@Decorator\n" + + "@Dependent\n" + + "class ValidDecoratorWithDependent {\n" + + "}\n\n" + + "// Valid decorator with no scope (defaults to @Dependent)\n" + + "@Decorator\n" + + "class ValidDecoratorWithNoScope {\n" + + "}\n\n" + + "// ========== Invalid Interceptors with Built-in Normal Scopes ==========\n\n" + + "// Invalid interceptor with @ApplicationScoped\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "class InterceptorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid interceptor with @SessionScoped\n" + + "@Interceptor\n" + + "@SessionScoped\n" + + "class InterceptorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid interceptor with multiple scopes including illegal ones\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "@SessionScoped\n" + + "class InterceptorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Decorators with Built-in Normal Scopes ==========\n\n" + + "// Invalid decorator with @ApplicationScoped\n" + + "@Dependent\n" + + "@Decorator\n" + + "class DecoratorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid decorator with @SessionScoped\n" + + "@Decorator\n" + + "@SessionScoped\n" + + "class DecoratorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid decorator with multiple scopes including illegal ones\n" + + "@Decorator\n" + + "@RequestScoped\n" + + "@ConversationScoped\n" + + "class DecoratorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Interceptors/Decorators with Custom Normal Scopes ==========\n\n" + + "// Invalid interceptor with custom normal scope\n" + + "@Interceptor\n" + + "@CustomNormalScope\n" + + "class InterceptorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid decorator with custom normal scope\n" + + "@Decorator\n" + + "@CustomNormalScope\n" + + "class DecoratorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid interceptor with both built-in and custom normal scopes\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class InterceptorWithMixedScopes {\n" + + "}\n\n" + + "// Invalid decorator with both built-in and custom normal scopes\n" + + "@Decorator\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class DecoratorWithMixedScopes {\n" + + "}\n"; + TextEdit replaceDecoratorAppScopedEdit = te(0, 0, 105, 0, newText3); + CodeAction replaceDecoratorAppScopedAction = ca(uri, "Replace @ApplicationScoped with @Dependent", decoratorAppScoped, replaceDecoratorAppScopedEdit); + assertJavaCodeAction(createCodeActionParams(uri, decoratorAppScoped), utils, replaceDecoratorAppScopedAction); + + // Test quickfix for interceptor with custom scope (line 83) + String newText4 = "package io.openliberty.sample.jakarta.cdi;\n\n" + + "import jakarta.interceptor.Interceptor;\n" + + "import jakarta.decorator.Decorator;\n" + + "import jakarta.enterprise.context.ApplicationScoped;\n" + + "import jakarta.enterprise.context.SessionScoped;\n" + + "import jakarta.enterprise.context.RequestScoped;\n" + + "import jakarta.enterprise.context.ConversationScoped;\n" + + "import jakarta.enterprise.context.Dependent;\n\n" + + "// ========== Valid Interceptors ==========\n\n" + + "// Valid interceptor with explicit @Dependent scope\n" + + "@Interceptor\n" + + "@Dependent\n" + + "class ValidInterceptorWithDependent {\n" + + "}\n\n" + + "// Valid interceptor with no scope (defaults to @Dependent)\n" + + "@Interceptor\n" + + "class ValidInterceptorWithNoScope {\n" + + "}\n\n" + + "// ========== Valid Decorators ==========\n\n" + + "// Valid decorator with explicit @Dependent scope\n" + + "@Decorator\n" + + "@Dependent\n" + + "class ValidDecoratorWithDependent {\n" + + "}\n\n" + + "// Valid decorator with no scope (defaults to @Dependent)\n" + + "@Decorator\n" + + "class ValidDecoratorWithNoScope {\n" + + "}\n\n" + + "// ========== Invalid Interceptors with Built-in Normal Scopes ==========\n\n" + + "// Invalid interceptor with @ApplicationScoped\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "class InterceptorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid interceptor with @SessionScoped\n" + + "@Interceptor\n" + + "@SessionScoped\n" + + "class InterceptorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid interceptor with multiple scopes including illegal ones\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "@SessionScoped\n" + + "class InterceptorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Decorators with Built-in Normal Scopes ==========\n\n" + + "// Invalid decorator with @ApplicationScoped\n" + + "@Decorator\n" + + "@ApplicationScoped\n" + + "class DecoratorWithApplicationScoped {\n" + + "}\n\n" + + "// Invalid decorator with @SessionScoped\n" + + "@Decorator\n" + + "@SessionScoped\n" + + "class DecoratorWithSessionScoped {\n" + + "}\n\n" + + "// Invalid decorator with multiple scopes including illegal ones\n" + + "@Decorator\n" + + "@RequestScoped\n" + + "@ConversationScoped\n" + + "class DecoratorWithMultipleIllegalScopes {\n" + + "}\n\n" + + "// ========== Invalid Interceptors/Decorators with Custom Normal Scopes ==========\n\n" + + "// Invalid interceptor with custom normal scope\n" + + "@Dependent\n" + + "@Interceptor\n" + + "class InterceptorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid decorator with custom normal scope\n" + + "@Decorator\n" + + "@CustomNormalScope\n" + + "class DecoratorWithCustomNormalScope {\n" + + "}\n\n" + + "// Invalid interceptor with both built-in and custom normal scopes\n" + + "@Interceptor\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class InterceptorWithMixedScopes {\n" + + "}\n\n" + + "// Invalid decorator with both built-in and custom normal scopes\n" + + "@Decorator\n" + + "@ApplicationScoped\n" + + "@CustomNormalScope\n" + + "class DecoratorWithMixedScopes {\n" + + "}\n"; + TextEdit replaceCustomScopeEdit = te(0, 0, 105, 0, newText4); + CodeAction replaceCustomScopeAction = ca(uri, "Replace @CustomNormalScope with @Dependent", interceptorCustomScope, replaceCustomScopeEdit); + assertJavaCodeAction(createCodeActionParams(uri, interceptorCustomScope), utils, replaceCustomScopeAction); } private JsonArray createJsonArray(String... values) {