feat: add WARNING on usage top-level exportable declarations with non-consumable identifiers.
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
|||||||
|
// !OPT_IN: kotlin.js.ExperimentalJsExport
|
||||||
|
// !RENDER_DIAGNOSTICS_MESSAGES
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
fun <!NON_CONSUMABLE_EXPORTED_IDENTIFIER!>delete<!>() {}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
val <!NON_CONSUMABLE_EXPORTED_IDENTIFIER!>instanceof<!> = 4
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
class <!NON_CONSUMABLE_EXPORTED_IDENTIFIER!>eval<!>
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
@JsName(<!NON_CONSUMABLE_EXPORTED_IDENTIFIER!>"await"<!>)
|
||||||
|
fun foo() {}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
@JsName(<!NON_CONSUMABLE_EXPORTED_IDENTIFIER!>"this"<!>)
|
||||||
|
val bar = 4
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
@JsName(<!NON_CONSUMABLE_EXPORTED_IDENTIFIER!>"super"<!>)
|
||||||
|
class Baz
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
class Test {
|
||||||
|
fun instanceof() {}
|
||||||
|
|
||||||
|
@JsName("eval")
|
||||||
|
fun test() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
object NaN
|
||||||
|
|
||||||
|
@JsExport
|
||||||
|
enum class Nums {
|
||||||
|
Infinity,
|
||||||
|
undefined
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
package foo {
|
||||||
|
@kotlin.js.JsExport @kotlin.js.JsName(name = "this") public val bar: kotlin.Int = 4
|
||||||
|
@kotlin.js.JsExport public val instanceof: kotlin.Int = 4
|
||||||
|
@kotlin.js.JsExport public fun delete(): kotlin.Unit
|
||||||
|
@kotlin.js.JsExport @kotlin.js.JsName(name = "await") public fun foo(): kotlin.Unit
|
||||||
|
|
||||||
|
@kotlin.js.JsExport @kotlin.js.JsName(name = "super") public final class Baz {
|
||||||
|
public constructor Baz()
|
||||||
|
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.JsExport public object NaN {
|
||||||
|
private constructor NaN()
|
||||||
|
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.JsExport public final enum class Nums : kotlin.Enum<foo.Nums> {
|
||||||
|
enum entry Infinity
|
||||||
|
|
||||||
|
enum entry undefined
|
||||||
|
|
||||||
|
private constructor Nums()
|
||||||
|
public final override /*1*/ /*fake_override*/ val name: kotlin.String
|
||||||
|
public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int
|
||||||
|
protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any
|
||||||
|
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: foo.Nums): kotlin.Int
|
||||||
|
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
|
||||||
|
// Static members
|
||||||
|
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): foo.Nums
|
||||||
|
public final /*synthesized*/ fun values(): kotlin.Array<foo.Nums>
|
||||||
|
}
|
||||||
|
|
||||||
|
@kotlin.js.JsExport public final class Test {
|
||||||
|
public constructor Test()
|
||||||
|
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 final fun instanceof(): kotlin.Unit
|
||||||
|
@kotlin.js.JsName(name = "eval") public final fun test(): kotlin.Unit
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
|
||||||
|
@kotlin.js.JsExport public final class eval {
|
||||||
|
public constructor eval()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -401,6 +401,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes
|
|||||||
runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.kt");
|
runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/jsExportOnNestedDeclarations.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("nonConsumableIdentifiers.kt")
|
||||||
|
public void testNonConsumableIdentifiers() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/testsWithJsStdLib/export/nonConsumableIdentifiers.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("secondaryConstructorWithoutJsName.kt")
|
@TestMetadata("secondaryConstructorWithoutJsName.kt")
|
||||||
public void testSecondaryConstructorWithoutJsName() throws Exception {
|
public void testSecondaryConstructorWithoutJsName() throws Exception {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.backend.ast
|
package org.jetbrains.kotlin.js.backend.ast
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.js.common.RESERVED_KEYWORDS
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class JsObjectScope(parent: JsScope, description: String) : JsScope(parent, description)
|
class JsObjectScope(parent: JsScope, description: String) : JsScope(parent, description)
|
||||||
@@ -70,23 +71,7 @@ open class JsDeclarationScope(parent: JsScope, description: String, useParentSco
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val RESERVED_WORDS: Set<String> = setOf(
|
val RESERVED_WORDS: Set<String> = RESERVED_KEYWORDS + setOf(
|
||||||
// keywords
|
|
||||||
"await", "break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else", "finally", "for", "function", "if",
|
|
||||||
"in", "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with",
|
|
||||||
|
|
||||||
// future reserved words
|
|
||||||
"class", "const", "enum", "export", "extends", "import", "super",
|
|
||||||
|
|
||||||
// as future reserved words in strict mode
|
|
||||||
"implements", "interface", "let", "package", "private", "protected", "public", "static", "yield",
|
|
||||||
|
|
||||||
// additional reserved words
|
|
||||||
"null", "true", "false",
|
|
||||||
|
|
||||||
// disallowed as variable names in strict mode
|
|
||||||
"eval", "arguments",
|
|
||||||
|
|
||||||
// global identifiers usually declared in a typical JS interpreter
|
// global identifiers usually declared in a typical JS interpreter
|
||||||
"NaN", "isNaN", "Infinity", "undefined",
|
"NaN", "isNaN", "Infinity", "undefined",
|
||||||
"Error", "Object", "Math", "String", "Number", "Boolean", "Date", "Array", "RegExp", "JSON",
|
"Error", "Object", "Math", "String", "Number", "Boolean", "Date", "Array", "RegExp", "JSON",
|
||||||
|
|||||||
@@ -50,3 +50,21 @@ fun String.isValidES5Identifier(): Boolean {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val RESERVED_KEYWORDS: Set<String> = setOf(
|
||||||
|
// keywords
|
||||||
|
"await", "break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else", "finally", "for", "function", "if",
|
||||||
|
"in", "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with",
|
||||||
|
|
||||||
|
// future reserved words
|
||||||
|
"class", "const", "enum", "export", "extends", "import", "super",
|
||||||
|
|
||||||
|
// as future reserved words in strict mode
|
||||||
|
"implements", "interface", "let", "package", "private", "protected", "public", "static", "yield",
|
||||||
|
|
||||||
|
// additional reserved words
|
||||||
|
"null", "true", "false",
|
||||||
|
|
||||||
|
// disallowed as variable names in strict mode
|
||||||
|
"eval", "arguments",
|
||||||
|
)
|
||||||
+2
@@ -106,6 +106,8 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
|
|||||||
put(ErrorsJs.WRONG_EXPORTED_DECLARATION, "Declaration of such kind ({0}) can''t be exported to JS", STRING)
|
put(ErrorsJs.WRONG_EXPORTED_DECLARATION, "Declaration of such kind ({0}) can''t be exported to JS", STRING)
|
||||||
put(ErrorsJs.NON_EXPORTABLE_TYPE, "Exported declaration uses non-exportable {0} type: {1}", STRING, RENDER_TYPE)
|
put(ErrorsJs.NON_EXPORTABLE_TYPE, "Exported declaration uses non-exportable {0} type: {1}", STRING, RENDER_TYPE)
|
||||||
|
|
||||||
|
put(ErrorsJs.NON_CONSUMABLE_EXPORTED_IDENTIFIER, "Exported declaration contains non-consumable identifier '${0}', that can't be represented inside TS definitions and ESM", STRING)
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ public interface ErrorsJs {
|
|||||||
DiagnosticFactory1<KtExpression, String> WRONG_EXPORTED_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
DiagnosticFactory1<KtExpression, String> WRONG_EXPORTED_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||||
DiagnosticFactory2<PsiElement, String, KotlinType> NON_EXPORTABLE_TYPE = DiagnosticFactory2.create(WARNING, DECLARATION_SIGNATURE_OR_DEFAULT);
|
DiagnosticFactory2<PsiElement, String, KotlinType> NON_EXPORTABLE_TYPE = DiagnosticFactory2.create(WARNING, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||||
|
|
||||||
|
DiagnosticFactory1<PsiElement, String> NON_CONSUMABLE_EXPORTED_IDENTIFIER = DiagnosticFactory1.create(WARNING, DEFAULT);
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
Object _initializer = new Object() {
|
Object _initializer = new Object() {
|
||||||
{
|
{
|
||||||
|
|||||||
+38
-2
@@ -5,15 +5,19 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.resolve.diagnostics
|
package org.jetbrains.kotlin.js.resolve.diagnostics
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.builtins.StandardNames
|
|
||||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind.*
|
import org.jetbrains.kotlin.descriptors.ClassKind.*
|
||||||
import org.jetbrains.kotlin.js.resolve.diagnostics.JsExportDeclarationChecker.isExportable
|
import org.jetbrains.kotlin.js.common.RESERVED_KEYWORDS
|
||||||
|
import org.jetbrains.kotlin.js.naming.NameSuggestion
|
||||||
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
|
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
|
||||||
|
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
|
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||||
@@ -22,6 +26,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
|
|||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isInsideInterface
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isInsideInterface
|
||||||
import org.jetbrains.kotlin.resolve.inline.isInlineWithReified
|
import org.jetbrains.kotlin.resolve.inline.isInlineWithReified
|
||||||
import org.jetbrains.kotlin.resolve.isInlineClass
|
import org.jetbrains.kotlin.resolve.isInlineClass
|
||||||
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.isDynamic
|
import org.jetbrains.kotlin.types.isDynamic
|
||||||
import org.jetbrains.kotlin.types.typeUtil.*
|
import org.jetbrains.kotlin.types.typeUtil.*
|
||||||
@@ -61,6 +66,8 @@ object JsExportDeclarationChecker : DeclarationChecker {
|
|||||||
reportWrongExportedDeclaration("expect")
|
reportWrongExportedDeclaration("expect")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateDeclarationOnConsumableName(declaration, descriptor, trace)
|
||||||
|
|
||||||
when (descriptor) {
|
when (descriptor) {
|
||||||
is FunctionDescriptor -> {
|
is FunctionDescriptor -> {
|
||||||
for (typeParameter in descriptor.typeParameters) {
|
for (typeParameter in descriptor.typeParameters) {
|
||||||
@@ -201,4 +208,33 @@ object JsExportDeclarationChecker : DeclarationChecker {
|
|||||||
|
|
||||||
return descriptor.isEffectivelyExternal() || AnnotationsUtils.isExportedObject(descriptor, bindingContext)
|
return descriptor.isEffectivelyExternal() || AnnotationsUtils.isExportedObject(descriptor, bindingContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun validateDeclarationOnConsumableName(
|
||||||
|
declaration: KtDeclaration,
|
||||||
|
declarationDescriptor: DeclarationDescriptor,
|
||||||
|
trace: BindingTrace
|
||||||
|
) {
|
||||||
|
if (!declarationDescriptor.isTopLevelInPackage() || declarationDescriptor.name.isSpecial) return
|
||||||
|
|
||||||
|
val name = declarationDescriptor.getKotlinOrJsName()
|
||||||
|
|
||||||
|
if (name !in RESERVED_KEYWORDS && NameSuggestion.sanitizeName(name) == name) return
|
||||||
|
|
||||||
|
val reportTarget = declarationDescriptor.getJsNameArgument() ?: declaration.getIdentifier()
|
||||||
|
|
||||||
|
trace.report(ErrorsJs.NON_CONSUMABLE_EXPORTED_IDENTIFIER.on(reportTarget, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DeclarationDescriptor.getKotlinOrJsName(): String {
|
||||||
|
return AnnotationsUtils.getJsName(this) ?: name.identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtDeclaration.getIdentifier(): PsiElement {
|
||||||
|
return (this as KtNamedDeclaration).nameIdentifier!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DeclarationDescriptor.getJsNameArgument(): PsiElement? {
|
||||||
|
val jsNameAnnotation = AnnotationsUtils.getJsNameAnnotation(this) ?: return null
|
||||||
|
return (jsNameAnnotation.source.getPsi() as KtAnnotationEntry).valueArgumentList?.arguments?.first()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import java.util.List;
|
|||||||
import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt.isEffectivelyExternal;
|
import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt.isEffectivelyExternal;
|
||||||
|
|
||||||
public final class AnnotationsUtils {
|
public final class AnnotationsUtils {
|
||||||
private static final String JS_NAME = "kotlin.js.JsName";
|
public static final FqName JS_NAME = new FqName("kotlin.js.JsName");
|
||||||
private static final FqName JS_EXPORT = new FqName("kotlin.js.JsExport");
|
private static final FqName JS_EXPORT = new FqName("kotlin.js.JsExport");
|
||||||
public static final FqName JS_MODULE_ANNOTATION = new FqName("kotlin.js.JsModule");
|
public static final FqName JS_MODULE_ANNOTATION = new FqName("kotlin.js.JsModule");
|
||||||
private static final FqName JS_NON_MODULE_ANNOTATION = new FqName("kotlin.js.JsNonModule");
|
private static final FqName JS_NON_MODULE_ANNOTATION = new FqName("kotlin.js.JsNonModule");
|
||||||
@@ -172,7 +172,7 @@ public final class AnnotationsUtils {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static AnnotationDescriptor getJsNameAnnotation(@NotNull DeclarationDescriptor descriptor) {
|
public static AnnotationDescriptor getJsNameAnnotation(@NotNull DeclarationDescriptor descriptor) {
|
||||||
return descriptor.getAnnotations().findAnnotation(new FqName(JS_NAME));
|
return descriptor.getAnnotations().findAnnotation(JS_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
Reference in New Issue
Block a user