[FIR LT] Make syntax error reporting in tests consistent with cli

This commit is contained in:
Kirill Rakhman
2023-04-21 12:03:40 +02:00
committed by Space Team
parent bdf0b41026
commit 6119606cb6
6 changed files with 39 additions and 100 deletions
@@ -6,14 +6,10 @@
package org.jetbrains.kotlin.test.services package org.jetbrains.kotlin.test.services
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.KtInMemoryTextSourceFile import org.jetbrains.kotlin.KtInMemoryTextSourceFile
import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir import org.jetbrains.kotlin.fir.lightTree.LightTree2Fir
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.sourceFiles.LightTreeFile import org.jetbrains.kotlin.sourceFiles.LightTreeFile
import org.jetbrains.kotlin.test.model.TestFile import org.jetbrains.kotlin.test.model.TestFile
@@ -115,13 +111,15 @@ fun SourceFileProvider.getKtFilesForSourceFiles(testFiles: Collection<TestFile>,
} }
fun SourceFileProvider.getLightTreeKtFileForSourceFile(testFile: TestFile): LightTreeFile { fun SourceFileProvider.getLightTreeKtFileForSourceFile(testFile: TestFile): LightTreeFile {
val shortName = testFile.name.substringAfterLast('/').substringAfterLast('\\') val shortName = testFile.toLightTreeShortName()
val sourceFile = KtInMemoryTextSourceFile(shortName, "/$shortName", getContentOfSourceFile(testFile)) val sourceFile = KtInMemoryTextSourceFile(shortName, "/$shortName", getContentOfSourceFile(testFile))
val linesMapping = sourceFile.text.toSourceLinesMapping() val linesMapping = sourceFile.text.toSourceLinesMapping()
val lightTree = LightTree2Fir.buildLightTree(sourceFile.text) val lightTree = LightTree2Fir.buildLightTree(sourceFile.text)
return LightTreeFile(lightTree, sourceFile, linesMapping) return LightTreeFile(lightTree, sourceFile, linesMapping)
} }
fun TestFile.toLightTreeShortName() = name.substringAfterLast('/').substringAfterLast('\\')
fun SourceFileProvider.getLightTreeFilesForSourceFiles(testFiles: Collection<TestFile>): Map<TestFile, LightTreeFile> { fun SourceFileProvider.getLightTreeFilesForSourceFiles(testFiles: Collection<TestFile>): Map<TestFile, LightTreeFile> {
return testFiles.mapNotNull { return testFiles.mapNotNull {
if (!it.isKtFile) return@mapNotNull null if (!it.isKtFile) return@mapNotNull null
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.test.getAnalyzerServices
import org.jetbrains.kotlin.test.model.FrontendFacade import org.jetbrains.kotlin.test.model.FrontendFacade
import org.jetbrains.kotlin.test.model.FrontendKinds import org.jetbrains.kotlin.test.model.FrontendKinds
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.runners.lightTreeSyntaxDiagnosticsReporterHolder
import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.services.*
import java.nio.file.Paths import java.nio.file.Paths
@@ -319,6 +320,7 @@ open class FirFrontendFacade(
IrGenerationExtension.getInstances(project), IrGenerationExtension.getInstances(project),
parser, parser,
enablePluginPhases, enablePluginPhases,
testServices.lightTreeSyntaxDiagnosticsReporterHolder?.reporter,
) )
val firFiles = firAnalyzerFacade.runResolution() val firFiles = firAnalyzerFacade.runResolution()
val filesMap = firFiles.mapNotNull { firFile -> val filesMap = firFiles.mapNotNull { firFile ->
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.test.frontend.fir.handlers package org.jetbrains.kotlin.test.frontend.fir.handlers
import com.intellij.lang.impl.PsiBuilderImpl
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.* import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory0 import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory0
@@ -46,6 +45,7 @@ import org.jetbrains.kotlin.test.directives.model.singleValue
import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact
import org.jetbrains.kotlin.test.model.TestFile import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.runners.lightTreeSyntaxDiagnosticsReporterHolder
import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.utils.AbstractTwoAttributesMetaInfoProcessor import org.jetbrains.kotlin.test.utils.AbstractTwoAttributesMetaInfoProcessor
import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -118,20 +118,20 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
) )
} }
} else { } else {
collectLightTreeSyntaxErrors(firFile).flatMap { sourceElement -> testServices.lightTreeSyntaxDiagnosticsReporterHolder
FirSyntaxErrors.SYNTAX.on( ?.reporter
sourceElement, ?.diagnosticsByFilePath
PsiBuilderImpl.getErrorMessage(sourceElement.lighterASTNode) ?: "Syntax error", ?.get("/${testFile.toLightTreeShortName()}")
positioningStrategy = null ?.flatMap {
).toMetaInfos( it.toMetaInfos(
module, module,
testFile, testFile,
globalMetadataInfoHandler1 = globalMetadataInfoHandler, globalMetadataInfoHandler1 = globalMetadataInfoHandler,
lightTreeEnabled, lightTreeEnabled,
lightTreeComparingModeEnabled, lightTreeComparingModeEnabled,
forceRenderArguments, forceRenderArguments,
) )
} }.orEmpty()
} }
globalMetadataInfoHandler.addMetadataInfosForFile(testFile, metaInfos) globalMetadataInfoHandler.addMetadataInfosForFile(testFile, metaInfos)
@@ -1,76 +0,0 @@
/*
* Copyright 2010-2021 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.test.frontend.fir.handlers
import com.intellij.lang.LighterASTNode
import com.intellij.psi.TokenType
import com.intellij.util.diff.FlyweightCapableTreeStructure
import org.jetbrains.kotlin.KtLightSourceElement
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.visitors.FirVisitor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.toKtLightSourceElement
import org.jetbrains.kotlin.util.getChildren
private typealias Tree = FlyweightCapableTreeStructure<LighterASTNode>
private class LightTreeErrorsCollector(private val tree: Tree) {
private inline fun LighterASTNode.forEachChildren(f: (LighterASTNode) -> Unit) {
val kidsArray = this.getChildren(tree)
for (kid in kidsArray) {
val tokenType = kid.tokenType
if (KtTokens.COMMENTS.contains(tokenType) || tokenType == KtTokens.WHITE_SPACE || tokenType == KtTokens.SEMICOLON) continue
f(kid)
}
}
fun collectErrorNodes(node: LighterASTNode, acc: MutableList<LighterASTNode> = mutableListOf()): List<LighterASTNode> {
if (node.tokenType == TokenType.ERROR_ELEMENT) {
acc.add(node)
} else {
node.forEachChildren { child ->
collectErrorNodes(child, acc)
}
}
return acc
}
}
private data class TreeWithOffset(val tree: Tree, val offset: Int)
private data class VisitorState(
val lastTree: Tree? = null,
val visitedTrees: MutableSet<Tree> = mutableSetOf(),
val result: MutableList<TreeWithOffset> = mutableListOf()
)
private object FirTreesExtractVisitor : FirVisitor<Unit, VisitorState>() {
override fun visitElement(element: FirElement, data: VisitorState) {
val source = element.source ?: return
val currentTree = source.treeStructure
val (lastTree, visitedTrees, result) = data
val newData = if (lastTree !== currentTree && visitedTrees.add(currentTree)) {
val newTreeWithOffset = TreeWithOffset(currentTree, element.source?.startOffset ?: 0)
result.add(newTreeWithOffset)
data.copy(lastTree = lastTree)
} else {
data
}
element.acceptChildren(this, newData)
}
}
internal fun collectLightTreeSyntaxErrors(file: FirFile): List<KtLightSourceElement> {
val state = VisitorState()
file.accept(FirTreesExtractVisitor,state)
return state.result.flatMap { (tree, offset) ->
LightTreeErrorsCollector(tree).collectErrorNodes(tree.root).map {
it.toKtLightSourceElement(tree, startOffset = it.startOffset + offset, endOffset = it.endOffset + offset)
}
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.test.runners package org.jetbrains.kotlin.test.runners
import org.jetbrains.kotlin.config.ExplicitApiMode import org.jetbrains.kotlin.config.ExplicitApiMode
import org.jetbrains.kotlin.diagnostics.impl.SimpleDiagnosticsCollector
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.symbols.FirLazyDeclarationResolver import org.jetbrains.kotlin.fir.symbols.FirLazyDeclarationResolver
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
@@ -39,6 +40,8 @@ import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSource
import org.jetbrains.kotlin.test.FirParser import org.jetbrains.kotlin.test.FirParser
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.services.JsLibraryProvider import org.jetbrains.kotlin.test.services.JsLibraryProvider
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractFirDiagnosticTestBase(val parser: FirParser) : AbstractKotlinCompilerTest() { abstract class AbstractFirDiagnosticTestBase(val parser: FirParser) : AbstractKotlinCompilerTest() {
override fun TestConfigurationBuilder.configuration() { override fun TestConfigurationBuilder.configuration() {
@@ -49,12 +52,23 @@ abstract class AbstractFirDiagnosticTestBase(val parser: FirParser) : AbstractKo
} }
abstract class AbstractFirPsiDiagnosticTest : AbstractFirDiagnosticTestBase(FirParser.Psi) abstract class AbstractFirPsiDiagnosticTest : AbstractFirDiagnosticTestBase(FirParser.Psi)
abstract class AbstractFirLightTreeDiagnosticsTest : AbstractFirDiagnosticTestBase(FirParser.LightTree) abstract class AbstractFirLightTreeDiagnosticsTest : AbstractFirDiagnosticTestBase(FirParser.LightTree) {
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
builder.useAdditionalService { LightTreeSyntaxDiagnosticsReporterHolder() }
}
}
class LightTreeSyntaxDiagnosticsReporterHolder : TestService {
val reporter = SimpleDiagnosticsCollector()
}
val TestServices.lightTreeSyntaxDiagnosticsReporterHolder: LightTreeSyntaxDiagnosticsReporterHolder? by TestServices.nullableTestServiceAccessor()
abstract class AbstractFirWithActualizerDiagnosticsTest(val parser: FirParser) : AbstractKotlinCompilerWithTargetBackendTest(TargetBackend.JVM_IR) { abstract class AbstractFirWithActualizerDiagnosticsTest(val parser: FirParser) : AbstractKotlinCompilerWithTargetBackendTest(TargetBackend.JVM_IR) {
override fun configure(builder: TestConfigurationBuilder) { override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder) super.configure(builder)
with (builder) { with(builder) {
defaultDirectives { defaultDirectives {
+CodegenTestDirectives.IGNORE_FIR2IR_EXCEPTIONS_IF_FIR_CONTAINS_ERRORS +CodegenTestDirectives.IGNORE_FIR2IR_EXCEPTIONS_IF_FIR_CONTAINS_ERRORS
} }
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.fir
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.diagnostics.KtDiagnostic import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.fir.analysis.collectors.FirDiagnosticsCollector import org.jetbrains.kotlin.fir.analysis.collectors.FirDiagnosticsCollector
@@ -48,6 +48,7 @@ class FirAnalyzerFacade(
val irGeneratorExtensions: Collection<IrGenerationExtension>, val irGeneratorExtensions: Collection<IrGenerationExtension>,
val parser: FirParser, val parser: FirParser,
val enablePluginPhases: Boolean = false, val enablePluginPhases: Boolean = false,
val diagnosticReporterForLightTree: DiagnosticReporter? = null
) : AbstractFirAnalyzerFacade() { ) : AbstractFirAnalyzerFacade() {
private var firFiles: List<FirFile>? = null private var firFiles: List<FirFile>? = null
private var _scopeSession: ScopeSession? = null private var _scopeSession: ScopeSession? = null
@@ -61,7 +62,7 @@ class FirAnalyzerFacade(
val firProvider = (session.firProvider as FirProviderImpl) val firProvider = (session.firProvider as FirProviderImpl)
firFiles = when (parser) { firFiles = when (parser) {
FirParser.LightTree -> { FirParser.LightTree -> {
val builder = LightTree2Fir(session, firProvider.kotlinScopeProvider) val builder = LightTree2Fir(session, firProvider.kotlinScopeProvider, diagnosticReporterForLightTree)
lightTreeFiles.map { lightTreeFiles.map {
builder.buildFirFile(it.lightTree, it.sourceFile, it.linesMapping).also { firFile -> builder.buildFirFile(it.lightTree, it.sourceFile, it.linesMapping).also { firFile ->
firProvider.recordFile(firFile) firProvider.recordFile(firFile)