Skip to content

Commit

Permalink
Thrown exceptions not in preconditons (#37)
Browse files Browse the repository at this point in the history
* Add tests to verify thrown exceptions not in preconditions

* Exclude exceptions thrown from method declarations

* Add missing licenses
  • Loading branch information
timtebeek authored Dec 12, 2023
1 parent 98c21e0 commit 22a166c
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,71 +29,93 @@
import java.util.Set;

public class ImportDetector {
/**
* Locate types that are directly referred to by name in the
* given method declaration and therefore need an import in the template.
* <p>
* Excludes exceptions thrown by the method.
*
* @return The list of imports to add.
*/
public static List<Symbol> imports(JCTree.JCMethodDecl methodDecl) {
ImportScanner importScanner = new ImportScanner();
importScanner.scan(methodDecl.getBody());
importScanner.scan(methodDecl.getReturnType());
for (JCTree.JCVariableDecl param : methodDecl.getParameters()) {
importScanner.scan(param);
}
for (JCTree.JCTypeParameter param : methodDecl.getTypeParameters()) {
importScanner.scan(param);
}
return new ArrayList<>(importScanner.imports);
}

/**
* Locate types that are directly referred to by name in the
* given tree and therefore need an import in the template.
*
* @return The list of imports to add.
*/
public static List<Symbol> imports(JCTree input) {
Set<Symbol> imports = new LinkedHashSet<>();
public static List<Symbol> imports(JCTree tree) {
ImportScanner importScanner = new ImportScanner();
importScanner.scan(tree);
return new ArrayList<>(importScanner.imports);
}

new TreeScanner() {
@Override
public void scan(JCTree tree) {
JCTree maybeFieldAccess = tree;
if (maybeFieldAccess instanceof JCFieldAccess &&
((JCFieldAccess) maybeFieldAccess).sym instanceof Symbol.ClassSymbol &&
Character.isUpperCase(((JCFieldAccess) maybeFieldAccess).getIdentifier().toString().charAt(0))) {
while (maybeFieldAccess instanceof JCFieldAccess) {
maybeFieldAccess = ((JCFieldAccess) maybeFieldAccess).getExpression();
if (maybeFieldAccess instanceof JCIdent &&
Character.isUpperCase(((JCIdent) maybeFieldAccess).getName().toString().charAt(0))) {
// this might be a fully qualified type name, so we don't want to add an import for it
// and returning will skip the nested identifier which represents just the class simple name
return;
}
private static class ImportScanner extends TreeScanner {
final Set<Symbol> imports = new LinkedHashSet<>();

@Override
public void scan(JCTree tree) {
JCTree maybeFieldAccess = tree;
if (maybeFieldAccess instanceof JCFieldAccess &&
((JCFieldAccess) maybeFieldAccess).sym instanceof Symbol.ClassSymbol &&
Character.isUpperCase(((JCFieldAccess) maybeFieldAccess).getIdentifier().toString().charAt(0))) {
while (maybeFieldAccess instanceof JCFieldAccess) {
maybeFieldAccess = ((JCFieldAccess) maybeFieldAccess).getExpression();
if (maybeFieldAccess instanceof JCIdent &&
Character.isUpperCase(((JCIdent) maybeFieldAccess).getName().toString().charAt(0))) {
// this might be a fully qualified type name, so we don't want to add an import for it
// and returning will skip the nested identifier which represents just the class simple name
return;
}
}
}

if (tree instanceof JCTree.JCAnnotation) {
// completely skip annotations for now
return;
}

if (tree instanceof JCTree.JCAnnotation) {
// completely skip annotations for now
if (tree instanceof JCIdent) {
if (tree.type == null || !(tree.type.tsym instanceof Symbol.ClassSymbol)) {
return;
}

if (tree instanceof JCIdent) {
if (tree.type == null || !(tree.type.tsym instanceof Symbol.ClassSymbol)) {
return;
}
if (((JCIdent) tree).sym.getKind() == ElementKind.CLASS || ((JCIdent) tree).sym.getKind() == ElementKind.INTERFACE) {
imports.add(tree.type.tsym);
} else if (((JCIdent) tree).sym.getKind() == ElementKind.FIELD) {
imports.add(((JCIdent) tree).sym);
} else if (((JCIdent) tree).sym.getKind() == ElementKind.METHOD) {
imports.add(((JCIdent) tree).sym);
} else if (((JCIdent) tree).sym.getKind() == ElementKind.ENUM_CONSTANT) {
imports.add(((JCIdent) tree).sym);
}
} else if (tree instanceof JCFieldAccess && ((JCFieldAccess) tree).sym instanceof Symbol.VarSymbol
&& ((JCFieldAccess) tree).selected instanceof JCIdent
&& ((JCIdent) ((JCFieldAccess) tree).selected).sym instanceof Symbol.ClassSymbol) {
imports.add(((JCIdent) ((JCFieldAccess) tree).selected).sym);
} else if (tree instanceof JCFieldAccess && ((JCFieldAccess) tree).sym instanceof Symbol.MethodSymbol
&& ((JCFieldAccess) tree).selected instanceof JCIdent
&& ((JCIdent) ((JCFieldAccess) tree).selected).sym instanceof Symbol.ClassSymbol) {
imports.add(((JCIdent) ((JCFieldAccess) tree).selected).sym);
} else if (tree instanceof JCFieldAccess && ((JCFieldAccess) tree).sym instanceof Symbol.ClassSymbol
&& ((JCFieldAccess) tree).selected instanceof JCIdent
&& ((JCIdent) ((JCFieldAccess) tree).selected).sym instanceof Symbol.ClassSymbol
&& !(((JCIdent) ((JCFieldAccess) tree).selected).sym.type instanceof Type.ErrorType)) {
imports.add(((JCIdent) ((JCFieldAccess) tree).selected).sym);
if (((JCIdent) tree).sym.getKind() == ElementKind.CLASS || ((JCIdent) tree).sym.getKind() == ElementKind.INTERFACE) {
imports.add(tree.type.tsym);
} else if (((JCIdent) tree).sym.getKind() == ElementKind.FIELD) {
imports.add(((JCIdent) tree).sym);
} else if (((JCIdent) tree).sym.getKind() == ElementKind.METHOD) {
imports.add(((JCIdent) tree).sym);
} else if (((JCIdent) tree).sym.getKind() == ElementKind.ENUM_CONSTANT) {
imports.add(((JCIdent) tree).sym);
}

super.scan(tree);
} else if (tree instanceof JCFieldAccess && ((JCFieldAccess) tree).sym instanceof Symbol.VarSymbol
&& ((JCFieldAccess) tree).selected instanceof JCIdent
&& ((JCIdent) ((JCFieldAccess) tree).selected).sym instanceof Symbol.ClassSymbol) {
imports.add(((JCIdent) ((JCFieldAccess) tree).selected).sym);
} else if (tree instanceof JCFieldAccess && ((JCFieldAccess) tree).sym instanceof Symbol.MethodSymbol
&& ((JCFieldAccess) tree).selected instanceof JCIdent
&& ((JCIdent) ((JCFieldAccess) tree).selected).sym instanceof Symbol.ClassSymbol) {
imports.add(((JCIdent) ((JCFieldAccess) tree).selected).sym);
} else if (tree instanceof JCFieldAccess && ((JCFieldAccess) tree).sym instanceof Symbol.ClassSymbol
&& ((JCFieldAccess) tree).selected instanceof JCIdent
&& ((JCIdent) ((JCFieldAccess) tree).selected).sym instanceof Symbol.ClassSymbol
&& !(((JCIdent) ((JCFieldAccess) tree).selected).sym.type instanceof Type.ErrorType)) {
imports.add(((JCIdent) ((JCFieldAccess) tree).selected).sym);
}
}.scan(input);

return new ArrayList<>(imports);
super.scan(tree);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
import static com.google.testing.compile.Compiler.javac;

class RefasterTemplateProcessorTest {

@ParameterizedTest
@ValueSource(strings = {
"UseStringIsEmpty",
"MethodThrows",
"NestedPreconditions",
"ParameterReuse",
"UseStringIsEmpty",
"SimplifyBooleans",
})
void generateRecipe(String recipeName) {
Expand Down
36 changes: 36 additions & 0 deletions src/test/resources/refaster/MethodThrows.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 foo;

import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class MethodThrows {
@BeforeTemplate
void before(Path path) throws IOException {
Files.readAllLines(path, StandardCharsets.UTF_8);
}

@AfterTemplate
void after(Path path) throws Exception {
Files.readAllLines(path);
}
}
85 changes: 85 additions & 0 deletions src/test/resources/refaster/MethodThrowsRecipe.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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 foo;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.lang.NonNullApi;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.search.*;
import org.openrewrite.java.template.Primitive;
import org.openrewrite.java.template.Semantics;
import org.openrewrite.java.template.function.*;
import org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor;
import org.openrewrite.java.tree.*;

import java.util.*;

import static org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor.EmbeddingOption.*;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.StandardCharsets;

@NonNullApi
public class MethodThrowsRecipe extends Recipe {

@Override
public String getDisplayName() {
return "Refaster template `MethodThrows`";
}

@Override
public String getDescription() {
return "Recipe created for the following Refaster template:\n```java\npublic class MethodThrows {\n \n @BeforeTemplate()\n void before(Path path) throws IOException {\n Files.readAllLines(path, StandardCharsets.UTF_8);\n }\n \n @AfterTemplate()\n void after(Path path) throws Exception {\n Files.readAllLines(path);\n }\n}\n```\n.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
JavaVisitor<ExecutionContext> javaVisitor = new AbstractRefasterJavaVisitor() {
final JavaTemplate before = Semantics.statement(this, "before", (java.nio.file.Path path) -> java.nio.file.Files.readAllLines(path, java.nio.charset.StandardCharsets.UTF_8)).build();
final JavaTemplate after = Semantics.statement(this, "after", (java.nio.file.Path path) -> java.nio.file.Files.readAllLines(path)).build();

@Override
public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) {
JavaTemplate.Matcher matcher;
if ((matcher = before.matcher(getCursor())).find()) {
maybeRemoveImport("java.nio.charset.StandardCharsets");
return embed(
after.apply(getCursor(), elem.getCoordinates().replace(), matcher.parameter(0)),
getCursor(),
ctx,
SHORTEN_NAMES
);
}
return super.visitMethodInvocation(elem, ctx);
}

};
return Preconditions.check(
Preconditions.and(
new UsesType<>("java.nio.charset.StandardCharsets", true),
new UsesType<>("java.nio.file.Files", true),
new UsesType<>("java.nio.file.Path", true),
new UsesMethod<>("java.nio.file.Files readAllLines(..)")
),
javaVisitor
);
}
}
2 changes: 0 additions & 2 deletions src/test/resources/refaster/MultipleDereferencesRecipes.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import static org.openrewrite.java.template.internal.AbstractRefasterJavaVisitor.EmbeddingOption.*;

import java.nio.file.Files;
import java.io.IOException;
import java.nio.file.Path;

public class MultipleDereferencesRecipes extends Recipe {
Expand Down Expand Up @@ -93,7 +92,6 @@ public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) {
};
return Preconditions.check(
Preconditions.and(
new UsesType<>("java.io.IOException", true),
new UsesType<>("java.nio.file.Files", true),
new UsesType<>("java.nio.file.Path", true),
new UsesMethod<>("java.nio.file.Files delete(..)")
Expand Down

0 comments on commit 22a166c

Please sign in to comment.