JS: prohibit declaration names that clash with JS builtin functions
See KT-18095, KT-17475, KT-18105, KT-5259
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
class C {
|
||||
class <!JS_BUILTIN_NAME_CLASH!>prototype<!>
|
||||
|
||||
class <!JS_BUILTIN_NAME_CLASH!>length<!>
|
||||
|
||||
class <!JS_BUILTIN_NAME_CLASH!>`$metadata$`<!>
|
||||
|
||||
<!JS_BUILTIN_NAME_CLASH!>fun constructor()<!> {}
|
||||
}
|
||||
|
||||
class D {
|
||||
private class <!JS_BUILTIN_NAME_CLASH!>prototype<!>
|
||||
|
||||
private class <!JS_BUILTIN_NAME_CLASH!>length<!>
|
||||
|
||||
private class <!JS_BUILTIN_NAME_CLASH!>`$metadata$`<!>
|
||||
|
||||
private fun constructor() {}
|
||||
}
|
||||
|
||||
class E {
|
||||
@JsName("prototype")
|
||||
class <!JS_BUILTIN_NAME_CLASH!>D<!>
|
||||
|
||||
<!JS_BUILTIN_NAME_CLASH!>@JsName("constructor")
|
||||
fun f()<!> {}
|
||||
}
|
||||
|
||||
class F {
|
||||
@JsName("A")
|
||||
class prototype
|
||||
|
||||
@JsName("B")
|
||||
class length
|
||||
|
||||
@JsName("f")
|
||||
fun constructor() {}
|
||||
}
|
||||
|
||||
class prototype
|
||||
|
||||
class length
|
||||
|
||||
fun constructor() {
|
||||
}
|
||||
|
||||
fun f() {
|
||||
class prototype
|
||||
class length
|
||||
|
||||
fun constructor() {}
|
||||
}
|
||||
|
||||
external interface Object {
|
||||
val constructor: Any?
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package
|
||||
|
||||
public fun constructor(): kotlin.Unit
|
||||
public fun f(): kotlin.Unit
|
||||
|
||||
public final class C {
|
||||
public constructor C()
|
||||
public final fun constructor(): kotlin.Unit
|
||||
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
|
||||
|
||||
public final class `$metadata$` {
|
||||
public constructor `$metadata$`()
|
||||
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
|
||||
}
|
||||
|
||||
public final class length {
|
||||
public constructor length()
|
||||
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
|
||||
}
|
||||
|
||||
public final class prototype {
|
||||
public constructor prototype()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public final class D {
|
||||
public constructor D()
|
||||
private final fun constructor(): kotlin.Unit
|
||||
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
|
||||
|
||||
private final class `$metadata$` {
|
||||
public constructor `$metadata$`()
|
||||
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
|
||||
}
|
||||
|
||||
private final class length {
|
||||
public constructor length()
|
||||
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
|
||||
}
|
||||
|
||||
private final class prototype {
|
||||
public constructor prototype()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public final class E {
|
||||
public constructor E()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
@kotlin.js.JsName(name = "constructor") public final fun f(): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
@kotlin.js.JsName(name = "prototype") public final class D {
|
||||
public constructor D()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public final class F {
|
||||
public constructor F()
|
||||
@kotlin.js.JsName(name = "f") public final fun constructor(): kotlin.Unit
|
||||
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
|
||||
|
||||
@kotlin.js.JsName(name = "B") public final class length {
|
||||
public constructor length()
|
||||
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
|
||||
}
|
||||
|
||||
@kotlin.js.JsName(name = "A") public final class prototype {
|
||||
public constructor prototype()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public external interface Object {
|
||||
public abstract val constructor: kotlin.Any?
|
||||
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
|
||||
}
|
||||
|
||||
public final class length {
|
||||
public constructor length()
|
||||
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
|
||||
}
|
||||
|
||||
public final class prototype {
|
||||
public constructor prototype()
|
||||
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
|
||||
}
|
||||
@@ -485,6 +485,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("builtinClash.kt")
|
||||
public void testBuiltinClash() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("classAndFunction.kt")
|
||||
public void testClassAndFunction() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt");
|
||||
|
||||
@@ -19,6 +19,7 @@ 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.NameSuggestion
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.*
|
||||
import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap
|
||||
import org.jetbrains.kotlin.resolve.OverloadFilter
|
||||
@@ -56,11 +57,13 @@ object JsPlatformConfigurator : PlatformConfigurator(
|
||||
overridesBackwardCompatibilityHelper = OverridesBackwardCompatibilityHelper.DEFAULT
|
||||
) {
|
||||
override fun configureModuleComponents(container: StorageComponentContainer) {
|
||||
container.useInstance(NameSuggestion())
|
||||
container.useImpl<JsCallChecker>()
|
||||
container.useInstance(SyntheticScopes.Empty)
|
||||
container.useInstance(JsTypeSpecificityComparator)
|
||||
container.useInstance(JsNameClashChecker())
|
||||
container.useInstance(JsNameCharsChecker())
|
||||
container.useImpl<JsNameClashChecker>()
|
||||
container.useImpl<JsNameCharsChecker>()
|
||||
container.useImpl<JsBuiltinNameClashChecker>()
|
||||
container.useInstance(JsModuleClassLiteralChecker)
|
||||
container.useImpl<JsReflectionAPICallChecker>()
|
||||
container.useImpl<JsNativeRttiChecker>()
|
||||
|
||||
+2
@@ -44,6 +44,8 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
|
||||
Renderers.STRING, Renderers.COMPACT)
|
||||
put(ErrorsJs.JS_FAKE_NAME_CLASH, "JavaScript name {0} is generated for different inherited members: {1} and {2}",
|
||||
Renderers.STRING, Renderers.COMPACT, Renderers.COMPACT)
|
||||
put(ErrorsJs.JS_BUILTIN_NAME_CLASH, "JavaScript name generated for this declaration clashes with built-in declaration {1}",
|
||||
Renderers.STRING)
|
||||
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")
|
||||
|
||||
@@ -48,6 +48,8 @@ public interface ErrorsJs {
|
||||
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
DiagnosticFactory3<KtElement, String, DeclarationDescriptor, DeclarationDescriptor> JS_FAKE_NAME_CLASH =
|
||||
DiagnosticFactory3.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
DiagnosticFactory1<KtElement, String> JS_BUILTIN_NAME_CLASH = DiagnosticFactory1.create(
|
||||
ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
DiagnosticFactory0<PsiElement> JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> JS_NAME_ON_ACCESSOR_AND_PROPERTY = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> JS_NAME_IS_NOT_ON_ALL_ACCESSORS = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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 org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||
import org.jetbrains.kotlin.js.naming.NameSuggestion
|
||||
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.checkers.SimpleDeclarationChecker
|
||||
|
||||
class JsBuiltinNameClashChecker(private val nameSuggestion: NameSuggestion) : SimpleDeclarationChecker {
|
||||
override fun check(
|
||||
declaration: KtDeclaration, descriptor: DeclarationDescriptor,
|
||||
diagnosticHolder: DiagnosticSink, bindingContext: BindingContext
|
||||
) {
|
||||
if (AnnotationsUtils.isNativeObject(descriptor)) return
|
||||
if (descriptor.containingDeclaration !is ClassDescriptor) return
|
||||
|
||||
val suggestedName = nameSuggestion.suggest(descriptor)!!
|
||||
if (!suggestedName.stable) return
|
||||
val simpleName = suggestedName.names.single()
|
||||
|
||||
if (descriptor is ClassDescriptor) {
|
||||
if (simpleName in PROHIBITED_STATIC_NAMES) {
|
||||
diagnosticHolder.report(ErrorsJs.JS_BUILTIN_NAME_CLASH.on(declaration, "Function.$simpleName"))
|
||||
}
|
||||
}
|
||||
else if (descriptor is CallableMemberDescriptor) {
|
||||
if (simpleName in PROHIBITED_MEMBER_NAMES) {
|
||||
diagnosticHolder.report(ErrorsJs.JS_BUILTIN_NAME_CLASH.on(declaration, "Object.prototype.$simpleName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val PROHIBITED_STATIC_NAMES = setOf("prototype", "length", "\$metadata\$")
|
||||
|
||||
@JvmField
|
||||
val PROHIBITED_MEMBER_NAMES = setOf("constructor")
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -41,9 +41,7 @@ import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
|
||||
|
||||
class JsNameCharsChecker() : SimpleDeclarationChecker {
|
||||
val suggestion = NameSuggestion()
|
||||
|
||||
class JsNameCharsChecker(private val suggestion: NameSuggestion) : SimpleDeclarationChecker {
|
||||
override fun check(
|
||||
declaration: KtDeclaration, descriptor: DeclarationDescriptor,
|
||||
diagnosticHolder: DiagnosticSink, bindingContext: BindingContext
|
||||
|
||||
+1
-2
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
|
||||
class JsNameClashChecker : SimpleDeclarationChecker {
|
||||
class JsNameClashChecker(private val nameSuggestion: NameSuggestion) : SimpleDeclarationChecker {
|
||||
companion object {
|
||||
private val COMMON_DIAGNOSTICS = setOf(
|
||||
Errors.REDECLARATION,
|
||||
@@ -38,7 +38,6 @@ class JsNameClashChecker : SimpleDeclarationChecker {
|
||||
Errors.PACKAGE_OR_CLASSIFIER_REDECLARATION)
|
||||
}
|
||||
|
||||
private val nameSuggestion = NameSuggestion()
|
||||
private val scopes = mutableMapOf<DeclarationDescriptor, MutableMap<String, DeclarationDescriptor>>()
|
||||
private val clashedFakeOverrides = mutableMapOf<DeclarationDescriptor, Pair<DeclarationDescriptor, DeclarationDescriptor>>()
|
||||
private val clashedDescriptors = mutableSetOf<Pair<DeclarationDescriptor, String>>()
|
||||
|
||||
@@ -6331,6 +6331,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/topLevelFunctionAndParameter.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("withBuiltin.kt")
|
||||
public void testWithBuiltin() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/withBuiltin.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("js/js.translator/testData/box/native")
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.context;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.containers.hash.LinkedHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -35,6 +36,7 @@ import org.jetbrains.kotlin.js.config.JsConfig;
|
||||
import org.jetbrains.kotlin.js.naming.NameSuggestion;
|
||||
import org.jetbrains.kotlin.js.naming.NameSuggestionKt;
|
||||
import org.jetbrains.kotlin.js.naming.SuggestedName;
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.JsBuiltinNameClashChecker;
|
||||
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
|
||||
import org.jetbrains.kotlin.js.translate.context.generator.Generator;
|
||||
import org.jetbrains.kotlin.js.translate.context.generator.Rule;
|
||||
@@ -146,6 +148,11 @@ public final class StaticContext {
|
||||
|
||||
private final boolean isStdlib;
|
||||
|
||||
private static final Set<String> BUILTIN_JS_PROPERTIES = Sets.union(
|
||||
JsBuiltinNameClashChecker.PROHIBITED_MEMBER_NAMES,
|
||||
JsBuiltinNameClashChecker.PROHIBITED_STATIC_NAMES
|
||||
);
|
||||
|
||||
public StaticContext(
|
||||
@NotNull BindingTrace bindingTrace,
|
||||
@NotNull JsConfig config,
|
||||
@@ -615,7 +622,6 @@ public final class StaticContext {
|
||||
}
|
||||
|
||||
private final class ScopeGenerator extends Generator<JsScope> {
|
||||
|
||||
public ScopeGenerator() {
|
||||
Rule<JsScope> generateNewScopesForClassesWithNoAncestors = descriptor -> {
|
||||
if (!(descriptor instanceof ClassDescriptor)) {
|
||||
@@ -623,6 +629,9 @@ public final class StaticContext {
|
||||
}
|
||||
if (getSuperclass((ClassDescriptor) descriptor) == null) {
|
||||
JsFunction function = new JsFunction(new JsRootScope(program), new JsBlock(), descriptor.toString());
|
||||
for (String builtinName : BUILTIN_JS_PROPERTIES) {
|
||||
function.getScope().declareName(builtinName);
|
||||
}
|
||||
scopeToFunction.put(function.getScope(), function);
|
||||
return function.getScope();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1135
|
||||
open class C {
|
||||
private fun constructor() = "C.constructor"
|
||||
|
||||
fun f(): String = constructor()
|
||||
}
|
||||
|
||||
class D : C()
|
||||
|
||||
fun box(): String {
|
||||
val d = D()
|
||||
|
||||
val x = d.f()
|
||||
if (x != "C.constructor") return "fail1: $x"
|
||||
|
||||
if (x.asDynamic().constructor === D::class.js) return "fail2"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user