Field can be local: added support for nested and local classes (IDEA-206647)

This commit is contained in:
Artemiy Sartakov
2019-02-07 17:25:25 +07:00
parent 164e3869e1
commit 043c2d563e
12 changed files with 161 additions and 5 deletions

View File

@@ -69,10 +69,13 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo
if (candidates.isEmpty()) return;
final List<ImplicitUsageProvider> implicitUsageProviders = ImplicitUsageProvider.EP_NAME.getExtensionList();
PsiClass scope = PsiTreeUtil.getTopmostParentOfType(aClass, PsiClass.class);
if (scope == null) scope = aClass;
FieldLoop:
for (final PsiField field : candidates) {
if (usedFields.contains(field) && !hasImplicitReadOrWriteUsage(field, implicitUsageProviders)) {
final Query<PsiReference> references = ReferencesSearch.search(field, new LocalSearchScope(aClass));
final Query<PsiReference> references = ReferencesSearch.search(field, new LocalSearchScope(scope));
final Map<PsiCodeBlock, Collection<PsiReference>> refs = new HashMap<>();
for (PsiReference reference : references.findAll()) {
final PsiElement element = reference.getElement();
@@ -151,6 +154,11 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo
//do not go inside class initializer
}
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {
// do not go inside lambda
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
excludeFieldCandidate(expression);
@@ -295,7 +303,7 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo
private static boolean groupReferenceByCodeBlocks(Map<PsiCodeBlock, Collection<PsiReference>> refs, PsiReference psiReference) {
final PsiElement element = psiReference.getElement();
final PsiCodeBlock block = PsiTreeUtil.getTopmostParentOfType(element, PsiCodeBlock.class);
final PsiCodeBlock block = getTopmostBlock(element);
if (block == null) {
return false;
}
@@ -310,6 +318,17 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo
return true;
}
@Nullable
private static PsiCodeBlock getTopmostBlock(@NotNull PsiElement element) {
PsiElement parent = element.getParent();
PsiCodeBlock block = null;
while (parent != null && !(parent instanceof PsiClass)) {
if (parent instanceof PsiCodeBlock) block = (PsiCodeBlock)parent;
parent = parent.getParent();
}
return block;
}
private static boolean findExistentBlock(Map<PsiCodeBlock, Collection<PsiReference>> refs,
PsiReference psiReference,
PsiCodeBlock block,
@@ -365,8 +384,29 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo
return new JavaElementVisitor() {
@Override
public void visitJavaFile(PsiJavaFile file) {
final JavaElementVisitor visitor = new JavaElementVisitor() {
@Override
public void visitClass(PsiClass aClass) {
super.visitClass(aClass);
Arrays.stream(aClass.getChildren()).forEach(c -> c.accept(this));
doCheckClass(aClass, holder, EXCLUDE_ANNOS, IGNORE_FIELDS_USED_IN_MULTIPLE_METHODS);
}
@Override
public void visitDeclarationStatement(PsiDeclarationStatement statement) {
super.visitDeclarationStatement(statement);
Arrays.stream(statement.getDeclaredElements()).forEach(d -> d.accept(this));
}
@Override
public void visitMethod(PsiMethod method) {
super.visitMethod(method);
final PsiCodeBlock body = method.getBody();
if (body != null) Arrays.stream(body.getChildren()).forEach(c -> c.accept(this));
}
};
for (PsiClass aClass : file.getClasses()) {
doCheckClass(aClass, holder, EXCLUDE_ANNOS, IGNORE_FIELDS_USED_IN_MULTIPLE_METHODS);
aClass.accept(visitor);
}
}
};

View File

@@ -0,0 +1,12 @@
// "Convert field to local variable in method 'test'" "true"
class Foo {
void test() {
class Bar {
void test() {
int x = 2; // could be local
System.out.println(x);
}
}
}
}

View File

@@ -0,0 +1,19 @@
// "Convert to local" "true"
class Outer {
void test() {
class Local {
void foo() {
String s = "1";
System.out.println(s);
}
void bar() {
String s = "2";
System.out.println(s);
}
}
}
}

View File

@@ -0,0 +1,10 @@
// "Convert field to local variable in method 'test'" "true"
class Foo {
static class Bar {
void test() {
int x = 2; // could be local
System.out.println(x);
}
}
}

View File

@@ -0,0 +1,7 @@
// "Convert to local" "true"
class Test {
private Runnable r = () -> {
String field = "foo";
}
}

View File

@@ -0,0 +1,15 @@
// "Fix all 'Field can be local' problems in file" "false"
class Outer {
void test(Inner inner) {
System.out.println(inner.field);
}
class Inner {
private final String f<caret>ield;
Inner(String field) {
this.field = field;
}
}
}

View File

@@ -0,0 +1,13 @@
// "Convert field to local variable in method 'test'" "true"
class Foo {
void test() {
class Bar {
private int <caret>x;
void test() {
x = 2; // could be local
System.out.println(x);
}
}
}
}

View File

@@ -0,0 +1,20 @@
// "Convert to local" "true"
class Outer {
void test() {
class Local {
private String <caret>s;
void foo() {
s = "1";
System.out.println(s);
}
void bar() {
s = "2";
System.out.println(s);
}
}
}
}

View File

@@ -0,0 +1,11 @@
// "Convert field to local variable in method 'test'" "true"
class Foo {
static class Bar {
private int <caret>x;
void test() {
x = 2; // could be local
System.out.println(x);
}
}
}

View File

@@ -0,0 +1,9 @@
// "Convert to local" "true"
class Test {
private String <caret>field;
private Runnable r = () -> {
field = "foo";
}
}

View File

@@ -2,7 +2,7 @@
private int value = 0;
public class Inner {
private final int myValue;
private final int <warning descr="Field can be converted to a local variable">myValue</warning>;
public Inner() {
myValue = value++;

View File

@@ -2,7 +2,7 @@
private int <warning descr="Field can be converted to a local variable">value</warning> = 0;
public class Inner {
private final int myValue;
private final int <warning descr="Field can be converted to a local variable">myValue</warning>;
public Inner() {
myValue = value;