Report if the widget has an invalid type
This commit is contained in:
+15
@@ -21,6 +21,7 @@ import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.android.synthetic.codegen.AndroidExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.android.synthetic.diagnostic.AndroidExtensionPropertiesCallChecker
|
||||
import org.jetbrains.kotlin.android.synthetic.res.AndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
|
||||
import org.jetbrains.kotlin.android.synthetic.res.CliSyntheticFileGenerator
|
||||
@@ -32,8 +33,13 @@ import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
|
||||
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.CompilerConfigurationKey
|
||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||
import org.jetbrains.kotlin.container.useInstance
|
||||
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
|
||||
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.resolve.TargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||
|
||||
public object AndroidConfigurationKeys {
|
||||
public val ANDROID_RES_PATH: CompilerConfigurationKey<List<String>> = CompilerConfigurationKey.create<List<String>>("android resources search path")
|
||||
@@ -86,6 +92,15 @@ public class AndroidComponentRegistrar : ComponentRegistrar {
|
||||
|
||||
ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project))
|
||||
ExpressionCodegenExtension.registerExtension(project, AndroidExpressionCodegenExtension())
|
||||
StorageComponentContainerContributor.registerExtension(project, AndroidExtensionPropertiesComponentContainerContributor())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AndroidExtensionPropertiesComponentContainerContributor : StorageComponentContainerContributor {
|
||||
override fun addDeclarations(container: StorageComponentContainer, platform: TargetPlatform) {
|
||||
if (platform is JvmPlatform) {
|
||||
container.useInstance(AndroidExtensionPropertiesCallChecker())
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -81,8 +81,9 @@ public fun parseAndroidResource(id: String, tag: String, fqNameResolver: (String
|
||||
"fragment" -> AndroidFragment(id)
|
||||
"include" -> AndroidWidget(id, AndroidConst.VIEW_FQNAME)
|
||||
else -> {
|
||||
val fqName = fqNameResolver(tag) ?: AndroidConst.VIEW_FQNAME
|
||||
AndroidWidget(id, fqName)
|
||||
val fqName = fqNameResolver(tag)
|
||||
val invalidType = if (fqName != null) null else tag
|
||||
AndroidWidget(id, fqName ?: AndroidConst.VIEW_FQNAME, invalidType)
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.synthetic.diagnostic
|
||||
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||
import org.jetbrains.kotlin.android.synthetic.res.SyntheticFileGenerator
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
|
||||
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
|
||||
public class AndroidExtensionPropertiesCallChecker : CallChecker {
|
||||
override fun <F : CallableDescriptor> check(resolvedCall: ResolvedCall<F>, context: BasicCallResolutionContext) {
|
||||
val expression = context.call.calleeExpression ?: return
|
||||
|
||||
val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return
|
||||
val syntheticPackage = propertyDescriptor.containingDeclaration as? PackageFragmentDescriptor ?: return
|
||||
if (!syntheticPackage.fqName.asString().startsWith("${AndroidConst.SYNTHETIC_PACKAGE}.")) return
|
||||
|
||||
val invalidWidgetTypeAnnotation = propertyDescriptor.annotations.findAnnotation(
|
||||
SyntheticFileGenerator.INVALID_WIDGET_TYPE_ANNOTATION_FQNAME) ?: return
|
||||
|
||||
val type = invalidWidgetTypeAnnotation.allValueArguments.filterKeys {
|
||||
it.name.asString() == SyntheticFileGenerator.INVALID_WIDGET_TYPE_ANNOTATION_TYPE_PARAMETER
|
||||
}.values().firstOrNull() as? StringValue ?: return
|
||||
|
||||
context.trace.report(ErrorsAndroid.SYNTHETIC_INVALID_WIDGET_TYPE.on(expression, type.value))
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.synthetic.diagnostic
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
|
||||
|
||||
public class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
|
||||
|
||||
private companion object {
|
||||
val MAP = DiagnosticFactoryToRendererMap()
|
||||
|
||||
init {
|
||||
MAP.put(ErrorsAndroid.SYNTHETIC_INVALID_WIDGET_TYPE,
|
||||
"Widget has an invalid type ''{0}''. Please specify the fully qualified widget class name in XML",
|
||||
Renderers.TO_STRING)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMap() = MAP
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.synthetic.diagnostic;
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
|
||||
import org.jetbrains.kotlin.diagnostics.Errors;
|
||||
import org.jetbrains.kotlin.psi.JetExpression;
|
||||
|
||||
import static org.jetbrains.kotlin.diagnostics.Severity.WARNING;
|
||||
|
||||
public interface ErrorsAndroid {
|
||||
DiagnosticFactory1<JetExpression, String> SYNTHETIC_INVALID_WIDGET_TYPE = DiagnosticFactory1.create(WARNING);
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
Object _initializer = new Object() {
|
||||
{
|
||||
Errors.Initializer.initializeFactoryNames(ErrorsAndroid.class);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+24
-8
@@ -28,10 +28,15 @@ import com.intellij.testFramework.LightVirtualFile
|
||||
import org.jetbrains.kotlin.android.synthetic.AndroidConst
|
||||
import org.jetbrains.kotlin.android.synthetic.KotlinStringWriter
|
||||
import org.jetbrains.kotlin.android.synthetic.escapeAndroidIdentifier
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.types.Flexibility
|
||||
|
||||
public class AndroidSyntheticFile(val name: String, val contents: String)
|
||||
public class AndroidSyntheticFile(val name: String, val contents: String) {
|
||||
companion object {
|
||||
fun create(name: String, vararg contents: String) = AndroidSyntheticFile(name, contents.joinToString("\n\n"))
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
|
||||
@@ -73,7 +78,7 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
}
|
||||
val clearCacheFile = renderSyntheticFile
|
||||
|
||||
return listOf(clearCacheFile, FLEXIBLE_TYPE_FILE)
|
||||
return listOf(clearCacheFile, TOOLS_FILE)
|
||||
}
|
||||
|
||||
protected abstract fun extractLayoutResources(files: List<PsiFile>, scope: GlobalSearchScope): List<AndroidResource>
|
||||
@@ -120,10 +125,15 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
}
|
||||
|
||||
private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, resource: AndroidResource, stubCall: String) {
|
||||
// extract startsWith() to fun
|
||||
val className = if (isFromSupportV4Package(receiver)) resource.supportClassName else resource.className
|
||||
val cast = if (resource.className != "View") " as? $className" else ""
|
||||
val body = arrayListOf("return $stubCall$cast")
|
||||
|
||||
// Annotation on wrong widget type
|
||||
if (resource is AndroidWidget && resource.invalidType != null) {
|
||||
writeText("@${INVALID_WIDGET_TYPE_ANNOTATION_FQNAME.asString()}(\"${resource.invalidType}\")")
|
||||
}
|
||||
|
||||
writeImmutableExtensionProperty(receiver,
|
||||
name = resource.id,
|
||||
retType = "$EXPLICIT_FLEXIBLE_CLASS_NAME<$className, $className?>",
|
||||
@@ -172,9 +182,9 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
|
||||
if (!res.sameClass(existing)) {
|
||||
resourcesToExclude.add(res.id)
|
||||
} else if (res is AndroidWidget && existing.className != res.className && existing.className != "View") {
|
||||
} else if (res is AndroidWidget && existing.className != res.className && existing.className != AndroidConst.VIEW_FQNAME) {
|
||||
// Widgets with the same id but different types exist.
|
||||
resourceMap.put(res.id, AndroidWidget(res.id, "View"))
|
||||
resourceMap.put(res.id, AndroidWidget(res.id, AndroidConst.VIEW_FQNAME))
|
||||
}
|
||||
}
|
||||
else resourceMap.put(res.id, res)
|
||||
@@ -198,14 +208,20 @@ public abstract class SyntheticFileGenerator(protected val project: Project) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
public companion object {
|
||||
private val EXPLICIT_FLEXIBLE_PACKAGE = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.packageFqName.asString()
|
||||
private val EXPLICIT_FLEXIBLE_CLASS_NAME = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.relativeClassName.asString()
|
||||
|
||||
private val ANDROID_IMPORTS = listOf(Flexibility.FLEXIBLE_TYPE_CLASSIFIER.asSingleFqName().asString())
|
||||
|
||||
private val FLEXIBLE_TYPE_FILE =
|
||||
AndroidSyntheticFile("ft", "package $EXPLICIT_FLEXIBLE_PACKAGE\n\nclass $EXPLICIT_FLEXIBLE_CLASS_NAME<L, U>")
|
||||
private val INVALID_WIDGET_TYPE_ANNOTATION = "InvalidWidgetType"
|
||||
public val INVALID_WIDGET_TYPE_ANNOTATION_TYPE_PARAMETER: String = "type"
|
||||
public val INVALID_WIDGET_TYPE_ANNOTATION_FQNAME: FqName = FqName("$EXPLICIT_FLEXIBLE_PACKAGE.$INVALID_WIDGET_TYPE_ANNOTATION")
|
||||
|
||||
private val TOOLS_FILE = AndroidSyntheticFile.create("tools",
|
||||
"package $EXPLICIT_FLEXIBLE_PACKAGE",
|
||||
"class $EXPLICIT_FLEXIBLE_CLASS_NAME<L, U>",
|
||||
"annotation class $INVALID_WIDGET_TYPE_ANNOTATION(public val $INVALID_WIDGET_TYPE_ANNOTATION_TYPE_PARAMETER: String)")
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -34,7 +34,11 @@ public abstract class AndroidResource(val id: String) {
|
||||
public open fun sameClass(other: AndroidResource): Boolean = false
|
||||
}
|
||||
|
||||
public class AndroidWidget(id: String, override val className: String) : AndroidResource(id) {
|
||||
public class AndroidWidget(
|
||||
id: String,
|
||||
override val className: String,
|
||||
val invalidType: String? = null // When widget type is invalid, this value is the widget tag name
|
||||
) : AndroidResource(id) {
|
||||
private companion object {
|
||||
val MAIN_PROPERTIES = listOf(
|
||||
AndroidConst.ACTIVITY_FQNAME to "findViewById(0)",
|
||||
|
||||
Reference in New Issue
Block a user