[FIR] Syntax checkers

Introduce base class for checkers that dive into source trees.
This commit is contained in:
Andrey Zinovyev
2021-04-27 11:02:00 +03:00
committed by teamcityserver
parent 1be39cb505
commit 787c743333
17 changed files with 407 additions and 144 deletions
@@ -0,0 +1,70 @@
/*
* 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.fir.analysis.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirLightSourceElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.diagnostics.getAncestors
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression
import org.jetbrains.kotlin.psi.KtTypeReference
/**
* Service to answer source-related questions in generic fashion.
* Shouldn't expose (receive or return) any specific source tree types
*/
interface SourceNavigator {
fun FirTypeRef.isInConstructorCallee(): Boolean
fun FirTypeRef.isInTypeConstraint(): Boolean
companion object {
private val lightTreeInstance = LightTreeSourceNavigator()
fun forElement(e: FirElement): SourceNavigator = when (e.source) {
is FirLightSourceElement -> lightTreeInstance
is FirPsiSourceElement<*> -> PsiSourceNavigator
null -> lightTreeInstance //shouldn't matter
}
inline fun <R> FirElement.withNavigator(block: SourceNavigator.() -> R): R = with(forElement(this), block)
}
}
open class LightTreeSourceNavigator : SourceNavigator {
private fun <T> FirElement.withSource(f: (FirSourceElement) -> T): T? =
source?.let { f(it) }
override fun FirTypeRef.isInConstructorCallee(): Boolean = withSource { source ->
source.treeStructure.getParent(source.lighterASTNode)?.tokenType == KtNodeTypes.CONSTRUCTOR_CALLEE
} ?: false
override fun FirTypeRef.isInTypeConstraint(): Boolean {
val source = source ?: return false
return source.treeStructure.getAncestors(source.lighterASTNode)
.find { it.tokenType == KtNodeTypes.TYPE_CONSTRAINT || it.tokenType == KtNodeTypes.TYPE_PARAMETER }
?.tokenType == KtNodeTypes.TYPE_CONSTRAINT
}
}
//by default psi tree can reuse light tree manipulations
object PsiSourceNavigator : LightTreeSourceNavigator() {
//Swallows incorrect casts!!!
private inline fun <reified P : PsiElement> FirElement.psi(): P? {
val psi = (source as? FirPsiSourceElement<*>)?.psi
return psi as? P
}
override fun FirTypeRef.isInConstructorCallee(): Boolean = psi<KtTypeReference>()?.parent is KtConstructorCalleeExpression
}
@@ -1,29 +0,0 @@
/*
* Copyright 2010-2020 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.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.isInterface
object FirDelegationInInterfaceChecker : FirRegularClassChecker() {
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isInterface) {
return
}
for (superTypeRef in declaration.superTypeRefs) {
val source = superTypeRef.source ?: continue
if (source.treeStructure.getParent(source.lighterASTNode)?.tokenType == KtNodeTypes.DELEGATED_SUPER_TYPE_ENTRY) {
reporter.reportOn(source, FirErrors.DELEGATION_IN_INTERFACE, context)
}
}
}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.toFirLightSourceElement
object FirFunctionTypeParametersChecker : FirSimpleFunctionChecker() {
override fun check(declaration: FirSimpleFunction, context: CheckerContext, reporter: DiagnosticReporter) {
declaration.source?.let { source ->
if (source.kind is FirFakeSourceElementKind) return
val typeParamsNode = source.treeStructure.typeParametersList(source.lighterASTNode)
val nameNode = source.treeStructure.nameIdentifier(source.lighterASTNode)
if (typeParamsNode != null && nameNode != null && typeParamsNode.startOffset > nameNode.startOffset) {
reporter.reportOn(
source,
FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX,
context
)
}
}
}
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClass
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
@@ -25,10 +26,11 @@ import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull
object FirPrimaryConstructorSuperTypeChecker : FirRegularClassChecker() {
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isInterface) {
for (superTypeRef in declaration.superTypeRefs) {
val source = superTypeRef.source ?: continue
if (source.treeStructure.getParent(source.lighterASTNode)?.tokenType == KtNodeTypes.CONSTRUCTOR_CALLEE) {
reporter.reportOn(source, FirErrors.SUPERTYPE_INITIALIZED_IN_INTERFACE, context)
with(SourceNavigator.forElement(declaration)) {
for (superTypeRef in declaration.superTypeRefs) {
if (superTypeRef.isInConstructorCallee()) {
reporter.reportOn(superTypeRef.source, FirErrors.SUPERTYPE_INITIALIZED_IN_INTERFACE, context)
}
}
}
return
@@ -97,10 +99,11 @@ object FirPrimaryConstructorSuperTypeChecker : FirRegularClassChecker() {
reporter: DiagnosticReporter,
context: CheckerContext
) {
for (superTypeRef in regularClass.superTypeRefs) {
val source = superTypeRef.source ?: continue
if (source.treeStructure.getParent(source.lighterASTNode)?.tokenType == KtNodeTypes.CONSTRUCTOR_CALLEE) {
reporter.reportOn(regularClass.source, FirErrors.SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR, context)
with(SourceNavigator.forElement(regularClass)) {
for (superTypeRef in regularClass.superTypeRefs) {
if (superTypeRef.isInConstructorCallee()) {
reporter.reportOn(regularClass.source, FirErrors.SUPERTYPE_INITIALIZED_WITHOUT_PRIMARY_CONSTRUCTOR, context)
}
}
}
}
@@ -5,14 +5,12 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirRealSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.getAncestors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.typeContext
@@ -43,7 +41,6 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() {
checkBoundUniqueness(declaration, context, reporter)
checkConflictingBounds(declaration, context, reporter)
checkTypeAliasBound(declaration, containingDeclaration, context, reporter)
checkBoundsPlacement(declaration, context, reporter)
checkDynamicBounds(declaration, context, reporter)
}
@@ -92,7 +89,9 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() {
// report the diagnostic on that bound
//take TypeConstraint bounds only to report on the same point as old FE
val constraintBounds = bounds.filter { it.isInTypeConstraint() }.toSet()
val constraintBounds = with(SourceNavigator.forElement(declaration)){
bounds.filter { it.isInTypeConstraint() }.toSet()
}
val reportOn =
if (bounds.size == 2) {
val boundDecl = otherBounds.firstOrNull() ?: boundWithParam.last()
@@ -105,14 +104,6 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() {
}
}
private fun FirTypeRef.isInTypeConstraint(): Boolean {
val source = source ?: return false
return source.treeStructure.getAncestors(source.lighterASTNode)
.find { it.tokenType == KtNodeTypes.TYPE_CONSTRAINT || it.tokenType == KtNodeTypes.TYPE_PARAMETER }
?.tokenType == KtNodeTypes.TYPE_CONSTRAINT
}
private fun checkBoundUniqueness(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) {
val seenClasses = mutableSetOf<FirRegularClass>()
val allNonErrorBounds = declaration.bounds.filter { it !is FirErrorTypeRef }
@@ -152,16 +143,6 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() {
}
}
//TODO should be moved to extended checkers (because this is basically a code-style warning)
private fun checkBoundsPlacement(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.bounds.size < 2) return
val (constraint, params) = declaration.bounds.partition { it.isInTypeConstraint() }
if (params.isNotEmpty() && constraint.isNotEmpty()) {
reporter.reportOn(declaration.source, FirErrors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS, context)
}
}
private fun checkDynamicBounds(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) {
declaration.bounds.forEach { bound ->
if (bound is FirDynamicTypeRef) {
@@ -20,22 +20,5 @@ object FirAnonymousFunctionChecker : FirAnonymousFunctionAsExpressionChecker() {
reporter.reportOn(source, FirErrors.USELESS_VARARG_ON_PARAMETER, context)
}
}
checkTypeParameters(expression, reporter, context)
}
private fun checkTypeParameters(
expression: FirAnonymousFunction,
reporter: DiagnosticReporter,
context: CheckerContext
) {
val source = expression.source ?: return
source.treeStructure.typeParametersList(source.lighterASTNode)?.let { _ ->
reporter.reportOn(
source,
FirErrors.TYPE_PARAMETERS_NOT_ALLOWED,
context
)
}
}
}
@@ -8,9 +8,10 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMemberDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.implicitModality
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirDeclarationSyntaxChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REDUNDANT_MODALITY_MODIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.modalityModifier
@@ -18,22 +19,29 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.modality
import org.jetbrains.kotlin.psi.KtDeclaration
object RedundantModalityModifierChecker : FirMemberDeclarationChecker() {
override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val source = declaration.source
if (source?.kind is FirFakeSourceElementKind) return
object RedundantModalityModifierSyntaxChecker : FirDeclarationSyntaxChecker<FirMemberDeclaration, KtDeclaration>() {
val modality = declaration.modality ?: return
override fun isApplicable(element: FirMemberDeclaration, source: FirSourceElement): Boolean =
source.kind !is FirFakeSourceElementKind
override fun checkLightTree(
element: FirMemberDeclaration,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val modality = element.modality ?: return
if (
modality == Modality.FINAL
&& (context.containingDeclarations.last() as? FirClass<*>)?.classKind == ClassKind.INTERFACE
) return
if (source != null && source.treeStructure.modalityModifier(source.lighterASTNode) == null) return
val implicitModality = declaration.implicitModality(context)
if (modality != implicitModality) return
reporter.reportOn(source, REDUNDANT_MODALITY_MODIFIER, context)
if (source.treeStructure.modalityModifier(source.lighterASTNode) == null) return
val implicitModality = element.implicitModality(context)
if (modality == implicitModality) {
reporter.reportOn(source, REDUNDANT_MODALITY_MODIFIER, context)
}
}
}
@@ -40,7 +40,7 @@ object RedundantSingleExpressionStringTemplateChecker : FirStringConcatenationCa
return when (val source = source) {
is FirPsiSourceElement<*> -> source.psi.stringParentChildrenCount()
is FirLightSourceElement -> source.lighterASTNode.stringParentChildrenCount(source)
else -> null
null -> null
}
}
@@ -11,45 +11,50 @@ import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.FirFakeSourceElement
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirDeclarationSyntaxChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.psi.KtDeclaration
object RedundantVisibilityModifierChecker : FirBasicDeclarationChecker() {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val source = declaration.source ?: return
if (declaration is FirConstructor && source.kind is FirFakeSourceElementKind) return
object RedundantVisibilityModifierSyntaxChecker : FirDeclarationSyntaxChecker<FirDeclaration, KtDeclaration>() {
override fun checkLightTree(
element: FirDeclaration,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (element is FirConstructor && source.kind is FirFakeSourceElementKind) return
if (source is FirFakeSourceElement<*>) return
if (
declaration !is FirMemberDeclaration
&& !(declaration is FirPropertyAccessor && declaration.visibility == context.containingPropertyVisibility)
element !is FirMemberDeclaration
&& !(element is FirPropertyAccessor && element.visibility == context.containingPropertyVisibility)
) return
val visibilityModifier = source.treeStructure.visibilityModifier(source.lighterASTNode)
val explicitVisibility = (visibilityModifier?.tokenType as? KtModifierKeywordToken)?.toVisibilityOrNull()
val implicitVisibility = declaration.implicitVisibility(context)
val implicitVisibility = element.implicitVisibility(context)
val containingMemberDeclaration = context.findClosest<FirMemberDeclaration>()
val redundantVisibility = when {
explicitVisibility == implicitVisibility -> implicitVisibility
explicitVisibility == Visibilities.Internal &&
containingMemberDeclaration.let { decl ->
decl != null && decl.isLocalMember
} -> Visibilities.Internal
explicitVisibility == Visibilities.Internal && containingMemberDeclaration?.isLocalMember == true -> Visibilities.Internal
else -> return
}
if (
redundantVisibility == Visibilities.Public
&& declaration is FirProperty
&& element is FirProperty
&& source.treeStructure.overrideModifier(source.lighterASTNode) != null
&& declaration.isVar
&& declaration.setter?.visibility == Visibilities.Public
&& element.isVar
&& element.setter?.visibility == Visibilities.Public
) return
reporter.reportOn(source, FirErrors.REDUNDANT_VISIBILITY_MODIFIER, context)
@@ -0,0 +1,48 @@
/*
* 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.fir.analysis.checkers.syntax
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.diagnostics.typeParametersList
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.psi.KtFunction
object FirAnonymousFunctionSyntaxChecker : FirExpressionSyntaxChecker<FirAnonymousFunction, KtFunction>() {
override fun checkPsi(
element: FirAnonymousFunction,
source: FirPsiSourceElement<KtFunction>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (source.psi.typeParameterList != null) {
reporter.reportOn(
source,
FirErrors.TYPE_PARAMETERS_NOT_ALLOWED,
context
)
}
}
override fun checkLightTree(
element: FirAnonymousFunction,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
source.treeStructure.typeParametersList(source.lighterASTNode)?.let { _ ->
reporter.reportOn(
source,
FirErrors.TYPE_PARAMETERS_NOT_ALLOWED,
context
)
}
}
}
@@ -0,0 +1,50 @@
/*
* 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.fir.analysis.checkers.syntax
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.isInterface
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry
object FirDelegationInInterfaceSyntaxChecker : FirDeclarationSyntaxChecker<FirRegularClass, KtClass>() {
override fun isApplicable(element: FirRegularClass, source: FirSourceElement): Boolean = element.isInterface
override fun checkPsi(
element: FirRegularClass,
source: FirPsiSourceElement<KtClass>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
for (superTypeRef in element.superTypeRefs) {
val superSource = superTypeRef.source ?: continue
if (superSource.psi?.parent is KtDelegatedSuperTypeEntry) {
reporter.reportOn(superSource, FirErrors.DELEGATION_IN_INTERFACE, context)
}
}
}
override fun checkLightTree(
element: FirRegularClass,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
for (superTypeRef in element.superTypeRefs) {
val superSource = superTypeRef.source ?: continue
if (superSource.treeStructure.getParent(superSource.lighterASTNode)?.tokenType == KtNodeTypes.DELEGATED_SUPER_TYPE_ENTRY) {
reporter.reportOn(superSource, FirErrors.DELEGATION_IN_INTERFACE, context)
}
}
}
}
@@ -0,0 +1,55 @@
/*
* 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.fir.analysis.checkers.syntax
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.startOffset
object FirFunctionTypeParametersSyntaxChecker : FirDeclarationSyntaxChecker<FirSimpleFunction, KtFunction>() {
override fun isApplicable(element: FirSimpleFunction, source: FirSourceElement): Boolean =
source.kind !is FirFakeSourceElementKind
override fun checkPsi(
element: FirSimpleFunction,
source: FirPsiSourceElement<KtFunction>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val typeParamsNode = source.psi.typeParameterList
val nameNode = source.psi.nameIdentifier
if (typeParamsNode != null && nameNode != null && typeParamsNode.startOffset > nameNode.startOffset) {
reporter.reportOn(
source,
FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX,
context
)
}
}
override fun checkLightTree(
element: FirSimpleFunction,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val typeParamsNode = source.treeStructure.typeParametersList(source.lighterASTNode)
val nameNode = source.treeStructure.nameIdentifier(source.lighterASTNode)
if (typeParamsNode != null && nameNode != null && typeParamsNode.startOffset > nameNode.startOffset) {
reporter.reportOn(
source,
FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX,
context
)
}
}
}
@@ -0,0 +1,65 @@
/*
* 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.fir.analysis.checkers.syntax
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirLightSourceElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirExpressionChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.expressions.FirStatement
/**
* Checker that heavily relies on source tree. So it should have different implementation for each tree variant
*/
interface FirSyntaxChecker<in D : FirElement, P : PsiElement> {
fun checkSyntax(element: D, context: CheckerContext, reporter: DiagnosticReporter) {
val source = element.source ?: return
if (!isApplicable(element, source)) return
@Suppress("UNCHECKED_CAST")
when (source) {
is FirPsiSourceElement<*> -> checkPsi(element, source as FirPsiSourceElement<P>, context, reporter)
is FirLightSourceElement -> checkLightTree(element, source, context, reporter)
}
}
fun isApplicable(element: D, source: FirSourceElement): Boolean = true
/**
* By default psi tree should be equivalent to light tree and can be processed the same way
*/
fun checkPsi(element: D, source: FirPsiSourceElement<P>, context: CheckerContext, reporter: DiagnosticReporter) {
checkLightTree(element, source, context, reporter)
}
/*
accepts FirSourceElement instead of FirLightSourceElement because now they have the same interface
and it allows to reuse LT check for psi tree
*/
fun checkLightTree(element: D, source: FirSourceElement, context: CheckerContext, reporter: DiagnosticReporter)
}
abstract class FirDeclarationSyntaxChecker<in D : FirDeclaration, P : PsiElement> :
FirDeclarationChecker<D>(),
FirSyntaxChecker<D, P> {
final override fun check(declaration: D, context: CheckerContext, reporter: DiagnosticReporter) {
checkSyntax(declaration, context, reporter)
}
}
abstract class FirExpressionSyntaxChecker<in E : FirStatement, P : PsiElement> :
FirExpressionChecker<E>(),
FirSyntaxChecker<E, P> {
final override fun check(expression: E, context: CheckerContext, reporter: DiagnosticReporter) {
checkSyntax(expression, context, reporter)
}
}
@@ -0,0 +1,50 @@
/*
* 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.fir.analysis.checkers.syntax
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.SourceNavigator.Companion.withNavigator
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.psi.KtTypeConstraint
import org.jetbrains.kotlin.psi.KtTypeParameter
object FirTypeParameterSyntaxChecker : FirDeclarationSyntaxChecker<FirTypeParameter, KtTypeParameter>() {
override fun isApplicable(element: FirTypeParameter, source: FirSourceElement): Boolean =
element.bounds.size >= 2
override fun checkPsi(
element: FirTypeParameter,
source: FirPsiSourceElement<KtTypeParameter>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val (constraint, params) = element.bounds.partition { it.psi?.parent is KtTypeConstraint }
if (params.isNotEmpty() && constraint.isNotEmpty()) {
reporter.reportOn(source, FirErrors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS, context)
}
}
override fun checkLightTree(
element: FirTypeParameter,
source: FirSourceElement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val (constraint, params) = element.withNavigator {
element.bounds.partition { it.isInTypeConstraint() }
}
if (params.isNotEmpty() && constraint.isNotEmpty()) {
reporter.reportOn(source, FirErrors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS, context)
}
}
}
@@ -11,6 +11,9 @@ import org.jetbrains.kotlin.fir.analysis.cfa.FirPropertyInitializationAnalyzer
import org.jetbrains.kotlin.fir.analysis.cfa.FirReturnsImpliesAnalyzer
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirDelegationInInterfaceSyntaxChecker
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirFunctionTypeParametersSyntaxChecker
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirTypeParameterSyntaxChecker
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirReservedUnderscoreDeclarationChecker
object CommonDeclarationCheckers : DeclarationCheckers() {
@@ -43,7 +46,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker>
get() = setOf(
FirFunctionNameChecker,
FirFunctionTypeParametersChecker,
FirFunctionTypeParametersSyntaxChecker,
)
override val propertyCheckers: Set<FirPropertyChecker>
@@ -72,7 +75,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirCommonConstructorDelegationIssuesChecker,
FirConstructorInInterfaceChecker,
FirDelegationSuperCallInEnumConstructorChecker,
FirDelegationInInterfaceChecker,
FirDelegationInInterfaceSyntaxChecker,
FirEnumClassSimpleChecker,
FirInterfaceWithSuperclassChecker,
FirLocalEntityNotAllowedChecker,
@@ -82,11 +85,11 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirPrimaryConstructorSuperTypeChecker,
FirTypeParametersInObjectChecker,
FirFunInterfaceDeclarationChecker,
FirMemberFunctionsChecker,
FirMemberPropertiesChecker,
FirNestedClassChecker,
FirInlineClassDeclarationChecker,
)
FirMemberFunctionsChecker,
FirMemberPropertiesChecker,
FirNestedClassChecker,
FirInlineClassDeclarationChecker,
)
override val constructorCheckers: Set<FirConstructorChecker>
get() = setOf(
@@ -116,5 +119,6 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirTypeParameterBoundsChecker,
FirTypeParameterVarianceChecker,
FirReifiedTypeParameterChecker,
FirTypeParameterSyntaxChecker,
)
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.checkers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirAnonymousFunctionSyntaxChecker
object CommonExpressionCheckers : ExpressionCheckers() {
override val annotationCallCheckers: Set<FirAnnotationCallChecker>
@@ -92,6 +93,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
override val anonymousFunctionAsExpressionCheckers: Set<FirAnonymousFunctionAsExpressionChecker>
get() = setOf(
FirAnonymousFunctionChecker,
FirAnonymousFunctionSyntaxChecker,
)
override val typeOperatorCallCheckers: Set<FirTypeOperatorCallChecker>
@@ -9,17 +9,16 @@ import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationCh
import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
import org.jetbrains.kotlin.fir.analysis.checkers.extended.*
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
object ExtendedDeclarationCheckers : DeclarationCheckers() {
override val basicDeclarationCheckers: Set<FirBasicDeclarationChecker>
get() = setOf(
RedundantVisibilityModifierChecker,
RedundantVisibilityModifierSyntaxChecker,
)
override val memberDeclarationCheckers: Set<FirMemberDeclarationChecker>
get() = setOf(
RedundantModalityModifierChecker,
RedundantModalityModifierSyntaxChecker,
RedundantExplicitTypeChecker,
RedundantSetterParameterTypeChecker,
)