forked from GCX-HCI/ThirtyInch
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrated to Kotlin & separated Ti/CA checks into two Detectors (see G…
- Loading branch information
1 parent
44b3f23
commit 57697a6
Showing
13 changed files
with
602 additions
and
216 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 0 additions & 16 deletions
16
lint/src/main/java/net/grandcentrix/thirtyinch/IssueRegistry.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
lint/src/main/kotlin/net/grandcentrix/thirtyinch/BaseMissingViewDetector.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package net.grandcentrix.thirtyinch | ||
|
||
import com.android.tools.lint.detector.api.Detector | ||
import com.android.tools.lint.detector.api.Issue | ||
import com.android.tools.lint.detector.api.JavaContext | ||
import com.android.tools.lint.detector.api.TextFormat | ||
import com.intellij.psi.PsiClass | ||
import com.intellij.psi.PsiClassType | ||
import com.intellij.psi.PsiType | ||
import com.intellij.psi.util.PsiUtil | ||
import org.jetbrains.uast.UClass | ||
import org.jetbrains.uast.getUastContext | ||
|
||
// Base class for Lint checks centered around the notion of "TiView not implemented" | ||
abstract class BaseMissingViewDetector : Detector(), Detector.UastScanner { | ||
|
||
/** | ||
* The Issue that the detector is connected to, | ||
* reported on illegal state detection | ||
*/ | ||
abstract val issue: Issue | ||
|
||
/** | ||
* The list of super-classes to detect. | ||
* We're forcing sub-classed Detectors to implement this by means of redeclaration | ||
*/ | ||
override abstract fun applicableSuperClasses(): List<String> | ||
|
||
/** | ||
* Tries to extract the PsiType of the TiView sub-class | ||
* that is relevant for the given declaration. The relevant | ||
* super-class (from applicableSuperClasses()) & its resolved variant | ||
* are given as well. | ||
*/ | ||
abstract fun tryFindViewInterface(context: JavaContext, declaration: UClass, extendedType: PsiClassType, resolvedType: PsiClass): PsiType? | ||
|
||
/** | ||
* Whether or not to allow the absence of an "implements TiView" clause | ||
* on the given declaration. The View interface is given as well to allow | ||
* for further introspection into the setup of the class at hand. | ||
* When false is returned here, Lint will report the Issue connected to this Detector | ||
* on the given declaration. | ||
*/ | ||
abstract fun allowMissingViewInterface(context: JavaContext, declaration: UClass, viewInterface: PsiType): Boolean | ||
|
||
override final fun visitClass(context: JavaContext, declaration: UClass) { | ||
if (!context.isEnabled(issue)) { | ||
return | ||
} | ||
|
||
// Don't trigger on abstract classes | ||
if (PsiUtil.isAbstractClass(declaration.psi)) { | ||
return | ||
} | ||
|
||
// Extract the MVP View type from the declaration | ||
tryFindViewInterface(context, declaration)?.let { viewInterface -> | ||
// Check if the class implements that interface as well | ||
if (!tryFindViewImplementation(context, declaration, viewInterface)) { | ||
// Interface not implemented; check if alternate condition applies | ||
if (!allowMissingViewInterface(context, declaration, viewInterface)) { | ||
// Invalid state: Report issue for this class | ||
context.report( | ||
issue, | ||
context.getLocation(declaration.nameIdentifier), | ||
issue.getBriefDescription(TextFormat.TEXT)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun tryFindViewInterface(context: JavaContext, declaration: UClass): PsiType? { | ||
for (extendedType in declaration.extendsListTypes) { | ||
extendedType.resolveGenerics().element?.let { resolvedType -> | ||
val qualifiedName = resolvedType.qualifiedName | ||
if (applicableSuperClasses().contains(qualifiedName)) { | ||
// This detector is interested in this class; delegate to it | ||
return tryFindViewInterface(context, declaration, extendedType, resolvedType) | ||
} | ||
|
||
// Crawl up the type hierarchy to catch declarations in super classes | ||
val uastContext = declaration.getUastContext() | ||
return tryFindViewInterface(context, uastContext.getClass(resolvedType)) | ||
} | ||
} | ||
|
||
return null | ||
} | ||
|
||
private fun tryFindViewImplementation(context: JavaContext, declaration: UClass, viewInterface: PsiType): Boolean { | ||
for (implementedType in declaration.implementsListTypes) { | ||
if (implementedType == viewInterface) { | ||
return true | ||
} | ||
|
||
implementedType.resolve()?.let { resolvedType -> | ||
val uastContext = declaration.getUastContext() | ||
return tryFindViewImplementation(context, uastContext.getClass(resolvedType), viewInterface) | ||
} | ||
} | ||
|
||
return false | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
lint/src/main/kotlin/net/grandcentrix/thirtyinch/IssueRegistry.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package net.grandcentrix.thirtyinch | ||
|
||
class IssueRegistry : com.android.tools.lint.client.api.IssueRegistry() { | ||
|
||
override fun getIssues() = listOf( | ||
MissingViewInThirtyInchDetector.ISSUE, | ||
MissingViewInCompositeDetector.ISSUE | ||
) | ||
} |
31 changes: 31 additions & 0 deletions
31
lint/src/main/kotlin/net/grandcentrix/thirtyinch/Issues.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package net.grandcentrix.thirtyinch | ||
|
||
import com.android.tools.lint.detector.api.* | ||
import java.util.* | ||
|
||
enum class Issues( | ||
val id: String, | ||
val briefDescription: String, | ||
val category: Category, | ||
val priority: Int, | ||
val severity: Severity) { | ||
|
||
MISSING_VIEW( | ||
id = "MissingTiViewImplementation", | ||
briefDescription = "TiView Implementation missing in class", | ||
category = Category.CORRECTNESS, | ||
priority = 8, | ||
severity = Severity.ERROR); | ||
|
||
fun create(detectorCls: Class<out Detector>, description: String = briefDescription): Issue = | ||
Issue.create( | ||
id, | ||
briefDescription, | ||
description, | ||
category, | ||
priority, | ||
severity, | ||
Implementation( | ||
detectorCls, | ||
EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES))) | ||
} |
95 changes: 95 additions & 0 deletions
95
lint/src/main/kotlin/net/grandcentrix/thirtyinch/MissingViewInCompositeDetector.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package net.grandcentrix.thirtyinch | ||
|
||
import com.android.annotations.VisibleForTesting | ||
import com.android.tools.lint.detector.api.Issue | ||
import com.android.tools.lint.detector.api.JavaContext | ||
import com.intellij.psi.* | ||
import org.jetbrains.uast.* | ||
|
||
private val ADD_PLUGIN_METHOD = "addPlugin" | ||
private val TI_ACTIVITY_PLUGIN_NAME = "TiActivityPlugin" | ||
private val TI_FRAGMENT_PLUGIN_NAME = "TiFragmentPlugin" | ||
private val CA_CLASS_NAMES = listOf( | ||
"com.pascalwelsch.compositeandroid.activity.CompositeActivity", | ||
"com.pascalwelsch.compositeandroid.fragment.CompositeFragment") | ||
|
||
class MissingViewInCompositeDetector : BaseMissingViewDetector() { | ||
|
||
companion object { | ||
@VisibleForTesting | ||
val ISSUE: Issue = Issues.MISSING_VIEW.create( | ||
MissingViewInCompositeDetector::class.java, | ||
"When using ThirtyInch, a class extending CompositeActivity or CompositeFragment " + | ||
"has to implement the TiView interface associated with it in its signature, " + | ||
"if it applies the respective plugin as well.") | ||
} | ||
|
||
override fun applicableSuperClasses() = CA_CLASS_NAMES | ||
|
||
override val issue: Issue | ||
get() = ISSUE | ||
|
||
override fun tryFindViewInterface(context: JavaContext, declaration: UClass, extendedType: PsiClassType, resolvedType: PsiClass): PsiType? { | ||
// Expect TiPlugin to be applied in the extended CA class | ||
var defaultConstructor: PsiMethod? = null | ||
for (constructor in declaration.constructors) { | ||
if (constructor.typeParameters.size == 0) { | ||
// Found default constructor | ||
defaultConstructor = constructor | ||
break | ||
} | ||
} | ||
|
||
if (defaultConstructor == null) { | ||
return null | ||
} | ||
|
||
val uastContext = declaration.getUastContext() | ||
val body = uastContext.getMethodBody(defaultConstructor) | ||
return tryFindViewFromCompositeConstructor(context, declaration, body) | ||
} | ||
|
||
private fun tryFindViewFromCompositeConstructor(context: JavaContext, declaration: UClass, expression: UExpression?): PsiType? { | ||
if (expression == null) { | ||
return null | ||
} | ||
|
||
if (expression is UBlockExpression) { | ||
// Unwrap block statements | ||
for (child in expression.expressions) { | ||
val resolved = tryFindViewFromCompositeConstructor(context, declaration, child) | ||
if (resolved != null) { | ||
return resolved | ||
} | ||
} | ||
|
||
} else if (expression is UCallExpression) { | ||
// Inspect call sites | ||
val call = expression as UCallExpression? | ||
if (ADD_PLUGIN_METHOD == call!!.methodName && call.valueArgumentCount == 1) { | ||
// Expect a plugin to be used as the only argument to this method | ||
val argument = call.valueArguments[0] | ||
if (argument is UCallExpression) { | ||
val argReference = argument.classReference ?: return null | ||
|
||
val resolvedName = argReference.resolvedName | ||
if (TI_ACTIVITY_PLUGIN_NAME == resolvedName || TI_FRAGMENT_PLUGIN_NAME == resolvedName) { | ||
// Matching names. Finally, find the type parameters passed to the plugin | ||
val psiReference = argReference.psi as PsiJavaCodeReferenceElement? ?: return null | ||
|
||
val parameterTypes = psiReference.typeParameters | ||
if (parameterTypes.size != 2) { | ||
return null | ||
} | ||
|
||
return parameterTypes[1] | ||
} | ||
} | ||
} | ||
} | ||
|
||
return null | ||
} | ||
|
||
override fun allowMissingViewInterface(context: JavaContext, declaration: UClass, viewInterface: PsiType) = false | ||
} |
56 changes: 56 additions & 0 deletions
56
lint/src/main/kotlin/net/grandcentrix/thirtyinch/MissingViewInThirtyInchDetector.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package net.grandcentrix.thirtyinch | ||
|
||
import com.android.annotations.VisibleForTesting | ||
import com.android.tools.lint.detector.api.Issue | ||
import com.android.tools.lint.detector.api.JavaContext | ||
import com.intellij.psi.PsiClass | ||
import com.intellij.psi.PsiClassType | ||
import com.intellij.psi.PsiType | ||
import org.jetbrains.uast.UClass | ||
|
||
private val TI_VIEW_FQ = "net.grandcentrix.thirtyinch.TiView" | ||
private val PROVIDE_VIEW_METHOD = "provideView" | ||
private val TI_CLASS_NAMES = listOf( | ||
"net.grandcentrix.thirtyinch.TiActivity", | ||
"net.grandcentrix.thirtyinch.TiFragment") | ||
|
||
class MissingViewInThirtyInchDetector : BaseMissingViewDetector() { | ||
|
||
companion object { | ||
@VisibleForTesting | ||
val ISSUE: Issue = Issues.MISSING_VIEW.create( | ||
MissingViewInThirtyInchDetector::class.java, | ||
"When using ThirtyInch, a class extending TiActivity, TiFragment or CompositeActivity " + | ||
"has to implement the TiView interface associated with it in its signature, " + | ||
"or implement `provideView()` instead to override this default behaviour.") | ||
} | ||
|
||
override fun applicableSuperClasses() = TI_CLASS_NAMES | ||
|
||
override val issue: Issue | ||
get() = ISSUE | ||
|
||
override fun tryFindViewInterface(context: JavaContext, declaration: UClass, extendedType: PsiClassType, resolvedType: PsiClass): PsiType? { | ||
// Expect <P extends TiPresenter, V extends TiView> signature in the extended Ti class | ||
val parameters = extendedType.parameters | ||
val parameterTypes = resolvedType.typeParameters | ||
if (parameters.size != 2 || parameterTypes.size != 2) { | ||
return null | ||
} | ||
|
||
// Check that the second type parameter is actually a TiView | ||
val parameterType = parameterTypes[1] | ||
val parameter = parameters[1] | ||
return parameterType.extendsListTypes | ||
.map { it.resolveGenerics().element } | ||
.filter { TI_VIEW_FQ == it?.qualifiedName } | ||
.map { parameter } | ||
.first() | ||
} | ||
|
||
override fun allowMissingViewInterface(context: JavaContext, declaration: UClass, viewInterface: PsiType): Boolean { | ||
// Interface not implemented; check if provideView() is overridden instead | ||
return declaration.findMethodsByName(PROVIDE_VIEW_METHOD, true) | ||
.any { viewInterface == it.returnType } | ||
} | ||
} |
14 changes: 0 additions & 14 deletions
14
lint/src/test/java/net/grandcentrix/thirtyinch/IssueRegistryTest.java
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.