Skip to content
This repository has been archived by the owner on Mar 16, 2021. It is now read-only.

Refactoring: Lint Name and Stub Extraction #189

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8ec3cc6
nails the Lint API level declaration to 1 to support backwards compat…
jreehuis Feb 19, 2019
bdc44d6
also check the super class when looking for the MVP view
jreehuis Feb 19, 2019
74d9ae0
also check the super class when looking for the MVP view implementation
jreehuis Feb 19, 2019
49267ea
adds tests for the case that TiActivity, TiPresenter and TiView are j…
jreehuis Feb 19, 2019
f410a65
prevent that the provideView overridden check succeeds to to the TI s…
jreehuis Mar 15, 2019
a5fa3bf
adds new TiIssue values
jreehuis Mar 18, 2019
6db9e81
implements the DistinctUntilChanged annotation lint check about param…
jreehuis Mar 18, 2019
ecffc35
adds tests for the DistinctUntilChanged annotation lint checks
jreehuis Mar 18, 2019
722d79b
adjusts TiLintRegistryTest
jreehuis Mar 18, 2019
66a4149
decouple the parameter and return type lint check triggering
jreehuis Mar 18, 2019
6447b80
extracts the lint issue reporting to a method
jreehuis Mar 18, 2019
32e4d81
checks if the issue enabled before reporting it
jreehuis Mar 18, 2019
7bce507
adds new TiIssue value
jreehuis Mar 18, 2019
5a54c9d
implements the DistinctUntilChanged annotation lint check about annot…
jreehuis Mar 18, 2019
5675813
adds tests for the DistinctUntilChanged lint check about annotating a…
jreehuis Mar 18, 2019
2833b35
adopts the DistinctUntilChanged lint checks to CallOnMainThread
jreehuis Mar 18, 2019
7a242fd
extracts the TI fully qualified names to an object
jreehuis Mar 18, 2019
d1afa77
extracts the TI file stubs to an object
jreehuis Mar 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ private val CATEGORY_TI = Category.create("ThirtyInch", 90)
sealed class TiIssue(
val id: String,
val briefDescription: String,
val longDescription: String = briefDescription,
val category: Category,
val priority: Int,
val severity: Severity
Expand All @@ -25,11 +26,38 @@ sealed class TiIssue(
severity = Severity.ERROR
)

fun asLintIssue(detectorCls: Class<out Detector>, description: String = briefDescription): Issue =
object DistinctUntilChangedWithoutParameter : TiIssue(
id = "DistinctUntilChangedWithoutParameter",
briefDescription = "@DistinctUntilChanged Annotation on a method without Parameter is useless",
longDescription = "When using the @DistinctUntilChanged annotation on a method without parameter the method call will be executed each time. @DistinctUntilChanged needs at least one parameter to check if it changed compared to the last method invocation.",
category = CATEGORY_TI,
priority = 5,
severity = Severity.WARNING
)

object AnnotationOnNonVoidMethod : TiIssue(
id = "TiAnnotationOnNonVoidMethod",
briefDescription = "Annotation of a non Void method is not supported in ThirtyInch",
longDescription = "When using a ThirtyInch annotation on a method without parameter the method call will be executed each time. Return types are not supported.",
category = CATEGORY_TI,
priority = 5,
severity = Severity.WARNING
)

object AnnotationOnNonTiView : TiIssue(
id = "TiAnnotationOnNonTiViewInterface",
briefDescription = "ThirtyInch Annotations on a method of a not TiView Interface is useless",
longDescription = "When using a ThirtyInch annotation on a method of a not TiView Interface it will not be recognized and so will not change anything.",
category = CATEGORY_TI,
priority = 5,
severity = Severity.WARNING
)

fun asLintIssue(detectorCls: Class<out Detector>, description: String? = null): Issue =
Issue.create(
id,
briefDescription,
description,
description ?: longDescription,
category,
priority,
severity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ 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.CallOnMainThreadUsageDetector
import net.grandcentrix.thirtyinch.lint.detector.DistinctUntilChangedUsageDetector
import net.grandcentrix.thirtyinch.lint.detector.MissingViewInCompositeDetector
import net.grandcentrix.thirtyinch.lint.detector.MissingViewInThirtyInchDetector

Expand All @@ -13,8 +15,28 @@ class TiLintRegistry : IssueRegistry() {
},
MissingViewInCompositeDetector.ISSUE.apply {
setEnabledByDefault(true)
},
DistinctUntilChangedUsageDetector.ISSUE_NO_PARAMETER.apply {
setEnabledByDefault(true)
},
DistinctUntilChangedUsageDetector.ISSUE_NON_VOID_RETURN_TYPE.apply {
setEnabledByDefault(true)
},
DistinctUntilChangedUsageDetector.ISSUE_NO_TIVIEW_CHILD.apply {
setEnabledByDefault(true)
},
CallOnMainThreadUsageDetector.ISSUE_NON_VOID_RETURN_TYPE.apply {
setEnabledByDefault(true)
},
CallOnMainThreadUsageDetector.ISSUE_NO_TIVIEW_CHILD.apply {
setEnabledByDefault(true)
}
)

override val api: Int = com.android.tools.lint.detector.api.CURRENT_API
}
/**
* Lint API Version for which the Checks are build.
*
* See [com.android.tools.lint.detector.api.describeApi] for possible options
*/
override val api: Int = 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.grandcentrix.thirtyinch.lint

internal object TiNames {
const val FQN_CLASS_TIACTIVITY = "net.grandcentrix.thirtyinch.TiActivity"
const val FQN_CLASS_TIFRAGMENT = "net.grandcentrix.thirtyinch.TiFragment"
const val FQN_CLASS_TIDIALOGFRAGMENT = "net.grandcentrix.thirtyinch.TiDialogFragment"
const val FQN_CLASS_TIVIEW = "net.grandcentrix.thirtyinch.TiView"

const val FQN_ANNOTATION_DISTINCTUNTILCHANGED = "net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChanged"
const val FQN_ANNOTATION_CALLONMAINTHREAD = "net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThread"
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ abstract class BaseMissingViewDetector : Detector(), Detector.UastScanner {
return tryFindViewImplementation(context, uastContext.getClass(resolvedType), viewInterface)
}
}
return false

val superClass = declaration.superClass

return superClass != null && tryFindViewImplementation(context, superClass, viewInterface)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package net.grandcentrix.thirtyinch.lint.detector

import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Detector.UastScanner
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.TextFormat.TEXT
import com.intellij.psi.PsiType
import net.grandcentrix.thirtyinch.lint.TiIssue
import net.grandcentrix.thirtyinch.lint.TiNames.FQN_ANNOTATION_CALLONMAINTHREAD
import net.grandcentrix.thirtyinch.lint.TiNames.FQN_CLASS_TIVIEW
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.getContainingUClass
import org.jetbrains.uast.toUElement

class CallOnMainThreadUsageDetector : Detector(), UastScanner {
companion object {
val ISSUE_NON_VOID_RETURN_TYPE = TiIssue.AnnotationOnNonVoidMethod.asLintIssue(
detectorCls = CallOnMainThreadUsageDetector::class.java
)
val ISSUE_NO_TIVIEW_CHILD = TiIssue.AnnotationOnNonTiView.asLintIssue(
detectorCls = CallOnMainThreadUsageDetector::class.java
)
}

override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(
UAnnotation::class.java
)

override fun createUastHandler(context: JavaContext): UElementHandler = object : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
if (node.qualifiedName != FQN_ANNOTATION_CALLONMAINTHREAD) return

val method = node.uastParent as? UMethod ?: return

if (context.isEnabled(ISSUE_NON_VOID_RETURN_TYPE) && method.returnType != PsiType.VOID) {
report(context, node, ISSUE_NON_VOID_RETURN_TYPE)
}

if (context.isEnabled(ISSUE_NO_TIVIEW_CHILD)) {
val methodClass = method.getContainingUClass()
if (methodClass == null || !methodClass.isInterface || !methodClass.extends(FQN_CLASS_TIVIEW)) {
report(context, node, ISSUE_NO_TIVIEW_CHILD)
}
}
}
}

private fun UClass.extends(fqClassName: String): Boolean = qualifiedName == fqClassName ||
interfaces.mapNotNull { iFace -> iFace.toUElement() as? UClass }
.any { iFace -> iFace.extends(fqClassName) }

private fun report(context: JavaContext, annotation: UAnnotation, issue: Issue) {
context.report(
issue,
context.getLocation(annotation),
issue.getBriefDescription(TEXT)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package net.grandcentrix.thirtyinch.lint.detector

import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Detector.UastScanner
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.TextFormat.TEXT
import com.intellij.psi.PsiType
import net.grandcentrix.thirtyinch.lint.TiIssue
import net.grandcentrix.thirtyinch.lint.TiNames.FQN_ANNOTATION_DISTINCTUNTILCHANGED
import net.grandcentrix.thirtyinch.lint.TiNames.FQN_CLASS_TIVIEW
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.getContainingUClass
import org.jetbrains.uast.toUElement

class DistinctUntilChangedUsageDetector : Detector(), UastScanner {
companion object {
val ISSUE_NO_PARAMETER = TiIssue.DistinctUntilChangedWithoutParameter.asLintIssue(
detectorCls = DistinctUntilChangedUsageDetector::class.java
)
val ISSUE_NON_VOID_RETURN_TYPE = TiIssue.AnnotationOnNonVoidMethod.asLintIssue(
detectorCls = DistinctUntilChangedUsageDetector::class.java
)
val ISSUE_NO_TIVIEW_CHILD = TiIssue.AnnotationOnNonTiView.asLintIssue(
detectorCls = DistinctUntilChangedUsageDetector::class.java
)
}

override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(
UAnnotation::class.java
)

override fun createUastHandler(context: JavaContext): UElementHandler = object : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
if (node.qualifiedName != FQN_ANNOTATION_DISTINCTUNTILCHANGED) return

val method = node.uastParent as? UMethod ?: return

if (context.isEnabled(ISSUE_NO_PARAMETER) && !method.hasParameters()) {
report(context, node, ISSUE_NO_PARAMETER)
}

if (context.isEnabled(ISSUE_NON_VOID_RETURN_TYPE) && method.returnType != PsiType.VOID) {
report(context, node, ISSUE_NON_VOID_RETURN_TYPE)
}

if (context.isEnabled(ISSUE_NO_TIVIEW_CHILD)) {
val methodClass = method.getContainingUClass()
if (methodClass == null || !methodClass.isInterface || !methodClass.extends(FQN_CLASS_TIVIEW)) {
report(context, node, ISSUE_NO_TIVIEW_CHILD)
}
}
}
}

private fun UClass.extends(fqClassName: String): Boolean = qualifiedName == fqClassName ||
interfaces.mapNotNull { iFace -> iFace.toUElement() as? UClass }
.any { iFace -> iFace.extends(fqClassName) }

private fun report(context: JavaContext, annotation: UAnnotation, issue: Issue) {
context.report(
issue,
context.getLocation(annotation),
issue.getBriefDescription(TEXT)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import com.android.tools.lint.detector.api.JavaContext
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiType
import net.grandcentrix.thirtyinch.lint.TiIssue.MissingView
import net.grandcentrix.thirtyinch.lint.TiNames
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
import org.jetbrains.uast.UClass

private const val TI_VIEW_FQ = "net.grandcentrix.thirtyinch.TiView"
private const val PROVIDE_VIEW_METHOD = "provideView"
private val TI_CLASS_NAMES = listOf(
"net.grandcentrix.thirtyinch.TiActivity",
"net.grandcentrix.thirtyinch.TiFragment",
"net.grandcentrix.thirtyinch.TiDialogFragment"
TiNames.FQN_CLASS_TIACTIVITY,
TiNames.FQN_CLASS_TIFRAGMENT,
TiNames.FQN_CLASS_TIDIALOGFRAGMENT
)

class MissingViewInThirtyInchDetector : BaseMissingViewDetector() {
Expand Down Expand Up @@ -48,17 +48,27 @@ class MissingViewInThirtyInchDetector : BaseMissingViewDetector() {
.firstNotNullResult { (type, typeParameter) ->
typeParameter.extendsListTypes
.map { it.resolveGenerics().element }
.filter { TI_VIEW_FQ == it?.qualifiedName }
.filter { TiNames.FQN_CLASS_TIVIEW == it?.qualifiedName }
.map { type }
.firstOrNull()
?: (type as? PsiClassType)?.let { tryFindViewInterface(it) }
}
?: extendedType.superTypes.firstNotNullResult { superType ->
(superType as? PsiClassType)?.let { tryFindViewInterface(it) }
}
}

override fun allowMissingViewInterface(context: JavaContext, declaration: UClass,
viewInterface: PsiType): Boolean {
// if the superClass is one of the TI base classes, prevent deeper recursive checks
val superClass = declaration.superClass
.let { sClass ->
if (TI_CLASS_NAMES.any { tiName -> tiName == sClass?.qualifiedName }) null else sClass
}

// Interface not implemented; check if provideView() is overridden instead
return declaration.findMethodsByName(PROVIDE_VIEW_METHOD, true)
.any { viewInterface == it.returnType }
return declaration.findMethodsByName(PROVIDE_VIEW_METHOD, false)
.firstNotNullResult { viewInterface == it.returnType }
?: (superClass != null && allowMissingViewInterface(context, superClass, viewInterface))
}
}
Loading