[java-highlighting] IDEA-265053 Highlight not-wellformed statements without semicolon as a "not-a-statement"

Also suggest 'introduce local variable' as a quick-fix

GitOrigin-RevId: 2a15a1d35ed1755ab043a40a06136ed946c4a516
This commit is contained in:
Tagir Valeev
2021-03-24 14:31:32 +07:00
committed by intellij-monorepo-bot
parent 8e045b6473
commit b5f948dd5f
16 changed files with 131 additions and 10 deletions

View File

@@ -516,4 +516,6 @@ public abstract class QuickFixFactory {
public abstract @NotNull IntentionAction createConvertInterfaceToClassFix(@NotNull PsiClass aClass);
public abstract @NotNull IntentionAction createUnwrapArrayInitializerMemberValueAction(@NotNull PsiArrayInitializerMemberValue arrayValue);
public abstract @NotNull IntentionAction createIntroduceVariableAction(@NotNull PsiExpression expression);
}

View File

@@ -14,8 +14,10 @@ import com.intellij.codeInsight.daemon.impl.quickfix.*;
import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
import com.intellij.codeInspection.LocalQuickFixOnPsiElementAsIntentionAdapter;
import com.intellij.core.JavaPsiBundle;
import com.intellij.ide.IdeBundle;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.openapi.diagnostic.Logger;
@@ -1538,7 +1540,20 @@ public final class HighlightUtil {
static HighlightInfo checkNotAStatement(@NotNull PsiStatement statement) {
if (!PsiUtil.isStatement(statement) && !PsiUtilCore.hasErrorElementChild(statement)) {
if (!PsiUtil.isStatement(statement)) {
PsiElement anchor = statement;
if (PsiUtilCore.hasErrorElementChild(statement)) {
boolean allowedError = false;
if (statement instanceof PsiExpressionStatement) {
PsiElement[] children = statement.getChildren();
if (children[0] instanceof PsiExpression && children[1] instanceof PsiErrorElement &&
((PsiErrorElement)children[1]).getErrorDescription().equals(JavaPsiBundle.message("expected.semicolon"))) {
allowedError = true;
anchor = children[0];
}
}
if (!allowedError) return null;
}
boolean isDeclarationNotAllowed = false;
if (statement instanceof PsiDeclarationStatement) {
PsiElement parent = statement.getParent();
@@ -1546,8 +1561,13 @@ public final class HighlightUtil {
}
String description = JavaErrorBundle.message(isDeclarationNotAllowed ? "declaration.not.allowed" : "not.a.statement");
HighlightInfo error =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create();
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(anchor).descriptionAndTooltip(description).create();
if (statement instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement)statement).getExpression();
if (statement.getParent() instanceof PsiCodeBlock) {
QuickFixAction.registerQuickFixAction(error, PriorityIntentionActionWrapper
.highPriority(getFixFactory().createIntroduceVariableAction(expression)));
}
QuickFixAction.registerQuickFixAction(error, getFixFactory().createDeleteSideEffectAwareFix((PsiExpressionStatement)statement));
}
return error;

View File

@@ -0,0 +1,28 @@
// Copyright 2000-2021 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.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.highlighting.HighlightErrorFilter;
import com.intellij.core.JavaPsiBundle;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiExpressionStatement;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
public class JavaHighlightErrorFilter extends HighlightErrorFilter {
@Override
public boolean shouldHighlightErrorElement(@NotNull PsiErrorElement element) {
String description = element.getErrorDescription();
if (description.equals(JavaPsiBundle.message("expected.semicolon"))) {
PsiElement parent = element.getParent();
if (parent instanceof PsiExpressionStatement && !PsiUtil.isStatement(parent)) {
// unterminated expression statement which is not a statement at all:
// let's report it as not-a-statement instead
// (see HighlightUtil.checkNotAStatement); it's more visible and provides
// more useful fixes.
return false;
}
}
return true;
}
}

View File

@@ -2101,6 +2101,7 @@
</intentionAction>
<externalAnnotationsArtifactsResolver implementation="com.intellij.jarRepository.ExternalAnnotationsRepositoryResolver"/>
<errorQuickFixProvider implementation="com.intellij.codeInsight.daemon.impl.analysis.JavaErrorQuickFixProvider"/>
<highlightErrorFilter implementation="com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightErrorFilter"/>
<searchEverywhereResultsEqualityProvider implementation="com.intellij.ide.JavaClassAndFileEqualityProvider"/>

View File

@@ -0,0 +1,45 @@
// Copyright 2000-2021 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.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiFile;
import com.intellij.refactoring.introduceVariable.IntroduceVariableHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class IntroduceVariableErrorFixAction extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction {
public IntroduceVariableErrorFixAction(@NotNull PsiExpression expression) {
super(expression);
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
new IntroduceVariableHandler().invoke(project, editor, (PsiExpression)startElement);
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
public @NotNull String getText() {
return JavaBundle.message("intention.introduce.variable.text");
}
@Override
public @NotNull String getFamilyName() {
return getText();
}
}

View File

@@ -21,6 +21,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.BaseRefactoringIntentionAction;
import com.intellij.refactoring.introduceVariable.IntroduceEmptyVariableHandler;
import com.intellij.refactoring.introduceVariable.IntroduceVariableHandler;
@@ -60,6 +61,12 @@ public class IntroduceVariableIntentionAction extends BaseRefactoringIntentionAc
return false;
}
if (expression.getParent() instanceof PsiExpressionStatement &&
!PsiUtil.isStatement(expression.getParent())) {
// Same action is available as an error quick-fix
return false;
}
final PsiType expressionType = expression.getType();
return expressionType != null && !PsiType.VOID.equals(expressionType) && !(expression instanceof PsiAssignmentExpression);
}

View File

@@ -1009,4 +1009,9 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
public @NotNull IntentionAction createUnwrapArrayInitializerMemberValueAction(@NotNull PsiArrayInitializerMemberValue arrayValue) {
return new UnwrapArrayInitializerMemberValueAction(arrayValue);
}
@Override
public @NotNull IntentionAction createIntroduceVariableAction(@NotNull PsiExpression expression) {
return new IntroduceVariableErrorFixAction(expression);
}
}

View File

@@ -4,6 +4,6 @@ class C {
}
void f(int x, int y) {
if (x == 0 ||<error descr="')' expected"><error descr="Expression expected"><error descr="Illegal character: U+00A0"> </error></error></error>y == 0<error descr="';' expected"><error descr="Unexpected token">)</error></error> { }
if (x == 0 ||<error descr="')' expected"><error descr="Expression expected"><error descr="Illegal character: U+00A0"> </error></error></error><error descr="Not a statement">y == 0</error><error descr="Unexpected token">)</error> { }
}
}

View File

@@ -54,7 +54,7 @@ public class a12 {
<error descr="Cannot resolve method 'xxxxxx' in 'a12'">xxxxxx</error>(<error descr="Cannot resolve symbol 'xxxxxx'">xxxxxx</error>);
// incomplete code should not cause 'expr expected'
Object<error descr="';' expected"> </error>
<error descr="Not a statement">Object</error>
<error descr="Array type expected; found: 'int'">4</error>[1] = 5;

View File

@@ -0,0 +1,9 @@
class C {
void foo(int i, int j) {
<error descr="Not a statement">123</error>
<error descr="Not a statement">i+j</error> /*oops*/
foo(1, 2)<EOLError descr="';' expected"></EOLError>
i++<EOLError descr="';' expected"></EOLError>
toString()<error descr="';' expected"> </error>/*oops*/
}
}

View File

@@ -397,6 +397,10 @@ public class LightAdvHighlightingTest extends LightDaemonAnalyzerTestCase {
}
public void testUnreachableArrayElementAssignment() { doTest(false); }
public void testNotWellFormedExpressionStatementWithoutSemicolon() {
doTest(false);
}
public void testInsane() {
configureFromFileText("x.java", "class X { \nx_x_x_x\n }");

View File

@@ -35,7 +35,7 @@ class LightJava11HighlightingTest : LightJavaCodeInsightFixtureTestCase() {
"""#!/path/to/java
|class Main {{
|int i = 0;
|i*<error descr="';' expected"><error descr="Expression expected"><error descr="Unexpected token">*</error></error></error>;
|i*<error descr="Expression expected"><error descr="Unexpected token">*</error></error>;
|}}""".trimMargin())
myFixture.checkHighlighting()
Assert.assertTrue(HighlightClassUtil.isJavaHashBangScript(file))

View File

@@ -1,5 +1,5 @@
class F {
{
<error descr="Unclosed character literal">'/</error><EOLError descr="';' expected"></EOLError>
<error descr="Unclosed character literal">'/</error>
}
}

View File

@@ -17,7 +17,7 @@ public class ConstantOnRHS
class C {
void t() {
<error descr="Cannot resolve method 'method' in 'C'">method</error>(String.format("", <error descr="Expression expected">StringBuffer</error>)<error descr="',' or ')' expected">"</error>" +<EOLError descr="Expression expected"></EOLError>
<<error descr="')' expected"><error descr="Expression expected">/</error></error><error descr="Cannot resolve symbol 'plugin'">plugin</error>><error descr="Illegal line end in string literal">" +</error><EOLError descr="';' expected"></EOLError>
""<error descr="';' expected"><error descr="Unexpected token">)</error></error>;
<<error descr="')' expected"><error descr="Expression expected">/</error></error><error descr="Cannot resolve symbol 'plugin'">plugin</error>><error descr="Illegal line end in string literal">" +</error>
<error descr="Not a statement">""</error><error descr="Unexpected token">)</error>;
}
}

View File

@@ -7,7 +7,7 @@ class A {
}
class B {
B() {
<error descr="Cannot resolve symbol 'sup'">sup</error><EOLError descr="';' expected"></EOLError>
<error descr="Cannot resolve symbol 'sup'">sup</error>
}
<warning descr="Implicit call to 'super()'">B</warning>(int i) {

View File

@@ -151,6 +151,6 @@ public class UnnecessaryParenthesesInspection
class A{
A() {
((<error descr="Expression expected"><</error><error descr="Cannot resolve symbol 'x'">x</error>><error descr="Expression expected">)</error><EOLError descr="';' expected"></EOLError>
((<error descr="Expression expected"><</error><error descr="Cannot resolve symbol 'x'">x</error>><error descr="Expression expected">)</error>
}
}