feat: add WARNING on usage top-level exportable declarations with non-consumable identifiers.

This commit is contained in:
Artem Kobzar
2022-05-04 11:22:57 +00:00
committed by Space
parent 0ea9c1fbce
commit a2b40acc98
9 changed files with 170 additions and 22 deletions
@@ -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.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
}
}
@@ -110,6 +110,8 @@ public interface ErrorsJs {
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);
DiagnosticFactory1<PsiElement, String> NON_CONSUMABLE_EXPORTED_IDENTIFIER = DiagnosticFactory1.create(WARNING, DEFAULT);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
@@ -5,15 +5,19 @@
package org.jetbrains.kotlin.js.resolve.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.descriptors.*
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.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
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.inline.isInlineWithReified
import org.jetbrains.kotlin.resolve.isInlineClass
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.isDynamic
import org.jetbrains.kotlin.types.typeUtil.*
@@ -61,6 +66,8 @@ object JsExportDeclarationChecker : DeclarationChecker {
reportWrongExportedDeclaration("expect")
}
validateDeclarationOnConsumableName(declaration, descriptor, trace)
when (descriptor) {
is FunctionDescriptor -> {
for (typeParameter in descriptor.typeParameters) {
@@ -201,4 +208,33 @@ object JsExportDeclarationChecker : DeclarationChecker {
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;
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");
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");
@@ -172,7 +172,7 @@ public final class AnnotationsUtils {
@Nullable
public static AnnotationDescriptor getJsNameAnnotation(@NotNull DeclarationDescriptor descriptor) {
return descriptor.getAnnotations().findAnnotation(new FqName(JS_NAME));
return descriptor.getAnnotations().findAnnotation(JS_NAME);
}
@Nullable