/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.annotation;
import com.intellij.openapi.util.text.StringUtil;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtAnnotationUseSiteTarget;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.renderer.AnnotationArgumentsRenderingPolicy;
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isNonCompanionObject;
public abstract class AbstractAnnotationDescriptorResolveTest extends KotlinTestWithEnvironment {
private static final DescriptorRenderer WITH_ANNOTATION_ARGUMENT_TYPES = DescriptorRenderer.Companion.withOptions(
options -> {
options.setVerbose(true);
options.setAnnotationArgumentsRenderingPolicy(AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY);
options.setClassifierNamePolicy(ClassifierNamePolicy.SHORT.INSTANCE);
options.setModifiers(DescriptorRendererModifier.ALL);
return Unit.INSTANCE;
}
);
private static final String PATH = "compiler/testData/resolveAnnotations/testFile.kt";
private static final FqName PACKAGE = new FqName("test");
protected BindingContext context;
@Override
protected KotlinCoreEnvironment createEnvironment() {
return KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(getTestRootDisposable());
}
protected void doTest(@NotNull String content, @NotNull String expectedAnnotation) {
checkAnnotationOnAllExceptLocalDeclarations(content, expectedAnnotation);
checkAnnotationOnLocalDeclarations(expectedAnnotation);
}
protected void checkAnnotationOnAllExceptLocalDeclarations(String content, String expectedAnnotation) {
KtFile testFile = getFile(content);
PackageFragmentDescriptor testPackage = getPackage(testFile);
checkAnnotationsOnFile(expectedAnnotation, testFile);
ClassDescriptor myClass = getClassDescriptor(testPackage, "MyClass");
checkDescriptor(expectedAnnotation, myClass);
ClassDescriptor companionObjectDescriptor = myClass.getCompanionObjectDescriptor();
assert companionObjectDescriptor != null : "Cannot find companion object for class " + myClass.getName();
checkDescriptor(expectedAnnotation, companionObjectDescriptor);
checkDescriptor(expectedAnnotation, getInnerClassDescriptor(myClass, "InnerClass"));
FunctionDescriptor foo = getFunctionDescriptor(myClass, "foo");
checkAnnotationsOnFunction(expectedAnnotation, foo);
SimpleFunctionDescriptor anonymousFun = getAnonymousFunDescriptor();
if (anonymousFun instanceof AnonymousFunctionDescriptor) {
for (ValueParameterDescriptor descriptor : anonymousFun.getValueParameters()) {
List<VariableDescriptor> destructuringVariables = ValueParameterDescriptorImpl.getDestructuringVariablesOrNull(descriptor);
if (destructuringVariables == null) continue;
for (VariableDescriptor entry : destructuringVariables) {
checkDescriptor(expectedAnnotation, entry);
}
}
}
PropertyDescriptor prop = getPropertyDescriptor(myClass, "prop");
checkAnnotationsOnProperty(expectedAnnotation, prop);
FunctionDescriptor topFoo = getFunctionDescriptor(testPackage, "topFoo");
checkAnnotationsOnFunction(expectedAnnotation, topFoo);
PropertyDescriptor topProp = getPropertyDescriptor(testPackage, "topProp", true);
checkAnnotationsOnProperty(expectedAnnotation, topProp);
checkDescriptor(expectedAnnotation, getClassDescriptor(testPackage, "MyObject"));
checkDescriptor(expectedAnnotation, getConstructorParameterDescriptor(myClass, "consProp"));
checkDescriptor(expectedAnnotation, getConstructorParameterDescriptor(myClass, "param"));
}
private void checkAnnotationsOnFile(String expectedAnnotation, KtFile file) {
String actualAnnotation = StringUtil.join(file.getAnnotationEntries(), annotationEntry -> {
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
assertNotNull(annotationDescriptor);
KtAnnotationUseSiteTarget target = annotationEntry.getUseSiteTarget();
if (target != null) {
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(
annotationDescriptor, target.getAnnotationUseSiteTarget());
}
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(annotationDescriptor, null);
}, " ");
String expectedAnnotationWithTarget = "@" + AnnotationUseSiteTarget.FILE.getRenderName() + ":" + expectedAnnotation.substring(1);
assertEquals(expectedAnnotationWithTarget, actualAnnotation);
}
private void checkAnnotationOnLocalDeclarations(String expectedAnnotation) {
checkDescriptor(expectedAnnotation, getLocalClassDescriptor("LocalClass"));
checkDescriptor(expectedAnnotation, getLocalObjectDescriptor("LocalObject"));
checkDescriptor(expectedAnnotation, getLocalFunDescriptor("localFun"));
checkDescriptor(expectedAnnotation, getLocalVarDescriptor(context, "localVar"));
}
private static void checkAnnotationsOnProperty(String expectedAnnotation, PropertyDescriptor prop) {
checkDescriptorWithTarget(expectedAnnotation, prop, AnnotationUseSiteTarget.FIELD);
checkDescriptor(expectedAnnotation, prop.getGetter());
PropertySetterDescriptor propSetter = prop.getSetter();
assertNotNull(propSetter);
checkAnnotationsOnFunction(expectedAnnotation, propSetter);
}
private static void checkAnnotationsOnFunction(String expectedAnnotation, FunctionDescriptor foo) {
checkDescriptor(expectedAnnotation, foo);
checkDescriptor(expectedAnnotation, getFunctionParameterDescriptor(foo, "param"));
}
@NotNull
protected static FunctionDescriptor getFunctionDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name) {
Name functionName = Name.identifier(name);
MemberScope memberScope = packageView.getMemberScope();
Collection<SimpleFunctionDescriptor> functions = memberScope.getContributedFunctions(functionName, NoLookupLocation.FROM_TEST);
assert functions.size() == 1 : "Failed to find function " + functionName + " in class" + "." + packageView.getName();
return functions.iterator().next();
}
@NotNull
private static FunctionDescriptor getFunctionDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name functionName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.emptyList());
Collection<SimpleFunctionDescriptor> functions = memberScope.getContributedFunctions(functionName, NoLookupLocation.FROM_TEST);
assert functions.size() == 1 : "Failed to find function " + functionName + " in class" + "." + classDescriptor.getName();
return functions.iterator().next();
}
@Nullable
protected static PropertyDescriptor getPropertyDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name, boolean failOnMissing) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = packageView.getMemberScope();
Collection<PropertyDescriptor> properties = memberScope.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
if (properties.isEmpty()) {
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(memberScope)) {
if (descriptor instanceof ClassDescriptor) {
Collection<PropertyDescriptor> classProperties = ((ClassDescriptor) descriptor).getMemberScope(Collections.emptyList())
.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
if (!classProperties.isEmpty()) {
properties = classProperties;
break;
}
}
}
}
if (failOnMissing) {
assert properties.size() == 1 : "Failed to find property " + propertyName + " in class " + packageView.getName();
}
else if (properties.size() != 1) {
return null;
}
return properties.iterator().next();
}
@NotNull
private static PropertyDescriptor getPropertyDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.emptyList());
Collection<PropertyDescriptor> properties = memberScope.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
assert properties.size() == 1 : "Failed to find property " + propertyName + " in class " + classDescriptor.getName();
return properties.iterator().next();
}
@NotNull
protected static ClassDescriptor getClassDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name) {
Name className = Name.identifier(name);
ClassifierDescriptor aClass = packageView.getMemberScope().getContributedClassifier(className, NoLookupLocation.FROM_TEST);
assertNotNull("Failed to find class: " + packageView.getName() + "." + className, aClass);
assert aClass instanceof ClassDescriptor : "Not a class: " + aClass;
return (ClassDescriptor) aClass;
}
@NotNull
private static ClassDescriptor getInnerClassDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.emptyList());
ClassifierDescriptor innerClass = memberScope.getContributedClassifier(propertyName, NoLookupLocation.FROM_TEST);
assert innerClass instanceof ClassDescriptor : "Failed to find inner class " +
propertyName +
" in class " +
classDescriptor.getName();
return (ClassDescriptor) innerClass;
}
@NotNull
private ClassDescriptor getLocalClassDescriptor(@NotNull String name) {
for (ClassDescriptor descriptor : context.getSliceContents(BindingContext.CLASS).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local class " + name);
return null;
}
@NotNull
private ClassDescriptor getLocalObjectDescriptor(@NotNull String name) {
ClassDescriptor localClassDescriptor = getLocalClassDescriptor(name);
if (isNonCompanionObject(localClassDescriptor)) {
return localClassDescriptor;
}
fail("Failed to find local object " + name);
return null;
}
@NotNull
private SimpleFunctionDescriptor getLocalFunDescriptor(@NotNull String name) {
for (SimpleFunctionDescriptor descriptor : context.getSliceContents(BindingContext.FUNCTION).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local fun " + name);
return null;
}
@NotNull
protected static VariableDescriptor getLocalVarDescriptor(@NotNull BindingContext context, @NotNull String name) {
for (VariableDescriptor descriptor : context.getSliceContents(BindingContext.VARIABLE).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local variable " + name);
return null;
}
@NotNull
private SimpleFunctionDescriptor getAnonymousFunDescriptor() {
for (SimpleFunctionDescriptor descriptor : context.getSliceContents(BindingContext.FUNCTION).values()) {
if (descriptor instanceof AnonymousFunctionDescriptor) {
return descriptor;
}
}
fail("Failed to find anonymous fun");
return null;
}
@NotNull
private static ValueParameterDescriptor getConstructorParameterDescriptor(
@NotNull ClassDescriptor classDescriptor,
@NotNull String name
) {
ConstructorDescriptor constructorDescriptor = getConstructorDescriptor(classDescriptor);
ValueParameterDescriptor parameter = findValueParameter(constructorDescriptor.getValueParameters(), name);
assertNotNull("Cannot find constructor parameter with name " + name, parameter);
return parameter;
}
@NotNull
private static ConstructorDescriptor getConstructorDescriptor(@NotNull ClassDescriptor classDescriptor) {
Collection<ClassConstructorDescriptor> constructors = classDescriptor.getConstructors();
assert constructors.size() == 1;
return constructors.iterator().next();
}
private static ValueParameterDescriptor findValueParameter(List<ValueParameterDescriptor> parameters, String name) {
for (ValueParameterDescriptor parameter : parameters) {
if (parameter.getName().asString().equals(name)) {
return parameter;
}
}
return null;
}
@NotNull
private static ValueParameterDescriptor getFunctionParameterDescriptor(
@NotNull FunctionDescriptor functionDescriptor,
@NotNull String name
) {
ValueParameterDescriptor parameter = findValueParameter(functionDescriptor.getValueParameters(), name);
assertNotNull("Cannot find function parameter with name " + name, parameter);
return parameter;
}
@NotNull
protected KtFile getFile(@NotNull String content) {
KtFile ktFile = KotlinTestUtils.createFile("dummy.kt", content, getProject());
AnalysisResult analysisResult = KotlinTestUtils.analyzeFile(ktFile, getEnvironment());
context = analysisResult.getBindingContext();
return ktFile;
}
@NotNull
protected PackageFragmentDescriptor getPackage(@NotNull KtFile ktFile) {
PackageFragmentDescriptor packageFragment = context.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, ktFile);
assertNotNull("Failed to find package: " + PACKAGE, packageFragment);
return packageFragment;
}
@NotNull
protected PackageFragmentDescriptor getPackage(@NotNull String content) {
return getPackage(getFile(content));
}
protected static String getContent(@NotNull String annotationText) throws IOException {
File file = new File(PATH);
return KotlinTestUtils.doLoadFile(file).replaceAll("ANNOTATION", annotationText);
}
private static String renderAnnotations(Annotations annotations, @Nullable AnnotationUseSiteTarget defaultTarget) {
return StringUtil.join(annotations.getAllAnnotations(), annotationWithTarget -> {
AnnotationUseSiteTarget targetToRender = annotationWithTarget.getTarget();
if (targetToRender == defaultTarget) {
targetToRender = null;
}
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(annotationWithTarget.getAnnotation(), targetToRender);
}, " ");
}
protected static void checkDescriptor(String expectedAnnotation, DeclarationDescriptor member) {
String actual = getAnnotations(member);
assertEquals("Failed to resolve annotation descriptor for " + member.toString(), expectedAnnotation, actual);
}
private static void checkDescriptorWithTarget(String expectedAnnotation, DeclarationDescriptor member, AnnotationUseSiteTarget target) {
String actual = renderAnnotations(member.getAnnotations(), target);
assertEquals("Failed to resolve annotation descriptor for " + member.toString(), expectedAnnotation, actual);
}
@NotNull
protected static String getAnnotations(DeclarationDescriptor member) {
return renderAnnotations(member.getAnnotations(), null);
}
@Override
public void tearDown() throws Exception {
context = null;
super.tearDown();
}
}