mirror of
https://github.com/BetterAndroid/Hikage.git
synced 2025-12-08 06:33:42 +08:00
refactor: merge to BetterAndroid new usage
This commit is contained in:
@@ -60,6 +60,7 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
|
||||
|
||||
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
|
||||
if (node.selector !is UBinaryExpressionWithType) return
|
||||
|
||||
val castExpr = node.selector as UBinaryExpressionWithType
|
||||
visitAndLint(context, castExpr, node)
|
||||
}
|
||||
@@ -75,11 +76,14 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
|
||||
) {
|
||||
// Get the parent node, if it is wrapped with brackets will also be included.
|
||||
val locationNode = node.uastParent as? UParenthesizedExpression ?: node
|
||||
|
||||
val receiver = parent?.receiver ?: node.operand
|
||||
val receiverType = (node.operand as? UArrayAccessExpression)?.receiver?.getExpressionType() ?: return
|
||||
val receiverClass = receiverType.canonicalText
|
||||
|
||||
// Filter retains results that meet the conditions.
|
||||
if (receiverClass != DeclaredSymbol.HIKAGE_CLASS) return
|
||||
|
||||
// Like `hikage["your_id"] as YourView`.
|
||||
val exprText = node.sourcePsi?.text ?: return
|
||||
// Like `hikage["your_id"]`.
|
||||
@@ -88,11 +92,15 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
|
||||
val receiverNameText = receiverText.split("[")[0]
|
||||
// Like `"your_id"`.
|
||||
val receiverContent = runCatching { receiverText.split("[")[1].split("]")[0] }.getOrNull() ?: return
|
||||
|
||||
val isSafeCast = exprText.contains("as?") || exprText.endsWith("?")
|
||||
|
||||
// Like `YourView`.
|
||||
val castTypeContent = node.typeReference?.sourcePsi?.text?.removeSuffix("?") ?: return
|
||||
|
||||
val replacement = "$receiverNameText.${if (isSafeCast) "getOrNull" else "get"}<$castTypeContent>($receiverContent)"
|
||||
val replaceSuggestion = if (isSafeCast) "Hikage.getOrNull<$castTypeContent>" else "Hikage.get<$castTypeContent>"
|
||||
|
||||
val location = context.getLocation(locationNode)
|
||||
val lintFix = LintFix.create()
|
||||
.name("Replace with '$replacement'")
|
||||
@@ -101,6 +109,7 @@ class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
|
||||
.with(replacement)
|
||||
.reformat(true)
|
||||
.build()
|
||||
|
||||
context.report(
|
||||
ISSUE, locationNode, location,
|
||||
message = "Can be replaced with safe type cast `$replaceSuggestion`.",
|
||||
|
||||
@@ -70,6 +70,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
|
||||
override fun visitCallExpression(node: UCallExpression) {
|
||||
val callExpr = node.sourcePsi as? KtCallExpression ?: return
|
||||
val method = node.resolve() ?: return
|
||||
|
||||
startLint(callExpr, method)
|
||||
organizeAndReport()
|
||||
}
|
||||
@@ -78,6 +79,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
|
||||
val className = method.containingClass?.qualifiedName ?: ""
|
||||
val hasHikageable = method.hasHikageable()
|
||||
val hasLayoutParams = className == DeclaredSymbol.HIKAGE_PERFORMER_CLASS && method.name == "LayoutParams"
|
||||
|
||||
if (hasHikageable || hasLayoutParams) visitAndLint(callExpr, method)
|
||||
}
|
||||
|
||||
@@ -85,6 +87,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
|
||||
reports.forEach {
|
||||
// Check if the call has been reported before reporting.
|
||||
if (reportedNodes.contains(it.callExpr)) return@forEach
|
||||
|
||||
val location = context.getLocation(it.callExpr)
|
||||
val lintFix = LintFix.create()
|
||||
.name("Delete Call Expression")
|
||||
@@ -93,6 +96,7 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
|
||||
// Delete the call expression.
|
||||
.with("")
|
||||
.build()
|
||||
|
||||
context.report(ISSUE, it.callExpr, location, it.message, lintFix)
|
||||
reportedNodes.add(it.callExpr)
|
||||
}
|
||||
@@ -102,28 +106,37 @@ class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
|
||||
val bodyBlocks = mutableMapOf<String, KtExpression>()
|
||||
val parameters = method.parameterList.parameters
|
||||
val valueArguments = callExpr.valueArgumentList?.arguments ?: emptyList()
|
||||
|
||||
fun visitValueArg(arg: KtValueArgument) {
|
||||
val name = arg.getArgumentName()?.asName?.identifier ?: ""
|
||||
val expr = arg.getArgumentExpression()
|
||||
|
||||
val parameter = parameters.firstOrNull { it.name == name }
|
||||
// If the last bit is a lambda expression, then `parameter` must have a lambda parameter defined by the last bit.
|
||||
?: if (arg is KtLambdaArgument) parameters.lastOrNull() else null
|
||||
|
||||
val isMatched = parameter?.type?.canonicalText?.matches(DeclaredSymbol.HIKAGE_VIEW_REGEX) == true &&
|
||||
!parameter.type.canonicalText.contains(DeclaredSymbol.HIKAGE_PERFORMER_CLASS)
|
||||
|
||||
if (expr is KtLambdaExpression && isMatched)
|
||||
expr.bodyExpression?.let { bodyBlocks[name] = it }
|
||||
}
|
||||
|
||||
// Get the last lambda expression.
|
||||
val lastLambda = callExpr.lambdaArguments.lastOrNull()
|
||||
if (lastLambda != null) visitValueArg(lastLambda)
|
||||
|
||||
valueArguments.forEach { arg -> visitValueArg(arg) }
|
||||
|
||||
bodyBlocks.toList().flatMap { (_, value) -> value.children.filterIsInstance<KtCallExpression>() }.forEach {
|
||||
val expression = it.toUElementOfType<UCallExpression>() ?: return@forEach
|
||||
val sCallExpr = expression.sourcePsi as? KtCallExpression ?: return@forEach
|
||||
val sMethod = expression.resolve() ?: return@forEach
|
||||
|
||||
if (sMethod.hasHikageable()) {
|
||||
val message = "Performers are not allowed to appear in `${method.name}` DSL creation process."
|
||||
reports.add(ReportDetail(message, expression))
|
||||
|
||||
// Recursively to visit next level.
|
||||
visitAndLint(sCallExpr, sMethod)
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
|
||||
|
||||
override fun visitMethod(node: UMethod) {
|
||||
val uastBody = node.uastBody as? UBlockExpression ?: return
|
||||
|
||||
val bodyHasHikageable = uastBody.expressions.any {
|
||||
when (it) {
|
||||
is UCallExpression -> it.resolve()?.hasHikageable() ?: false
|
||||
@@ -73,17 +74,22 @@ class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.hasHikageable() && bodyHasHikageable) {
|
||||
val location = context.getLocation(node)
|
||||
val nameLocation = context.getNameLocation(node)
|
||||
|
||||
val message = "Function `${node.name}` must be marked with the `@Hikageable` annotation."
|
||||
|
||||
val functionText = node.asSourceString()
|
||||
val hasDoubleSlash = functionText.startsWith("//")
|
||||
|
||||
val replacement = functionRegex.replace(functionText) { result ->
|
||||
val functionBody = result.groupValues.getOrNull(0) ?: functionText
|
||||
val prefix = if (hasDoubleSlash) "\n" else ""
|
||||
"$prefix@Hikageable $functionBody"
|
||||
}
|
||||
|
||||
val lintFix = LintFix.create()
|
||||
.name("Add '@Hikageable' to '${node.name}'")
|
||||
.replace()
|
||||
@@ -92,6 +98,7 @@ class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
|
||||
.imports(DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS)
|
||||
.reformat(true)
|
||||
.build()
|
||||
|
||||
context.report(ISSUE, node, nameLocation, message, lintFix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,18 +135,23 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
|
||||
importRefs.getOrNull(1)?.trim()
|
||||
else null
|
||||
val importPrefix = importRefs[0]
|
||||
|
||||
val hasPrefix = importPrefix.contains(".")
|
||||
|
||||
val name = if (hasPrefix)
|
||||
importPrefix.split(".").last()
|
||||
else importPrefix
|
||||
|
||||
val packagePrefix = importPrefix.replace(if (hasPrefix) ".$name" else name, "")
|
||||
val reference = ImportReference(packagePrefix, name, alias)
|
||||
|
||||
importReferences.add(reference)
|
||||
}
|
||||
|
||||
override fun visitCallExpression(node: UCallExpression) {
|
||||
val callExpr = node.sourcePsi as? KtCallExpression ?: return
|
||||
val method = node.resolve() ?: return
|
||||
|
||||
startLint(callExpr, method)
|
||||
}
|
||||
|
||||
@@ -165,7 +170,9 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
|
||||
type.canonicalText == VIEW_GROUP_CLASS_NAME
|
||||
}
|
||||
}
|
||||
|
||||
val isTypedViewFunction = typedViewFunctionIndex >= 0
|
||||
|
||||
val imports = typeArgumentsText.mapNotNull { typeName ->
|
||||
when {
|
||||
// Like `TextView`.
|
||||
@@ -179,18 +186,24 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val matchedIndex = builtInWidgets.indexOfFirst { imports.any { e -> e.alias == it || e.name == it } }
|
||||
val isBuiltInWidget = matchedIndex >= 0
|
||||
|
||||
if (isTypedViewFunction && isBuiltInWidget) {
|
||||
val widgetName = builtInWidgets[matchedIndex]
|
||||
|
||||
val sourceLocation = context.getLocation(callExpr)
|
||||
val sourceText = callExpr.toUElement()?.asSourceString() ?: return
|
||||
val callExprElement = callExpr.toUElement() ?: return
|
||||
|
||||
// Matchs '>' and like `View<TextView`'s length + 1.
|
||||
val callExprLength = sourceText.split(">")[0].trim().length + 1
|
||||
val nameLocation = context.getRangeLocation(callExprElement, fromDelta = 0, callExprLength)
|
||||
|
||||
// Only replace the first one, because there may be multiple sub-functions in DSL.
|
||||
val replacement = sourceText.replaceFirst(viewExpressionRegex, widgetName)
|
||||
|
||||
val lintFix = LintFix.create()
|
||||
.name("Replace with '$widgetName'")
|
||||
.replace()
|
||||
@@ -199,6 +212,7 @@ class WidgetsUsageDetector : Detector(), Detector.UastScanner {
|
||||
.imports("$WIDGET_FUNCTION_PREFIX.$widgetName")
|
||||
.reformat(true)
|
||||
.build()
|
||||
|
||||
val message = "Can be simplified to `$widgetName`."
|
||||
context.report(ISSUE, callExpr, nameLocation, message, lintFix)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user