Compute constant expression as an inspection; minor improvements

Works for unary operation
Do not suggested if replacement is the same as the original code
Replacement is displayed in the quick-fix name
This commit is contained in:
Tagir Valeev
2018-01-25 15:43:48 +07:00
parent 20bfde4499
commit 6cc8936598
22 changed files with 267 additions and 192 deletions
@@ -0,0 +1,7 @@
<html>
<body>
Allows you to replace compile-time constant expression with its actual value (e.g. "2 + 2" with "4").
<!-- tooltip end -->
<small>New in 2018.1</small>
</body>
</html>
@@ -0,0 +1,7 @@
// "Compute constant value of '"The quick brown fox jumps " + 100000 + " times" + " over the lazy dog"'" "true"
class Test {
void test() {
// Do not display the result in action name
String foo = "The quick brown fox jumps 100000 times over the lazy dog";
}
}
@@ -0,0 +1,4 @@
// "Replace '2 * 2' with constant value '4'" "true"
class Test {
int x = 4;
}
@@ -0,0 +1,16 @@
// "Fix all 'Constant expression can be evaluated' problems in file" "true"
class Test {
void test() {
String[] squares = new String[10];
squares[0] = "0*0=0";
squares[1] = "1*1=1";
squares[2] = "2*2=4";
squares[3] = "3*3=9";
squares[4] = "4*4=16";
squares[5] = "5*5=25";
squares[6] = "6*6=36";
squares[7] = "7*7=49";
squares[8] = "8*8=64";
squares[9] = "9*9=81";
}
}
@@ -0,0 +1,4 @@
// "Replace '~2' with constant value '-3'" "true"
class Test {
int x = -3;
}
@@ -0,0 +1,7 @@
// "Compute constant value of '"The quick brown fox jumps " + 100000 + " times" + " over the lazy dog"'" "true"
class Test {
void test() {
// Do not display the result in action name
String foo = "The quick brown fox jumps " + 10<caret>0000 + " times" + " over the lazy dog";
}
}
@@ -0,0 +1,4 @@
// "Replace '2 * 2' with constant value '4'" "true"
class Test {
int x = 2*<caret>2;
}
@@ -0,0 +1,16 @@
// "Fix all 'Constant expression can be evaluated' problems in file" "true"
class Test {
void test() {
String[] squares = new String[10];
squares[0] = 0 <caret>+ "*" + 0 + "=" + 0 * 0;
squares[1] = 1 + "*" + 1 + "=" + 1 * 1;
squares[2] = 2 + "*" + 2 + "=" + 2 * 2;
squares[3] = 3 + "*" + 3 + "=" + 3 * 3;
squares[4] = 4 + "*" + 4 + "=" + 4 * 4;
squares[5] = 5 + "*" + 5 + "=" + 5 * 5;
squares[6] = 6 + "*" + 6 + "=" + 6 * 6;
squares[7] = 7 + "*" + 7 + "=" + 7 * 7;
squares[8] = 8 + "*" + 8 + "=" + 8 * 8;
squares[9] = 9 + "*" + 9 + "=" + 9 * 9;
}
}
@@ -0,0 +1,4 @@
// "Replace '~2' with constant value '-3'" "true"
class Test {
int x = ~<caret>2;
}
@@ -0,0 +1,4 @@
// "Fix all 'Constant expression can be evaluated' problems in file" "false"
class Test {
int x = -<caret>2;
}
@@ -0,0 +1,11 @@
// "Fix all 'Constant expression can be evaluated' problems in file" "false"
class Test {
void test() {
// Do not suggest to compute for performance reasons
String foo = "The quick brown fox jumps " + 10<caret>0000 + "times" + "over the lazy dog"+
"The quick brown fox jumps " + 100000 + "times" + "over the lazy dog"+
"The quick brown fox jumps " + 100000 + "times" + "over the lazy dog"+
"The quick brown fox jumps " + 100000 + "times" + "over the lazy dog"+
"The quick brown fox jumps " + 100000 + "times" + "over the lazy dog";
}
}
@@ -2261,4 +2261,9 @@ copy.constructor.misses.field.problem.descriptor.3=Copy constructor does not cop
copy.constructor.misses.field.problem.descriptor.many=Copy constructor does not copy {0} fields
fix.add.argument.family.name=Add argument
fix.add.argument.name=Add ''{0}'' argument
fix.add.argument.name=Add ''{0}'' argument
inspection.constant.expression.display.name=Constant expression can be evaluated
inspection.constant.expression.fix.name=Compute constant value of ''{0}''
inspection.constant.expression.fix.name.with.value=Replace ''{0}'' with constant value ''{1}''
inspection.constant.expression.fix.family.name=Compute constant value
@@ -0,0 +1,150 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.style;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.ConstantEvaluationOverflowException;
import com.intellij.psi.util.PsiExpressionTrimRenderer;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class ConstantExpressionInspection extends AbstractBaseJavaLocalInspectionTool {
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitUnaryExpression(PsiUnaryExpression expression) {
handle(expression);
}
@Override
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
handle(expression);
}
void handle(PsiExpression expression) {
// intention disabled for long expressions because of performance issues on
// relatively common large string expressions.
if (expression.getTextLength() > 200) return;
if (expression.getType() == null) return;
if (!PsiUtil.isConstantExpression(expression)) return;
final PsiElement parent = expression.getParent();
if (parent instanceof PsiExpression && PsiUtil.isConstantExpression((PsiExpression)parent)) return;
try {
final Object value = ExpressionUtils.computeConstantExpression(expression, true);
if (value != null) {
String valueText = getValueText(value);
if (!expression.textMatches(valueText)) {
holder.registerProblem(expression, InspectionGadgetsBundle.message("inspection.constant.expression.display.name"),
new ComputeConstantValueFix(expression, valueText));
}
}
}
catch (ConstantEvaluationOverflowException ignore) {
}
}
};
}
private static class ComputeConstantValueFix implements LocalQuickFix {
private final String myText;
private String myValueText;
public ComputeConstantValueFix(PsiExpression expression, String valueText) {
myText = PsiExpressionTrimRenderer.render(expression);
myValueText = valueText;
}
@Nls
@NotNull
@Override
public String getName() {
if (myValueText.length() > 50) {
return InspectionGadgetsBundle.message("inspection.constant.expression.fix.name", myText);
}
return InspectionGadgetsBundle.message("inspection.constant.expression.fix.name.with.value", myText, myValueText);
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("inspection.constant.expression.fix.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiExpression expression = (PsiExpression)descriptor.getStartElement();
final Object value = ExpressionUtils.computeConstantExpression(expression);
@NonNls final String newExpression = getValueText(value);
PsiReplacementUtil.replaceExpression(expression, newExpression, new CommentTracker());
}
}
private static String getValueText(Object value) {
@NonNls final String newExpression;
if (value instanceof String) {
final String string = (String)value;
newExpression = '"' + StringUtil.escapeStringCharacters(string) + '"';
}
else if (value instanceof Character) {
newExpression = '\'' + StringUtil.escapeStringCharacters(value.toString()) + '\'';
}
else if (value instanceof Long) {
newExpression = value.toString() + 'L';
}
else if (value instanceof Double) {
final double v = ((Double)value).doubleValue();
if (Double.isNaN(v)) {
newExpression = "java.lang.Double.NaN";
}
else if (Double.isInfinite(v)) {
if (v > 0.0) {
newExpression = "java.lang.Double.POSITIVE_INFINITY";
}
else {
newExpression = "java.lang.Double.NEGATIVE_INFINITY";
}
}
else {
newExpression = Double.toString(v);
}
}
else if (value instanceof Float) {
final float v = ((Float)value).floatValue();
if (Float.isNaN(v)) {
newExpression = "java.lang.Float.NaN";
}
else if (Float.isInfinite(v)) {
if (v > 0.0F) {
newExpression = "java.lang.Float.POSITIVE_INFINITY";
}
else {
newExpression = "java.lang.Float.NEGATIVE_INFINITY";
}
}
else {
newExpression = Float.toString(v) + 'f';
}
}
else if (value == null) {
newExpression = "null";
}
else {
newExpression = String.valueOf(value);
}
return newExpression;
}
}
@@ -2788,6 +2788,10 @@
bundle="com.siyeh.InspectionGadgetsBundle" key="variable.type.can.be.explicit.display.name"
groupBundle="messages.InspectionsBundle" groupKey="group.names.language.level.specific.issues.and.migration.aids10"
implementationClass="com.intellij.codeInspection.VariableTypeCanBeExplicitInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="ConstantExpression" enabledByDefault="true" level="INFORMATION"
bundle="com.siyeh.InspectionGadgetsBundle" key="inspection.constant.expression.display.name"
groupBundle="messages.InspectionsBundle" groupKey="group.names.code.style.issues"
implementationClass="com.siyeh.ig.style.ConstantExpressionInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,23 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.style;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import org.jetbrains.annotations.NotNull;
public class ConstantExpressionInspectionTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new ConstantExpressionInspection()
};
}
public void test() { doAllTests(); }
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/constantExpression";
}
}
@@ -435,11 +435,6 @@
<bundleName>com.siyeh.IntentionPowerPackBundle</bundleName>
<categoryKey>intention.category.other</categoryKey>
</intentionAction>
<intentionAction>
<className>com.siyeh.ipp.constant.ConstantExpressionIntention</className>
<bundleName>com.siyeh.IntentionPowerPackBundle</bundleName>
<categoryKey>intention.category.other</categoryKey>
</intentionAction>
<intentionAction>
<className>com.siyeh.ipp.constant.ConstantSubexpressionIntention</className>
<bundleName>com.siyeh.IntentionPowerPackBundle</bundleName>
@@ -169,7 +169,6 @@ negate.comparison.intention.name=Negate ''{0}''
negate.comparison.intention.name1=Negate ''{0}'' to ''{1}''
flip.commutative.method.call.intention.name=Flip ''.{0}()''
flip.commutative.method.call.intention.name1=Flip ''.{0}()'' (may change semantics)
constant.expression.intention.name=Compute constant value of ''{0}''
status.bar.escape.highlighting.message=Press Escape to remove the highlighting
1.fully.qualified.name.status.bar.escape.highlighting.message=1 fully qualified name replaced with import (press Escape to remove highlighting)
multiple.fully.qualified.names.status.bar.escape.highlighting.message={0} fully qualified names replaced with import (press Escape to remove highlighting)
@@ -1,102 +0,0 @@
/*
* Copyright 2003-2018 Dave Griffith, Bas Leijdekkers
*
* 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 com.siyeh.ipp.constant;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.siyeh.IntentionPowerPackBundle;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ipp.base.MutablyNamedIntention;
import com.siyeh.ipp.base.PsiElementPredicate;
import com.siyeh.ipp.psiutils.HighlightUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class ConstantExpressionIntention extends MutablyNamedIntention {
@Override
protected String getTextForElement(PsiElement element) {
final String text = HighlightUtil.getPresentableText(element);
return IntentionPowerPackBundle.message("constant.expression.intention.name", text);
}
@Override
@NotNull
protected PsiElementPredicate getElementPredicate() {
return new ConstantExpressionPredicate();
}
@Override
public void processIntention(PsiElement element) {
final PsiExpression expression = (PsiExpression)element;
final Object value = ExpressionUtils.computeConstantExpression(expression);
@NonNls final String newExpression;
if (value instanceof String) {
final String string = (String)value;
newExpression = '"' + StringUtil.escapeStringCharacters(string) + '"';
}
else if (value instanceof Character) {
newExpression = '\'' + StringUtil.escapeStringCharacters(value.toString()) + '\'';
}
else if (value instanceof Long) {
newExpression = value.toString() + 'L';
}
else if (value instanceof Double) {
final double v = ((Double)value).doubleValue();
if (Double.isNaN(v)) {
newExpression = "java.lang.Double.NaN";
}
else if (Double.isInfinite(v)) {
if (v > 0.0) {
newExpression = "java.lang.Double.POSITIVE_INFINITY";
}
else {
newExpression = "java.lang.Double.NEGATIVE_INFINITY";
}
}
else {
newExpression = Double.toString(v);
}
}
else if (value instanceof Float) {
final float v = ((Float)value).floatValue();
if (Float.isNaN(v)) {
newExpression = "java.lang.Float.NaN";
}
else if (Float.isInfinite(v)) {
if (v > 0.0F) {
newExpression = "java.lang.Float.POSITIVE_INFINITY";
}
else {
newExpression = "java.lang.Float.NEGATIVE_INFINITY";
}
}
else {
newExpression = Float.toString(v) + 'f';
}
}
else if (value == null) {
newExpression = "null";
}
else {
newExpression = String.valueOf(value);
}
PsiReplacementUtil.replaceExpression(expression, newExpression, new CommentTracker());
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2003-2013 Dave Griffith, Bas Leijdekkers
*
* 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 com.siyeh.ipp.constant;
import com.intellij.psi.*;
import com.intellij.psi.util.ConstantEvaluationOverflowException;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ipp.base.PsiElementPredicate;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
class ConstantExpressionPredicate implements PsiElementPredicate {
public boolean satisfiedBy(PsiElement element) {
if (!(element instanceof PsiPolyadicExpression)) {
return false;
}
if (element instanceof PsiLiteralExpression || element instanceof PsiClassObjectAccessExpression) {
return false;
}
final PsiPolyadicExpression expression = (PsiPolyadicExpression)element;
final PsiType expressionType = expression.getType();
if (expressionType == null || expressionType.equalsToText(JAVA_LANG_STRING)) {
// intention disabled for string concatenations because of performance issues on
// relatively common large string expressions.
return false;
}
final PsiExpression[] operands = expression.getOperands();
for (PsiExpression operand : operands) {
if (operand == null) {
return false;
}
final PsiType type = operand.getType();
if (type == null || type.equalsToText(JAVA_LANG_STRING)) {
return false;
}
}
if (!PsiUtil.isConstantExpression(expression)) {
return false;
}
try {
final Object value = ExpressionUtils.computeConstantExpression(expression, true);
if (value == null) {
return false;
}
}
catch (ConstantEvaluationOverflowException ignore) {
return false;
}
final PsiElement parent = element.getParent();
return !(parent instanceof PsiExpression) || !PsiUtil.isConstantExpression((PsiExpression)parent);
}
}
@@ -1,5 +0,0 @@
public class X {
void f() {
int i = 3600;
}
}
@@ -1,5 +0,0 @@
public class X {
void f() {
int i = <spot>60 * 60</spot>;
}
}
@@ -1,6 +0,0 @@
<html>
<body>
This intention calculates the value of a compile
time constant expression, and replaces it with an equivalent literal expression.
</body>
</html>