[java-inspections] checkNotAStatement -> StatementChecker

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 9450280ccdb333167828b22238d4a7ebf95de3c2
This commit is contained in:
Tagir Valeev
2025-01-27 16:50:02 +01:00
committed by intellij-monorepo-bot
parent 9dbf2ace73
commit 4c3dca5425
12 changed files with 54 additions and 59 deletions

View File

@@ -218,6 +218,8 @@ reference.implicit.class=Implicitly declared class ''{0}'' cannot be referenced
statement.case.outside.switch=Case statement outside switch
statement.invalid=Invalid statement
statement.declaration.not.allowed=Declaration not allowed here
statement.bad.expression=Not a statement
guard.misplaced=Guard is allowed after patterns only
guard.evaluated.to.false=This case label has a guard that is a constant expression with value 'false'

View File

@@ -110,6 +110,12 @@ final class JavaErrorVisitor extends JavaElementVisitor {
if (!hasErrorResults()) myRecordChecker.checkRecordAccessorReturnType(recordComponent);
}
@Override
public void visitStatement(@NotNull PsiStatement statement) {
super.visitStatement(statement);
if (!hasErrorResults()) myStatementChecker.checkNotAStatement(statement);
}
@Override
public void visitTryStatement(@NotNull PsiTryStatement statement) {
super.visitTryStatement(statement);

View File

@@ -2,6 +2,7 @@
package com.intellij.java.codeserver.highlighting;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.core.JavaPsiBundle;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
import com.intellij.pom.java.JavaFeature;
@@ -271,4 +272,26 @@ final class StatementChecker {
myVisitor.report(JavaErrorKinds.RETURN_VALUE_MISSING.create(statement, method));
}
}
void checkNotAStatement(@NotNull PsiStatement statement) {
if (PsiUtil.isStatement(statement)) return;
if (PsiUtilCore.hasErrorElementChild(statement)) {
boolean allowedError = false;
if (statement instanceof PsiExpressionStatement) {
PsiElement[] children = statement.getChildren();
if (children[0] instanceof PsiExpression && children[1] instanceof PsiErrorElement errorElement &&
errorElement.getErrorDescription().equals(JavaPsiBundle.message("expected.semicolon"))) {
allowedError = true;
}
}
if (!allowedError) return;
}
boolean isDeclarationNotAllowed = false;
if (statement instanceof PsiDeclarationStatement) {
PsiElement parent = statement.getParent();
isDeclarationNotAllowed = parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement;
}
var kind = isDeclarationNotAllowed ? JavaErrorKinds.STATEMENT_DECLARATION_NOT_ALLOWED : JavaErrorKinds.STATEMENT_BAD_EXPRESSION;
myVisitor.report(kind.create(statement));
}
}

View File

@@ -765,6 +765,8 @@ public final class JavaErrorKinds {
public static final Simple<PsiSwitchLabelStatementBase> STATEMENT_CASE_OUTSIDE_SWITCH = error("statement.case.outside.switch");
public static final Simple<PsiStatement> STATEMENT_INVALID = error("statement.invalid");
public static final Simple<PsiStatement> STATEMENT_BAD_EXPRESSION = error("statement.bad.expression");
public static final Simple<PsiStatement> STATEMENT_DECLARATION_NOT_ALLOWED = error("statement.declaration.not.allowed");
public static final Simple<PsiExpression> GUARD_MISPLACED = error("guard.misplaced");
public static final Simple<PsiExpression> GUARD_EVALUATED_TO_FALSE = error("guard.evaluated.to.false");

View File

@@ -388,7 +388,7 @@ public final class HighlightFixUtil {
}
}
public static void registerFixesForExpressionStatement(@NotNull PsiElement statement, @NotNull List<? super IntentionAction> registrar) {
public static void registerFixesForExpressionStatement(@NotNull PsiElement statement, @NotNull Consumer<? super CommonIntentionAction> info) {
if (!(statement instanceof PsiExpressionStatement)) return;
PsiCodeBlock block = ObjectUtils.tryCast(statement.getParent(), PsiCodeBlock.class);
if (block == null) return;
@@ -397,14 +397,14 @@ public final class HighlightFixUtil {
PsiType type = expression.getType();
if (type == null) return;
if (!type.equals(PsiTypes.voidType())) {
registrar.add(PriorityIntentionActionWrapper.highPriority(QuickFixFactory.getInstance().createIterateFix(expression)));
info.accept(PriorityIntentionActionWrapper.highPriority(QuickFixFactory.getInstance().createIterateFix(expression)));
if (PsiTreeUtil.skipWhitespacesAndCommentsForward(statement) == block.getRBrace() && block.getParent() instanceof PsiMethod method) {
PsiType returnType = method.getReturnType();
if (returnType != null && isPossibleReturnValue(expression, type, returnType)) {
registrar.add(QuickFixFactory.getInstance().createInsertReturnFix(expression));
info.accept(QuickFixFactory.getInstance().createInsertReturnFix(expression));
}
}
registrar.add(QuickFixFactory.getInstance().createIntroduceVariableAction(expression));
info.accept(QuickFixFactory.getInstance().createIntroduceVariableAction(expression));
}
}

View File

@@ -13,10 +13,8 @@ import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation;
import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixUpdater;
import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix;
import com.intellij.core.JavaPsiBundle;
import com.intellij.ide.IdeBundle;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.openapi.diagnostic.Logger;
@@ -634,47 +632,6 @@ public final class HighlightUtil {
return null;
}
static HighlightInfo.Builder checkNotAStatement(@NotNull PsiStatement statement) {
if (PsiUtil.isStatement(statement)) {
return null;
}
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 errorElement &&
errorElement.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();
isDeclarationNotAllowed = parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement;
}
String description = JavaErrorBundle.message(isDeclarationNotAllowed ? "declaration.not.allowed" : "not.a.statement");
HighlightInfo.Builder error =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(anchor).descriptionAndTooltip(description);
if (statement instanceof PsiExpressionStatement expressionStatement) {
List<IntentionAction> registrar = new ArrayList<>();
HighlightFixUtil.registerFixesForExpressionStatement(statement, registrar);
QuickFixAction.registerQuickFixActions(error, null, registrar);
PsiElement parent = expressionStatement.getParent();
if (parent instanceof PsiCodeBlock ||
parent instanceof PsiIfStatement ||
parent instanceof PsiLoopStatement loop && loop.getBody() == expressionStatement) {
IntentionAction action = PriorityIntentionActionWrapper
.lowPriority(getFixFactory().createDeleteSideEffectAwareFix(expressionStatement));
error.registerFix(action, null, null, null, null);
}
}
return error;
}
static void checkSwitchExpressionReturnTypeCompatible(@NotNull PsiSwitchExpression switchExpression,
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
if (!PsiPolyExpressionUtil.isPolyExpression(switchExpression)) {

View File

@@ -1132,12 +1132,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!hasErrorResults()) add(GenericsHighlightUtil.checkParametersOnRaw(list, myLanguageLevel));
}
@Override
public void visitStatement(@NotNull PsiStatement statement) {
super.visitStatement(statement);
if (!hasErrorResults()) add(HighlightUtil.checkNotAStatement(statement));
}
@Override
public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) {
super.visitSwitchStatement(statement);

View File

@@ -147,6 +147,17 @@ final class JavaErrorFixProvider {
};
fixes(RETURN_FROM_CONSTRUCTOR, fixReturnFromVoid);
fixes(RETURN_FROM_VOID_METHOD, fixReturnFromVoid);
fixes(STATEMENT_BAD_EXPRESSION, (error, sink) -> {
if (error.psi() instanceof PsiExpressionStatement expressionStatement) {
HighlightFixUtil.registerFixesForExpressionStatement(expressionStatement, sink);
PsiElement parent = expressionStatement.getParent();
if (parent instanceof PsiCodeBlock ||
parent instanceof PsiIfStatement ||
parent instanceof PsiLoopStatement loop && loop.getBody() == expressionStatement) {
sink.accept(PriorityIntentionActionWrapper.lowPriority(myFactory.createDeleteSideEffectAwareFix(expressionStatement)));
}
}
});
}
private void createMethodFixes() {

View File

@@ -27,7 +27,7 @@ public final class JavaErrorQuickFixProvider implements ErrorQuickFixProvider, D
List<IntentionAction> registrar = new ArrayList<>();
if (description.equals(JavaPsiBundle.message("expected.semicolon"))) {
info.registerFix(new InsertMissingTokenFix(";"), null, null, null, null);
HighlightFixUtil.registerFixesForExpressionStatement(parent, registrar);
HighlightFixUtil.registerFixesForExpressionStatement(parent, action -> registrar.add(action.asIntention()));
}
if (parent instanceof PsiTryStatement && description.equals(JavaPsiBundle.message("expected.catch.or.finally"))) {
registrar.add(new AddExceptionToCatchFix(false).asIntention());

View File

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

View File

@@ -20,7 +20,7 @@ class TryWithResources {
void testType() throws Exception {
String s = "";
try (<error descr="Incompatible types. Found: 'java.lang.String', required: 'java.lang.AutoCloseable'">s</error>;
try (<error descr="Incompatible types. Found: 'java.lang.String', required: 'java.lang.AutoCloseable'">s;</error>
<error descr="Incompatible types. Found: 'TryWithResources', required: 'java.lang.AutoCloseable'">this</error>) { }
}

View File

@@ -56,7 +56,7 @@ class Test {
mode = 0;
}
void testNested(Object o, Integer in) {
void testNested(Object o, Integer in, AutoCloseable in1) {
switch (o) {
case Integer mode when (<error descr="Cannot assign a value to variable 'mode', because it is declared outside the guard">mode</error> = 42) > 9:
switch (o) {
@@ -97,9 +97,9 @@ class Test {
<error descr="Variable used in lambda expression should be final or effectively final">in</error> = 1;
};
// try-with-resources
try (<error descr="Variable used as a try-with-resources resource should be final or effectively final">in</error>) {
try (<error descr="Variable used as a try-with-resources resource should be final or effectively final">in1</error>) {
switch (o) {
case AutoCloseable ii when (<error descr="Cannot assign a value to variable 'in', because it is declared outside the guard">in</error> = 1) != null: break;
case AutoCloseable ii when (<error descr="Cannot assign a value to variable 'in1', because it is declared outside the guard">in1</error> = ii) != null: break;
default: break;
}
} catch (Exception e) {