/* * 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 provideProvider() {}") .hasError("@Provides methods must not return framework types"); } @Test public void providesMethodReturnsJakartaProvider() { assertThatModuleMethod("@Provides jakarta.inject.Provider provideProvider() {}") .hasError("@Provides methods must not return framework types"); } @Test public void providesMethodReturnsDaggerInternalProvider() { assertThatModuleMethod("@Provides dagger.internal.Provider provideProvider() {}") .hasError("@Provides methods must not return disallowed types"); } @Test public void providesIntoSetMethodReturnsDaggerInternalProvider() { assertThatModuleMethod( "@Provides @IntoSet dagger.internal.Provider provideProvider() {}") .hasError("@Provides methods must not return disallowed types"); } @Test public void providesMethodReturnsLazy() { assertThatModuleMethod("@Provides Lazy provideLazy() {}") .hasError("@Provides methods must not return framework types"); } @Test public void providesMethodReturnsMembersInjector() { assertThatModuleMethod("@Provides MembersInjector provideMembersInjector() {}") .hasError("@Provides methods must not return framework types"); } @Test public void providesMethodReturnsProducer() { assertThatModuleMethod("@Provides Producer provideProducer() {}") .hasError("@Provides methods must not return framework types"); } @Test public void providesMethodReturnsProduced() { assertThatModuleMethod("@Provides Produced provideProduced() {}") .hasError("@Provides methods must not return framework types"); } @Test public void providesMethodWithTypeParameter() { assertThatModuleMethod("@Provides 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> provideProvider() {}") .hasError("@Provides methods must not return disallowed types"); } @Test public void providesMethodSetValuesNotASet() { assertThatModuleMethod( "@Provides @ElementsIntoSet List provideStrings() { return null; }") .hasError("@Provides methods annotated with @ElementsIntoSet must return a Set"); } @Test public void bindsMethodReturnsProvider() { assertThatModuleMethod("@Binds abstract Provider bindsProvider(Provider impl);") .hasError("@Binds methods must not return framework types"); } @Test public void bindsMethodReturnsDaggerProvider() { assertThatModuleMethod("@Binds abstract dagger.internal.Provider " + "bindsProvider(dagger.internal.Provider 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 {}"); 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 {", " 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 provideObjects(", " @QualifierA Object a, @QualifierB Object b, MembersInjector 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> 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 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 strings() {", " return null;", " }", "", " @Provides Set 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> {", " @Provides List provideListB(B b) {", " List list = new ArrayList();", " 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 {", " @Provides Number provideNumber() { return 1; }", "}"); Source integerChild = CompilerTests.javaSource("test.ChildIntegerModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "class ChildIntegerModule extends ParentModule {", " @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 numberList();", " List 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 {", " @Provides List provideListT() {", " return new ArrayList<>();", " }", "", " @Provides static Map 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 numberProvider) {", " return \"foo\";", " }", "}"); CompilerTests.daggerCompiler(moduleFile, QUALIFIER_A, QUALIFIER_B) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "Dagger does not support injecting Provider, Lazy, Producer, or " + "Produced 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 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 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 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 moduleLines = new ImmutableList.Builder() .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); } }