From 9dcbd71cc6da5f26f505e96b15dd82565e5e3fa5 Mon Sep 17 00:00:00 2001 From: Artemiy Sartakov Date: Fri, 11 Feb 2022 13:26:46 +0700 Subject: [PATCH] MultiplePathConstructionsInspection: added inspection (IDEA-288529) Reports multiple Path.of() or Paths.get() calls in a row with same expression GitOrigin-RevId: 654825f7946243d0fd58ed2d2d4d577305ad0da5 --- .../MultiplePathConstructionsInspection.java | 194 ++++++++++++++++++ java/java-impl/src/META-INF/JavaPlugin.xml | 5 + .../ExpressionHashingStrategy.java | 2 +- .../MultiplePathConstructions.html | 32 +++ .../afterPathOfTwoCallsFinalVar.java | 13 ++ .../afterPathOfTwoCallsTwoFinalArgs.java | 13 ++ .../afterPathsGetPathOf.java | 11 + ...PathsGetPathOfCallsOneWithParenthesis.java | 14 ++ .../beforePathOfOneCall.java | 9 + .../beforePathOfTwoCallsDifferentArg.java | 14 ++ .../beforePathOfTwoCallsFinalVar.java | 12 ++ .../beforePathOfTwoCallsMutatedPartOfArg.java | 11 + .../beforePathOfTwoCallsMutatedVar.java | 12 ++ ...thOfTwoCallsTwoArgsOneFinalOneMutated.java | 13 ++ .../beforePathOfTwoCallsTwoFinalArgs.java | 12 ++ .../beforePathsGetOneCall.java | 9 + .../beforePathsGetPathOf.java | 10 + ...PathsGetPathOfCallsOneWithParenthesis.java | 10 + ...ltiplePathConstructionsInspectionTest.java | 33 +++ .../resources/messages/JavaBundle.properties | 2 + 20 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 java/java-impl-inspections/src/com/intellij/codeInspection/MultiplePathConstructionsInspection.java create mode 100644 java/java-impl/src/inspectionDescriptions/MultiplePathConstructions.html create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsFinalVar.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsTwoFinalArgs.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOfCallsOneWithParenthesis.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfOneCall.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsDifferentArg.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsFinalVar.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedPartOfArg.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedVar.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoArgsOneFinalOneMutated.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoFinalArgs.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetOneCall.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOf.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOfCallsOneWithParenthesis.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInspection/MultiplePathConstructionsInspectionTest.java diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/MultiplePathConstructionsInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/MultiplePathConstructionsInspection.java new file mode 100644 index 000000000000..b69ff502a03e --- /dev/null +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/MultiplePathConstructionsInspection.java @@ -0,0 +1,194 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInspection; + +import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; +import com.intellij.codeInspection.duplicateExpressions.ExpressionHashingStrategy; +import com.intellij.java.JavaBundle; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.CommonJavaRefactoringUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.SmartList; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashingStrategy; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.CommentTracker; +import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.HighlightUtils; +import com.siyeh.ig.psiutils.TypeUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +public class MultiplePathConstructionsInspection extends AbstractBaseJavaLocalInspectionTool { + + private static final CallMatcher PATH_CONSTRUCTION_CALL = CallMatcher.anyOf( + CallMatcher.staticCall("java.nio.file.Path", "of"), + CallMatcher.staticCall("java.nio.file.Paths", "get") + ); + + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + if (!PsiUtil.isLanguageLevel7OrHigher(holder.getFile())) return PsiElementVisitor.EMPTY_VISITOR; + return new JavaElementVisitor() { + @Override + public void visitMethod(PsiMethod method) { + super.visitMethod(method); + PsiCodeBlock methodBody = method.getBody(); + if (methodBody == null) return; + PathCallsCollector pathCallsCollector = new PathCallsCollector(); + methodBody.accept(pathCallsCollector); + pathCallsCollector.getCalls().forEach((args, calls) -> { + if (calls.size() < 2 || !isEffectivelyFinal(methodBody, args)) return; + calls.forEach(call -> holder.registerProblem(call, + JavaBundle.message("inspection.multiple.path.constructions.description"), + new ReplaceWithPathVariableFix(isOnTheFly))); + }); + } + }; + } + + private static boolean isEffectivelyFinal(@NotNull PsiElement context, @NotNull PsiExpressionList expressionList) { + return ContainerUtil.and(expressionList.getExpressions(), e -> isEffectivelyFinal(context, e)); + } + + private static boolean isEffectivelyFinal(@NotNull PsiElement context, PsiExpression expression) { + return ExpressionUtils.nonStructuralChildren(expression).allMatch(c -> isEffectivelyFinal(context, expression, c)); + } + + private static boolean isEffectivelyFinal(PsiElement context, PsiExpression parent, PsiExpression child) { + if (child != parent) return isEffectivelyFinal(context, child); + if (child instanceof PsiLiteralExpression) return true; + if (child instanceof PsiPolyadicExpression) { + return ContainerUtil.and(((PsiPolyadicExpression)child).getOperands(), e -> isEffectivelyFinal(context, e)); + } + if (!(child instanceof PsiReferenceExpression)) return false; + PsiVariable target = ObjectUtils.tryCast(((PsiReferenceExpression)child).resolve(), PsiVariable.class); + if (!PsiUtil.isJvmLocalVariable(target)) return false; + return HighlightControlFlowUtil.isEffectivelyFinal(target, context, null); + } + + private static class PathCallsCollector extends JavaRecursiveElementWalkingVisitor { + + private final Map> myCalls = + CollectionFactory.createCustomHashingStrategyMap(new ExpressionListHashingStrategy()); + + + private static class ExpressionListHashingStrategy implements HashingStrategy { + + private static final ExpressionHashingStrategy EXPRESSION_STRATEGY = new ExpressionHashingStrategy(); + + @Override + public int hashCode(PsiExpressionList list) { + if (list == null) return 0; + int hash = 0; + for (PsiExpression expression : list.getExpressions()) { + hash = hash * 31 + EXPRESSION_STRATEGY.hashCode(expression); + } + return hash; + } + + @Override + public boolean equals(PsiExpressionList l1, PsiExpressionList l2) { + if (l1 == null || l2 == null) return l1 == l2; + PsiExpression[] e1 = l1.getExpressions(); + PsiExpression[] e2 = l2.getExpressions(); + if (e1.length != e2.length) return false; + for (int i = 0; i < e1.length; i++) { + if (!EXPRESSION_STRATEGY.equals(e1[i], e2[i])) return false; + } + return true; + } + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + if (!PATH_CONSTRUCTION_CALL.test(expression)) return; + PsiExpressionList args = expression.getArgumentList(); + List calls = myCalls.computeIfAbsent(args, k -> new SmartList<>()); + calls.add(expression); + } + + private Map> getCalls() { + return myCalls; + } + } + + private static class ReplaceWithPathVariableFix implements LocalQuickFix { + + private final boolean myIsOnTheFly; + + private ReplaceWithPathVariableFix(boolean isOnTheFly) { + myIsOnTheFly = isOnTheFly; + } + + @Override + public @NotNull String getFamilyName() { + return JavaBundle.message("inspection.extract.to.path.variable.fix.family.name"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiMethodCallExpression call = ObjectUtils.tryCast(descriptor.getPsiElement(), PsiMethodCallExpression.class); + if (call == null) return; + PsiMethod containingMethod = PsiTreeUtil.getParentOfType(call, PsiMethod.class); + if (containingMethod == null) return; + PsiCodeBlock methodBody = containingMethod.getBody(); + if (methodBody == null) return; + if (!PATH_CONSTRUCTION_CALL.test(call)) return; + PsiExpressionList args = call.getArgumentList(); + PathCallsCollector callsCollector = new PathCallsCollector(); + methodBody.accept(callsCollector); + List argCalls = callsCollector.getCalls().get(args); + if (argCalls == null) return; + PsiExpression[] occurrences = argCalls.toArray(PsiExpression.EMPTY_ARRAY); + if (occurrences.length < 2) return; + PsiElement anchor = CommonJavaRefactoringUtil.getAnchorElementForMultipleExpressions(occurrences, containingMethod); + if (anchor == null) return; + PsiElement parent = anchor.getParent(); + if (parent == null) return; + PsiDeclarationStatement declaration = createDeclaration(methodBody, call, anchor); + if (declaration == null) return; + declaration = (PsiDeclarationStatement)parent.addBefore(declaration, anchor); + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(parent.getProject()); + declaration = (PsiDeclarationStatement)codeStyleManager.shortenClassReferences(declaration); + PsiVariable pathVar = ObjectUtils.tryCast(declaration.getDeclaredElements()[0], PsiVariable.class); + if (pathVar == null) return; + String pathVarName = pathVar.getName(); + if (pathVarName == null) return; + PsiReference[] refs = new PsiReference[occurrences.length]; + for (int i = 0; i < occurrences.length; i++) { + PsiExpression occurrence = occurrences[i]; + refs[i] = (PsiReference)new CommentTracker().replaceAndRestoreComments(occurrence, pathVarName); + } + if (!myIsOnTheFly) return; + HighlightUtils.showRenameTemplate(containingMethod, pathVar, refs); + } + + private static @Nullable PsiDeclarationStatement createDeclaration(@NotNull PsiCodeBlock block, + @NotNull PsiMethodCallExpression toPathCall, + @NotNull PsiElement anchor) { + PsiClassType type = TypeUtils.getType("java.nio.file.Path", block); + String varName = getSuggestedName(type, toPathCall, anchor); + if (varName == null) return null; + PsiElementFactory elementFactory = PsiElementFactory.getInstance(block.getProject()); + return elementFactory.createVariableDeclarationStatement(varName, type, toPathCall); + } + + private static @Nullable String getSuggestedName(@NotNull PsiType type, + @NotNull PsiExpression initializer, + @NotNull PsiElement anchor) { + SuggestedNameInfo nameInfo = CommonJavaRefactoringUtil.getSuggestedName(type, initializer, anchor); + String[] names = nameInfo.names; + return names.length == 0 ? null : names[0]; + } + } +} diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 1c3081355e32..6c2fdad06f20 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1878,6 +1878,11 @@ groupKey="group.names.performance.issues" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.BulkFileAttributesReadInspection"/> + diff --git a/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/ExpressionHashingStrategy.java b/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/ExpressionHashingStrategy.java index 48369ce7b7ac..eb875284100c 100644 --- a/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/ExpressionHashingStrategy.java +++ b/java/java-impl/src/com/intellij/codeInspection/duplicateExpressions/ExpressionHashingStrategy.java @@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable; /** * @author Pavel.Dolgov */ -final class ExpressionHashingStrategy implements HashingStrategy { +public final class ExpressionHashingStrategy implements HashingStrategy { private static final EquivalenceChecker EQUIVALENCE_CHECKER = new NoSideEffectExpressionEquivalenceChecker(); @Override diff --git a/java/java-impl/src/inspectionDescriptions/MultiplePathConstructions.html b/java/java-impl/src/inspectionDescriptions/MultiplePathConstructions.html new file mode 100644 index 000000000000..71e837b67454 --- /dev/null +++ b/java/java-impl/src/inspectionDescriptions/MultiplePathConstructions.html @@ -0,0 +1,32 @@ + + +Reports multiple java.nio.file.Path constructions java.nio.file.Paths.get or java.nio.file.Path.of +in a row when it is possible to replace them with a single java.nio.file.Path variable. +

Example:

+

+if (Files.isRegularFile(Path.of(fileName))) {
+  try(InputStream is = Files.newInputStream(Path.of(fileName))) {
+    // some code
+  }
+  catch (IOException e) {
+    throw new UncheckedIOException(e);
+  }
+}
+
+

After the quick-fix is applied:

+

+Path path = Path.of(fileName);
+if (Files.isRegularFile(path)) {
+  try(InputStream is = Files.newInputStream(path)) {
+    // some code
+  }
+  catch (IOException e) {
+    throw new UncheckedIOException(e);
+  }
+}
+
+ +

This inspection only reports if the language level of the project or module is 7 or higher.

+

New in 2022.1

+ + \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsFinalVar.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsFinalVar.java new file mode 100644 index 000000000000..3e5a04410b4d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsFinalVar.java @@ -0,0 +1,13 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory() { + String fileName = "foo"; + Path of = Path.of(fileName); + if (of.isAbsolute()) return false; + System.out.println(fileName); + return Files.isDirectory(of); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsTwoFinalArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsTwoFinalArgs.java new file mode 100644 index 000000000000..9088ee6d22b3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathOfTwoCallsTwoFinalArgs.java @@ -0,0 +1,13 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(String bar) { + String fileName = "foo"; + Path of = Path.of(fileName, bar + "baz"); + if (of.isAbsolute()) return false; + System.out.println(fileName); + return Files.isDirectory(of); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOf.java new file mode 100644 index 000000000000..2ece29d2195f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOf.java @@ -0,0 +1,11 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(String fileName) { + Path of = Path.of(fileName); + if (of.isAbsolute()) return false; + return Files.isDirectory(of); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOfCallsOneWithParenthesis.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOfCallsOneWithParenthesis.java new file mode 100644 index 000000000000..79f095cefd4b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/afterPathsGetPathOfCallsOneWithParenthesis.java @@ -0,0 +1,14 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(String start, String end) { + Path of = Path.of(start.length() > 2 ? start + end : "baz"); + if (of.isAbsolute()) return false; + /*1*/ + /*2*/ + /*3*/ + return Files.isDirectory(/*0*/of); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfOneCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfOneCall.java new file mode 100644 index 000000000000..27e5431e0a6f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfOneCall.java @@ -0,0 +1,9 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "false" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isDirectory(String fileName) { + return Files.isDirectory(Path.of(fileName)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsDifferentArg.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsDifferentArg.java new file mode 100644 index 000000000000..ff8c4322f846 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsDifferentArg.java @@ -0,0 +1,14 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "false" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(boolean b) { + { + String fileName = "foo"; + if (Path.of(fileName).isAbsolute()) return false; + } + String fileName = "bar"; + return Files.isDirectory(Path.of(fileName)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsFinalVar.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsFinalVar.java new file mode 100644 index 000000000000..81835a2679a5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsFinalVar.java @@ -0,0 +1,12 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory() { + String fileName = "foo"; + if (Path.of(fileName).isAbsolute()) return false; + System.out.println(fileName); + return Files.isDirectory(Path.of(fileName)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedPartOfArg.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedPartOfArg.java new file mode 100644 index 000000000000..7858858a0c78 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedPartOfArg.java @@ -0,0 +1,11 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "false" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(String start, String end) { + if (Path.of((start.length() > 2 ? (start + end) : ("baz"))).isAbsolute()) return false; + if (end.length() > 2) end = "fo"; + return Files.isDirectory(Path.of((start.length() > 2 ? (start + end) : ("baz")))); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedVar.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedVar.java new file mode 100644 index 000000000000..cf6988901495 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsMutatedVar.java @@ -0,0 +1,12 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "false" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(boolean b) { + String fileName = "foo"; + if (Path.of(fileName).isAbsolute()) return false; + if (b) fileName = "baz"; + return Files.isDirectory(Path.of(fileName)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoArgsOneFinalOneMutated.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoArgsOneFinalOneMutated.java new file mode 100644 index 000000000000..8ca1dbd3c4c3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoArgsOneFinalOneMutated.java @@ -0,0 +1,13 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "false" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(boolean b, String bar) { + String fileName = "foo"; + if (Path.of(fileName, b ? bar + "baz" : "baz").isAbsolute()) return false; + System.out.println(fileName); + bar = "bar"; + return Files.isDirectory(Path.of(fileName, b ? bar + "baz" : "baz")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoFinalArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoFinalArgs.java new file mode 100644 index 000000000000..de7d5f873a5e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathOfTwoCallsTwoFinalArgs.java @@ -0,0 +1,12 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(String bar) { + String fileName = "foo"; + if (Path.of(fileName, bar + "baz").isAbsolute()) return false; + System.out.println(fileName); + return Files.isDirectory(Path.of(fileName, bar + "baz")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetOneCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetOneCall.java new file mode 100644 index 000000000000..5f189dfea256 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetOneCall.java @@ -0,0 +1,9 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "false" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isDirectory(String fileName) { + return Files.isDirectory(Paths.get(fileName)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOf.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOf.java new file mode 100644 index 000000000000..57496039fd98 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOf.java @@ -0,0 +1,10 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(String fileName) { + if (Path.of(fileName).isAbsolute()) return false; + return Files.isDirectory(Paths.get(fileName)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOfCallsOneWithParenthesis.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOfCallsOneWithParenthesis.java new file mode 100644 index 000000000000..53ab59a6ed4c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions/beforePathsGetPathOfCallsOneWithParenthesis.java @@ -0,0 +1,10 @@ +// "Extract 'java.nio.file.Path' constructions to variable" "true" +import java.io.*; +import java.nio.file.*; + +class Foo { + public boolean isRelativeDirectory(String start, String end) { + if (Path.of(start.length() > 2 ? start + end : "baz").isAbsolute()) return false; + return Files.isDirectory(/*0*/Paths.get(((((start/*1*/).length()) > 2) ? ((start)/*2*/ + end) : ("baz"))/*3*/)); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/MultiplePathConstructionsInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/MultiplePathConstructionsInspectionTest.java new file mode 100644 index 000000000000..c08f836d2208 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/MultiplePathConstructionsInspectionTest.java @@ -0,0 +1,33 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInspection; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.MultiplePathConstructionsInspection; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.testFramework.IdeaTestUtil; +import org.jetbrains.annotations.NotNull; + +public class MultiplePathConstructionsInspectionTest extends LightQuickFixParameterizedTestCase { + + @Override + protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new MultiplePathConstructionsInspection()}; + } + + @Override + protected Sdk getProjectJDK() { + return IdeaTestUtil.getMockJdk11(); + } + + @Override + protected LanguageLevel getLanguageLevel() { + return LanguageLevel.JDK_11; + } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/multiplePathConstructions"; + } +} diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 349c9f13ddc9..eab7170b95f7 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -1725,6 +1725,8 @@ inspection.output.stream.constructor.message='OutputStream' can be constructed u inspection.bulk.file.attributes.read.description=Bulk 'Files.readAttributes' call can be used instead of multiple file attribute calls inspection.replace.with.bulk.file.attributes.read.fix.family.name=Replace with bulk 'Files.readAttributes' call inspection.bulk.file.attributes.read.message=Multiple file attribute calls can be replaced with single 'Files.readAttributes' call +inspection.multiple.path.constructions.description=Multiple 'java.nio.file.Path' constructions can be replaced with a single variable +inspection.extract.to.path.variable.fix.family.name=Extract 'java.nio.file.Path' constructions to variable external.annotations.problem.title=Unable to read external annotations external.annotations.problem.parse.error=File: {0}
Problem: {1} external.annotations.open.file=Open annotations file