001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.js.translate.utils;
018
019 import com.intellij.psi.PsiFile;
020 import org.jetbrains.annotations.NotNull;
021 import org.jetbrains.annotations.Nullable;
022 import org.jetbrains.kotlin.descriptors.*;
023 import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
024 import org.jetbrains.kotlin.descriptors.annotations.AnnotationWithTarget;
025 import org.jetbrains.kotlin.descriptors.annotations.Annotations;
026 import org.jetbrains.kotlin.js.PredefinedAnnotation;
027 import org.jetbrains.kotlin.name.FqName;
028 import org.jetbrains.kotlin.name.FqNameUnsafe;
029 import org.jetbrains.kotlin.psi.KtAnnotationEntry;
030 import org.jetbrains.kotlin.psi.KtFile;
031 import org.jetbrains.kotlin.resolve.BindingContext;
032 import org.jetbrains.kotlin.resolve.DescriptorUtils;
033 import org.jetbrains.kotlin.resolve.constants.ConstantValue;
034 import org.jetbrains.kotlin.resolve.source.PsiSourceFile;
035 import org.jetbrains.kotlin.serialization.js.KotlinJavascriptPackageFragment;
036
037 import java.util.ArrayList;
038 import java.util.Collections;
039 import java.util.List;
040
041 public final class AnnotationsUtils {
042 private static final String JS_NAME = "kotlin.js.JsName";
043 public static final FqName JS_MODULE_ANNOTATION = new FqName("kotlin.js.JsModule");
044 private static final FqName JS_NON_MODULE_ANNOTATION = new FqName("kotlin.js.JsNonModule");
045 public static final FqName JS_QUALIFIER_ANNOTATION = new FqName("kotlin.js.JsQualifier");
046
047 private AnnotationsUtils() {
048 }
049
050 public static boolean hasAnnotation(
051 @NotNull DeclarationDescriptor descriptor,
052 @NotNull PredefinedAnnotation annotation
053 ) {
054 return getAnnotationByName(descriptor, annotation) != null;
055 }
056
057 @Nullable
058 private static String getAnnotationStringParameter(@NotNull DeclarationDescriptor declarationDescriptor,
059 @NotNull PredefinedAnnotation annotation) {
060 AnnotationDescriptor annotationDescriptor = getAnnotationByName(declarationDescriptor, annotation);
061 assert annotationDescriptor != null;
062 //TODO: this is a quick fix for unsupported default args problem
063 if (annotationDescriptor.getAllValueArguments().isEmpty()) {
064 return null;
065 }
066 ConstantValue<?> constant = annotationDescriptor.getAllValueArguments().values().iterator().next();
067 //TODO: this is a quick fix for unsupported default args problem
068 if (constant == null) {
069 return null;
070 }
071 Object value = constant.getValue();
072 assert value instanceof String : "Native function annotation should have one String parameter";
073 return (String) value;
074 }
075
076 @Nullable
077 public static String getNameForAnnotatedObject(@NotNull DeclarationDescriptor declarationDescriptor,
078 @NotNull PredefinedAnnotation annotation) {
079 if (!hasAnnotation(declarationDescriptor, annotation)) {
080 return null;
081 }
082 return getAnnotationStringParameter(declarationDescriptor, annotation);
083 }
084
085 @Nullable
086 public static String getNameForAnnotatedObject(@NotNull DeclarationDescriptor descriptor) {
087 String defaultJsName = getJsName(descriptor);
088
089 for (PredefinedAnnotation annotation : PredefinedAnnotation.Companion.getWITH_CUSTOM_NAME()) {
090 if (!hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
091 continue;
092 }
093 String name = getNameForAnnotatedObject(descriptor, annotation);
094 if (name == null) {
095 name = defaultJsName;
096 }
097 return name != null ? name : descriptor.getName().asString();
098 }
099
100 if (defaultJsName == null && isEffectivelyExternal(descriptor)) {
101 return descriptor.getName().asString();
102 }
103
104 return defaultJsName;
105 }
106
107 @Nullable
108 private static AnnotationDescriptor getAnnotationByName(
109 @NotNull DeclarationDescriptor descriptor,
110 @NotNull PredefinedAnnotation annotation
111 ) {
112 return getAnnotationByName(descriptor, annotation.getFqName());
113 }
114
115 @Nullable
116 private static AnnotationDescriptor getAnnotationByName(@NotNull DeclarationDescriptor descriptor, @NotNull FqName fqName) {
117 AnnotationWithTarget annotationWithTarget = Annotations.Companion.findAnyAnnotation(descriptor.getAnnotations(), (fqName));
118 return annotationWithTarget != null ? annotationWithTarget.getAnnotation() : null;
119 }
120
121 public static boolean isNativeObject(@NotNull DeclarationDescriptor descriptor) {
122 if (hasAnnotationOrInsideAnnotatedClass(descriptor, PredefinedAnnotation.NATIVE) || isEffectivelyExternal(descriptor)) return true;
123
124 if (descriptor instanceof PropertyAccessorDescriptor) {
125 PropertyAccessorDescriptor accessor = (PropertyAccessorDescriptor) descriptor;
126 return hasAnnotationOrInsideAnnotatedClass(accessor.getCorrespondingProperty(), PredefinedAnnotation.NATIVE);
127 }
128
129 return false;
130 }
131
132 public static boolean isNativeInterface(@NotNull DeclarationDescriptor descriptor) {
133 return isNativeObject(descriptor) && DescriptorUtils.isInterface(descriptor);
134 }
135
136 private static boolean isEffectivelyExternal(@NotNull DeclarationDescriptor descriptor) {
137 return descriptor instanceof MemberDescriptor && DescriptorUtils.isEffectivelyExternal((MemberDescriptor) descriptor);
138 }
139
140 public static boolean isLibraryObject(@NotNull DeclarationDescriptor descriptor) {
141 return hasAnnotationOrInsideAnnotatedClass(descriptor, PredefinedAnnotation.LIBRARY);
142 }
143
144 @Nullable
145 public static String getJsName(@NotNull DeclarationDescriptor descriptor) {
146 AnnotationDescriptor annotation = getJsNameAnnotation(descriptor);
147 if (annotation == null || annotation.getAllValueArguments().isEmpty()) return null;
148
149 ConstantValue<?> value = annotation.getAllValueArguments().values().iterator().next();
150 if (value == null) return null;
151
152 Object result = value.getValue();
153 if (!(result instanceof String)) return null;
154
155 return (String) result;
156 }
157
158 @Nullable
159 public static AnnotationDescriptor getJsNameAnnotation(@NotNull DeclarationDescriptor descriptor) {
160 return getAnnotationByName(descriptor, new FqName(JS_NAME));
161 }
162
163 public static boolean isPredefinedObject(@NotNull DeclarationDescriptor descriptor) {
164 if (descriptor instanceof MemberDescriptor && ((MemberDescriptor) descriptor).isHeader()) return true;
165 if (isEffectivelyExternal(descriptor)) return true;
166
167 for (PredefinedAnnotation annotation : PredefinedAnnotation.values()) {
168 if (hasAnnotationOrInsideAnnotatedClass(descriptor, annotation)) {
169 return true;
170 }
171 }
172
173 return false;
174 }
175
176 private static boolean hasAnnotationOrInsideAnnotatedClass(
177 @NotNull DeclarationDescriptor descriptor,
178 @NotNull PredefinedAnnotation annotation
179 ) {
180 return hasAnnotationOrInsideAnnotatedClass(descriptor, annotation.getFqName());
181 }
182
183 private static boolean hasAnnotationOrInsideAnnotatedClass(@NotNull DeclarationDescriptor descriptor, @NotNull FqName fqName) {
184 if (getAnnotationByName(descriptor, fqName) != null) {
185 return true;
186 }
187 ClassDescriptor containingClass = DescriptorUtils.getContainingClass(descriptor);
188 return containingClass != null && hasAnnotationOrInsideAnnotatedClass(containingClass, fqName);
189 }
190
191 public static boolean hasJsNameInAccessors(@NotNull PropertyDescriptor property) {
192 for (PropertyAccessorDescriptor accessor : property.getAccessors()) {
193 if (getJsName(accessor) != null) return true;
194 }
195 return false;
196 }
197
198 @Nullable
199 public static String getModuleName(@NotNull DeclarationDescriptor declaration) {
200 AnnotationDescriptor annotation = declaration.getAnnotations().findAnnotation(JS_MODULE_ANNOTATION);
201 return annotation != null ? extractSingleStringArgument(annotation) : null;
202 }
203
204 @Nullable
205 public static String getFileModuleName(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) {
206 return getSingleStringAnnotationArgument(bindingContext, declaration, JS_MODULE_ANNOTATION);
207 }
208
209 @Nullable
210 public static String getFileQualifier(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) {
211 return getSingleStringAnnotationArgument(bindingContext, declaration, JS_QUALIFIER_ANNOTATION);
212 }
213
214 @Nullable
215 private static String getSingleStringAnnotationArgument(
216 @NotNull BindingContext bindingContext,
217 @NotNull DeclarationDescriptor declaration,
218 @NotNull FqName annotationFqName
219 ) {
220 for (AnnotationDescriptor annotation : getContainingFileAnnotations(bindingContext, declaration)) {
221 DeclarationDescriptor annotationType = annotation.getType().getConstructor().getDeclarationDescriptor();
222 if (annotationType == null) continue;
223
224 FqNameUnsafe fqName = DescriptorUtils.getFqName(annotation.getType().getConstructor().getDeclarationDescriptor());
225 if (fqName.equals(annotationFqName.toUnsafe())) {
226 return extractSingleStringArgument(annotation);
227 }
228 }
229 return null;
230 }
231
232 public static boolean isNonModule(@NotNull DeclarationDescriptor declaration) {
233 return declaration.getAnnotations().findAnnotation(JS_NON_MODULE_ANNOTATION) != null;
234 }
235
236 public static boolean isFromNonModuleFile(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor declaration) {
237 for (AnnotationDescriptor annotation : getContainingFileAnnotations(bindingContext, declaration)) {
238 DeclarationDescriptor annotationType = annotation.getType().getConstructor().getDeclarationDescriptor();
239 if (annotationType == null) continue;
240
241 DeclarationDescriptor annotationTypeDescriptor = annotation.getType().getConstructor().getDeclarationDescriptor();
242 assert annotationTypeDescriptor != null : "Annotation type should have descriptor: " + annotation.getType();
243
244 FqNameUnsafe fqName = DescriptorUtils.getFqName(annotationTypeDescriptor);
245 if (fqName.equals(JS_NON_MODULE_ANNOTATION.toUnsafe())) {
246 return true;
247 }
248 }
249
250 return false;
251 }
252
253 @Nullable
254 private static String extractSingleStringArgument(@NotNull AnnotationDescriptor annotation) {
255 if (annotation.getAllValueArguments().isEmpty()) return null;
256
257 ConstantValue<?> importValue = annotation.getAllValueArguments().values().iterator().next();
258 if (importValue == null) return null;
259
260 if (!(importValue.getValue() instanceof String)) return null;
261 return (String) importValue.getValue();
262 }
263
264 @NotNull
265 public static List<AnnotationDescriptor> getContainingFileAnnotations(
266 @NotNull BindingContext bindingContext,
267 @NotNull DeclarationDescriptor descriptor
268 ) {
269 PackageFragmentDescriptor containingPackage = DescriptorUtils.getParentOfType(descriptor, PackageFragmentDescriptor.class, false);
270 if (containingPackage instanceof KotlinJavascriptPackageFragment) {
271 return ((KotlinJavascriptPackageFragment) containingPackage).getContainingFileAnnotations(descriptor);
272 }
273
274 KtFile kotlinFile = getFile(descriptor);
275 if (kotlinFile != null) {
276 List<AnnotationDescriptor> annotations = new ArrayList<AnnotationDescriptor>();
277 for (KtAnnotationEntry psiAnnotation : kotlinFile.getAnnotationEntries()) {
278 AnnotationDescriptor annotation = bindingContext.get(BindingContext.ANNOTATION, psiAnnotation);
279 if (annotation != null) {
280 annotations.add(annotation);
281 }
282 }
283 return annotations;
284 }
285
286 return Collections.emptyList();
287 }
288
289 @Nullable
290 private static KtFile getFile(DeclarationDescriptor descriptor) {
291 if (!(descriptor instanceof DeclarationDescriptorWithSource)) return null;
292 SourceFile file = ((DeclarationDescriptorWithSource) descriptor).getSource().getContainingFile();
293 if (!(file instanceof PsiSourceFile)) return null;
294
295 PsiFile psiFile = ((PsiSourceFile) file).getPsiFile();
296 if (!(psiFile instanceof KtFile)) return null;
297
298 return (KtFile) psiFile;
299 }
300 }