Skip to content

Commit

Permalink
New ShortenFullyQualifiedTypeReferences recipe
Browse files Browse the repository at this point in the history
Allows to replace fully qualified type references with simple names and a corresponding import, provided that it doesn't result in any conflict.

Fixes: openrewrite#3094
  • Loading branch information
knutwannheden committed Apr 17, 2023
1 parent 0d3312d commit 9b11d3f
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.cleanup;

import org.junit.jupiter.api.Test;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;

public class ShortenFullyQualifiedTypeReferencesTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new ShortenFullyQualifiedTypeReferences());
}

@Test
void redundantImport() {
rewriteRun(
java(
"""
import java.util.List;
class T {
java.util.List<String> list;
}
""",
"""
import java.util.List;
class T {
List<String> list;
}
"""
)
);
}

@Test
void withinStaticFieldAccess() {
rewriteRun(
java(
"""
class T {
int dotall = java.util.regex.Pattern.DOTALL;
}
""",
"""
import java.util.regex.Pattern;
class T {
int dotall = Pattern.DOTALL;
}
"""
)
);
}

@Test
void inGenericTypeParameter() {
rewriteRun(
java(
"""
import java.util.List;
class T {
List<java.util.List<String>> list;
}
""",
"""
import java.util.List;
class T {
List<List<String>> list;
}
"""
)
);
}

@Test
void noImport() {
rewriteRun(
java(
"""
class T {
java.util.List<String> list;
}
""",
"""
import java.util.List;
class T {
List<String> list;
}
"""
)
);
}

@Test
void multipleConflictingTypesWithoutImport() {
rewriteRun(
java(
"""
class T {
java.util.List<String> list;
java.awt.List list2;
}
""",
"""
import java.util.List;
class T {
List<String> list;
java.awt.List list2;
}
"""
)
);
}

@Test
void conflictWithLocalType() {
rewriteRun(
java(
"""
class T {
java.util.List<String> list;
class List {
}
}
"""
)
);
}

@Test
void conflictExistingImport() {
rewriteRun(
java(
"""
import java.awt.List;
class T {
java.util.List<String> list;
}
"""
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.cleanup;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.SourceFile;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;

import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static org.openrewrite.java.tree.TypeUtils.isWellFormedType;

public class ShortenFullyQualifiedTypeReferences extends Recipe {

@Override
public String getDisplayName() {
return "Add imports for fully qualified references to types";
}

@Override
public String getDescription() {
return "Any fully qualified references to Java types will be replaced with corresponding simple "
+ "names and import statements, provided that it doesn't result in "
+ "any conflicts with other imports or types declared in the local compilation unit.";
}

@Override
public @Nullable Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(2);
}

@Override
protected TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaVisitor<ExecutionContext>() {
final Set<String> localTypes = new HashSet<>();
final Map<String, JavaType> importedTypes = new HashMap<>();
final Map<String, JavaType> staticallyImportedMembers = new HashMap<>();

@Override
public @Nullable J visitSourceFile(SourceFile sourceFile, ExecutionContext ctx) {
if (sourceFile instanceof J.CompilationUnit) {
J.CompilationUnit cu = (J.CompilationUnit) sourceFile;
JavaIsoVisitor<Set<String>> typeCollector = new JavaIsoVisitor<Set<String>>() {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Set<String> types) {
types.add(classDecl.getSimpleName());
return super.visitClassDeclaration(classDecl, types);
}
};
typeCollector.visit(cu, localTypes);
}
return super.visitSourceFile(sourceFile, ctx);
}

@Override
public J visitImport(J.Import impoort, ExecutionContext ctx) {
if (impoort.isStatic() && isWellFormedType(impoort.getQualid().getType())) {
staticallyImportedMembers.put(impoort.getQualid().getSimpleName(), impoort.getQualid().getType());
} else if (!impoort.isStatic() && isWellFormedType(impoort.getQualid().getType())) {
importedTypes.put(impoort.getQualid().getSimpleName(), impoort.getQualid().getType());
}
return impoort;
}

@Override
public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) {
JavaType type = fieldAccess.getType();
if (type instanceof JavaType.Class) {
String simpleName = fieldAccess.getSimpleName();
if (type.equals(importedTypes.get(simpleName))) {
return fieldAccess.getName().withPrefix(fieldAccess.getPrefix());
} else if (!importedTypes.containsKey(simpleName) && !localTypes.contains(simpleName)) {
maybeAddImport(((JavaType.FullyQualified) type).getFullyQualifiedName());
importedTypes.put(simpleName, type);
return fieldAccess.getName().withPrefix(fieldAccess.getPrefix());
}
}
return super.visitFieldAccess(fieldAccess, ctx);
}
};
}
}

0 comments on commit 9b11d3f

Please sign in to comment.