Skip to content

Commit e7126b3

Browse files
leveretkaGodin
authored andcommitted
SONARKT-400 Migrate predictRuntimeValue family of functions to kotlin-analysis-api
1 parent 096bf2e commit e7126b3

File tree

1 file changed

+215
-2
lines changed
  • sonar-kotlin-api/src/main/java/org/sonarsource/kotlin/api/checks

1 file changed

+215
-2
lines changed

sonar-kotlin-api/src/main/java/org/sonarsource/kotlin/api/checks/ApiExtensions.kt

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ import com.intellij.psi.PsiComment
2020
import com.intellij.psi.PsiElement
2121
import com.intellij.psi.PsiWhiteSpace
2222
import com.intellij.psi.impl.source.tree.LeafPsiElement
23+
import com.intellij.psi.util.PsiTreeUtil
24+
import org.jetbrains.kotlin.analysis.api.resolution.KaCall
25+
import org.jetbrains.kotlin.analysis.api.resolution.KaExplicitReceiverValue
26+
import org.jetbrains.kotlin.analysis.api.resolution.KaFunctionCall
27+
import org.jetbrains.kotlin.analysis.api.resolution.KaImplicitReceiverValue
28+
import org.jetbrains.kotlin.analysis.api.resolution.successfulCallOrNull
29+
import org.jetbrains.kotlin.analysis.api.resolution.successfulFunctionCallOrNull
30+
import org.jetbrains.kotlin.analysis.api.resolution.symbol
31+
import org.jetbrains.kotlin.analysis.api.symbols.KaAnonymousFunctionSymbol
32+
import org.jetbrains.kotlin.analysis.api.symbols.KaSymbol
33+
import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol
34+
import org.jetbrains.kotlin.analysis.api.symbols.KaVariableSymbol
35+
import org.jetbrains.kotlin.analysis.api.symbols.name
2336
import org.jetbrains.kotlin.analysis.api.types.KaClassType
2437
import org.jetbrains.kotlin.analysis.api.types.KaType
2538
import org.jetbrains.kotlin.coroutines.hasSuspendFunctionType
@@ -32,18 +45,22 @@ import org.jetbrains.kotlin.descriptors.SyntheticPropertyDescriptor
3245
import org.jetbrains.kotlin.descriptors.ValueDescriptor
3346
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
3447
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
48+
import org.jetbrains.kotlin.idea.references.mainReference
3549
import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName
3650
import org.jetbrains.kotlin.lexer.KtTokens
3751
import org.jetbrains.kotlin.psi.Call
3852
import org.jetbrains.kotlin.psi.KtAnnotated
3953
import org.jetbrains.kotlin.psi.KtAnnotationEntry
54+
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
4055
import org.jetbrains.kotlin.psi.KtBinaryExpression
4156
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
4257
import org.jetbrains.kotlin.psi.KtBlockExpression
58+
import org.jetbrains.kotlin.psi.KtCallElement
4359
import org.jetbrains.kotlin.psi.KtCallExpression
4460
import org.jetbrains.kotlin.psi.KtClass
4561
import org.jetbrains.kotlin.psi.KtClassOrObject
4662
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
63+
import org.jetbrains.kotlin.psi.KtElement
4764
import org.jetbrains.kotlin.psi.KtExpression
4865
import org.jetbrains.kotlin.psi.KtFunction
4966
import org.jetbrains.kotlin.psi.KtFunctionLiteral
@@ -60,6 +77,7 @@ import org.jetbrains.kotlin.psi.KtStringTemplateExpression
6077
import org.jetbrains.kotlin.psi.KtThisExpression
6178
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
6279
import org.jetbrains.kotlin.psi.KtTypeReference
80+
import org.jetbrains.kotlin.psi.KtUnaryExpression
6381
import org.jetbrains.kotlin.psi.KtValueArgument
6482
import org.jetbrains.kotlin.psi.KtWhenExpression
6583
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
@@ -114,36 +132,57 @@ private val STRING_TO_BYTE_FUNS = listOf(
114132
FunMatcher(qualifier = "java.lang.String", name = "getBytes")
115133
)
116134

135+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.predictRuntimeStringValue()"))
117136
fun KtExpression.predictRuntimeStringValue(bindingContext: BindingContext) =
118137
predictRuntimeValueExpression(bindingContext).stringValue(bindingContext)
119138

139+
fun KtExpression.predictRuntimeStringValue() =
140+
predictRuntimeValueExpression().stringValue()
141+
120142
fun KtExpression.predictRuntimeStringValueWithSecondaries(bindingContext: BindingContext) =
121143
mutableListOf<PsiElement>().let {
122144
predictRuntimeValueExpression(bindingContext, it)
123145
.stringValue(bindingContext, it) to it
124146
}
125147

148+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.predictRuntimeIntValue()"))
126149
fun KtExpression.predictRuntimeIntValue(bindingContext: BindingContext) =
127150
predictRuntimeValueExpression(bindingContext).let { runtimeValueExpression ->
128151
runtimeValueExpression.getType(bindingContext)?.let {
129152
bindingContext[BindingContext.COMPILE_TIME_VALUE, runtimeValueExpression]?.getValue(it) as? Int
130153
}
131154
}
132155

156+
fun KtExpression.predictRuntimeIntValue(): Int? = withKaSession {
157+
val valueExpression = predictRuntimeValueExpression()
158+
if (valueExpression.expressionType?.isIntType == true) {
159+
valueExpression.evaluate()?.value as? Int
160+
} else null
161+
}
162+
163+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.predictRuntimeBooleanValue()"))
133164
fun KtExpression.predictRuntimeBooleanValue(bindingContext: BindingContext) =
134165
predictRuntimeValueExpression(bindingContext).let { runtimeValueExpression ->
135166
runtimeValueExpression.getType(bindingContext)?.let {
136167
bindingContext[BindingContext.COMPILE_TIME_VALUE, runtimeValueExpression]?.getValue(it) as? Boolean
137168
}
138169
}
139170

171+
fun KtExpression.predictRuntimeBooleanValue() = withKaSession {
172+
val valueExpression = predictRuntimeValueExpression()
173+
if (valueExpression.expressionType?.isBooleanType == true) {
174+
valueExpression.evaluate()?.value as? Boolean
175+
} else null
176+
}
177+
140178
/**
141179
* In Kotlin, we may often be dealing with expressions that can already statically be resolved to prior and more accurate expressions that
142180
* they will alias at runtime. A good example of this are `it` within `let` and `also` scopes, as well as `this` within `with`, `apply`
143181
* and `run` scopes. Other examples include constants assigned to a property elsewhere.
144182
*
145183
* This function will try to resolve the current expression as far as it statically can, including deparenthesizing the expression.
146184
*/
185+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.predictRuntimeValueExpression(declarations)"))
147186
fun KtExpression.predictRuntimeValueExpression(
148187
bindingContext: BindingContext,
149188
declarations: MutableList<PsiElement> = mutableListOf(),
@@ -165,6 +204,35 @@ fun KtExpression.predictRuntimeValueExpression(
165204
} ?: deparenthesized as? KtExpression
166205
} ?: this
167206

207+
fun KtExpression.predictRuntimeValueExpression(
208+
declarations: MutableList<PsiElement> = mutableListOf(),
209+
): KtExpression = this.deparenthesize().let { deparenthesized ->
210+
when (deparenthesized) {
211+
is KtReferenceExpression -> run {
212+
val referenceTarget = deparenthesized.extractLetAlsoTargetExpression()
213+
?: deparenthesized.extractFromInitializer(declarations)
214+
215+
referenceTarget?.predictRuntimeValueExpression(declarations)
216+
}
217+
218+
is KtParenthesizedExpression -> deparenthesized.expression?.predictRuntimeValueExpression(declarations)
219+
220+
is KtBinaryExpressionWithTypeRHS -> deparenthesized.left.predictRuntimeValueExpression(declarations)
221+
222+
is KtThisExpression -> withKaSession {
223+
var symbol = deparenthesized.instanceReference.mainReference.resolveToSymbol()
224+
// TODO investigate: in K1 the symbol is anonymous function symbol, in K2 it is a parameter
225+
if (symbol !is KaAnonymousFunctionSymbol) symbol = symbol?.containingSymbol
226+
symbol?.findFunctionLiteral(deparenthesized)?.findLetAlsoRunWithTargetExpression()
227+
}
228+
229+
else -> withKaSession {
230+
deparenthesized.resolveToCall()?.successfulFunctionCallOrNull()?.predictValueExpression()
231+
}
232+
} ?: deparenthesized as? KtExpression
233+
} ?: this
234+
235+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.predictReceiverExpression()"))
168236
fun KtCallExpression.predictReceiverExpression(
169237
bindingContext: BindingContext,
170238
precomputedResolvedCall: ResolvedCall<*>? = null,
@@ -180,6 +248,18 @@ fun KtCallExpression.predictReceiverExpression(
180248
return resolvedCall?.getImplicitReceiverValue()?.extractWithRunApplyTargetExpression(this, bindingContext)
181249
}
182250

251+
fun KtExpression.predictReceiverExpression(): KtExpression? = withKaSession {
252+
val resolvedCall = this@predictReceiverExpression.resolveToCall()?.successfulFunctionCallOrNull()
253+
val symbol = resolvedCall?.partiallyAppliedSymbol
254+
val receiver = symbol?.extensionReceiver ?: symbol?.dispatchReceiver
255+
when (receiver) {
256+
is KaExplicitReceiverValue -> receiver.expression.predictRuntimeValueExpression()
257+
is KaImplicitReceiverValue -> receiver.symbol.containingSymbol
258+
?.findFunctionLiteral(this@predictReceiverExpression)?.findLetAlsoRunWithTargetExpression()
259+
else -> null
260+
}
261+
}
262+
183263
fun KtStringTemplateExpression.asString() = entries.joinToString("") { it.text }
184264

185265
fun PsiElement.linesOfCode(): Set<Int> {
@@ -209,6 +289,7 @@ fun PsiComment.getContent() =
209289
/**
210290
* @param declarations is used to collect all visited declaration for reporting secondary locations
211291
*/
292+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.stringValue(declarations)"))
212293
private fun KtExpression.stringValue(
213294
bindingContext: BindingContext,
214295
declarations: MutableList<PsiElement> = mutableListOf(),
@@ -240,11 +321,50 @@ private fun KtExpression.stringValue(
240321
else -> null
241322
}
242323

324+
fun KtExpression.stringValue(
325+
declarations: MutableList<PsiElement> = mutableListOf(),
326+
): String? = withKaSession {
327+
when (this@stringValue) {
328+
is KtStringTemplateExpression -> {
329+
val entries = entries.map {
330+
if (it.expression != null) it.expression!!.stringValue(declarations) else it.text
331+
}
332+
if (entries.all { it != null }) entries.joinToString("") else null
333+
}
334+
335+
is KtNameReferenceExpression -> {
336+
(this@stringValue.mainReference.resolveToSymbol() as? KaVariableSymbol)
337+
?.let {
338+
if (it.isVal) {
339+
(it.psi as? KtProperty)
340+
?.apply { declarations.add(this) }
341+
?.delegateExpressionOrInitializer?.stringValue(declarations)
342+
} else null
343+
}
344+
}
345+
346+
is KtDotQualifiedExpression -> selectorExpression?.stringValue(declarations)
347+
is KtBinaryExpression ->
348+
if (operationToken == KtTokens.PLUS)
349+
left?.stringValue(declarations)?.plus(right?.stringValue(declarations))
350+
else null
351+
352+
else -> null
353+
}
354+
}
355+
356+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.predictValueExpression()"))
243357
private fun Call.predictValueExpression(bindingContext: BindingContext) =
244358
if (GET_PROP_WITH_DEFAULT_MATCHER.matches(this, bindingContext)) {
245359
valueArguments[1].getArgumentExpression()
246360
} else null
247361

362+
private fun KaFunctionCall<*>.predictValueExpression(): KtExpression? =
363+
if (GET_PROP_WITH_DEFAULT_MATCHER.matches(this)) {
364+
argumentMapping.keys.elementAt(1)
365+
} else null
366+
367+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.extractFromInitializer()"))
248368
private fun KtReferenceExpression.extractFromInitializer(
249369
bindingContext: BindingContext,
250370
declarations: MutableList<PsiElement> = mutableListOf(),
@@ -258,18 +378,46 @@ private fun KtReferenceExpression.extractFromInitializer(
258378
} else null
259379
}
260380

381+
382+
private fun KtReferenceExpression.extractFromInitializer(
383+
declarations: MutableList<PsiElement> = mutableListOf(),
384+
) = withKaSession {
385+
(this@extractFromInitializer.mainReference.resolveToSymbol() as? KaVariableSymbol)
386+
?.let {
387+
if (it.isVal) {
388+
(it.psi as? KtProperty)
389+
?.apply { declarations.add(this) }
390+
?.delegateExpressionOrInitializer?.predictRuntimeValueExpression(declarations)
391+
} else null
392+
}
393+
}
394+
261395
/**
262396
* Will try to resolve what `it` is an alias for inside of a `let` or `also` scope.
263397
*/
398+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.extractLetAlsoTargetExpression()"))
264399
private fun KtReferenceExpression.extractLetAlsoTargetExpression(bindingContext: BindingContext) =
265400
findReceiverScopeFunctionLiteral(bindingContext)?.findLetAlsoRunWithTargetExpression(bindingContext)
266401

402+
private fun KtReferenceExpression.extractLetAlsoTargetExpression() =
403+
findReceiverScopeFunctionLiteral()?.findLetAlsoRunWithTargetExpression()
404+
267405
/**
268406
* Will try to resolve what `this` is an alias for inside a `with`, `run` or `apply` scope.
269407
*/
270-
private fun ImplicitReceiver.extractWithRunApplyTargetExpression(startNode: PsiElement, bindingContext: BindingContext) =
408+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.extractWithRunApplyTargetExpression()"))
409+
private fun ImplicitReceiver.extractWithRunApplyTargetExpression(
410+
startNode: PsiElement,
411+
bindingContext: BindingContext
412+
) =
271413
findReceiverScopeFunctionLiteral(startNode, bindingContext)?.findLetAlsoRunWithTargetExpression(bindingContext)
272414

415+
private fun KaImplicitReceiverValue.extractWithRunApplyTargetExpression(
416+
startNode: PsiElement
417+
) =
418+
findReceiverScopeFunctionLiteral(startNode)?.findLetAlsoRunWithTargetExpression()
419+
420+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.findLetAlsoRunWithTargetExpression()"))
273421
private fun KtFunctionLiteral.findLetAlsoRunWithTargetExpression(bindingContext: BindingContext): KtExpression? =
274422
getParentCall(bindingContext)?.let { larwCallCandidate ->
275423
when (larwCallCandidate.callElement.getCalleeExpressionIfAny()?.text) {
@@ -286,13 +434,66 @@ private fun KtFunctionLiteral.findLetAlsoRunWithTargetExpression(bindingContext:
286434
}
287435
}
288436

437+
private fun KtFunctionLiteral.findLetAlsoRunWithTargetExpression(): KtExpression? = withKaSession {
438+
(getParentCall() as? KaFunctionCall<*>)?.let { larwCallCandidate ->
439+
withKaSession {
440+
when (larwCallCandidate.partiallyAppliedSymbol.symbol.name?.asString()) {
441+
in KOTLIN_CHAIN_CALL_CONSTRUCTS -> {
442+
(larwCallCandidate.partiallyAppliedSymbol.extensionReceiver as? KaExplicitReceiverValue)?.expression?.predictRuntimeValueExpression()
443+
}
444+
445+
"with" -> {
446+
larwCallCandidate.getFirstArgumentExpression()
447+
?.predictRuntimeValueExpression()
448+
}
449+
450+
else -> null
451+
}
452+
}
453+
}
454+
}
455+
456+
fun KtElement.getParentCall(): KaCall? {
457+
val callExpressionTypes = arrayOf(
458+
KtSimpleNameExpression::class.java,
459+
KtCallElement::class.java,
460+
KtBinaryExpression::class.java,
461+
KtUnaryExpression::class.java,
462+
KtArrayAccessExpression::class.java
463+
)
464+
val parentOfType = PsiTreeUtil.getParentOfType(this, *callExpressionTypes)
465+
return withKaSession { parentOfType?.resolveToCall()?.successfulCallOrNull() }
466+
}
467+
468+
fun KaFunctionCall<*>.getFirstArgumentExpression() =
469+
argumentMapping.keys.elementAtOrNull(0)
470+
471+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.findReceiverScopeFunctionLiteral()"))
289472
private fun KtReferenceExpression.findReceiverScopeFunctionLiteral(bindingContext: BindingContext): KtFunctionLiteral? =
290473
(bindingContext[BindingContext.REFERENCE_TARGET, this] as? ValueParameterDescriptor)?.containingDeclaration
291474
?.findFunctionLiteral(this, bindingContext)
292475

293-
private fun ImplicitReceiver.findReceiverScopeFunctionLiteral(startNode: PsiElement, bindingContext: BindingContext): KtFunctionLiteral? =
476+
private fun KtReferenceExpression.findReceiverScopeFunctionLiteral(): KtFunctionLiteral? = withKaSession {
477+
when (val resolvedSymbol = this@findReceiverScopeFunctionLiteral.mainReference.resolveToSymbol()) {
478+
is KaValueParameterSymbol -> resolvedSymbol.containingSymbol
479+
?.findFunctionLiteral(this@findReceiverScopeFunctionLiteral)
480+
else -> null
481+
}
482+
}
483+
484+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.findReceiverScopeFunctionLiteral(startNode)"))
485+
private fun ImplicitReceiver.findReceiverScopeFunctionLiteral(
486+
startNode: PsiElement,
487+
bindingContext: BindingContext
488+
): KtFunctionLiteral? =
294489
declarationDescriptor.findFunctionLiteral(startNode, bindingContext)
295490

491+
private fun KaImplicitReceiverValue.findReceiverScopeFunctionLiteral(
492+
startNode: PsiElement,
493+
): KtFunctionLiteral? =
494+
symbol.findFunctionLiteral(startNode)
495+
496+
@Deprecated("use kotlin-analysis-api instead", ReplaceWith("this.findFunctionLiteral(startNode)"))
296497
private fun DeclarationDescriptor.findFunctionLiteral(
297498
startNode: PsiElement,
298499
bindingContext: BindingContext,
@@ -307,6 +508,18 @@ private fun DeclarationDescriptor.findFunctionLiteral(
307508
return null
308509
}
309510

511+
private fun KaSymbol.findFunctionLiteral(
512+
startNode: PsiElement,
513+
): KtFunctionLiteral? = withKaSession {
514+
var curNode: PsiElement? = startNode
515+
for (i in 0 until MAX_AST_PARENT_TRAVERSALS) {
516+
curNode = curNode?.parent ?: break
517+
if (curNode is KtFunctionLiteral && curNode.symbol == this@findFunctionLiteral)
518+
return curNode
519+
}
520+
return null
521+
}
522+
310523
fun KtNamedFunction.overrides() = modifierList?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false
311524

312525
fun KtNamedFunction.isAbstract() = modifierList?.hasModifier(KtTokens.ABSTRACT_KEYWORD) ?: false

0 commit comments

Comments
 (0)