Coverage Summary for Class: IrKDocUtilKt (com.kotlinorm.compiler.plugin.utils)
Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
IrKDocUtilKt |
100%
(1/1)
|
100%
(5/5)
|
78.4%
(58/74)
|
95.9%
(70/73)
|
97.9%
(460/470)
|
/**
* Copyright 2022-2025 kronos-orm
*
* 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 com.kotlinorm.compiler.plugin.utils
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.sourceElement
import java.io.File
import kotlin.text.Charsets.UTF_8
/**
* Calculates the real start offset of the source code, skipping over comments and annotations.
*
* This function iterates through the source code lines starting from the given offset and skips
* lines that are annotations, single-line comments, or multi-line comments. It returns the first
* line that is not a comment or annotation.
*
* @param source The list of source code lines.
* @param startOffset The initial offset to start checking from.
* @return The real start offset after skipping comments and annotations.
*/
fun realStartOffset(source: List<String>, startOffset: Int): Int {
var realStartOffset = startOffset
var multiLineCommentFlag = false
while (
source[realStartOffset].trimStart().startsWith("@") ||
source[realStartOffset].trimStart().startsWith("//") ||
source[realStartOffset].trimStart().startsWith("/*") ||
multiLineCommentFlag
) {
if (source[realStartOffset].trimStart().startsWith("/*")) {
multiLineCommentFlag = true
}
if (source[realStartOffset].trimStart().endsWith("*/")) {
multiLineCommentFlag = false
}
realStartOffset++
}
return realStartOffset
}
internal const val SOURCE_FILE_CACHE_SIZE = 128
internal val sourceFileCache: LRUCache<String, List<String>> = LRUCache(SOURCE_FILE_CACHE_SIZE)
/**
* Extract the comment content within the specified range.
*
* This function will extract single-line or multi-line comments within the specified range from the given list of lines.
* If no comments are found within the specified range, the function will search upwards to the beginning of the file to find possible comments.
*
* 提取指定范围内的注释内容。
*
* 此函数会从给定的行列表中提取位于指定范围内的单行或多行注释。
* 如果在指定范围内没有找到注释,函数将向上查找直到文件的开头,以获取可能的注释。
*
* @param lines a list of code lines 包含代码行的列表
* @param range the range of lines to check 指定要检查的行范围
* @return the extracted comment content, or null if no comment is found 找到的注释内容,如果没有找到则返回 null
*/
fun extractDeclarationComment(lines: List<String>, range: IntRange): String? {
val startIndex = range.first
val endIndex = range.last
var comment: String? = null
val commentIgnore = { char: Char -> char == '*' || char == ' ' }
// Find single-line or multi-line comments within the specified range
// 在指定范围内查找单行或多行注释
for (i in startIndex..endIndex) {
val line = lines.getOrNull(i)?.trim() ?: continue
// Extract single-line comments
// 提取单行注释
val singleLineComment = line.substringAfter("//", "").substringBefore("//").trim()
// Extract multi-line comments
// 查找多行注释的起始和结束位置
val multiLineCommentStart = line.indexOf("/*")
val multiLineCommentEnd = line.indexOf("*/")
// If a single-line comment is found
// 如果找到单行注释
if (singleLineComment.isNotEmpty()) {
comment = singleLineComment
break
}
// If a multi-line comment is found
// 如果找到多行注释
else if (multiLineCommentStart != -1 && multiLineCommentEnd != -1) {
comment = line.substring(multiLineCommentStart + 2, multiLineCommentEnd).trim(commentIgnore)
break
}
}
// If no comments are found within the specified range, search upwards
// 如果在指定范围内没有找到注释,向上查找
if (comment == null) {
var multiLineCommentFlag = false
var singleLineCommentFlag = false
for (i in (startIndex - 1) downTo 0) {
val line = lines.getOrNull(i)?.trim() ?: continue
if (line.startsWith("@")) continue
val singleLineComment = line.substringAfter("//", "").trim()
val multiLineCommentStart = line.indexOf("/*")
val multiLineCommentEnd = line.indexOf("*/")
// Handle single-line comments
// 处理单行注释
if (line.startsWith("//") && singleLineComment.isNotEmpty()) {
if (singleLineCommentFlag) {
comment = singleLineComment + comment
} else {
comment = singleLineComment
singleLineCommentFlag = true
}
continue
}
// Handle multi-line comments
// 处理多行注释
else if (multiLineCommentStart != -1 && multiLineCommentEnd != -1) {
comment = line.substring(multiLineCommentStart + 2, multiLineCommentEnd).trim(commentIgnore)
break
}
// Handle multi-line comments that start but do not end
// 处理多行注释开始但未结束的情况
else if (multiLineCommentStart != -1 && multiLineCommentFlag) {
comment = line.substring(multiLineCommentStart + 2).trim(commentIgnore) + comment
break
}
// Handle multi-line comments that end but do not start
// 处理多行注释结束的情况
else if (multiLineCommentEnd != -1) {
comment = line.substring(0, multiLineCommentEnd).trim(commentIgnore)
multiLineCommentFlag = true
continue
}
// Handle non-empty lines
// 处理非空行
else if (line.isNotBlank()) {
if (multiLineCommentFlag) {
comment = line.trim(commentIgnore) + comment
continue
}
if (line.startsWith("@")) { // 如果是注解
continue
}
break
}
}
}
return comment
}
/**
* Retrieves the KDoc string for the current IR declaration.
*
* This function attempts to extract the KDoc comment associated with the current IR declaration.
* It uses the source offsets to locate the relevant lines in the source file and then extracts
* the comment content.
*
* @receiver The IR declaration for which to retrieve the KDoc string.
* @return An IR expression containing the KDoc string, or null if no KDoc comment is found.
*/
context(builder: IrBuilderWithScope)
fun IrDeclaration.getKDocString(): IrExpression {
val declaration = this
val sourceOffsets = sourceElement()
if (sourceOffsets != null) {
val startOffset = sourceOffsets.startOffset
val endOffset = sourceOffsets.endOffset
val fileEntry = file.fileEntry
val sourceRange = fileEntry.getSourceRangeInfo(startOffset, endOffset)
val source = sourceFileCache.getOrPut(fileEntry.name) {
File(sourceRange.filePath).readLines(UTF_8)
}
val realStartOffset = realStartOffset(source, sourceRange.startLineNumber)
val comment = when (declaration) {
is IrProperty -> extractDeclarationComment(
source,
realStartOffset..sourceRange.endLineNumber
)
is IrClass -> extractDeclarationComment(
source,
sourceRange.startLineNumber..realStartOffset
)
else -> null
}
if (comment != null) {
return builder.irString(comment)
}
}
return builder.irString("")
}