KT-2752: add diagnostic that checks whether Kotlin declarations produce conflicting names in generated JS

This commit is contained in:
Alexey Andreev
2016-05-27 15:33:03 +03:00
parent f70b50b6e2
commit 8f829557c8
26 changed files with 381 additions and 1 deletions
@@ -24,9 +24,10 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject
import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
import java.util.*
class FQNGenerator {
private val cache = mutableMapOf<DeclarationDescriptor, FQNPart>()
private val cache: MutableMap<DeclarationDescriptor, FQNPart> = WeakHashMap()
fun generate(descriptor: DeclarationDescriptor) = cache.getOrPut(descriptor) { generateCacheMiss(descriptor.original) }
@@ -19,7 +19,9 @@ package org.jetbrains.kotlin.js.resolve
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useImpl
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.js.naming.FQNGenerator
import org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker
import org.jetbrains.kotlin.js.resolve.diagnostics.JsNameChecker
import org.jetbrains.kotlin.js.resolve.diagnostics.NativeInnerClassChecker
import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap
import org.jetbrains.kotlin.resolve.IdentifierChecker
@@ -46,5 +48,6 @@ object JsPlatformConfigurator : PlatformConfigurator(
container.useInstance(SyntheticScopes.Empty)
container.useInstance(SyntheticConstructorsProvider.Empty)
container.useInstance(JsTypeSpecificityComparator)
container.useInstance(JsNameChecker())
}
}
@@ -37,6 +37,8 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
put(ErrorsJs.REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED, "Callable references for builtin members are not supported yet: ''{0}''", RenderFirstLineOfElementText)
put(ErrorsJs.JSCODE_NO_JAVASCRIPT_PRODUCED, "Argument must be non-empty JavaScript code")
put(ErrorsJs.NATIVE_INNER_CLASS_PROHIBITED, "Native inner classes are prohibited")
put(ErrorsJs.JS_NAME_CLASH, "JavaScript name ({0}) generated for this declaration clashes with another declaration: {1}",
Renderers.STRING, Renderers.COMPACT)
this
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.js.resolve.diagnostics;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2;
@@ -43,6 +44,7 @@ public interface ErrorsJs {
DiagnosticFactory1<KtElement, KtElement> REFERENCE_TO_BUILTIN_MEMBERS_NOT_SUPPORTED = DiagnosticFactory1.create(ERROR, DEFAULT);
DiagnosticFactory0<KtExpression> JSCODE_NO_JAVASCRIPT_PRODUCED = DiagnosticFactory0.create(ERROR, DEFAULT);
DiagnosticFactory0<KtExpression> NATIVE_INNER_CLASS_PROHIBITED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<KtElement, String, DeclarationDescriptor> JS_NAME_CLASH = DiagnosticFactory2.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
@@ -0,0 +1,106 @@
/*
* Copyright 2010-2016 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.js.resolve.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.js.naming.FQNGenerator
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DeclarationChecker
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.source.getPsi
class JsNameChecker : DeclarationChecker {
private val fqnGenerator = FQNGenerator()
private val scopes = mutableMapOf<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
private val clashedDescriptors = mutableSetOf<DeclarationDescriptor>()
override fun check(
declaration: KtDeclaration,
descriptor: DeclarationDescriptor,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext
) {
checkDescriptor(descriptor, declaration, diagnosticHolder)
}
private fun checkDescriptor(descriptor: DeclarationDescriptor, declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) {
val fqn = fqnGenerator.generate(descriptor)
if (fqn.shared && fqn.scope is ClassOrPackageFragmentDescriptor && isOpaque(fqn.descriptor)) {
val scope = getScope(fqn.scope)
val name = fqn.names.last()
val existing = scope[name]
if (existing != null && existing != fqn.descriptor) {
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing))
val existingDeclaration = findPsi(existing) ?: declaration
if (clashedDescriptors.add(existing) && existingDeclaration is KtDeclaration && existingDeclaration != declaration) {
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor))
}
}
}
}
private fun findPsi(descriptor: DeclarationDescriptor): PsiElement? {
val psi = (descriptor as? DeclarationDescriptorWithSource)?.source?.getPsi()
return if (psi == null && descriptor is CallableMemberDescriptor &&
descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
) {
descriptor.overriddenDescriptors.mapNotNull { findPsi(it) }.firstOrNull()
}
else {
psi
}
}
private fun getScope(descriptor: DeclarationDescriptor) = scopes.getOrPut(descriptor) {
val scope = mutableMapOf<String, DeclarationDescriptor>()
when (descriptor) {
is PackageFragmentDescriptor -> {
collect(descriptor.getMemberScope(), scope)
val module = DescriptorUtils.getContainingModule(descriptor)
module.getSubPackagesOf(descriptor.fqName) { true }
.flatMap { module.getPackage(it).fragments }
.forEach { collect(it, scope) }
}
is ClassDescriptor -> collect(descriptor.defaultType.memberScope, scope)
}
scope
}
private fun collect(scope: MemberScope, target: MutableMap<String, DeclarationDescriptor>) {
for (descriptor in scope.getContributedDescriptors()) {
val fqn = fqnGenerator.generate(descriptor)
if (fqn.shared && isOpaque(fqn.descriptor)) {
target[fqn.names.last()] = fqn.descriptor
}
}
}
private fun collect(descriptor: DeclarationDescriptor, target: MutableMap<String, DeclarationDescriptor>) {
val fqn = fqnGenerator.generate(descriptor)
if (fqn.shared && isOpaque(fqn.descriptor)) {
target[fqn.names.last()] = fqn.descriptor
}
}
private fun isOpaque(descriptor: DeclarationDescriptor) =
!AnnotationsUtils.isNativeObject(descriptor) && !AnnotationsUtils.isLibraryObject(descriptor)
}