diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/DependencyModuleHintProvider.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/DependencyModuleHintProvider.java index 3258c014b..4fd761e92 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/DependencyModuleHintProvider.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/DependencyModuleHintProvider.java @@ -41,7 +41,7 @@ public String message() { if (tags.isEmpty()) { return """ Missing component: %s - Component can be provided by standard Kora module you may forgot to plug it: + Component is provided by standard Kora module you may forgot to plug it: Gradle dependency: implementation("%s") Module interface: %s """.formatted(type, artifact, module); diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java index 6a327ae17..8b70d6486 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java @@ -11,6 +11,7 @@ import ru.tinkoff.kora.kora.app.annotation.processor.component.ResolvedComponent; import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration; import ru.tinkoff.kora.kora.app.annotation.processor.exception.CircularDependencyException; +import ru.tinkoff.kora.kora.app.annotation.processor.exception.DuplicateDependencyException; import ru.tinkoff.kora.kora.app.annotation.processor.exception.NewRoundException; import ru.tinkoff.kora.kora.app.annotation.processor.exception.UnresolvedDependencyException; import ru.tinkoff.kora.kora.app.annotation.processor.extension.ExtensionResult; @@ -145,13 +146,7 @@ public static ProcessingState processProcessing(ProcessingContext ctx, RoundEnvi } } if (results.size() > 1) { - var deps = templates.stream().map(Objects::toString).collect(Collectors.joining("\n")).indent(2); - if (dependencyClaim.tags().isEmpty()) { - throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, declaration.source()); - } else { - var tagMsg = dependencyClaim.tags().stream().collect(Collectors.joining(", ", "@Tag(", ")")); - throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + " with tag " + tagMsg + " :\n" + deps, declaration.source()); - } + throw new DuplicateDependencyException(dependencyClaim, declaration, templates); } throw exception; } @@ -477,17 +472,17 @@ private static boolean checkCycle(ProcessingContext ctx, ProcessingState.Process var dependencyClaimType = dependencyClaim.type(); var dependencyClaimTypeElement = ctx.types.asElement(dependencyClaimType); if (!(ctx.types.isAssignable(declaration.type(), dependencyClaimType) || ctx.serviceTypeHelper.isAssignableToUnwrapped(declaration.type(), dependencyClaimType) || ctx.serviceTypeHelper.isInterceptor(declaration.type()))) { - throw new CircularDependencyException(List.of(prevComponent.declaration().toString(), declaration.toString()), declaration); + throw new CircularDependencyException(List.of(prevComponent.declaration(), declaration), declaration); } for (var inStackFrame : processing.resolutionStack()) { if (!(inStackFrame instanceof ProcessingState.ResolutionFrame.Component componentFrame) || componentFrame.declaration() != declaration) { continue; } if (dependencyClaim.type().getKind() != TypeKind.DECLARED) { - throw new CircularDependencyException(List.of(prevComponent.declaration().toString(), declaration.toString()), componentFrame.declaration()); + throw new CircularDependencyException(List.of(prevComponent.declaration(), declaration), componentFrame.declaration()); } if (dependencyClaimTypeElement.getKind() != ElementKind.INTERFACE && (dependencyClaimTypeElement.getKind() != ElementKind.CLASS || dependencyClaimTypeElement.getModifiers().contains(Modifier.FINAL))) { - throw new CircularDependencyException(List.of(prevComponent.declaration().toString(), declaration.toString()), componentFrame.declaration()); + throw new CircularDependencyException(List.of(prevComponent.declaration(), declaration), componentFrame.declaration()); } var proxyDependencyClaim = new DependencyClaim( dependencyClaimType, Set.of(CommonClassNames.promisedProxy.canonicalName()), dependencyClaim.claimType() diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphResolutionHelper.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphResolutionHelper.java index baf381000..07b5309b1 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphResolutionHelper.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphResolutionHelper.java @@ -8,14 +8,13 @@ import ru.tinkoff.kora.kora.app.annotation.processor.component.DependencyClaim; import ru.tinkoff.kora.kora.app.annotation.processor.component.ResolvedComponent; import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration; +import ru.tinkoff.kora.kora.app.annotation.processor.exception.DuplicateDependencyException; import javax.lang.model.element.*; import javax.lang.model.type.*; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.function.Predicate; -import java.util.stream.Collectors; import static ru.tinkoff.kora.kora.app.annotation.processor.component.DependencyClaim.DependencyClaimType.*; @@ -37,12 +36,7 @@ public static ComponentDependency.SingleDependency findDependency(ProcessingCont return null; } - var deps = dependencies.stream() - .map(ComponentDependency.SingleDependency::component) - .map(Objects::toString) - .collect(Collectors.joining("\n")).indent(2); - - throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, forDeclaration.source()); + throw new DuplicateDependencyException(dependencies, dependencyClaim, forDeclaration); } public static List findDependencies(ProcessingContext ctx, List resolvedComponents, DependencyClaim dependencyClaim) { @@ -152,15 +146,14 @@ public static ComponentDeclaration findDependencyDeclarationFromTemplate(Process if (declarations.size() == 1) { return declarations.get(0); } - var exactMatch = declarations.stream().filter(d -> ctx.types.isSameType( - d.type(), - dependencyClaim.type() - )).toList(); + var exactMatch = declarations.stream() + .filter(d -> ctx.types.isSameType(d.type(), dependencyClaim.type())) + .toList(); if (exactMatch.size() == 1) { return exactMatch.get(0); } - var deps = declarations.stream().map(Objects::toString).collect(Collectors.joining("\n")).indent(2); - throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, forDeclaration.source()); + + throw new DuplicateDependencyException(dependencyClaim, forDeclaration, declarations); } public static List findDependencyDeclarationsFromTemplate(ProcessingContext ctx, ComponentDeclaration forDeclaration, List sourceDeclarations, DependencyClaim dependencyClaim) { @@ -326,8 +319,7 @@ public static ComponentDeclaration findDependencyDeclaration(ProcessingContext c return nonDefaultComponents.get(0); } - var deps = declarations.stream().map(Objects::toString).collect(Collectors.joining("\n")).indent(2); - throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, forDeclaration.source()); + throw new DuplicateDependencyException(dependencyClaim, forDeclaration, declarations); } public static List findDependencyDeclarations(ProcessingContext ctx, List sourceDeclarations, DependencyClaim dependencyClaim) { diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/exception/CircularDependencyException.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/exception/CircularDependencyException.java index 970bc9afa..4e214232d 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/exception/CircularDependencyException.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/exception/CircularDependencyException.java @@ -1,12 +1,38 @@ package ru.tinkoff.kora.kora.app.annotation.processor.exception; +import com.squareup.javapoet.TypeName; +import ru.tinkoff.kora.annotation.processor.common.CommonClassNames; +import ru.tinkoff.kora.annotation.processor.common.ProcessingError; import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration; import java.util.List; +import java.util.stream.Collectors; public class CircularDependencyException extends ProcessingErrorException { - public CircularDependencyException(List cycle, ComponentDeclaration source) { - super(String.format("There's a cycle in graph: \n\t%s", String.join("\n\t", cycle)), source.source()); + + public CircularDependencyException(List cycle, ComponentDeclaration declaration) { + super(getError(cycle, declaration)); + } + + private static ProcessingError getError(List cycle, + ComponentDeclaration declaration) { + var deps = cycle.stream() + .map(c -> String.format("- %s", c.declarationString())) + .collect(Collectors.joining("\n", "Cycle dependency candidates:\n", "")).indent(2); + + if (declaration.tags().isEmpty()) { + return new ProcessingError("Encountered circular dependency in graph for source type: " + TypeName.get(declaration.type()) + " (no tags)\n" + + deps + + "\nPlease check that you are not using cycle dependency in %s, this is forbidden.".formatted(CommonClassNames.lifecycle), + declaration.source()); + } else { + var tagMsg = declaration.tags().stream() + .collect(Collectors.joining(", ", "@Tag(", ")")); + return new ProcessingError("Encountered circular dependency in graph for source type: " + TypeName.get(declaration.type()) + " with " + tagMsg + "\n" + + deps + + "\nPlease check that you are not using cycle dependency in %s, this is forbidden.".formatted(CommonClassNames.lifecycle), + declaration.source()); + } } } diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/exception/DuplicateDependencyException.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/exception/DuplicateDependencyException.java new file mode 100644 index 000000000..4e5d05fad --- /dev/null +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/exception/DuplicateDependencyException.java @@ -0,0 +1,65 @@ +package ru.tinkoff.kora.kora.app.annotation.processor.exception; + +import com.squareup.javapoet.TypeName; +import ru.tinkoff.kora.annotation.processor.common.ProcessingError; +import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; +import ru.tinkoff.kora.kora.app.annotation.processor.component.ComponentDependency; +import ru.tinkoff.kora.kora.app.annotation.processor.component.DependencyClaim; +import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration; + +import java.util.List; +import java.util.stream.Collectors; + +public class DuplicateDependencyException extends ProcessingErrorException { + + public DuplicateDependencyException(DependencyClaim claim, + ComponentDeclaration declaration, + List foundDeclarations) { + super(List.of(getErrorForDeclarations(claim, declaration, foundDeclarations))); + } + + public DuplicateDependencyException(List foundDeclarations, + DependencyClaim claim, + ComponentDeclaration declaration) { + super(List.of(getErrorForDependencies(claim, declaration, foundDeclarations))); + } + + private static ProcessingError getErrorForDeclarations(DependencyClaim claim, + ComponentDeclaration declaration, + List foundDeclarations) { + var deps = foundDeclarations.stream() + .map(c -> String.format("- %s", c.declarationString())) + .collect(Collectors.joining("\n", "Candidates for injection:\n", "")).indent(2); + + return getError(claim, declaration, deps); + } + + private static ProcessingError getErrorForDependencies(DependencyClaim claim, + ComponentDeclaration declaration, + List foundDeclarations) { + var deps = foundDeclarations.stream() + .map(ComponentDependency.SingleDependency::component) + .map(c -> String.format("- %s", c.declaration().declarationString())) + .collect(Collectors.joining("\n", "Candidates for injection:\n", "")).indent(2); + + return getError(claim, declaration, deps); + } + + private static ProcessingError getError(DependencyClaim claim, + ComponentDeclaration declaration, + String deps) { + if (claim.tags().isEmpty()) { + return new ProcessingError("More than one component matches dependency type: " + TypeName.get(claim.type()) + " (no tags)\n" + + deps + + "\nPlease check that injection dependency is declared correctly or that @DefaultComponent annotation is not missing if was intended.", + declaration.source()); + } else { + var tagMsg = claim.tags().stream() + .collect(Collectors.joining(", ", "@Tag(", ")")); + return new ProcessingError("More than one component matches dependency type: " + TypeName.get(claim.type()) + " with " + tagMsg + "\n" + + deps + + "\nPlease check that injection dependency is declared correctly or that @DefaultComponent annotation is not missing if was intended.", + declaration.source()); + } + } +} diff --git a/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java b/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java index 5b8ed5d8e..3076f4b35 100644 --- a/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java +++ b/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java @@ -171,7 +171,7 @@ void unresolvedDependency() { void testCircularDependency() { assertThatThrownBy(() -> testClass(AppWithCircularDependency.class)) .isInstanceOfSatisfying(CompilationErrorException.class, e -> SoftAssertions.assertSoftly(s -> { - s.assertThat(e.getMessage()).startsWith("There's a cycle in graph: "); + s.assertThat(e.getMessage()).startsWith("Encountered circular dependency in graph for source type:"); s.assertThat(e.diagnostics.get(0).getSource().getName()).isEqualTo("src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/app/AppWithCircularDependency.java"); })); } @@ -194,7 +194,7 @@ void appWithFactory() throws Throwable { // testClass(AppWithFactories5.class).init();; TODO больше не нужно assertThatThrownBy(() -> testClass(AppWithFactories6.class)) .isInstanceOf(CompilationErrorException.class) - .hasMessageStartingWith("There's a cycle in graph:"); + .hasMessageStartingWith("Encountered circular dependency in graph for source type"); testClass(AppWithFactories7.class).init(); testClass(AppWithFactories8.class).init(); testClass(AppWithFactories9.class).init(); @@ -261,11 +261,7 @@ void appWithComponentDescriptorCollisionAndDirect() { .isInstanceOfSatisfying(CompilationErrorException.class, e -> SoftAssertions.assertSoftly(s -> { var error = e.getDiagnostics().stream().filter(d -> d.getKind() == Diagnostic.Kind.ERROR).findFirst().get(); s.assertThat(error.getMessage(Locale.US)) - .startsWith("More than one component matches dependency claim ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect.Class1:"); - - s.assertThat(error.getMessage(Locale.US)).contains("FromModuleComponent[type=ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect.Class1, module=MixedInModule[element=ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect], method=c1()"); - s.assertThat(error.getMessage(Locale.US)).contains("FromModuleComponent[type=ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect.Class1, module=MixedInModule[element=ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect], method=c2()"); - s.assertThat(error.getMessage(Locale.US)).contains("FromModuleComponent[type=ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect.Class1, module=MixedInModule[element=ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect], method=c3()"); + .startsWith("More than one component matches dependency type: ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect.Class1"); })); } diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/DependencyModuleHintProvider.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/DependencyModuleHintProvider.kt index 7f3b1b00b..0d35aa6d6 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/DependencyModuleHintProvider.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/DependencyModuleHintProvider.kt @@ -40,7 +40,7 @@ class DependencyModuleHintProvider(private val resolver: Resolver) { if (tags.isEmpty()) { return """ Missing component: ${type.toTypeName()} - Component can be provided by standard Kora module you may forgot to plug it: + Component is provided by standard Kora module you may forgot to plug it: Gradle dependency: implementation("$artifact") Module interface: $module """.trimIndent() @@ -55,7 +55,7 @@ class DependencyModuleHintProvider(private val resolver: Resolver) { return """ Missing component: ${type.toTypeName()} with $tagForMsg - Component can be provided by standard Kora module you may forgot to plug it: + Component is provided by standard Kora module you may forgot to plug it: Gradle dependency: implementation("$artifact") Module interface: $module """.trimIndent() diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt index 072d15805..e8cac869c 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt @@ -14,6 +14,7 @@ import ru.tinkoff.kora.kora.app.ksp.component.DependencyClaim.DependencyClaimTyp import ru.tinkoff.kora.kora.app.ksp.component.ResolvedComponent import ru.tinkoff.kora.kora.app.ksp.declaration.ComponentDeclaration import ru.tinkoff.kora.kora.app.ksp.exception.CircularDependencyException +import ru.tinkoff.kora.kora.app.ksp.exception.DuplicateDependencyException import ru.tinkoff.kora.kora.app.ksp.exception.NewRoundException import ru.tinkoff.kora.kora.app.ksp.exception.UnresolvedDependencyException import ru.tinkoff.kora.kora.app.ksp.extension.ExtensionResult @@ -142,15 +143,7 @@ object GraphBuilder { } } if (results.size > 1) { - val deps = templates.stream().map { Objects.toString(it) } - .collect(Collectors.joining("\n")) - .prependIndent(" ") - throw ProcessingErrorException( - """ - More than one component matches dependency claim ${dependencyClaim.type}: - $deps - """.trimIndent(), declaration.source - ) + throw DuplicateDependencyException(dependencyClaim, declaration, templates) } throw exception!! } @@ -538,7 +531,7 @@ object GraphBuilder { if (frame !is ProcessingState.ResolutionFrame.Component || frame.declaration !== declaration) { continue } - val circularDependencyException = CircularDependencyException(listOf(prevFrame.declaration.toString(), declaration.toString()), frame.declaration) + val circularDependencyException = CircularDependencyException(listOf(prevFrame.declaration, declaration), frame.declaration) if (claimTypeDeclaration !is KSClassDeclaration) throw circularDependencyException if (claimTypeDeclaration.classKind != ClassKind.INTERFACE && !(claimTypeDeclaration.classKind == ClassKind.CLASS && claimTypeDeclaration.isOpen())) throw circularDependencyException val proxyDependencyClaim = DependencyClaim( diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphResolutionHelper.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphResolutionHelper.kt index afb037791..8551858be 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphResolutionHelper.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphResolutionHelper.kt @@ -9,8 +9,8 @@ import ru.tinkoff.kora.kora.app.ksp.component.ComponentDependency.* import ru.tinkoff.kora.kora.app.ksp.component.DependencyClaim import ru.tinkoff.kora.kora.app.ksp.component.ResolvedComponent import ru.tinkoff.kora.kora.app.ksp.declaration.ComponentDeclaration +import ru.tinkoff.kora.kora.app.ksp.exception.DuplicateDependencyException import ru.tinkoff.kora.ksp.common.TagUtils -import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException object GraphResolutionHelper { fun findDependency(ctx: ProcessingContext, forDeclaration: ComponentDeclaration, resolvedComponents: List, dependencyClaim: DependencyClaim): SingleDependency? { @@ -21,11 +21,8 @@ object GraphResolutionHelper { if (dependencies.isEmpty()) { return null } - val deps = dependencies.joinToString("\n") { it.toString() }.prependIndent(" ") - throw ProcessingErrorException( - "More than one component matches dependency claim ${dependencyClaim.type.declaration.qualifiedName?.asString()} tag=${dependencyClaim.tags}:\n$deps", - forDeclaration.source - ) + + throw DuplicateDependencyException(dependencies, dependencyClaim, forDeclaration) } fun findDependencies(ctx: ProcessingContext, resolvedComponents: List, dependencyClaim: DependencyClaim): List { @@ -119,11 +116,8 @@ object GraphResolutionHelper { if (result.size == 1) { return result[0] } - val deps = result.asSequence().map { it.toString() }.joinToString("\n").prependIndent(" ") - throw ProcessingErrorException( - "More than one component matches dependency claim ${dependencyClaim.type.declaration.qualifiedName?.asString()} tag=${dependencyClaim.tags}:\n$deps", - forDeclaration.source - ) + + throw DuplicateDependencyException(dependencyClaim, forDeclaration, result) } @@ -237,14 +231,16 @@ object GraphResolutionHelper { for (methodParameterType in template.methodParameterTypes) { realParams.add(ComponentTemplateHelper.replace(ctx.resolver, methodParameterType, map)!!) } - result.add(ComponentDeclaration.FromExtensionComponent( - realReturnType, - sourceMethod, - realParams, - template.methodParameterTags, - template.tags, - template.generator - )) + result.add( + ComponentDeclaration.FromExtensionComponent( + realReturnType, + sourceMethod, + realParams, + template.methodParameterTags, + template.tags, + template.generator + ) + ) } is ComponentDeclaration.DiscoveredAsDependencyComponent -> throw IllegalStateException() @@ -296,11 +292,7 @@ object GraphResolutionHelper { return exactMatch[0] } - val deps = declarations.asSequence().map { it.toString() }.joinToString("\n").prependIndent(" ") - throw ProcessingErrorException( - "More than one component matches dependency claim ${dependencyClaim.type.declaration.qualifiedName?.asString()} tag=${dependencyClaim.tags}:\n$deps", - forDeclaration.source - ) + throw DuplicateDependencyException(dependencyClaim, forDeclaration, declarations) } fun findDependencyDeclarations(ctx: ProcessingContext, sourceDeclarations: List, dependencyClaim: DependencyClaim): List { diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/exception/CircularDependencyException.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/exception/CircularDependencyException.kt index 4225ae017..32013095f 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/exception/CircularDependencyException.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/exception/CircularDependencyException.kt @@ -1,12 +1,45 @@ package ru.tinkoff.kora.kora.app.ksp.exception +import com.squareup.kotlinpoet.ksp.toClassName import ru.tinkoff.kora.kora.app.ksp.declaration.ComponentDeclaration +import ru.tinkoff.kora.ksp.common.CommonClassNames +import ru.tinkoff.kora.ksp.common.exception.ProcessingError import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException +import java.util.stream.Collectors data class CircularDependencyException( - val cycle: List, - val source: ComponentDeclaration + val cycle: List, + val declaration: ComponentDeclaration ) : ProcessingErrorException( - String.format("There's a cycle in graph: \n\t%s", cycle.joinToString("\n\t")), - source.source -) + getError(cycle, declaration) +) { + + companion object { + private fun getError( + cycle: List, + declaration: ComponentDeclaration + ): ProcessingError { + val deps = cycle + .map { String.format("- %s", it.declarationString()) } + .joinToString("\n", "Cycle dependency candidates:\n", "").prependIndent(" ") + + if (declaration.tags.isEmpty()) { + return ProcessingError( + """Encountered circular dependency in graph for source type: ${declaration.type.toClassName()} (no tags) + $deps + Please check that you are not using cycle dependency in ${CommonClassNames.lifecycle}, this is forbidden.""".trimIndent(), + declaration.source + ) + } else { + val tagMsg: String = declaration.tags.stream() + .collect(Collectors.joining(", ", "@Tag(", ")")) + return ProcessingError( + """Encountered circular dependency in graph for source type: ${declaration.type.toClassName()} with $tagMsg + $deps + Please check that you are not using cycle dependency in ${CommonClassNames.lifecycle}, this is forbidden.""".trimIndent(), + declaration.source + ) + } + } + } +} diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/exception/DuplicateDependencyException.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/exception/DuplicateDependencyException.kt new file mode 100644 index 000000000..374dda42b --- /dev/null +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/exception/DuplicateDependencyException.kt @@ -0,0 +1,77 @@ +package ru.tinkoff.kora.kora.app.ksp.exception + +import com.squareup.kotlinpoet.ksp.toClassName +import ru.tinkoff.kora.kora.app.ksp.component.ComponentDependency +import ru.tinkoff.kora.kora.app.ksp.component.DependencyClaim +import ru.tinkoff.kora.kora.app.ksp.declaration.ComponentDeclaration +import ru.tinkoff.kora.ksp.common.exception.ProcessingError +import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException +import java.util.stream.Collectors + +data class DuplicateDependencyException( + val claim: DependencyClaim, + val declaration: ComponentDeclaration, + val foundDeclarations: List +) : ProcessingErrorException( + listOf(getErrorForDeclarations(claim, declaration, foundDeclarations)) +) { + + constructor( + foundDeclarations: List, + claim: DependencyClaim, + declaration: ComponentDeclaration + ) : this( + claim, declaration, foundDeclarations.map { it.component!!.declaration }.toList() + ) + + companion object { + private fun getErrorForDeclarations( + claim: DependencyClaim, + declaration: ComponentDeclaration, + foundDeclarations: List + ): ProcessingError { + val deps = foundDeclarations + .map { String.format("- %s", it.declarationString()) } + .joinToString("\n", "Candidates for injection:\n", "").prependIndent(" ") + + return getError(claim, declaration, deps) + } + + private fun getErrorForDependencies( + claim: DependencyClaim, + declaration: ComponentDeclaration, + foundDeclarations: List + ): ProcessingError { + val deps: String = foundDeclarations + .map { it.component!!.declaration } + .map { String.format("- %s", it.declarationString()) } + .joinToString("\n", "Candidates for injection:\n", "").prependIndent(" ") + + return getError(claim, declaration, deps) + } + + private fun getError( + claim: DependencyClaim, + declaration: ComponentDeclaration, + deps: String + ): ProcessingError { + if (claim.tags.isEmpty()) { + return ProcessingError( + """More than one component matches dependency type: ${claim.type.toClassName()} (no tags) + $deps + Please check that injection dependency is declared correctly or that @DefaultComponent annotation is not missing if was intended.""".trimIndent(), + declaration.source + ) + } else { + val tagMsg: String = claim.tags.stream() + .collect(Collectors.joining(", ", "@Tag(", ")")) + return ProcessingError( + """More than one component matches dependency type: ${claim.type.toClassName()} with $tagMsg + $deps + Please check that injection dependency is declared correctly or that @DefaultComponent annotation is not missing if was intended.""".trimIndent(), + declaration.source + ) + } + } + } +} diff --git a/kora-app-symbol-processor/src/test/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppKspTest.kt b/kora-app-symbol-processor/src/test/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppKspTest.kt index f9d85a8ff..73479156c 100644 --- a/kora-app-symbol-processor/src/test/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppKspTest.kt +++ b/kora-app-symbol-processor/src/test/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppKspTest.kt @@ -219,7 +219,7 @@ class KoraAppKspTest { Assertions.assertThatThrownBy { testClass(AppWithCircularDependency::class) } .isInstanceOfSatisfying(CompilationErrorException::class.java) { e -> SoftAssertions.assertSoftly { s: SoftAssertions -> - s.assertThat(e.messages).anyMatch { it.contains("There's a cycle in graph: ") } + s.assertThat(e.messages).anyMatch { it.contains("Encountered circular dependency in graph for source type") } } } } @@ -247,7 +247,7 @@ class KoraAppKspTest { Assertions.assertThatThrownBy { testClass(AppWithFactories6::class) } .isInstanceOfSatisfying(CompilationErrorException::class.java) { e -> SoftAssertions.assertSoftly { s: SoftAssertions -> - s.assertThat(e.messages).anyMatch { it.contains("There's a cycle in graph: ") } + s.assertThat(e.messages).anyMatch { it.contains("Encountered circular dependency in graph for source type") } } } @@ -308,7 +308,7 @@ class KoraAppKspTest { .isInstanceOfSatisfying(CompilationErrorException::class.java) { e -> SoftAssertions.assertSoftly { s: SoftAssertions -> s.assertThat(e.messages) - .anyMatch { it.contains("More than one component matches dependency claim ru.tinkoff.kora.kora.app.ksp.app.AppWithComponentCollisionAndDirect.Class1 tag=[]:") } + .anyMatch { it.contains("More than one component matches dependency type: ru.tinkoff.kora.kora.app.ksp.app.AppWithComponentCollisionAndDirect.Class1") } } } }