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: FqName = FqName("kotlin.jvm.JvmMultifileClass")
|
||||||
val JVM_MULTIFILE_CLASS_SHORT = JVM_MULTIFILE_CLASS.shortName().asString()
|
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 val JVM_PACKAGE_NAME_SHORT = JVM_PACKAGE_NAME.shortName().asString()
|
||||||
|
|
||||||
private const val MULTIFILE_PART_NAME_DELIMITER = "__"
|
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.descriptors.annotations.KotlinTarget
|
||||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||||
import org.jetbrains.kotlin.name.FqName
|
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.AdditionalAnnotationChecker
|
||||||
import org.jetbrains.kotlin.resolve.AnnotationChecker
|
import org.jetbrains.kotlin.resolve.AnnotationChecker
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
|
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||||
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.jvm.diagnostics.ErrorsJvm
|
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
|
||||||
|
|
||||||
object RepeatableAnnotationChecker: AdditionalAnnotationChecker {
|
object RepeatableAnnotationChecker: AdditionalAnnotationChecker {
|
||||||
@@ -60,7 +58,7 @@ object RepeatableAnnotationChecker: AdditionalAnnotationChecker {
|
|||||||
|
|
||||||
object FileClassAnnotationsChecker: AdditionalAnnotationChecker {
|
object FileClassAnnotationsChecker: AdditionalAnnotationChecker {
|
||||||
// JvmName & JvmMultifileClass annotations are applicable to multi-file class parts regardless of their retention.
|
// 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) {
|
override fun checkEntries(entries: List<KtAnnotationEntry>, actualTargets: List<KotlinTarget>, trace: BindingTrace) {
|
||||||
val fileAnnotationsToCheck = arrayListOf<Pair<KtAnnotationEntry, ClassDescriptor>>()
|
val fileAnnotationsToCheck = arrayListOf<Pair<KtAnnotationEntry, ClassDescriptor>>()
|
||||||
@@ -74,13 +72,40 @@ object FileClassAnnotationsChecker: AdditionalAnnotationChecker {
|
|||||||
fileAnnotationsToCheck.add(Pair(entry, classDescriptor))
|
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) {
|
if (isMultifileClass) {
|
||||||
val classFqName = classDescriptor.classId!!.asSingleFqName()
|
for ((entry, classDescriptor) in fileAnnotationsToCheck) {
|
||||||
if (classFqName in ALWAYS_APPLICABLE) continue
|
val classFqName = classDescriptor.fqNameSafe
|
||||||
if (classDescriptor.getAnnotationRetention() != KotlinRetention.SOURCE) {
|
if (classFqName in alwaysApplicable) continue
|
||||||
trace.report(ErrorsJvm.ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES.on(entry, classFqName))
|
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(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(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. " +
|
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");
|
"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);
|
DiagnosticFactory0<KtAnnotationEntry> NON_SOURCE_REPEATED_ANNOTATION = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory1<KtAnnotationEntry, FqName> ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES = DiagnosticFactory1.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> INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory0<PsiElement> SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<PsiElement> SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC = DiagnosticFactory0.create(ERROR);
|
||||||
|
|
||||||
|
|||||||
Vendored
+34
@@ -0,0 +1,34 @@
|
|||||||
|
// !DIAGNOSTICS: -INVISIBLE_MEMBER -INVISIBLE_REFERENCE
|
||||||
|
// !API_VERSION: 1.2
|
||||||
|
|
||||||
|
// FILE: a.kt
|
||||||
|
<!JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_MULTIFILE_CLASSES!>@file:JvmPackageName("a")<!>
|
||||||
|
@file:JvmMultifileClass
|
||||||
|
package a
|
||||||
|
fun a() {}
|
||||||
|
|
||||||
|
// FILE: b.kt
|
||||||
|
<!JVM_PACKAGE_NAME_CANNOT_BE_EMPTY!>@file:JvmPackageName("")<!>
|
||||||
|
package b
|
||||||
|
fun b() {}
|
||||||
|
|
||||||
|
// FILE: c.kt
|
||||||
|
<!JVM_PACKAGE_NAME_MUST_BE_VALID_NAME!>@file:JvmPackageName("invalid-fq-name")<!>
|
||||||
|
package c
|
||||||
|
fun c() {}
|
||||||
|
|
||||||
|
// FILE: d.kt
|
||||||
|
<!JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES!>@file:JvmPackageName("d")<!>
|
||||||
|
package d
|
||||||
|
class D
|
||||||
|
fun d() {}
|
||||||
|
|
||||||
|
// FILE: e.kt
|
||||||
|
@file:JvmPackageName(<!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!>)
|
||||||
|
package e
|
||||||
|
fun e() {}
|
||||||
|
|
||||||
|
// FILE: f.kt
|
||||||
|
@file:JvmPackageName(<!EXPRESSION_EXPECTED_PACKAGE_FOUND!>f<!>)
|
||||||
|
package f
|
||||||
|
const val name = "f"
|
||||||
compiler/testData/diagnostics/testsWithStdLib/annotations/jvmPackageName/incorrectJvmPackageName.txt
Vendored
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
package a {
|
||||||
|
public fun a(): kotlin.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
package b {
|
||||||
|
public fun b(): kotlin.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
package c {
|
||||||
|
public fun c(): kotlin.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
package d {
|
||||||
|
public fun d(): kotlin.Unit
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package e {
|
||||||
|
public fun e(): kotlin.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
package f {
|
||||||
|
public const val name: kotlin.String = "f"
|
||||||
|
}
|
||||||
@@ -539,6 +539,21 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmPackageName")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class JvmPackageName extends AbstractDiagnosticsTestWithStdLib {
|
||||||
|
public void testAllFilesPresentInJvmPackageName() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmPackageName"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("incorrectJvmPackageName.kt")
|
||||||
|
public void testIncorrectJvmPackageName() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmPackageName/incorrectJvmPackageName.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmStatic")
|
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmStatic")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
+15
@@ -539,6 +539,21 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmPackageName")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class JvmPackageName extends AbstractDiagnosticsTestWithStdLibUsingJavac {
|
||||||
|
public void testAllFilesPresentInJvmPackageName() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmPackageName"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("incorrectJvmPackageName.kt")
|
||||||
|
public void testIncorrectJvmPackageName() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmPackageName/incorrectJvmPackageName.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmStatic")
|
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmStatic")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user