[FIR] Implement UNDERSCORE_IS_RESERVED, UNDERSCORE_USAGE_WITHOUT_BACKTICKS diagnostics (lighttree)

This commit is contained in:
Ivan Kochurkin
2021-04-23 22:37:30 +03:00
committed by TeamCityServer
parent ea2d9f7c0c
commit 704b5a0e13
11 changed files with 294 additions and 51 deletions
@@ -5,55 +5,106 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import com.intellij.lang.LighterASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.fir.FirSourceElement
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.checkers.declaration.FirBasicDeclarationChecker
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.checkers.getChildren
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.stubs.elements.KtDotQualifiedExpressionElementType
import org.jetbrains.kotlin.psi.stubs.elements.KtNameReferenceExpressionElementType
import org.jetbrains.kotlin.psi.stubs.elements.KtParameterElementType
object FirReservedUnderscoreExpressionChecker : FirBasicExpressionChecker() {
override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
val source = expression.source
if (expression is FirFunctionCall) {
val calleeReferenceSource = expression.calleeReference.source
if (calleeReferenceSource is FirLightSourceElement && calleeReferenceSource.lighterASTNode.tokenType == KtNodeTypes.OPERATION_REFERENCE) {
return
}
reportIfUnderscore(
expression.calleeReference.psi?.text, expression.source, context, reporter,
expression.calleeReference.source.text, expression.calleeReference.source, context, reporter,
isExpression = true
)
for (argument in expression.arguments) {
if (argument is FirNamedArgumentExpression) {
reportIfUnderscore(argument.psi?.firstChild?.text, argument.source, context, reporter)
}
}
} else if (expression is FirQualifiedAccess) {
fun processQualifiedAccess(psi: PsiElement?) {
if (psi is KtNameReferenceExpression) {
reportIfUnderscore(psi.text, expression.source, context, reporter, isExpression = true)
} else if (psi is KtDotQualifiedExpression || psi is KtCallableReferenceExpression) {
processQualifiedAccess(psi.firstChild)
processQualifiedAccess(psi.lastChild)
}
}
val psi = expression.psi
if (psi != null && psi.parent !is KtDotQualifiedExpression && psi.parent !is KtCallableReferenceExpression) {
processQualifiedAccess(psi)
if (source is FirPsiSourceElement<*>) {
reportIfUnderscoreInQualifiedAccess(source, expression, context, reporter)
} else if (source is FirLightSourceElement) {
reportIfUnderscoreInQualifiedAccess(source, expression, context, reporter)
}
} else if (expression is FirGetClassCall) {
for (argument in expression.argumentList.arguments) {
reportIfUnderscore(argument.psi?.text, expression.source, context, reporter, isExpression = true)
reportIfUnderscore(argument.source.text, expression.source, context, reporter, isExpression = true)
}
} else if (expression is FirReturnExpression) {
reportIfUnderscore(expression.target.labelName, expression.source, context, reporter)
var labelName: String? = null
if (source is FirPsiSourceElement<*>) {
labelName = (source.psi.parent as? KtLabeledExpression)?.getLabelName()
} else if (source is FirLightSourceElement) {
val parent = source.treeStructure.getParent(source.lighterASTNode)
if (parent != null && parent.tokenType == KtNodeTypes.LABELED_EXPRESSION) {
labelName = source.treeStructure.findDescendantByType(parent, KtNodeTypes.LABEL).toString()
labelName = labelName.substring(0, labelName.length - 1)
}
}
reportIfUnderscore(labelName, expression.source, context, reporter)
}
}
private fun reportIfUnderscoreInQualifiedAccess(
source: FirPsiSourceElement<*>,
expression: FirStatement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
fun processQualifiedAccess(psi: PsiElement?) {
if (psi is KtNameReferenceExpression) {
reportIfUnderscore(psi.text, expression.source, context, reporter, isExpression = true)
} else if (psi is KtDotQualifiedExpression || psi is KtCallableReferenceExpression) {
processQualifiedAccess(psi.firstChild)
processQualifiedAccess(psi.lastChild)
}
}
val psi = source.psi
if (psi.parent !is KtDotQualifiedExpression && psi.parent !is KtCallableReferenceExpression) {
processQualifiedAccess(psi)
}
}
private fun reportIfUnderscoreInQualifiedAccess(
source: FirLightSourceElement,
expression: FirStatement,
context: CheckerContext,
reporter: DiagnosticReporter
) {
fun processQualifiedAccess(lightSourceElement: LighterASTNode?) {
val tokenType = lightSourceElement?.tokenType
if (tokenType is KtNameReferenceExpressionElementType) {
reportIfUnderscore(lightSourceElement.toString(), expression.source, context, reporter, isExpression = true)
} else if (lightSourceElement != null && (tokenType is KtDotQualifiedExpressionElementType || tokenType == KtNodeTypes.CALLABLE_REFERENCE_EXPRESSION)) {
val children = lightSourceElement.getChildren(source.treeStructure)
processQualifiedAccess(children.first())
processQualifiedAccess(children.last())
}
}
val astNode = source.lighterASTNode
val parent = source.treeStructure.getParent(astNode)
if (parent?.tokenType !is KtDotQualifiedExpressionElementType && parent?.tokenType != KtNodeTypes.CALLABLE_REFERENCE_EXPRESSION) {
processQualifiedAccess(astNode)
}
}
}
@@ -67,7 +118,6 @@ object FirReservedUnderscoreDeclarationChecker : FirBasicDeclarationChecker() {
declaration is FirProperty ||
declaration is FirTypeAlias
) {
reportIfUnderscore(declaration, context, reporter)
if (declaration is FirFunction<*>) {
@@ -94,15 +144,35 @@ private fun reportIfUnderscore(
reporter: DiagnosticReporter,
isSingleUnderscoreAllowed: Boolean = false
) {
val rawIdentifier = (declaration.psi as? PsiNameIdentifierOwner)?.nameIdentifier?.text ?: return
reportIfUnderscore(rawIdentifier, declaration.source, context, reporter, isSingleUnderscoreAllowed)
val source = declaration.source
val rawIdentifier = when (source) {
is FirPsiSourceElement<*> ->
(source.psi as? PsiNameIdentifierOwner)?.nameIdentifier?.text
is FirLightSourceElement ->
source.treeStructure.nameIdentifier(source.lighterASTNode)?.toString()
else ->
null
}
fun reportIfAnyDescendantIfUnderscore(typeRef: FirTypeRef?) {
if (typeRef == null) return
reportIfUnderscore(rawIdentifier, source, context, reporter, isSingleUnderscoreAllowed)
if (typeRef.psi?.anyDescendantOfType<LeafPsiElement> { isUnderscore(it.text) } == true) {
fun reportIfAnyDescendantIsUnderscore(typeRefSource: FirSourceElement?) {
if (typeRefSource == null) return
val isReport = when (typeRefSource) {
is FirPsiSourceElement<*> -> {
val psi = typeRefSource.psi
psi !is KtFunctionLiteral && psi.anyDescendantOfType<LeafPsiElement> { isUnderscore(it.text) }
}
is FirLightSourceElement ->
source?.treeStructure?.findFirstDescendant(typeRefSource.lighterASTNode) { node -> isUnderscore(node.toString()) } != null
else ->
false
}
if (isReport) {
reporter.reportOn(
typeRef.source,
typeRefSource,
FirErrors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS,
context
)
@@ -110,17 +180,30 @@ private fun reportIfUnderscore(
}
if (declaration is FirValueParameter) {
val psi = declaration.returnTypeRef.psi
if (psi !is KtFunctionLiteral && psi !is KtParameter) {
reportIfAnyDescendantIfUnderscore(declaration.returnTypeRef)
val isReport = when (val returnTypeRefSource = declaration.returnTypeRef.source) {
is FirPsiSourceElement<*> -> {
val psi = returnTypeRefSource.psi
psi !is KtFunctionLiteral && psi !is KtParameter
}
is FirLightSourceElement -> {
val tokenType = returnTypeRefSource.lighterASTNode.tokenType
tokenType !is KtParameterElementType && tokenType != KtNodeTypes.CLASS
}
else -> {
false
}
}
if (isReport) {
reportIfAnyDescendantIsUnderscore(declaration.returnTypeRef.source)
}
} else if (declaration is FirFunction<*>) {
reportIfAnyDescendantIfUnderscore(declaration.receiverTypeRef)
reportIfAnyDescendantIsUnderscore(declaration.receiverTypeRef?.source)
}
}
private fun reportIfUnderscore(
text: String?,
text: CharSequence?,
source: FirSourceElement?,
context: CheckerContext,
reporter: DiagnosticReporter,
@@ -140,4 +223,4 @@ private fun reportIfUnderscore(
}
}
private fun isUnderscore(text: String) = text.all { it == '_' }
private fun isUnderscore(text: CharSequence) = text.all { it == '_' }
@@ -644,6 +644,28 @@ object LightTreePositioningStrategies {
}
val RESERVED_UNDERSCORE: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
override fun mark(
node: LighterASTNode,
startOffset: Int,
endOffset: Int,
tree: FlyweightCapableTreeStructure<LighterASTNode>
): List<TextRange> {
if (node.tokenType == KtNodeTypes.RETURN) {
val parent = tree.getParent(node)
if (parent != null) {
val label = tree.findDescendantByType(parent, KtNodeTypes.LABEL)
if (label != null) {
return markElement(label, startOffset, endOffset - 1, tree, node)
}
}
}
val descendants =
tree.collectDescendantsOfType(node, KtTokens.IDENTIFIER) { descendant -> descendant.toString().all { it == '_' } };
if (descendants.isNotEmpty())
return descendants.map { markSingleElement(it, it, startOffset, endOffset, tree, node) }
return super.mark(node, startOffset, endOffset, tree)
}
}
}
@@ -858,6 +880,35 @@ fun FlyweightCapableTreeStructure<LighterASTNode>.findFirstDescendant(
?: childrenRef.get()?.firstNotNullResult { child -> child?.let { findFirstDescendant(it, predicate) } }
}
fun FlyweightCapableTreeStructure<LighterASTNode>.collectDescendantsOfType(
node: LighterASTNode, type: IElementType,
predicate: (LighterASTNode) -> Boolean = { true }
): List<LighterASTNode> {
val result = mutableListOf<LighterASTNode>()
fun FlyweightCapableTreeStructure<LighterASTNode>.collectDescendantByType(node: LighterASTNode) {
val childrenRef = Ref<Array<LighterASTNode?>>()
getChildren(node, childrenRef)
val childrenRefGet = childrenRef.get()
if (childrenRefGet != null) {
for (child in childrenRefGet) {
if (child?.tokenType == type && predicate(child)) {
result.add(child)
}
if (child != null) {
collectDescendantByType(child)
}
}
}
}
collectDescendantByType(node)
return result
}
private fun FlyweightCapableTreeStructure<LighterASTNode>.findChildByType(node: LighterASTNode, type: TokenSet): LighterASTNode? {
val childrenRef = Ref<Array<LighterASTNode?>>()
getChildren(node, childrenRef)
@@ -46,11 +46,22 @@ fun markRange(
tree: FlyweightCapableTreeStructure<LighterASTNode>,
originalNode: LighterASTNode
): List<TextRange> {
return listOf(markSingleElement(from, to, startOffset, endOffset, tree, originalNode))
}
fun markSingleElement(
from: LighterASTNode,
to: LighterASTNode,
startOffset: Int,
endOffset: Int,
tree: FlyweightCapableTreeStructure<LighterASTNode>,
originalNode: LighterASTNode
): TextRange {
val betterFrom = from.nonFillerFirstChildOrSelf(tree)
val betterTo = to.nonFillerLastChildOrSelf(tree)
val startDelta = tree.getStartOffset(betterFrom) - tree.getStartOffset(originalNode)
val endDelta = tree.getEndOffset(betterTo) - tree.getEndOffset(originalNode)
return listOf(TextRange(startDelta + startOffset, endDelta + endOffset))
return TextRange(startDelta + startOffset, endDelta + endOffset)
}
private val DOC_AND_COMMENT_TOKENS = setOf(
+2
View File
@@ -1,6 +1,7 @@
package
public val ______: _<kotlin.Int>
public var p: kotlin.Int?
public val something: kotlin.Any?
public val something2: kotlin.Any?
public fun __(/*0*/ ___: kotlin.Int, /*1*/ y: _<kotlin.Int>?): kotlin.Int
@@ -51,3 +52,4 @@ public final class _</*0*/ ________> {
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -74,7 +74,7 @@ class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(tes
val firFile = info.firFiles[file] ?: continue
var diagnostics = diagnosticsPerFile[firFile] ?: continue
if (AdditionalFilesDirectives.CHECK_TYPE in module.directives) {
diagnostics = diagnostics.filter { it.factory.name != Errors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS.name }
diagnostics = diagnostics.filter { it.factory.name != FirErrors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS.name }
}
val diagnosticsMetadataInfos = diagnostics.mapNotNull { diagnostic ->
if (!diagnosticsService.shouldRenderDiagnostic(module, diagnostic.factory.name)) return@mapNotNull null
@@ -0,0 +1,33 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE)
*
* SPEC VERSION: 0.1-100
* MAIN LINK: expressions, constant-literals, integer-literals, decimal-integer-literals -> paragraph 1 -> sentence 2
* NUMBER: 1
* DESCRIPTION: Integer literals with an underscore in the first position (it's considered as identifiers).
*/
// TESTCASE NUMBER: 1
val value_1 = <!UNRESOLVED_REFERENCE!>_5678_90<!>
// TESTCASE NUMBER: 2
val value_2 = <!UNRESOLVED_REFERENCE!>_2_3_4_5_6_7_8_9_<!>
// TESTCASE NUMBER: 3
val value_3 = <!UNRESOLVED_REFERENCE!>_____________0000<!>
// TESTCASE NUMBER: 4
val value_4 = <!UNRESOLVED_REFERENCE!>_______________________________________________________________________________________________________________________________________________________0<!>
// TESTCASE NUMBER: 5
val value_5 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>____________________________________________________<!>
// TESTCASE NUMBER: 6
val value_6 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>_<!>
// TESTCASE NUMBER: 7
val value_7 = <!UNRESOLVED_REFERENCE!>_0_<!>
// TESTCASE NUMBER: 8
val value_8 = <!UNRESOLVED_REFERENCE!>_9_<!>
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE)
*
@@ -21,10 +20,10 @@ val value_3 = <!UNRESOLVED_REFERENCE!>_____________0000<!>
val value_4 = <!UNRESOLVED_REFERENCE!>_______________________________________________________________________________________________________________________________________________________0<!>
// TESTCASE NUMBER: 5
val value_5 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>____________________________________________________<!>
val value_5 = <!UNRESOLVED_REFERENCE!>____________________________________________________<!>
// TESTCASE NUMBER: 6
val value_6 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>_<!>
val value_6 = <!UNRESOLVED_REFERENCE!>_<!>
// TESTCASE NUMBER: 7
val value_7 = <!UNRESOLVED_REFERENCE!>_0_<!>
@@ -0,0 +1,33 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE)
*
* SPEC VERSION: 0.1-100
* MAIN LINK: expressions, constant-literals, integer-literals, decimal-integer-literals -> paragraph 1 -> sentence 2
* NUMBER: 2
* DESCRIPTION: Integer literals with an underscore in the first position (it's considered as identifiers).
*/
// TESTCASE NUMBER: 1
val value_1 = <!UNRESOLVED_REFERENCE!>_5678_90<!>
// TESTCASE NUMBER: 2
val value_2 = <!UNRESOLVED_REFERENCE!>_2_3_4_5_6_7_8_9_<!>
// TESTCASE NUMBER: 3
val value_3 = <!UNRESOLVED_REFERENCE!>_____________0000<!>
// TESTCASE NUMBER: 4
val value_4 = <!UNRESOLVED_REFERENCE!>_______________________________________________________________________________________________________________________________________________________0<!>
// TESTCASE NUMBER: 5
val value_5 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>____________________________________________________<!>
// TESTCASE NUMBER: 6
val value_6 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>_<!>
// TESTCASE NUMBER: 7
val value_7 = <!UNRESOLVED_REFERENCE!>_0_<!>
// TESTCASE NUMBER: 8
val value_8 = <!UNRESOLVED_REFERENCE!>_9_<!>
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE)
*
@@ -21,10 +20,10 @@ val value_3 = <!UNRESOLVED_REFERENCE!>_____________0000<!>
val value_4 = <!UNRESOLVED_REFERENCE!>_______________________________________________________________________________________________________________________________________________________0<!>
// TESTCASE NUMBER: 5
val value_5 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>____________________________________________________<!>
val value_5 = <!UNRESOLVED_REFERENCE!>____________________________________________________<!>
// TESTCASE NUMBER: 6
val value_6 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>_<!>
val value_6 = <!UNRESOLVED_REFERENCE!>_<!>
// TESTCASE NUMBER: 7
val value_7 = <!UNRESOLVED_REFERENCE!>_0_<!>
@@ -0,0 +1,33 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE)
*
* SPEC VERSION: 0.1-100
* MAIN LINK: expressions, constant-literals, integer-literals, decimal-integer-literals -> paragraph 1 -> sentence 2
* NUMBER: 3
* DESCRIPTION: Integer literals with an underscore in the first position (it's considered as identifiers).
*/
// TESTCASE NUMBER: 1
val value_1 = <!UNRESOLVED_REFERENCE!>_5678_90<!>
// TESTCASE NUMBER: 2
val value_2 = <!UNRESOLVED_REFERENCE!>_2_3_4_5_6_7_8_9_<!>
// TESTCASE NUMBER: 3
val value_3 = <!UNRESOLVED_REFERENCE!>_____________0000<!>
// TESTCASE NUMBER: 4
val value_4 = <!UNRESOLVED_REFERENCE!>_______________________________________________________________________________________________________________________________________________________0<!>
// TESTCASE NUMBER: 5
val value_5 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>____________________________________________________<!>
// TESTCASE NUMBER: 6
val value_6 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>_<!>
// TESTCASE NUMBER: 7
val value_7 = <!UNRESOLVED_REFERENCE!>_0_<!>
// TESTCASE NUMBER: 8
val value_8 = <!UNRESOLVED_REFERENCE!>_9_<!>
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (NEGATIVE)
*
@@ -21,10 +20,10 @@ val value_3 = <!UNRESOLVED_REFERENCE!>_____________0000<!>
val value_4 = <!UNRESOLVED_REFERENCE!>_______________________________________________________________________________________________________________________________________________________0<!>
// TESTCASE NUMBER: 5
val value_5 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>____________________________________________________<!>
val value_5 = <!UNRESOLVED_REFERENCE!>____________________________________________________<!>
// TESTCASE NUMBER: 6
val value_6 = <!UNDERSCORE_USAGE_WITHOUT_BACKTICKS, UNRESOLVED_REFERENCE!>_<!>
val value_6 = <!UNRESOLVED_REFERENCE!>_<!>
// TESTCASE NUMBER: 7
val value_7 = <!UNRESOLVED_REFERENCE!>_0_<!>