• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.model.psi
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.FilterPredicate
21 import com.android.tools.metalava.model.Import
22 import com.android.tools.metalava.model.SourceFile
23 import com.intellij.psi.PsiClass
24 import com.intellij.psi.PsiClassOwner
25 import com.intellij.psi.PsiComment
26 import com.intellij.psi.PsiElement
27 import com.intellij.psi.PsiField
28 import com.intellij.psi.PsiFile
29 import com.intellij.psi.PsiJavaFile
30 import com.intellij.psi.PsiMethod
31 import com.intellij.psi.PsiPackage
32 import com.intellij.psi.PsiWhiteSpace
33 import java.util.TreeSet
34 import org.jetbrains.kotlin.kdoc.psi.api.KDoc
35 import org.jetbrains.kotlin.psi.KtFile
36 import org.jetbrains.kotlin.psi.psiUtil.startOffset
37 import org.jetbrains.uast.UFile
38 
39 /** Whether we should limit import statements to symbols found in class docs */
40 private const val ONLY_IMPORT_CLASSES_REFERENCED_IN_DOCS = true
41 
42 internal class PsiSourceFile(
43     val codebase: PsiBasedCodebase,
44     val file: PsiFile,
45     val uFile: UFile? = null
46 ) : SourceFile {
getHeaderCommentsnull47     override fun getHeaderComments(): String? {
48         if (uFile != null) {
49             var comment: String? = null
50             for (uComment in uFile.allCommentsInFile) {
51                 val text = uComment.text
52                 comment =
53                     if (comment != null) {
54                         comment + "\n" + text
55                     } else {
56                         text
57                     }
58             }
59             return comment
60         }
61 
62         // https://youtrack.jetbrains.com/issue/KT-22135
63         if (file is PsiJavaFile) {
64             val pkg = file.packageStatement ?: return null
65             return file.text.substring(0, pkg.startOffset)
66         } else if (file is KtFile) {
67             var curr: PsiElement? = file.firstChild
68             var comment: String? = null
69             while (curr != null) {
70                 if (curr is PsiComment || curr is KDoc) {
71                     val text = curr.text
72                     comment =
73                         if (comment != null) {
74                             comment + "\n" + text
75                         } else {
76                             text
77                         }
78                 } else if (curr !is PsiWhiteSpace) {
79                     break
80                 }
81                 curr = curr.nextSibling
82             }
83             return comment
84         }
85 
86         return super.getHeaderComments()
87     }
88 
getImportsnull89     override fun getImports(predicate: FilterPredicate): Collection<Import> {
90         val imports = TreeSet<Import>(compareBy { it.pattern })
91 
92         if (file is PsiJavaFile) {
93             val importList = file.importList
94             if (importList != null) {
95                 for (importStatement in importList.importStatements) {
96                     val resolved = importStatement.resolve() ?: continue
97                     if (resolved is PsiClass) {
98                         val classItem = codebase.findOrCreateClass(resolved)
99                         if (predicate.test(classItem)) {
100                             imports.add(Import(classItem))
101                         }
102                     } else if (resolved is PsiPackage) {
103                         val pkgItem = codebase.findPackage(resolved.qualifiedName) ?: continue
104                         if (
105                             predicate.test(pkgItem) &&
106                                 // Also make sure it isn't an empty package (after applying the
107                                 // filter)
108                                 // since in that case we'd have an invalid import
109                                 pkgItem.topLevelClasses().any { it.emit && predicate.test(it) }
110                         ) {
111                             imports.add(Import(pkgItem))
112                         }
113                     } else if (resolved is PsiMethod) {
114                         codebase.findClass(resolved.containingClass ?: continue) ?: continue
115                         val methodItem = codebase.findCallableByPsiMethod(resolved)
116                         if (predicate.test(methodItem)) {
117                             imports.add(Import(methodItem))
118                         }
119                     } else if (resolved is PsiField) {
120                         val classItem =
121                             codebase.findOrCreateClass(resolved.containingClass ?: continue)
122                         val fieldItem =
123                             classItem.findField(
124                                 resolved.name,
125                                 includeSuperClasses = true,
126                                 includeInterfaces = false
127                             )
128                                 ?: continue
129                         if (predicate.test(fieldItem)) {
130                             imports.add(Import(fieldItem))
131                         }
132                     }
133                 }
134             }
135         } else if (file is KtFile) {
136             for (importDirective in file.importDirectives) {
137                 val resolved = importDirective.reference?.resolve() ?: continue
138                 if (resolved is PsiClass) {
139                     val classItem = codebase.findOrCreateClass(resolved)
140                     if (predicate.test(classItem)) {
141                         imports.add(Import(classItem))
142                     }
143                 }
144             }
145         }
146 
147         // Next only keep those that are present in any docs; those are the only ones
148         // we need to import
149         if (imports.isNotEmpty()) {
150             @Suppress("ConstantConditionIf")
151             return if (ONLY_IMPORT_CLASSES_REFERENCED_IN_DOCS) {
152                 filterImports(imports, predicate)
153             } else {
154                 imports
155             }
156         }
157 
158         return emptyList()
159     }
160 
classesnull161     override fun classes(): Sequence<ClassItem> {
162         return (file as? PsiClassOwner)
163             ?.classes
164             ?.asSequence()
165             ?.mapNotNull { codebase.findClass(it) }
166             .orEmpty()
167     }
168 
equalsnull169     override fun equals(other: Any?): Boolean {
170         if (this === other) return true
171         return other is PsiSourceFile && file == other.file
172     }
173 
hashCodenull174     override fun hashCode(): Int = file.hashCode()
175 
176     override fun toString(): String = "file ${file.virtualFile?.path}"
177 }
178