From d0d846ccfc36f84d357572334a74dc8aaee16cef Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 20 Aug 2015 17:17:49 +0300 Subject: [PATCH] Report if the widget has an invalid type --- .../cli/jvm/compiler/KotlinCoreEnvironment.kt | 2 + .../StorageComponentContainerContributor.kt | 28 ++++++++++++ .../jetbrains/kotlin/frontend/di/injection.kt | 5 +++ idea/src/META-INF/extensions/common.xml | 3 ++ .../synthetic/AndroidComponentRegistrar.kt | 15 +++++++ .../kotlin/android/synthetic/AndroidConst.kt | 5 ++- .../AndroidExtensionPropertiesCallChecked.kt | 45 +++++++++++++++++++ .../diagnostic/DefaultErrorMessagesAndroid.kt | 36 +++++++++++++++ .../synthetic/diagnostic/ErrorsAndroid.java | 35 +++++++++++++++ .../synthetic/res/SyntheticFileGenerator.kt | 32 +++++++++---- .../android/synthetic/res/androidResources.kt | 6 ++- .../src/META-INF/plugin.xml | 2 + 12 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/extensions/StorageComponentContainerContributor.kt create mode 100644 plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/AndroidExtensionPropertiesCallChecked.kt create mode 100644 plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt create mode 100644 plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index cd982b1ecc0..487dd244a0a 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -71,6 +71,7 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.kotlinSourceRoots import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor import org.jetbrains.kotlin.idea.JetFileType import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinderFactory import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache @@ -144,6 +145,7 @@ public class KotlinCoreEnvironment private constructor( ExpressionCodegenExtension.registerExtensionPoint(project) ClassBuilderInterceptorExtension.registerExtensionPoint(project) AnalysisCompletedHandlerExtension.registerExtensionPoint(project) + StorageComponentContainerContributor.registerExtensionPoint(project) for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) { registrar.registerProjectComponents(project, configuration) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/extensions/StorageComponentContainerContributor.kt b/compiler/frontend/src/org/jetbrains/kotlin/extensions/StorageComponentContainerContributor.kt new file mode 100644 index 00000000000..286b045b736 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/extensions/StorageComponentContainerContributor.kt @@ -0,0 +1,28 @@ +/* + * 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.extensions + +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.resolve.TargetPlatform + +public interface StorageComponentContainerContributor { + companion object : ProjectExtensionDescriptor( + "org.jetbrains.kotlin.storageComponentContainerContributor", javaClass() + ) + + public fun addDeclarations(container: StorageComponentContainer, platform: TargetPlatform) +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt b/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt index dfd22dcf86a..823b36dbd64 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.container.* import org.jetbrains.kotlin.context.LazyResolveToken import org.jetbrains.kotlin.context.ModuleContext import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.lazy.FileScopeProvider @@ -42,6 +43,10 @@ public fun StorageComponentContainer.configureModule( useInstance(platform) platform.platformConfigurator.configure(this) + + for (extension in StorageComponentContainerContributor.getInstances(moduleContext.project)) { + extension.addDeclarations(this, platform) + } } public fun StorageComponentContainer.configureModule( diff --git a/idea/src/META-INF/extensions/common.xml b/idea/src/META-INF/extensions/common.xml index f33953893b8..800ebe14a84 100644 --- a/idea/src/META-INF/extensions/common.xml +++ b/idea/src/META-INF/extensions/common.xml @@ -25,5 +25,8 @@ area="IDEA_PROJECT"/> + diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt index fd0ca0eddb4..e3301bbdf69 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt @@ -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> = CompilerConfigurationKey.create>("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()) } } } \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt index 667f0ffc7a1..1c46d03b10b 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/AndroidConst.kt @@ -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) } } } \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/AndroidExtensionPropertiesCallChecked.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/AndroidExtensionPropertiesCallChecked.kt new file mode 100644 index 00000000000..2214a43b446 --- /dev/null +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/AndroidExtensionPropertiesCallChecked.kt @@ -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 check(resolvedCall: ResolvedCall, 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)) + } + +} \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt new file mode 100644 index 00000000000..7ea2c7099d3 --- /dev/null +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt @@ -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 +} \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java new file mode 100644 index 00000000000..a67f3634ca5 --- /dev/null +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java @@ -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 SYNTHETIC_INVALID_WIDGET_TYPE = DiagnosticFactory1.create(WARNING); + + @SuppressWarnings("UnusedDeclaration") + Object _initializer = new Object() { + { + Errors.Initializer.initializeFactoryNames(ErrorsAndroid.class); + } + }; + +} diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/SyntheticFileGenerator.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/SyntheticFileGenerator.kt index b41aa615f60..c73d749bdc2 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/SyntheticFileGenerator.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/SyntheticFileGenerator.kt @@ -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, scope: GlobalSearchScope): List @@ -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") + 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", + "annotation class $INVALID_WIDGET_TYPE_ANNOTATION(public val $INVALID_WIDGET_TYPE_ANNOTATION_TYPE_PARAMETER: String)") } } \ No newline at end of file diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/androidResources.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/androidResources.kt index 276808f8cda..88a01f9c5dd 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/androidResources.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/res/androidResources.kt @@ -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)", diff --git a/plugins/android-idea-plugin/src/META-INF/plugin.xml b/plugins/android-idea-plugin/src/META-INF/plugin.xml index 921c1a6b0cc..86203e5aeac 100644 --- a/plugins/android-idea-plugin/src/META-INF/plugin.xml +++ b/plugins/android-idea-plugin/src/META-INF/plugin.xml @@ -29,5 +29,7 @@ + +