IDEA-200005 Suggest to use Files.readString/writeString since Java 11

This commit is contained in:
Tagir Valeev
2018-10-05 14:34:07 +07:00
parent 8b01b90781
commit ccc9e29492
9 changed files with 270 additions and 0 deletions
@@ -734,6 +734,11 @@
groupKey="group.names.language.level.specific.issues.and.migration.aids9" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.java19api.Java9CollectionFactoryInspection"
displayName="Immutable collection creation can be replaced with collection factory call"/>
<localInspection groupPath="Java,Java language level migration aids" language="JAVA" shortName="ReadWriteStringCanBeUsed"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.language.level.specific.issues.and.migration.aids11" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.ReadWriteStringCanBeUsedInspection"
displayName="'Files.readString()' or 'Files.writeString()' can be used"/>
<localInspection groupPath="Java,Java language level migration aids" language="JAVA" shortName="Java8MapForEach"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.language.level.specific.issues.and.migration.aids8" enabledByDefault="true" level="WARNING"
@@ -0,0 +1,137 @@
// 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.intellij.codeInspection;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ConstructionUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import static com.intellij.util.ObjectUtils.tryCast;
public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalInspectionTool {
private static final CallMatcher FILES_WRITE = CallMatcher.staticCall("java.nio.file.Files", "write")
.parameterTypes("java.nio.file.Path", "byte[]", "java.nio.file.OpenOption...");
private static final CallMatcher FILES_READ_ALL_BYTES = CallMatcher.staticCall("java.nio.file.Files", "readAllBytes")
.parameterTypes("java.nio.file.Path");
private static final CallMatcher STRING_GET_BYTES = CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_STRING, "getBytes")
.parameterTypes("java.nio.charset.Charset");
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.isLanguageLevel11OrHigher(holder.getFile())) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@Override
public void visitMethodCallExpression(PsiMethodCallExpression call) {
if (FILES_WRITE.test(call)) {
PsiMethodCallExpression bytesExpression = tryCast(ExpressionUtils.resolveExpression(call.getArgumentList().getExpressions()[1]), PsiMethodCallExpression.class);
if (STRING_GET_BYTES.test(bytesExpression) && bytesExpression.getMethodExpression().getQualifierExpression() != null) {
holder.registerProblem(call, "Can be replaced with 'Files.writeString()'",
new ReplaceWithWriteStringFix());
}
} else if (FILES_READ_ALL_BYTES.test(call)) {
PsiExpressionList expressionList = tryCast(PsiUtil.skipParenthesizedExprUp(call.getParent()), PsiExpressionList.class);
if (expressionList != null) {
PsiNewExpression newExpression = tryCast(expressionList.getParent(), PsiNewExpression.class);
if (newExpression != null && newExpression.getAnonymousClass() == null &&
ConstructionUtils.isReferenceTo(newExpression.getClassReference(), CommonClassNames.JAVA_LANG_STRING)) {
PsiExpression[] args = expressionList.getExpressions();
if (args.length == 2 && PsiTreeUtil.isAncestor(args[0], call, false) &&
TypeUtils.typeEquals("java.nio.charset.Charset", args[1].getType())) {
holder.registerProblem(newExpression, "Can be replaced with 'Files.readString()'",
new ReplaceWithReadStringFix());
}
}
}
}
}
};
}
private static class ReplaceWithReadStringFix implements LocalQuickFix {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return CommonQuickFixBundle.message("fix.replace.with.x", "Files.readString()");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiNewExpression newExpression = tryCast(descriptor.getStartElement(), PsiNewExpression.class);
if (newExpression == null) return;
PsiExpressionList newArgList = newExpression.getArgumentList();
if (newArgList == null) return;
PsiExpression[] newArgs = newArgList.getExpressions();
if (newArgs.length != 2) return;
PsiMethodCallExpression readCall = tryCast(PsiUtil.skipParenthesizedExprDown(newArgs[0]), PsiMethodCallExpression.class);
if (!FILES_READ_ALL_BYTES.test(readCall)) return;
PsiExpression charsetExpression = newArgs[1];
CommentTracker ct = new CommentTracker();
ExpressionUtils.bindCallTo(readCall, "readString");
if (!isUtf8Charset(charsetExpression)) {
readCall.getArgumentList().add(ct.markUnchanged(charsetExpression));
}
ct.replaceAndRestoreComments(newExpression, readCall);
}
}
private static class ReplaceWithWriteStringFix implements LocalQuickFix {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return CommonQuickFixBundle.message("fix.replace.with.x", "Files.writeString()");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression writeCall = tryCast(descriptor.getStartElement(), PsiMethodCallExpression.class);
if (!FILES_WRITE.test(writeCall)) return;
PsiExpressionList argumentList = writeCall.getArgumentList();
PsiExpression[] args = argumentList.getExpressions();
if (args.length < 2) return;
PsiExpression bytesArg = args[1];
PsiMethodCallExpression bytesExpression = tryCast(ExpressionUtils.resolveExpression(bytesArg), PsiMethodCallExpression.class);
if (!STRING_GET_BYTES.test(bytesExpression)) return;
PsiExpression stringExpression = PsiUtil.skipParenthesizedExprDown(bytesExpression.getMethodExpression().getQualifierExpression());
if (stringExpression == null) return;
PsiExpression charsetExpression = bytesExpression.getArgumentList().getExpressions()[0];
CommentTracker ct = new CommentTracker();
ExpressionUtils.bindCallTo(writeCall, "writeString");
if (!isUtf8Charset(charsetExpression)) {
argumentList.addAfter(ct.markUnchanged(charsetExpression), bytesArg);
}
PsiLocalVariable variable = ExpressionUtils.resolveLocalVariable(PsiUtil.skipParenthesizedExprDown(bytesArg));
ct.replaceAndRestoreComments(bytesArg, stringExpression);
if (variable != null) {
ct = new CommentTracker();
ct.markUnchanged(stringExpression);
ct.deleteAndRestoreComments(variable);
}
}
}
static boolean isUtf8Charset(PsiExpression expression) {
PsiReferenceExpression ref = tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiReferenceExpression.class);
if (ref == null) return false;
if (!"UTF_8".equals(ref.getReferenceName())) {
return false;
}
PsiField target = tryCast(ref.resolve(), PsiField.class);
return target != null &&
target.getContainingClass() != null &&
"java.nio.charset.StandardCharsets".equals(target.getContainingClass().getQualifiedName());
}
}
@@ -0,0 +1,8 @@
<html>
<body>
Reports code fragments which could be replaced via methods <b>Files.readString</b> and <b>Files.writeString</b>
which were introduced in Java 11.
<!-- tooltip end -->
<p><small>New in 2018.3</small></p>
</body>
</html>
@@ -0,0 +1,21 @@
// "Fix all ''Files.readString()' or 'Files.writeString()' can be used' problems in file" "true"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Example {
void testRead1() throws IOException {
String s = Files.readString(Paths.get("/etc/passwd"), StandardCharsets.ISO_8859_1);
}
void testRead2() throws IOException {
/*3*/
/*4*/
/*8*/
/*9*/
/*1*/
/*2*/
String s = Files/*5*/./*6*/readString(/*7*/Paths.get("/etc/passwd"));
}
}
@@ -0,0 +1,27 @@
// "Fix all ''Files.readString()' or 'Files.writeString()' can be used' problems in file" "true"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
void test1() throws IOException {
/*1*/
/*3*/
/*4*/
/*5*/
Files.writeString(Paths.get("/etc/passwd"), "foo"/*2*/+"bar", StandardCharsets.ISO_8859_1/*6*/, StandardOpenOption.CREATE);
}
void test2() throws IOException {
/*3*/
/*1*/
/*2*/
Files.writeString(Paths.get("/etc/passwd"), "foo");
}
void test3() throws IOException {
Files.writeString(Paths.get("/etc/passwd"), "foo");
}
}
@@ -0,0 +1,15 @@
// "Fix all ''Files.readString()' or 'Files.writeString()' can be used' problems in file" "true"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Example {
void testRead1() throws IOException {
String s = new String(Files<caret>.readAllBytes(Paths.get("/etc/passwd")), StandardCharsets.ISO_8859_1);
}
void testRead2() throws IOException {
String s = new /*3*/String(/*4*/Files/*5*/./*6*/readAllBytes(/*7*/Paths.get("/etc/passwd"))/*8*/, /*9*/(StandardCharsets./*1*/UTF_8/*2*/));
}
}
@@ -0,0 +1,22 @@
// "Fix all ''Files.readString()' or 'Files.writeString()' can be used' problems in file" "true"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
void test1() throws IOException {
byte[] /*1*/bytes = ("foo"/*2*/+"bar")./*3*/getBytes(StandardCharsets.ISO_8859_1/*4*/)/*5*/;
Files.write<caret>(Paths.get("/etc/passwd"), bytes/*6*/, StandardOpenOption.CREATE);
}
void test2() throws IOException {
byte[] bytes = "foo".getBytes(StandardCharsets.UTF_8/*3*/);
Files.write(Paths.get("/etc/passwd"), (/*1*/bytes/*2*/));
}
void test3() throws IOException {
Files.write(Paths.get("/etc/passwd"), (("foo").getBytes(StandardCharsets.UTF_8)));
}
}
@@ -0,0 +1,34 @@
// 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.intellij.java.codeInspection;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ReadWriteStringCanBeUsedInspection;
import com.intellij.codeInspection.java19api.Java9CollectionFactoryInspection;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import org.jetbrains.annotations.NotNull;
public class ReadWriteStringCanBeUsedInspectionTest extends LightQuickFixParameterizedTestCase {
@Override
protected LanguageLevel getDefaultLanguageLevel() {
return LanguageLevel.JDK_11;
}
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new ReadWriteStringCanBeUsedInspection()};
}
@Override
protected Sdk getProjectJDK() {
return IdeaTestUtil.getMockJdk9();
}
@Override
protected String getBasePath() {
return "/inspection/filesReadWriteString";
}
}
@@ -584,6 +584,7 @@ group.names.language.level.specific.issues.and.migration.aids8=Java 8
group.names.language.level.specific.issues.and.migration.aids9=Java 9
group.names.language.level.specific.issues.and.migration.aids5=Java 5
group.names.language.level.specific.issues.and.migration.aids10=Java 10
group.names.language.level.specific.issues.and.migration.aids11=Java 11
group.names.language.level.specific.issues.and.migration.aids12=Java 12
group.names.javabeans.issues=JavaBeans issues
group.names.inheritance.issues=Inheritance issues