Skip to content

Commit 5a78005

Browse files
authored
Improved error message output (#489)
* Improve hint message * Added DuplicateDependencyException and improved error message * Improved CircularDependencyException error message * Fixed tests
1 parent 444b3ec commit 5a78005

File tree

12 files changed

+249
-80
lines changed

12 files changed

+249
-80
lines changed

kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/DependencyModuleHintProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public String message() {
4141
if (tags.isEmpty()) {
4242
return """
4343
Missing component: %s
44-
Component can be provided by standard Kora module you may forgot to plug it:
44+
Component is provided by standard Kora module you may forgot to plug it:
4545
Gradle dependency: implementation("%s")
4646
Module interface: %s
4747
""".formatted(type, artifact, module);

kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import ru.tinkoff.kora.kora.app.annotation.processor.component.ResolvedComponent;
1212
import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration;
1313
import ru.tinkoff.kora.kora.app.annotation.processor.exception.CircularDependencyException;
14+
import ru.tinkoff.kora.kora.app.annotation.processor.exception.DuplicateDependencyException;
1415
import ru.tinkoff.kora.kora.app.annotation.processor.exception.NewRoundException;
1516
import ru.tinkoff.kora.kora.app.annotation.processor.exception.UnresolvedDependencyException;
1617
import ru.tinkoff.kora.kora.app.annotation.processor.extension.ExtensionResult;
@@ -145,13 +146,7 @@ public static ProcessingState processProcessing(ProcessingContext ctx, RoundEnvi
145146
}
146147
}
147148
if (results.size() > 1) {
148-
var deps = templates.stream().map(Objects::toString).collect(Collectors.joining("\n")).indent(2);
149-
if (dependencyClaim.tags().isEmpty()) {
150-
throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, declaration.source());
151-
} else {
152-
var tagMsg = dependencyClaim.tags().stream().collect(Collectors.joining(", ", "@Tag(", ")"));
153-
throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + " with tag " + tagMsg + " :\n" + deps, declaration.source());
154-
}
149+
throw new DuplicateDependencyException(dependencyClaim, declaration, templates);
155150
}
156151
throw exception;
157152
}
@@ -477,17 +472,17 @@ private static boolean checkCycle(ProcessingContext ctx, ProcessingState.Process
477472
var dependencyClaimType = dependencyClaim.type();
478473
var dependencyClaimTypeElement = ctx.types.asElement(dependencyClaimType);
479474
if (!(ctx.types.isAssignable(declaration.type(), dependencyClaimType) || ctx.serviceTypeHelper.isAssignableToUnwrapped(declaration.type(), dependencyClaimType) || ctx.serviceTypeHelper.isInterceptor(declaration.type()))) {
480-
throw new CircularDependencyException(List.of(prevComponent.declaration().toString(), declaration.toString()), declaration);
475+
throw new CircularDependencyException(List.of(prevComponent.declaration(), declaration), declaration);
481476
}
482477
for (var inStackFrame : processing.resolutionStack()) {
483478
if (!(inStackFrame instanceof ProcessingState.ResolutionFrame.Component componentFrame) || componentFrame.declaration() != declaration) {
484479
continue;
485480
}
486481
if (dependencyClaim.type().getKind() != TypeKind.DECLARED) {
487-
throw new CircularDependencyException(List.of(prevComponent.declaration().toString(), declaration.toString()), componentFrame.declaration());
482+
throw new CircularDependencyException(List.of(prevComponent.declaration(), declaration), componentFrame.declaration());
488483
}
489484
if (dependencyClaimTypeElement.getKind() != ElementKind.INTERFACE && (dependencyClaimTypeElement.getKind() != ElementKind.CLASS || dependencyClaimTypeElement.getModifiers().contains(Modifier.FINAL))) {
490-
throw new CircularDependencyException(List.of(prevComponent.declaration().toString(), declaration.toString()), componentFrame.declaration());
485+
throw new CircularDependencyException(List.of(prevComponent.declaration(), declaration), componentFrame.declaration());
491486
}
492487
var proxyDependencyClaim = new DependencyClaim(
493488
dependencyClaimType, Set.of(CommonClassNames.promisedProxy.canonicalName()), dependencyClaim.claimType()

kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphResolutionHelper.java

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
import ru.tinkoff.kora.kora.app.annotation.processor.component.DependencyClaim;
99
import ru.tinkoff.kora.kora.app.annotation.processor.component.ResolvedComponent;
1010
import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration;
11+
import ru.tinkoff.kora.kora.app.annotation.processor.exception.DuplicateDependencyException;
1112

1213
import javax.lang.model.element.*;
1314
import javax.lang.model.type.*;
1415
import java.util.ArrayList;
1516
import java.util.List;
16-
import java.util.Objects;
1717
import java.util.function.Predicate;
18-
import java.util.stream.Collectors;
1918

2019
import static ru.tinkoff.kora.kora.app.annotation.processor.component.DependencyClaim.DependencyClaimType.*;
2120

@@ -37,12 +36,7 @@ public static ComponentDependency.SingleDependency findDependency(ProcessingCont
3736
return null;
3837
}
3938

40-
var deps = dependencies.stream()
41-
.map(ComponentDependency.SingleDependency::component)
42-
.map(Objects::toString)
43-
.collect(Collectors.joining("\n")).indent(2);
44-
45-
throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, forDeclaration.source());
39+
throw new DuplicateDependencyException(dependencies, dependencyClaim, forDeclaration);
4640
}
4741

4842
public static List<ComponentDependency.SingleDependency> findDependencies(ProcessingContext ctx, List<ResolvedComponent> resolvedComponents, DependencyClaim dependencyClaim) {
@@ -152,15 +146,14 @@ public static ComponentDeclaration findDependencyDeclarationFromTemplate(Process
152146
if (declarations.size() == 1) {
153147
return declarations.get(0);
154148
}
155-
var exactMatch = declarations.stream().filter(d -> ctx.types.isSameType(
156-
d.type(),
157-
dependencyClaim.type()
158-
)).toList();
149+
var exactMatch = declarations.stream()
150+
.filter(d -> ctx.types.isSameType(d.type(), dependencyClaim.type()))
151+
.toList();
159152
if (exactMatch.size() == 1) {
160153
return exactMatch.get(0);
161154
}
162-
var deps = declarations.stream().map(Objects::toString).collect(Collectors.joining("\n")).indent(2);
163-
throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, forDeclaration.source());
155+
156+
throw new DuplicateDependencyException(dependencyClaim, forDeclaration, declarations);
164157
}
165158

166159
public static List<ComponentDeclaration> findDependencyDeclarationsFromTemplate(ProcessingContext ctx, ComponentDeclaration forDeclaration, List<ComponentDeclaration> sourceDeclarations, DependencyClaim dependencyClaim) {
@@ -326,8 +319,7 @@ public static ComponentDeclaration findDependencyDeclaration(ProcessingContext c
326319
return nonDefaultComponents.get(0);
327320
}
328321

329-
var deps = declarations.stream().map(Objects::toString).collect(Collectors.joining("\n")).indent(2);
330-
throw new ProcessingErrorException("More than one component matches dependency claim " + dependencyClaim.type() + ":\n" + deps, forDeclaration.source());
322+
throw new DuplicateDependencyException(dependencyClaim, forDeclaration, declarations);
331323
}
332324

333325
public static List<ComponentDeclaration> findDependencyDeclarations(ProcessingContext ctx, List<ComponentDeclaration> sourceDeclarations, DependencyClaim dependencyClaim) {
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
11
package ru.tinkoff.kora.kora.app.annotation.processor.exception;
22

3+
import com.squareup.javapoet.TypeName;
4+
import ru.tinkoff.kora.annotation.processor.common.CommonClassNames;
5+
import ru.tinkoff.kora.annotation.processor.common.ProcessingError;
36
import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException;
47
import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration;
58

69
import java.util.List;
10+
import java.util.stream.Collectors;
711

812
public class CircularDependencyException extends ProcessingErrorException {
9-
public CircularDependencyException(List<String> cycle, ComponentDeclaration source) {
10-
super(String.format("There's a cycle in graph: \n\t%s", String.join("\n\t", cycle)), source.source());
13+
14+
public CircularDependencyException(List<ComponentDeclaration> cycle, ComponentDeclaration declaration) {
15+
super(getError(cycle, declaration));
16+
}
17+
18+
private static ProcessingError getError(List<ComponentDeclaration> cycle,
19+
ComponentDeclaration declaration) {
20+
var deps = cycle.stream()
21+
.map(c -> String.format("- %s", c.declarationString()))
22+
.collect(Collectors.joining("\n", "Cycle dependency candidates:\n", "")).indent(2);
23+
24+
if (declaration.tags().isEmpty()) {
25+
return new ProcessingError("Encountered circular dependency in graph for source type: " + TypeName.get(declaration.type()) + " (no tags)\n"
26+
+ deps
27+
+ "\nPlease check that you are not using cycle dependency in %s, this is forbidden.".formatted(CommonClassNames.lifecycle),
28+
declaration.source());
29+
} else {
30+
var tagMsg = declaration.tags().stream()
31+
.collect(Collectors.joining(", ", "@Tag(", ")"));
32+
return new ProcessingError("Encountered circular dependency in graph for source type: " + TypeName.get(declaration.type()) + " with " + tagMsg + "\n"
33+
+ deps
34+
+ "\nPlease check that you are not using cycle dependency in %s, this is forbidden.".formatted(CommonClassNames.lifecycle),
35+
declaration.source());
36+
}
1137
}
1238
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package ru.tinkoff.kora.kora.app.annotation.processor.exception;
2+
3+
import com.squareup.javapoet.TypeName;
4+
import ru.tinkoff.kora.annotation.processor.common.ProcessingError;
5+
import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException;
6+
import ru.tinkoff.kora.kora.app.annotation.processor.component.ComponentDependency;
7+
import ru.tinkoff.kora.kora.app.annotation.processor.component.DependencyClaim;
8+
import ru.tinkoff.kora.kora.app.annotation.processor.declaration.ComponentDeclaration;
9+
10+
import java.util.List;
11+
import java.util.stream.Collectors;
12+
13+
public class DuplicateDependencyException extends ProcessingErrorException {
14+
15+
public DuplicateDependencyException(DependencyClaim claim,
16+
ComponentDeclaration declaration,
17+
List<ComponentDeclaration> foundDeclarations) {
18+
super(List.of(getErrorForDeclarations(claim, declaration, foundDeclarations)));
19+
}
20+
21+
public DuplicateDependencyException(List<ComponentDependency.SingleDependency> foundDeclarations,
22+
DependencyClaim claim,
23+
ComponentDeclaration declaration) {
24+
super(List.of(getErrorForDependencies(claim, declaration, foundDeclarations)));
25+
}
26+
27+
private static ProcessingError getErrorForDeclarations(DependencyClaim claim,
28+
ComponentDeclaration declaration,
29+
List<ComponentDeclaration> foundDeclarations) {
30+
var deps = foundDeclarations.stream()
31+
.map(c -> String.format("- %s", c.declarationString()))
32+
.collect(Collectors.joining("\n", "Candidates for injection:\n", "")).indent(2);
33+
34+
return getError(claim, declaration, deps);
35+
}
36+
37+
private static ProcessingError getErrorForDependencies(DependencyClaim claim,
38+
ComponentDeclaration declaration,
39+
List<ComponentDependency.SingleDependency> foundDeclarations) {
40+
var deps = foundDeclarations.stream()
41+
.map(ComponentDependency.SingleDependency::component)
42+
.map(c -> String.format("- %s", c.declaration().declarationString()))
43+
.collect(Collectors.joining("\n", "Candidates for injection:\n", "")).indent(2);
44+
45+
return getError(claim, declaration, deps);
46+
}
47+
48+
private static ProcessingError getError(DependencyClaim claim,
49+
ComponentDeclaration declaration,
50+
String deps) {
51+
if (claim.tags().isEmpty()) {
52+
return new ProcessingError("More than one component matches dependency type: " + TypeName.get(claim.type()) + " (no tags)\n"
53+
+ deps
54+
+ "\nPlease check that injection dependency is declared correctly or that @DefaultComponent annotation is not missing if was intended.",
55+
declaration.source());
56+
} else {
57+
var tagMsg = claim.tags().stream()
58+
.collect(Collectors.joining(", ", "@Tag(", ")"));
59+
return new ProcessingError("More than one component matches dependency type: " + TypeName.get(claim.type()) + " with " + tagMsg + "\n"
60+
+ deps
61+
+ "\nPlease check that injection dependency is declared correctly or that @DefaultComponent annotation is not missing if was intended.",
62+
declaration.source());
63+
}
64+
}
65+
}

kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ void unresolvedDependency() {
171171
void testCircularDependency() {
172172
assertThatThrownBy(() -> testClass(AppWithCircularDependency.class))
173173
.isInstanceOfSatisfying(CompilationErrorException.class, e -> SoftAssertions.assertSoftly(s -> {
174-
s.assertThat(e.getMessage()).startsWith("There's a cycle in graph: ");
174+
s.assertThat(e.getMessage()).startsWith("Encountered circular dependency in graph for source type:");
175175
s.assertThat(e.diagnostics.get(0).getSource().getName()).isEqualTo("src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/app/AppWithCircularDependency.java");
176176
}));
177177
}
@@ -194,7 +194,7 @@ void appWithFactory() throws Throwable {
194194
// testClass(AppWithFactories5.class).init();; TODO больше не нужно
195195
assertThatThrownBy(() -> testClass(AppWithFactories6.class))
196196
.isInstanceOf(CompilationErrorException.class)
197-
.hasMessageStartingWith("There's a cycle in graph:");
197+
.hasMessageStartingWith("Encountered circular dependency in graph for source type");
198198
testClass(AppWithFactories7.class).init();
199199
testClass(AppWithFactories8.class).init();
200200
testClass(AppWithFactories9.class).init();
@@ -261,11 +261,7 @@ void appWithComponentDescriptorCollisionAndDirect() {
261261
.isInstanceOfSatisfying(CompilationErrorException.class, e -> SoftAssertions.assertSoftly(s -> {
262262
var error = e.getDiagnostics().stream().filter(d -> d.getKind() == Diagnostic.Kind.ERROR).findFirst().get();
263263
s.assertThat(error.getMessage(Locale.US))
264-
.startsWith("More than one component matches dependency claim ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect.Class1:");
265-
266-
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()");
267-
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()");
268-
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()");
264+
.startsWith("More than one component matches dependency type: ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithComponentCollisionAndDirect.Class1");
269265
}));
270266
}
271267

kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/DependencyModuleHintProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class DependencyModuleHintProvider(private val resolver: Resolver) {
4040
if (tags.isEmpty()) {
4141
return """
4242
Missing component: ${type.toTypeName()}
43-
Component can be provided by standard Kora module you may forgot to plug it:
43+
Component is provided by standard Kora module you may forgot to plug it:
4444
Gradle dependency: implementation("$artifact")
4545
Module interface: $module
4646
""".trimIndent()
@@ -55,7 +55,7 @@ class DependencyModuleHintProvider(private val resolver: Resolver) {
5555

5656
return """
5757
Missing component: ${type.toTypeName()} with $tagForMsg
58-
Component can be provided by standard Kora module you may forgot to plug it:
58+
Component is provided by standard Kora module you may forgot to plug it:
5959
Gradle dependency: implementation("$artifact")
6060
Module interface: $module
6161
""".trimIndent()

kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ru.tinkoff.kora.kora.app.ksp.component.DependencyClaim.DependencyClaimTyp
1414
import ru.tinkoff.kora.kora.app.ksp.component.ResolvedComponent
1515
import ru.tinkoff.kora.kora.app.ksp.declaration.ComponentDeclaration
1616
import ru.tinkoff.kora.kora.app.ksp.exception.CircularDependencyException
17+
import ru.tinkoff.kora.kora.app.ksp.exception.DuplicateDependencyException
1718
import ru.tinkoff.kora.kora.app.ksp.exception.NewRoundException
1819
import ru.tinkoff.kora.kora.app.ksp.exception.UnresolvedDependencyException
1920
import ru.tinkoff.kora.kora.app.ksp.extension.ExtensionResult
@@ -142,15 +143,7 @@ object GraphBuilder {
142143
}
143144
}
144145
if (results.size > 1) {
145-
val deps = templates.stream().map { Objects.toString(it) }
146-
.collect(Collectors.joining("\n"))
147-
.prependIndent(" ")
148-
throw ProcessingErrorException(
149-
"""
150-
More than one component matches dependency claim ${dependencyClaim.type}:
151-
$deps
152-
""".trimIndent(), declaration.source
153-
)
146+
throw DuplicateDependencyException(dependencyClaim, declaration, templates)
154147
}
155148
throw exception!!
156149
}
@@ -538,7 +531,7 @@ object GraphBuilder {
538531
if (frame !is ProcessingState.ResolutionFrame.Component || frame.declaration !== declaration) {
539532
continue
540533
}
541-
val circularDependencyException = CircularDependencyException(listOf(prevFrame.declaration.toString(), declaration.toString()), frame.declaration)
534+
val circularDependencyException = CircularDependencyException(listOf(prevFrame.declaration, declaration), frame.declaration)
542535
if (claimTypeDeclaration !is KSClassDeclaration) throw circularDependencyException
543536
if (claimTypeDeclaration.classKind != ClassKind.INTERFACE && !(claimTypeDeclaration.classKind == ClassKind.CLASS && claimTypeDeclaration.isOpen())) throw circularDependencyException
544537
val proxyDependencyClaim = DependencyClaim(

0 commit comments

Comments
 (0)