mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
MultiplePathConstructionsInspection: added inspection (IDEA-288529)
Reports multiple Path.of() or Paths.get() calls in a row with same expression GitOrigin-RevId: 654825f7946243d0fd58ed2d2d4d577305ad0da5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
11746b4456
commit
9dcbd71cc6
@@ -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<PsiExpressionList, List<PsiMethodCallExpression>> myCalls =
|
||||
CollectionFactory.createCustomHashingStrategyMap(new ExpressionListHashingStrategy());
|
||||
|
||||
|
||||
private static class ExpressionListHashingStrategy implements HashingStrategy<PsiExpressionList> {
|
||||
|
||||
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<PsiMethodCallExpression> calls = myCalls.computeIfAbsent(args, k -> new SmartList<>());
|
||||
calls.add(expression);
|
||||
}
|
||||
|
||||
private Map<PsiExpressionList, List<PsiMethodCallExpression>> 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<PsiMethodCallExpression> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1878,6 +1878,11 @@
|
||||
groupKey="group.names.performance.issues" groupBundle="messages.InspectionsBundle"
|
||||
enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.BulkFileAttributesReadInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="MultiplePathConstructions"
|
||||
key="inspection.multiple.path.constructions.description" bundle="messages.JavaBundle"
|
||||
groupKey="group.names.performance.issues" groupBundle="messages.InspectionsBundle"
|
||||
enabledByDefault="true" level="WEAK WARNING"
|
||||
implementationClass="com.intellij.codeInspection.MultiplePathConstructionsInspection"/>
|
||||
<globalInspection groupPath="Java" language="JAVA" shortName="EmptyMethod" groupKey="group.names.declaration.redundancy" enabledByDefault="true" groupBundle="messages.InspectionsBundle"
|
||||
level="WARNING" implementationClass="com.intellij.codeInspection.emptyMethod.EmptyMethodInspection"
|
||||
key="inspection.empty.method.display.name" bundle="messages.JavaBundle"/>
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
final class ExpressionHashingStrategy implements HashingStrategy<PsiExpression> {
|
||||
public final class ExpressionHashingStrategy implements HashingStrategy<PsiExpression> {
|
||||
private static final EquivalenceChecker EQUIVALENCE_CHECKER = new NoSideEffectExpressionEquivalenceChecker();
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports multiple <code>java.nio.file.Path</code> constructions <code>java.nio.file.Paths.get</code> or <code>java.nio.file.Path.of</code>
|
||||
in a row when it is possible to replace them with a single <code>java.nio.file.Path</code> variable.
|
||||
<p>Example:</p>
|
||||
<pre><code>
|
||||
if (Files.isRegularFile(Path.of(fileName))) {
|
||||
try(InputStream is = Files.newInputStream(Path.of(fileName))) {
|
||||
// some code
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>After the quick-fix is applied:</p>
|
||||
<pre><code>
|
||||
Path path = Path.of(fileName);
|
||||
if (Files.isRegularFile(path)) {
|
||||
try(InputStream is = Files.newInputStream(path)) {
|
||||
// some code
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<!-- tooltip end -->
|
||||
<p>This inspection only reports if the language level of the project or module is 7 or higher.</p>
|
||||
<p><small>New in 2022.1</small></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(fileNa<caret>me));
|
||||
}
|
||||
}
|
||||
@@ -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(fil<caret>eName).isAbsolute()) return false;
|
||||
}
|
||||
String fileName = "bar";
|
||||
return Files.isDirectory(Path.of(fileName));
|
||||
}
|
||||
}
|
||||
@@ -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<caret>).isAbsolute()) return false;
|
||||
System.out.println(fileName);
|
||||
return Files.isDirectory(Path.of(fileName));
|
||||
}
|
||||
}
|
||||
@@ -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.<caret>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"))));
|
||||
}
|
||||
}
|
||||
@@ -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<caret>).isAbsolute()) return false;
|
||||
if (b) fileName = "baz";
|
||||
return Files.isDirectory(Path.of(fileName));
|
||||
}
|
||||
}
|
||||
@@ -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<caret>, b ? bar + "baz" : "baz").isAbsolute()) return false;
|
||||
System.out.println(fileName);
|
||||
bar = "bar";
|
||||
return Files.isDirectory(Path.of(fileName, b ? bar + "baz" : "baz"));
|
||||
}
|
||||
}
|
||||
@@ -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<caret>, bar + "baz").isAbsolute()) return false;
|
||||
System.out.println(fileName);
|
||||
return Files.isDirectory(Path.of(fileName, bar + "baz"));
|
||||
}
|
||||
}
|
||||
@@ -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(fileNa<caret>me));
|
||||
}
|
||||
}
|
||||
@@ -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<caret>).isAbsolute()) return false;
|
||||
return Files.isDirectory(Paths.get(fileName));
|
||||
}
|
||||
}
|
||||
@@ -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.le<caret>ngth() > 2 ? start + end : "baz").isAbsolute()) return false;
|
||||
return Files.isDirectory(/*0*/Paths.get(((((start/*1*/).length()) > 2) ? ((start)/*2*/ + end) : ("baz"))/*3*/));
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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}<br>Problem: {1}
|
||||
external.annotations.open.file=Open annotations file
|
||||
|
||||
Reference in New Issue
Block a user