[java] Unnamed variables

IDEA-323910 Implement parser for "JEP 443: Unnamed Patterns and Variables (Preview)"
IDEA-323960 Support error highlighting for unnamed variables (JEP 443)

GitOrigin-RevId: 1b9ee424063dfd4d32c2215fc8b0a9838dbdcd95
This commit is contained in:
Tagir Valeev
2023-07-31 10:03:08 +02:00
committed by intellij-monorepo-bot
parent 9fb2a08303
commit a41ef84fea
32 changed files with 420 additions and 67 deletions

View File

@@ -648,3 +648,7 @@ notification.file.system.issue=File Operation Issue
notification.content.cannot.move.file=Cannot move ''{0}'' into ''{1}'': {2}
intention.family.name.replace.with.unnamed.pattern=Replace with unnamed pattern
intention.name.ignore.exception=Ignore exception ''{0}''
error.unnamed.variable.not.allowed=Unnamed variable is not allowed
error.unnamed.field.not.allowed=Unnamed field is not allowed
error.unnamed.method.parameter.not.allowed=Unnamed method parameter is not allowed
error.unnamed.local.variable.not.allowed.in.this.context=Unnamed local variable is not allowed in this context

View File

@@ -699,7 +699,7 @@ public final class HighlightUtil {
}
public static HighlightInfo.Builder checkVariableAlreadyDefined(@NotNull PsiVariable variable) {
if (variable instanceof ExternallyDefinedPsiElement) return null;
if (variable instanceof ExternallyDefinedPsiElement || variable.isUnnamed()) return null;
PsiVariable oldVariable = null;
PsiElement declarationScope = null;
if (variable instanceof PsiLocalVariable || variable instanceof PsiPatternVariable ||
@@ -838,12 +838,14 @@ public final class HighlightUtil {
static HighlightInfo.Builder checkUnderscore(@NotNull PsiIdentifier identifier, @NotNull LanguageLevel languageLevel) {
if ("_".equals(identifier.getText())) {
PsiElement parent = identifier.getParent();
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9) && !(parent instanceof PsiUnnamedPattern)) {
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9) && !(parent instanceof PsiUnnamedPattern) &&
!(parent instanceof PsiVariable var && var.isUnnamed())) {
String text = JavaErrorBundle.message("underscore.identifier.error");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text);
}
else if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
if (parent instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiLambdaExpression) {
if (parent instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiLambdaExpression &&
!parameter.isUnnamed()) {
String text = JavaErrorBundle.message("underscore.lambda.identifier");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text);
}
@@ -853,6 +855,29 @@ public final class HighlightUtil {
return null;
}
static HighlightInfo.Builder checkAllowedUnnamedLocation(@NotNull PsiVariable variable) {
if (variable instanceof PsiPatternVariable) return null;
if (variable instanceof PsiResourceVariable) return null;
String message;
if (variable instanceof PsiLocalVariable local) {
if (local.getParent() instanceof PsiDeclarationStatement decl && decl.getParent() instanceof PsiCodeBlock) return null;
message = JavaAnalysisBundle.message("error.unnamed.local.variable.not.allowed.in.this.context");
}
else if (variable instanceof PsiParameter parameter) {
PsiElement scope = parameter.getDeclarationScope();
if (!(scope instanceof PsiMethod)) return null;
message = JavaAnalysisBundle.message("error.unnamed.method.parameter.not.allowed");
}
else if (variable instanceof PsiField) {
message = JavaAnalysisBundle.message("error.unnamed.field.not.allowed");
}
else {
message = JavaAnalysisBundle.message("error.unnamed.variable.not.allowed");
}
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(Objects.requireNonNull(variable.getNameIdentifier()))
.descriptionAndTooltip(message);
}
@NotNull
public static @NlsSafe String formatClass(@NotNull PsiClass aClass) {
return formatClass(aClass, true);

View File

@@ -775,6 +775,14 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
PsiElement parent = identifier.getParent();
if (parent instanceof PsiVariable variable) {
add(HighlightUtil.checkVariableAlreadyDefined(variable));
if (variable.isUnnamed()) {
HighlightInfo.Builder notAvailable = checkFeature(variable, HighlightingFeature.UNNAMED_PATTERNS_AND_VARIABLES);
if (notAvailable != null) {
add(notAvailable);
} else {
add(HighlightUtil.checkAllowedUnnamedLocation(variable));
}
}
if (variable.getInitializer() == null) {
PsiElement child = variable.getLastChild();

View File

@@ -222,7 +222,7 @@ class PostHighlightingVisitor {
private HighlightInfo.Builder processLocalVariable(@NotNull PsiLocalVariable variable,
@NotNull PsiIdentifier identifier) {
if (PsiUtil.isIgnoredName(variable.getName())) return null;
if (variable.isUnnamed() || PsiUtil.isIgnoredName(variable.getName())) return null;
if (UnusedSymbolUtil.isImplicitUsage(myProject, variable)) return null;
String message = null;
@@ -366,7 +366,7 @@ class PostHighlightingVisitor {
private HighlightInfo.Builder processParameter(@NotNull Project project,
@NotNull PsiParameter parameter,
@NotNull PsiIdentifier identifier) {
if (PsiUtil.isIgnoredName(parameter.getName())) return null;
if (parameter.isUnnamed() || PsiUtil.isIgnoredName(parameter.getName())) return null;
PsiElement declarationScope = parameter.getDeclarationScope();
QuickFixFactory quickFixFactory = QuickFixFactory.getInstance();
if (declarationScope instanceof PsiMethod method) {
@@ -426,7 +426,9 @@ class PostHighlightingVisitor {
return highlightInfo;
}
}
else if (myUnusedSymbolInspection.checkParameterExcludingHierarchy() && declarationScope instanceof PsiLambdaExpression) {
else if ((myUnusedSymbolInspection.checkParameterExcludingHierarchy() ||
HighlightingFeature.UNNAMED_PATTERNS_AND_VARIABLES.isAvailable(declarationScope))
&& declarationScope instanceof PsiLambdaExpression) {
HighlightInfo.Builder highlightInfo = checkUnusedParameter(parameter, identifier, null);
if (highlightInfo != null) {
IntentionAction action1 = quickFixFactory.createRenameToIgnoredFix(parameter, true);

View File

@@ -15,29 +15,54 @@
*/
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
public class RenameToIgnoredFix extends RenameElementFix {
private static final String PREFIX = "ignored";
private RenameToIgnoredFix(@NotNull PsiNamedElement place, @NotNull String suffix) {
super(place, JavaCodeStyleManager.getInstance(place.getProject()).suggestUniqueVariableName(PREFIX + suffix, place, true));
private RenameToIgnoredFix(@NotNull PsiNamedElement place, @NotNull String name) {
super(place, name);
}
/**
* @param useElementNameAsSuffix if true, let the fix suggest the variable name that consists of the "ignored" and name of the element
* e.g. ignoredVar
* <p>if false, let the fix suggest the variable that consists of the "ignored" and some number
* e.g. ignored1
* e.g. ignored1.
* If variable can be unnamed (place and language level allows, and it's totally unused), renaming to unnamed
* will be suggested instead.
*/
public static RenameToIgnoredFix createRenameToIgnoreFix(@NotNull PsiNamedElement element, boolean useElementNameAsSuffix) {
if (element instanceof PsiVariable variable && canBeUnnamed(variable)
&& !VariableAccessUtils.variableIsUsed(variable, PsiUtil.getVariableCodeBlock(variable, null))) {
return new RenameToIgnoredFix(variable, "_");
}
String baseName = "";
if (useElementNameAsSuffix) {
String elementName = element.getName();
if (elementName != null) return new RenameToIgnoredFix(element, StringUtil.capitalize(elementName));
if (elementName != null) {
baseName = StringUtil.capitalize(elementName);
}
}
return new RenameToIgnoredFix(element, "");
return new RenameToIgnoredFix(element, JavaCodeStyleManager.getInstance(element.getProject())
.suggestUniqueVariableName(PREFIX + baseName, element, true));
}
private static boolean canBeUnnamed(PsiVariable variable) {
if (!HighlightingFeature.UNNAMED_PATTERNS_AND_VARIABLES.isAvailable(variable)) return false;
if (variable instanceof PsiPatternVariable || variable instanceof PsiResourceVariable) return true;
if (variable instanceof PsiLocalVariable) {
return variable.getParent() instanceof PsiDeclarationStatement decl && decl.getParent() instanceof PsiCodeBlock;
}
if (variable instanceof PsiParameter parameter) {
return !(parameter.getDeclarationScope() instanceof PsiMethod);
}
return false;
}
}

View File

@@ -224,7 +224,7 @@ public final class UnusedDeclarationInspection extends UnusedDeclarationInspecti
for (DefUseUtil.Info varDefInfo : unusedDefs) {
PsiElement parent = varDefInfo.getContext();
PsiVariable variable = varDefInfo.getVariable();
if (PsiUtil.isIgnoredName(variable.getName())) continue;
if (variable.isUnnamed() || PsiUtil.isIgnoredName(variable.getName())) continue;
if (parent instanceof PsiDeclarationStatement || parent instanceof PsiForeachStatement ||
variable instanceof PsiResourceVariable || variable instanceof PsiPatternVariable) {
if (!varDefInfo.isRead() && !SuppressionUtil.inspectionResultSuppressed(variable, UnusedDeclarationInspection.this)) {

View File

@@ -2,10 +2,13 @@
package com.intellij.psi;
import com.intellij.pom.PomRenameableTarget;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NonNls;
/**
* Represents a Java local variable, method parameter or field.
@@ -86,4 +89,16 @@ public interface PsiVariable extends PsiModifierListOwner, PsiNameIdentifierOwne
@Override
PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException;
/**
* @return true if the variable is an unnamed variable, according to the Java specification
*/
@ApiStatus.Experimental
default boolean isUnnamed() {
return "_".equals(getName()) &&
!(this instanceof PsiCompiledElement) &&
// Treat _ as unsupported unnamed variable since JDK 1.9,
// so we can get a proper suggestion to update language level
PsiUtil.getLanguageLevel(this).isAtLeast(LanguageLevel.JDK_1_9);
}
}

View File

@@ -1014,6 +1014,7 @@ public final class PsiUtil extends PsiUtilCore {
}
public static boolean checkName(@NotNull PsiElement element, @NotNull String name, @NotNull PsiElement context) {
if (element instanceof PsiVariable && ((PsiVariable)element).isUnnamed()) return false;
if (element instanceof PsiMetaOwner) {
PsiMetaData data = ((PsiMetaOwner) element).getMetaData();
if (data != null) {

View File

@@ -274,6 +274,9 @@ public class PsiParameterImpl extends JavaStubPsiElement<PsiParameterStub> imple
@Override
public @NotNull SearchScope getUseScope() {
if (isUnnamed()) {
return LocalSearchScope.EMPTY;
}
PsiElement declarationScope = getDeclarationScope();
return new LocalSearchScope(declarationScope);
}

View File

@@ -305,6 +305,9 @@ public class PsiLocalVariableImpl extends CompositePsiElement implements PsiLoca
@Override
public @NotNull SearchScope getUseScope() {
if (isUnnamed()) {
return LocalSearchScope.EMPTY;
}
final PsiElement parentElement = getParent();
if (parentElement instanceof PsiDeclarationStatement) {
return new LocalSearchScope(parentElement.getParent());

View File

@@ -415,7 +415,7 @@ cannot.resolve.package=Cannot resolve package {0}
override.not.allowed.in.interfaces=@Override is not allowed when implementing interface method
declaration.not.allowed=Declaration not allowed here
underscore.identifier.error=As of Java 9, '_' is a keyword, and may not be used as an identifier
underscore.identifier.error=Since Java 9, '_' is a keyword, and may not be used as an identifier
underscore.lambda.identifier=Use of '_' as a lambda parameter name is not allowed
assert.identifier.warn=Use of 'assert' as an identifier is not supported in releases since Java 1.4

View File

@@ -1,3 +1,3 @@
class Undescore {
void <error descr="As of Java 9, '_' is a keyword, and may not be used as an identifier">_</error>() { }
void <error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>() { }
}

View File

@@ -2,7 +2,7 @@ public class UnnamedPatterns {
record R(int a, int b) {}
void test(Object obj) {
if (obj instanceof <error descr="As of Java 9, '_' is a keyword, and may not be used as an identifier">_</error>) {}
if (obj instanceof <error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>) {}
if (obj instanceof R(_, _)) {}
if (obj instanceof R(int a, _)) {

View File

@@ -2,7 +2,7 @@ public class UnnamedPatterns {
record R(int a, int b) {}
void test(Object obj) {
if (obj instanceof <error descr="As of Java 9, '_' is a keyword, and may not be used as an identifier">_</error>) {}
if (obj instanceof <error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>) {}
if (obj instanceof R(<error descr="Unnamed patterns and variables are not supported at language level '20'">_</error>, <error descr="Unnamed patterns and variables are not supported at language level '20'">_</error>)) {}
if (obj instanceof R(int a, <error descr="Unnamed patterns and variables are not supported at language level '20'">_</error>)) {

View File

@@ -0,0 +1,42 @@
import java.util.function.*;
public class UnnamedVariables {
void testParameter(int <error descr="Unnamed method parameter is not allowed">_</error>, String <error descr="Unnamed method parameter is not allowed">_</error>) {
System.out.println(<error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>);
}
int <error descr="Unnamed field is not allowed">_</error> = 123;
String s = <error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>;
void testLambda() {
Consumer<String> consumer = _ -> System.out.println("Hello");
Consumer<String> consumer2 = _ -> System.out.println(<error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>);
Consumer<String> consumer3 = _ -> System.out.println(<error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>.trim());
Consumer<String> consumer4 = _ -> {
var v = <error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>;
System.out.println(v.<error descr="Cannot resolve method 'trim()'">trim</error>());
};
BiConsumer<String, String> consumer5 = (_,_) -> {};
}
void testLocal() {
int _ = 10;
int _ = 20;
for (int <error descr="Unnamed local variable is not allowed in this context">_</error> = 1;;) {}
}
void testCatch() {
try {
System.out.println();
}
catch (Exception _) {
System.out.println("ignore");
}
catch (Error _) {
int _ = 1;
for(int _:new int[10]) {
System.out.println("oops");
}
}
}
}

View File

@@ -0,0 +1,35 @@
import java.util.function.*;
public class UnnamedVariables {
void testParameter(int <error descr="Variable '_' is already defined in the scope">_</error>, String <error descr="Variable '_' is already defined in the scope">_</error>) {
System.out.println(_);
}
void testOneParam(String _) {
System.out.println(_);
}
int _ = 123;
<error descr="Incompatible types. Found: 'int', required: 'java.lang.String'">String s = _;</error>
void testLocal() {
int _ = 10;
int <error descr="Variable '_' is already defined in the scope">_</error> = 20;
for (int <error descr="Variable '_' is already defined in the scope">_</error> = 1;;) {}
}
void testCatch() {
try {
System.out.println();
}
catch (Exception _) {
System.out.println("ignore");
}
catch (Error _) {
int <error descr="Variable '_' is already defined in the scope">_</error> = 1;
for(int <error descr="Variable '_' is already defined in the scope">_</error>:new int[10]) {
System.out.println("oops");
}
}
}
}

View File

@@ -0,0 +1,42 @@
import java.util.function.*;
public class UnnamedVariables {
void testParameter(int <error descr="Variable '_' is already defined in the scope">_</error>, String <error descr="Variable '_' is already defined in the scope">_</error>) {
System.out.println(_);
}
int _ = 123;
<error descr="Incompatible types. Found: 'int', required: 'java.lang.String'">String s = _;</error>
void testLambda() {
Consumer<String> consumer = <error descr="Use of '_' as a lambda parameter name is not allowed">_</error> -> System.out.println("Hello");
Consumer<String> consumer2 = <error descr="Use of '_' as a lambda parameter name is not allowed">_</error> -> System.out.println(_);
Consumer<String> consumer3 = <error descr="Use of '_' as a lambda parameter name is not allowed">_</error> -> System.out.println(_.trim());
Consumer<String> consumer4 = <error descr="Use of '_' as a lambda parameter name is not allowed">_</error> -> {
<error descr="Cannot resolve symbol 'var'" textAttributesKey="WRONG_REFERENCES_ATTRIBUTES">var</error> v = _;
System.out.println(v.<error descr="Cannot resolve method 'trim()'" textAttributesKey="WRONG_REFERENCES_ATTRIBUTES">trim</error>());
};
BiConsumer<String, String> consumer5 = (<error descr="Use of '_' as a lambda parameter name is not allowed"><error descr="Variable '_' is already defined in the scope">_</error></error>,<error descr="Use of '_' as a lambda parameter name is not allowed"><error descr="Variable '_' is already defined in the scope">_</error></error>) -> {};
}
void testLocal() {
int _ = 10;
int <error descr="Variable '_' is already defined in the scope">_</error> = 20;
for (int <error descr="Variable '_' is already defined in the scope">_</error> = 1;;) {}
}
void testCatch() {
try {
System.out.println();
}
catch (Exception _) {
System.out.println("ignore");
}
catch (Error _) {
int <error descr="Variable '_' is already defined in the scope">_</error> = 1;
for(int <error descr="Variable '_' is already defined in the scope">_</error>:new int[10]) {
System.out.println("oops");
}
}
}
}

View File

@@ -0,0 +1,42 @@
import java.util.function.*;
public class UnnamedVariables {
void testParameter(<error descr="Unnamed patterns and variables are not supported at language level '9'">int _</error>, <error descr="Unnamed patterns and variables are not supported at language level '9'">String _</error>) {
System.out.println(<error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>);
}
<error descr="Unnamed patterns and variables are not supported at language level '9'">int _ = 123;</error>
String s = <error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>;
void testLambda() {
Consumer<String> consumer = <error descr="Unnamed patterns and variables are not supported at language level '9'">_</error> -> System.out.println("Hello");
Consumer<String> consumer2 = <error descr="Unnamed patterns and variables are not supported at language level '9'">_</error> -> System.out.println(<error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>);
Consumer<String> consumer3 = <error descr="Unnamed patterns and variables are not supported at language level '9'">_</error> -> System.out.println(<error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>.trim());
Consumer<String> consumer4 = <error descr="Unnamed patterns and variables are not supported at language level '9'">_</error> -> {
<error descr="Cannot resolve symbol 'var'" textAttributesKey="WRONG_REFERENCES_ATTRIBUTES">var</error> v = <error descr="Since Java 9, '_' is a keyword, and may not be used as an identifier">_</error>;
System.out.println(v.<error descr="Cannot resolve method 'trim()'" textAttributesKey="WRONG_REFERENCES_ATTRIBUTES">trim</error>());
};
BiConsumer<String, String> consumer5 = (<error descr="Unnamed patterns and variables are not supported at language level '9'">_</error>,<error descr="Unnamed patterns and variables are not supported at language level '9'">_</error>) -> {};
}
void testLocal() {
<error descr="Unnamed patterns and variables are not supported at language level '9'">int _ = 10;</error>
<error descr="Unnamed patterns and variables are not supported at language level '9'">int _ = 20;</error>
for (<error descr="Unnamed patterns and variables are not supported at language level '9'">int _ = 1;</error>;) {}
}
void testCatch() {
try {
System.out.println();
}
catch (<error descr="Unnamed patterns and variables are not supported at language level '9'">Exception _</error>) {
System.out.println("ignore");
}
catch (<error descr="Unnamed patterns and variables are not supported at language level '9'">Error _</error>) {
<error descr="Unnamed patterns and variables are not supported at language level '9'">int _ = 1;</error>
for(<error descr="Unnamed patterns and variables are not supported at language level '9'">int _</error>:new int[10]) {
System.out.println("oops");
}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Rename 'vvv' to '_'" "true-preview"
import java.util.function.Consumer;
class Simple {
void test() {
Consumer<String> cons = _ -> {};
cons.accept("");
}
}

View File

@@ -0,0 +1,8 @@
// "Rename 'x' to '_'" "true-preview"
class Simple {
void test() {
for(int _ : new int[10]) {
System.out.println("hello");
}
}
}

View File

@@ -0,0 +1,9 @@
// "Rename 'vvv' to '_'" "true-preview"
import java.util.function.Consumer;
class Simple {
void test() {
Consumer<String> cons = v<caret>vv -> {};
cons.accept("");
}
}

View File

@@ -0,0 +1,7 @@
// "Rename 'x' to '_'" "false"
class Simple {
void test() {
// Do not suggest unnamed local, even if it's allowed; only remove variable is suggested
int <caret>x = 0;
}
}

View File

@@ -0,0 +1,8 @@
// "Rename 'x' to '_'" "true-preview"
class Simple {
void test() {
for(int <caret>x : new int[10]) {
System.out.println("hello");
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
import static com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase.JAVA_21;
public class RenameToUnnamedVariableTest extends LightIntentionActionTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
enableInspectionTools(new UnusedDeclarationInspectionBase());
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/renameToUnnamed";
}
}

View File

@@ -178,7 +178,7 @@ public class LightPatternsForSwitchHighlightingTest extends LightJavaCodeInsight
public void testUnusedPatternVariable() {
myFixture.enableInspections(new UnusedDeclarationInspection());
doTest();
assertNotNull(myFixture.getAvailableIntention("Rename 's' to 'ignored'"));
assertNotNull(myFixture.getAvailableIntention("Rename 's' to '_'"));
}
public void testMalformedReferenceExpression() {

View File

@@ -0,0 +1,42 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.daemon;
import com.intellij.JavaTestUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
public class LightUnnamedVariablesHighlightingTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/advHighlightingUnnamed";
}
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
public void testUnnamedVariables() {
doTest();
}
public void testUnnamedVariablesJava9() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_1_9, () -> doTest());
}
public void testUnnamedVariablesJava8() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_1_8, () -> doTest());
}
public void testUnnamedVariablesJava7() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_1_7, () -> doTest());
}
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");
myFixture.checkHighlighting();
}
}

View File

@@ -77,7 +77,7 @@ public class ExceptionFromCatchWhichDoesntWrapInspection extends BaseInspection
return;
}
final PsiParameter parameter = catchSection.getParameter();
if (parameter == null) {
if (parameter == null || parameter.isUnnamed()) {
return;
}
@NonNls final String parameterName = parameter.getName();

View File

@@ -117,7 +117,7 @@ public final class VariableAccessUtils {
* @return true, if the specified variable was assigned in the specified context, false otherwise
*/
public static boolean variableIsAssigned(@NotNull PsiVariable variable, @Nullable PsiElement context) {
if (context == null) {
if (context == null || variable.isUnnamed()) {
return false;
}
final VariableAssignedVisitor visitor = new VariableAssignedVisitor(variable);
@@ -135,7 +135,7 @@ public final class VariableAccessUtils {
*/
public static boolean variableIsAssigned(@NotNull PsiVariable variable, @NotNull Predicate<? super PsiAssignmentExpression> skipFilter,
@Nullable PsiElement context) {
if (context == null) {
if (context == null || variable.isUnnamed()) {
return false;
}
final VariableAssignedVisitor visitor = new VariableAssignedVisitor(variable, skipFilter, true);
@@ -150,7 +150,7 @@ public final class VariableAccessUtils {
* @return true, if the specified variable was assigned in the specified context, false otherwise
*/
public static boolean variableIsAssigned(@NotNull PsiVariable variable, @Nullable PsiElement context, boolean recurseIntoClasses) {
if (context == null) {
if (context == null || variable.isUnnamed()) {
return false;
}
final VariableAssignedVisitor visitor = new VariableAssignedVisitor(variable, recurseIntoClasses);
@@ -163,7 +163,7 @@ public final class VariableAccessUtils {
}
public static boolean variableIsReturned(@NotNull PsiVariable variable, @Nullable PsiElement context, boolean builderPattern) {
if (context == null) {
if (context == null || variable.isUnnamed()) {
return false;
}
final VariableReturnedVisitor visitor = new VariableReturnedVisitor(variable, builderPattern);
@@ -173,7 +173,7 @@ public final class VariableAccessUtils {
public static boolean variableValueIsUsed(
@NotNull PsiVariable variable, @Nullable PsiElement context) {
if (context == null) {
if (context == null || variable.isUnnamed()) {
return false;
}
final VariableValueUsedVisitor visitor =
@@ -195,7 +195,7 @@ public final class VariableAccessUtils {
public static boolean variableIsUsedInInnerClass(
@NotNull PsiVariable variable, @Nullable PsiElement context) {
if (context == null) {
if (context == null || variable.isUnnamed()) {
return false;
}
final VariableUsedInInnerClassVisitor visitor =
@@ -209,7 +209,7 @@ public final class VariableAccessUtils {
}
static boolean mayEvaluateToVariable(@Nullable PsiExpression expression, @NotNull PsiVariable variable, boolean builderPattern) {
if (expression == null) {
if (expression == null || variable.isUnnamed()) {
return false;
}
if (expression instanceof PsiParenthesizedExpression parenthesizedExpression) {

View File

@@ -3,6 +3,7 @@ package com.siyeh.ig.errorhandling;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.intention.LowPriorityAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemsHolder;
@@ -42,7 +43,6 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.fixes.RenameFix;
import com.siyeh.ig.fixes.SuppressForTestsScopeFix;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.TestUtils;
@@ -92,7 +92,7 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp
private void checkCatchSection(PsiCatchSection section) {
final PsiParameter parameter = section.getParameter();
if (parameter == null) return;
if (parameter == null || parameter.isUnnamed()) return;
final PsiIdentifier identifier = parameter.getNameIdentifier();
if (identifier == null) return;
final String parameterName = parameter.getName();
@@ -116,7 +116,7 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp
if (block == null) return;
SuppressForTestsScopeFix fix = SuppressForTestsScopeFix.build(CatchMayIgnoreExceptionInspection.this, section);
if (ControlFlowUtils.isEmpty(block, m_ignoreCatchBlocksWithComments, true)) {
RenameCatchParameterFix renameFix = new RenameCatchParameterFix(generateName(block));
var renameFix = QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter, false);
AddCatchBodyFix addBodyFix = getAddBodyFix(block);
holder.registerProblem(catchToken, InspectionGadgetsBundle.message("inspection.catch.ignores.exception.empty.message"),
LocalQuickFix.notNullElements(renameFix, addBodyFix, fix));
@@ -125,7 +125,8 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp
if (!m_ignoreNonEmptyCatchBlock &&
(!m_ignoreCatchBlocksWithComments || PsiTreeUtil.getChildOfType(block, PsiComment.class) == null)) {
holder.registerProblem(identifier, InspectionGadgetsBundle.message("inspection.catch.ignores.exception.unused.message"),
LocalQuickFix.notNullElements(new RenameFix(generateName(block), false, false), fix));
LocalQuickFix.notNullElements(
QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter, false), fix));
}
}
else {
@@ -307,38 +308,4 @@ public class CatchMayIgnoreExceptionInspection extends AbstractBaseJavaLocalInsp
}
}
}
private static final class RenameCatchParameterFix extends PsiUpdateModCommandQuickFix {
private final String myName;
private RenameCatchParameterFix(String name) {
myName = name;
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getName() {
return InspectionGadgetsBundle.message("rename.catch.parameter.to.ignored", myName);
}
@Override
@NotNull
public String getFamilyName() {
return InspectionGadgetsBundle.message("rename.catch.parameter.to.ignored", IGNORED_PARAMETER_NAME);
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
final PsiElement parent = element.getParent();
if (!(parent instanceof PsiCatchSection catchSection)) return;
final PsiParameter parameter = catchSection.getParameter();
if (parameter == null) return;
final PsiIdentifier identifier = parameter.getNameIdentifier();
if (identifier == null) return;
final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
final PsiIdentifier newIdentifier = factory.createIdentifier(myName);
identifier.replace(newIdentifier);
}
}
}

View File

@@ -0,0 +1,11 @@
import java.io.IOException;
class AAA {
public static void main(String[] args) {
try {
System.out.println(System.in.read());
} c<caret>atch (IOException _) {
}
}
}

View File

@@ -0,0 +1,11 @@
import java.io.IOException;
class AAA {
public static void main(String[] args) {
try {
System.out.println(System.in.read());
} c<caret>atch (IOException ex) {
}
}
}

View File

@@ -15,6 +15,8 @@
*/
package com.siyeh.ig.fixes.errorhandling;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.IGQuickFixesTestCase;
import com.siyeh.ig.errorhandling.CatchMayIgnoreExceptionInspection;
@@ -42,10 +44,14 @@ public class CatchMayIgnoreExceptionInspectionFixTest extends IGQuickFixesTestCa
}
public void testRenameToIgnored() {
doTest(InspectionGadgetsBundle.message("rename.catch.parameter.to.ignored", "ignored"));
doTest("Rename 'ex' to 'ignored'");
}
public void testRenameToIgnoredJava21() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_21_PREVIEW, () -> doTest("Rename 'ex' to '_'"));
}
public void testRenameToIgnoredNameConflict() {
doTest(InspectionGadgetsBundle.message("rename.catch.parameter.to.ignored", "ignored1"));
doTest("Rename 'ex' to 'ignored1'");
}
}