KT-2752: add checkers for inconsistent usage of JsName
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class A {
|
||||||
|
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var x: Int
|
||||||
|
@JsName("get_x") get() = 23
|
||||||
|
set(value) {}<!>
|
||||||
|
|
||||||
|
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var y: Int
|
||||||
|
@JsName("get_y") get() = 23
|
||||||
|
set(value) {}<!>
|
||||||
|
|
||||||
|
var z: Int
|
||||||
|
@JsName("get_z") get() = 23
|
||||||
|
@JsName("set_z") set(value) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var xx: Int
|
||||||
|
@JsName("get_xx") get() = 23
|
||||||
|
set(value) {}<!>
|
||||||
|
|
||||||
|
<!JS_NAME_IS_NOT_ON_ALL_ACCESSORS!>var A.ext: Int
|
||||||
|
@JsName("get_ext") get() = 23
|
||||||
|
set(value) {}<!>
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
package foo {
|
||||||
|
public var xx: kotlin.Int
|
||||||
|
public var foo.A.ext: kotlin.Int
|
||||||
|
|
||||||
|
public final class A {
|
||||||
|
public constructor A()
|
||||||
|
public final var x: kotlin.Int
|
||||||
|
public final var y: kotlin.Int
|
||||||
|
public final var z: kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class A {
|
||||||
|
@JsName("x_") val x: Int
|
||||||
|
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_x")<!> get() = 23
|
||||||
|
|
||||||
|
@JsName("y_") val y = 0
|
||||||
|
|
||||||
|
@JsName("m_") var m: Int
|
||||||
|
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_m")<!> get() = 23
|
||||||
|
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("set_m")<!> set(value) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsName("xx_") val xx: Int
|
||||||
|
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_xx")<!> get() = 23
|
||||||
|
|
||||||
|
@JsName("yy_") val yy = 0
|
||||||
|
|
||||||
|
@JsName("mm_") var mm: Int
|
||||||
|
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_mm")<!> get() = 23
|
||||||
|
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("set_mm")<!> set(value) {}
|
||||||
|
|
||||||
|
@JsName("ext_") val A.ext: Int
|
||||||
|
<!JS_NAME_ON_ACCESSOR_AND_PROPERTY!>@JsName("get_ext")<!> get() = 23
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
package foo {
|
||||||
|
@kotlin.js.JsName(name = "mm_") public var mm: kotlin.Int
|
||||||
|
@kotlin.js.JsName(name = "xx_") public val xx: kotlin.Int
|
||||||
|
@kotlin.js.JsName(name = "yy_") public val yy: kotlin.Int = 0
|
||||||
|
@kotlin.js.JsName(name = "ext_") public val foo.A.ext: kotlin.Int
|
||||||
|
|
||||||
|
public final class A {
|
||||||
|
public constructor A()
|
||||||
|
@kotlin.js.JsName(name = "m_") public final var m: kotlin.Int
|
||||||
|
@kotlin.js.JsName(name = "x_") public final val x: kotlin.Int
|
||||||
|
@kotlin.js.JsName(name = "y_") public final val y: kotlin.Int = 0
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class A <!JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED!>@JsName("B")<!> constructor()
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
package foo {
|
||||||
|
|
||||||
|
public final class A {
|
||||||
|
@kotlin.js.JsName(name = "B") public constructor A()
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -325,6 +325,24 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("jsNameMissingOnAccessors.kt")
|
||||||
|
public void testJsNameMissingOnAccessors() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameMissingOnAccessors.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("jsNameOnPropertyAndAccessor.kt")
|
||||||
|
public void testJsNameOnPropertyAndAccessor() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/jsNameOnPropertyAndAccessor.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("jsNamePrihibitedOnPrimaryConstructor.kt")
|
||||||
|
public void testJsNamePrihibitedOnPrimaryConstructor() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/jsNamePrihibitedOnPrimaryConstructor.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("methodAndMethod.kt")
|
@TestMetadata("methodAndMethod.kt")
|
||||||
public void testMethodAndMethod() throws Exception {
|
public void testMethodAndMethod() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/methodAndMethod.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/methodAndMethod.kt");
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ package org.jetbrains.kotlin.js.resolve
|
|||||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||||
import org.jetbrains.kotlin.container.useImpl
|
import org.jetbrains.kotlin.container.useImpl
|
||||||
import org.jetbrains.kotlin.container.useInstance
|
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.JsCallChecker
|
||||||
import org.jetbrains.kotlin.js.resolve.diagnostics.JsNameChecker
|
import org.jetbrains.kotlin.js.resolve.diagnostics.JsNameChecker
|
||||||
|
import org.jetbrains.kotlin.js.resolve.diagnostics.JsNameClashChecker
|
||||||
import org.jetbrains.kotlin.js.resolve.diagnostics.NativeInnerClassChecker
|
import org.jetbrains.kotlin.js.resolve.diagnostics.NativeInnerClassChecker
|
||||||
import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap
|
import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap
|
||||||
import org.jetbrains.kotlin.resolve.IdentifierChecker
|
import org.jetbrains.kotlin.resolve.IdentifierChecker
|
||||||
@@ -34,7 +34,7 @@ import org.jetbrains.kotlin.types.DynamicTypesAllowed
|
|||||||
object JsPlatformConfigurator : PlatformConfigurator(
|
object JsPlatformConfigurator : PlatformConfigurator(
|
||||||
DynamicTypesAllowed(),
|
DynamicTypesAllowed(),
|
||||||
additionalDeclarationCheckers = listOf(NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(),
|
additionalDeclarationCheckers = listOf(NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker(),
|
||||||
NativeInnerClassChecker()),
|
NativeInnerClassChecker(), JsNameChecker),
|
||||||
additionalCallCheckers = listOf(),
|
additionalCallCheckers = listOf(),
|
||||||
additionalTypeCheckers = listOf(),
|
additionalTypeCheckers = listOf(),
|
||||||
additionalClassifierUsageCheckers = listOf(),
|
additionalClassifierUsageCheckers = listOf(),
|
||||||
@@ -48,6 +48,6 @@ object JsPlatformConfigurator : PlatformConfigurator(
|
|||||||
container.useInstance(SyntheticScopes.Empty)
|
container.useInstance(SyntheticScopes.Empty)
|
||||||
container.useInstance(SyntheticConstructorsProvider.Empty)
|
container.useInstance(SyntheticConstructorsProvider.Empty)
|
||||||
container.useInstance(JsTypeSpecificityComparator)
|
container.useInstance(JsTypeSpecificityComparator)
|
||||||
container.useInstance(JsNameChecker())
|
container.useInstance(JsNameClashChecker())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -39,6 +39,9 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
|
|||||||
put(ErrorsJs.NATIVE_INNER_CLASS_PROHIBITED, "Native inner classes are prohibited")
|
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}",
|
put(ErrorsJs.JS_NAME_CLASH, "JavaScript name ({0}) generated for this declaration clashes with another declaration: {1}",
|
||||||
Renderers.STRING, Renderers.COMPACT)
|
Renderers.STRING, Renderers.COMPACT)
|
||||||
|
put(ErrorsJs.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, "@JsName annotation is prohibited for primary constructors")
|
||||||
|
put(ErrorsJs.JS_NAME_ON_ACCESSOR_AND_PROPERTY, "@JsName can be either on a property or its accessors, not both of them")
|
||||||
|
put(ErrorsJs.JS_NAME_IS_NOT_ON_ALL_ACCESSORS, "@JsName should be on all of the property accessors")
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.resolve.diagnostics;
|
package org.jetbrains.kotlin.js.resolve.diagnostics;
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement;
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
|
||||||
@@ -45,6 +46,9 @@ public interface ErrorsJs {
|
|||||||
DiagnosticFactory0<KtExpression> JSCODE_NO_JAVASCRIPT_PRODUCED = DiagnosticFactory0.create(ERROR, DEFAULT);
|
DiagnosticFactory0<KtExpression> JSCODE_NO_JAVASCRIPT_PRODUCED = DiagnosticFactory0.create(ERROR, DEFAULT);
|
||||||
DiagnosticFactory0<KtExpression> NATIVE_INNER_CLASS_PROHIBITED = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<KtExpression> NATIVE_INNER_CLASS_PROHIBITED = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory2<KtElement, String, DeclarationDescriptor> JS_NAME_CLASH = DiagnosticFactory2.create(ERROR);
|
DiagnosticFactory2<KtElement, String, DeclarationDescriptor> JS_NAME_CLASH = DiagnosticFactory2.create(ERROR);
|
||||||
|
DiagnosticFactory0<KtElement> JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED = DiagnosticFactory0.create(ERROR);
|
||||||
|
DiagnosticFactory0<KtElement> JS_NAME_ON_ACCESSOR_AND_PROPERTY = DiagnosticFactory0.create(ERROR);
|
||||||
|
DiagnosticFactory0<PsiElement> JS_NAME_IS_NOT_ON_ALL_ACCESSORS = DiagnosticFactory0.create(ERROR);
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
Object _initializer = new Object() {
|
Object _initializer = new Object() {
|
||||||
|
|||||||
@@ -16,98 +16,39 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.resolve.diagnostics
|
package org.jetbrains.kotlin.js.resolve.diagnostics
|
||||||
|
|
||||||
import com.intellij.psi.PsiElement
|
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
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.js.translate.utils.AnnotationsUtils
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DeclarationChecker
|
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
|
||||||
import org.jetbrains.kotlin.resolve.SimpleDeclarationChecker
|
import org.jetbrains.kotlin.resolve.SimpleDeclarationChecker
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
|
||||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
|
||||||
|
|
||||||
class JsNameChecker : SimpleDeclarationChecker {
|
object JsNameChecker : SimpleDeclarationChecker {
|
||||||
private val fqnGenerator = FQNGenerator()
|
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, diagnosticHolder: DiagnosticSink,
|
||||||
private val scopes = mutableMapOf<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
|
bindingContext: BindingContext) {
|
||||||
private val clashedDescriptors = mutableSetOf<DeclarationDescriptor>()
|
if (descriptor is PropertyDescriptor) {
|
||||||
|
val namedAccessorCount = descriptor.accessors.count { AnnotationsUtils.getJsName(it) != null }
|
||||||
override fun check(
|
if (namedAccessorCount > 0 && namedAccessorCount < descriptor.accessors.size) {
|
||||||
declaration: KtDeclaration,
|
diagnosticHolder.report(ErrorsJs.JS_NAME_IS_NOT_ON_ALL_ACCESSORS.on(declaration))
|
||||||
descriptor: DeclarationDescriptor,
|
}
|
||||||
diagnosticHolder: DiagnosticSink,
|
|
||||||
bindingContext: BindingContext
|
|
||||||
) {
|
|
||||||
if (declaration !is KtProperty || !descriptor.isExtension) {
|
|
||||||
checkDescriptor(descriptor, declaration, diagnosticHolder)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkDescriptor(descriptor: DeclarationDescriptor, declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) {
|
val jsNamePsi = AnnotationsUtils.getJsNameAnnotationPsi(bindingContext, declaration) ?: return
|
||||||
val fqn = fqnGenerator.generate(descriptor)
|
|
||||||
if (fqn.shared && fqn.scope is ClassOrPackageFragmentDescriptor && isOpaque(fqn.descriptor)) {
|
when (descriptor) {
|
||||||
val scope = getScope(fqn.scope)
|
is ConstructorDescriptor -> {
|
||||||
val name = fqn.names.last()
|
if (descriptor.isPrimary) {
|
||||||
val existing = scope[name]
|
diagnosticHolder.report(ErrorsJs.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED.on(jsNamePsi))
|
||||||
if (existing != null && existing != fqn.descriptor) {
|
}
|
||||||
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(declaration, name, existing))
|
}
|
||||||
val existingDeclaration = findPsi(existing) ?: declaration
|
is PropertyAccessorDescriptor -> {
|
||||||
if (clashedDescriptors.add(existing) && existingDeclaration is KtDeclaration && existingDeclaration != declaration) {
|
if (!descriptor.isDefault && AnnotationsUtils.getJsName(descriptor.correspondingProperty) != null) {
|
||||||
diagnosticHolder.report(ErrorsJs.JS_NAME_CLASH.on(existingDeclaration, name, descriptor))
|
diagnosticHolder.report(ErrorsJs.JS_NAME_ON_ACCESSOR_AND_PROPERTY.on(jsNamePsi))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()) {
|
|
||||||
collect(descriptor, target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collect(descriptor: DeclarationDescriptor, target: MutableMap<String, DeclarationDescriptor>) {
|
|
||||||
if (descriptor is PropertyDescriptor && descriptor.isExtension) {
|
|
||||||
descriptor.accessors.forEach { collect(it, target) }
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* 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.psi.KtProperty
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.DeclarationChecker
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
import org.jetbrains.kotlin.resolve.SimpleDeclarationChecker
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||||
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
|
|
||||||
|
class JsNameClashChecker : SimpleDeclarationChecker {
|
||||||
|
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
|
||||||
|
) {
|
||||||
|
if (declaration !is KtProperty || !descriptor.isExtension) {
|
||||||
|
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()) {
|
||||||
|
collect(descriptor, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collect(descriptor: DeclarationDescriptor, target: MutableMap<String, DeclarationDescriptor>) {
|
||||||
|
if (descriptor is PropertyDescriptor && descriptor.isExtension) {
|
||||||
|
descriptor.accessors.forEach { collect(it, target) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -22,10 +22,18 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor;
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor;
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
|
||||||
import org.jetbrains.kotlin.js.PredefinedAnnotation;
|
import org.jetbrains.kotlin.js.PredefinedAnnotation;
|
||||||
import org.jetbrains.kotlin.name.FqName;
|
import org.jetbrains.kotlin.name.FqName;
|
||||||
|
import org.jetbrains.kotlin.name.FqNameUnsafe;
|
||||||
|
import org.jetbrains.kotlin.psi.KtAnnotated;
|
||||||
|
import org.jetbrains.kotlin.psi.KtAnnotation;
|
||||||
|
import org.jetbrains.kotlin.psi.KtAnnotationEntry;
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContextUtils;
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||||
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
|
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
|
||||||
|
|
||||||
@@ -143,6 +151,34 @@ public final class AnnotationsUtils {
|
|||||||
return (String) result;
|
return (String) result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static KtAnnotationEntry getJsNameAnnotationPsi(@NotNull BindingContext context, @NotNull KtAnnotated annotated) {
|
||||||
|
return findAnnotationPsi(context, annotated, new FqName(JS_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static KtAnnotationEntry findAnnotationPsi(
|
||||||
|
@NotNull BindingContext context, @NotNull KtAnnotated annotated,
|
||||||
|
@NotNull FqName nameToFind
|
||||||
|
) {
|
||||||
|
FqNameUnsafe nameToFindUnsafe = nameToFind.toUnsafe();
|
||||||
|
|
||||||
|
for (KtAnnotationEntry entry : annotated.getAnnotationEntries()) {
|
||||||
|
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, entry);
|
||||||
|
assert annotationDescriptor != null : "Annotation descriptor expected for annotation entry: " +
|
||||||
|
PsiUtilsKt.getTextWithLocation(entry);
|
||||||
|
ClassifierDescriptor typeDescriptor = annotationDescriptor.getType().getConstructor().getDeclarationDescriptor();
|
||||||
|
assert typeDescriptor instanceof ClassDescriptor : "Annotation type should be ClassDescriptor: " +
|
||||||
|
PsiUtilsKt.getTextWithLocation(entry);
|
||||||
|
FqNameUnsafe entryName = DescriptorUtils.getFqName(typeDescriptor);
|
||||||
|
if (entryName.equals(nameToFindUnsafe)) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isPredefinedObject(@NotNull DeclarationDescriptor descriptor) {
|
public static boolean isPredefinedObject(@NotNull DeclarationDescriptor descriptor) {
|
||||||
for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) {
|
for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) {
|
||||||
if (hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
|
if (hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
|
||||||
|
|||||||
@@ -49,5 +49,5 @@ public annotation class enumerable()
|
|||||||
public annotation class marker
|
public annotation class marker
|
||||||
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
@Target(CLASS, FUNCTION, PROPERTY, CONSTRUCTOR)
|
@Target(CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER)
|
||||||
annotation class JsName(val name: String)
|
annotation class JsName(val name: String)
|
||||||
@@ -4920,6 +4920,18 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/multiFile/functionsVisibleFromOtherFile.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/multiFile/functionsVisibleFromOtherFile.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("packageAndMangledMethodDoNotClash.kt")
|
||||||
|
public void testPackageAndMangledMethodDoNotClash() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/multiFile/packageAndMangledMethodDoNotClash.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("packageAndPrivateDeclarationDoNotClash.kt")
|
||||||
|
public void testPackageAndPrivateDeclarationDoNotClash() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/multiFile/packageAndPrivateDeclarationDoNotClash.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("js/js.translator/testData/box/multiModule")
|
@TestMetadata("js/js.translator/testData/box/multiModule")
|
||||||
@@ -5158,6 +5170,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/nameClashes"), Pattern.compile("^([^_](.+))\\.kt$"), true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/nameClashes"), Pattern.compile("^([^_](.+))\\.kt$"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("classAndCompanionObjectMembers.kt")
|
||||||
|
public void testClassAndCompanionObjectMembers() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/classAndCompanionObjectMembers.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("differenceInCapitalization.kt")
|
@TestMetadata("differenceInCapitalization.kt")
|
||||||
public void testDifferenceInCapitalization() throws Exception {
|
public void testDifferenceInCapitalization() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/differenceInCapitalization.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/differenceInCapitalization.kt");
|
||||||
@@ -5170,6 +5188,30 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("extensionPropertiesWithDifferentReceivers.kt")
|
||||||
|
public void testExtensionPropertiesWithDifferentReceivers() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/extensionPropertiesWithDifferentReceivers.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("extensionPropertyAndMethod.kt")
|
||||||
|
public void testExtensionPropertyAndMethod() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/extensionPropertyAndMethod.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("jsNameAndPrivate.kt")
|
||||||
|
public void testJsNameAndPrivate() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/jsNameAndPrivate.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("methodAndPrivateProperty.kt")
|
||||||
|
public void testMethodAndPrivateProperty() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/methodAndPrivateProperty.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("methodOverload.kt")
|
@TestMetadata("methodOverload.kt")
|
||||||
public void testMethodOverload() throws Exception {
|
public void testMethodOverload() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/methodOverload.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/methodOverload.kt");
|
||||||
@@ -5187,6 +5229,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/overloadExtension.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/overloadExtension.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("propertyAndNativeMethod.kt")
|
||||||
|
public void testPropertyAndNativeMethod() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/propertyAndNativeMethod.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("js/js.translator/testData/box/native")
|
@TestMetadata("js/js.translator/testData/box/native")
|
||||||
@@ -5227,6 +5275,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("jsName.kt")
|
||||||
|
public void testJsName() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/native/jsName.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt1519.kt")
|
@TestMetadata("kt1519.kt")
|
||||||
public void testKt1519() throws Exception {
|
public void testKt1519() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/native/kt1519.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/native/kt1519.kt");
|
||||||
|
|||||||
+6
@@ -1,3 +1,4 @@
|
|||||||
|
// FILE: foo.kt
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
fun bar(x: Int) = x
|
fun bar(x: Int) = x
|
||||||
@@ -7,3 +8,8 @@ fun box(): Boolean {
|
|||||||
assertEquals(42, foo.bar.x)
|
assertEquals(42, foo.bar.x)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FILE: foobar.kt
|
||||||
|
package foo.bar
|
||||||
|
|
||||||
|
val x = 42
|
||||||
+6
@@ -1,3 +1,4 @@
|
|||||||
|
// FILE: foo.kt
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
private fun bar() = 23
|
private fun bar() = 23
|
||||||
@@ -11,3 +12,8 @@ fun box(): Boolean {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FILE: foobar.kt
|
||||||
|
package foo.bar
|
||||||
|
|
||||||
|
val x = 32
|
||||||
Vendored
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
package foo.bar
|
|
||||||
|
|
||||||
val x = 42
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
package foo.bar
|
|
||||||
|
|
||||||
val x = 32
|
|
||||||
Reference in New Issue
Block a user