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:
Alexander Udalov
2017-09-06 15:59:26 +03:00
parent 2bb437b219
commit e8e38d90ff
8 changed files with 144 additions and 13 deletions
@@ -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 = "__"
@@ -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))
}
}
}
}
@@ -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);