diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerDebugInfoReporter.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerDebugInfoReporter.kt new file mode 100644 index 00000000000..32047d67711 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/CheckerDebugInfoReporter.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers + +import org.jetbrains.kotlin.checkers.diagnostics.ActualDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.DebugInfoDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory0 +import org.jetbrains.kotlin.checkers.utils.DebugInfoUtil +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtReferenceExpression + +class CheckerDebugInfoReporter( + private val dynamicCallDescriptors: MutableList, + private val markDynamicCalls: Boolean, + private val debugAnnotations: MutableList, + private val withNewInference: Boolean, + private val platform: String? +) : DebugInfoUtil.DebugInfoReporter() { + override fun reportElementWithErrorType(expression: KtReferenceExpression) { + newDiagnostic( + expression, + DebugInfoDiagnosticFactory0.ELEMENT_WITH_ERROR_TYPE + ) + } + + override fun reportMissingUnresolved(expression: KtReferenceExpression) { + newDiagnostic( + expression, + DebugInfoDiagnosticFactory0.MISSING_UNRESOLVED + ) + } + + override fun reportUnresolvedWithTarget( + expression: KtReferenceExpression, + target: String + ) { + newDiagnostic(expression, DebugInfoDiagnosticFactory0.UNRESOLVED_WITH_TARGET) + } + + override fun reportDynamicCall( + element: KtElement, + declarationDescriptor: DeclarationDescriptor + ) { + dynamicCallDescriptors.add(declarationDescriptor) + + if (markDynamicCalls) { + newDiagnostic(element, DebugInfoDiagnosticFactory0.DYNAMIC) + } + } + + private fun newDiagnostic( + element: KtElement, + factory: DebugInfoDiagnosticFactory0 + ) { + debugAnnotations.add( + ActualDiagnostic( + DebugInfoDiagnostic(element, factory), platform, withNewInference + ) + ) + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosedRange.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosedRange.kt new file mode 100644 index 00000000000..a53c0811620 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosedRange.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers + +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.checkers.diagnostics.TextDiagnostic + +class DiagnosedRange constructor(val start: Int) { + var end: Int = 0 + private val diagnostics = ContainerUtil.newSmartList() + + fun getDiagnostics(): List { + return diagnostics + } + + fun addDiagnostic(diagnostic: String) { + diagnostics.add(TextDiagnostic.parseDiagnostic(diagnostic)) + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosticDescriptors.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosticDescriptors.kt new file mode 100644 index 00000000000..0a97ba450a8 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosticDescriptors.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers + +import com.google.common.collect.Maps +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.checkers.diagnostics.AbstractTestDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.PositionalTextDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.TextDiagnostic + +abstract class AbstractDiagnosticDescriptor internal constructor(val start: Int, val end: Int) { + val textRange: TextRange + get() = TextRange(start, end) +} + +class ActualDiagnosticDescriptor internal constructor(start: Int, end: Int, val diagnostics: List) : + AbstractDiagnosticDescriptor(start, end) { + + val textDiagnosticsMap: MutableMap + get() { + val diagnosticMap = Maps.newLinkedHashMap() + for (diagnostic in diagnostics) { + diagnosticMap[diagnostic] = TextDiagnostic.asTextDiagnostic(diagnostic) + } + + return diagnosticMap + } +} + +class TextDiagnosticDescriptor internal constructor(private val positionalTextDiagnostic: PositionalTextDiagnostic) : + AbstractDiagnosticDescriptor(positionalTextDiagnostic.start, positionalTextDiagnostic.end) { + + val textDiagnostic: TextDiagnostic + get() = positionalTextDiagnostic.diagnostic +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosticDiffCallbacks.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosticDiffCallbacks.kt new file mode 100644 index 00000000000..a18ab2a075b --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/DiagnosticDiffCallbacks.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers + +import org.jetbrains.kotlin.checkers.diagnostics.TextDiagnostic + +interface DiagnosticDiffCallbacks { + fun missingDiagnostic(diagnostic: TextDiagnostic, expectedStart: Int, expectedEnd: Int) + + fun wrongParametersDiagnostic(expectedDiagnostic: TextDiagnostic, actualDiagnostic: TextDiagnostic, start: Int, end: Int) + + fun unexpectedDiagnostic(diagnostic: TextDiagnostic, actualStart: Int, actualEnd: Int) +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/AbstractTestDiagnostic.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/AbstractTestDiagnostic.kt new file mode 100644 index 00000000000..1c5a183183a --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/AbstractTestDiagnostic.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics + +interface AbstractTestDiagnostic : Comparable { + val name: String + + val platform: String? + + val inferenceCompatibility: TextDiagnostic.InferenceCompatibility + + fun enhanceInferenceCompatibility(inferenceCompatibility: TextDiagnostic.InferenceCompatibility) + + override fun compareTo(other: AbstractTestDiagnostic): Int +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/ActualDiagnostic.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/ActualDiagnostic.kt new file mode 100644 index 00000000000..92e6c7a2f44 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/ActualDiagnostic.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics + +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1 + +class ActualDiagnostic constructor(val diagnostic: Diagnostic, override val platform: String?, withNewInference: Boolean) : + AbstractTestDiagnostic { + override var inferenceCompatibility = if (withNewInference) + TextDiagnostic.InferenceCompatibility.NEW + else + TextDiagnostic.InferenceCompatibility.OLD + + override val name: String + get() = diagnostic.factory.name + + val file: PsiFile + get() = diagnostic.psiFile + + override fun compareTo(other: AbstractTestDiagnostic): Int { + return if (this.diagnostic is DiagnosticWithParameters1<*, *> && other is ActualDiagnostic && other.diagnostic is DiagnosticWithParameters1<*, *>) { + (name + this.diagnostic.a).compareTo(other.name + other.diagnostic.a) + } else if (this.diagnostic is DiagnosticWithParameters1<*, *>) { + (name + this.diagnostic.a).compareTo(other.name) + } else if (other is ActualDiagnostic && other.diagnostic is DiagnosticWithParameters1<*, *>) { + name.compareTo(other.name + other.diagnostic.a) + } else { + name.compareTo(other.name) + } + } + + override fun enhanceInferenceCompatibility(inferenceCompatibility: TextDiagnostic.InferenceCompatibility) { + this.inferenceCompatibility = inferenceCompatibility + } + + override fun equals(other: Any?): Boolean { + if (other !is ActualDiagnostic) return false + + // '==' on diagnostics is intentional here + return other.diagnostic === diagnostic && + (if (other.platform == null) platform == null else other.platform == platform) && + other.inferenceCompatibility == inferenceCompatibility + } + + override fun hashCode(): Int { + var result = System.identityHashCode(diagnostic) + result = 31 * result + (platform?.hashCode() ?: 0) + result = 31 * result + inferenceCompatibility.hashCode() + return result + } + + override fun toString(): String { + val inferenceAbbreviation = inferenceCompatibility.abbreviation + return (if (inferenceAbbreviation != null) inferenceAbbreviation + ";" else "") + + (if (platform != null) "$platform:" else "") + + diagnostic.toString() + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/DiagnosticForTests.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/DiagnosticForTests.kt new file mode 100644 index 00000000000..1219ffdc8ef --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/DiagnosticForTests.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics + +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiErrorElement +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.checkers.diagnostics.factories.SyntaxErrorDiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Severity +import org.jetbrains.kotlin.psi.KtElement + +class DebugInfoDiagnostic(element: KtElement, factory: DiagnosticFactory<*>) : AbstractDiagnosticForTests(element, factory) +class SyntaxErrorDiagnostic(errorElement: PsiErrorElement) : AbstractDiagnosticForTests(errorElement, + SyntaxErrorDiagnosticFactory.INSTANCE +) + +open class AbstractDiagnosticForTests(private val element: PsiElement, private val factory: DiagnosticFactory<*>) : Diagnostic { + override fun getFactory(): DiagnosticFactory<*> { + return factory + } + + override fun getSeverity(): Severity { + return Severity.ERROR + } + + override fun getPsiElement(): PsiElement { + return element + } + + override fun getTextRanges(): List { + return listOf(element.textRange) + } + + override fun getPsiFile(): PsiFile { + return element.containingFile + } + + override fun isValid(): Boolean { + return true + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/PositionalTextDiagnostic.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/PositionalTextDiagnostic.kt index 10fe9c36acb..beb0ae8c486 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/PositionalTextDiagnostic.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/PositionalTextDiagnostic.kt @@ -1,19 +1,8 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. - * - * 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. + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics -data class PositionalTextDiagnostic(val diagnostic: CheckerTestUtil.TextDiagnostic, val start: Int, val end: Int) +data class PositionalTextDiagnostic(val diagnostic: TextDiagnostic, val start: Int, val end: Int) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/TextDiagnostic.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/TextDiagnostic.kt new file mode 100644 index 00000000000..c4e0cb96434 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/TextDiagnostic.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.util.SmartList +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil.INDIVIDUAL_PARAMETER_PATTERN +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil.SHOULD_BE_ESCAPED +import org.jetbrains.kotlin.diagnostics.rendering.AbstractDiagnosticWithParametersRenderer +import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages +import java.util.regex.Pattern + +class TextDiagnostic( + override val name: String, + override val platform: String?, + val parameters: List?, + inference: InferenceCompatibility? +) : AbstractTestDiagnostic { + override var inferenceCompatibility = inference ?: InferenceCompatibility.ALL + + val description: String + get() = (if (platform != null) "$platform:" else "") + name + + enum class InferenceCompatibility constructor(internal var abbreviation: String?) { + NEW(CheckerTestUtil.NEW_INFERENCE_PREFIX), OLD(CheckerTestUtil.OLD_INFERENCE_PREFIX), ALL(null); + + fun isCompatible(other: InferenceCompatibility): Boolean { + return this == other || this == ALL || other == ALL + } + } + + override fun compareTo(other: AbstractTestDiagnostic): Int { + return name.compareTo(other.name) + } + + override fun enhanceInferenceCompatibility(inferenceCompatibility: InferenceCompatibility) { + this.inferenceCompatibility = inferenceCompatibility + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + + val that = other as TextDiagnostic? + + if (name != that!!.name) return false + if (if (platform != null) platform != that.platform else that.platform != null) return false + if (if (parameters != null) parameters != that.parameters else that.parameters != null) return false + return inferenceCompatibility == that.inferenceCompatibility + + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + (platform?.hashCode() ?: 0) + result = 31 * result + (parameters?.hashCode() ?: 0) + result = 31 * result + inferenceCompatibility.hashCode() + return result + } + + fun asString(): String { + val result = StringBuilder() + if (inferenceCompatibility.abbreviation != null) { + result.append(inferenceCompatibility.abbreviation) + result.append(";") + } + if (platform != null) { + result.append(platform) + result.append(":") + } + result.append(name) + if (parameters != null) { + result.append("(") + result.append(StringUtil.join(parameters, { escape(it) }, "; ")) + result.append(")") + } + return result.toString() + } + + override fun toString(): String { + return asString() + } + + companion object { + private fun escape(s: String): String { + return s.replace("([$SHOULD_BE_ESCAPED])".toRegex(), "\\\\$1") + } + + private fun unescape(s: String): String { + return s.replace("\\\\([$SHOULD_BE_ESCAPED])".toRegex(), "$1") + } + fun parseDiagnostic(text: String): TextDiagnostic { + val matcher = CheckerTestUtil.individualDiagnosticPattern.matcher(text) + if (!matcher.find()) + throw IllegalArgumentException("Could not parse diagnostic: $text") + + val inference = computeInferenceCompatibility( + extractDataBefore( + matcher.group(1), + ";" + ) + ) + val platform = + extractDataBefore(matcher.group(2), ":") + + val name = matcher.group(3) + val parameters = matcher.group(4) ?: return TextDiagnostic( + name, + platform, + null, + inference + ) + + val parsedParameters = SmartList() + val parametersMatcher = INDIVIDUAL_PARAMETER_PATTERN.matcher(parameters) + while (parametersMatcher.find()) + parsedParameters.add(unescape(parametersMatcher.group().trim({ it <= ' ' }))) + return TextDiagnostic(name, platform, parsedParameters, inference) + } + + private fun computeInferenceCompatibility(abbreviation: String?): InferenceCompatibility { + return if (abbreviation == null) InferenceCompatibility.ALL else InferenceCompatibility.values().single { inference -> abbreviation == inference.abbreviation } + } + + private fun extractDataBefore(prefix: String?, anchor: String): String? { + assert(prefix == null || prefix.endsWith(anchor)) { prefix ?: "" } + return prefix?.substringBeforeLast(anchor, prefix) + } + + fun asTextDiagnostic(abstractTestDiagnostic: AbstractTestDiagnostic): TextDiagnostic { + return if (abstractTestDiagnostic is ActualDiagnostic) { + asTextDiagnostic(abstractTestDiagnostic) + } else abstractTestDiagnostic as TextDiagnostic + + } + + private fun asTextDiagnostic(actualDiagnostic: ActualDiagnostic): TextDiagnostic { + val diagnostic = actualDiagnostic.diagnostic + + val renderer = DefaultErrorMessages.getRendererForDiagnostic(diagnostic) + val diagnosticName = actualDiagnostic.name + if (renderer is AbstractDiagnosticWithParametersRenderer) { + val renderParameters = renderer.renderParameters(diagnostic) + val parameters = ContainerUtil.map(renderParameters, { it.toString() }) + return TextDiagnostic( + diagnosticName, + actualDiagnostic.platform, + parameters, + actualDiagnostic.inferenceCompatibility + ) + } + return TextDiagnostic( + diagnosticName, + actualDiagnostic.platform, + null, + actualDiagnostic.inferenceCompatibility + ) + } + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/DebugInfoDiagnosticFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/DebugInfoDiagnosticFactory.kt new file mode 100644 index 00000000000..dd78088ae71 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/DebugInfoDiagnosticFactory.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics.factories + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.resolve.BindingContext + +interface DebugInfoDiagnosticFactory { + val withExplicitDefinitionOnly: Boolean + + fun createDiagnostic( + expression: KtExpression, + bindingContext: BindingContext + ): Diagnostic +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/DebugInfoDiagnosticFactory0.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/DebugInfoDiagnosticFactory0.kt new file mode 100644 index 00000000000..1aa39893fa1 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/DebugInfoDiagnosticFactory0.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics.factories + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.checkers.diagnostics.DebugInfoDiagnostic +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 +import org.jetbrains.kotlin.diagnostics.PositioningStrategies +import org.jetbrains.kotlin.diagnostics.Severity +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.resolve.BindingContext + +class DebugInfoDiagnosticFactory0 : DiagnosticFactory0, + DebugInfoDiagnosticFactory { + private val name: String + override val withExplicitDefinitionOnly: Boolean + + override fun createDiagnostic( + expression: KtExpression, + bindingContext: BindingContext + ): Diagnostic { + return DebugInfoDiagnostic(expression, this) + } + + private constructor(name: String, severity: Severity = Severity.ERROR) : super(severity, PositioningStrategies.DEFAULT) { + this.name = name + this.withExplicitDefinitionOnly = false + } + + private constructor(name: String, severity: Severity, withExplicitDefinitionOnly: Boolean) : super( + severity, + PositioningStrategies.DEFAULT + ) { + this.name = name + this.withExplicitDefinitionOnly = withExplicitDefinitionOnly + } + + override fun getName(): String { + return "DEBUG_INFO_$name" + } + + companion object { + val SMARTCAST = DebugInfoDiagnosticFactory0("SMARTCAST", Severity.INFO) + val IMPLICIT_RECEIVER_SMARTCAST = + DebugInfoDiagnosticFactory0("IMPLICIT_RECEIVER_SMARTCAST", Severity.INFO) + val CONSTANT = DebugInfoDiagnosticFactory0("CONSTANT", Severity.INFO) + val LEAKING_THIS = DebugInfoDiagnosticFactory0("LEAKING_THIS") + val IMPLICIT_EXHAUSTIVE = + DebugInfoDiagnosticFactory0("IMPLICIT_EXHAUSTIVE", Severity.INFO) + val ELEMENT_WITH_ERROR_TYPE = DebugInfoDiagnosticFactory0("ELEMENT_WITH_ERROR_TYPE") + val UNRESOLVED_WITH_TARGET = DebugInfoDiagnosticFactory0("UNRESOLVED_WITH_TARGET") + val MISSING_UNRESOLVED = DebugInfoDiagnosticFactory0("MISSING_UNRESOLVED") + val DYNAMIC = DebugInfoDiagnosticFactory0("DYNAMIC", Severity.INFO) + } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/SyntaxErrorDiagnosticFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/SyntaxErrorDiagnosticFactory.kt new file mode 100644 index 00000000000..ec997552994 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/diagnostics/factories/SyntaxErrorDiagnosticFactory.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.checkers.diagnostics.factories + +import org.jetbrains.kotlin.checkers.diagnostics.SyntaxErrorDiagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Severity + +class SyntaxErrorDiagnosticFactory private constructor() : DiagnosticFactory(Severity.ERROR) { + override fun getName(): String { + return "SYNTAX" + } + + companion object { + val INSTANCE = SyntaxErrorDiagnosticFactory() + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt index 9c6c8e17f2d..04878f811d5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt @@ -1,1033 +1,552 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * Copyright 2010-2018 JetBrains s.r.o. 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.checkers.utils; +package org.jetbrains.kotlin.checkers.utils -import com.google.common.collect.LinkedListMultimap; -import com.google.common.collect.Maps; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.Function; -import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.Stack; -import kotlin.Pair; -import kotlin.TuplesKt; -import kotlin.collections.ArraysKt; -import kotlin.collections.CollectionsKt; -import kotlin.text.StringsKt; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.checkers.DebugInfoUtil; -import org.jetbrains.kotlin.checkers.PositionalTextDiagnostic; -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; -import org.jetbrains.kotlin.diagnostics.Diagnostic; -import org.jetbrains.kotlin.diagnostics.DiagnosticFactory; -import org.jetbrains.kotlin.diagnostics.Severity; -import org.jetbrains.kotlin.diagnostics.rendering.AbstractDiagnosticWithParametersRenderer; -import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages; -import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticRenderer; -import org.jetbrains.kotlin.psi.KtElement; -import org.jetbrains.kotlin.psi.KtExpression; -import org.jetbrains.kotlin.psi.KtReferenceExpression; -import org.jetbrains.kotlin.resolve.AnalyzingUtils; -import org.jetbrains.kotlin.resolve.BindingContext; -import org.jetbrains.kotlin.resolve.MultiTargetPlatform; -import org.jetbrains.kotlin.util.slicedMap.WritableSlice; +import com.google.common.collect.LinkedListMultimap +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.containers.Stack +import org.jetbrains.kotlin.checkers.* +import org.jetbrains.kotlin.checkers.diagnostics.* +import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory +import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory0 +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.resolve.AnalyzingUtils +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.MultiTargetPlatform +import java.util.* +import java.util.regex.Pattern -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +object CheckerTestUtil { + const val NEW_INFERENCE_PREFIX = "NI" + const val OLD_INFERENCE_PREFIX = "OI" -public class CheckerTestUtil { - public static final Comparator DIAGNOSTIC_COMPARATOR = (o1, o2) -> { - List ranges1 = o1.diagnostic.getTextRanges(); - List ranges2 = o2.diagnostic.getTextRanges(); - int minNumberOfRanges = ranges1.size() < ranges2.size() ? ranges1.size() : ranges2.size(); - for (int i = 0; i < minNumberOfRanges; i++) { - TextRange range1 = ranges1.get(i); - TextRange range2 = ranges2.get(i); - int startOffset1 = range1.getStartOffset(); - int startOffset2 = range2.getStartOffset(); - if (startOffset1 != startOffset2) { - // Start early -- go first - return startOffset1 - range2.getStartOffset(); - } - int endOffset1 = range1.getEndOffset(); - int endOffset2 = range2.getEndOffset(); - if (endOffset1 != endOffset2) { - // start at the same offset, the one who end later is the outer, i.e. goes first - return endOffset2 - endOffset1; - } - } - return ranges1.size() - ranges2.size(); - }; + private const val IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE" + val SHOULD_BE_ESCAPED = "\\)\\(;" + private val DIAGNOSTIC_PARAMETER = "(?:(?:\\\\[" + SHOULD_BE_ESCAPED + "])|[^" + SHOULD_BE_ESCAPED + "])+" + private val INDIVIDUAL_DIAGNOSTIC = "(\\w+;)?(\\w+:)?(\\w+)(\\(" + DIAGNOSTIC_PARAMETER + "(;\\s*" + DIAGNOSTIC_PARAMETER + ")*\\))?" + val INDIVIDUAL_PARAMETER_PATTERN = Pattern.compile(DIAGNOSTIC_PARAMETER) - private static final String IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE"; - private static final String SHOULD_BE_ESCAPED = "\\)\\(;"; - private static final String DIAGNOSTIC_PARAMETER = "(?:(?:\\\\[" + SHOULD_BE_ESCAPED + "])|[^" + SHOULD_BE_ESCAPED + "])+"; - private static final String INDIVIDUAL_DIAGNOSTIC = "(\\w+;)?(\\w+:)?(\\w+)(\\(" + DIAGNOSTIC_PARAMETER + "(;\\s*" + DIAGNOSTIC_PARAMETER + ")*\\))?"; - private static final Pattern RANGE_START_OR_END_PATTERN = Pattern.compile("()|()"); - private static final Pattern INDIVIDUAL_DIAGNOSTIC_PATTERN = Pattern.compile(INDIVIDUAL_DIAGNOSTIC); - private static final Pattern INDIVIDUAL_PARAMETER_PATTERN = Pattern.compile(DIAGNOSTIC_PARAMETER); + private val rangeStartOrEndPattern = Pattern.compile("()|()") + val individualDiagnosticPattern: Pattern = Pattern.compile(INDIVIDUAL_DIAGNOSTIC) - private static final String NEW_INFERENCE_PREFIX = "NI"; - private static final String OLD_INFERENCE_PREFIX = "OI"; + fun getDiagnosticsIncludingSyntaxErrors( + bindingContext: BindingContext, + implementingModulesBindings: List>, + root: PsiElement, + markDynamicCalls: Boolean, + dynamicCallDescriptors: MutableList, + withNewInference: Boolean + ): List { + val result = getDiagnosticsIncludingSyntaxErrors( + bindingContext, + root, + markDynamicCalls, + dynamicCallDescriptors, + null, + withNewInference + ) + val sortedBindings = implementingModulesBindings.sortedBy { it.first } - @NotNull - public static List getDiagnosticsIncludingSyntaxErrors( - @NotNull BindingContext bindingContext, - @NotNull List> implementingModulesBindings, - @NotNull PsiElement root, - boolean markDynamicCalls, - @Nullable List dynamicCallDescriptors, - boolean withNewInference - ) { - List result = - getDiagnosticsIncludingSyntaxErrors(bindingContext, root, markDynamicCalls, dynamicCallDescriptors, null, withNewInference); + for ((platform, second) in sortedBindings) { + assert(platform is MultiTargetPlatform.Specific) { "Implementing module must have a specific platform: $platform" } - List> sortedBindings = CollectionsKt.sortedWith( - implementingModulesBindings, - (o1, o2) -> o1.getFirst().compareTo(o2.getFirst()) - ); - - for (Pair binding : sortedBindings) { - MultiTargetPlatform platform = binding.getFirst(); - assert platform instanceof MultiTargetPlatform.Specific : "Implementing module must have a specific platform: " + platform; - - result.addAll(getDiagnosticsIncludingSyntaxErrors( - binding.getSecond(), root, markDynamicCalls, dynamicCallDescriptors, - ((MultiTargetPlatform.Specific) platform).getPlatform(), withNewInference - )); + result.addAll( + getDiagnosticsIncludingSyntaxErrors( + second, + root, + markDynamicCalls, + dynamicCallDescriptors, + (platform as MultiTargetPlatform.Specific).platform, + withNewInference + ) + ) } - return result; + return result } - @NotNull - public static List getDiagnosticsIncludingSyntaxErrors( - @NotNull BindingContext bindingContext, - @NotNull PsiElement root, - boolean markDynamicCalls, - @Nullable List dynamicCallDescriptors, - @Nullable String platform, - boolean withNewInference - ) { - List diagnostics = new ArrayList<>(); - for (Diagnostic diagnostic : bindingContext.getDiagnostics().all()) { - if (PsiTreeUtil.isAncestor(root, diagnostic.getPsiElement(), false)) { - diagnostics.add(new ActualDiagnostic(diagnostic, platform, withNewInference)); + fun getDiagnosticsIncludingSyntaxErrors( + bindingContext: BindingContext, + root: PsiElement, + markDynamicCalls: Boolean, + dynamicCallDescriptors: MutableList, + platform: String?, + withNewInference: Boolean + ): MutableList { + val diagnostics: MutableList = mutableListOf() + + bindingContext.diagnostics.forEach { diagnostic -> + if (PsiTreeUtil.isAncestor(root, diagnostic.psiElement, false)) { + diagnostics.add(ActualDiagnostic(diagnostic, platform, withNewInference)) } } - for (PsiErrorElement errorElement : AnalyzingUtils.getSyntaxErrorRanges(root)) { - diagnostics.add(new ActualDiagnostic(new SyntaxErrorDiagnostic(errorElement), platform, withNewInference)); + for (errorElement in AnalyzingUtils.getSyntaxErrorRanges(root)) { + diagnostics.add(ActualDiagnostic(SyntaxErrorDiagnostic(errorElement), platform, withNewInference)) } - diagnostics.addAll(getDebugInfoDiagnostics(root, bindingContext, markDynamicCalls, dynamicCallDescriptors, platform, withNewInference)); - return diagnostics; + diagnostics.addAll( + getDebugInfoDiagnostics( + root, + bindingContext, + markDynamicCalls, + dynamicCallDescriptors, + platform, + withNewInference + ) + ) + + return diagnostics } - @SuppressWarnings("TestOnlyProblems") - @NotNull - private static List getDebugInfoDiagnostics( - @NotNull PsiElement root, - @NotNull BindingContext bindingContext, - boolean markDynamicCalls, - @Nullable List dynamicCallDescriptors, - @Nullable String platform, - boolean withNewInference - ) { - List debugAnnotations = new ArrayList<>(); + private fun getDebugInfoDiagnostics( + root: PsiElement, + bindingContext: BindingContext, + markDynamicCalls: Boolean, + dynamicCallDescriptors: MutableList, + platform: String?, + withNewInference: Boolean + ): List { + val debugAnnotations = mutableListOf() - DebugInfoUtil.markDebugAnnotations(root, bindingContext, new DebugInfoUtil.DebugInfoReporter() { - @Override - public void reportElementWithErrorType(@NotNull KtReferenceExpression expression) { - newDiagnostic(expression, DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE); - } - - @Override - public void reportMissingUnresolved(@NotNull KtReferenceExpression expression) { - newDiagnostic(expression, DebugInfoDiagnosticFactory.MISSING_UNRESOLVED); - } - - @Override - public void reportUnresolvedWithTarget(@NotNull KtReferenceExpression expression, @NotNull String target) { - newDiagnostic(expression, DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET); - } - - @Override - public void reportDynamicCall(@NotNull KtElement element, DeclarationDescriptor declarationDescriptor) { - if (dynamicCallDescriptors != null) { - dynamicCallDescriptors.add(declarationDescriptor); - } - - if (markDynamicCalls) { - newDiagnostic(element, DebugInfoDiagnosticFactory.DYNAMIC); - } - } - - private void newDiagnostic(KtElement element, DebugInfoDiagnosticFactory factory) { - debugAnnotations.add(new ActualDiagnostic(new DebugInfoDiagnostic(element, factory), platform, withNewInference)); - } - }); + DebugInfoUtil.markDebugAnnotations( + root, + bindingContext, + CheckerDebugInfoReporter( + dynamicCallDescriptors, + markDynamicCalls, + debugAnnotations, + withNewInference, + platform + ) + ) // this code is used in tests and in internal action 'copy current file as diagnostic test' //noinspection unchecked - for (Pair, DebugInfoDiagnosticFactory> factory : Arrays.asList( - TuplesKt.to(BindingContext.SMARTCAST, DebugInfoDiagnosticFactory.SMARTCAST), - TuplesKt.to(BindingContext.IMPLICIT_RECEIVER_SMARTCAST, DebugInfoDiagnosticFactory.IMPLICIT_RECEIVER_SMARTCAST), - TuplesKt.to(BindingContext.SMARTCAST_NULL, DebugInfoDiagnosticFactory.CONSTANT), - TuplesKt.to(BindingContext.LEAKING_THIS, DebugInfoDiagnosticFactory.LEAKING_THIS), - TuplesKt.to(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, DebugInfoDiagnosticFactory.IMPLICIT_EXHAUSTIVE) - )) { - for (KtExpression expression : bindingContext.getSliceContents(factory.getFirst()).keySet()) { + + val factoryList = listOf( + BindingContext.SMARTCAST to DebugInfoDiagnosticFactory0.SMARTCAST, + BindingContext.IMPLICIT_RECEIVER_SMARTCAST to DebugInfoDiagnosticFactory0.IMPLICIT_RECEIVER_SMARTCAST, + BindingContext.SMARTCAST_NULL to DebugInfoDiagnosticFactory0.CONSTANT, + BindingContext.LEAKING_THIS to DebugInfoDiagnosticFactory0.LEAKING_THIS, + BindingContext.IMPLICIT_EXHAUSTIVE_WHEN to DebugInfoDiagnosticFactory0.IMPLICIT_EXHAUSTIVE + ) + + for ((context, factory) in factoryList) { + for ((expression, _) in bindingContext.getSliceContents(context)) { if (PsiTreeUtil.isAncestor(root, expression, false)) { - debugAnnotations.add(new ActualDiagnostic(new DebugInfoDiagnostic(expression, factory.getSecond()), platform, - withNewInference)); + val diagnostic = factory.createDiagnostic( + expression, + bindingContext + ) + debugAnnotations.add(ActualDiagnostic(diagnostic, platform, withNewInference)) } } } - return debugAnnotations; + return debugAnnotations } - public interface DiagnosticDiffCallbacks { - void missingDiagnostic(TextDiagnostic diagnostic, int expectedStart, int expectedEnd); - void wrongParametersDiagnostic(TextDiagnostic expectedDiagnostic, TextDiagnostic actualDiagnostic, int start, int end); + fun diagnosticsDiff( + expected: List, + actual: Collection, + callbacks: DiagnosticDiffCallbacks + ): Map { + val diagnosticToExpectedDiagnostic = mutableMapOf() - void unexpectedDiagnostic(TextDiagnostic diagnostic, int actualStart, int actualEnd); - } + assertSameFile(actual) - public static Map diagnosticsDiff( - List expected, - Collection actual, - DiagnosticDiffCallbacks callbacks - ) { - Map diagnosticToExpectedDiagnostic = new HashMap<>(); + val expectedDiagnostics = expected.iterator() + val sortedDiagnosticDescriptors = getActualSortedDiagnosticDescriptors(actual) + val actualDiagnostics = sortedDiagnosticDescriptors.iterator() + var currentExpected = safeAdvance(expectedDiagnostics) + var currentActual = safeAdvance(actualDiagnostics) - assertSameFile(actual); - - Iterator expectedDiagnostics = expected.iterator(); - List sortedDiagnosticDescriptors = getActualSortedDiagnosticDescriptors(actual); - Iterator actualDiagnostics = sortedDiagnosticDescriptors.iterator(); - - DiagnosedRange currentExpected = safeAdvance(expectedDiagnostics); - ActualDiagnosticDescriptor currentActual = safeAdvance(actualDiagnostics); while (currentExpected != null || currentActual != null) { - if (currentExpected != null) { - if (currentActual == null) { - missingDiagnostics(callbacks, currentExpected); - currentExpected = safeAdvance(expectedDiagnostics); - } - else { - int expectedStart = currentExpected.getStart(); - int actualStart = currentActual.getStart(); - int expectedEnd = currentExpected.getEnd(); - int actualEnd = currentActual.getEnd(); - if (expectedStart < actualStart) { - missingDiagnostics(callbacks, currentExpected); - currentExpected = safeAdvance(expectedDiagnostics); - } - else if (expectedStart > actualStart) { - unexpectedDiagnostics(currentActual, callbacks); - currentActual = safeAdvance(actualDiagnostics); - } - else if (expectedEnd > actualEnd) { - assert expectedStart == actualStart; - missingDiagnostics(callbacks, currentExpected); - currentExpected = safeAdvance(expectedDiagnostics); - } - else if (expectedEnd < actualEnd) { - assert expectedStart == actualStart; - unexpectedDiagnostics(currentActual, callbacks); - currentActual = safeAdvance(actualDiagnostics); - } - else { - compareDiagnostics(callbacks, currentExpected, currentActual, diagnosticToExpectedDiagnostic); - currentExpected = safeAdvance(expectedDiagnostics); - currentActual = safeAdvance(actualDiagnostics); - } - } - } - else { - //noinspection ConstantConditions - assert (currentActual != null); + if (currentExpected == null) { + assert(currentActual != null) - unexpectedDiagnostics(currentActual, callbacks); - currentActual = safeAdvance(actualDiagnostics); + unexpectedDiagnostics(currentActual!!, callbacks) + currentActual = safeAdvance(actualDiagnostics) + continue + } + + if (currentActual == null) { + missingDiagnostics(callbacks, currentExpected) + currentExpected = safeAdvance(expectedDiagnostics) + continue + } + + val expectedStart = currentExpected.start + val actualStart = currentActual.start + val expectedEnd = currentExpected.end + val actualEnd = currentActual.end + + when { + expectedStart < actualStart -> { + missingDiagnostics(callbacks, currentExpected) + currentExpected = safeAdvance(expectedDiagnostics) + } + expectedStart > actualStart -> { + unexpectedDiagnostics(currentActual, callbacks) + currentActual = safeAdvance(actualDiagnostics) + } + expectedEnd > actualEnd -> { + assert(expectedStart == actualStart) + missingDiagnostics(callbacks, currentExpected) + currentExpected = safeAdvance(expectedDiagnostics) + } + expectedEnd < actualEnd -> { + assert(expectedStart == actualStart) + unexpectedDiagnostics(currentActual, callbacks) + currentActual = safeAdvance(actualDiagnostics) + } + else -> { + compareDiagnostics(callbacks, currentExpected, currentActual, diagnosticToExpectedDiagnostic) + currentExpected = safeAdvance(expectedDiagnostics) + currentActual = safeAdvance(actualDiagnostics) + } } } - return diagnosticToExpectedDiagnostic; + return diagnosticToExpectedDiagnostic } - private static void compareDiagnostics( - @NotNull DiagnosticDiffCallbacks callbacks, - @NotNull DiagnosedRange currentExpected, - @NotNull ActualDiagnosticDescriptor currentActual, - @NotNull Map diagnosticToInput + private fun compareDiagnostics( + callbacks: DiagnosticDiffCallbacks, + currentExpected: DiagnosedRange, + currentActual: ActualDiagnosticDescriptor, + diagnosticToInput: MutableMap ) { - int expectedStart = currentExpected.getStart(); - int expectedEnd = currentExpected.getEnd(); + val expectedStart = currentExpected.start + val expectedEnd = currentExpected.end + val actualStart = currentActual.start + val actualEnd = currentActual.end + assert(expectedStart == actualStart && expectedEnd == actualEnd) - int actualStart = currentActual.getStart(); - int actualEnd = currentActual.getEnd(); - assert expectedStart == actualStart && expectedEnd == actualEnd; + val actualDiagnostics = currentActual.textDiagnosticsMap + val expectedDiagnostics = currentExpected.getDiagnostics() + val diagnosticNames = HashSet() - Map actualDiagnostics = currentActual.getTextDiagnosticsMap(); - List expectedDiagnostics = currentExpected.getDiagnostics(); + for (expectedDiagnostic in expectedDiagnostics) { + var actualDiagnosticEntry = actualDiagnostics.entries.firstOrNull { entry -> + val actualDiagnostic = entry.value + expectedDiagnostic.description == actualDiagnostic.description + && expectedDiagnostic.inferenceCompatibility.isCompatible(actualDiagnostic.inferenceCompatibility) + && expectedDiagnostic.parameters == actualDiagnostic.parameters + } - for (TextDiagnostic expectedDiagnostic : expectedDiagnostics) { - Map.Entry actualDiagnosticEntry = CollectionsKt.firstOrNull( - actualDiagnostics.entrySet(), entry -> { - TextDiagnostic actualDiagnostic = entry.getValue(); - return expectedDiagnostic.getDescription().equals(actualDiagnostic.getDescription()) && - expectedDiagnostic.inferenceCompatibility.isCompatible(actualDiagnostic.inferenceCompatibility); - } - ); - - if (actualDiagnosticEntry != null) { - AbstractTestDiagnostic actualDiagnostic = actualDiagnosticEntry.getKey(); - TextDiagnostic actualTextDiagnostic = actualDiagnosticEntry.getValue(); - - if (!compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) { - callbacks.wrongParametersDiagnostic(expectedDiagnostic, actualTextDiagnostic, expectedStart, expectedEnd); + if (actualDiagnosticEntry == null) { + actualDiagnosticEntry = actualDiagnostics.entries.firstOrNull { entry -> + val actualDiagnostic = entry.value + expectedDiagnostic.description == actualDiagnostic.description + && expectedDiagnostic.inferenceCompatibility.isCompatible(actualDiagnostic.inferenceCompatibility) } - - actualDiagnostics.remove(actualDiagnostic); - actualDiagnostic.enhanceInferenceCompatibility(expectedDiagnostic.inferenceCompatibility); - - diagnosticToInput.put(actualDiagnostic, expectedDiagnostic); } - else { - callbacks.missingDiagnostic(expectedDiagnostic, expectedStart, expectedEnd); + + if (actualDiagnosticEntry == null) { + callbacks.missingDiagnostic(expectedDiagnostic, expectedStart, expectedEnd) + continue } + + val actualDiagnostic = actualDiagnosticEntry.key + val actualTextDiagnostic = actualDiagnosticEntry.value + + if (!compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) + callbacks.wrongParametersDiagnostic(expectedDiagnostic, actualTextDiagnostic, expectedStart, expectedEnd) + + actualDiagnostics.remove(actualDiagnostic) + diagnosticNames.add(actualDiagnostic.name) + actualDiagnostic.enhanceInferenceCompatibility(expectedDiagnostic.inferenceCompatibility) + + diagnosticToInput[actualDiagnostic] = expectedDiagnostic } - for (TextDiagnostic unexpectedDiagnostic : actualDiagnostics.values()) { - callbacks.unexpectedDiagnostic(unexpectedDiagnostic, actualStart, actualEnd); + for (unexpectedDiagnostic in actualDiagnostics.keys) { + val textDiagnostic = actualDiagnostics[unexpectedDiagnostic] + + if (hasExplicitDefinitionOnlyOption(unexpectedDiagnostic) && !diagnosticNames.contains(unexpectedDiagnostic.name)) + continue + + callbacks.unexpectedDiagnostic(textDiagnostic!!, actualStart, actualEnd) } } - private static boolean compareTextDiagnostic(@NotNull TextDiagnostic expected, @NotNull TextDiagnostic actual) { - if (!expected.getDescription().equals(actual.getDescription())) return false; + private fun compareTextDiagnostic(expected: TextDiagnostic, actual: TextDiagnostic): Boolean { + if (expected.description != actual.description) + return false + if (expected.parameters == null) + return true + if (actual.parameters == null || expected.parameters.size != actual.parameters.size) + return false - if (expected.getParameters() == null) return true; - if (actual.getParameters() == null || expected.getParameters().size() != actual.getParameters().size()) return false; - - for (int index = 0; index < expected.getParameters().size(); index++) { - String expectedParameter = expected.getParameters().get(index); - String actualParameter = actual.getParameters().get(index); - if (!expectedParameter.equals(IGNORE_DIAGNOSTIC_PARAMETER) && !expectedParameter.equals(actualParameter)) { - return false; - } + expected.parameters.forEachIndexed { index: Int, expectedParameter: String -> + if (expectedParameter != IGNORE_DIAGNOSTIC_PARAMETER && expectedParameter != actual.parameters[index]) + return false } - return true; + + return true } - private static void assertSameFile(Collection actual) { - if (actual.isEmpty()) return; - PsiFile file = CollectionsKt.first(actual).getFile(); - for (ActualDiagnostic actualDiagnostic : actual) { - assert actualDiagnostic.getFile().equals(file) - : "All diagnostics should come from the same file: " + actualDiagnostic.getFile() + ", " + file; + + private fun assertSameFile(actual: Collection) { + if (actual.isEmpty()) return + val file = actual.first().file + for (actualDiagnostic in actual) { + assert(actualDiagnostic.file == file) { "All diagnostics should come from the same file: " + actualDiagnostic.file + ", " + file } } } - private static void unexpectedDiagnostics(ActualDiagnosticDescriptor descriptor, DiagnosticDiffCallbacks callbacks) { - for (AbstractTestDiagnostic diagnostic : descriptor.diagnostics) { - callbacks.unexpectedDiagnostic(TextDiagnostic.asTextDiagnostic(diagnostic), descriptor.getStart(), descriptor.getEnd()); + private fun unexpectedDiagnostics(descriptor: ActualDiagnosticDescriptor, callbacks: DiagnosticDiffCallbacks) { + for (diagnostic in descriptor.diagnostics) { + if (hasExplicitDefinitionOnlyOption(diagnostic)) + continue + + callbacks.unexpectedDiagnostic(TextDiagnostic.asTextDiagnostic(diagnostic), descriptor.start, descriptor.end) } } - private static void missingDiagnostics(DiagnosticDiffCallbacks callbacks, DiagnosedRange currentExpected) { - for (TextDiagnostic diagnostic : currentExpected.getDiagnostics()) { - callbacks.missingDiagnostic(diagnostic, currentExpected.getStart(), currentExpected.getEnd()); + private fun missingDiagnostics(callbacks: DiagnosticDiffCallbacks, currentExpected: DiagnosedRange) { + for (diagnostic in currentExpected.getDiagnostics()) { + callbacks.missingDiagnostic(diagnostic, currentExpected.start, currentExpected.end) } } - private static T safeAdvance(Iterator iterator) { - return iterator.hasNext() ? iterator.next() : null; + private fun safeAdvance(iterator: Iterator): T? { + return if (iterator.hasNext()) iterator.next() else null } - public static String parseDiagnosedRanges(String text, List result) { - Matcher matcher = RANGE_START_OR_END_PATTERN.matcher(text); - Stack opened = new Stack<>(); - - int offsetCompensation = 0; + fun parseDiagnosedRanges(text: String, result: MutableList): String { + val matcher = rangeStartOrEndPattern.matcher(text) + val opened = Stack() + var offsetCompensation = 0 while (matcher.find()) { - int effectiveOffset = matcher.start() - offsetCompensation; - String matchedText = matcher.group(); - if ("".equals(matchedText)) { - opened.pop().setEnd(effectiveOffset); + val effectiveOffset = matcher.start() - offsetCompensation + val matchedText = matcher.group() + if (matchedText == "") { + opened.pop().end = effectiveOffset + } else { + val diagnosticTypeMatcher = individualDiagnosticPattern.matcher(matchedText) + val range = DiagnosedRange(effectiveOffset) + while (diagnosticTypeMatcher.find()) + range.addDiagnostic(diagnosticTypeMatcher.group()) + opened.push(range) + result.add(range) } - else { - Matcher diagnosticTypeMatcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(matchedText); - DiagnosedRange range = new DiagnosedRange(effectiveOffset); - while (diagnosticTypeMatcher.find()) { - range.addDiagnostic(diagnosticTypeMatcher.group()); - } - opened.push(range); - result.add(range); - } - offsetCompensation += matchedText.length(); + offsetCompensation += matchedText.length } - assert opened.isEmpty() : "Stack is not empty"; + assert(opened.isEmpty()) { "Stack is not empty" } - matcher.reset(); - return matcher.replaceAll(""); + matcher.reset() + + return matcher.replaceAll("") } - public static StringBuffer addDiagnosticMarkersToText(@NotNull PsiFile psiFile, @NotNull Collection diagnostics) { - return addDiagnosticMarkersToText(psiFile, diagnostics, Collections.emptyMap(), PsiElement::getText, Collections.emptyList(), false); + private fun hasExplicitDefinitionOnlyOption(diagnostic: AbstractTestDiagnostic): Boolean { + if (diagnostic !is ActualDiagnostic) + return false + + val factory = diagnostic.diagnostic.factory + return factory is DebugInfoDiagnosticFactory && (factory as DebugInfoDiagnosticFactory).withExplicitDefinitionOnly } - public static StringBuffer addDiagnosticMarkersToText( - @NotNull PsiFile psiFile, - @NotNull Collection diagnostics, - @NotNull Map diagnosticToExpectedDiagnostic, - @NotNull Function getFileText, - @NotNull Collection uncheckedDiagnostics, - boolean withNewInferenceDirective - ) { - return addDiagnosticMarkersToText(psiFile, diagnostics, diagnosticToExpectedDiagnostic, getFileText, uncheckedDiagnostics, withNewInferenceDirective, false); - } + fun addDiagnosticMarkersToText(psiFile: PsiFile, diagnostics: Collection) = + addDiagnosticMarkersToText( + psiFile, + diagnostics, + emptyMap(), + { it.text }, + emptyList(), + false, + false + ) - public static StringBuffer addDiagnosticMarkersToText( - @NotNull PsiFile psiFile, - @NotNull Collection diagnostics, - @NotNull Map diagnosticToExpectedDiagnostic, - @NotNull Function getFileText, - @NotNull Collection uncheckedDiagnostics, - boolean withNewInferenceDirective, - boolean renderDiagnosticMessages - ) { - String text = getFileText.fun(psiFile); - StringBuffer result = new StringBuffer(); - diagnostics = CollectionsKt.filter(diagnostics, actualDiagnostic -> psiFile.equals(actualDiagnostic.getFile())); - if (diagnostics.isEmpty() && uncheckedDiagnostics.isEmpty()) { - result.append(text); - return result; + fun addDiagnosticMarkersToText( + psiFile: PsiFile, + diagnostics: Collection, + diagnosticToExpectedDiagnostic: Map, + getFileText: com.intellij.util.Function, + uncheckedDiagnostics: Collection, + withNewInferenceDirective: Boolean, + renderDiagnosticMessages: Boolean + ): StringBuffer { + val text = getFileText.`fun`(psiFile) + val result = StringBuffer() + val diagnosticsFiltered = diagnostics.filter { actualDiagnostic -> psiFile == actualDiagnostic.file } + if (diagnosticsFiltered.isEmpty() && uncheckedDiagnostics.isEmpty()) { + result.append(text) + return result } - List diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnostics, uncheckedDiagnostics); + val diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnosticsFiltered, uncheckedDiagnostics) + val opened = Stack() + val iterator = diagnosticDescriptors.listIterator() + var currentDescriptor: AbstractDiagnosticDescriptor? = iterator.next() - Stack opened = new Stack<>(); - ListIterator iterator = diagnosticDescriptors.listIterator(); - AbstractDiagnosticDescriptor currentDescriptor = iterator.next(); - - for (int i = 0; i < text.length(); i++) { - char c = text.charAt(i); + for (i in 0 until text.length) { + val c = text[i] while (!opened.isEmpty() && i == opened.peek().end) { - closeDiagnosticString(result); - opened.pop(); + closeDiagnosticString(result) + opened.pop() } while (currentDescriptor != null && i == currentDescriptor.start) { - openDiagnosticsString(result, currentDescriptor, diagnosticToExpectedDiagnostic, withNewInferenceDirective, renderDiagnosticMessages); - if (currentDescriptor.getEnd() == i) { - closeDiagnosticString(result); - } - else { - opened.push(currentDescriptor); - } - if (iterator.hasNext()) { - currentDescriptor = iterator.next(); - } - else { - currentDescriptor = null; - } + val isSkip = openDiagnosticsString( + result, + currentDescriptor, + diagnosticToExpectedDiagnostic, + withNewInferenceDirective, + renderDiagnosticMessages + ) + + if (currentDescriptor.end == i && !isSkip) + closeDiagnosticString(result) + else if (!isSkip) + opened.push(currentDescriptor) + currentDescriptor = if (iterator.hasNext()) iterator.next() else null } - result.append(c); + result.append(c) } if (currentDescriptor != null) { - assert currentDescriptor.start == text.length(); - assert currentDescriptor.end == text.length(); - openDiagnosticsString(result, currentDescriptor, diagnosticToExpectedDiagnostic, withNewInferenceDirective, renderDiagnosticMessages); - opened.push(currentDescriptor); + assert(currentDescriptor.start == text.length) + assert(currentDescriptor.end == text.length) + val isSkip = openDiagnosticsString( + result, + currentDescriptor, + diagnosticToExpectedDiagnostic, + withNewInferenceDirective, + renderDiagnosticMessages + ) + + if (!isSkip) + opened.push(currentDescriptor) } - while (!opened.isEmpty() && text.length() == opened.peek().end) { - closeDiagnosticString(result); - opened.pop(); + while (!opened.isEmpty() && text.length == opened.peek().end) { + closeDiagnosticString(result) + opened.pop() } - assert opened.isEmpty() : "Stack is not empty: " + opened; + assert(opened.isEmpty()) { "Stack is not empty: $opened" } - return result; + return result } - private static void openDiagnosticsString( - StringBuffer result, - AbstractDiagnosticDescriptor currentDescriptor, - Map diagnosticToExpectedDiagnostic, - boolean withNewInferenceDirective, - boolean renderDiagnosticMessages - ) { - result.append(" diagnostics = ((ActualDiagnosticDescriptor) currentDescriptor).getDiagnostics(); - for (Iterator iterator = diagnostics.iterator(); iterator.hasNext(); ) { - AbstractTestDiagnostic diagnostic = iterator.next(); - TextDiagnostic expectedDiagnostic = diagnosticToExpectedDiagnostic.get(diagnostic); - if (expectedDiagnostic != null) { - TextDiagnostic actualTextDiagnostic = TextDiagnostic.asTextDiagnostic(diagnostic); - if (compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) { - result.append(expectedDiagnostic.asString()); - } - else { - result.append(actualTextDiagnostic.asString()); - } - } - else { - if (withNewInferenceDirective && diagnostic.getInferenceCompatibility().abbreviation != null) { - result.append(diagnostic.getInferenceCompatibility().abbreviation); - result.append(";"); - } - if (diagnostic.getPlatform() != null) { - result.append(diagnostic.getPlatform()); - result.append(":"); - } - result.append(diagnostic.getName()); - if (renderDiagnosticMessages) { - TextDiagnostic textDiagnostic = TextDiagnostic.asTextDiagnostic(diagnostic); - if (textDiagnostic.getParameters() != null) { - result.append("(") - .append(String.join(", ", textDiagnostic.getParameters())) - .append(")"); + private fun openDiagnosticsString( + result: StringBuffer, + currentDescriptor: AbstractDiagnosticDescriptor, + diagnosticToExpectedDiagnostic: Map, + withNewInferenceDirective: Boolean, + renderDiagnosticMessages: Boolean + ): Boolean { + var isSkip = true + val diagnosticsAsText = mutableListOf() + + when (currentDescriptor) { + is TextDiagnosticDescriptor -> diagnosticsAsText.add(currentDescriptor.textDiagnostic.asString()) + is ActualDiagnosticDescriptor -> { + val diagnostics = currentDescriptor.diagnostics + + for (diagnostic in diagnostics) { + val expectedDiagnostic = diagnosticToExpectedDiagnostic[diagnostic] + if (expectedDiagnostic != null) { + val actualTextDiagnostic = TextDiagnostic.asTextDiagnostic(diagnostic) + diagnosticsAsText.add( + if (compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) + expectedDiagnostic.asString() else actualTextDiagnostic.asString() + ) + } else if (!hasExplicitDefinitionOnlyOption(diagnostic)) { + val diagnosticText = StringBuilder() + if (withNewInferenceDirective && diagnostic.inferenceCompatibility.abbreviation != null) { + diagnosticText.append(diagnostic.inferenceCompatibility.abbreviation) + diagnosticText.append(";") } + if (diagnostic.platform != null) { + diagnosticText.append(diagnostic.platform) + diagnosticText.append(":") + } + diagnosticText.append(diagnostic.name) + if (renderDiagnosticMessages) { + val textDiagnostic = TextDiagnostic.asTextDiagnostic(diagnostic) + if (textDiagnostic.parameters != null) { + diagnosticText + .append("(") + .append(textDiagnostic.parameters.joinToString(", ")) + .append(")") + } + } + diagnosticsAsText.add(diagnosticText.toString()) } } - if (iterator.hasNext()) { - result.append(", "); - } } + else -> throw IllegalStateException("Unknown diagnostic descriptor: $currentDescriptor") } - else { - throw new IllegalStateException("Unknown diagnostic descriptor: " + currentDescriptor); + + if (diagnosticsAsText.size != 0) { + result.append("") + isSkip = false } - result.append("!>"); + + return isSkip } - private static void closeDiagnosticString(StringBuffer result) { - result.append(""); + private fun closeDiagnosticString(result: StringBuffer) = result.append("") + + private fun getActualSortedDiagnosticDescriptors(diagnostics: Collection) = + getSortedDiagnosticDescriptors(diagnostics, emptyList()).filterIsInstance(ActualDiagnosticDescriptor::class.java) + + private fun getSortedDiagnosticDescriptors( + diagnostics: Collection, + uncheckedDiagnostics: Collection + ): List { + val validDiagnostics = diagnostics.filter { actualDiagnostic -> actualDiagnostic.diagnostic.isValid } + val diagnosticDescriptors = groupDiagnosticsByTextRange(validDiagnostics, uncheckedDiagnostics) + diagnosticDescriptors.sortWith { d1: AbstractDiagnosticDescriptor, d2: AbstractDiagnosticDescriptor -> + if (d1.start != d2.start) d1.start - d2.start else d2.end - d1.end + } + return diagnosticDescriptors } - public static class AbstractDiagnosticForTests implements Diagnostic { - private final PsiElement element; - private final DiagnosticFactory factory; + private fun groupDiagnosticsByTextRange( + diagnostics: Collection, + uncheckedDiagnostics: Collection + ): MutableList { + val diagnosticsGroupedByRanges = LinkedListMultimap.create() - public AbstractDiagnosticForTests(@NotNull PsiElement element, @NotNull DiagnosticFactory factory) { - this.element = element; - this.factory = factory; - } - - @NotNull - @Override - public DiagnosticFactory getFactory() { - return factory; - } - - @NotNull - @Override - public Severity getSeverity() { - return Severity.ERROR; - } - - @NotNull - @Override - public PsiElement getPsiElement() { - return element; - } - - @NotNull - @Override - public List getTextRanges() { - return Collections.singletonList(element.getTextRange()); - } - - @NotNull - @Override - public PsiFile getPsiFile() { - return element.getContainingFile(); - } - - @Override - public boolean isValid() { - return true; - } - } - - public static class SyntaxErrorDiagnosticFactory extends DiagnosticFactory { - public static final SyntaxErrorDiagnosticFactory INSTANCE = new SyntaxErrorDiagnosticFactory(); - - private SyntaxErrorDiagnosticFactory() { - super(Severity.ERROR); - } - - @NotNull - @Override - public String getName() { - return "SYNTAX"; - } - } - - public static class SyntaxErrorDiagnostic extends AbstractDiagnosticForTests { - public SyntaxErrorDiagnostic(@NotNull PsiErrorElement errorElement) { - super(errorElement, SyntaxErrorDiagnosticFactory.INSTANCE); - } - } - - public static class DebugInfoDiagnosticFactory extends DiagnosticFactory { - public static final DebugInfoDiagnosticFactory SMARTCAST = new DebugInfoDiagnosticFactory("SMARTCAST", Severity.INFO); - public static final DebugInfoDiagnosticFactory IMPLICIT_RECEIVER_SMARTCAST = new DebugInfoDiagnosticFactory("IMPLICIT_RECEIVER_SMARTCAST", Severity.INFO); - public static final DebugInfoDiagnosticFactory CONSTANT = new DebugInfoDiagnosticFactory("CONSTANT", Severity.INFO); - public static final DebugInfoDiagnosticFactory LEAKING_THIS = new DebugInfoDiagnosticFactory("LEAKING_THIS"); - public static final DebugInfoDiagnosticFactory IMPLICIT_EXHAUSTIVE = new DebugInfoDiagnosticFactory("IMPLICIT_EXHAUSTIVE", Severity.INFO); - public static final DebugInfoDiagnosticFactory ELEMENT_WITH_ERROR_TYPE = new DebugInfoDiagnosticFactory("ELEMENT_WITH_ERROR_TYPE"); - public static final DebugInfoDiagnosticFactory UNRESOLVED_WITH_TARGET = new DebugInfoDiagnosticFactory("UNRESOLVED_WITH_TARGET"); - public static final DebugInfoDiagnosticFactory MISSING_UNRESOLVED = new DebugInfoDiagnosticFactory("MISSING_UNRESOLVED"); - public static final DebugInfoDiagnosticFactory DYNAMIC = new DebugInfoDiagnosticFactory("DYNAMIC", Severity.INFO); - - private final String name; - - private DebugInfoDiagnosticFactory(String name, Severity severity) { - super(severity); - this.name = name; - } - - private DebugInfoDiagnosticFactory(String name) { - this(name, Severity.ERROR); - } - - @NotNull - @Override - public String getName() { - return "DEBUG_INFO_" + name; - } - } - - public static class DebugInfoDiagnostic extends AbstractDiagnosticForTests { - public DebugInfoDiagnostic(@NotNull KtElement element, @NotNull DebugInfoDiagnosticFactory factory) { - super(element, factory); - } - } - - private static List getActualSortedDiagnosticDescriptors( - @NotNull Collection diagnostics - ) { - return CollectionsKt.filterIsInstance( - getSortedDiagnosticDescriptors(diagnostics, Collections.emptyList()), - ActualDiagnosticDescriptor.class - ); - } - - @NotNull - private static List getSortedDiagnosticDescriptors( - @NotNull Collection diagnostics, - @NotNull Collection uncheckedDiagnostics - ) { - List validDiagnostics = CollectionsKt.filter(diagnostics, actualDiagnostic -> actualDiagnostic.diagnostic.isValid()); - List diagnosticDescriptors = groupDiagnosticsByTextRange(validDiagnostics, uncheckedDiagnostics); - diagnosticDescriptors.sort((d1, d2) -> (d1.start != d2.start) ? d1.start - d2.start : d2.end - d1.end); - return diagnosticDescriptors; - } - - @NotNull - private static List groupDiagnosticsByTextRange( - @NotNull Collection diagnostics, - @NotNull Collection uncheckedDiagnostics - ) { - LinkedListMultimap diagnosticsGroupedByRanges = LinkedListMultimap.create(); - for (ActualDiagnostic actualDiagnostic : diagnostics) { - Diagnostic diagnostic = actualDiagnostic.diagnostic; - for (TextRange textRange : diagnostic.getTextRanges()) { - diagnosticsGroupedByRanges.put(textRange, actualDiagnostic); + for (actualDiagnostic in diagnostics) { + val diagnostic = actualDiagnostic.diagnostic + for (textRange in diagnostic.textRanges) { + diagnosticsGroupedByRanges.put(textRange, actualDiagnostic) } } - for (PositionalTextDiagnostic uncheckedDiagnostic : uncheckedDiagnostics) { - TextRange range = new TextRange(uncheckedDiagnostic.getStart(), uncheckedDiagnostic.getEnd()); - diagnosticsGroupedByRanges.put(range, uncheckedDiagnostic.getDiagnostic()); + for ((diagnostic, start, end) in uncheckedDiagnostics) { + val range = TextRange(start, end) + diagnosticsGroupedByRanges.put(range, diagnostic) } - return CollectionsKt.map(diagnosticsGroupedByRanges.keySet(), range -> { - List abstractDiagnostics = diagnosticsGroupedByRanges.get(range); + return diagnosticsGroupedByRanges.keySet().map { range -> + val abstractDiagnostics = diagnosticsGroupedByRanges.get(range) + val needSortingByName = + abstractDiagnostics.any { diagnostic -> diagnostic.inferenceCompatibility != TextDiagnostic.InferenceCompatibility.ALL } - Comparator comparator = Comparator.comparing(AbstractTestDiagnostic::getInferenceCompatibility); - boolean needSortingByName = CollectionsKt.any( - abstractDiagnostics, - diagnostic -> diagnostic.getInferenceCompatibility() != TextDiagnostic.InferenceCompatibility.ALL - ); if (needSortingByName) { - comparator = comparator.thenComparing(Comparator.comparing(AbstractTestDiagnostic::getName)); + abstractDiagnostics.sortBy { it.name } + } else { + abstractDiagnostics.sortBy { it } } - abstractDiagnostics.sort(comparator); - - return new ActualDiagnosticDescriptor(range.getStartOffset(), range.getEndOffset(), abstractDiagnostics); - }); + ActualDiagnosticDescriptor(range.startOffset, range.endOffset, abstractDiagnostics) + }.toMutableList() } - - private static abstract class AbstractDiagnosticDescriptor { - private final int start; - private final int end; - - AbstractDiagnosticDescriptor(int start, int end) { - this.start = start; - this.end = end; - } - - public int getStart() { - return start; - } - - public int getEnd() { - return end; - } - - public TextRange getTextRange() { - return new TextRange(start, end); - } - } - - private static class ActualDiagnosticDescriptor extends AbstractDiagnosticDescriptor { - private final List diagnostics; - - ActualDiagnosticDescriptor(int start, int end, List diagnostics) { - super(start, end); - this.diagnostics = diagnostics; - } - - public List getDiagnostics() { - return diagnostics; - } - - public Map getTextDiagnosticsMap() { - Map diagnosticMap = Maps.newLinkedHashMap(); - for (AbstractTestDiagnostic diagnostic : diagnostics) { - diagnosticMap.put(diagnostic, TextDiagnostic.asTextDiagnostic(diagnostic)); - } - return diagnosticMap; - } - } - - private static class TextDiagnosticDescriptor extends AbstractDiagnosticDescriptor { - private final PositionalTextDiagnostic positionalTextDiagnostic; - - TextDiagnosticDescriptor(PositionalTextDiagnostic positionalTextDiagnostic) { - super(positionalTextDiagnostic.getStart(), positionalTextDiagnostic.getEnd()); - this.positionalTextDiagnostic = positionalTextDiagnostic; - } - - public TextDiagnostic getTextDiagnostic() { - return positionalTextDiagnostic.getDiagnostic(); - } - } - - public interface AbstractTestDiagnostic { - String getName(); - - String getPlatform(); - - TextDiagnostic.InferenceCompatibility getInferenceCompatibility(); - - void enhanceInferenceCompatibility(TextDiagnostic.InferenceCompatibility inferenceCompatibility); - } - - public static class ActualDiagnostic implements AbstractTestDiagnostic { - public final Diagnostic diagnostic; - public final String platform; - public TextDiagnostic.InferenceCompatibility inferenceCompatibility; - - ActualDiagnostic(@NotNull Diagnostic diagnostic, @Nullable String platform, boolean withNewInference) { - this.diagnostic = diagnostic; - this.platform = platform; - this.inferenceCompatibility = withNewInference ? - TextDiagnostic.InferenceCompatibility.NEW : - TextDiagnostic.InferenceCompatibility.OLD; - } - - @Override - @NotNull - public String getName() { - return diagnostic.getFactory().getName(); - } - - @Override - public String getPlatform() { - return platform; - } - - @NotNull - public PsiFile getFile() { - return diagnostic.getPsiFile(); - } - - @Override - public TextDiagnostic.InferenceCompatibility getInferenceCompatibility() { - return inferenceCompatibility; - } - - @Override - public void enhanceInferenceCompatibility(TextDiagnostic.InferenceCompatibility inferenceCompatibility) { - this.inferenceCompatibility = inferenceCompatibility; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ActualDiagnostic)) return false; - - ActualDiagnostic other = (ActualDiagnostic) obj; - // '==' on diagnostics is intentional here - return other.diagnostic == diagnostic && - (other.platform == null ? platform == null : other.platform.equals(platform)) && - (other.inferenceCompatibility == inferenceCompatibility); - } - - @Override - public int hashCode() { - int result = System.identityHashCode(diagnostic); - result = 31 * result + (platform != null ? platform.hashCode() : 0); - result = 31 * result + inferenceCompatibility.hashCode(); - return result; - } - - @Override - public String toString() { - String inferenceAbbreviation = inferenceCompatibility.abbreviation; - return (inferenceAbbreviation != null ? inferenceAbbreviation + ";" : "") + - (platform != null ? platform + ":" : "") + - diagnostic.toString(); - } - } - - public static class TextDiagnostic implements AbstractTestDiagnostic { - public enum InferenceCompatibility { - NEW(NEW_INFERENCE_PREFIX), OLD(OLD_INFERENCE_PREFIX), ALL(null); - - @Nullable String abbreviation; - - InferenceCompatibility(@Nullable String abbreviation) { - this.abbreviation = abbreviation; - } - - public boolean isCompatible(InferenceCompatibility other) { - return this == other || this == ALL || other == ALL; - } - } - - @NotNull - private static TextDiagnostic parseDiagnostic(String text) { - Matcher matcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(text); - if (!matcher.find()) - throw new IllegalArgumentException("Could not parse diagnostic: " + text); - - InferenceCompatibility inference = computeInferenceCompatibility(extractDataBefore(matcher.group(1), ";")); - String platform = extractDataBefore(matcher.group(2), ":"); - - String name = matcher.group(3); - String parameters = matcher.group(4); - if (parameters == null) { - return new TextDiagnostic(name, platform, null, inference); - } - - List parsedParameters = new SmartList<>(); - Matcher parametersMatcher = INDIVIDUAL_PARAMETER_PATTERN.matcher(parameters); - while (parametersMatcher.find()) - parsedParameters.add(unescape(parametersMatcher.group().trim())); - return new TextDiagnostic(name, platform, parsedParameters, inference); - } - - private static InferenceCompatibility computeInferenceCompatibility(@Nullable String abbreviation) { - if (abbreviation == null) return InferenceCompatibility.ALL; - return ArraysKt.single(InferenceCompatibility.values(), inference -> abbreviation.equals(inference.abbreviation)); - } - - private static String extractDataBefore(@Nullable String prefix, @NotNull String anchor) { - assert prefix == null || prefix.endsWith(anchor) : prefix; - return prefix == null ? null : StringsKt.substringBeforeLast(prefix, anchor, prefix); - } - - private static @NotNull String escape(@NotNull String s) { - return s.replaceAll("([" + SHOULD_BE_ESCAPED + "])", "\\\\$1"); - } - - private static @NotNull String unescape(@NotNull String s) { - return s.replaceAll("\\\\([" + SHOULD_BE_ESCAPED + "])", "$1"); - } - - public static TextDiagnostic asTextDiagnostic(@NotNull AbstractTestDiagnostic abstractTestDiagnostic) { - if (abstractTestDiagnostic instanceof ActualDiagnostic) { - return asTextDiagnostic((ActualDiagnostic) abstractTestDiagnostic); - } - - return (TextDiagnostic) abstractTestDiagnostic; - } - - @NotNull - @SuppressWarnings("unchecked") - public static TextDiagnostic asTextDiagnostic(@NotNull ActualDiagnostic actualDiagnostic) { - Diagnostic diagnostic = actualDiagnostic.diagnostic; - //noinspection TestOnlyProblems - DiagnosticRenderer renderer = DefaultErrorMessages.getRendererForDiagnostic(diagnostic); - String diagnosticName = actualDiagnostic.getName(); - if (renderer instanceof AbstractDiagnosticWithParametersRenderer) { - Object[] renderParameters = ((AbstractDiagnosticWithParametersRenderer) renderer).renderParameters(diagnostic); - List parameters = ContainerUtil.map(renderParameters, Object::toString); - return new TextDiagnostic(diagnosticName, actualDiagnostic.platform, parameters, actualDiagnostic.inferenceCompatibility); - } - return new TextDiagnostic(diagnosticName, actualDiagnostic.platform, null, actualDiagnostic.inferenceCompatibility); - } - - @NotNull - private final String name; - @Nullable - private final String platform; - @Nullable - private final List parameters; - @NotNull - private InferenceCompatibility inferenceCompatibility; - - public TextDiagnostic( - @NotNull String name, - @Nullable String platform, - @Nullable List parameters, - @Nullable InferenceCompatibility inference - ) { - this.name = name; - this.platform = platform; - this.parameters = parameters; - this.inferenceCompatibility = inference != null ? inference : InferenceCompatibility.ALL; - } - - @NotNull - @Override - public String getName() { - return name; - } - - @Override - @Nullable - public String getPlatform() { - return platform; - } - - @NotNull - public String getDescription() { - return (platform != null ? platform + ":" : "") + name; - } - - @Nullable - public List getParameters() { - return parameters; - } - - @NotNull - @Override - public InferenceCompatibility getInferenceCompatibility() { - return inferenceCompatibility; - } - - @Override - public void enhanceInferenceCompatibility(InferenceCompatibility inferenceCompatibility) { - this.inferenceCompatibility = inferenceCompatibility; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - TextDiagnostic that = (TextDiagnostic) o; - - if (!name.equals(that.name)) return false; - if (platform != null ? !platform.equals(that.platform) : that.platform != null) return false; - if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) return false; - if (inferenceCompatibility != that.inferenceCompatibility) return false; - - return true; - } - - @Override - public int hashCode() { - int result = name.hashCode(); - result = 31 * result + (platform != null ? platform.hashCode() : 0); - result = 31 * result + (parameters != null ? parameters.hashCode() : 0); - result = 31 * result + inferenceCompatibility.hashCode(); - return result; - } - - @NotNull - public String asString() { - StringBuilder result = new StringBuilder(); - if (inferenceCompatibility.abbreviation != null) { - result.append(inferenceCompatibility.abbreviation); - result.append(";"); - } - if (platform != null) { - result.append(platform); - result.append(":"); - } - result.append(name); - if (parameters != null) { - result.append("("); - result.append(StringUtil.join(parameters, TextDiagnostic::escape, "; ")); - result.append(")"); - } - return result.toString(); - } - - @Override - public String toString() { - return asString(); - } - } - - public static class DiagnosedRange { - private final int start; - private int end; - private final List diagnostics = ContainerUtil.newSmartList(); - - protected DiagnosedRange(int start) { - this.start = start; - } - - public int getStart() { - return start; - } - - public int getEnd() { - return end; - } - - public List getDiagnostics() { - return diagnostics; - } - - public void setEnd(int end) { - this.end = end; - } - - public void addDiagnostic(String diagnostic) { - diagnostics.add(TextDiagnostic.parseDiagnostic(diagnostic)); - } - } -} +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/DebugInfoUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/DebugInfoUtil.java similarity index 94% rename from compiler/frontend/src/org/jetbrains/kotlin/checkers/DebugInfoUtil.java rename to compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/DebugInfoUtil.java index b4d8518a532..8b14d0bec6a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/DebugInfoUtil.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/DebugInfoUtil.java @@ -1,20 +1,9 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. - * - * 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. + * Copyright 2010-2018 JetBrains s.r.o. 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.checkers; +package org.jetbrains.kotlin.checkers.utils; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt index 6ed701881be..a7857efa552 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.kt @@ -26,7 +26,12 @@ import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.asJava.getJvmSignatureDiagnostics import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest.TestFile import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest.TestModule -import org.jetbrains.kotlin.checkers.CheckerTestUtil.ActualDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.ActualDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.PositionalTextDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.TextDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory0 +import org.jetbrains.kotlin.checkers.diagnostics.factories.SyntaxErrorDiagnosticFactory +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.LanguageFeature @@ -127,7 +132,7 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava ) { - private val diagnosedRanges: List = ArrayList() + private val diagnosedRanges: MutableList = ArrayList() val actualDiagnostics: MutableList = ArrayList() val expectedText: String val clearText: String @@ -139,7 +144,7 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava = ArrayList() + val dynamicCallDescriptors: MutableList = ArrayList() val withNewInferenceDirective: Boolean val newInferenceEnabled: Boolean val renderDiagnosticMessages: Boolean @@ -251,8 +256,8 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava { @@ -346,10 +351,10 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava> = setOf( Errors.UNRESOLVED_REFERENCE, Errors.UNRESOLVED_REFERENCE_WRONG_RECEIVER, - CheckerTestUtil.SyntaxErrorDiagnosticFactory.INSTANCE, - CheckerTestUtil.DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE, - CheckerTestUtil.DebugInfoDiagnosticFactory.MISSING_UNRESOLVED, - CheckerTestUtil.DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET + SyntaxErrorDiagnosticFactory.INSTANCE, + DebugInfoDiagnosticFactory0.ELEMENT_WITH_ERROR_TYPE, + DebugInfoDiagnosticFactory0.MISSING_UNRESOLVED, + DebugInfoDiagnosticFactory0.UNRESOLVED_WITH_TARGET ) val DEFAULT_DIAGNOSTIC_TESTS_FEATURES = mapOf( diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CheckerTestUtilTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CheckerTestUtilTest.kt index 46569b17dc5..6bd8d980757 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CheckerTestUtilTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CheckerTestUtilTest.kt @@ -1,258 +1,238 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. - * - * 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. + * Copyright 2010-2018 JetBrains s.r.o. 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.checkers; +package org.jetbrains.kotlin.checkers -import com.google.common.collect.Lists; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.checkers.CheckerTestUtil.ActualDiagnostic; -import org.jetbrains.kotlin.checkers.CheckerTestUtil.DiagnosedRange; -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; -import org.jetbrains.kotlin.psi.KtFile; -import org.jetbrains.kotlin.resolve.BindingContext; -import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil; -import org.jetbrains.kotlin.test.ConfigurationKind; -import org.jetbrains.kotlin.test.KotlinTestUtils; -import org.jetbrains.kotlin.test.KotlinTestWithEnvironment; +import com.google.common.collect.Lists +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.checkers.diagnostics.ActualDiagnostic +import org.jetbrains.kotlin.checkers.diagnostics.TextDiagnostic +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil +import org.jetbrains.kotlin.test.ConfigurationKind +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.KotlinTestWithEnvironment +import org.jetbrains.kotlin.tests.di.createContainerForTests +import java.io.File +import kotlin.test.assertEquals -import java.io.File; -import java.util.List; +private data class DiagnosticData( + val index: Int, + val rangeIndex: Int, + val name: String, + val startOffset: Int, + val endOffset: Int +) -public class CheckerTestUtilTest extends KotlinTestWithEnvironment { - @NotNull - private static String getTestDataPath() { - return KotlinTestUtils.getTestDataPathBase() + "/diagnostics/checkerTestUtil"; - } +private abstract class Test(private vararg val expectedMessages: String) { + fun test(psiFile: PsiFile, environment: KotlinCoreEnvironment) { + val bindingContext = JvmResolveUtil.analyze(psiFile as KtFile, environment).bindingContext + val expectedText = CheckerTestUtil.addDiagnosticMarkersToText( + psiFile, + CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors( + bindingContext, psiFile, + false, + mutableListOf(), + null, + false + ) + ).toString() + val diagnosedRanges = Lists.newArrayList() - @Override - protected KotlinCoreEnvironment createEnvironment() { - return createEnvironmentWithMockJdk(ConfigurationKind.ALL); - } + CheckerTestUtil.parseDiagnosedRanges(expectedText, diagnosedRanges) - protected void doTest(TheTest theTest) throws Exception { - String text = KotlinTestUtils.doLoadFile(getTestDataPath(), "test.kt"); - theTest.test(TestCheckerUtil.createCheckAndReturnPsiFile("test.kt", text, getProject()), getEnvironment()); - } + val actualDiagnostics = CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors( + bindingContext, + psiFile, + false, + mutableListOf(), + null, + false + ) - public void testEquals() throws Exception { - doTest(new TheTest() { - @Override - protected void makeTestData(List diagnostics, List diagnosedRanges) { + makeTestData(actualDiagnostics, diagnosedRanges) + + val expectedMessages = listOf(*expectedMessages) + val actualMessages = mutableListOf() + + CheckerTestUtil.diagnosticsDiff(diagnosedRanges, actualDiagnostics, object : DiagnosticDiffCallbacks { + override fun missingDiagnostic(diagnostic: TextDiagnostic, expectedStart: Int, expectedEnd: Int) { + actualMessages.add(CheckerTestUtilTest.missing(diagnostic.description, expectedStart, expectedEnd)) } - }); - } - public void testMissing() throws Exception { - DiagnosticData typeMismatch1 = diagnostics.get(1); - doTest(new TheTest(missing(typeMismatch1)) { - @Override - protected void makeTestData(List diagnostics, List diagnosedRanges) { - diagnostics.remove(typeMismatch1.index); + override fun wrongParametersDiagnostic( + expectedDiagnostic: TextDiagnostic, + actualDiagnostic: TextDiagnostic, + start: Int, + end: Int + ) { + actualMessages.add( + CheckerTestUtilTest.wrongParameters(expectedDiagnostic.asString(), actualDiagnostic.asString(), start, end) + ) } - }); - } - public void testUnexpected() throws Exception { - DiagnosticData typeMismatch1 = diagnostics.get(1); - doTest(new TheTest(unexpected(typeMismatch1)) { - @Override - protected void makeTestData(List diagnostics, List diagnosedRanges) { - diagnosedRanges.remove(typeMismatch1.index); + override fun unexpectedDiagnostic(diagnostic: TextDiagnostic, actualStart: Int, actualEnd: Int) { + actualMessages.add(CheckerTestUtilTest.unexpected(diagnostic.description, actualStart, actualEnd)) } - }); + }) + + assertEquals(expectedMessages.joinToString("\n"), actualMessages.joinToString("\n")) } - public void testBoth() throws Exception { - DiagnosticData typeMismatch1 = diagnostics.get(1); - DiagnosticData unresolvedReference = diagnostics.get(6); - doTest(new TheTest(unexpected(typeMismatch1), missing(unresolvedReference)) { - @Override - protected void makeTestData(List diagnostics, List diagnosedRanges) { - diagnosedRanges.remove(typeMismatch1.rangeIndex); - diagnostics.remove(unresolvedReference.index); - } - }); - } - - public void testMissingInTheMiddle() throws Exception { - DiagnosticData noneApplicable = diagnostics.get(4); - DiagnosticData typeMismatch3 = diagnostics.get(5); - doTest(new TheTest(unexpected(noneApplicable), missing(typeMismatch3)) { - @Override - protected void makeTestData(List diagnostics, List diagnosedRanges) { - diagnosedRanges.remove(noneApplicable.rangeIndex); - diagnostics.remove(typeMismatch3.index); - } - }); - } - - public void testWrongParameters() throws Exception { - DiagnosticData unused = diagnostics.get(2); - String unusedDiagnostic = asTextDiagnostic(unused, "i"); - DiagnosedRange range = asDiagnosticRange(unused, unusedDiagnostic); - doTest(new TheTest(wrongParameters(unusedDiagnostic, "OI;UNUSED_VARIABLE(a)", unused.startOffset, unused.endOffset)) { - @Override - protected void makeTestData(List diagnostics, List diagnosedRanges) { - diagnosedRanges.set(unused.rangeIndex, range); - } - }); - } - - public void testWrongParameterInMultiRange() throws Exception { - DiagnosticData unresolvedReference = diagnostics.get(6); - String unusedDiagnostic = asTextDiagnostic(unresolvedReference, "i"); - String toManyArguments = asTextDiagnostic(diagnostics.get(7)); - DiagnosedRange range = asDiagnosticRange(unresolvedReference, unusedDiagnostic, toManyArguments); - doTest(new TheTest(wrongParameters(unusedDiagnostic, "OI;UNRESOLVED_REFERENCE(xx)", unresolvedReference.startOffset, unresolvedReference.endOffset)) { - @Override - protected void makeTestData(List diagnostics, List diagnosedRanges) { - diagnosedRanges.set(unresolvedReference.rangeIndex, range); - } - }); - } - - public void testAbstractJetDiagnosticsTest() throws Exception { - AbstractDiagnosticsTest test = new AbstractDiagnosticsTest() { - {setUp();} - }; - test.doTest(getTestDataPath() + File.separatorChar + "test_with_diagnostic.kt"); - } - - private static abstract class TheTest { - private final String[] expected; - - protected TheTest(String... expectedMessages) { - this.expected = expectedMessages; - } - - public void test(@NotNull PsiFile psiFile, @NotNull KotlinCoreEnvironment environment) { - BindingContext bindingContext = - JvmResolveUtil.analyze((KtFile) psiFile, environment).getBindingContext(); - - String expectedText = CheckerTestUtil.addDiagnosticMarkersToText( - psiFile, - CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors(bindingContext, psiFile, false, null, null, false) - ).toString(); - - List diagnosedRanges = Lists.newArrayList(); - CheckerTestUtil.parseDiagnosedRanges(expectedText, diagnosedRanges); - - List actualDiagnostics = - CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors(bindingContext, psiFile, false, null, null, false); - actualDiagnostics.sort(CheckerTestUtil.DIAGNOSTIC_COMPARATOR); - - makeTestData(actualDiagnostics, diagnosedRanges); - - List expectedMessages = Lists.newArrayList(expected); - List actualMessages = Lists.newArrayList(); - - CheckerTestUtil.diagnosticsDiff(diagnosedRanges, actualDiagnostics, new CheckerTestUtil.DiagnosticDiffCallbacks() { - @Override - public void missingDiagnostic(CheckerTestUtil.TextDiagnostic diagnostic, int expectedStart, int expectedEnd) { - actualMessages.add(missing(diagnostic.getDescription(), expectedStart, expectedEnd)); - } - - @Override - public void wrongParametersDiagnostic( - CheckerTestUtil.TextDiagnostic expectedDiagnostic, - CheckerTestUtil.TextDiagnostic actualDiagnostic, - int start, - int end - ) { - actualMessages.add(wrongParameters(expectedDiagnostic.asString(), actualDiagnostic.asString(), start, end)); - } - - @Override - public void unexpectedDiagnostic(CheckerTestUtil.TextDiagnostic diagnostic, int actualStart, int actualEnd) { - actualMessages.add(unexpected(diagnostic.getDescription(), actualStart, actualEnd)); - } - }); - - assertEquals(listToString(expectedMessages), listToString(actualMessages)); - } - - private static String listToString(List expectedMessages) { - StringBuilder stringBuilder = new StringBuilder(); - for (String expectedMessage : expectedMessages) { - stringBuilder.append(expectedMessage).append("\n"); - } - return stringBuilder.toString(); - } - - protected abstract void makeTestData(List diagnostics, List diagnosedRanges); - } - - private static String wrongParameters(String expected, String actual, int start, int end) { - return "Wrong parameters " + expected + " != " + actual +" at " + start + " to " + end; - } - - private static String unexpected(String type, int actualStart, int actualEnd) { - return "Unexpected " + type + " at " + actualStart + " to " + actualEnd; - } - - private static String missing(String type, int expectedStart, int expectedEnd) { - return "Missing " + type + " at " + expectedStart + " to " + expectedEnd; - } - - private static String unexpected(DiagnosticData data) { - return unexpected(data.name, data.startOffset, data.endOffset); - } - - private static String missing(DiagnosticData data) { - return missing(data.name, data.startOffset, data.endOffset); - } - - private static String asTextDiagnostic(DiagnosticData diagnosticData, String... params) { - return diagnosticData.name + "(" + StringUtil.join(params, "; ") + ")"; - } - - private static DiagnosedRange asDiagnosticRange(DiagnosticData diagnosticData, String... textDiagnostics) { - DiagnosedRange range = new DiagnosedRange(diagnosticData.startOffset); - range.setEnd(diagnosticData.endOffset); - for (String textDiagnostic : textDiagnostics) - range.addDiagnostic(textDiagnostic); - return range; - } - - private static class DiagnosticData { - public int index; - public int rangeIndex; - public String name; - public int startOffset; - public int endOffset; - - private DiagnosticData(int index, int rangeIndex, String name, int startOffset, int endOffset) { - this.index = index; - this.rangeIndex = rangeIndex; - this.name = name; - this.startOffset = startOffset; - this.endOffset = endOffset; - } - } - - private final List diagnostics = Lists.newArrayList( - new DiagnosticData(0, 0, "UNUSED_PARAMETER", 8, 9), - new DiagnosticData(1, 1, "CONSTANT_EXPECTED_TYPE_MISMATCH", 56, 57), - new DiagnosticData(2, 2, "UNUSED_VARIABLE", 67, 68), - new DiagnosticData(3, 3, "TYPE_MISMATCH", 98, 99), - new DiagnosticData(4, 4, "NONE_APPLICABLE", 120, 121), - new DiagnosticData(5, 5, "TYPE_MISMATCH", 159, 167), - new DiagnosticData(6, 6, "UNRESOLVED_REFERENCE", 164, 166), - new DiagnosticData(7, 6, "TOO_MANY_ARGUMENTS", 164, 166) - ); + abstract fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) +} + +class CheckerTestUtilTest : KotlinTestWithEnvironment() { + private val diagnostics = listOf( + DiagnosticData(0, 0, "UNUSED_PARAMETER", 8, 9), + DiagnosticData(1, 1, "CONSTANT_EXPECTED_TYPE_MISMATCH", 56, 57), + DiagnosticData(2, 2, "UNUSED_VARIABLE", 67, 68), + DiagnosticData(3, 3, "TYPE_MISMATCH", 98, 99), + DiagnosticData(4, 4, "NONE_APPLICABLE", 120, 121), + DiagnosticData(5, 5, "TYPE_MISMATCH", 159, 167), + DiagnosticData(6, 6, "UNRESOLVED_REFERENCE", 164, 166), + DiagnosticData(7, 6, "TOO_MANY_ARGUMENTS", 164, 166) + ) + + private fun getTestDataPath() = KotlinTestUtils.getTestDataPathBase() + "/diagnostics/checkerTestUtil" + + override fun createEnvironment(): KotlinCoreEnvironment { + println(System.getProperty("user.home")) + System.setProperty("user.dir", "/Users/victor.petukhov/IdeaProjects/kotlin") + println(System.getProperty("user.dir")) + return createEnvironmentWithMockJdk(ConfigurationKind.ALL) + } + + private fun doTest(test: Test) = test.test( + TestCheckerUtil.createCheckAndReturnPsiFile( + "test.kt", + KotlinTestUtils.doLoadFile(getTestDataPath(), "test.kt"), + project + ), + environment + ) + + fun testEquals() { + doTest(object : Test() { + override fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) {} + }) + } + + fun testMissing() { + val typeMismatch1 = diagnostics[1] + + doTest(object : Test(missing(typeMismatch1)) { + override fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) { + diagnostics.removeAt(typeMismatch1.index) + } + }) + } + + fun testUnexpected() { + val typeMismatch1 = diagnostics[1] + + doTest(object : Test(unexpected(typeMismatch1)) { + override fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) { + diagnosedRanges.removeAt(typeMismatch1.index) + } + }) + } + + fun testBoth() { + val typeMismatch1 = diagnostics[1] + val unresolvedReference = diagnostics[6] + + doTest(object : Test(unexpected(typeMismatch1), missing(unresolvedReference)) { + override fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) { + diagnosedRanges.removeAt(typeMismatch1.rangeIndex) + diagnostics.removeAt(unresolvedReference.index) + } + }) + } + + fun testMissingInTheMiddle() { + val noneApplicable = diagnostics[4] + val typeMismatch3 = diagnostics[5] + + doTest(object : Test(unexpected(noneApplicable), missing(typeMismatch3)) { + override fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) { + diagnosedRanges.removeAt(noneApplicable.rangeIndex) + diagnostics.removeAt(typeMismatch3.index) + } + }) + } + + fun testWrongParameters() { + val unused = diagnostics[2] + val unusedDiagnostic = asTextDiagnostic(unused, "i") + val range = asDiagnosticRange(unused, unusedDiagnostic) + val wrongParameter = wrongParameters(unusedDiagnostic, "OI;UNUSED_VARIABLE(a)", unused.startOffset, unused.endOffset) + + doTest(object : Test(wrongParameter) { + override fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) { + diagnosedRanges[unused.rangeIndex] = range + } + }) + } + + fun testWrongParameterInMultiRange() { + val unresolvedReference = diagnostics[6] + val unusedDiagnostic = asTextDiagnostic(unresolvedReference, "i") + val toManyArguments = asTextDiagnostic(diagnostics[7]) + val range = asDiagnosticRange(unresolvedReference, unusedDiagnostic, toManyArguments) + val wrongParameter = wrongParameters( + unusedDiagnostic, + "OI;UNRESOLVED_REFERENCE(xx)", + unresolvedReference.startOffset, + unresolvedReference.endOffset + ) + + doTest(object : Test(wrongParameter) { + override fun makeTestData(diagnostics: MutableList, diagnosedRanges: MutableList) { + diagnosedRanges[unresolvedReference.rangeIndex] = range + } + }) + } + + fun testAbstractJetDiagnosticsTest() { + val test = object : AbstractDiagnosticsTest() { + init { + setUp() + } + } + + test.doTest(getTestDataPath() + File.separatorChar + "test_with_diagnostic.kt") + } + + companion object { + fun wrongParameters(expected: String, actual: String, start: Int, end: Int) = + "Wrong parameters $expected != $actual at $start to $end" + + fun unexpected(type: String, actualStart: Int, actualEnd: Int) = + "Unexpected $type at $actualStart to $actualEnd" + + fun missing(type: String, expectedStart: Int, expectedEnd: Int) = + "Missing $type at $expectedStart to $expectedEnd" + + private fun unexpected(data: DiagnosticData) = unexpected(data.name, data.startOffset, data.endOffset) + + private fun missing(data: DiagnosticData) = missing(data.name, data.startOffset, data.endOffset) + + private fun asTextDiagnostic(diagnosticData: DiagnosticData, vararg params: String) = + diagnosticData.name + "(" + StringUtil.join(params, "; ") + ")" + + private fun asDiagnosticRange(diagnosticData: DiagnosticData, vararg textDiagnostics: String): DiagnosedRange { + val range = DiagnosedRange(diagnosticData.startOffset) + range.end = diagnosticData.endOffset + for (textDiagnostic in textDiagnostics) + range.addDiagnostic(textDiagnostic) + return range + } + } } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java index 33474aab44b..60be6272a13 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.TestsCompilerError; import org.jetbrains.kotlin.TestsCompiletimeError; import org.jetbrains.kotlin.backend.common.output.OutputFile; import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection; -import org.jetbrains.kotlin.checkers.CheckerTestUtil; +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil; import org.jetbrains.kotlin.checkers.CompilerTestLanguageVersionSettings; import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys; import org.jetbrains.kotlin.cli.common.output.OutputUtilsKt; @@ -367,7 +367,7 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { List ktFiles = new ArrayList<>(files.size()); for (TestFile file : files) { if (file.name.endsWith(".kt")) { - String content = CheckerTestUtil.parseDiagnosedRanges(file.content, new ArrayList<>(0)); + String content = CheckerTestUtil.INSTANCE.parseDiagnosedRanges(file.content, new ArrayList<>(0)); ktFiles.add(KotlinTestUtils.createFile(file.name, content, project)); } } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestFiles.java b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestFiles.java index 30fd5f906fd..6f2813e937f 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestFiles.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/CodegenTestFiles.java @@ -22,7 +22,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiErrorElement; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.checkers.CheckerTestUtil; +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.resolve.AnalyzingUtils; import org.jetbrains.kotlin.test.KotlinTestUtils; @@ -101,7 +101,7 @@ public class CodegenTestFiles { @NotNull public static CodegenTestFiles create(@NotNull String fileName, @NotNull String contentWithDiagnosticMarkup, @NotNull Project project) { - String content = CheckerTestUtil.parseDiagnosedRanges(contentWithDiagnosticMarkup, new ArrayList<>()); + String content = CheckerTestUtil.INSTANCE.parseDiagnosedRanges(contentWithDiagnosticMarkup, new ArrayList<>()); KtFile file = KotlinTestUtils.createFile(fileName, content, project); List ranges = AnalyzingUtils.getSyntaxErrorRanges(file); assert ranges.isEmpty() : "Syntax errors found in " + file + ": " + ranges; diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/DebugInfoAnnotator.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/DebugInfoAnnotator.kt index 61ca38233e2..6c65cb92e73 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/DebugInfoAnnotator.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/DebugInfoAnnotator.kt @@ -21,7 +21,7 @@ import com.intellij.lang.annotation.Annotator import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.checkers.DebugInfoUtil +import org.jetbrains.kotlin.checkers.utils.DebugInfoUtil import org.jetbrains.kotlin.idea.KotlinPluginUtil import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent import org.jetbrains.kotlin.idea.util.ProjectRootsUtil diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/internal/CopyAsDiagnosticTestAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/internal/CopyAsDiagnosticTestAction.kt index 83962ec2af0..5a75f1c923d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/internal/CopyAsDiagnosticTestAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/internal/CopyAsDiagnosticTestAction.kt @@ -20,7 +20,7 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.application.ApplicationManager -import org.jetbrains.kotlin.checkers.CheckerTestUtil +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil import org.jetbrains.kotlin.idea.caches.resolve.* import org.jetbrains.kotlin.psi.KtFile import java.awt.* @@ -35,7 +35,12 @@ class CopyAsDiagnosticTestAction : AnAction() { val bindingContext = (psiFile as KtFile).analyzeWithContent() val diagnostics = CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors( - bindingContext, psiFile, false, null, null, false + bindingContext, + psiFile, + false, + mutableListOf(), + null, + false ) val result = CheckerTestUtil.addDiagnosticMarkersToText(psiFile, diagnostics).toString() diff --git a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt index 64e4ad8200d..9e1b9768242 100644 --- a/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt +++ b/plugins/kapt3/kapt3-compiler/test/org/jetbrains/kotlin/kapt3/test/AbstractKotlinKapt3Test.kt @@ -27,7 +27,7 @@ import junit.framework.ComparisonFailure import org.jetbrains.kotlin.base.kapt3.DetectMemoryLeaksMode import org.jetbrains.kotlin.base.kapt3.KaptFlag import org.jetbrains.kotlin.base.kapt3.KaptOptions -import org.jetbrains.kotlin.checkers.CheckerTestUtil +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil import org.jetbrains.kotlin.cli.common.messages.MessageRenderer import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment @@ -102,7 +102,7 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() { val ktFiles = ArrayList(files.size) for (file in files.sorted()) { if (file.name.endsWith(".kt")) { - val content = CheckerTestUtil.parseDiagnosedRanges(file.content, ArrayList(0)) + val content = CheckerTestUtil.parseDiagnosedRanges(file.content, ArrayList(0)) val tmpKtFile = File(tmpDir, file.name).apply { writeText(content) } val virtualFile = StandardFileSystems.local().findFileByPath(tmpKtFile.path) ?: error("Can't find ${file.name}") ktFiles.add(psiManager.findFile(virtualFile) as? KtFile ?: error("Can't load ${file.name}"))