[FE] Commonize positioning strategies and move them to :frontend-psi module

This commit is contained in:
Dmitriy Novozhilov
2021-09-08 18:04:34 +03:00
committed by TeamCityServer
parent 7536dadb65
commit 850ca1916f
8 changed files with 130 additions and 128 deletions
@@ -0,0 +1,113 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.diagnostics
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.cfg.UnreachableCode
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.modalityModifier
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
object ClassicPositioningStrategies {
@JvmField
val ACTUAL_DECLARATION_NAME: PositioningStrategy<KtNamedDeclaration> =
object : PositioningStrategies.DeclarationHeader<KtNamedDeclaration>() {
override fun mark(element: KtNamedDeclaration): List<TextRange> {
val nameIdentifier = element.nameIdentifier
return when {
nameIdentifier != null -> markElement(nameIdentifier)
element is KtNamedFunction -> PositioningStrategies.DECLARATION_SIGNATURE.mark(element)
else -> PositioningStrategies.DEFAULT.mark(element)
}
}
}
// TODO: move to specific strategies
private val DiagnosticMarker.firstIncompatibility: ExpectActualCompatibility.Incompatible<MemberDescriptor>?
get() {
@Suppress("UNCHECKED_CAST")
val map = when (factoryName) {
Errors.NO_ACTUAL_FOR_EXPECT.name -> (this as DiagnosticWithParameters3Marker<*, *, *>).c
Errors.ACTUAL_WITHOUT_EXPECT.name -> (this as DiagnosticWithParameters2Marker<*, *>).b
else -> null
} as? Map<ExpectActualCompatibility.Incompatible<MemberDescriptor>, Collection<MemberDescriptor>> ?: return null
return map.keys.firstOrNull()
}
@JvmField
val INCOMPATIBLE_DECLARATION: PositioningStrategy<KtNamedDeclaration> =
object : PositioningStrategies.DeclarationHeader<KtNamedDeclaration>() {
override fun markDiagnostic(diagnostic: DiagnosticMarker): List<TextRange> {
val element = diagnostic.psiElement as KtNamedDeclaration
val callableDeclaration = element as? KtCallableDeclaration
val incompatibility = diagnostic.firstIncompatibility
return when (incompatibility) {
null, ExpectActualCompatibility.Incompatible.Unknown, is ExpectActualCompatibility.Incompatible.ClassScopes, ExpectActualCompatibility.Incompatible.EnumEntries -> null
ExpectActualCompatibility.Incompatible.ClassKind -> {
val startElement =
element.modifierList?.getModifier(KtTokens.ENUM_KEYWORD)
?: element.modifierList?.getModifier(KtTokens.ANNOTATION_KEYWORD)
val endElement =
element.node.findChildByType(PositioningStrategies.classKindTokens)?.psi
?: element.nameIdentifier
if (startElement != null && endElement != null) {
return markRange(startElement, endElement)
} else {
endElement
}
}
ExpectActualCompatibility.Incompatible.TypeParameterNames, ExpectActualCompatibility.Incompatible.TypeParameterCount,
ExpectActualCompatibility.Incompatible.TypeParameterUpperBounds, ExpectActualCompatibility.Incompatible.TypeParameterVariance, ExpectActualCompatibility.Incompatible.TypeParameterReified -> {
(element as? KtTypeParameterListOwner)?.typeParameterList
}
ExpectActualCompatibility.Incompatible.CallableKind -> {
(callableDeclaration as? KtNamedFunction)?.funKeyword
?: (callableDeclaration as? KtProperty)?.valOrVarKeyword
}
ExpectActualCompatibility.Incompatible.ParameterShape -> {
callableDeclaration?.let { it.receiverTypeReference ?: it.valueParameterList }
}
ExpectActualCompatibility.Incompatible.ParameterCount, ExpectActualCompatibility.Incompatible.ParameterTypes, ExpectActualCompatibility.Incompatible.ParameterNames,
ExpectActualCompatibility.Incompatible.ValueParameterVararg, ExpectActualCompatibility.Incompatible.ValueParameterNoinline, ExpectActualCompatibility.Incompatible.ValueParameterCrossinline -> {
callableDeclaration?.valueParameterList
}
ExpectActualCompatibility.Incompatible.ReturnType -> {
callableDeclaration?.typeReference
}
ExpectActualCompatibility.Incompatible.FunctionModifiersDifferent, ExpectActualCompatibility.Incompatible.FunctionModifiersNotSubset,
ExpectActualCompatibility.Incompatible.PropertyModifiers, ExpectActualCompatibility.Incompatible.ClassModifiers -> {
element.modifierList
}
ExpectActualCompatibility.Incompatible.PropertyKind -> {
element.node.findChildByType(PositioningStrategies.propertyKindTokens)?.psi
}
ExpectActualCompatibility.Incompatible.Supertypes -> {
(element as? KtClassOrObject)?.getSuperTypeList()
}
ExpectActualCompatibility.Incompatible.Modality -> {
element.modalityModifier()
}
ExpectActualCompatibility.Incompatible.Visibility -> {
element.visibilityModifier()
}
}?.let { markElement(it) } ?: ACTUAL_DECLARATION_NAME.mark(element)
}
}
@JvmField
val UNREACHABLE_CODE: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
override fun markDiagnostic(diagnostic: DiagnosticMarker): List<TextRange> {
@Suppress("UNCHECKED_CAST")
val unreachableCode = diagnostic as DiagnosticWithParameters2Marker<Set<KtElement>, Set<KtElement>>
return UnreachableCode.getUnreachableTextRanges(unreachableCode.psiElement as KtElement, unreachableCode.a, unreachableCode.b)
}
}
}
@@ -31,7 +31,8 @@ public abstract class DiagnosticFactoryWithPsiElement<E extends PsiElement, D ex
}
protected List<TextRange> getTextRanges(ParametrizedDiagnostic<E> diagnostic) {
return positioningStrategy.markDiagnostic(diagnostic);
// TODO: it's strange that java requires cast here, because ParametrizedDiagnostic<E> inherits DiagnosticMarker
return positioningStrategy.markDiagnostic((DiagnosticMarker) diagnostic);
}
protected boolean isValid(ParametrizedDiagnostic<E> diagnostic) {
@@ -41,6 +41,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.jetbrains.kotlin.diagnostics.ClassicPositioningStrategies.ACTUAL_DECLARATION_NAME;
import static org.jetbrains.kotlin.diagnostics.ClassicPositioningStrategies.INCOMPATIBLE_DECLARATION;
import static org.jetbrains.kotlin.diagnostics.PositioningStrategies.*;
import static org.jetbrains.kotlin.diagnostics.Severity.*;
@@ -953,7 +955,7 @@ public interface Errors {
// Control flow / Data flow
DiagnosticFactory2<KtElement, Set<KtElement>, Set<KtElement>> UNREACHABLE_CODE = DiagnosticFactory2.create(
WARNING, PositioningStrategies.UNREACHABLE_CODE);
WARNING, ClassicPositioningStrategies.UNREACHABLE_CODE);
DiagnosticFactory0<KtVariableDeclaration> VARIABLE_WITH_NO_TYPE_NO_INITIALIZER = DiagnosticFactory0.create(ERROR, DECLARATION_NAME);
File diff suppressed because it is too large Load Diff
@@ -1,94 +0,0 @@
/*
* 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.
*/
package org.jetbrains.kotlin.diagnostics
import com.intellij.lang.ASTNode
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffset
open class PositioningStrategy<in E : PsiElement> {
open fun markDiagnostic(diagnostic: ParametrizedDiagnostic<out E>): List<TextRange> {
return mark(diagnostic.psiElement)
}
open fun mark(element: E): List<TextRange> {
return markElement(element)
}
open fun isValid(element: E): Boolean {
return !hasSyntaxErrors(element)
}
}
fun markElement(element: PsiElement): List<TextRange> {
return listOf(TextRange(getStartOffset(element), getEndOffset(element)))
}
fun markSingleElement(element: PsiElement): TextRange {
return TextRange(getStartOffset(element), getEndOffset(element))
}
fun markNode(node: ASTNode): List<TextRange> {
return markElement(node.psi)
}
fun markRange(range: TextRange): List<TextRange> {
return listOf(range)
}
fun markRange(from: PsiElement, to: PsiElement): List<TextRange> {
return markRange(TextRange(getStartOffset(from), getEndOffset(to)))
}
private fun getStartOffset(element: PsiElement): Int {
var child = element.firstChild
if (child != null) {
while (child is PsiComment || child is PsiWhiteSpace) {
child = child.nextSibling
}
if (child != null) {
return getStartOffset(child)
}
}
return element.startOffset
}
private fun getEndOffset(element: PsiElement): Int {
var child = element.lastChild
if (child != null) {
while (child is PsiComment || child is PsiWhiteSpace) {
child = child.prevSibling
}
if (child != null) {
return getEndOffset(child)
}
}
return element.endOffset
}
fun hasSyntaxErrors(psiElement: PsiElement): Boolean {
if (psiElement is PsiErrorElement) return true
val children = psiElement.children
return children.isNotEmpty() && hasSyntaxErrors(children.last())
}