FIR LT: Introduce source file abstraction, carry it from parsing to IR
along with source lines mapping, allows to "emulate" usage of the PSI files which allows to extract source file and line mapping info on every stage from source element. It makes sense to use this mapping for the error reporting too.
This commit is contained in:
@@ -14,9 +14,6 @@ import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import com.intellij.util.diff.FlyweightCapableTreeStructure
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
|
||||
sealed class KtSourceElementKind
|
||||
|
||||
@@ -444,90 +441,3 @@ inline fun LighterASTNode.toKtLightSourceElement(
|
||||
endOffset: Int = this.endOffset
|
||||
): KtLightSourceElement = KtLightSourceElement(this, startOffset, endOffset, tree, kind)
|
||||
|
||||
|
||||
class KtSourceFilePosition(val line: Int, val column: Int, val lineContent: String?) {
|
||||
|
||||
// NOTE: This method is used for presenting positions to the user
|
||||
override fun toString(): String = if (line < 0) "(offset: $column line unknown)" else "($line,$column)"
|
||||
|
||||
companion object {
|
||||
val NONE = KtSourceFilePosition(-1, -1, null)
|
||||
}
|
||||
}
|
||||
|
||||
class SequentialFilePositionFinder(file: File) : Closeable {
|
||||
|
||||
private var reader: InputStreamReader = file.reader(/* TODO: select proper charset */)
|
||||
|
||||
private var currentLineContent: String? = null
|
||||
private val buffer = CharArray(255)
|
||||
private var bufLength = -1
|
||||
private var bufPos = 0
|
||||
private var endOfStream = false
|
||||
private var skipNextLf = false
|
||||
|
||||
private var charsRead = 0
|
||||
private var currentLine = 0
|
||||
|
||||
// assuming that if called multiple times, calls should be sorted by ascending offset
|
||||
fun findNextPosition(offset: Int, withLineContents: Boolean = true): KtSourceFilePosition {
|
||||
assert(offset >= charsRead - (currentLineContent?.length ?: 0))
|
||||
|
||||
fun posInCurrentLine(): KtSourceFilePosition? {
|
||||
val col = offset - (charsRead - currentLineContent!!.length - 1)/* beginning of line offset */ + 1 /* col is 1-based */
|
||||
return if (col <= currentLineContent!!.length)
|
||||
KtSourceFilePosition(currentLine, col, if (withLineContents) currentLineContent else null)
|
||||
else null
|
||||
}
|
||||
|
||||
if (offset < charsRead) {
|
||||
return posInCurrentLine()!!
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (currentLineContent == null) {
|
||||
currentLineContent = readNextLine()
|
||||
}
|
||||
|
||||
posInCurrentLine()?.let { return@findNextPosition it }
|
||||
|
||||
if (endOfStream) return KtSourceFilePosition(-1, offset, if (withLineContents) currentLineContent else null)
|
||||
|
||||
currentLineContent = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun readNextLine() = buildString {
|
||||
while (true) {
|
||||
if (bufPos >= bufLength) {
|
||||
bufLength = reader.read(buffer)
|
||||
bufPos = 0
|
||||
if (bufLength < 0) {
|
||||
endOfStream = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
val c = buffer[bufPos++]
|
||||
charsRead++
|
||||
when {
|
||||
c == '\n' && skipNextLf -> {
|
||||
skipNextLf = false
|
||||
}
|
||||
c == '\n' || c == '\r' -> {
|
||||
currentLine++
|
||||
skipNextLf = c == '\r'
|
||||
break
|
||||
}
|
||||
else -> {
|
||||
append(c)
|
||||
skipNextLf = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
reader.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
interface KtSourceFile {
|
||||
val name: String
|
||||
val path: String?
|
||||
|
||||
fun getContentsAsStream(): InputStream
|
||||
}
|
||||
|
||||
class KtPsiSourceFile(val psiFile: PsiFile) : KtSourceFile {
|
||||
override val name: String
|
||||
get() = psiFile.name
|
||||
|
||||
override val path: String?
|
||||
get() = psiFile.virtualFile?.path
|
||||
|
||||
override fun getContentsAsStream(): InputStream = psiFile.virtualFile.inputStream
|
||||
}
|
||||
|
||||
class KtVirtualFileSourceFile(val virtualFile: VirtualFile) : KtSourceFile {
|
||||
override val name: String
|
||||
get() = virtualFile.name
|
||||
|
||||
override val path: String
|
||||
get() = virtualFile.path
|
||||
|
||||
override fun getContentsAsStream(): InputStream = virtualFile.inputStream
|
||||
}
|
||||
|
||||
class KtIoFileSourceFile(val file: File) : KtSourceFile {
|
||||
override val name: String
|
||||
get() = file.name
|
||||
override val path: String
|
||||
get() = FileUtilRt.toSystemIndependentName(file.path)
|
||||
|
||||
override fun getContentsAsStream(): InputStream = file.inputStream()
|
||||
}
|
||||
|
||||
class KtInMemoryTextSourceFile(
|
||||
override val name: String,
|
||||
override val path: String?,
|
||||
val text: CharSequence
|
||||
) : KtSourceFile {
|
||||
override fun getContentsAsStream(): InputStream = ByteArrayInputStream(text.toString().toByteArray())
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin
|
||||
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.psi.PsiFile
|
||||
import java.io.InputStreamReader
|
||||
|
||||
interface KtSourceFileLinesMapping {
|
||||
fun getLineStartOffset(line: Int): Int
|
||||
fun getLineAndColumnByOffset(offset: Int): Pair<Int, Int>
|
||||
fun getLineByOffset(offset: Int): Int
|
||||
|
||||
val lastOffset: Int
|
||||
val linesCount: Int
|
||||
}
|
||||
|
||||
class KtPsiSourceFileLinesMapping(val psiFile: PsiFile) : KtSourceFileLinesMapping {
|
||||
private val document: Document? by lazy { psiFile.viewProvider.document }
|
||||
|
||||
override fun getLineStartOffset(line: Int): Int =
|
||||
document?.getLineStartOffset(line) ?: -1
|
||||
|
||||
override fun getLineAndColumnByOffset(offset: Int): Pair<Int, Int> =
|
||||
document?.let {
|
||||
val lineNumber = it.getLineNumber(offset)
|
||||
val lineStartOffset = it.getLineStartOffset(lineNumber)
|
||||
lineNumber to offset - lineStartOffset
|
||||
} ?: (-1 to -1)
|
||||
|
||||
override fun getLineByOffset(offset: Int): Int =
|
||||
document?.getLineNumber(offset) ?: -1
|
||||
|
||||
override val lastOffset: Int
|
||||
get() = document?.textLength ?: -1
|
||||
|
||||
override val linesCount: Int
|
||||
get() = document?.lineCount ?: 0
|
||||
}
|
||||
|
||||
open class KtSourceFileLinesMappingFromLineStartOffsets(
|
||||
val lineStartOffsets: IntArray, override val lastOffset: Int
|
||||
) : KtSourceFileLinesMapping {
|
||||
override fun getLineStartOffset(line: Int): Int = lineStartOffsets[line]
|
||||
|
||||
override fun getLineAndColumnByOffset(offset: Int): Pair<Int, Int> {
|
||||
val lineNumber = getLineByOffset(offset)
|
||||
if (lineNumber < 0) return -1 to -1
|
||||
val lineStartOffset = lineStartOffsets[lineNumber]
|
||||
return lineNumber to offset - lineStartOffset
|
||||
}
|
||||
|
||||
override fun getLineByOffset(offset: Int): Int {
|
||||
val index = lineStartOffsets.binarySearch(offset)
|
||||
return if (index >= 0) index else -index - 2
|
||||
}
|
||||
|
||||
override val linesCount: Int
|
||||
get() = lineStartOffsets.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads file contents from reader, converts line separators and calculates source lines to file offsets mapping
|
||||
*
|
||||
* Returns KtSourceFileLinesMapping and char sequence (StringBuilder to avoid premature copying) containing converted text
|
||||
* The separators are converted similarly to the com.intellij.openapi.util.text.StringUtilRt algorithms
|
||||
*/
|
||||
fun InputStreamReader.readSourceFileWithMapping(): Pair<CharSequence, KtSourceFileLinesMapping> {
|
||||
val buffer = CharArray(255)
|
||||
var bufLength = -1
|
||||
var bufPos = 0
|
||||
var skipNextLf = false
|
||||
|
||||
var charsRead = 0
|
||||
|
||||
val lineOffsets = mutableListOf(0) // TODO: consider using implicit first line offset (needs to be handled properly in IR)
|
||||
val sb = StringBuilder()
|
||||
|
||||
while (true) {
|
||||
if (bufPos >= bufLength) {
|
||||
bufLength = read(buffer)
|
||||
bufPos = 0
|
||||
if (bufLength < 0) {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
val c = buffer[bufPos++]
|
||||
charsRead++
|
||||
when {
|
||||
c == '\n' && skipNextLf -> {
|
||||
lineOffsets[lineOffsets.size - 1] = charsRead
|
||||
skipNextLf = false
|
||||
}
|
||||
c == '\n' || c == '\r' -> {
|
||||
sb.append('\n')
|
||||
lineOffsets.add(charsRead)
|
||||
skipNextLf = c == '\r'
|
||||
}
|
||||
else -> {
|
||||
sb.append(c)
|
||||
skipNextLf = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb to KtSourceFileLinesMappingFromLineStartOffsets(lineOffsets.toIntArray(), charsRead)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts source lines to offsets mapping from text
|
||||
*
|
||||
* intended for using mainly in tests, so no care is taken about performance or possible corner cases
|
||||
*/
|
||||
fun CharSequence.toSourceLinesMapping(): KtSourceFileLinesMapping {
|
||||
val lineOffsets = mutableListOf(0)
|
||||
var offset = 0
|
||||
for (c in this) {
|
||||
offset++
|
||||
if (c == '\n') lineOffsets.add(offset)
|
||||
}
|
||||
return KtSourceFileLinesMappingFromLineStartOffsets(lineOffsets.toIntArray(), offset)
|
||||
}
|
||||
Reference in New Issue
Block a user