[java] IDEA-371865 Inspection to convert 'System.out'<->'IO'

- fixes for comments
- simplified replacement
- support char[]
- support ImplicitArrayToStringInspection

(cherry picked from commit 49f9f829821a498259aa53ebb12cd0b0007f1238)


(cherry picked from commit 5f4445631fa55911d98477066d3821423c37ec11)

IJ-MR-169535

GitOrigin-RevId: cf93370312fc5fbe632eb73ca8840d157732da50
This commit is contained in:
Mikhail Pyltsin
2025-07-04 17:10:14 +02:00
committed by intellij-monorepo-bot
parent 0af3e03979
commit fdab4d3999
18 changed files with 200 additions and 62 deletions

View File

@@ -304,6 +304,71 @@ public interface CallMatcher extends Predicate<PsiMethodCallExpression> {
myCallType = callType;
}
/**
* Creates a new matcher based on the current matcher, allowing unresolved method calls to be matched.
* This matcher supports verifying unresolved method calls and their context, such as method names,
* qualifier expressions, and class names.
* <p>
* The resulting matcher enforces the following criteria for unresolved calls:
* - Method name must match the specified names.
* - The argument list must match certain conditions based on parameter types.
* - Class name must end with qualifier expressions. Qualifier expression should be unresolved.
* - Call type (for example, static/instance) is not checked
* <p>
* This matcher supports only {@link #test(PsiMethodCallExpression)} method.
*
* @return a new CallMatcher instance that allows unresolved method calls to be matched
*/
public CallMatcher allowUnresolved() {
return new CallMatcher() {
@Override
public Stream<String> names() {
return Simple.this.names();
}
@Override
public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) {
throw new UnsupportedOperationException("PsiMethodReferenceExpression is not supported");
}
@Override
public boolean test(@Nullable PsiMethodCallExpression call) {
if (Simple.this.test(call)) return true;
if (call == null) return false;
String name = call.getMethodExpression().getReferenceName();
if (name == null || !myNames.contains(name)) return false;
if (!unresolvedArgumentListMatch(call.getArgumentList())) return false;
PsiMethod method = call.resolveMethod();
if (method != null) return false;
PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression();
if (!(qualifierExpression instanceof PsiReferenceExpression qualifierRefExpression)) return false;
if (qualifierRefExpression.getQualifierExpression() != null) return false;
String referenceName = qualifierRefExpression.getReferenceName();
if (referenceName == null && myClassName.isEmpty()) return true;
if (referenceName == null) return false;
if (!myClassName.endsWith(referenceName)) return false;
PsiElement resolvedQualifier = qualifierRefExpression.resolve();
if (resolvedQualifier != null) return false;
return true;
}
@Override
public boolean methodMatches(@Nullable PsiMethod method) {
throw new UnsupportedOperationException("PsiMethod is not supported");
}
@Override
public boolean uCallMatches(@Nullable UCallExpression call) {
throw new UnsupportedOperationException("UCallExpression is not supported");
}
@Override
public boolean uCallableReferenceMatches(@Nullable UCallableReferenceExpression reference) {
throw new UnsupportedOperationException("UCallableReferenceExpression is not supported");
}
};
}
@Override
public Stream<String> names() {
return myNames.stream();
@@ -346,6 +411,13 @@ public interface CallMatcher extends Predicate<PsiMethodCallExpression> {
return psiType.equalsToText(type) || PsiTypesUtil.classNameEquals(psiType, type);
}
private static boolean expressionTypeMatches(@Nullable String type, @NotNull PsiExpression argument) {
if (type == null) return true;
PsiType psiType = argument.getType();
if (psiType == null) return false;
return psiType.equalsToText(type) || PsiTypesUtil.classNameEquals(psiType, type);
}
@Contract(pure = true)
@Override
public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) {
@@ -383,6 +455,20 @@ public interface CallMatcher extends Predicate<PsiMethodCallExpression> {
Simple::parameterTypeMatches).allMatch(Boolean.TRUE::equals);
}
private boolean unresolvedArgumentListMatch(@NotNull PsiExpressionList expressionList) {
if (myParameters == null) return true;
PsiExpression[] args = expressionList.getExpressions();
if (myParameters.length > 0) {
if (args.length < myParameters.length - 1) return false;
}
for (int i = 0; i < Math.min(myParameters.length, args.length); i++) {
PsiExpression arg = args[i];
String parameter = myParameters[i];
if (!expressionTypeMatches(parameter, arg)) return false;
}
return true;
}
@Override
@Contract(value = "null -> false", pure = true)
public boolean methodMatches(@Nullable PsiMethod method) {

View File

@@ -1479,10 +1479,11 @@ public final class ExpressionUtils {
yield !hasCharArrayParameter(method);
}
case "print", "println" -> {
if (arguments.length != 1 || hasCharArrayParameter(method)) yield false;
yield JAVA_UTIL_FORMATTER.equals(className) ||
InheritanceUtil.isInheritor(containingClass, JAVA_IO_PRINT_STREAM) ||
InheritanceUtil.isInheritor(containingClass, JAVA_IO_PRINT_WRITER);
if (arguments.length != 1) yield false;
yield (!hasCharArrayParameter(method) && (JAVA_UTIL_FORMATTER.equals(className) ||
InheritanceUtil.isInheritor(containingClass, JAVA_IO_PRINT_STREAM) ||
InheritanceUtil.isInheritor(containingClass, JAVA_IO_PRINT_WRITER))) ||
"java.lang.IO".equals(className);
}
case "printf", "format" -> {
if (arguments.length < 1) yield false;