diff --git a/pom.xml b/pom.xml index a5e9300..aabbb0f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ de.jsilbereisen perfumator-java - 0.3.1 + 0.4.0 jar diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java b/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java index e48575c..8e6eba1 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java @@ -6,10 +6,12 @@ import com.github.javaparser.ParserConfiguration; import com.github.javaparser.Problem; import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.symbolsolver.JavaSymbolSolver; import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import com.github.javaparser.symbolsolver.utils.SymbolSolverCollectionStrategy; import de.jsilbereisen.perfumator.model.AnalysisResult; import de.jsilbereisen.perfumator.model.StatisticsSummary; @@ -118,7 +120,8 @@ public static Builder builder(@NotNull Locale locale) { public static JavaParser getConfiguredJavaParser() { ParserConfiguration config = new ParserConfiguration(); - config.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17); + config.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); + config.setSymbolResolver(new JavaSymbolSolver(new ReflectionTypeSolver(false))); config.setDoNotAssignCommentsPrecedingEmptyLines(false); return new JavaParser(config); diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java new file mode 100644 index 0000000..02e9dfd --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java @@ -0,0 +1,72 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import lombok.EqualsAndHashCode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link Detector} for the "Assert all" {@link Perfume}. + * Detects the perfume only if the method is part of the {@link org.junit.jupiter.api.Assertions} class. + */ +@EqualsAndHashCode +public class AssertAllDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private static final String QUALIFIED_ASSERT_ALL_METHOD_NAME = "org.junit.jupiter.api.Assertions.assertAll"; + private static final String ASSERTIONS_IMPORT_NAME = "org.junit.jupiter.api.Assertions"; + private static final String ASSERT_ALL = "assertAll"; + + @Override + public @NotNull List> detect(@NotNull CompilationUnit astRoot) { + List> detectedInstances = new ArrayList<>(); + List assertAllMethodCallExpressions = getAssertAllMethodCalls(astRoot); + assertAllMethodCallExpressions + .forEach(callExpr -> detectedInstances.add(DetectedInstance.from(callExpr, perfume, astRoot))); + return detectedInstances; + } + + @Override + public void setConcreteDetectable(@NotNull Perfume concreteDetectable) { + this.perfume = concreteDetectable; + } + + @Override + public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { + this.analysisContext = analysisContext; + } + + private List getAssertAllMethodCalls(@NotNull CompilationUnit astRoot) { + return astRoot.findAll(MethodCallExpr.class, expr -> { + // contains instead of equals because of possible 'Assertions.assertAll' calls + if (!expr.getNameAsString().contains(ASSERT_ALL)) { + return false; + } + if (expr.getScope().isPresent()) { + // for non-static imports + ResolvedType resolvedType = expr.getScope().get().calculateResolvedType(); + return resolvedType instanceof ReferenceTypeImpl referenceType + && referenceType.getQualifiedName().equals(ASSERTIONS_IMPORT_NAME); + } else { + // for static imports + ResolvedMethodDeclaration resolvedMethodDeclaration = expr.resolve(); + String qualifiedName = resolvedMethodDeclaration.getQualifiedName(); + return qualifiedName.equals(QUALIFIED_ASSERT_ALL_METHOD_NAME); + } + }); + } +} diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/JFrameDisposeDetector.java b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/JFrameDisposeDetector.java new file mode 100644 index 0000000..f4564b6 --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/JFrameDisposeDetector.java @@ -0,0 +1,63 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link Detector} for the "JFrame dispose" {@link Perfume}. + * Detects the perfume only if the method is part of the {@link javax.swing.JFrame} class. + */ +public class JFrameDisposeDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private static final String DISPOSE_METHOD_NAME = "dispose"; + private static final String QUALIFIED_JFRAME_CLASS_NAME = "javax.swing.JFrame"; + + @Override + public @NotNull List> detect(@NotNull CompilationUnit astRoot) { + List> detectedInstances = new ArrayList<>(); + List disposeMethodCallExpressions = getJFrameDisposeMethodCalls(astRoot); + disposeMethodCallExpressions + .forEach(expr -> detectedInstances.add(DetectedInstance.from(expr, perfume, astRoot))); + return detectedInstances; + } + + @Override + public void setConcreteDetectable(@NotNull Perfume concreteDetectable) { + this.perfume = concreteDetectable; + } + + @Override + public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { + this.analysisContext = analysisContext; + } + + private List getJFrameDisposeMethodCalls(@NotNull CompilationUnit astRoot) { + return astRoot.findAll(MethodCallExpr.class, expr -> { + if (!expr.getNameAsString().equals(DISPOSE_METHOD_NAME)) { + return false; + } + var scope = expr.getScope(); + if (scope.isPresent()) { + var resolvedType = scope.get().calculateResolvedType(); + if (resolvedType instanceof ReferenceTypeImpl referenceType) { + return referenceType.getQualifiedName().equals(QUALIFIED_JFRAME_CLASS_NAME); + } + } + return false; + }); + } +} diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ParameterizedTestDetector.java b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ParameterizedTestDetector.java new file mode 100644 index 0000000..11d7c7b --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ParameterizedTestDetector.java @@ -0,0 +1,56 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import lombok.EqualsAndHashCode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link Detector} for the "Parameterized Test" {@link Perfume}. + * Detects the perfume only if the annotation is part of JUnit 5 ({@link org.junit.jupiter.params.ParameterizedTest}). + */ +@EqualsAndHashCode +public class ParameterizedTestDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private static final String PARAMETERIZED_TEST_IDENTIFIER = "org.junit.jupiter.params.ParameterizedTest"; + + @Override + public @NotNull List> detect(@NotNull CompilationUnit astRoot) { + List> detectedInstances = new ArrayList<>(); + List parameterizedTestMethodDeclarations = getParameterizedTestMethodDeclarations(astRoot); + parameterizedTestMethodDeclarations + .forEach(declaration -> detectedInstances.add(DetectedInstance.from(declaration, perfume, astRoot))); + return detectedInstances; + } + + @Override + public void setConcreteDetectable(@NotNull Perfume concreteDetectable) { + perfume = concreteDetectable; + } + + @Override + public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { + this.analysisContext = analysisContext; + } + + private List getParameterizedTestMethodDeclarations(@NotNull CompilationUnit astRoot) { + return astRoot.findAll(MethodDeclaration.class, methodDeclaration -> methodDeclaration.getAnnotations().stream() + .map(AnnotationExpr::resolve) + .map(ResolvedTypeDeclaration::getQualifiedName) + .anyMatch(name -> name.equals(PARAMETERIZED_TEST_IDENTIFIER))); + } +} diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SetupAndTeardownMethodDetector.java b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SetupAndTeardownMethodDetector.java new file mode 100644 index 0000000..42350cd --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SetupAndTeardownMethodDetector.java @@ -0,0 +1,61 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.resolution.declarations.ResolvedTypeDeclaration; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * {@link Detector} for the "Setup or teardown method" {@link Perfume}. + * Detects the perfume only if the annotation is part of the JUnit 5 package {@link org.junit.jupiter.api}. + */ +public class SetupAndTeardownMethodDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + public static final String IMPORT_QUALIFIER = "org.junit.jupiter.api."; + public static final Set TEST_ANNOTATIONS = Set.of("BeforeAll", "BeforeEach", "AfterAll", "AfterEach"); + + @Override + public @NotNull List> detect(@NotNull CompilationUnit astRoot) { + List> detectedInstances = new ArrayList<>(); + List setupAndTeardownMethods = getSetupAndTeardownMethodDeclarations(astRoot); + setupAndTeardownMethods + .forEach(declaration -> detectedInstances.add(DetectedInstance.from(declaration, perfume, astRoot))); + return detectedInstances; + } + + @Override + public void setConcreteDetectable(@NotNull Perfume concreteDetectable) { + this.perfume = concreteDetectable; + } + + @Override + public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { + this.analysisContext = analysisContext; + } + + private Set getQualifiedAnnotations() { + return TEST_ANNOTATIONS.stream().map(annotation -> IMPORT_QUALIFIER + annotation).collect(Collectors.toSet()); + } + + private List getSetupAndTeardownMethodDeclarations(@NotNull CompilationUnit astRoot) { + return astRoot.findAll(MethodDeclaration.class, methodDeclaration -> methodDeclaration.getAnnotations().stream() + .map(AnnotationExpr::resolve) + .map(ResolvedTypeDeclaration::getQualifiedName) + .anyMatch(name -> getQualifiedAnnotations().contains(name))); + } +} diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SwingTimerDetector.java b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SwingTimerDetector.java new file mode 100644 index 0000000..1ec6e9d --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SwingTimerDetector.java @@ -0,0 +1,63 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link Detector} for the "Swing timer" {@link Perfume}. + * Detects the perfume only if an object of type {@link javax.swing.Timer} is created. + */ +public class SwingTimerDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private static final String QUALIFIED_TIMER_NAME = "javax.swing.Timer"; + + @Override + public @NotNull List> detect(@NotNull CompilationUnit astRoot) { + List> detectedInstances = new ArrayList<>(); + List newTimerExpressions = getNewTimerExpressions(astRoot); + newTimerExpressions + .forEach(callExpr -> detectedInstances.add(DetectedInstance.from(callExpr, perfume, astRoot))); + return detectedInstances; + } + + @Override + public void setConcreteDetectable(@NotNull Perfume concreteDetectable) { + this.perfume = concreteDetectable; + } + + @Override + public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { + this.analysisContext = analysisContext; + } + + private List getNewTimerExpressions(@NotNull CompilationUnit astRoot) { + return astRoot.findAll(ObjectCreationExpr.class, expr -> { + ResolvedType resolvedType; + // when classes from the same package are instantiated, the javaparser cannot resolve the type, therefore + // we simply skip such occurrences of object creation expressions + try { + resolvedType = expr.calculateResolvedType(); + } catch (UnsolvedSymbolException e) { + return false; + } + return resolvedType instanceof ReferenceTypeImpl referenceType + && referenceType.getQualifiedName().equals(QUALIFIED_TIMER_NAME); + }); + } +} diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java new file mode 100644 index 0000000..d4758ed --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java @@ -0,0 +1,73 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; +import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import lombok.EqualsAndHashCode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * {@link Detector} for the "Thread safe Swing" {@link Perfume}. + * Detects method calls to the {@link javax.swing.SwingUtilities#invokeAndWait(Runnable)} and + * {@link javax.swing.SwingUtilities#invokeLater(Runnable)} methods. + */ +@EqualsAndHashCode +public class ThreadSafeSwingDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private final static String INVOKE_LATER = "invokeLater"; + private final static String INVOKE_AND_WAIT = "invokeAndWait"; + private final static Set QUALIFIED_METHOD_NAMES + = Set.of("javax.swing.SwingUtilities.invokeLater", "javax.swing.SwingUtilities.invokeAndWait"); + private final static String IMPORT = "javax.swing.SwingUtilities"; + + @Override + public @NotNull List> detect(@NotNull CompilationUnit astRoot) { + List> detectedInstances = new ArrayList<>(); + List methodCalls = getInvokeLaterInvokeAndWaitMethodCalls(astRoot); + methodCalls.forEach(callExpr -> detectedInstances.add(DetectedInstance.from(callExpr, perfume, astRoot))); + return detectedInstances; + } + + @Override + public void setConcreteDetectable(@NotNull Perfume concreteDetectable) { + this.perfume = concreteDetectable; + } + + @Override + public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { + this.analysisContext = analysisContext; + } + + private List getInvokeLaterInvokeAndWaitMethodCalls(@NotNull CompilationUnit astRoot) { + return astRoot.findAll(MethodCallExpr.class, expr -> { + // contains instead of equals because of possible 'SwingUtilities.invokeLater' and '-.invokeAndWait' calls + if (!expr.getNameAsString().contains(INVOKE_LATER) && !expr.getNameAsString().contains(INVOKE_AND_WAIT)) { + return false; + } + if (expr.getScope().isPresent()) { + // for non-static imports + ResolvedType resolvedType = expr.getScope().get().calculateResolvedType(); + return resolvedType instanceof ReferenceTypeImpl referenceType + && referenceType.getQualifiedName().equals(IMPORT); + } else { + // for static imports + ResolvedMethodDeclaration resolvedMethodDeclaration = expr.resolve(); + String qualifiedName = resolvedMethodDeclaration.getQualifiedName(); + return QUALIFIED_METHOD_NAMES.contains(qualifiedName); + } + }); + } +} diff --git a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json new file mode 100644 index 0000000..ed3c54f --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json @@ -0,0 +1,9 @@ +{ + "name": "Assert all", + "description": "The \"org.junit.jupiter.api.assertAll\" is used to group multiple individual assertions together. The advantage of using this method to group the assertions instead of calling them individually is that if one assertion fails, the others are still executed. Therefore, the user gains insight on the results of all tests, instead of only the tests up to the failed assertion, where the thrown exception would otherwise terminate the test execution.", + "detectorClassSimpleName": "AssertAllDetector", + "i18nBaseBundleName": "assertAll", + "sources": ["https://www.baeldung.com/junit5-assertall-vs-multiple-assertions"], + "relatedPattern": null, + "additionalInformation": null +} \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json new file mode 100644 index 0000000..3e5800c --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json @@ -0,0 +1,9 @@ +{ + "name": "JFrame dispose", + "description": "Instead of calling \"System.exit\", which completely terminates the java virtual machine, the \"javax.swing.JFrame.dispose\" method closes the frame and its assigned resources in a more controlled manner. Closing the application this way, one has to make sure to terminate all running threads programmatically. While this may seem like a downside, it ultimately provides the most control, as ongoing processes can be completed before the program is terminated.", + "detectorClassSimpleName": "JFrameDisposeDetector", + "i18nBaseBundleName": "jFrameDispose", + "sources": ["Programmierung II - Uni Passau", "https://docs.oracle.com/javase/8/docs/api/java/awt/Window.html#dispose--"], + "relatedPattern": "BUG", + "additionalInformation": null +} \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json new file mode 100644 index 0000000..c9a813b --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json @@ -0,0 +1,9 @@ +{ + "name": "Parameterized test", + "description": "Tests can be annotated with the \"org.junit.jupiter.params.ParameterizedTest\" annotation. Such parameterized tests make the tested code reusable in cases where the logic is similar, and is preferable over writing multiple tests that essentially test the same thing with different values.", + "detectorClassSimpleName": "ParameterizedTestDetector", + "i18nBaseBundleName": "parameterizedTest", + "sources": ["https://rules.sonarsource.com/java/RSPEC-5976/"], + "relatedPattern": "SMELL", + "additionalInformation": null +} \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Setup_and_teardown_methods.json b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Setup_and_teardown_methods.json new file mode 100644 index 0000000..5b0ffda --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Setup_and_teardown_methods.json @@ -0,0 +1,9 @@ +{ + "name": "Setup or teardown method", + "description": "Common setup and teardown tasks should preferably be performed inside methods annotated with either \"@BeforeEach\", \"@BeforeAll\", \"@AfterEach\" or \"@AfterAll\". This can greatly reduce the boilerplate code inside individual tests and makes the tests more focused on the actual logic that is being tested.", + "detectorClassSimpleName": "SetupAndTeardownMethodDetector", + "i18nBaseBundleName": "setupAndTeardownMethods", + "sources": ["https://www.baeldung.com/junit-before-beforeclass-beforeeach-beforeall"], + "relatedPattern": "SMELL", + "additionalInformation": null +} \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Swing_timer.json b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Swing_timer.json new file mode 100644 index 0000000..d722469 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Swing_timer.json @@ -0,0 +1,9 @@ +{ + "name": "Swing timer", + "description": "The timer from the \"javax.swing\" library simplifies GUI updates significantly. Its constructor requires a time intervall (in milliseconds) and an implementation of the \"java.awt.event.ActionListener\" interface. The code provided in the implementation (for example, GUI updates) is executed in the time interval that is defined by the first parameter of the timer. The timer always executes the code in the event dispatch thread.", + "detectorClassSimpleName": "SwingTimerDetector", + "i18nBaseBundleName": "swingTimer", + "sources": ["Programmierung II - Uni Passau", "https://docs.oracle.com/javase/8/docs/api/javax/swing/Timer.html"], + "relatedPattern": null, + "additionalInformation": null +} \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Thread_safe_swing.json b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Thread_safe_swing.json new file mode 100644 index 0000000..2aa7e88 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Thread_safe_swing.json @@ -0,0 +1,9 @@ +{ + "name": "Thread safe Swing", + "description": "Java Swing applications make use of the event dispatch thread. This thread must handle the initialization of and update operations on the GUI components, as well as event handling. We as programmers are responsible that GUI components are created and updated in the event dispatch thread. We can achieve this by placing the related code inside a Runnable that we pass as an argument to either the \"SwingUtilities.invokeLater\" or the \"SwingUtilities.invokeAndWait\" method.", + "detectorClassSimpleName": "ThreadSafeSwingDetector", + "i18nBaseBundleName": "threadSafeSwing", + "sources": ["Programmierung II - Uni Passau", "https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html"], + "relatedPattern": "BUG", + "additionalInformation": null +} \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties new file mode 100644 index 0000000..e8ed016 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties @@ -0,0 +1,3 @@ +name=Setup und Teardown Methoden +description=Gemeinsamer Setup- und Teardown-Code sollte vorzugsweise innerhalb von Methoden durchgefuehrt werden, die entweder mit "@BeforeEach", "@BeforeAll", "@AfterEach" oder "@AfterAll" annotiert sind. Auf diese Weise laesst sich Boilerplate-Code in den einzelnen Tests erheblich reduzieren und der Fokus liegt staerker auf der eigentlichen Logik, die getestet wird. +source#1=https://www.baeldung.com/junit-before-beforeclass-beforeeach-beforeall \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties new file mode 100644 index 0000000..738a22c --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties @@ -0,0 +1,3 @@ +name=Assert all +description=Die Methode \"org.junit.jupiter.api.assertAll\" wird verwendet, um mehrere einzelne Assertions zusammenzufassen. Der Vorteil dieser Methode im Vergleich zu mehreren einzelnen Assertions besteht darin, dass bei einem Fehlschlag einer Assertion die folgenden Assertions trotzdem ausgefuehrt werden. Daher erhaelt der Benutzer die Ergebnisse aller Tests und nicht nur der Tests bis zur fehlgeschlagenen Assertions, die ansonsten die Ausfuehrung des Tests beenden wuerde. +source#1=https://www.baeldung.com/junit5-assertall-vs-multiple-assertions \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/copyConstructor_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/copyConstructor_de.properties index 19837f4..fabd7f3 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/copyConstructor_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/copyConstructor_de.properties @@ -1,4 +1,4 @@ name=Kopier-Konstruktor -description=Es gibt verschiedene Ansaetze, um Objekte in Java zu kopieren. Die Java Standardbibliothek bietet etwa die "Cloneable" Schnittstelle (Interface) an, welches Klassen implementieren können, um Kopien von sich zu erzeugen. Wie jedoch Joshua Bloch in seinem Buch "Effective Java" (Kapitel 3.4) erklaert, ist beim implementieren dieses Interfaces Vorsicht geboten, da sich im Bezug auf Vererbung mehrere Gefahren verbergen. Er schlaegt daher als saubereren Weg, um Kopien von Objekten zu erzeugen, einen Kopier-Konstruktor vor: ein Konstruktor, der ein Argument desselben Typs nimmt, und welcher dann die Felder der Klasse kopiert ("deep"-copy, also alle veraenderbaren Referenztypen "tief" zu kopieren). Dieser Weg sichert den korrekten Laufzeittyp der Kopie und ist nicht so fehleranfaellig wie "Cloneable" zu implementieren. Obwohl es keine definierte Regel dafuer gibt, markiert das IntelliJ IDEA Plugin "SonarLint" von SonarSource das Implementieren von "Cloneable" ebenso aus diesem Grund als Code Smell. +description=Es gibt verschiedene Ansaetze, um Objekte in Java zu kopieren. Die Java Standardbibliothek bietet etwa die "Cloneable" Schnittstelle (Interface) an, welches Klassen implementieren koennen, um Kopien von sich zu erzeugen. Wie jedoch Joshua Bloch in seinem Buch "Effective Java" (Kapitel 3.4) erklaert, ist beim implementieren dieses Interfaces Vorsicht geboten, da sich im Bezug auf Vererbung mehrere Gefahren verbergen. Er schlaegt daher als saubereren Weg, um Kopien von Objekten zu erzeugen, einen Kopier-Konstruktor vor: ein Konstruktor, der ein Argument desselben Typs nimmt, und welcher dann die Felder der Klasse kopiert ("deep"-copy, also alle veraenderbaren Referenztypen "tief" zu kopieren). Dieser Weg sichert den korrekten Laufzeittyp der Kopie und ist nicht so fehleranfaellig wie "Cloneable" zu implementieren. Obwohl es keine definierte Regel dafuer gibt, markiert das IntelliJ IDEA Plugin "SonarLint" von SonarSource das Implementieren von "Cloneable" ebenso aus diesem Grund als Code Smell. source#1=J. Bloch: Effective Java, Kap. 3.4 additionalInformation=Eine andere Option, um "Cloneable" zum Kopieren von Objecten zu vermeiden, waere etwa eine (statische) Kopier-Methode oder ein eigenes Interface, welches einen Kopier-Mechanismus vorschreibt. \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties new file mode 100644 index 0000000..fb3fa30 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties @@ -0,0 +1,4 @@ +name=JFrame dispose +description=Anstelle der \"System.exit\" Methode, die die Java Virtual Machine vollstaendig beendet, schliesst \"javax.swing.JFrame.dispose\" das JFrame Object und die ihm zugewiesenen Ressourcen auf kontrolliertere Weise. Wenn man die Anwendung auf diese Weise schliesst, muss man sicherstellen, dass alle verbleibenden Threads programmatisch beendet werden. Dies mag zwar wie ein Nachteil erscheinen, bietet aber letztlich die groesste Kontrolle, da laufende Prozesse kontrolliert beendet werden koennen. +source#1=Programmierung II - Uni Passau +source#2=https://docs.oracle.com/javase/8/docs/api/java/awt/Window.html#dispose-- \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties new file mode 100644 index 0000000..1833891 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties @@ -0,0 +1,3 @@ +name=Parametrisierte Tests +description=Tests koennen mit der Annotation \"org.junit.jupiter.params.ParameterizedTest\" versehen werden. Solche parametrisierten Tests machen den getesteten Code wiederverwendbar. Sie sind dem Schreiben mehrerer Tests, die im Wesentlichen das gleiche Logik mit unterschiedlichen Werten testen, vorzuziehen. +source#1=https://rules.sonarsource.com/java/RSPEC-5976/ \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer_de.properties new file mode 100644 index 0000000..b5ccde0 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer_de.properties @@ -0,0 +1,4 @@ +name=Swing Timer +description=Der Timer aus der \"javax.swing\" Bibliothek vereinfacht die Aktualisierung von Benutzeroberflaechen erheblich. Der Konstruktor verlangt ein Zeitintervall (in Millisekunden) und eine Implementierung des \"java.awt.event.ActionListener\"-Interfaces. Der Code des ActionListeners (z.B. GUI-Aktualisierungen) wird in dem Zeitintervall ausgefuehrt, das durch den ersten Parameter des Timers definiert ist. Der Timer fuehrt den Code immer im Event-Dispatch-Thread aus. +source#1=Programmierung II - Uni Passau +source#2=https://docs.oracle.com/javase/8/docs/api/javax/swing/Timer.html \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties new file mode 100644 index 0000000..d5f36c9 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties @@ -0,0 +1,4 @@ +name=Thread-safe Swing +description=Java Swing-Anwendungen verwenden den Event Dispatch Thread. Dieser Thread muss die Initialisierung und die Aktualisierungsoperationen der GUI-Komponenten sowie Event-Handling uebernehmen. Der Programmierer ist dafuer verantwortlich, dass die GUI-Komponenten im Event Dispatch Thread erstellt und aktualisiert werden. Das kann dadurch erreicht werden, indem der entsprechende Code in einem \"Runnable\" platziert wird, das wir als Argument entweder an die Methode \"SwingUtilities.invokeLater\" oder \"SwingUtilities.invokeAndWait\" uebergeben. +source#1=Programmierung II - Uni Passau +source#2=https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html \ No newline at end of file diff --git a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/useTryWithResources_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/useTryWithResources_de.properties index 7bf39eb..24c3f2f 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/useTryWithResources_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/useTryWithResources_de.properties @@ -1,8 +1,2 @@ name=Ressourcen-Management in Try-catch -description=Um beim Benutzen eines try-catch-statements sicherzustellen, dass benutzte Ressourcen zuverlaessig \ - geschlossen werden,\nbietet Java das \"try-with-resources\" Konstrukt. Dieses zu benutzen ist Best-Practice,\num \ - Ressourcen-Leaks zu vermeiden. Anfaenger begegnen diesem Konstrukt etwa bei ersten Uebungen zum Lesen oder schreiben \ - von Dateien\nund beim Nutzen von I/O Streams. Sie sollten fuer das Nutzen dieses Idioms belohnt werden, da es den \ - geschriebenen Code simplifiziert\nund die Alternative, also nicht-benutzen von Try-with-resources, sie in der Gefahr \ - laesst, dass Ressources nicht geschlossen werden.\nDieses Perfume ist ein Loesungsmuster, inspiriert durch den \ - \"Try-with-resources should be used\" Code Smell von SonarSource. \ No newline at end of file +description=Um beim Benutzen eines try-catch-statements sicherzustellen, dass benutzte Ressourcen zuverlaessig geschlossen werden, bietet Java das \"try-with-resources\" Konstrukt. Dieses zu benutzen ist Best-Practice, um Ressourcen-Leaks zu vermeiden. Anfaenger begegnen diesem Konstrukt etwa bei ersten Uebungen zum Lesen oder schreiben von Dateien\nund beim Nutzen von I/O Streams. Sie sollten fuer das Nutzen dieses Idioms belohnt werden, da es den geschriebenen Code simplifiziert und die Alternative, also nicht-benutzen von Try-with-resources, sie in der Gefahr laesst, dass Ressources nicht geschlossen werden. Dieses Perfume ist ein Loesungsmuster, inspiriert durch den \"Try-with-resources should be used\" Code Smell von SonarSource. \ No newline at end of file diff --git a/src/test/java/detectors/AssertAllDetectorTest.java b/src/test/java/detectors/AssertAllDetectorTest.java new file mode 100644 index 0000000..9342afc --- /dev/null +++ b/src/test/java/detectors/AssertAllDetectorTest.java @@ -0,0 +1,86 @@ +package detectors; + +import com.github.javaparser.ast.CompilationUnit; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.detector.perfume.AssertAllDetector; +import de.jsilbereisen.perfumator.model.CodeRange; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import test.AbstractDetectorTest; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class AssertAllDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILES_DIR = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("assert_all"); + + private static Perfume perfume; + + private static Detector detector; + + private static CompilationUnit ast; + + @BeforeAll + static void init() { + perfume = new Perfume(); + perfume.setName("Assert All"); + + detector = new AssertAllDetector(); + detector.setConcreteDetectable(perfume); + } + + @Test + void detectStaticImport() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("AssertAllStaticImport.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(1); + + DetectedInstance detection = detections.get(0); + + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("AssertAllStaticImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(15, 9, 20, 9)); + } + + @Test + void detectWithoutStaticImport() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("AssertAllNoStaticImport.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(1); + + DetectedInstance detection = detections.get(0); + + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("AssertAllNoStaticImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(14, 9, 19, 9)); + } + + @Test + void detectWithWildcardImport() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("AssertionsStaticWildcardImport.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(1); + + DetectedInstance detection = detections.get(0); + + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("AssertionsStaticWildcardImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(15, 9, 20, 9)); + } + + @Test + void detectNoPerfumeForDifferentAssertAllMethod() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("AssertAllNoPerfume.java")); + List> detections = detector.detect(ast); + + assertThat(detections).isEmpty(); + } +} diff --git a/src/test/java/detectors/JFrameDisposeDetectorTest.java b/src/test/java/detectors/JFrameDisposeDetectorTest.java new file mode 100644 index 0000000..f6919a0 --- /dev/null +++ b/src/test/java/detectors/JFrameDisposeDetectorTest.java @@ -0,0 +1,61 @@ +package detectors; + +import com.github.javaparser.ast.CompilationUnit; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.detector.perfume.JFrameDisposeDetector; +import de.jsilbereisen.perfumator.model.CodeRange; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import test.AbstractDetectorTest; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JFrameDisposeDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILES_DIR = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("swing"); + + private static Perfume perfume; + + private static Detector detector; + + private static CompilationUnit ast; + + @BeforeAll + static void init() { + perfume = new Perfume(); + perfume.setName("Assert All"); + + detector = new JFrameDisposeDetector(); + detector.setConcreteDetectable(perfume); + + ast = parseAstForFile(TEST_FILES_DIR.resolve("JFrameDispose.java")); + } + + @Test + void detect() { + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(3); + + DetectedInstance detection = detections.get(0); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("JFrameDispose"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(18, 9, 18, 24)); + + detection = detections.get(1); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("JFrameDispose"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(19, 9, 19, 24)); + + detection = detections.get(2); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("JFrameDispose"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(20, 9, 20, 24)); + } +} diff --git a/src/test/java/detectors/ParameterizedTestDetectorTest.java b/src/test/java/detectors/ParameterizedTestDetectorTest.java new file mode 100644 index 0000000..97dc12e --- /dev/null +++ b/src/test/java/detectors/ParameterizedTestDetectorTest.java @@ -0,0 +1,63 @@ +package detectors; + +import com.github.javaparser.ast.CompilationUnit; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.detector.perfume.ParameterizedTestDetector; +import de.jsilbereisen.perfumator.model.CodeRange; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import test.AbstractDetectorTest; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ParameterizedTestDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILES_DIR = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("parameterized_tests"); + + private static Perfume perfume; + + private static Detector detector; + + private static CompilationUnit ast; + + @BeforeAll + static void init() { + perfume = new Perfume(); + perfume.setName("Parameterized Test"); + + detector = new ParameterizedTestDetector(); + detector.setConcreteDetectable(perfume); + } + + @Test + void detect() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("ParameterizedTests.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(1); + + DetectedInstance detection = detections.get(0); + + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("ParameterizedTests"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(22, 5, 26, 5)); + } + + @Test + void detectNoPerfumeForOnwAnnotation() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("ParameterizedTestsOwnAnnotation.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(1); + + DetectedInstance detection = detections.get(0); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("ParameterizedTestsOwnAnnotation"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(17, 5, 21, 5)); + } +} diff --git a/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java b/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java new file mode 100644 index 0000000..5974370 --- /dev/null +++ b/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java @@ -0,0 +1,92 @@ +package detectors; + +import com.github.javaparser.ast.CompilationUnit; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.detector.perfume.SetupAndTeardownMethodDetector; +import de.jsilbereisen.perfumator.model.CodeRange; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import test.AbstractDetectorTest; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SetupAndTeardownMethodDetectorTest extends AbstractDetectorTest { + private static final Path TEST_FILES_DIR = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("setup_and_teardown_methods"); + + private static Perfume perfume; + + private static Detector detector; + + private static CompilationUnit ast; + + @BeforeAll + static void init() { + perfume = new Perfume(); + perfume.setName("Setup or teardown method"); + + detector = new SetupAndTeardownMethodDetector(); + detector.setConcreteDetectable(perfume); + } + + @Test + void detect() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("SetupAndTeardownMethods.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(4); + + DetectedInstance beforeAllDetection = detections.get(0); + assertThat(beforeAllDetection.getDetectable()).isEqualTo(perfume); + assertThat(beforeAllDetection.getTypeName()).isEqualTo("SetupAndTeardownMethods"); + assertThat(beforeAllDetection.getCodeRanges()).containsExactly(CodeRange.of(12, 5, 15, 5)); + + DetectedInstance beforeEachDetection = detections.get(1); + assertThat(beforeEachDetection.getDetectable()).isEqualTo(perfume); + assertThat(beforeEachDetection.getTypeName()).isEqualTo("SetupAndTeardownMethods"); + assertThat(beforeEachDetection.getCodeRanges()).containsExactly(CodeRange.of(17, 5, 20, 5)); + + DetectedInstance afterEachDetection = detections.get(2); + assertThat(afterEachDetection.getDetectable()).isEqualTo(perfume); + assertThat(afterEachDetection.getTypeName()).isEqualTo("SetupAndTeardownMethods"); + assertThat(afterEachDetection.getCodeRanges()).containsExactly(CodeRange.of(27, 5, 30, 5)); + + DetectedInstance afterAllDetection = detections.get(3); + assertThat(afterAllDetection.getDetectable()).isEqualTo(perfume); + assertThat(afterAllDetection.getTypeName()).isEqualTo("SetupAndTeardownMethods"); + assertThat(afterAllDetection.getCodeRanges()).containsExactly(CodeRange.of(32, 5, 35, 5)); + } + + @Test + void detectWithOwnAnnotations() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("SetupAndTeardownMethodsOwnAnnotations.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(4); + + DetectedInstance beforeAllDetection = detections.get(0); + assertThat(beforeAllDetection.getDetectable()).isEqualTo(perfume); + assertThat(beforeAllDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodsOwnAnnotations"); + assertThat(beforeAllDetection.getCodeRanges()).containsExactly(CodeRange.of(7, 5, 10, 5)); + + DetectedInstance beforeEachDetection = detections.get(1); + assertThat(beforeEachDetection.getDetectable()).isEqualTo(perfume); + assertThat(beforeEachDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodsOwnAnnotations"); + assertThat(beforeEachDetection.getCodeRanges()).containsExactly(CodeRange.of(12, 5, 15, 5)); + + DetectedInstance afterEachDetection = detections.get(2); + assertThat(afterEachDetection.getDetectable()).isEqualTo(perfume); + assertThat(afterEachDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodsOwnAnnotations"); + assertThat(afterEachDetection.getCodeRanges()).containsExactly(CodeRange.of(22, 5, 25, 5)); + + DetectedInstance afterAllDetection = detections.get(3); + assertThat(afterAllDetection.getDetectable()).isEqualTo(perfume); + assertThat(afterAllDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodsOwnAnnotations"); + assertThat(afterAllDetection.getCodeRanges()).containsExactly(CodeRange.of(27, 5, 30, 5)); + } +} diff --git a/src/test/java/detectors/SwingTimerDetectorTest.java b/src/test/java/detectors/SwingTimerDetectorTest.java new file mode 100644 index 0000000..a4edfc1 --- /dev/null +++ b/src/test/java/detectors/SwingTimerDetectorTest.java @@ -0,0 +1,65 @@ +package detectors; + +import com.github.javaparser.ast.CompilationUnit; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.detector.perfume.SwingTimerDetector; +import de.jsilbereisen.perfumator.model.CodeRange; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import test.AbstractDetectorTest; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class SwingTimerDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILES_DIR = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("swing"); + + private static Perfume perfume; + + private static Detector detector; + + private static CompilationUnit ast; + + @BeforeAll + static void init() { + perfume = new Perfume(); + perfume.setName("Swing Timer"); + + detector = new SwingTimerDetector(); + detector.setConcreteDetectable(perfume); + } + + @Test + void detect() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("SwingTimer.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(4); + + DetectedInstance detection = detections.get(0); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("SwingTimer"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(9, 15, 9, 54)); + + detection = detections.get(1); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("SwingTimer"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(14, 18, 19, 10)); + + detection = detections.get(2); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("SwingTimer"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(20, 22, 20, 60)); + + detection = detections.get(3); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("SwingTimer"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(21, 36, 21, 71)); + } +} diff --git a/src/test/java/detectors/ThreadSafeSwingDetectorTest.java b/src/test/java/detectors/ThreadSafeSwingDetectorTest.java new file mode 100644 index 0000000..0c78ae3 --- /dev/null +++ b/src/test/java/detectors/ThreadSafeSwingDetectorTest.java @@ -0,0 +1,100 @@ +package detectors; + +import com.github.javaparser.ast.CompilationUnit; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.detector.perfume.ThreadSafeSwingDetector; +import de.jsilbereisen.perfumator.model.CodeRange; +import de.jsilbereisen.perfumator.model.DetectedInstance; +import de.jsilbereisen.perfumator.model.perfume.Perfume; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import test.AbstractDetectorTest; + +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ThreadSafeSwingDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILES_DIR = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("swing").resolve("invoke_later_invoke_and_wait"); + + private static Perfume perfume; + + private static Detector detector; + + private static CompilationUnit ast; + + @BeforeAll + static void init() { + perfume = new Perfume(); + perfume.setName("Thread safe Swing"); + + detector = new ThreadSafeSwingDetector(); + detector.setConcreteDetectable(perfume); + } + + @Test + void detectStaticImport() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("InvokeLaterInvokeAndWaitStaticImport.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(2); + + DetectedInstance detection = detections.get(0); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWaitStaticImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(9, 9, 11, 10)); + + detection = detections.get(1); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWaitStaticImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(13, 9, 15, 10)); + } + + @Test + void detectWithoutStaticImport() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("InvokeLaterInvokeAndWaitNoStaticImport.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(2); + + DetectedInstance detection = detections.get(0); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWaitNoStaticImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(8, 9, 10, 10)); + + detection = detections.get(1); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWaitNoStaticImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(12, 9, 14, 10)); + } + + @Test + void detectWithWildcardImport() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("InvokeLaterInvokeAndWaitStaticWildcardImport.java")); + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(2); + + DetectedInstance detection = detections.get(0); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWaitStaticWildcardImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(8, 9, 10, 10)); + + detection = detections.get(1); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWaitStaticWildcardImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(12, 9, 14, 10)); + } + + @Test + void detectNoPerfume() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("InvokeLaterInvokeAndWaitNoPerfume.java")); + List> detections = detector.detect(ast); + + assertThat(detections).isEmpty(); + } + +} diff --git a/src/test/resources/detectors/assert_all/AssertAllNoPerfume.java b/src/test/resources/detectors/assert_all/AssertAllNoPerfume.java new file mode 100644 index 0000000..92e283d --- /dev/null +++ b/src/test/resources/detectors/assert_all/AssertAllNoPerfume.java @@ -0,0 +1,15 @@ +package de.jsilbereisen.test; + +import org.junit.jupiter.api.Test; + +public class AssertAllNoStaticImport { + + @Test + void test() { + assertAll(); + } + + private void assertAll() { + // imposter assertAll, should not be detected + } +} diff --git a/src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java b/src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java new file mode 100644 index 0000000..8c6eaf4 --- /dev/null +++ b/src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java @@ -0,0 +1,21 @@ +package de.jsilbereisen.test; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class AssertAllNoStaticImport { + + @Test + void testWithAssertAll() { + String i = "1"; + int j = 2; + int k = 3; + + Assertions.assertAll( + "grouped type assertions", + () -> assertThat(i instanceof Integer), + () -> assertThat(j instanceof Integer), + () -> assertThat(k instanceof Integer) + ); + } +} diff --git a/src/test/resources/detectors/assert_all/AssertAllStaticImport.java b/src/test/resources/detectors/assert_all/AssertAllStaticImport.java new file mode 100644 index 0000000..56f77d9 --- /dev/null +++ b/src/test/resources/detectors/assert_all/AssertAllStaticImport.java @@ -0,0 +1,22 @@ +package de.jsilbereisen.test; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; + +public class AssertAllStaticImport { + + @Test + void testWithAssertAll() { + String i = "1"; + int j = 2; + int k = 3; + + assertAll( + "grouped type assertions", + () -> assertThat(i instanceof Integer), + () -> assertThat(j instanceof Integer), + () -> assertThat(k instanceof Integer) + ); + } +} diff --git a/src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java b/src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java new file mode 100644 index 0000000..4056748 --- /dev/null +++ b/src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java @@ -0,0 +1,22 @@ +package de.jsilbereisen.test; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class AssertionsStaticWildcardImport { + + @Test + void testWithAssertAll() { + String i = "1"; + int j = 2; + int k = 3; + + assertAll( + "grouped type assertions", + () -> assertThat(i instanceof Integer), + () -> assertThat(j instanceof Integer), + () -> assertThat(k instanceof Integer) + ); + } +} diff --git a/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java b/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java new file mode 100644 index 0000000..68576df --- /dev/null +++ b/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java @@ -0,0 +1,27 @@ +package de.jsilbereisen.test; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ParameterizedTests { + + @Test + void nonParameterized1() { + int i = 1; + assertThat(i).isInstanceOf(Integer.class); + } + + @Test + void nonParameterized2() { + int i = 2; + assertThat(i).isInstanceOf(Integer.class); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2}) + void paramterized(int i) { + assertThat(i).isInstanceOf(Integer.class); + } +} diff --git a/src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java b/src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java new file mode 100644 index 0000000..b668607 --- /dev/null +++ b/src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java @@ -0,0 +1,22 @@ +package de.jsilbereisen.test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ParameterizedTests { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ParameterizedTest { + } + + @ParameterizedTest + void paramterized() { + // no perfume + } + + @org.junit.jupiter.params.ParameterizedTest + @ValueSource(ints = {1, 2}) + void paramterized(int i) { + assertThat(i).isInstanceOf(Integer.class); + } +} diff --git a/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java b/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java new file mode 100644 index 0000000..69bdf8a --- /dev/null +++ b/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java @@ -0,0 +1,36 @@ +package de.jsilbereisen.test; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SetupAndTeardownMethodPerfumes { + + @BeforeAll + void setUpBeforeAll() { + // setup once before all tests + } + + @BeforeEach + void setUpBeforeEach() { + // setup before every test + } + + void test() { + int i = 1; + assertThat(i == 1); + } + + @AfterEach + void tearDownAfterEach() { + // teardown after every test + } + + @AfterAll + void tearDownAfterAll() { + // teardown after all tests + } +} diff --git a/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethodsOwnAnnotations.java b/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethodsOwnAnnotations.java new file mode 100644 index 0000000..9b39468 --- /dev/null +++ b/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethodsOwnAnnotations.java @@ -0,0 +1,76 @@ +package de.jsilbereisen.test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SetupAndTeardownMethodPerfumes { + + @org.junit.jupiter.api.BeforeAll + void setUpBeforeAll() { + // setup once before all tests + } + + @org.junit.jupiter.api.BeforeEach + void setUpBeforeEach() { + // setup before every test + } + + void test() { + int i = 1; + assertThat(i == 1); + } + + @org.junit.jupiter.api.AfterEach + void tearDownAfterEach() { + // teardown after every test + } + + @org.junit.jupiter.api.AfterAll + void tearDownAfterAll() { + // teardown after all tests + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface BeforeAll { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface BeforeEach { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface AfterEach { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface AfterAll { + } + + @BeforeAll + void setUpBeforeAll() { + + } + + @BeforeEach + void setUpBeforeEach() { + + } + + void test() { + int i = 1; + assertThat(i == 1); + } + + @AfterEach + void tearDownAfterEach() { + + } + + @AfterAll + void tearDownAfterAll() { + + } +} diff --git a/src/test/resources/detectors/swing/JFrameDispose.java b/src/test/resources/detectors/swing/JFrameDispose.java new file mode 100644 index 0000000..78c2f43 --- /dev/null +++ b/src/test/resources/detectors/swing/JFrameDispose.java @@ -0,0 +1,22 @@ +package de.jsilbereisen.test; + +import javax.swing.*; + +public class JFrameDispose { + class NoRealJFrame { + public void dispose() { + + } + } + + public static void main(String[] args) { + NoRealJFrame frame = new NoRealJFrame(); + JFrame frame1 = new JFrame(); + javax.swing.JFrame frame2 = new javax.swing.JFrame(); + var frame3 = new JFrame(); + frame.dispose(); + frame1.dispose(); + frame2.dispose(); + frame3.dispose(); + } +} diff --git a/src/test/resources/detectors/swing/SwingTimer.java b/src/test/resources/detectors/swing/SwingTimer.java new file mode 100644 index 0000000..9ab6492 --- /dev/null +++ b/src/test/resources/detectors/swing/SwingTimer.java @@ -0,0 +1,23 @@ +package de.jsilbereisen.test; + +import javax.swing.Timer; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class SwingTimer { + + Timer t = new Timer(1000, new ActionListener() {}); + SomeClass c = new SomeClass(); + + public static void main(String[] args) { + Timer timer1; + timer1 = new Timer(50, new ActionListener() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + // do something with gui data + } + }); + var timer2 = new Timer(100, new ActionListener() {}); + javax.swing.Timer timer3 = new javax.swing.Timer(100, () -> {}); + } +} diff --git a/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoPerfume.java b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoPerfume.java new file mode 100644 index 0000000..6373668 --- /dev/null +++ b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoPerfume.java @@ -0,0 +1,17 @@ +package de.jsilbereisen.test; + +public class InvokeLaterInvokeAndWaitNoPerfume { + + public static void main(String[] args) { + invokeLater(); + invokeAndWait(); + } + + public static void invokeLater() { + + } + + public static void invokeAndWait() { + + } +} diff --git a/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoStaticImport.java b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoStaticImport.java new file mode 100644 index 0000000..88f8a5c --- /dev/null +++ b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoStaticImport.java @@ -0,0 +1,24 @@ +package de.jsilbereisen.test; + +import javax.swing.SwingUtilities; + +public class InvokeLaterInvokeAndWait { + + public static void main(String[] args) { + SwingUtilities.invokeLater(new Runnable() { + // perfume + }); + + SwingUtilities.invokeAndWait(() -> { + // perfume + }); + + invokeLater(() -> { + // no perfume + }); + } + + public static void invokeLater(Runnable runnable) { + + } +} diff --git a/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticImport.java b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticImport.java new file mode 100644 index 0000000..f9a00f3 --- /dev/null +++ b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticImport.java @@ -0,0 +1,17 @@ +package de.jsilbereisen.test; + +import static javax.swing.SwingUtilities.invokeAndWait; +import static javax.swing.SwingUtilities.invokeLater; + +public class InvokeLaterInvokeAndWait { + + public static void main(String[] args) { + invokeLater(new Runnable() { + // perfume + }); + + invokeAndWait(() -> { + // perfume + }); + } +} diff --git a/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticWildcardImport.java b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticWildcardImport.java new file mode 100644 index 0000000..8358eae --- /dev/null +++ b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticWildcardImport.java @@ -0,0 +1,16 @@ +package de.jsilbereisen.test; + +import static javax.swing.SwingUtilities.*; + +public class InvokeLaterInvokeAndWait { + + public static void main(String[] args) { + invokeLater(new Runnable() { + // perfume + }); + + invokeAndWait(() -> { + // perfume + }); + } +}