Validate JvmPackageName annotation value and placement
- do not allow it to be used together with JvmMultifileClass (otherwise implementation becomes complex) - do not allow to declare classes in a JvmPackageName-annotated file (similarly, the implementation of this would be much harder in the compiler, and there would need to be special support in the IDE) - check that the value is a valid FQ name - do not allow root package just in case
This commit is contained in:
@@ -33,7 +33,7 @@ object JvmFileClassUtil {
|
||||
val JVM_MULTIFILE_CLASS: FqName = FqName("kotlin.jvm.JvmMultifileClass")
|
||||
val JVM_MULTIFILE_CLASS_SHORT = JVM_MULTIFILE_CLASS.shortName().asString()
|
||||
|
||||
private val JVM_PACKAGE_NAME: FqName = FqName("kotlin.jvm.JvmPackageName")
|
||||
val JVM_PACKAGE_NAME: FqName = FqName("kotlin.jvm.JvmPackageName")
|
||||
private val JVM_PACKAGE_NAME_SHORT = JVM_PACKAGE_NAME.shortName().asString()
|
||||
|
||||
private const val MULTIFILE_PART_NAME_DELIMITER = "__"
|
||||
|
||||
+37
-12
@@ -22,15 +22,13 @@ import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.name.isValidJavaFqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
|
||||
import org.jetbrains.kotlin.resolve.AnnotationChecker
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getAnnotationRetention
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isRepeatableAnnotation
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
|
||||
|
||||
object RepeatableAnnotationChecker: AdditionalAnnotationChecker {
|
||||
@@ -60,7 +58,7 @@ object RepeatableAnnotationChecker: AdditionalAnnotationChecker {
|
||||
|
||||
object FileClassAnnotationsChecker: AdditionalAnnotationChecker {
|
||||
// JvmName & JvmMultifileClass annotations are applicable to multi-file class parts regardless of their retention.
|
||||
private val ALWAYS_APPLICABLE = hashSetOf(JvmFileClassUtil.JVM_NAME, JvmFileClassUtil.JVM_MULTIFILE_CLASS)
|
||||
private val alwaysApplicable = hashSetOf(JvmFileClassUtil.JVM_NAME, JvmFileClassUtil.JVM_MULTIFILE_CLASS)
|
||||
|
||||
override fun checkEntries(entries: List<KtAnnotationEntry>, actualTargets: List<KotlinTarget>, trace: BindingTrace) {
|
||||
val fileAnnotationsToCheck = arrayListOf<Pair<KtAnnotationEntry, ClassDescriptor>>()
|
||||
@@ -74,13 +72,40 @@ object FileClassAnnotationsChecker: AdditionalAnnotationChecker {
|
||||
fileAnnotationsToCheck.add(Pair(entry, classDescriptor))
|
||||
}
|
||||
|
||||
if (!fileAnnotationsToCheck.any { it.second.classId?.asSingleFqName() == JvmFileClassUtil.JVM_MULTIFILE_CLASS }) return
|
||||
val isMultifileClass = fileAnnotationsToCheck.any { it.second.fqNameSafe == JvmFileClassUtil.JVM_MULTIFILE_CLASS }
|
||||
|
||||
for ((entry, classDescriptor) in fileAnnotationsToCheck) {
|
||||
val classFqName = classDescriptor.classId!!.asSingleFqName()
|
||||
if (classFqName in ALWAYS_APPLICABLE) continue
|
||||
if (classDescriptor.getAnnotationRetention() != KotlinRetention.SOURCE) {
|
||||
trace.report(ErrorsJvm.ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES.on(entry, classFqName))
|
||||
if (isMultifileClass) {
|
||||
for ((entry, classDescriptor) in fileAnnotationsToCheck) {
|
||||
val classFqName = classDescriptor.fqNameSafe
|
||||
if (classFqName in alwaysApplicable) continue
|
||||
if (classDescriptor.getAnnotationRetention() != KotlinRetention.SOURCE) {
|
||||
trace.report(ErrorsJvm.ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES.on(entry, classFqName))
|
||||
}
|
||||
if (classFqName == JvmFileClassUtil.JVM_PACKAGE_NAME) {
|
||||
trace.report(ErrorsJvm.JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_MULTIFILE_CLASSES.on(entry))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for ((entry, classDescriptor) in fileAnnotationsToCheck) {
|
||||
if (classDescriptor.fqNameSafe != JvmFileClassUtil.JVM_PACKAGE_NAME) continue
|
||||
|
||||
val argumentExpression = entry.valueArguments.firstOrNull()?.getArgumentExpression() ?: continue
|
||||
val stringTemplateEntries = (argumentExpression as? KtStringTemplateExpression)?.entries ?: continue
|
||||
if (stringTemplateEntries.size > 1) continue
|
||||
|
||||
val value = (stringTemplateEntries.singleOrNull() as? KtLiteralStringTemplateEntry)?.text
|
||||
if (value == null) {
|
||||
trace.report(ErrorsJvm.JVM_PACKAGE_NAME_CANNOT_BE_EMPTY.on(entry))
|
||||
}
|
||||
else if (!isValidJavaFqName(value)) {
|
||||
trace.report(ErrorsJvm.JVM_PACKAGE_NAME_MUST_BE_VALID_NAME.on(entry))
|
||||
}
|
||||
else if (entry.containingKtFile.declarations.any {
|
||||
it !is KtFunction && it !is KtProperty && it !is KtTypeAlias
|
||||
}) {
|
||||
trace.report(ErrorsJvm.JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES.on(entry))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -73,6 +73,11 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
|
||||
MAP.put(NON_SOURCE_REPEATED_ANNOTATION, "Only annotations with SOURCE retention can be repeated on JVM version before 1.8");
|
||||
MAP.put(ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES, "Annotation ''@{0}'' is not applicable to the multi-file classes", TO_STRING);
|
||||
|
||||
MAP.put(JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_MULTIFILE_CLASSES, "''@JvmPackageName'' annotation is not supported in multi-file classes");
|
||||
MAP.put(JVM_PACKAGE_NAME_CANNOT_BE_EMPTY, "''@JvmPackageName'' annotation value cannot be empty");
|
||||
MAP.put(JVM_PACKAGE_NAME_MUST_BE_VALID_NAME, "''@JvmPackageName'' annotation value must be a valid dot-qualified name of a package");
|
||||
MAP.put(JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES, "''@JvmPackageName'' annotation is not supported for files with class declarations");
|
||||
|
||||
MAP.put(NO_REFLECTION_IN_CLASS_PATH, "Call uses reflection API which is not found in compilation classpath. " +
|
||||
"Make sure you have kotlin-reflect.jar in the classpath");
|
||||
|
||||
|
||||
@@ -73,6 +73,11 @@ public interface ErrorsJvm {
|
||||
DiagnosticFactory0<KtAnnotationEntry> NON_SOURCE_REPEATED_ANNOTATION = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory1<KtAnnotationEntry, FqName> ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<KtAnnotationEntry> JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_MULTIFILE_CLASSES = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<KtAnnotationEntry> JVM_PACKAGE_NAME_CANNOT_BE_EMPTY = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<KtAnnotationEntry> JVM_PACKAGE_NAME_MUST_BE_VALID_NAME = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<KtAnnotationEntry> JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<PsiElement> INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user