Bump hikage-core, hikage-extension, hikage-extension-betterandroid, hikage-extension-compose, hikage-compiler, hikage-widget-androidx, hikage-widget-material version to 1.0.0

This commit is contained in:
2025-04-20 05:32:06 +08:00
parent 794c535789
commit 99abe3cd18
218 changed files with 13256 additions and 627 deletions

View File

@@ -0,0 +1,46 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
autowire(libs.plugins.kotlin.jvm)
}
group = property.project.groupName
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlin {
jvmToolchain(17)
compilerOptions {
freeCompilerArgs = listOf(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
}
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
tasks.named<Jar>("jar") {
manifest {
attributes(
"Lint-Registry-V2" to property.project.hikage.core.lint.registry.v2.clazz
)
}
}
dependencies {
compileOnly(org.jetbrains.kotlin.kotlin.stdlib)
compileOnly(com.android.tools.lint.lint.api)
compileOnly(com.android.tools.lint.lint.checks)
testImplementation(com.android.tools.lint.lint)
testImplementation(com.android.tools.lint.lint.tests)
}

View File

@@ -0,0 +1,31 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/3/17.
*/
package com.highcapable.hikage.core.lint
object DeclaredSymbol {
const val HIKAGEABLE_ANNOTATION_CLASS = "com.highcapable.hikage.annotation.Hikageable"
const val HIKAGE_CLASS = "com.highcapable.hikage.core.Hikage"
const val HIKAGE_PERFORMER_CLASS = "com.highcapable.hikage.core.Hikage.Performer"
val HIKAGE_VIEW_REGEX = "kotlin.jvm.functions.Function1<\\?\\s*super\\s+[^,]+,kotlin.Unit>".toRegex()
}

View File

@@ -0,0 +1,52 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/3/14.
*/
@file:Suppress("unused")
package com.highcapable.hikage.core.lint
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.highcapable.hikage.core.lint.detector.HikageSafeTypeCastDetector
import com.highcapable.hikage.core.lint.detector.HikageableBeyondScopeDetector
import com.highcapable.hikage.core.lint.detector.HikageableFunctionsDetector
import com.highcapable.hikage.core.lint.detector.WidgetsUsageDetector
import com.highcapable.hikage.generated.HikageCoreLintProperties
class HikageIssueRegistry : IssueRegistry() {
override val issues get() = listOf(
HikageableBeyondScopeDetector.ISSUE,
HikageableFunctionsDetector.ISSUE,
HikageSafeTypeCastDetector.ISSUE,
WidgetsUsageDetector.ISSUE
)
override val minApi = HikageCoreLintProperties.PROJECT_HIKAGE_CORE_LINT_MIN_API
override val api = CURRENT_API
override val vendor = Vendor(
vendorName = HikageCoreLintProperties.PROJECT_NAME,
identifier = HikageCoreLintProperties.PROJECT_HIKAGE_CORE_LINT_IDENTIFIER,
feedbackUrl = "${HikageCoreLintProperties.PROJECT_URL}/issues",
contact = HikageCoreLintProperties.PROJECT_URL
)
}

View File

@@ -0,0 +1,111 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/4/2.
*/
package com.highcapable.hikage.core.lint.detector
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.highcapable.hikage.core.lint.DeclaredSymbol
import org.jetbrains.uast.UArrayAccessExpression
import org.jetbrains.uast.UBinaryExpressionWithType
import org.jetbrains.uast.UParenthesizedExpression
import org.jetbrains.uast.UQualifiedReferenceExpression
class HikageSafeTypeCastDetector : Detector(), Detector.UastScanner {
companion object {
val ISSUE = Issue.create(
id = "UseHikageSafeTypeCast",
briefDescription = "Hikage safe type cast usage.",
explanation = "Recommended to use `hikage.get<YourView>(\"your_id\")` instead of `hikage[\"your_id\"] as YourView`.",
category = Category.COMPLIANCE,
priority = 5,
severity = Severity.WARNING,
implementation = Implementation(
HikageSafeTypeCastDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
}
override fun getApplicableUastTypes() = listOf(UQualifiedReferenceExpression::class.java, UBinaryExpressionWithType::class.java)
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
if (node.selector !is UBinaryExpressionWithType) return
val castExpr = node.selector as UBinaryExpressionWithType
visitAndLint(context, castExpr, node)
}
override fun visitBinaryExpressionWithType(node: UBinaryExpressionWithType) {
visitAndLint(context, node)
}
private fun visitAndLint(
context: JavaContext,
node: UBinaryExpressionWithType,
parent: UQualifiedReferenceExpression? = null
) {
// 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"]`.
val receiverText = receiver.sourcePsi?.text ?: return
// Like `hikage`.
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'")
.replace()
.range(location)
.with(replacement)
.reformat(true)
.build()
context.report(
ISSUE, locationNode, location,
message = "Can be replaced with safe type cast `$replaceSuggestion`.",
quickfixData = lintFix
)
}
}
}

View File

@@ -0,0 +1,133 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/3/17.
*/
package com.highcapable.hikage.core.lint.detector
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.highcapable.hikage.core.lint.DeclaredSymbol
import com.highcapable.hikage.core.lint.detector.entity.ReportDetail
import com.highcapable.hikage.core.lint.detector.extension.hasHikageable
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLambdaArgument
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.toUElementOfType
class HikageableBeyondScopeDetector : Detector(), Detector.UastScanner {
companion object {
val ISSUE = Issue.create(
id = "HikageableBeyondScope",
briefDescription = "Hikageable beyond scope.",
explanation = "Functions marked with `@Hikageable` can only be passed in `Hikage.Performer`.",
category = Category.COMPLIANCE,
priority = 10,
severity = Severity.ERROR,
implementation = Implementation(
HikageableBeyondScopeDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
}
override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
private val reportedNodes = mutableSetOf<UCallExpression>()
private val reports = mutableListOf<ReportDetail>()
override fun visitCallExpression(node: UCallExpression) {
val callExpr = node.sourcePsi as? KtCallExpression ?: return
val method = node.resolve() ?: return
startLint(callExpr, method)
organizeAndReport()
}
private fun startLint(callExpr: KtCallExpression, method: PsiMethod) {
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)
}
private fun organizeAndReport() {
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")
.replace()
.range(location)
// Delete the call expression.
.with("")
.build()
context.report(ISSUE, it.callExpr, location, it.message, lintFix)
reportedNodes.add(it.callExpr)
}
}
private fun visitAndLint(callExpr: KtCallExpression, method: PsiMethod) {
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)
}
}
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/3/17.
*/
package com.highcapable.hikage.core.lint.detector
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.highcapable.hikage.core.lint.DeclaredSymbol
import com.highcapable.hikage.core.lint.detector.extension.hasHikageable
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UBlockExpression
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UReturnExpression
import org.jetbrains.uast.tryResolve
class HikageableFunctionsDetector : Detector(), Detector.UastScanner {
companion object {
val ISSUE = Issue.create(
id = "HikageableFunctions",
briefDescription = "Hikageable functions.",
explanation = "Functions which invoke `@Hikageable` functions must be marked with the `@Hikageable` annotation.",
category = Category.COMPLIANCE,
priority = 10,
severity = Severity.ERROR,
implementation = Implementation(
HikageableFunctionsDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
private val functionRegex = "(\\s?.+)?fun\\s?".toRegex()
}
override fun getApplicableUastTypes() = listOf(UMethod::class.java)
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
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
is UReturnExpression ->
(it.returnExpression?.tryResolve() as? PsiMethod?)?.hasHikageable() ?: false
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()
.range(location)
.with(replacement)
.imports(DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS)
.reformat(true)
.build()
context.report(ISSUE, node, nameLocation, message, lintFix)
}
}
}
}

View File

@@ -0,0 +1,207 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/3/17.
*/
package com.highcapable.hikage.core.lint.detector
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.highcapable.hikage.core.lint.detector.extension.hasHikageable
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UImportStatement
import org.jetbrains.uast.toUElement
class WidgetsUsageDetector : Detector(), Detector.UastScanner {
companion object {
val ISSUE = Issue.create(
id = "ReplaceWithHikageWidgets",
briefDescription = "Hikage built-in widget usability.",
explanation = "Use the built-in widget function component provided by Hikage like `TextView(...)` " +
"without using a form like `View<TextView>(...)` to use the component.",
category = Category.USABILITY,
priority = 5,
severity = Severity.WARNING,
implementation = Implementation(
WidgetsUsageDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
private const val BUILT_IN_WIDGETS_PACKAGE_PREFIX = "android.widget"
private const val WIDGET_FUNCTION_PREFIX = "com.highcapable.hikage.widget.$BUILT_IN_WIDGETS_PACKAGE_PREFIX"
private const val VIEW_CLASS_NAME = "android.view.View"
private const val VIEW_GROUP_CLASS_NAME = "android.view.ViewGroup"
private val viewExpressionRegex = "(?:View|ViewGroup)<.*?>".toRegex()
private val builtInWidgets = listOf(
"SeekBar",
"LinearLayout",
"ScrollView",
"TextView",
"EditText",
"AutoCompleteTextView",
"ExpandableListView",
"ListView",
"RatingBar",
"ViewSwitcher",
"ActionMenuView",
"ImageView",
"ViewAnimator",
"HorizontalScrollView",
"MediaController",
"RelativeLayout",
"TextClock",
"CalendarView",
"ToggleButton",
"RadioGroup",
"VideoView",
"GridView",
"QuickContactBadge",
"TableLayout",
"NumberPicker",
"Toolbar",
"ViewFlipper",
"Chronometer",
"ImageSwitcher",
"Button",
"CheckBox",
"TabWidget",
"TabHost",
"SearchView",
"Spinner",
"TimePicker",
"ImageButton",
"TextSwitcher",
"DatePicker",
"RadioButton",
"CheckedTextView",
"FrameLayout",
"Space",
"GridLayout",
"Switch",
"ProgressBar",
"TableRow"
)
}
data class ImportReference(val packagePrefix: String, val name: String, val alias: String? = null)
override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UImportStatement::class.java, UCallExpression::class.java)
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
private val importReferences = mutableSetOf<ImportReference>()
override fun visitImportStatement(node: UImportStatement) {
val imported = node.asSourceString().replace("import", "").let {
when {
it.contains("//") -> it.split("//")[0]
it.contains("/*") -> it.split("/*")[0]
else -> it
}
}.trim()
val importRefs = imported.split(" as ")
val alias = if (importRefs.size > 1)
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)
}
private fun startLint(callExpr: KtCallExpression, method: PsiMethod) {
val hasHikageable = method.hasHikageable()
if (hasHikageable) visitAndReport(callExpr, method)
}
private fun visitAndReport(callExpr: KtCallExpression, method: PsiMethod) {
val typeParameters = method.typeParameterList?.typeParameters ?: emptyArray()
val typeArguments = callExpr.typeArgumentList?.arguments ?: emptyList()
val typeArgumentsText = typeArguments.mapNotNull { it.text }
val typedViewFunctionIndex = typeParameters.indexOfFirst {
it.extendsListTypes.any { type ->
type.canonicalText == VIEW_CLASS_NAME ||
type.canonicalText == VIEW_GROUP_CLASS_NAME
}
}
val isTypedViewFunction = typedViewFunctionIndex >= 0
val imports = typeArgumentsText.mapNotNull { typeName ->
when {
// Like `TextView`.
!typeName.contains(".") -> importReferences.firstOrNull {
it.packagePrefix == BUILT_IN_WIDGETS_PACKAGE_PREFIX &&
(it.alias == typeName || it.name == typeName)
}
// Like `android.widget.TextView`.
typeName.startsWith(BUILT_IN_WIDGETS_PACKAGE_PREFIX) ->
ImportReference(BUILT_IN_WIDGETS_PACKAGE_PREFIX, typeName.replace("$BUILT_IN_WIDGETS_PACKAGE_PREFIX.", ""))
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()
.range(sourceLocation)
.with(replacement)
.imports("$WIDGET_FUNCTION_PREFIX.$widgetName")
.reformat(true)
.build()
val message = "Can be simplified to `$widgetName`."
context.report(ISSUE, callExpr, nameLocation, message, lintFix)
}
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/3/17.
*/
package com.highcapable.hikage.core.lint.detector.entity
import org.jetbrains.uast.UCallExpression
data class ReportDetail(
val message: String,
val callExpr: UCallExpression
)

View File

@@ -0,0 +1,27 @@
/*
* Hikage - An Android responsive UI building tool.
* Copyright (C) 2019 HighCapable
* https://github.com/BetterAndroid/Hikage
*
* Apache License Version 2.0
*
* 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
*
* https://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.
*
* This file is created by fankes on 2025/3/30.
*/
package com.highcapable.hikage.core.lint.detector.extension
import com.highcapable.hikage.core.lint.DeclaredSymbol
import com.intellij.psi.PsiMethod
fun PsiMethod.hasHikageable() = hasAnnotation(DeclaredSymbol.HIKAGEABLE_ANNOTATION_CLASS)