Add intention to evaluate compile time expression

#KT-40251 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-09-13 19:17:00 +09:00
committed by klunnii
parent 8cac3f654c
commit d9cf4ee732
37 changed files with 233 additions and 0 deletions
@@ -0,0 +1,5 @@
<html>
<body>
This intention replaces compile-time constant expression with its actual value.
</body>
</html>
@@ -2224,6 +2224,7 @@ inspection.redundant.nullable.return.type.display.name=Redundant nullable return
0.is.always.non.null.type=''{0}'' is always non-null type
inspection.simplifiable.scope.function.display.name=Scope function with nested forEach can be simplified
nested.1.call.in.0.could.be.simplified.to.2=Nested ''{1}'' call in ''{0}'' could be simplified to {2}
evaluate.compile.time.expression=Evaluate compile-time expression
hints.title.codevision=Code Vision
hints.title.codevision.show.hints.for=Show hints for:
+4
View File
@@ -835,6 +835,10 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.EvaluateCompileTimeExpressionIntention</className>
<category>Kotlin</category>
</intentionAction>
<lang.inspectionSuppressor language="kotlin" implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinInspectionSuppressor"/>
@@ -0,0 +1,68 @@
/*
* 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.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class EvaluateCompileTimeExpressionIntention : SelfTargetingOffsetIndependentIntention<KtBinaryExpression>(
KtBinaryExpression::class.java,
KotlinBundle.lazyMessage("evaluate.compile.time.expression")
) {
companion object {
val constantNodeTypes = listOf(KtNodeTypes.FLOAT_CONSTANT, KtNodeTypes.CHARACTER_CONSTANT, KtNodeTypes.INTEGER_CONSTANT)
}
override fun isApplicableTo(element: KtBinaryExpression): Boolean {
if (element.getStrictParentOfType<KtBinaryExpression>() != null || !element.isConstantExpression()) return false
val constantValue = element.getConstantValue() ?: return false
setTextGetter { KotlinBundle.message("replace.with.0", constantValue) }
return true
}
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
val constantValue = element.getConstantValue() ?: return
element.replace(KtPsiFactory(element).createExpression(constantValue))
}
private fun KtExpression?.isConstantExpression(): Boolean {
return when (val expression = KtPsiUtil.deparenthesize(this)) {
is KtConstantExpression -> expression.elementType in constantNodeTypes
is KtPrefixExpression -> expression.baseExpression.isConstantExpression()
is KtBinaryExpression -> expression.left.isConstantExpression() && expression.right.isConstantExpression()
else -> false
}
}
private fun KtBinaryExpression.getConstantValue(): String? {
val context = analyze(BodyResolveMode.PARTIAL)
val type = getType(context) ?: return null
val constantValue = ConstantExpressionEvaluator.getConstant(this, context)?.toConstantValue(type) ?: return null
return when (val value = constantValue.value) {
is Char -> "'${StringUtil.escapeStringCharacters(value.toString())}'"
is Long -> "${value}L"
is Float -> when {
value.isNaN() -> "Float.NaN"
value.isInfinite() -> if (value > 0.0f) "Float.POSITIVE_INFINITY" else "Float.NEGATIVE_INFINITY"
else -> "${value}f"
}
is Double -> when {
value.isNaN() -> "Double.NaN"
value.isInfinite() -> if (value > 0.0) "Double.POSITIVE_INFINITY" else "Double.NEGATIVE_INFINITY"
else -> value.toString()
}
else -> value.toString()
}
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.EvaluateCompileTimeExpressionIntention
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
val x = <caret>true && false
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with ''b''"
val x = <caret>'a' + 1
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with ''b''"
val x = 'b'
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with ''\n''"
val x = <caret>'\t' + 1
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with ''\n''"
val x = '\n'
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '3.0'"
val x = <caret>1.0 + 2.0
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '3.0'"
val x = 3.0
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Double.NaN'"
val x = <caret>0.0 / 0.0
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Double.NaN'"
val x = Double.NaN
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Double.NEGATIVE_INFINITY'"
val x = <caret>-1.0 / 0.0
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Double.NEGATIVE_INFINITY'"
val x = Double.NEGATIVE_INFINITY
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Double.POSITIVE_INFINITY'"
val x = <caret>1.0 / 0.0
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Double.POSITIVE_INFINITY'"
val x = Double.POSITIVE_INFINITY
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '3.0f'"
val x = <caret>1.0f + 2.0f
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '3.0f'"
val x = 3.0f
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Float.NaN'"
val x = <caret>0.0f / 0.0f
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Float.NaN'"
val x = Float.NaN
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Float.NEGATIVE_INFINITY'"
val x = <caret>-1.0f / 0.0f
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Float.NEGATIVE_INFINITY'"
val x = Float.NEGATIVE_INFINITY
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Float.POSITIVE_INFINITY'"
val x = <caret>1.0f / 0.0f
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with 'Float.POSITIVE_INFINITY'"
val x = Float.POSITIVE_INFINITY
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '5'"
val x = <caret>2 + 3
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '5'"
val x = 5
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '5L'"
val x = 2L + 3L<caret>
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '5L'"
val x = 5L
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '14'"
val x = ((1 + 2) * (-3 + 4))<caret> + (5 + 6)
@@ -0,0 +1,2 @@
// INTENTION_TEXT: "Replace with '14'"
val x = 14
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
val x = <caret>"a" + "b"
@@ -0,0 +1,3 @@
// IS_APPLICABLE: false
val i = 3
val x = <caret>2 + i
@@ -8804,6 +8804,99 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/evaluateCompileTimeExpression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluateCompileTimeExpression extends AbstractIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInEvaluateCompileTimeExpression() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/evaluateCompileTimeExpression"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
}
@TestMetadata("boolean.kt")
public void testBoolean() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/boolean.kt");
}
@TestMetadata("char.kt")
public void testChar() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/char.kt");
}
@TestMetadata("char2.kt")
public void testChar2() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/char2.kt");
}
@TestMetadata("double.kt")
public void testDouble() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/double.kt");
}
@TestMetadata("doubleNaN.kt")
public void testDoubleNaN() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/doubleNaN.kt");
}
@TestMetadata("doubleNegativeInfinity.kt")
public void testDoubleNegativeInfinity() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/doubleNegativeInfinity.kt");
}
@TestMetadata("doublePositiveInfinity.kt")
public void testDoublePositiveInfinity() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/doublePositiveInfinity.kt");
}
@TestMetadata("float.kt")
public void testFloat() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/float.kt");
}
@TestMetadata("floatNaN.kt")
public void testFloatNaN() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/floatNaN.kt");
}
@TestMetadata("floatNegativeInfinity.kt")
public void testFloatNegativeInfinity() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/floatNegativeInfinity.kt");
}
@TestMetadata("floatPositiveInfinity.kt")
public void testFloatPositiveInfinity() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/floatPositiveInfinity.kt");
}
@TestMetadata("int.kt")
public void testInt() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/int.kt");
}
@TestMetadata("long.kt")
public void testLong() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/long.kt");
}
@TestMetadata("parens.kt")
public void testParens() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/parens.kt");
}
@TestMetadata("string.kt")
public void testString() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/string.kt");
}
@TestMetadata("variable.kt")
public void testVariable() throws Exception {
runTest("idea/testData/intentions/evaluateCompileTimeExpression/variable.kt");
}
}
@TestMetadata("idea/testData/intentions/expandBooleanExpression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)