From 80b4990ee47690582eec1c46ed1c8c6d5268ec00 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Tue, 24 Sep 2024 20:23:00 +0200 Subject: [PATCH 01/15] add testing perfumes --- .../detector/perfume/AssertAllDetector.java | 56 ++++++++++++++++ .../perfume/ParameterizedTestDetector.java | 62 ++++++++++++++++++ .../SetupAndTeardownMethodDetector.java | 57 ++++++++++++++++ .../perfumator/data/perfumes/Assert_all.json | 9 +++ .../data/perfumes/Parameterized_test.json | 9 +++ .../perfumes/Setup_and_teardown_methods.json | 9 +++ .../java/detectors/AssertAllDetector.java | 51 +++++++++++++++ .../ParameterizedTestDetectorTest.java | 51 +++++++++++++++ .../SetupAndTeardownMethodDetectorTest.java | 65 +++++++++++++++++++ .../resources/detectors/AssertAllPerfume.java | 23 +++++++ .../detectors/ParameterizedTests.java | 27 ++++++++ .../SetupAndTeardownMethodPerfumes.java | 36 ++++++++++ 12 files changed, 455 insertions(+) create mode 100644 src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java create mode 100644 src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ParameterizedTestDetector.java create mode 100644 src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SetupAndTeardownMethodDetector.java create mode 100644 src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json create mode 100644 src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json create mode 100644 src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Setup_and_teardown_methods.json create mode 100644 src/test/java/detectors/AssertAllDetector.java create mode 100644 src/test/java/detectors/ParameterizedTestDetectorTest.java create mode 100644 src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java create mode 100644 src/test/resources/detectors/AssertAllPerfume.java create mode 100644 src/test/resources/detectors/ParameterizedTests.java create mode 100644 src/test/resources/detectors/SetupAndTeardownMethodPerfumes.java 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..bb2a4e3 --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java @@ -0,0 +1,56 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.visitor.MethodCallByNameVisitor; +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; + +@EqualsAndHashCode +public class AssertAllDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private static final String ASSERT_ALL_METHOD_NAME = "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) { + MethodCallByNameVisitor methodCallByNameVisitor = new MethodCallByNameVisitor(); + astRoot.accept(methodCallByNameVisitor, null); + List assertAllMethodCallExpressions = new ArrayList<>(); + for (MethodCallExpr methodCallExpr : methodCallByNameVisitor.getMethodCalls()) { + if (ASSERT_ALL_METHOD_NAME.equals(methodCallExpr.getNameAsString())) { + assertAllMethodCallExpressions.add(methodCallExpr); + } + } + return assertAllMethodCallExpressions; + } +} 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..f1d61a9 --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ParameterizedTestDetector.java @@ -0,0 +1,62 @@ +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.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.visitor.MethodDeclarationVisitor; +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; + +@EqualsAndHashCode +public class ParameterizedTestDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private static final String PARAMETERIZED_TEST_IDENTIFIER = "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) { + MethodDeclarationVisitor methodDeclarationVisitor = new MethodDeclarationVisitor(); + astRoot.accept(methodDeclarationVisitor, null); + List parameterizedTestMethodDeclarations = new ArrayList<>(); + for (MethodDeclaration declaration : methodDeclarationVisitor.getMethodDeclarations()) { + boolean hasParameterizedTestAnnotation = declaration.getAnnotations() + .stream() + .map(AnnotationExpr::getNameAsString) + .anyMatch(id -> id.equals(PARAMETERIZED_TEST_IDENTIFIER)); + + if (hasParameterizedTestAnnotation) { + parameterizedTestMethodDeclarations.add(declaration); + } + } + return parameterizedTestMethodDeclarations; + } +} 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..ec191dd --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SetupAndTeardownMethodDetector.java @@ -0,0 +1,57 @@ +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.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.visitor.MethodDeclarationVisitor; +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; + +public class SetupAndTeardownMethodDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + public static final List TEST_ANNOTATIONS = List.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 List getSetupAndTeardownMethodDeclarations(@NotNull CompilationUnit astRoot) { + MethodDeclarationVisitor methodDeclarationVisitor = new MethodDeclarationVisitor(); + astRoot.accept(methodDeclarationVisitor, null); + List setupAndTeardownMethodDeclarations = new ArrayList<>(); + for (MethodDeclaration declaration : methodDeclarationVisitor.getMethodDeclarations()) { + for (AnnotationExpr annotation : declaration.getAnnotations()) { + if (TEST_ANNOTATIONS.contains(annotation.getNameAsString())) { + setupAndTeardownMethodDeclarations.add(declaration); + } + } + } + return setupAndTeardownMethodDeclarations; + } +} 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..090dd70 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json @@ -0,0 +1,9 @@ +{ + "name": "Assert all", + "description": "By using an assertAll instead of multiple assertThat expressions, the programmer is informed about all results, even though some assertions may fail. In contrast, a failing assertThat would terminate the test execution.", + "detectorClassSimpleName": "AssertAllDetector", + "i18nBaseBundleName": "assertAll", + "sources": null, + "relatedPattern": "SMELL", + "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..2406c49 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json @@ -0,0 +1,9 @@ +{ + "name": "Parameterized test", + "description": "Similar tests should be grouped in a single Parameterized test", + "detectorClassSimpleName": "ParameterizedTestDetector", + "i18nBaseBundleName": "parameterizedTests", + "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..f98d628 --- /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.", + "detectorClassSimpleName": "SetupAndTeardownMethodDetector", + "i18nBaseBundleName": "setupAndTeardownMethod", + "sources": null, + "relatedPattern": "SMELL", + "additionalInformation": null +} \ No newline at end of file diff --git a/src/test/java/detectors/AssertAllDetector.java b/src/test/java/detectors/AssertAllDetector.java new file mode 100644 index 0000000..bf82e36 --- /dev/null +++ b/src/test/java/detectors/AssertAllDetector.java @@ -0,0 +1,51 @@ +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 AssertAllDetector extends AbstractDetectorTest { + + private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("ParameterizedTests.java"); + + 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); + + ast = parseAstForFile(TEST_FILE); + } + + @Test + void detect() { + 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(21, 5, 25, 5)); + } +} diff --git a/src/test/java/detectors/ParameterizedTestDetectorTest.java b/src/test/java/detectors/ParameterizedTestDetectorTest.java new file mode 100644 index 0000000..e194d7a --- /dev/null +++ b/src/test/java/detectors/ParameterizedTestDetectorTest.java @@ -0,0 +1,51 @@ +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 ParameterizedTestDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("AssertAllPerfume.java"); + + 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); + + ast = parseAstForFile(TEST_FILE); + } + + @Test + void detect() { + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(1); + + DetectedInstance detection = detections.get(0); + + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("AssertAllPerfume"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(16, 9, 21, 9)); + } +} diff --git a/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java b/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java new file mode 100644 index 0000000..c886e37 --- /dev/null +++ b/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.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.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_FILE = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("SetupAndTeardownMethodPerfumes.java"); + + 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); + + ast = parseAstForFile(TEST_FILE); + } + + @Test + void detect() { + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(4); + + DetectedInstance beforeAllDetection = detections.get(0); + assertThat(beforeAllDetection.getDetectable()).isEqualTo(perfume); + assertThat(beforeAllDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodPerfumes"); + assertThat(beforeAllDetection.getCodeRanges()).containsExactly(CodeRange.of(12, 5, 15, 5)); + + DetectedInstance beforeEachDetection = detections.get(1); + assertThat(beforeEachDetection.getDetectable()).isEqualTo(perfume); + assertThat(beforeEachDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodPerfumes"); + assertThat(beforeEachDetection.getCodeRanges()).containsExactly(CodeRange.of(17, 5, 20, 5)); + + DetectedInstance afterEachDetection = detections.get(2); + assertThat(afterEachDetection.getDetectable()).isEqualTo(perfume); + assertThat(afterEachDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodPerfumes"); + assertThat(afterEachDetection.getCodeRanges()).containsExactly(CodeRange.of(27, 5, 30, 5)); + + DetectedInstance afterAllDetection = detections.get(3); + assertThat(afterAllDetection.getDetectable()).isEqualTo(perfume); + assertThat(afterAllDetection.getTypeName()).isEqualTo("SetupAndTeardownMethodPerfumes"); + assertThat(afterAllDetection.getCodeRanges()).containsExactly(CodeRange.of(32, 5, 35, 5)); + } +} diff --git a/src/test/resources/detectors/AssertAllPerfume.java b/src/test/resources/detectors/AssertAllPerfume.java new file mode 100644 index 0000000..5f0e492 --- /dev/null +++ b/src/test/resources/detectors/AssertAllPerfume.java @@ -0,0 +1,23 @@ +package de.jsilbereisen.test; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertAll; + +public class AssertAllPerfume { + + @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) + ); + } +} \ No newline at end of file diff --git a/src/test/resources/detectors/ParameterizedTests.java b/src/test/resources/detectors/ParameterizedTests.java new file mode 100644 index 0000000..0cbaa85 --- /dev/null +++ b/src/test/resources/detectors/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); + } +} \ No newline at end of file diff --git a/src/test/resources/detectors/SetupAndTeardownMethodPerfumes.java b/src/test/resources/detectors/SetupAndTeardownMethodPerfumes.java new file mode 100644 index 0000000..14def1d --- /dev/null +++ b/src/test/resources/detectors/SetupAndTeardownMethodPerfumes.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 + } +} \ No newline at end of file From a3dddea4996b277434b79f60be31168dd4eb5b2c Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Thu, 3 Oct 2024 18:16:07 +0200 Subject: [PATCH 02/15] fix i18n for some files --- .../i18n/perfumes/copyConstructor_de.properties | 2 +- .../i18n/perfumes/useTryWithResources_de.properties | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) 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/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 From 28c9d57166331c59fcde302a361754cff2634f45 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Fri, 4 Oct 2024 14:53:53 +0200 Subject: [PATCH 03/15] add swingutilities perfume --- .../perfume/ThreadSafeSwingDetector.java | 85 +++++++++++++++++++ .../data/perfumes/Thread_safe_swing.json | 9 ++ ...tector.java => AssertAllDetectorTest.java} | 15 ++-- .../ParameterizedTestDetectorTest.java | 16 ++-- .../ThreadSafeSwingDetectorTest.java | 56 ++++++++++++ .../swing/InvokeLaterInvokeAndWait.java | 24 ++++++ 6 files changed, 189 insertions(+), 16 deletions(-) create mode 100644 src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java create mode 100644 src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Thread_safe_swing.json rename src/test/java/detectors/{AssertAllDetector.java => AssertAllDetectorTest.java} (75%) create mode 100644 src/test/java/detectors/ThreadSafeSwingDetectorTest.java create mode 100644 src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java 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..0f6037c --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java @@ -0,0 +1,85 @@ +package de.jsilbereisen.perfumator.engine.detector.perfume; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade; +import de.jsilbereisen.perfumator.engine.detector.Detector; +import de.jsilbereisen.perfumator.engine.visitor.MethodCallByNameVisitor; +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.*; + +@EqualsAndHashCode +public class ThreadSafeSwingDetector implements Detector { + + private Perfume perfume; + + private JavaParserFacade analysisContext; + + private final static String SWING_UTILITIES = "SwingUtilities"; + private final static String INVOKE_LATER = "invokeLater"; + private final static String INVOKE_AND_WAIT = "invokeAndWait"; + private final static Set RELEVANT_METHOD_NAMES = Set.of(INVOKE_LATER, INVOKE_AND_WAIT); + private final static Map RELEVANT_IMPORTS = + Map.of(INVOKE_LATER, "javax.swing.SwingUtilities.invokeLater", + INVOKE_AND_WAIT, "javax.swing.SwingUtilities.invokeAndWait"); + + @Override + public @NotNull List> detect(@NotNull CompilationUnit astRoot) { + List> detectedInstances = new ArrayList<>(); + Set staticImports = getStaticImports(astRoot); + List methodCalls = getInvokeLaterInvokeAndWaitMethodCalls(astRoot, staticImports); + 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 Set getStaticImports(@NotNull CompilationUnit astRoot) { + Set importedAnnotations = new HashSet<>(); + + for (ImportDeclaration importDeclaration : astRoot.getImports()) { + for (Map.Entry annotationToImport : RELEVANT_IMPORTS.entrySet()) { + if (importDeclaration.getNameAsString().equals(annotationToImport.getValue())) { + importedAnnotations.add(annotationToImport.getKey()); + } + } + } + return importedAnnotations; + } + + private List getInvokeLaterInvokeAndWaitMethodCalls(@NotNull CompilationUnit astRoot, + Set imports) { + MethodCallByNameVisitor methodCallByNameVisitor = new MethodCallByNameVisitor(); + astRoot.accept(methodCallByNameVisitor, null); + List relevantMethodCallExpressions = new ArrayList<>(); + for (MethodCallExpr methodCallExpr : methodCallByNameVisitor.getMethodCalls()) { + if (RELEVANT_METHOD_NAMES.contains(methodCallExpr.getNameAsString())) { + if (isPartOfSwingUtilities(methodCallExpr)) { + relevantMethodCallExpressions.add(methodCallExpr); + } else if (imports.contains(methodCallExpr.getNameAsString())) { + relevantMethodCallExpressions.add(methodCallExpr); + } + } + } + return relevantMethodCallExpressions; + } + + private boolean isPartOfSwingUtilities(MethodCallExpr methodCallExpr) { + var scope = methodCallExpr.getScope(); + return scope.map(expression -> expression.toString().equals(SWING_UTILITIES)).orElse(false); + } +} 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..77b395b --- /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 application 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. Event handlers 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 at 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/test/java/detectors/AssertAllDetector.java b/src/test/java/detectors/AssertAllDetectorTest.java similarity index 75% rename from src/test/java/detectors/AssertAllDetector.java rename to src/test/java/detectors/AssertAllDetectorTest.java index bf82e36..910369b 100644 --- a/src/test/java/detectors/AssertAllDetector.java +++ b/src/test/java/detectors/AssertAllDetectorTest.java @@ -2,7 +2,6 @@ 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; @@ -15,9 +14,9 @@ import static org.assertj.core.api.Assertions.assertThat; -public class AssertAllDetector extends AbstractDetectorTest { - - private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("ParameterizedTests.java"); +class AssertAllDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("AssertAllPerfume.java"); private static Perfume perfume; @@ -28,9 +27,9 @@ public class AssertAllDetector extends AbstractDetectorTest { @BeforeAll static void init() { perfume = new Perfume(); - perfume.setName("Parameterized Test"); + perfume.setName("Assert All"); - detector = new ParameterizedTestDetector(); + detector = new de.jsilbereisen.perfumator.engine.detector.perfume.AssertAllDetector(); detector.setConcreteDetectable(perfume); ast = parseAstForFile(TEST_FILE); @@ -45,7 +44,7 @@ void detect() { DetectedInstance detection = detections.get(0); assertThat(detection.getDetectable()).isEqualTo(perfume); - assertThat(detection.getTypeName()).isEqualTo("ParameterizedTests"); - assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(21, 5, 25, 5)); + assertThat(detection.getTypeName()).isEqualTo("AssertAllPerfume"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(16, 9, 21, 9)); } } diff --git a/src/test/java/detectors/ParameterizedTestDetectorTest.java b/src/test/java/detectors/ParameterizedTestDetectorTest.java index e194d7a..ced340c 100644 --- a/src/test/java/detectors/ParameterizedTestDetectorTest.java +++ b/src/test/java/detectors/ParameterizedTestDetectorTest.java @@ -2,7 +2,7 @@ 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.engine.detector.perfume.ParameterizedTestDetector; import de.jsilbereisen.perfumator.model.CodeRange; import de.jsilbereisen.perfumator.model.DetectedInstance; import de.jsilbereisen.perfumator.model.perfume.Perfume; @@ -15,9 +15,9 @@ import static org.assertj.core.api.Assertions.assertThat; -class ParameterizedTestDetectorTest extends AbstractDetectorTest { - - private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("AssertAllPerfume.java"); +public class ParameterizedTestDetectorTest extends AbstractDetectorTest { + + private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("ParameterizedTests.java"); private static Perfume perfume; @@ -28,9 +28,9 @@ class ParameterizedTestDetectorTest extends AbstractDetectorTest { @BeforeAll static void init() { perfume = new Perfume(); - perfume.setName("Assert All"); + perfume.setName("Parameterized Test"); - detector = new AssertAllDetector(); + detector = new ParameterizedTestDetector(); detector.setConcreteDetectable(perfume); ast = parseAstForFile(TEST_FILE); @@ -45,7 +45,7 @@ void detect() { DetectedInstance detection = detections.get(0); assertThat(detection.getDetectable()).isEqualTo(perfume); - assertThat(detection.getTypeName()).isEqualTo("AssertAllPerfume"); - assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(16, 9, 21, 9)); + assertThat(detection.getTypeName()).isEqualTo("ParameterizedTests"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(22, 5, 26, 5)); } } diff --git a/src/test/java/detectors/ThreadSafeSwingDetectorTest.java b/src/test/java/detectors/ThreadSafeSwingDetectorTest.java new file mode 100644 index 0000000..d3c60a6 --- /dev/null +++ b/src/test/java/detectors/ThreadSafeSwingDetectorTest.java @@ -0,0 +1,56 @@ +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_FILE = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("swing").resolve("InvokeLaterInvokeAndWait.java"); + + 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); + + ast = parseAstForFile(TEST_FILE); + } + + @Test + void detect() { + List> detections = detector.detect(ast); + + assertThat(detections).hasSize(2); + + DetectedInstance detection = detections.get(0); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWait"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(9, 9, 11, 10)); + + detection = detections.get(1); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("InvokeLaterInvokeAndWait"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(13, 9, 15, 10)); + } +} diff --git a/src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java b/src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java new file mode 100644 index 0000000..7228113 --- /dev/null +++ b/src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java @@ -0,0 +1,24 @@ +package de.jsilbereisen.test; + +import javax.swing.SwingUtilities; +import static javax.swing.SwingUtilities.invokeAndWait; + +public class InvokeLaterInvokeAndWait { + + public static void main(String[] args) { + SwingUtilities.invokeLater(new Runnable() { + // perfume + }); + + invokeAndWait(() -> { + // perfume + }); + + // no perfume + invokeLater(); + } + + public static void invokeLater() { + // no perfume, not the library method we are looking for + } +} \ No newline at end of file From b735623087566462077c101fa3b2bc0c0559b8f6 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Sun, 6 Oct 2024 12:04:07 +0200 Subject: [PATCH 04/15] add swing perfumes and many improvements --- .../engine/PerfumeDetectionEngine.java | 3 + .../detector/perfume/AssertAllDetector.java | 35 ++++++--- .../perfume/JFrameDisposeDetector.java | 63 +++++++++++++++ .../perfume/ParameterizedTestDetector.java | 30 +++----- .../SetupAndTeardownMethodDetector.java | 30 ++++---- .../detector/perfume/SwingTimerDetector.java | 55 ++++++++++++++ .../perfume/ThreadSafeSwingDetector.java | 68 +++++++---------- .../data/perfumes/JFrame_dispose.json | 9 +++ .../perfumator/data/perfumes/Swing_timer.json | 9 +++ .../data/perfumes/Thread_safe_swing.json | 2 +- .../java/detectors/AssertAllDetectorTest.java | 48 ++++++++++-- .../detectors/JFrameDisposeDetectorTest.java | 61 +++++++++++++++ .../ParameterizedTestDetectorTest.java | 18 ++++- .../SetupAndTeardownMethodDetectorTest.java | 43 +++++++++-- .../detectors/SwingTimerDetectorTest.java | 65 ++++++++++++++++ .../ThreadSafeSwingDetectorTest.java | 58 ++++++++++++-- .../assert_all/AssertAllNoPerfume.java | 15 ++++ .../assert_all/AssertAllNoStaticImport.java | 21 +++++ .../AssertAllStaticImport.java} | 5 +- .../AssertionsStaticWildcardImport.java | 22 ++++++ .../ParameterizedTests.java | 0 .../ParameterizedTestsOwnAnnotation.java | 22 ++++++ .../SetupAndTeardownMethods.java} | 0 ...SetupAndTeardownMethodsOwnAnnotations.java | 76 +++++++++++++++++++ .../detectors/swing/JFrameDispose.java | 22 ++++++ .../resources/detectors/swing/SwingTimer.java | 22 ++++++ .../InvokeLaterInvokeAndWaitNoPerfume.java | 17 +++++ ...vokeLaterInvokeAndWaitNoStaticImport.java} | 14 ++-- .../InvokeLaterInvokeAndWaitStaticImport.java | 17 +++++ ...aterInvokeAndWaitStaticWildcardImport.java | 16 ++++ 30 files changed, 750 insertions(+), 116 deletions(-) create mode 100644 src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/JFrameDisposeDetector.java create mode 100644 src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SwingTimerDetector.java create mode 100644 src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json create mode 100644 src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Swing_timer.json create mode 100644 src/test/java/detectors/JFrameDisposeDetectorTest.java create mode 100644 src/test/java/detectors/SwingTimerDetectorTest.java create mode 100644 src/test/resources/detectors/assert_all/AssertAllNoPerfume.java create mode 100644 src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java rename src/test/resources/detectors/{AssertAllPerfume.java => assert_all/AssertAllStaticImport.java} (73%) create mode 100644 src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java rename src/test/resources/detectors/{ => parameterized_tests}/ParameterizedTests.java (100%) create mode 100644 src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java rename src/test/resources/detectors/{SetupAndTeardownMethodPerfumes.java => setup_and_teardown_methods/SetupAndTeardownMethods.java} (100%) create mode 100644 src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethodsOwnAnnotations.java create mode 100644 src/test/resources/detectors/swing/JFrameDispose.java create mode 100644 src/test/resources/detectors/swing/SwingTimer.java create mode 100644 src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoPerfume.java rename src/test/resources/detectors/swing/{InvokeLaterInvokeAndWait.java => invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoStaticImport.java} (57%) create mode 100644 src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticImport.java create mode 100644 src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitStaticWildcardImport.java diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java b/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java index e48575c..cb84d53 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; @@ -119,6 +121,7 @@ public static JavaParser getConfiguredJavaParser() { ParserConfiguration config = new ParserConfiguration(); config.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17); + 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 index bb2a4e3..394ff62 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java @@ -2,9 +2,11 @@ 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.engine.visitor.MethodCallByNameVisitor; import de.jsilbereisen.perfumator.model.DetectedInstance; import de.jsilbereisen.perfumator.model.perfume.Perfume; import lombok.EqualsAndHashCode; @@ -14,6 +16,10 @@ 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 { @@ -21,7 +27,8 @@ public class AssertAllDetector implements Detector { private JavaParserFacade analysisContext; - private static final String ASSERT_ALL_METHOD_NAME = "assertAll"; + 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"; @Override public @NotNull List> detect(@NotNull CompilationUnit astRoot) { @@ -43,14 +50,22 @@ public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { } private List getAssertAllMethodCalls(@NotNull CompilationUnit astRoot) { - MethodCallByNameVisitor methodCallByNameVisitor = new MethodCallByNameVisitor(); - astRoot.accept(methodCallByNameVisitor, null); - List assertAllMethodCallExpressions = new ArrayList<>(); - for (MethodCallExpr methodCallExpr : methodCallByNameVisitor.getMethodCalls()) { - if (ASSERT_ALL_METHOD_NAME.equals(methodCallExpr.getNameAsString())) { - assertAllMethodCallExpressions.add(methodCallExpr); + return astRoot.findAll(MethodCallExpr.class, expr -> { + // contains instead of equals because of possible 'Assertions.assertAll' calls + if (!expr.getNameAsString().contains("assertAll")) { + return false; } - } - return assertAllMethodCallExpressions; + 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 index f1d61a9..11d7c7b 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ParameterizedTestDetector.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ParameterizedTestDetector.java @@ -3,9 +3,9 @@ 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.engine.visitor.MethodDeclarationVisitor; import de.jsilbereisen.perfumator.model.DetectedInstance; import de.jsilbereisen.perfumator.model.perfume.Perfume; import lombok.EqualsAndHashCode; @@ -15,6 +15,10 @@ 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 { @@ -22,8 +26,8 @@ public class ParameterizedTestDetector implements Detector { private JavaParserFacade analysisContext; - private static final String PARAMETERIZED_TEST_IDENTIFIER = "ParameterizedTest"; - + private static final String PARAMETERIZED_TEST_IDENTIFIER = "org.junit.jupiter.params.ParameterizedTest"; + @Override public @NotNull List> detect(@NotNull CompilationUnit astRoot) { List> detectedInstances = new ArrayList<>(); @@ -42,21 +46,11 @@ public void setConcreteDetectable(@NotNull Perfume concreteDetectable) { public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { this.analysisContext = analysisContext; } - + private List getParameterizedTestMethodDeclarations(@NotNull CompilationUnit astRoot) { - MethodDeclarationVisitor methodDeclarationVisitor = new MethodDeclarationVisitor(); - astRoot.accept(methodDeclarationVisitor, null); - List parameterizedTestMethodDeclarations = new ArrayList<>(); - for (MethodDeclaration declaration : methodDeclarationVisitor.getMethodDeclarations()) { - boolean hasParameterizedTestAnnotation = declaration.getAnnotations() - .stream() - .map(AnnotationExpr::getNameAsString) - .anyMatch(id -> id.equals(PARAMETERIZED_TEST_IDENTIFIER)); - - if (hasParameterizedTestAnnotation) { - parameterizedTestMethodDeclarations.add(declaration); - } - } - return parameterizedTestMethodDeclarations; + 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 index ec191dd..42350cd 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SetupAndTeardownMethodDetector.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SetupAndTeardownMethodDetector.java @@ -3,9 +3,9 @@ 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.engine.visitor.MethodDeclarationVisitor; import de.jsilbereisen.perfumator.model.DetectedInstance; import de.jsilbereisen.perfumator.model.perfume.Perfume; import org.jetbrains.annotations.NotNull; @@ -13,14 +13,21 @@ 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 List TEST_ANNOTATIONS = List.of("BeforeAll", "BeforeEach", "AfterAll", "AfterEach"); + 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) { @@ -41,17 +48,14 @@ 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) { - MethodDeclarationVisitor methodDeclarationVisitor = new MethodDeclarationVisitor(); - astRoot.accept(methodDeclarationVisitor, null); - List setupAndTeardownMethodDeclarations = new ArrayList<>(); - for (MethodDeclaration declaration : methodDeclarationVisitor.getMethodDeclarations()) { - for (AnnotationExpr annotation : declaration.getAnnotations()) { - if (TEST_ANNOTATIONS.contains(annotation.getNameAsString())) { - setupAndTeardownMethodDeclarations.add(declaration); - } - } - } - return setupAndTeardownMethodDeclarations; + 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..1fe29d4 --- /dev/null +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SwingTimerDetector.java @@ -0,0 +1,55 @@ +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.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 = expr.calculateResolvedType(); + 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 index 0f6037c..d4758ed 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/ThreadSafeSwingDetector.java @@ -1,11 +1,12 @@ package de.jsilbereisen.perfumator.engine.detector.perfume; import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.ImportDeclaration; 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.engine.visitor.MethodCallByNameVisitor; import de.jsilbereisen.perfumator.model.DetectedInstance; import de.jsilbereisen.perfumator.model.perfume.Perfume; import lombok.EqualsAndHashCode; @@ -14,6 +15,11 @@ 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 { @@ -21,19 +27,16 @@ public class ThreadSafeSwingDetector implements Detector { private JavaParserFacade analysisContext; - private final static String SWING_UTILITIES = "SwingUtilities"; private final static String INVOKE_LATER = "invokeLater"; private final static String INVOKE_AND_WAIT = "invokeAndWait"; - private final static Set RELEVANT_METHOD_NAMES = Set.of(INVOKE_LATER, INVOKE_AND_WAIT); - private final static Map RELEVANT_IMPORTS = - Map.of(INVOKE_LATER, "javax.swing.SwingUtilities.invokeLater", - INVOKE_AND_WAIT, "javax.swing.SwingUtilities.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<>(); - Set staticImports = getStaticImports(astRoot); - List methodCalls = getInvokeLaterInvokeAndWaitMethodCalls(astRoot, staticImports); + List methodCalls = getInvokeLaterInvokeAndWaitMethodCalls(astRoot); methodCalls.forEach(callExpr -> detectedInstances.add(DetectedInstance.from(callExpr, perfume, astRoot))); return detectedInstances; } @@ -48,38 +51,23 @@ public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { this.analysisContext = analysisContext; } - private Set getStaticImports(@NotNull CompilationUnit astRoot) { - Set importedAnnotations = new HashSet<>(); - - for (ImportDeclaration importDeclaration : astRoot.getImports()) { - for (Map.Entry annotationToImport : RELEVANT_IMPORTS.entrySet()) { - if (importDeclaration.getNameAsString().equals(annotationToImport.getValue())) { - importedAnnotations.add(annotationToImport.getKey()); - } + 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; } - } - return importedAnnotations; - } - - private List getInvokeLaterInvokeAndWaitMethodCalls(@NotNull CompilationUnit astRoot, - Set imports) { - MethodCallByNameVisitor methodCallByNameVisitor = new MethodCallByNameVisitor(); - astRoot.accept(methodCallByNameVisitor, null); - List relevantMethodCallExpressions = new ArrayList<>(); - for (MethodCallExpr methodCallExpr : methodCallByNameVisitor.getMethodCalls()) { - if (RELEVANT_METHOD_NAMES.contains(methodCallExpr.getNameAsString())) { - if (isPartOfSwingUtilities(methodCallExpr)) { - relevantMethodCallExpressions.add(methodCallExpr); - } else if (imports.contains(methodCallExpr.getNameAsString())) { - relevantMethodCallExpressions.add(methodCallExpr); - } + 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); } - } - return relevantMethodCallExpressions; - } - - private boolean isPartOfSwingUtilities(MethodCallExpr methodCallExpr) { - var scope = methodCallExpr.getScope(); - return scope.map(expression -> expression.toString().equals(SWING_UTILITIES)).orElse(false); + }); } } 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..05148d3 --- /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()\" 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 remaining threads programmatically. While this may seem like a downside, it ultimately provides the most control, as ongoing processes can be waited for until they are completed.", + "detectorClassSimpleName": "JFrameDisposeDetector", + "i18nBaseBundleName": "jFrameDispose", + "sources": ["Programmierung II - Uni Passau", "https://stackoverflow.com/questions/13360430/jframe-dispose-vs-system-exit"], + "relatedPattern": "BUG", + "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..98f9052 --- /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 delay (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 index 77b395b..1991672 100644 --- 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 @@ -3,7 +3,7 @@ "description": "Java Swing application 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. Event handlers 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 at Uni Passau", "https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html"], + "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/test/java/detectors/AssertAllDetectorTest.java b/src/test/java/detectors/AssertAllDetectorTest.java index 910369b..9342afc 100644 --- a/src/test/java/detectors/AssertAllDetectorTest.java +++ b/src/test/java/detectors/AssertAllDetectorTest.java @@ -2,6 +2,7 @@ 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; @@ -16,7 +17,7 @@ class AssertAllDetectorTest extends AbstractDetectorTest { - private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("AssertAllPerfume.java"); + private static final Path TEST_FILES_DIR = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("assert_all"); private static Perfume perfume; @@ -29,14 +30,41 @@ static void init() { perfume = new Perfume(); perfume.setName("Assert All"); - detector = new de.jsilbereisen.perfumator.engine.detector.perfume.AssertAllDetector(); + 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); - ast = parseAstForFile(TEST_FILE); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("AssertAllNoStaticImport"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(14, 9, 19, 9)); } @Test - void detect() { + void detectWithWildcardImport() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("AssertionsStaticWildcardImport.java")); List> detections = detector.detect(ast); assertThat(detections).hasSize(1); @@ -44,7 +72,15 @@ void detect() { DetectedInstance detection = detections.get(0); assertThat(detection.getDetectable()).isEqualTo(perfume); - assertThat(detection.getTypeName()).isEqualTo("AssertAllPerfume"); - assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(16, 9, 21, 9)); + 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 index ced340c..97dc12e 100644 --- a/src/test/java/detectors/ParameterizedTestDetectorTest.java +++ b/src/test/java/detectors/ParameterizedTestDetectorTest.java @@ -17,7 +17,7 @@ public class ParameterizedTestDetectorTest extends AbstractDetectorTest { - private static final Path TEST_FILE = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("ParameterizedTests.java"); + private static final Path TEST_FILES_DIR = DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("parameterized_tests"); private static Perfume perfume; @@ -32,12 +32,11 @@ static void init() { detector = new ParameterizedTestDetector(); detector.setConcreteDetectable(perfume); - - ast = parseAstForFile(TEST_FILE); } @Test void detect() { + ast = parseAstForFile(TEST_FILES_DIR.resolve("ParameterizedTests.java")); List> detections = detector.detect(ast); assertThat(detections).hasSize(1); @@ -48,4 +47,17 @@ void detect() { 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 index c886e37..5974370 100644 --- a/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java +++ b/src/test/java/detectors/SetupAndTeardownMethodDetectorTest.java @@ -16,8 +16,8 @@ import static org.assertj.core.api.Assertions.assertThat; public class SetupAndTeardownMethodDetectorTest extends AbstractDetectorTest { - private static final Path TEST_FILE = - DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("SetupAndTeardownMethodPerfumes.java"); + private static final Path TEST_FILES_DIR = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("setup_and_teardown_methods"); private static Perfume perfume; @@ -32,34 +32,61 @@ static void init() { detector = new SetupAndTeardownMethodDetector(); detector.setConcreteDetectable(perfume); - - ast = parseAstForFile(TEST_FILE); } @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("SetupAndTeardownMethodPerfumes"); + 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("SetupAndTeardownMethodPerfumes"); + 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("SetupAndTeardownMethodPerfumes"); + 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("SetupAndTeardownMethodPerfumes"); + 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..97c8503 --- /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(13, 18, 18, 10)); + + detection = detections.get(2); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("SwingTimer"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(19, 22, 19, 60)); + + detection = detections.get(3); + assertThat(detection.getDetectable()).isEqualTo(perfume); + assertThat(detection.getTypeName()).isEqualTo("SwingTimer"); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(20, 36, 20, 71)); + } +} diff --git a/src/test/java/detectors/ThreadSafeSwingDetectorTest.java b/src/test/java/detectors/ThreadSafeSwingDetectorTest.java index d3c60a6..0c78ae3 100644 --- a/src/test/java/detectors/ThreadSafeSwingDetectorTest.java +++ b/src/test/java/detectors/ThreadSafeSwingDetectorTest.java @@ -17,8 +17,8 @@ public class ThreadSafeSwingDetectorTest extends AbstractDetectorTest { - private static final Path TEST_FILE = - DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("swing").resolve("InvokeLaterInvokeAndWait.java"); + private static final Path TEST_FILES_DIR = + DEFAULT_DETECTOR_TEST_FILES_DIR.resolve("swing").resolve("invoke_later_invoke_and_wait"); private static Perfume perfume; @@ -33,24 +33,68 @@ static void init() { detector = new ThreadSafeSwingDetector(); detector.setConcreteDetectable(perfume); - - ast = parseAstForFile(TEST_FILE); } @Test - void detect() { + 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("InvokeLaterInvokeAndWait"); + 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("InvokeLaterInvokeAndWait"); + 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..67e8d13 --- /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 + } +} \ No newline at end of file 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..948cc09 --- /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) + ); + } +} \ No newline at end of file diff --git a/src/test/resources/detectors/AssertAllPerfume.java b/src/test/resources/detectors/assert_all/AssertAllStaticImport.java similarity index 73% rename from src/test/resources/detectors/AssertAllPerfume.java rename to src/test/resources/detectors/assert_all/AssertAllStaticImport.java index 5f0e492..9e5a624 100644 --- a/src/test/resources/detectors/AssertAllPerfume.java +++ b/src/test/resources/detectors/assert_all/AssertAllStaticImport.java @@ -2,10 +2,9 @@ import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertAll; -public class AssertAllPerfume { +public class AssertAllStaticImport { @Test void testWithAssertAll() { 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..a4c2915 --- /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) + ); + } +} \ No newline at end of file diff --git a/src/test/resources/detectors/ParameterizedTests.java b/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java similarity index 100% rename from src/test/resources/detectors/ParameterizedTests.java rename to src/test/resources/detectors/parameterized_tests/ParameterizedTests.java 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..43355eb --- /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); + } +} \ No newline at end of file diff --git a/src/test/resources/detectors/SetupAndTeardownMethodPerfumes.java b/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java similarity index 100% rename from src/test/resources/detectors/SetupAndTeardownMethodPerfumes.java rename to src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java 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..1df3213 --- /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(); + } +} \ No newline at end of file diff --git a/src/test/resources/detectors/swing/SwingTimer.java b/src/test/resources/detectors/swing/SwingTimer.java new file mode 100644 index 0000000..8b6692d --- /dev/null +++ b/src/test/resources/detectors/swing/SwingTimer.java @@ -0,0 +1,22 @@ +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() {}); + + 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, () -> {}); + } +} \ No newline at end of file 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..74853fb --- /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() { + + } +} \ No newline at end of file diff --git a/src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoStaticImport.java similarity index 57% rename from src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java rename to src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoStaticImport.java index 7228113..f0a2286 100644 --- a/src/test/resources/detectors/swing/InvokeLaterInvokeAndWait.java +++ b/src/test/resources/detectors/swing/invoke_later_invoke_and_wait/InvokeLaterInvokeAndWaitNoStaticImport.java @@ -1,7 +1,6 @@ package de.jsilbereisen.test; import javax.swing.SwingUtilities; -import static javax.swing.SwingUtilities.invokeAndWait; public class InvokeLaterInvokeAndWait { @@ -9,16 +8,17 @@ public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { // perfume }); - - invokeAndWait(() -> { + + SwingUtilities.invokeAndWait(() -> { // perfume }); - // no perfume - invokeLater(); + invokeLater(() -> { + // no perfume + }); } - public static void invokeLater() { - // no perfume, not the library method we are looking for + public static void invokeLater(Runnable runnable) { + } } \ No newline at end of file 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..f50f5ac --- /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 + }); + } +} \ No newline at end of file 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..47b4093 --- /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 + }); + } +} \ No newline at end of file From 527fefc6eff046be5383f00675a633a298089646 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Sun, 6 Oct 2024 12:51:15 +0200 Subject: [PATCH 05/15] i18n for new perfumes --- .../jsilbereisen/perfumator/data/perfumes/Assert_all.json | 6 +++--- .../perfumator/data/perfumes/JFrame_dispose.json | 2 +- .../perfumator/data/perfumes/Parameterized_test.json | 4 ++-- .../data/perfumes/Setup_and_teardown_methods.json | 6 +++--- .../jsilbereisen/perfumator/data/perfumes/Swing_timer.json | 2 +- .../perfumator/data/perfumes/Thread_safe_swing.json | 2 +- .../i18n/perfumes/SetupAndTeardownMethods.properties | 0 .../i18n/perfumes/SetupAndTeardownMethods_de.properties | 3 +++ .../perfumator/i18n/perfumes/assertAll.properties | 0 .../perfumator/i18n/perfumes/assertAll_de.properties | 3 +++ .../perfumator/i18n/perfumes/jFrameDispose.properties | 0 .../perfumator/i18n/perfumes/jFrameDispose_de.properties | 4 ++++ .../perfumator/i18n/perfumes/parameterizedTest.properties | 0 .../i18n/perfumes/parameterizedTest_de.properties | 3 +++ .../perfumator/i18n/perfumes/swingTimer.properties | 0 .../perfumator/i18n/perfumes/swingTimer_de.properties | 4 ++++ .../perfumator/i18n/perfumes/threadSafeSwing.properties | 0 .../perfumator/i18n/perfumes/threadSafeSwing_de.properties | 4 ++++ 18 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer_de.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing.properties create mode 100644 src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties 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 index 090dd70..4e6954d 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json @@ -1,9 +1,9 @@ { "name": "Assert all", - "description": "By using an assertAll instead of multiple assertThat expressions, the programmer is informed about all results, even though some assertions may fail. In contrast, a failing assertThat would terminate the test execution.", + "description": "The \"org.junit.jupiter.api.assertAll\" is used 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": null, - "relatedPattern": "SMELL", + "sources": ["https://stackoverflow.com/questions/40796756/assertall-vs-multiple-assertions-in-junit5"], + "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 index 05148d3..e52f93a 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json @@ -1,6 +1,6 @@ { "name": "JFrame dispose", - "description": "Instead of calling \"System.exit()\", which completely terminates the java virtual machine, the \"javax.swing.JFrame.dispose()\" 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 remaining threads programmatically. While this may seem like a downside, it ultimately provides the most control, as ongoing processes can be waited for until they are completed.", + "description": "Instead of calling \"System.exit\", which completely terminates the java virtual machine, the \"javax.swing.JFrame.dispose\" 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 remaining 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://stackoverflow.com/questions/13360430/jframe-dispose-vs-system-exit"], 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 index 2406c49..4b5627a 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json @@ -1,8 +1,8 @@ { "name": "Parameterized test", - "description": "Similar tests should be grouped in a single Parameterized test", + "description": "Tests can be annotated with the \"org.junit.jupiter.params.ParameterizedTest\" annotation. Such parameterized tests makes 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": "parameterizedTests", + "i18nBaseBundleName": "parameterizedTest", "sources": ["https://rules.sonarsource.com/java/RSPEC-5976/"], "relatedPattern": "SMELL", "additionalInformation": null 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 index f98d628..5b0ffda 100644 --- 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 @@ -1,9 +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.", + "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": "setupAndTeardownMethod", - "sources": null, + "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 index 98f9052..d722469 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Swing_timer.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Swing_timer.json @@ -1,6 +1,6 @@ { "name": "Swing timer", - "description": "The timer from the \"javax.swing\" library simplifies GUI updates significantly. Its constructor requires a delay (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.", + "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"], 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 index 1991672..625bcc2 100644 --- 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 @@ -1,6 +1,6 @@ { "name": "Thread safe Swing", - "description": "Java Swing application 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. Event handlers 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.", + "description": "Java Swing application 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"], 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..2612068 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties @@ -0,0 +1,3 @@ +ame=Setup und Teardown Methoden +description=Gemeinsamer Setup- und Teardown-Code sollte vorzugsweise innerhalb von Methoden durchgeführt werden, die entweder mit "@BeforeEach", "@BeforeAll", "@AfterEach" oder "@AfterAll" annotiert sind. Auf diese Weise lässt sich Boilerplate-Code in den einzelnen Tests erheblich reduzieren und der Fokus liegt stärker 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..8f0bd02 --- /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 ausgeführt werden. Daher erhält der Benutzer die Ergebnisse aller Tests und nicht nur der Tests bis zur fehlgeschlagenen Assertions, die ansonsten die Ausführung des Tests beenden würde. +source#1="https://stackoverflow.com/questions/40796756/assertall-vs-multiple-assertions-in-junit5" \ 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..4ce10b7 --- /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 vollständig beendet, schließt \"javax.swing.JFrame.dispose\" das JFrame Object und die ihm zugewiesenen Ressourcen auf kontrolliertere Weise. Wenn man die Anwendung auf diese Weise schließt, muss man sicherstellen, dass alle verbleibenden Threads programmatisch beendet werden. Dies mag zwar wie ein Nachteil erscheinen, bietet aber letztlich die größte Kontrolle, da laufende Prozesse kontrolliert beendet werden können. +source#1=Programmierung II - Uni Passau +source#2="https://stackoverflow.com/questions/13360430/jframe-dispose-vs-system-exit" \ 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..d303823 --- /dev/null +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties @@ -0,0 +1,3 @@ +name=Parametrisierte Tests +description=Tests können 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..e0f8280 --- /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 Benutzeroberflächen 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 ausgeführt, das durch den ersten Parameter des Timers definiert ist. Der Timer führt 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..6548f24 --- /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 übernehmen. Der Programmierer ist dafür 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, die wir als Argument entweder an die Methode \"SwingUtilities.invokeLater\" oder \"SwingUtilities.invokeAndWait\" übergeben. +source#1=Programmierung II - Uni Passau +source#2=https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html \ No newline at end of file From 0a2a58a82eeff7edf76ea0f0cdd1629fb0a28684 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Sun, 6 Oct 2024 13:02:59 +0200 Subject: [PATCH 06/15] add newlines at the end of testing resources --- src/test/resources/detectors/assert_all/AssertAllNoPerfume.java | 2 +- .../resources/detectors/assert_all/AssertAllNoStaticImport.java | 2 +- .../resources/detectors/assert_all/AssertAllStaticImport.java | 2 +- .../detectors/assert_all/AssertionsStaticWildcardImport.java | 2 +- .../detectors/parameterized_tests/ParameterizedTests.java | 2 +- .../parameterized_tests/ParameterizedTestsOwnAnnotation.java | 2 +- .../setup_and_teardown_methods/SetupAndTeardownMethods.java | 2 +- src/test/resources/detectors/swing/JFrameDispose.java | 2 +- src/test/resources/detectors/swing/SwingTimer.java | 2 +- .../InvokeLaterInvokeAndWaitNoPerfume.java | 2 +- .../InvokeLaterInvokeAndWaitNoStaticImport.java | 2 +- .../InvokeLaterInvokeAndWaitStaticImport.java | 2 +- .../InvokeLaterInvokeAndWaitStaticWildcardImport.java | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/test/resources/detectors/assert_all/AssertAllNoPerfume.java b/src/test/resources/detectors/assert_all/AssertAllNoPerfume.java index 67e8d13..92e283d 100644 --- a/src/test/resources/detectors/assert_all/AssertAllNoPerfume.java +++ b/src/test/resources/detectors/assert_all/AssertAllNoPerfume.java @@ -12,4 +12,4 @@ void test() { private void assertAll() { // imposter assertAll, should not be detected } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java b/src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java index 948cc09..8c6eaf4 100644 --- a/src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java +++ b/src/test/resources/detectors/assert_all/AssertAllNoStaticImport.java @@ -18,4 +18,4 @@ void testWithAssertAll() { () -> assertThat(k instanceof Integer) ); } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/assert_all/AssertAllStaticImport.java b/src/test/resources/detectors/assert_all/AssertAllStaticImport.java index 9e5a624..56f77d9 100644 --- a/src/test/resources/detectors/assert_all/AssertAllStaticImport.java +++ b/src/test/resources/detectors/assert_all/AssertAllStaticImport.java @@ -19,4 +19,4 @@ void testWithAssertAll() { () -> assertThat(k instanceof Integer) ); } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java b/src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java index a4c2915..4056748 100644 --- a/src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java +++ b/src/test/resources/detectors/assert_all/AssertionsStaticWildcardImport.java @@ -19,4 +19,4 @@ void testWithAssertAll() { () -> assertThat(k instanceof Integer) ); } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java b/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java index 0cbaa85..68576df 100644 --- a/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java +++ b/src/test/resources/detectors/parameterized_tests/ParameterizedTests.java @@ -24,4 +24,4 @@ void nonParameterized2() { void paramterized(int i) { assertThat(i).isInstanceOf(Integer.class); } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java b/src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java index 43355eb..b668607 100644 --- a/src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java +++ b/src/test/resources/detectors/parameterized_tests/ParameterizedTestsOwnAnnotation.java @@ -19,4 +19,4 @@ void paramterized() { void paramterized(int i) { assertThat(i).isInstanceOf(Integer.class); } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java b/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java index 14def1d..69bdf8a 100644 --- a/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java +++ b/src/test/resources/detectors/setup_and_teardown_methods/SetupAndTeardownMethods.java @@ -33,4 +33,4 @@ void tearDownAfterEach() { void tearDownAfterAll() { // teardown after all tests } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/swing/JFrameDispose.java b/src/test/resources/detectors/swing/JFrameDispose.java index 1df3213..78c2f43 100644 --- a/src/test/resources/detectors/swing/JFrameDispose.java +++ b/src/test/resources/detectors/swing/JFrameDispose.java @@ -19,4 +19,4 @@ public static void main(String[] args) { frame2.dispose(); frame3.dispose(); } -} \ No newline at end of file +} diff --git a/src/test/resources/detectors/swing/SwingTimer.java b/src/test/resources/detectors/swing/SwingTimer.java index 8b6692d..0a3ea24 100644 --- a/src/test/resources/detectors/swing/SwingTimer.java +++ b/src/test/resources/detectors/swing/SwingTimer.java @@ -19,4 +19,4 @@ public void actionPerformed(ActionEvent actionEvent) { var timer2 = new Timer(100, new ActionListener() {}); javax.swing.Timer timer3 = new javax.swing.Timer(100, () -> {}); } -} \ No newline at end of file +} 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 index 74853fb..6373668 100644 --- 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 @@ -14,4 +14,4 @@ public static void invokeLater() { public static void invokeAndWait() { } -} \ No newline at end of file +} 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 index f0a2286..88f8a5c 100644 --- 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 @@ -21,4 +21,4 @@ public static void main(String[] args) { public static void invokeLater(Runnable runnable) { } -} \ No newline at end of file +} 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 index f50f5ac..f9a00f3 100644 --- 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 @@ -14,4 +14,4 @@ public static void main(String[] args) { // perfume }); } -} \ No newline at end of file +} 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 index 47b4093..8358eae 100644 --- 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 @@ -13,4 +13,4 @@ public static void main(String[] args) { // perfume }); } -} \ No newline at end of file +} From a3f032bda5f80e9519c4d84ce42a0a0f6d4d3097 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Sun, 6 Oct 2024 13:03:29 +0200 Subject: [PATCH 07/15] bump version number to 0.3.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a5e9300..8eb644e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ de.jsilbereisen perfumator-java - 0.3.1 + 0.3.2 jar From 804de2ddafdf2a53103c251624eb78053be36188 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Sun, 6 Oct 2024 13:09:07 +0200 Subject: [PATCH 08/15] replace umlaute --- .../i18n/perfumes/SetupAndTeardownMethods_de.properties | 2 +- .../perfumator/i18n/perfumes/assertAll_de.properties | 2 +- .../perfumator/i18n/perfumes/jFrameDispose_de.properties | 2 +- .../perfumator/i18n/perfumes/parameterizedTest_de.properties | 2 +- .../perfumator/i18n/perfumes/swingTimer_de.properties | 2 +- .../perfumator/i18n/perfumes/threadSafeSwing_de.properties | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) 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 index 2612068..77cb5c5 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties @@ -1,3 +1,3 @@ ame=Setup und Teardown Methoden -description=Gemeinsamer Setup- und Teardown-Code sollte vorzugsweise innerhalb von Methoden durchgeführt werden, die entweder mit "@BeforeEach", "@BeforeAll", "@AfterEach" oder "@AfterAll" annotiert sind. Auf diese Weise lässt sich Boilerplate-Code in den einzelnen Tests erheblich reduzieren und der Fokus liegt stärker auf der eigentlichen Logik, die getestet wird. +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_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties index 8f0bd02..e7f59dc 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties @@ -1,3 +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 ausgeführt werden. Daher erhält der Benutzer die Ergebnisse aller Tests und nicht nur der Tests bis zur fehlgeschlagenen Assertions, die ansonsten die Ausführung des Tests beenden würde. +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://stackoverflow.com/questions/40796756/assertall-vs-multiple-assertions-in-junit5" \ No newline at end of file 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 index 4ce10b7..906c3e9 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties @@ -1,4 +1,4 @@ name=JFrame dispose -description=Anstelle der \"System.exit\" Methode, die die Java Virtual Machine vollständig beendet, schließt \"javax.swing.JFrame.dispose\" das JFrame Object und die ihm zugewiesenen Ressourcen auf kontrolliertere Weise. Wenn man die Anwendung auf diese Weise schließt, muss man sicherstellen, dass alle verbleibenden Threads programmatisch beendet werden. Dies mag zwar wie ein Nachteil erscheinen, bietet aber letztlich die größte Kontrolle, da laufende Prozesse kontrolliert beendet werden können. +description=Anstelle der \"System.exit\" Methode, die die Java Virtual Machine vollstaendig beendet, schließt \"javax.swing.JFrame.dispose\" das JFrame Object und die ihm zugewiesenen Ressourcen auf kontrolliertere Weise. Wenn man die Anwendung auf diese Weise schließt, muss man sicherstellen, dass alle verbleibenden Threads programmatisch beendet werden. Dies mag zwar wie ein Nachteil erscheinen, bietet aber letztlich die groeßte Kontrolle, da laufende Prozesse kontrolliert beendet werden koennen. source#1=Programmierung II - Uni Passau source#2="https://stackoverflow.com/questions/13360430/jframe-dispose-vs-system-exit" \ No newline at end of file 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 index d303823..40098b3 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties @@ -1,3 +1,3 @@ name=Parametrisierte Tests -description=Tests können 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. +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_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer_de.properties index e0f8280..b5ccde0 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/swingTimer_de.properties @@ -1,4 +1,4 @@ name=Swing Timer -description=Der Timer aus der \"javax.swing\" Bibliothek vereinfacht die Aktualisierung von Benutzeroberflächen 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 ausgeführt, das durch den ersten Parameter des Timers definiert ist. Der Timer führt den Code immer im Event-Dispatch-Thread aus. +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_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties index 6548f24..1c5d4ee 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties @@ -1,4 +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 übernehmen. Der Programmierer ist dafür 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, die wir als Argument entweder an die Methode \"SwingUtilities.invokeLater\" oder \"SwingUtilities.invokeAndWait\" übergeben. +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, die 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 From e504e9a5113fe6433e6f372f2ac2e4c6a69f980f Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Mon, 7 Oct 2024 10:03:07 +0200 Subject: [PATCH 09/15] fix typos --- .../de/jsilbereisen/perfumator/data/perfumes/Assert_all.json | 2 +- .../jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json | 2 +- .../perfumator/data/perfumes/Parameterized_test.json | 2 +- .../perfumator/data/perfumes/Thread_safe_swing.json | 2 +- .../perfumator/i18n/perfumes/threadSafeSwing_de.properties | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) 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 index 4e6954d..c2a9e63 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json @@ -1,6 +1,6 @@ { "name": "Assert all", - "description": "The \"org.junit.jupiter.api.assertAll\" is used 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.", + "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://stackoverflow.com/questions/40796756/assertall-vs-multiple-assertions-in-junit5"], 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 index e52f93a..32ab802 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json @@ -1,6 +1,6 @@ { "name": "JFrame dispose", - "description": "Instead of calling \"System.exit\", which completely terminates the java virtual machine, the \"javax.swing.JFrame.dispose\" 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 remaining 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.", + "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://stackoverflow.com/questions/13360430/jframe-dispose-vs-system-exit"], 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 index 4b5627a..c9a813b 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Parameterized_test.json @@ -1,6 +1,6 @@ { "name": "Parameterized test", - "description": "Tests can be annotated with the \"org.junit.jupiter.params.ParameterizedTest\" annotation. Such parameterized tests makes 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.", + "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/"], 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 index 625bcc2..2aa7e88 100644 --- 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 @@ -1,6 +1,6 @@ { "name": "Thread safe Swing", - "description": "Java Swing application 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.", + "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"], 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 index 1c5d4ee..d5f36c9 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/threadSafeSwing_de.properties @@ -1,4 +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, die wir als Argument entweder an die Methode \"SwingUtilities.invokeLater\" oder \"SwingUtilities.invokeAndWait\" uebergeben. +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 From a6b48f16ea4a38f8236744b4de6c2c08df9791d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Kn=C3=B6dlseder?= <53149143+chrisknedl@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:14:39 +0200 Subject: [PATCH 10/15] Increase version number Co-authored-by: Benedikt Fein --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8eb644e..aabbb0f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ de.jsilbereisen perfumator-java - 0.3.2 + 0.4.0 jar From 034cc5df94296cfbaaee761a5fd31712556e3d8b Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Tue, 8 Oct 2024 19:42:30 +0200 Subject: [PATCH 11/15] set language level to java 21 --- .../jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java b/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java index cb84d53..8e6eba1 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/PerfumeDetectionEngine.java @@ -120,7 +120,7 @@ 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); From f8481c5df1c4da55e5951a493364ab037958694b Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Tue, 8 Oct 2024 19:45:14 +0200 Subject: [PATCH 12/15] fix bug when classes from the same package are instantiated --- .../engine/detector/perfume/AssertAllDetector.java | 3 ++- .../engine/detector/perfume/SwingTimerDetector.java | 10 +++++++++- src/test/java/detectors/SwingTimerDetectorTest.java | 6 +++--- src/test/resources/detectors/swing/SwingTimer.java | 1 + 4 files changed, 15 insertions(+), 5 deletions(-) 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 index 394ff62..02e9dfd 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/AssertAllDetector.java @@ -29,6 +29,7 @@ public class AssertAllDetector implements Detector { 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) { @@ -52,7 +53,7 @@ public void setAnalysisContext(@Nullable JavaParserFacade 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("assertAll")) { + if (!expr.getNameAsString().contains(ASSERT_ALL)) { return false; } if (expr.getScope().isPresent()) { 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 index 1fe29d4..1ec6e9d 100644 --- a/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SwingTimerDetector.java +++ b/src/main/java/de/jsilbereisen/perfumator/engine/detector/perfume/SwingTimerDetector.java @@ -2,6 +2,7 @@ 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; @@ -47,7 +48,14 @@ public void setAnalysisContext(@Nullable JavaParserFacade analysisContext) { private List getNewTimerExpressions(@NotNull CompilationUnit astRoot) { return astRoot.findAll(ObjectCreationExpr.class, expr -> { - ResolvedType resolvedType = expr.calculateResolvedType(); + 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/test/java/detectors/SwingTimerDetectorTest.java b/src/test/java/detectors/SwingTimerDetectorTest.java index 97c8503..a4edfc1 100644 --- a/src/test/java/detectors/SwingTimerDetectorTest.java +++ b/src/test/java/detectors/SwingTimerDetectorTest.java @@ -50,16 +50,16 @@ void detect() { detection = detections.get(1); assertThat(detection.getDetectable()).isEqualTo(perfume); assertThat(detection.getTypeName()).isEqualTo("SwingTimer"); - assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(13, 18, 18, 10)); + 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(19, 22, 19, 60)); + 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(20, 36, 20, 71)); + assertThat(detection.getCodeRanges()).containsExactly(CodeRange.of(21, 36, 21, 71)); } } diff --git a/src/test/resources/detectors/swing/SwingTimer.java b/src/test/resources/detectors/swing/SwingTimer.java index 0a3ea24..9ab6492 100644 --- a/src/test/resources/detectors/swing/SwingTimer.java +++ b/src/test/resources/detectors/swing/SwingTimer.java @@ -7,6 +7,7 @@ public class SwingTimer { Timer t = new Timer(1000, new ActionListener() {}); + SomeClass c = new SomeClass(); public static void main(String[] args) { Timer timer1; From 72ffd45518baf4fca83f44826e9a6617f61a2c74 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Wed, 9 Oct 2024 11:18:40 +0200 Subject: [PATCH 13/15] better sources for some perfumes --- .../de/jsilbereisen/perfumator/data/perfumes/Assert_all.json | 2 +- .../jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index c2a9e63..ed3c54f 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/Assert_all.json @@ -3,7 +3,7 @@ "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://stackoverflow.com/questions/40796756/assertall-vs-multiple-assertions-in-junit5"], + "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 index 32ab802..3e5800c 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json +++ b/src/main/resources/de/jsilbereisen/perfumator/data/perfumes/JFrame_dispose.json @@ -3,7 +3,7 @@ "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://stackoverflow.com/questions/13360430/jframe-dispose-vs-system-exit"], + "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 From 6bcd8c01bebb4b3f6b4d9751a86e5f23aacbdc47 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Wed, 9 Oct 2024 11:37:02 +0200 Subject: [PATCH 14/15] almost forgot an 'n' --- .../i18n/perfumes/SetupAndTeardownMethods_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 77cb5c5..e8ed016 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/SetupAndTeardownMethods_de.properties @@ -1,3 +1,3 @@ -ame=Setup und Teardown Methoden +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 From 5e174b87a9ba12f1e7a59146434ac9e34b23ef72 Mon Sep 17 00:00:00 2001 From: Christoph Knoedlseder Date: Wed, 9 Oct 2024 12:03:35 +0200 Subject: [PATCH 15/15] update i18n --- .../perfumator/i18n/perfumes/assertAll_de.properties | 2 +- .../perfumator/i18n/perfumes/jFrameDispose_de.properties | 4 ++-- .../perfumator/i18n/perfumes/parameterizedTest_de.properties | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 index e7f59dc..738a22c 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/assertAll_de.properties @@ -1,3 +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://stackoverflow.com/questions/40796756/assertall-vs-multiple-assertions-in-junit5" \ No newline at end of file +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/jFrameDispose_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties index 906c3e9..fb3fa30 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/jFrameDispose_de.properties @@ -1,4 +1,4 @@ name=JFrame dispose -description=Anstelle der \"System.exit\" Methode, die die Java Virtual Machine vollstaendig beendet, schließt \"javax.swing.JFrame.dispose\" das JFrame Object und die ihm zugewiesenen Ressourcen auf kontrolliertere Weise. Wenn man die Anwendung auf diese Weise schließt, muss man sicherstellen, dass alle verbleibenden Threads programmatisch beendet werden. Dies mag zwar wie ein Nachteil erscheinen, bietet aber letztlich die groeßte Kontrolle, da laufende Prozesse kontrolliert beendet werden koennen. +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://stackoverflow.com/questions/13360430/jframe-dispose-vs-system-exit" \ No newline at end of file +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_de.properties b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties index 40098b3..1833891 100644 --- a/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties +++ b/src/main/resources/de/jsilbereisen/perfumator/i18n/perfumes/parameterizedTest_de.properties @@ -1,3 +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 +source#1=https://rules.sonarsource.com/java/RSPEC-5976/ \ No newline at end of file