blob: ccff741a8b61326e6b8425533dc90f68995411ac [file] [log] [blame]
/*
* Copyright (C) 2014 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.internal.codegen;
import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass;
import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.util.Source;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dagger.testing.compile.CompilerTests;
import dagger.testing.golden.GoldenFileRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ModuleFactoryGeneratorTest {
private static final Source NON_TYPE_USE_NULLABLE =
CompilerTests.javaSource(
"test.Nullable", // force one-string-per-line format
"package test;",
"",
"public @interface Nullable {}");
@Rule public GoldenFileRule goldenFileRule = new GoldenFileRule();
// TODO(gak): add tests for invalid combinations of scope and qualifier annotations like we have
// for @Inject
@Test public void providesMethodNotInModule() {
assertThatMethodInUnannotatedClass("@Provides String provideString() { return null; }")
.hasError("@Provides methods can only be present within a @Module or @ProducerModule");
}
@Test public void providesMethodAbstract() {
assertThatModuleMethod("@Provides abstract String abstractMethod();")
.hasError("@Provides methods cannot be abstract");
}
@Test public void providesMethodPrivate() {
assertThatModuleMethod("@Provides private String privateMethod() { return null; }")
.hasError("@Provides methods cannot be private");
}
@Test public void providesMethodReturnVoid() {
assertThatModuleMethod("@Provides void voidMethod() {}")
.hasError("@Provides methods must return a value (not void)");
}
@Test
public void providesMethodReturnsProvider() {
assertThatModuleMethod("@Provides Provider<String> provideProvider() {}")
.hasError("@Provides methods must not return framework types");
}
@Test
public void providesMethodReturnsJakartaProvider() {
assertThatModuleMethod("@Provides jakarta.inject.Provider<String> provideProvider() {}")
.hasError("@Provides methods must not return framework types");
}
@Test
public void providesMethodReturnsDaggerInternalProvider() {
assertThatModuleMethod("@Provides dagger.internal.Provider<String> provideProvider() {}")
.hasError("@Provides methods must not return disallowed types");
}
@Test
public void providesIntoSetMethodReturnsDaggerInternalProvider() {
assertThatModuleMethod(
"@Provides @IntoSet dagger.internal.Provider<String> provideProvider() {}")
.hasError("@Provides methods must not return disallowed types");
}
@Test
public void providesMethodReturnsLazy() {
assertThatModuleMethod("@Provides Lazy<String> provideLazy() {}")
.hasError("@Provides methods must not return framework types");
}
@Test
public void providesMethodReturnsMembersInjector() {
assertThatModuleMethod("@Provides MembersInjector<String> provideMembersInjector() {}")
.hasError("@Provides methods must not return framework types");
}
@Test
public void providesMethodReturnsProducer() {
assertThatModuleMethod("@Provides Producer<String> provideProducer() {}")
.hasError("@Provides methods must not return framework types");
}
@Test
public void providesMethodReturnsProduced() {
assertThatModuleMethod("@Provides Produced<String> provideProduced() {}")
.hasError("@Provides methods must not return framework types");
}
@Test public void providesMethodWithTypeParameter() {
assertThatModuleMethod("@Provides <T> String typeParameter() { return null; }")
.hasError("@Provides methods may not have type parameters");
}
@Test public void providesMethodSetValuesWildcard() {
assertThatModuleMethod("@Provides @ElementsIntoSet Set<?> provideWildcard() { return null; }")
.hasError(
"@Provides methods must return a primitive, an array, a type variable, "
+ "or a declared type");
}
@Test public void providesMethodSetValuesRawSet() {
assertThatModuleMethod("@Provides @ElementsIntoSet Set provideSomething() { return null; }")
.hasError("@Provides methods annotated with @ElementsIntoSet cannot return a raw Set");
}
@Test public void providesElementsIntoSetMethodReturnsSetDaggerProvider() {
assertThatModuleMethod(
"@Provides @ElementsIntoSet Set<dagger.internal.Provider<String>> provideProvider() {}")
.hasError("@Provides methods must not return disallowed types");
}
@Test public void providesMethodSetValuesNotASet() {
assertThatModuleMethod(
"@Provides @ElementsIntoSet List<String> provideStrings() { return null; }")
.hasError("@Provides methods annotated with @ElementsIntoSet must return a Set");
}
@Test
public void bindsMethodReturnsProvider() {
assertThatModuleMethod("@Binds abstract Provider<Number> bindsProvider(Provider<Long> impl);")
.hasError("@Binds methods must not return framework types");
}
@Test
public void bindsMethodReturnsDaggerProvider() {
assertThatModuleMethod("@Binds abstract dagger.internal.Provider<Number> "
+ "bindsProvider(dagger.internal.Provider<Long> impl);")
.hasError("@Binds methods must not return disallowed types");
}
@Test public void modulesWithTypeParamsMustBeAbstract() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"",
"@Module",
"final class TestModule<A> {}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining("Modules with type parameters must be abstract")
.onSource(moduleFile)
.onLine(6);
});
}
@Test public void provideOverriddenByNoProvide() {
Source parent =
CompilerTests.javaSource(
"test.Parent",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"class Parent {",
" @Provides String foo() { return null; }",
"}");
assertThatModuleMethod("String foo() { return null; }")
.withDeclaration("@Module class %s extends Parent { %s }")
.withAdditionalSources(parent)
.hasError(
"Binding methods may not be overridden in modules. Overrides: "
+ "@Provides String test.Parent.foo()");
}
@Test public void provideOverriddenByProvide() {
Source parent =
CompilerTests.javaSource(
"test.Parent",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"class Parent {",
" @Provides String foo() { return null; }",
"}");
assertThatModuleMethod("@Provides String foo() { return null; }")
.withDeclaration("@Module class %s extends Parent { %s }")
.withAdditionalSources(parent)
.hasError(
"Binding methods may not override another method. Overrides: "
+ "@Provides String test.Parent.foo()");
}
@Test public void providesOverridesNonProvides() {
Source parent =
CompilerTests.javaSource(
"test.Parent",
"package test;",
"",
"import dagger.Module;",
"",
"@Module",
"class Parent {",
" String foo() { return null; }",
"}");
assertThatModuleMethod("@Provides String foo() { return null; }")
.withDeclaration("@Module class %s extends Parent { %s }")
.withAdditionalSources(parent)
.hasError(
"Binding methods may not override another method. Overrides: "
+ "String test.Parent.foo()");
}
@Test public void validatesIncludedModules() {
Source module =
CompilerTests.javaSource(
"test.Parent",
"package test;",
"",
"import dagger.Module;",
"",
"@Module(",
" includes = {",
" Void.class,",
" String.class,",
" }",
")",
"class TestModule {}");
CompilerTests.daggerCompiler(module)
.compile(
subject -> {
subject.hasErrorCount(2);
// We avoid asserting on the line number because ksp and javac report different lines.
// The main issue here is that ksp doesn't allow reporting errors on individual
// annotation values, it only allows reporting errors on annotations themselves.
subject.hasErrorContaining(
"java.lang.Void is listed as a module, but is not annotated with @Module")
.onSource(module);
subject.hasErrorContaining(
"java.lang.String is listed as a module, but is not annotated with @Module")
.onSource(module);
});
}
@Test public void singleProvidesMethodNoArgs() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides String provideString() {",
" return \"\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/TestModule_ProvideStringFactory"));
});
}
@Test public void singleProvidesMethodNoArgs_disableNullable() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides String provideString() {",
" return \"\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.withProcessingOptions(ImmutableMap.of("dagger.nullableValidation", "WARNING"))
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/TestModule_ProvideStringFactory"));
});
}
@Test
public void nonTypeUseNullableProvides() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides @Nullable String provideString() { return null; }",
"}");
CompilerTests.daggerCompiler(moduleFile, NON_TYPE_USE_NULLABLE)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/TestModule_ProvideStringFactory"));
});
}
@Test
public void kotlinNullableProvides() {
Source moduleFile =
CompilerTests.kotlinSource(
"TestModule.kt",
"package test",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"class TestModule {",
" @Provides fun provideString(): String? { return null; }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(0);
boolean isJavac = CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC;
subject.generatedSource(
CompilerTests.javaSource(
"test.TestModule_ProvideStringFactory",
"package test;",
"",
"import dagger.internal.DaggerGenerated;",
"import dagger.internal.Factory;",
"import dagger.internal.QualifierMetadata;",
"import dagger.internal.ScopeMetadata;",
"import javax.annotation.processing.Generated;",
isJavac ? "import org.jetbrains.annotations.Nullable;\n" : "",
"@ScopeMetadata",
"@QualifierMetadata",
"@DaggerGenerated",
"@Generated(",
" value = \"dagger.internal.codegen.ComponentProcessor\",",
" comments = \"https://dagger.dev\"",
")",
"@SuppressWarnings({",
" \"unchecked\",",
" \"rawtypes\",",
" \"KotlinInternal\",",
" \"KotlinInternalInJava\",",
" \"cast\",",
" \"deprecation\",",
" \"nullness:initialization.field.uninitialized\"",
"})",
"public final class TestModule_ProvideStringFactory implements"
+ " Factory<String> {",
" private final TestModule module;",
"",
" public TestModule_ProvideStringFactory(TestModule module) {",
" this.module = module;",
" }",
"",
// TODO(b/368129744): KSP should output the @Nullable annotation after this
// bug is fixed.
isJavac ? " @Override\n @Nullable" : " @Override",
" public String get() {",
" return provideString(module);",
" }",
"",
" public static TestModule_ProvideStringFactory create(TestModule module) {",
" return new TestModule_ProvideStringFactory(module);",
" }",
// TODO(b/368129744): KSP should output the @Nullable annotation after this
// bug is fixed.
isJavac ? "\n @Nullable" : "",
" public static String provideString(TestModule instance) {",
" return instance.provideString();",
" }",
"}"));
});
}
@Test public void multipleProvidesMethods() {
Source classXFile =
CompilerTests.javaSource("test.X",
"package test;",
"",
"import javax.inject.Inject;",
"",
"class X {",
" @Inject public String s;",
"}");
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.MembersInjector;",
"import dagger.Module;",
"import dagger.Provides;",
"import java.util.Arrays;",
"import java.util.List;",
"",
"@Module",
"final class TestModule {",
" @Provides List<Object> provideObjects(",
" @QualifierA Object a, @QualifierB Object b, MembersInjector<X> xInjector) {",
" return Arrays.asList(a, b);",
" }",
"",
" @Provides @QualifierA Object provideAObject() {",
" return new Object();",
" }",
"",
" @Provides @QualifierB Object provideBObject() {",
" return new Object();",
" }",
"}");
CompilerTests.daggerCompiler(classXFile, moduleFile, QUALIFIER_A, QUALIFIER_B)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/TestModule_ProvideObjectsFactory"));
});
}
@Test
public void providesSetElement() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import java.util.logging.Logger;",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.IntoSet;",
"",
"@Module",
"final class TestModule {",
" @Provides @IntoSet String provideString() {",
" return \"\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/TestModule_ProvideStringFactory"));
});
}
@Test public void providesSetElementWildcard() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import java.util.logging.Logger;",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.IntoSet;",
"import java.util.ArrayList;",
"import java.util.List;",
"",
"@Module",
"final class TestModule {",
" @Provides @IntoSet List<List<?>> provideWildcardList() {",
" return new ArrayList<>();",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/TestModule_ProvideWildcardListFactory"));
});
}
@Test public void providesSetValues() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.ElementsIntoSet;",
"import java.util.Set;",
"",
"@Module",
"final class TestModule {",
" @Provides @ElementsIntoSet Set<String> provideStrings() {",
" return null;",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/TestModule_ProvideStringsFactory"));
});
}
@Test public void multipleProvidesMethodsWithSameName() {
Source moduleFile =
CompilerTests.javaSource("test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides Object provide(int i) {",
" return i;",
" }",
"",
" @Provides String provide() {",
" return \"\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(2);
subject.hasErrorContaining(
"Cannot have more than one binding method with the same name in a single "
+ "module")
.onSource(moduleFile)
.onLine(8);
subject.hasErrorContaining(
"Cannot have more than one binding method with the same name in a single "
+ "module")
.onSource(moduleFile)
.onLine(12);
});
}
@Test
public void providesMethodThrowsChecked() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides int i() throws Exception {",
" return 0;",
" }",
"",
" @Provides String s() throws Throwable {",
" return \"\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(2);
subject.hasErrorContaining("@Provides methods may only throw unchecked exceptions")
.onSource(moduleFile)
.onLine(8);
subject.hasErrorContaining("@Provides methods may only throw unchecked exceptions")
.onSource(moduleFile)
.onLine(12);
});
}
@Test
public void providedTypes() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import java.io.Closeable;",
"import java.util.Set;",
"",
"@Module",
"final class TestModule {",
" @Provides String string() {",
" return null;",
" }",
"",
" @Provides Set<String> strings() {",
" return null;",
" }",
"",
" @Provides Set<? extends Closeable> closeables() {",
" return null;",
" }",
"",
" @Provides String[] stringArray() {",
" return null;",
" }",
"",
" @Provides int integer() {",
" return 0;",
" }",
"",
" @Provides int[] integers() {",
" return null;",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile).compile(subject -> subject.hasErrorCount(0));
}
@Test
public void privateModule() {
Source moduleFile =
CompilerTests.javaSource(
"test.Enclosing",
"package test;",
"",
"import dagger.Module;",
"",
"final class Enclosing {",
" @Module private static final class PrivateModule {",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining("Modules cannot be private")
.onSource(moduleFile)
.onLine(6);
});
}
@Test
public void privateModule_kotlin() {
Source moduleFile =
CompilerTests.kotlinSource(
"test.TestModule.kt",
"package test",
"",
"import dagger.Component",
"import dagger.Module",
"import dagger.Provides",
"",
"@Module",
"private class TestModule {",
" @Provides fun provideInt(): Int = 1",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(1);
subject
.hasErrorContaining("Modules cannot be private")
.onSource(moduleFile);
});
}
@Test
public void enclosedInPrivateModule() {
Source moduleFile =
CompilerTests.javaSource("test.Enclosing",
"package test;",
"",
"import dagger.Module;",
"",
"final class Enclosing {",
" private static final class PrivateEnclosing {",
" @Module static final class TestModule {",
" }",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining("Modules cannot be enclosed in private types")
.onSource(moduleFile)
.onLine(7);
});
}
@Test
public void publicModuleNonPublicIncludes() {
Source publicModuleFile =
CompilerTests.javaSource("test.PublicModule",
"package test;",
"",
"import dagger.Module;",
"",
"@Module(includes = {",
" BadNonPublicModule.class, OtherPublicModule.class, OkNonPublicModule.class",
"})",
"public final class PublicModule {",
"}");
Source badNonPublicModuleFile =
CompilerTests.javaSource(
"test.BadNonPublicModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class BadNonPublicModule {",
" @Provides",
" int provideInt() {",
" return 42;",
" }",
"}");
Source okNonPublicModuleFile =
CompilerTests.javaSource("test.OkNonPublicModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class OkNonPublicModule {",
" @Provides",
" static String provideString() {",
" return \"foo\";",
" }",
"}");
Source otherPublicModuleFile =
CompilerTests.javaSource("test.OtherPublicModule",
"package test;",
"",
"import dagger.Module;",
"",
"@Module",
"public final class OtherPublicModule {",
"}");
CompilerTests.daggerCompiler(
publicModuleFile,
badNonPublicModuleFile,
okNonPublicModuleFile,
otherPublicModuleFile)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
"This module is public, but it includes non-public (or effectively non-"
+ "public) modules (test.BadNonPublicModule) that have non-static, non-"
+ "abstract binding methods. Either reduce the visibility of this module"
+ ", make the included modules public, or make all of the binding "
+ "methods on the included modules abstract or static.")
.onSource(publicModuleFile)
.onLine(8);
});
}
@Test
public void genericSubclassedModule() {
Source parent =
CompilerTests.javaSource(
"test.ParentModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.IntoSet;",
"import dagger.multibindings.IntoMap;",
"import dagger.multibindings.StringKey;",
"import java.util.List;",
"import java.util.ArrayList;",
"",
"@Module",
"abstract class ParentModule<A extends CharSequence,",
" B,",
" C extends Number & Comparable<C>> {",
" @Provides List<B> provideListB(B b) {",
" List<B> list = new ArrayList<B>();",
" list.add(b);",
" return list;",
" }",
"",
" @Provides @IntoSet B provideBElement(B b) {",
" return b;",
" }",
"",
" @Provides @IntoMap @StringKey(\"b\") B provideBEntry(B b) {",
" return b;",
" }",
"}");
Source numberChild =
CompilerTests.javaSource("test.ChildNumberModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"class ChildNumberModule extends ParentModule<String, Number, Double> {",
" @Provides Number provideNumber() { return 1; }",
"}");
Source integerChild =
CompilerTests.javaSource("test.ChildIntegerModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"class ChildIntegerModule extends ParentModule<StringBuilder, Integer, Float> {",
" @Provides Integer provideInteger() { return 2; }",
"}");
Source component =
CompilerTests.javaSource("test.C",
"package test;",
"",
"import dagger.Component;",
"import java.util.List;",
"",
"@Component(modules={ChildNumberModule.class, ChildIntegerModule.class})",
"interface C {",
" List<Number> numberList();",
" List<Integer> integerList();",
"}");
CompilerTests.daggerCompiler(parent, numberChild, integerChild, component)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/ParentModule_ProvideListBFactory"));
subject.generatedSource(
goldenFileRule.goldenSource("test/ParentModule_ProvideBElementFactory"));
subject.generatedSource(
goldenFileRule.goldenSource("test/ParentModule_ProvideBEntryFactory"));
subject.generatedSource(
goldenFileRule.goldenSource("test/ChildNumberModule_ProvideNumberFactory"));
subject.generatedSource(
goldenFileRule.goldenSource("test/ChildIntegerModule_ProvideIntegerFactory"));
});
}
@Test public void parameterizedModuleWithStaticProvidesMethodOfGenericType() {
Source moduleFile =
CompilerTests.javaSource(
"test.ParameterizedModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import java.util.List;",
"import java.util.ArrayList;",
"import java.util.Map;",
"import java.util.HashMap;",
"",
"@Module abstract class ParameterizedModule<T> {",
" @Provides List<T> provideListT() {",
" return new ArrayList<>();",
" }",
"",
" @Provides static Map<String, Number> provideMapStringNumber() {",
" return new HashMap<>();",
" }",
"",
" @Provides static Object provideNonGenericType() {",
" return new Object();",
" }",
"",
" @Provides static String provideNonGenericTypeWithDeps(Object o) {",
" return o.toString();",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource(
"test/ParameterizedModule_ProvideMapStringNumberFactory"));
subject.generatedSource(
goldenFileRule.goldenSource(
"test/ParameterizedModule_ProvideNonGenericTypeFactory"));
subject.generatedSource(
goldenFileRule.goldenSource(
"test/ParameterizedModule_ProvideNonGenericTypeWithDepsFactory"));
});
}
private static final Source QUALIFIER_A =
CompilerTests.javaSource(
"test.QualifierA",
"package test;",
"",
"import javax.inject.Qualifier;",
"",
"@Qualifier @interface QualifierA {}");
private static final Source QUALIFIER_B =
CompilerTests.javaSource(
"test.QualifierB",
"package test;",
"",
"import javax.inject.Qualifier;",
"",
"@Qualifier @interface QualifierB {}");
@Test
public void providesMethodMultipleQualifiersOnMethod() {
Source moduleFile =
CompilerTests.javaSource("test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides",
" @QualifierA",
" @QualifierB",
" String provideString() {",
" return \"foo\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile, QUALIFIER_A, QUALIFIER_B)
.compile(
subject -> {
// There are 2 errors -- 1 per qualifier.
subject.hasErrorCount(2);
subject.hasErrorContaining("may not use more than one @Qualifier")
.onSource(moduleFile)
.onLine(9);
subject.hasErrorContaining("may not use more than one @Qualifier")
.onSource(moduleFile)
.onLine(10);
});
}
@Test
public void providesMethodMultipleQualifiersOnParameter() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides",
" static String provideString(",
" @QualifierA",
" @QualifierB",
" Object object) {",
" return \"foo\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile, QUALIFIER_A, QUALIFIER_B)
.compile(
subject -> {
// There are two errors -- 1 per qualifier.
subject.hasErrorCount(2);
subject.hasErrorContaining("may not use more than one @Qualifier")
.onSource(moduleFile)
.onLine(10);
subject.hasErrorContaining("may not use more than one @Qualifier")
.onSource(moduleFile)
.onLine(11);
});
}
@Test
public void providesMethodWildcardDependency() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import javax.inject.Provider;",
"",
"@Module",
"final class TestModule {",
" @Provides static String provideString(Provider<? extends Number> numberProvider) {",
" return \"foo\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile, QUALIFIER_A, QUALIFIER_B)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
"Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or "
+ "Produced<T> when T is a wildcard type such as ? extends java.lang.Number");
});
}
private static final Source SCOPE_A =
CompilerTests.javaSource(
"test.ScopeA",
"package test;",
"",
"import javax.inject.Scope;",
"",
"@Scope @interface ScopeA {}");
private static final Source SCOPE_B =
CompilerTests.javaSource(
"test.ScopeB",
"package test;",
"",
"import javax.inject.Scope;",
"",
"@Scope @interface ScopeB {}");
@Test
public void providesMethodMultipleScopes() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"final class TestModule {",
" @Provides",
" @ScopeA",
" @ScopeB",
" String provideString() {",
" return \"foo\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile, SCOPE_A, SCOPE_B)
.compile(
subject -> {
subject.hasErrorCount(2);
subject.hasErrorContaining("cannot use more than one @Scope")
.onSource(moduleFile)
.onLineContaining("@ScopeA");
subject.hasErrorContaining("cannot use more than one @Scope")
.onSource(moduleFile)
.onLineContaining("@ScopeB");
});
}
@Test public void providerDependsOnProduced() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.producers.Producer;",
"",
"@Module",
"final class TestModule {",
" @Provides String provideString(Producer<Integer> producer) {",
" return \"foo\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining("Producer may only be injected in @Produces methods");
});
}
@Test public void providerDependsOnProducer() {
Source moduleFile =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.producers.Produced;",
"",
"@Module",
"final class TestModule {",
" @Provides String provideString(Produced<Integer> produced) {",
" return \"foo\";",
" }",
"}");
CompilerTests.daggerCompiler(moduleFile)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining("Produced may only be injected in @Produces methods");
});
}
@Test
public void proxyMethodsConflictWithOtherFactoryMethods() throws Exception {
Source module =
CompilerTests.javaSource(
"test.TestModule",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"",
"@Module",
"interface TestModule {",
" @Provides",
" static int get() { return 1; }",
"",
" @Provides",
" static boolean create() { return true; }",
"}");
CompilerTests.daggerCompiler(module)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(goldenFileRule.goldenSource("test/TestModule_GetFactory"));
subject.generatedSource(goldenFileRule.goldenSource("test/TestModule_CreateFactory"));
});
}
@Test
public void testScopedMetadataOnStaticProvides() throws Exception {
Source module =
CompilerTests.javaSource(
"test.ScopedBinding",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import javax.inject.Singleton;",
"",
"@Module",
"interface MyModule {",
" @NonScope",
" @Singleton",
" @Provides",
" static String provideString() {",
" return \"\";",
" }",
"}");
Source nonScope =
CompilerTests.javaSource(
"test.NonScope",
"package test;",
"",
"@interface NonScope {}");
CompilerTests.daggerCompiler(module, nonScope)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/MyModule_ProvideStringFactory"));
});
}
@Test
public void testScopedMetadataOnNonStaticProvides() throws Exception {
Source module =
CompilerTests.javaSource(
"test.ScopedBinding",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import javax.inject.Singleton;",
"",
"@Module",
"class MyModule {",
" @NonScope",
" @Singleton",
" @Provides",
" String provideString() {",
" return \"\";",
" }",
"}");
Source nonScope =
CompilerTests.javaSource(
"test.NonScope",
"package test;",
"",
"@interface NonScope {}");
CompilerTests.daggerCompiler(module, nonScope)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/MyModule_ProvideStringFactory"));
});
}
@Test
public void testScopeMetadataWithCustomScope() throws Exception {
Source module =
CompilerTests.javaSource(
"test.ScopedBinding",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import javax.inject.Singleton;",
"",
"@Module",
"interface MyModule {",
" @NonScope(\"someValue\")",
" @CustomScope(\"someOtherValue\")",
" @Provides",
" static String provideString() {",
" return \"\";",
" }",
"}");
Source customScope =
CompilerTests.javaSource(
"test.CustomScope",
"package test;",
"",
"import javax.inject.Scope;",
"",
"@Scope",
"@interface CustomScope {",
" String value();",
"}");
Source nonScope =
CompilerTests.javaSource(
"test.NonScope",
"package test;",
"",
"@interface NonScope {",
" String value();",
"}");
CompilerTests.daggerCompiler(module, customScope, nonScope)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/MyModule_ProvideStringFactory"));
});
}
@Test
public void testQualifierMetadataOnProvides() throws Exception {
Source module =
CompilerTests.javaSource(
"test.ScopedBinding",
"package test;",
"",
"import dagger.Module;",
"import dagger.Provides;",
"import javax.inject.Singleton;",
"",
"@Module",
"interface MyModule {",
" @Provides",
" @NonQualifier",
" @MethodQualifier",
" static String provideString(@NonQualifier @ParamQualifier int i) {",
" return \"\";",
" }",
"}");
Source methodQualifier =
CompilerTests.javaSource(
"test.MethodQualifier",
"package test;",
"",
"import javax.inject.Qualifier;",
"",
"@Qualifier",
"@interface MethodQualifier {}");
Source paramQualifier =
CompilerTests.javaSource(
"test.ParamQualifier",
"package test;",
"",
"import javax.inject.Qualifier;",
"",
"@Qualifier",
"@interface ParamQualifier {}");
Source nonQualifier =
CompilerTests.javaSource(
"test.NonQualifier",
"package test;",
"",
"@interface NonQualifier {}");
CompilerTests.daggerCompiler(module, methodQualifier, paramQualifier, nonQualifier)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.generatedSource(
goldenFileRule.goldenSource("test/MyModule_ProvideStringFactory"));
});
}
private static final String BINDS_METHOD = "@Binds abstract Foo bindFoo(FooImpl impl);";
private static final String MULTIBINDS_METHOD = "@Multibinds abstract Set<Foo> foos();";
private static final String STATIC_PROVIDES_METHOD =
"@Provides static Bar provideBar() { return new Bar(); }";
private static final String INSTANCE_PROVIDES_METHOD =
"@Provides Baz provideBaz() { return new Baz(); }";
private static final String SOME_ABSTRACT_METHOD = "abstract void blah();";
@Test
public void bindsWithInstanceProvides() {
compileMethodCombination(BINDS_METHOD, INSTANCE_PROVIDES_METHOD)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
"A @Module may not contain both non-static and abstract binding methods");
});
}
@Test
public void multibindsWithInstanceProvides() {
compileMethodCombination(MULTIBINDS_METHOD, INSTANCE_PROVIDES_METHOD)
.compile(
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
"A @Module may not contain both non-static and abstract binding methods");
});
}
@Test
public void bindsWithStaticProvides() {
compileMethodCombination(BINDS_METHOD, STATIC_PROVIDES_METHOD)
.compile(subject -> subject.hasErrorCount(0));
}
@Test
public void bindsWithMultibinds() {
compileMethodCombination(BINDS_METHOD, MULTIBINDS_METHOD)
.compile(subject -> subject.hasErrorCount(0));
}
@Test
public void multibindsWithStaticProvides() {
compileMethodCombination(MULTIBINDS_METHOD, STATIC_PROVIDES_METHOD)
.compile(subject -> subject.hasErrorCount(0));
}
@Test
public void instanceProvidesWithAbstractMethod() {
compileMethodCombination(INSTANCE_PROVIDES_METHOD, SOME_ABSTRACT_METHOD)
.compile(subject -> subject.hasErrorCount(0));
}
private CompilerTests.DaggerCompiler compileMethodCombination(String... methodLines) {
Source fooFile =
CompilerTests.javaSource(
"test.Foo",
"package test;",
"",
"interface Foo {}");
Source fooImplFile =
CompilerTests.javaSource(
"test.FooImpl",
"package test;",
"",
"import javax.inject.Inject;",
"",
"final class FooImpl implements Foo {",
" @Inject FooImpl() {}",
"}");
Source barFile =
CompilerTests.javaSource(
"test.Bar",
"package test;",
"",
"final class Bar {}");
Source bazFile =
CompilerTests.javaSource(
"test.Baz",
"package test;",
"",
"final class Baz {}");
ImmutableList<String> moduleLines =
new ImmutableList.Builder<String>()
.add(
"package test;",
"",
"import dagger.Binds;",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.Multibinds;",
"import java.util.Set;",
"",
"@Module abstract class TestModule {")
.add(methodLines)
.add("}")
.build();
Source bindsMethodAndInstanceProvidesMethodModuleFile =
CompilerTests.javaSource("test.TestModule", moduleLines);
return CompilerTests.daggerCompiler(
fooFile, fooImplFile, barFile, bazFile, bindsMethodAndInstanceProvidesMethodModuleFile);
}
}