|
1 | 1 | package com.github.codeql.comments |
2 | 2 |
|
3 | 3 | import com.github.codeql.* |
4 | | -import com.github.codeql.utils.IrVisitorLookup |
5 | 4 | import com.github.codeql.utils.isLocalFunction |
6 | | -import com.github.codeql.utils.Psi2IrFacade |
7 | | -import com.github.codeql.utils.versions.getPsi2Ir |
8 | | -import com.intellij.psi.PsiComment |
9 | | -import com.intellij.psi.PsiElement |
10 | | -import org.jetbrains.kotlin.config.KotlinCompilerVersion |
11 | 5 | import org.jetbrains.kotlin.ir.IrElement |
12 | 6 | import org.jetbrains.kotlin.ir.declarations.* |
13 | 7 | import org.jetbrains.kotlin.ir.expressions.IrBody |
14 | 8 | import org.jetbrains.kotlin.ir.expressions.IrExpression |
15 | | -import org.jetbrains.kotlin.ir.util.isAnonymousObject |
16 | 9 | import org.jetbrains.kotlin.ir.util.parentClassOrNull |
17 | | -import org.jetbrains.kotlin.kdoc.psi.api.KDoc |
18 | | -import org.jetbrains.kotlin.lexer.KtTokens |
19 | | -import org.jetbrains.kotlin.psi.KtVisitor |
20 | | -import org.jetbrains.kotlin.psi.psiUtil.endOffset |
21 | | -import org.jetbrains.kotlin.psi.psiUtil.startOffset |
22 | 10 |
|
23 | | -class CommentExtractor(private val fileExtractor: KotlinFileExtractor, private val file: IrFile, private val fileLabel: Label<out DbFile>) { |
24 | | - private val tw = fileExtractor.tw |
25 | | - private val logger = fileExtractor.logger |
| 11 | +open class CommentExtractor(protected val fileExtractor: KotlinFileExtractor, protected val file: IrFile, protected val fileLabel: Label<out DbFile>) { |
| 12 | + protected val tw = fileExtractor.tw |
| 13 | + protected val logger = fileExtractor.logger |
26 | 14 |
|
27 | | - fun extract() { |
28 | | - val psi2Ir = getPsi2Ir() |
29 | | - if (psi2Ir == null) { |
30 | | - logger.warn("Comments will not be extracted as Kotlin version is too old (${KotlinCompilerVersion.getVersion()})") |
31 | | - return |
| 15 | + protected fun getLabel(element: IrElement): Label<out DbTop>? { |
| 16 | + if (element == file) |
| 17 | + return fileLabel |
| 18 | + |
| 19 | + if (element is IrValueParameter && element.index == -1) { |
| 20 | + // Don't attribute comments to the implicit `this` parameter of a function. |
| 21 | + return null |
| 22 | + } |
| 23 | + |
| 24 | + val label: String |
| 25 | + val existingLabel = if (element is IrVariable) { |
| 26 | + // local variables are not named globally, so we need to get them from the variable label cache |
| 27 | + label = "variable ${element.name.asString()}" |
| 28 | + tw.getExistingVariableLabelFor(element) |
| 29 | + } else if (element is IrFunction && element.isLocalFunction()) { |
| 30 | + // local functions are not named globally, so we need to get them from the local function label cache |
| 31 | + label = "local function ${element.name.asString()}" |
| 32 | + fileExtractor.getExistingLocallyVisibleFunctionLabel(element) |
| 33 | + } else { |
| 34 | + label = getLabelForNamedElement(element) ?: return null |
| 35 | + tw.getExistingLabelFor<DbTop>(label) |
32 | 36 | } |
33 | | - val ktFile = psi2Ir.getKtFile(file) |
34 | | - if (ktFile == null) { |
35 | | - logger.warn("Comments are not being processed in ${file.path}.") |
36 | | - return |
| 37 | + if (existingLabel == null) { |
| 38 | + logger.warn("Couldn't get existing label for $label") |
| 39 | + return null |
37 | 40 | } |
38 | | - val commentVisitor = mkCommentVisitor(psi2Ir) |
39 | | - ktFile.accept(commentVisitor) |
| 41 | + return existingLabel |
40 | 42 | } |
41 | 43 |
|
42 | | - private fun mkCommentVisitor(psi2Ir: Psi2IrFacade): KtVisitor<Unit, Unit> = |
43 | | - object : KtVisitor<Unit, Unit>() { |
44 | | - override fun visitElement(element: PsiElement) { |
45 | | - element.acceptChildren(this) |
46 | | - |
47 | | - // Slightly hacky, but `visitComment` doesn't seem to visit comments with `tokenType` `KtTokens.DOC_COMMENT` |
48 | | - if (element is PsiComment){ |
49 | | - visitCommentElement(element) |
50 | | - } |
51 | | - } |
52 | | - |
53 | | - private fun visitCommentElement(comment: PsiComment) { |
54 | | - val type: CommentType = when (comment.tokenType) { |
55 | | - KtTokens.EOL_COMMENT -> { |
56 | | - CommentType.SingleLine |
57 | | - } |
58 | | - KtTokens.BLOCK_COMMENT -> { |
59 | | - CommentType.Block |
60 | | - } |
61 | | - KtTokens.DOC_COMMENT -> { |
62 | | - CommentType.Doc |
63 | | - } |
64 | | - else -> { |
65 | | - logger.warn("Unhandled comment token type: ${comment.tokenType}") |
66 | | - return |
67 | | - } |
68 | | - } |
69 | | - |
70 | | - val commentLabel = tw.getFreshIdLabel<DbKtcomment>() |
71 | | - tw.writeKtComments(commentLabel, type.value, comment.text) |
72 | | - val locId = tw.getLocation(comment.startOffset, comment.endOffset) |
73 | | - tw.writeHasLocation(commentLabel, locId) |
74 | | - |
75 | | - if (comment.tokenType != KtTokens.DOC_COMMENT) { |
76 | | - return |
77 | | - } |
78 | | - |
79 | | - if (comment !is KDoc) { |
80 | | - logger.warn("Unexpected comment type with DocComment token type.") |
81 | | - return |
82 | | - } |
83 | | - |
84 | | - for (sec in comment.getAllSections()) { |
85 | | - val commentSectionLabel = tw.getFreshIdLabel<DbKtcommentsection>() |
86 | | - tw.writeKtCommentSections(commentSectionLabel, commentLabel, sec.getContent()) |
87 | | - val name = sec.name |
88 | | - if (name != null) { |
89 | | - tw.writeKtCommentSectionNames(commentSectionLabel, name) |
90 | | - } |
91 | | - val subjectName = sec.getSubjectName() |
92 | | - if (subjectName != null) { |
93 | | - tw.writeKtCommentSectionSubjectNames(commentSectionLabel, subjectName) |
94 | | - } |
95 | | - } |
96 | | - |
97 | | - // Only storing the owner of doc comments: |
98 | | - val ownerPsi = getKDocOwner(comment) ?: return |
99 | | - |
100 | | - val owners = mutableListOf<IrElement>() |
101 | | - file.accept(IrVisitorLookup(psi2Ir, ownerPsi, file), owners) |
102 | | - |
103 | | - for (ownerIr in owners) { |
104 | | - val ownerLabel = getLabel(ownerIr) |
105 | | - if (ownerLabel != null) { |
106 | | - tw.writeKtCommentOwners(commentLabel, ownerLabel) |
107 | | - } |
108 | | - } |
109 | | - } |
110 | | - |
111 | | - private fun getKDocOwner(comment: KDoc) : PsiElement? { |
112 | | - val owner = comment.owner |
113 | | - if (owner == null) { |
114 | | - logger.warn("Couldn't get owner of KDoc. The comment is extracted without an owner.") |
115 | | - } |
116 | | - return owner |
117 | | - } |
118 | | - |
119 | | - private fun getLabel(element: IrElement): Label<out DbTop>? { |
120 | | - if (element == file) |
121 | | - return fileLabel |
122 | | - |
123 | | - if (element is IrValueParameter && element.index == -1) { |
124 | | - // Don't attribute comments to the implicit `this` parameter of a function. |
125 | | - return null |
126 | | - } |
127 | | - |
128 | | - val label: String |
129 | | - val existingLabel = if (element is IrVariable) { |
130 | | - // local variables are not named globally, so we need to get them from the variable label cache |
131 | | - label = "variable ${element.name.asString()}" |
132 | | - tw.getExistingVariableLabelFor(element) |
133 | | - } else if (element is IrFunction && element.isLocalFunction()) { |
134 | | - // local functions are not named globally, so we need to get them from the local function label cache |
135 | | - label = "local function ${element.name.asString()}" |
136 | | - fileExtractor.getExistingLocallyVisibleFunctionLabel(element) |
| 44 | + private fun getLabelForNamedElement(element: IrElement) : String? { |
| 45 | + when (element) { |
| 46 | + is IrClass -> return fileExtractor.getClassLabel(element, listOf()).classLabel |
| 47 | + is IrTypeParameter -> return fileExtractor.getTypeParameterLabel(element) |
| 48 | + is IrFunction -> { |
| 49 | + return if (element.isLocalFunction()) { |
| 50 | + null |
137 | 51 | } else { |
138 | | - label = getLabelForNamedElement(element) ?: return null |
139 | | - tw.getExistingLabelFor<DbTop>(label) |
| 52 | + fileExtractor.getFunctionLabel(element, null) |
140 | 53 | } |
141 | | - if (existingLabel == null) { |
142 | | - logger.warn("Couldn't get existing label for $label") |
| 54 | + } |
| 55 | + is IrValueParameter -> return fileExtractor.getValueParameterLabel(element, null) |
| 56 | + is IrProperty -> return fileExtractor.getPropertyLabel(element) |
| 57 | + is IrField -> return fileExtractor.getFieldLabel(element) |
| 58 | + is IrEnumEntry -> return fileExtractor.getEnumEntryLabel(element) |
| 59 | + is IrTypeAlias -> return fileExtractor.getTypeAliasLabel(element) |
| 60 | + |
| 61 | + is IrAnonymousInitializer -> { |
| 62 | + val parentClass = element.parentClassOrNull |
| 63 | + if (parentClass == null) { |
| 64 | + logger.warnElement("Parent of anonymous initializer is not a class", element) |
143 | 65 | return null |
144 | 66 | } |
145 | | - return existingLabel |
| 67 | + // Assign the comment to the class. The content of the `init` blocks might be extracted in multiple constructors. |
| 68 | + return getLabelForNamedElement(parentClass) |
146 | 69 | } |
147 | 70 |
|
148 | | - private fun getLabelForNamedElement(element: IrElement) : String? { |
149 | | - when (element) { |
150 | | - is IrClass -> return fileExtractor.getClassLabel(element, listOf()).classLabel |
151 | | - is IrTypeParameter -> return fileExtractor.getTypeParameterLabel(element) |
152 | | - is IrFunction -> { |
153 | | - return if (element.isLocalFunction()) { |
154 | | - null |
155 | | - } else { |
156 | | - fileExtractor.getFunctionLabel(element, null) |
157 | | - } |
158 | | - } |
159 | | - is IrValueParameter -> return fileExtractor.getValueParameterLabel(element, null) |
160 | | - is IrProperty -> return fileExtractor.getPropertyLabel(element) |
161 | | - is IrField -> return fileExtractor.getFieldLabel(element) |
162 | | - is IrEnumEntry -> return fileExtractor.getEnumEntryLabel(element) |
163 | | - is IrTypeAlias -> return fileExtractor.getTypeAliasLabel(element) |
| 71 | + // Fresh entities, not named elements: |
| 72 | + is IrBody -> return null |
| 73 | + is IrExpression -> return null |
164 | 74 |
|
165 | | - is IrAnonymousInitializer -> { |
166 | | - val parentClass = element.parentClassOrNull |
167 | | - if (parentClass == null) { |
168 | | - logger.warnElement("Parent of anonymous initializer is not a class", element) |
169 | | - return null |
170 | | - } |
171 | | - // Assign the comment to the class. The content of the `init` blocks might be extracted in multiple constructors. |
172 | | - return getLabelForNamedElement(parentClass) |
173 | | - } |
174 | | - |
175 | | - // Fresh entities, not named elements: |
176 | | - is IrBody -> return null |
177 | | - is IrExpression -> return null |
178 | | - |
179 | | - // todo add others: |
180 | | - else -> { |
181 | | - logger.warnElement("Unhandled element type found during comment extraction: ${element::class}", element) |
182 | | - return null |
183 | | - } |
184 | | - } |
| 75 | + // todo add others: |
| 76 | + else -> { |
| 77 | + logger.warnElement("Unhandled element type found during comment extraction: ${element::class}", element) |
| 78 | + return null |
| 79 | + } |
185 | 80 | } |
186 | 81 | } |
187 | 82 | } |
0 commit comments