diff --git a/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiIssue.kt b/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiIssue.kt index 7905707f..d7bc5f1b 100644 --- a/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiIssue.kt +++ b/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiIssue.kt @@ -25,6 +25,14 @@ sealed class TiIssue( severity = Severity.ERROR ) + object GetViewOrThrowInOnAttach : TiIssue( + id = "GetViewOrThrowInOnAttach", + briefDescription = "TiPresenter.getViewOrThrow() might throw an exception", + category = CATEGORY_TI, + priority = 6, + severity = Severity.WARNING + ) + fun asLintIssue(detectorCls: Class, description: String = briefDescription): Issue = Issue.create( id, diff --git a/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistry.kt b/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistry.kt index 64159cc1..04bbe588 100644 --- a/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistry.kt +++ b/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistry.kt @@ -2,19 +2,22 @@ package net.grandcentrix.thirtyinch.lint import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.detector.api.Issue +import net.grandcentrix.thirtyinch.lint.detector.GetViewOrThrowInOnAttachDetector import net.grandcentrix.thirtyinch.lint.detector.MissingViewInCompositeDetector import net.grandcentrix.thirtyinch.lint.detector.MissingViewInThirtyInchDetector class TiLintRegistry : IssueRegistry() { - override val issues: List - get() = listOf( - MissingViewInThirtyInchDetector.ISSUE.apply { - setEnabledByDefault(true) - }, - MissingViewInCompositeDetector.ISSUE.apply { - setEnabledByDefault(true) - } - ) + override val issues: List = listOf( + MissingViewInThirtyInchDetector.ISSUE.apply { + setEnabledByDefault(true) + }, + MissingViewInCompositeDetector.ISSUE.apply { + setEnabledByDefault(true) + }, + GetViewOrThrowInOnAttachDetector.ISSUE.apply { + setEnabledByDefault(true) + } + ) override val api: Int = com.android.tools.lint.detector.api.CURRENT_API } diff --git a/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/detector/GetViewOrThrowInOnAttachDetector.kt b/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/detector/GetViewOrThrowInOnAttachDetector.kt new file mode 100644 index 00000000..a353ee56 --- /dev/null +++ b/thirtyinch-lint/src/main/kotlin/net/grandcentrix/thirtyinch/lint/detector/GetViewOrThrowInOnAttachDetector.kt @@ -0,0 +1,99 @@ +package net.grandcentrix.thirtyinch.lint.detector + +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.TextFormat +import net.grandcentrix.thirtyinch.lint.TiIssue.GetViewOrThrowInOnAttach +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UClass +import org.jetbrains.uast.UDeclarationsExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.ULocalVariable +import org.jetbrains.uast.UQualifiedReferenceExpression +import org.jetbrains.uast.UReturnExpression +import org.jetbrains.uast.USimpleNameReferenceExpression +import org.jetbrains.uast.getUastContext +import org.jetbrains.uast.toUElement + +private const val TI_CLASS_PRESENTER = "net.grandcentrix.thirtyinch.TiPresenter" +private const val TI_METHOD_ONATTACHVIEW = "onAttachView" +private const val TI_METHOD_GETVIEWORTHROW = "getViewOrThrow" +private const val TI_REFERENCE_VIEWORTHROW = "viewOrThrow" + +private const val MAX_TRANSITIVE_CHECK_DEPTH = 5 + +class GetViewOrThrowInOnAttachDetector : Detector(), Detector.UastScanner { + + companion object { + val ISSUE = GetViewOrThrowInOnAttach.asLintIssue( + GetViewOrThrowInOnAttachDetector::class.java, + "When using getViewOrThrow() in TiPresenter.onAttachView() the view might still be null." + + " So getViewOrThrow() might throw an exception during runtime." + + " Consider using the view parameter of TiPresenter.onAttachView() directly." + ) + } + + override fun applicableSuperClasses(): List = listOf(TI_CLASS_PRESENTER) + + override fun visitClass(context: JavaContext, declaration: UClass) { + val methods = declaration.findMethodsByName(TI_METHOD_ONATTACHVIEW, true) + + methods + .mapNotNull { method -> method.toUElement()?.getUastContext()?.getMethodBody(method) } + .forEach { methodBody -> checkForTransitiveUsage(context, methodBody) } + } + + private fun checkForTransitiveUsage( + context: JavaContext, + uElement: UElement, + depth: Int = 0, + reportElement: UElement = uElement + ) { + if (depth > MAX_TRANSITIVE_CHECK_DEPTH) return // limit check of call cascades to reduce lint check speed + + when (uElement) { + is UBlockExpression -> { + uElement.expressions.forEach { checkForTransitiveUsage(context, it) } + } + is UDeclarationsExpression -> { + uElement.declarations.forEach { checkForTransitiveUsage(context, it) } + } + is ULocalVariable -> { + uElement.uastInitializer?.run { checkForTransitiveUsage(context, uElement = this) } + } + is UReturnExpression -> { + uElement.returnExpression?.run { checkForTransitiveUsage(context, uElement = this) } + } + is USimpleNameReferenceExpression -> { + if (uElement.identifier == TI_REFERENCE_VIEWORTHROW) report(context, reportElement) + } + is UQualifiedReferenceExpression -> { + checkForTransitiveUsage(context, uElement.receiver) + checkForTransitiveUsage(context, uElement.selector, reportElement = uElement) + } + is UCallExpression -> { + if (shouldWarn(context, uElement)) report(context, uElement.methodIdentifier ?: uElement) + else { + uElement.resolve() + ?.let { uElement.getUastContext().getMethodBody(it) } + ?.run { checkForTransitiveUsage(context, uElement = this, depth = depth + 1) } + } + } + } + } + + private fun shouldWarn(context: JavaContext, call: UCallExpression): Boolean { + return call.valueArgumentCount == 0 + && TI_METHOD_GETVIEWORTHROW == call.methodName + && call.resolve()?.let { context.evaluator.isMemberInClass(it, TI_CLASS_PRESENTER) } == true + } + + private fun report(context: JavaContext, element: UElement) { + context.report( + ISSUE, + context.getLocation(element), + ISSUE.getBriefDescription(TextFormat.TEXT) + ) + } +} \ No newline at end of file diff --git a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/GetViewOrThrowInOnAttachDetectorTest.kt b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/GetViewOrThrowInOnAttachDetectorTest.kt new file mode 100644 index 00000000..c2f016bd --- /dev/null +++ b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/GetViewOrThrowInOnAttachDetectorTest.kt @@ -0,0 +1,458 @@ +package net.grandcentrix.thirtyinch.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import net.grandcentrix.thirtyinch.lint.detector.GetViewOrThrowInOnAttachDetector +import org.assertj.core.api.Assertions + +private val view = LintDetectorTest.java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "interface MyView extends TiView {\n" + + " void invoke();\n" + + "}" +) + +class GetViewOrThrowInOnAttachDetectorTest : LintDetectorTest() { + + override fun getIssues(): MutableList = mutableListOf(GetViewOrThrowInOnAttachDetector.ISSUE) + + override fun getDetector(): Detector { + return GetViewOrThrowInOnAttachDetector() + } + + fun testJava_noDirectUsage_noWarning() { + val presenter = java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "public class MyPresenter extends TiPresenter {\n" + + " protected void onAttachView(MyView view) {\n" + + " final int test = 42;\n" + + " final int test2 = test();\n" + + " test();\n" + + " }\n" + + " private int test() {" + + " return 42;\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).isEqualTo(NO_WARNINGS) + } + + fun testKotlin_noDirectUsage_noWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " val test = 42\n" + + " val test2 = test()\n" + + " test()\n" + + " }" + + " private fun test(): Int {" + + " return 42\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).isEqualTo(NO_WARNINGS) + } + + fun testJava_asMethod_hasWarning() { + val presenter = java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "public class MyPresenter extends TiPresenter {\n" + + " protected void onAttachView(MyView view) {" + + " getViewOrThrow();" + + " }" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_asReference_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " viewOrThrow" + + " }" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_asMethod_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " getViewOrThrow()" + + " }" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testJava_asMethod_chained_hasWarning() { + val presenter = java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "public class MyPresenter extends TiPresenter {\n" + + " protected void onAttachView(MyView view) {" + + " getViewOrThrow().invoke();" + + " }" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_asReference_chained_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " viewOrThrow.invoke()" + + " }" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_asMethod_chained_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " getViewOrThrow().invoke()" + + " }" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testJava_asAssignment_hasWarning() { + val presenter = java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "public class MyPresenter extends TiPresenter {\n" + + " protected void onAttachView(MyView view) {\n" + + " MyView view = getViewOrThrow();\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testJava_asReturn_hasWarning() { + val presenter = java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "public class MyPresenter extends TiPresenter {\n" + + " protected void onAttachView(MyView view) {\n" + + " MyView view = test();\n" + + " }\n" + + " private MyView test() {" + + " return getViewOrThrow();\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_asReturn_reference_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " val view = test()\n" + + " }" + + " private fun test(): MyView {" + + " return viewOrThrow\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_asReturn_method_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " val view = test()\n" + + " }" + + " private fun test(): MyView {" + + " return getViewOrThrow()\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testJava_transitiveUsage_hasWarning() { + val presenter = java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "public class MyPresenter extends TiPresenter {\n" + + " protected void onAttachView(MyView view) {\n" + + " test();\n" + + " }\n" + + " private void test() {" + + " test2();\n" + + " }\n" + + " private void test2() {" + + " test3();\n" + + " }\n" + + " private void test3() {" + + " test4();\n" + + " }\n" + + " private void test4() {" + + " getViewOrThrow();\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_transitiveUsage_reference_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " test()\n" + + " }" + + " private fun test() {" + + " test2()\n" + + " }\n" + + " private fun test2() {" + + " test3()\n" + + " }\n" + + " private fun test3() {" + + " test4()\n" + + " }\n" + + " private fun test4() {" + + " viewOrThrow\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testKotlin_transitiveUsage_method_hasWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " test()\n" + + " }" + + " private fun test() {" + + " test2()\n" + + " }\n" + + " private fun test2() {" + + " test3()\n" + + " }\n" + + " private fun test3() {" + + " test4()\n" + + " }\n" + + " private fun test4() {" + + " getViewOrThrow()\n" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).containsOnlyOnce(TiIssue.GetViewOrThrowInOnAttach.id) + } + + fun testJava_noTransitiveUsage_noWarning() { + val presenter = java( + "package foo;\n" + + "import net.grandcentrix.thirtyinch.*;\n" + + "public class MyPresenter extends TiPresenter {\n" + + " protected void onAttachView(MyView view) {\n" + + " test();\n" + + " }\n" + + " private void test() {" + + " test2();\n" + + " }\n" + + " private void test2() {" + + " test3();\n" + + " }\n" + + " private void test3() {" + + " test4();\n" + + " }\n" + + " private void test4() {" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).isEqualTo(NO_WARNINGS) + } + + fun testKotlin_noTransitiveUsage_noWarning() { + val presenter = kotlin( + "package foo\n" + + "import net.grandcentrix.thirtyinch.*\n" + + "class MyPresenter : TiPresenter() {\n" + + " override fun onAttachView(view: MyView) {" + + " test()\n" + + " }" + + " private fun test() {" + + " test2()\n" + + " }\n" + + " private fun test2() {" + + " test3()\n" + + " }\n" + + " private fun test3() {" + + " test4()\n" + + " }\n" + + " private fun test4() {" + + " }\n" + + "}" + ) + + Assertions.assertThat( + lintProject( + tiPresenterStub, + tiViewStub, + view, + presenter + ) + ).isEqualTo(NO_WARNINGS) + } +} \ No newline at end of file diff --git a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/LintUtils.kt b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/LintUtils.kt new file mode 100644 index 00000000..a65801cc --- /dev/null +++ b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/LintUtils.kt @@ -0,0 +1,35 @@ +package net.grandcentrix.thirtyinch.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest + +internal const val NO_WARNINGS = "No warnings." + +internal val tiActivityStub = LintDetectorTest.java( + "package net.grandcentrix.thirtyinch;\n" + + "public abstract class TiActivity

, V extends TiView> {\n" + + "}" +) + +internal val tiFragmentStub = LintDetectorTest.java( + "package net.grandcentrix.thirtyinch;\n" + + "public abstract class TiFragment

, V extends TiView> {\n" + + "}" +) + +internal val tiViewStub = LintDetectorTest.java( + "package net.grandcentrix.thirtyinch;\n" + + "public interface TiView {\n" + + "}" +) + +internal val tiPresenterStub = LintDetectorTest.java( + "package net.grandcentrix.thirtyinch;\n" + + "public abstract class TiPresenter {\n" + + " public V getView() {\n" + + " return null;\n" + + " }\n" + + " public V getViewOrThrow() {\n" + + " return null;\n" + + " }\n" + + "}" +) \ No newline at end of file diff --git a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInCompositeDetectorTest.kt b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInCompositeDetectorTest.kt index b7ac826b..4d84f201 100644 --- a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInCompositeDetectorTest.kt +++ b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInCompositeDetectorTest.kt @@ -6,24 +6,10 @@ import com.android.tools.lint.detector.api.Issue import net.grandcentrix.thirtyinch.lint.detector.MissingViewInCompositeDetector import org.assertj.core.api.Assertions.* -private const val NO_WARNINGS = "No warnings." - class MissingViewInCompositeDetectorTest : LintDetectorTest() { /* Stubbed-out source files */ - private val tiPresenterStub = java( - "package net.grandcentrix.thirtyinch;\n" + - "public abstract class TiPresenter {\n" + - "}" - ) - - private val tiViewStub = java( - "package net.grandcentrix.thirtyinch;\n" + - "public interface TiView {\n" + - "}" - ) - private val caBasePluginStub = java( "package com.pascalwelsch.compositeandroid;\n" + "public interface Plugin {\n" + diff --git a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInThirtyInchDetectorTest.kt b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInThirtyInchDetectorTest.kt index 8adb175e..d55050f1 100644 --- a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInThirtyInchDetectorTest.kt +++ b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/MissingViewInThirtyInchDetectorTest.kt @@ -6,36 +6,10 @@ import com.android.tools.lint.detector.api.Issue import net.grandcentrix.thirtyinch.lint.detector.MissingViewInThirtyInchDetector import org.assertj.core.api.Assertions.* -private const val NO_WARNINGS = "No warnings." - class MissingViewInThirtyInchDetectorTest : LintDetectorTest() { /* Stubbed-out source files */ - private val tiActivityStub = java( - "package net.grandcentrix.thirtyinch;\n" + - "public abstract class TiActivity

, V extends TiView> {\n" + - "}" - ) - - private val tiFragmentStub = java( - "package net.grandcentrix.thirtyinch;\n" + - "public abstract class TiFragment

, V extends TiView> {\n" + - "}" - ) - - private val tiPresenterStub = java( - "package net.grandcentrix.thirtyinch;\n" + - "public abstract class TiPresenter {\n" + - "}" - ) - - private val tiViewStub = java( - "package net.grandcentrix.thirtyinch;\n" + - "public interface TiView {\n" + - "}" - ) - private val view = java( "package foo;\n" + "import net.grandcentrix.thirtyinch.*;\n" + diff --git a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistryTest.kt b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistryTest.kt index 60fa4e5c..4e356edf 100644 --- a/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistryTest.kt +++ b/thirtyinch-lint/src/test/kotlin/net/grandcentrix/thirtyinch/lint/TiLintRegistryTest.kt @@ -1,5 +1,6 @@ package net.grandcentrix.thirtyinch.lint +import net.grandcentrix.thirtyinch.lint.detector.GetViewOrThrowInOnAttachDetector import net.grandcentrix.thirtyinch.lint.detector.MissingViewInCompositeDetector import net.grandcentrix.thirtyinch.lint.detector.MissingViewInThirtyInchDetector import org.assertj.core.api.Assertions.* @@ -12,7 +13,8 @@ class IssueRegistryTest { assertThat(TiLintRegistry().issues) .containsExactly( MissingViewInThirtyInchDetector.ISSUE, - MissingViewInCompositeDetector.ISSUE + MissingViewInCompositeDetector.ISSUE, + GetViewOrThrowInOnAttachDetector.ISSUE ) } } \ No newline at end of file