Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AST Functionality #248

Merged
merged 28 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b60a56d
First implementation of the AST-Tester-functionality embedded in asse…
MarkusPaulsen Oct 14, 2022
6c459e6
Implementation of the first four test cases.
MarkusPaulsen Oct 14, 2022
40fc7bf
Fixed a small bug regarding a test case.
MarkusPaulsen Oct 14, 2022
906be7b
Implemented checking for Conditionals, Functions, Local Classes, Exce…
MarkusPaulsen Nov 1, 2022
7ed9fe2
Implemented part of the feedback.
MarkusPaulsen Jan 12, 2023
e832e2f
Implemented most of the rest of the feedback:
MarkusPaulsen Jan 17, 2023
80a4a25
Fixed code style with mvn spotless:apply
MarkusPaulsen Jan 17, 2023
22b9c08
Fixed code style with mvn spotless:apply; 2nd try
MarkusPaulsen Jan 17, 2023
c323275
Merge branch 'master' into AddASTFunctionality
MarkusPaulsen Jan 18, 2023
00fcaa4
Fixed the malformed HTML error of the SonarCloud Test.
MarkusPaulsen Jan 18, 2023
ead5d75
Implemented part of the new feedback.
MarkusPaulsen Feb 2, 2023
97addf9
Applied "mvn spotless:apply".
MarkusPaulsen Feb 2, 2023
9c8dd1e
Add API status for AST assertion framework
MaisiKoleni Feb 5, 2023
bca453a
Naming and Javadoc suggestions
MaisiKoleni Feb 5, 2023
0a1d6ab
Support nested test classes with the UserBased annotation
MaisiKoleni Feb 5, 2023
625a5f2
Implemented part of the third feedback iteration (test cases are not …
MarkusPaulsen Feb 16, 2023
059a770
Implemented rest of the third feedback iteration.
MarkusPaulsen May 8, 2023
c53a03c
Merge branch 'master' into AddASTFunctionality
MarkusPaulsen May 8, 2023
1af6f56
Removed Umlaut.
MarkusPaulsen May 8, 2023
25f1a5d
Fixed code style with mvn spotless:apply.
MarkusPaulsen May 8, 2023
da16407
Fixed test cases after applying mvn spotless:apply.
MarkusPaulsen May 8, 2023
48504dd
Implemented feedback of the third feedback iteration, which was missi…
MarkusPaulsen May 10, 2023
6624e6b
Implemented tests for the pom.xml file support.
MarkusPaulsen May 10, 2023
33fc1de
Implemented feedback of the fourth feedback iteration.
MarkusPaulsen May 19, 2023
be54b1f
Suggestion: simplify AST API and unify project sources search
MaisiKoleni Jun 20, 2023
bff635f
Improve and correct assertThatProjectSources() Javadoc
MaisiKoleni Jun 20, 2023
ae04080
Merge pull request #300 from ls1intum/AddASTFunctionalityVariant
MaisiKoleni Jun 21, 2023
82deee3
Merge branch 'master' into AddASTFunctionality
MaisiKoleni Jun 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
<artifactId>java-string-similarity</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.24.10</version>
</dependency>
<!-- For testing we use a test framework testing framework -->
<dependency>
<groupId>org.junit.platform</groupId>
Expand Down Expand Up @@ -265,6 +270,7 @@
<file>${project.build.outputDirectory}/org/junit/</file>
<file>${project.build.outputDirectory}/org/opentest4j/</file>
<file>${project.build.outputDirectory}/sun/</file>
<file>${project.build.outputDirectory}/com/github/javaparser/</file>
<!-- Required for de.tum.in.test.api.security.ArtemisSecurityConfigurationTest -->
<!-- <file>${project.build.outputDirectory}/abc/def/</file> -->
<file>${project.build.outputDirectory}/org/gradle/</file>
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/de/tum/in/test/api/AresConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package de.tum.in.test.api;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

import de.tum.in.test.api.util.ProjectSourcesFinder;

/**
* Utility class for global Ares configuration.
*
* @author Christian Femers
* @since 1.12.0
* @version 1.0.0
*/
@API(status = Status.MAINTAINED)
public class AresConfiguration {

private AresConfiguration() {
}

/**
* Returns the global Maven POM-file path used by Ares.
* <p>
* Defaults to the relative path <code>pom.xml</code>.
*
* @return the configured pom.xml file path as string
*/
public static String getPomXmlPath() {
return ProjectSourcesFinder.getPomXmlPath();
}

/**
* Sets the global Maven POM-file path to the given file path string.
* <p>
* Set by default to the relative path <code>pom.xml</code>.
*
* @param path the path as string, may be both relative or absolute
*/
public static void setPomXmlPath(String path) {
ProjectSourcesFinder.setPomXmlPath(path);
}

/**
* Returns the global Gradle build file path used by Ares.
* <p>
* Defaults to the relative path <code>build.gradle</code>.
*
* @return the configured gradle.build file path as string
*/
public static String getBuildGradlePath() {
return ProjectSourcesFinder.getBuildGradlePath();
}

/**
* Sets the global Gradle build file path to the given file path string.
* <p>
* Set by default to the relative path <code>build.gradle</code>.
*
* @param path the path as string, may be both relative or absolute
*/
public static void setBuildGradlePath(String path) {
ProjectSourcesFinder.setBuildGradlePath(path);
}
}
MaisiKoleni marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package de.tum.in.test.api.ast.asserting;

import static de.tum.in.test.api.localization.Messages.localized;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.fail;

import java.nio.file.*;
import java.util.*;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.assertj.core.api.AbstractAssert;

import com.github.javaparser.ParserConfiguration.LanguageLevel;
import com.github.javaparser.StaticJavaParser;

import de.tum.in.test.api.AresConfiguration;
import de.tum.in.test.api.ast.model.UnwantedNode;
import de.tum.in.test.api.ast.type.*;
import de.tum.in.test.api.util.ProjectSourcesFinder;

/**
* Checks whole Java files for unwanted nodes
*
* @author Markus Paulsen
* @since 1.12.0
* @version 1.0.0
*/
@API(status = Status.MAINTAINED)
public class UnwantedNodesAssert extends AbstractAssert<UnwantedNodesAssert, Path> {
MaisiKoleni marked this conversation as resolved.
Show resolved Hide resolved

/**
* The language level for the Java parser
*/
private final LanguageLevel level;

private UnwantedNodesAssert(Path path, LanguageLevel level) {
super(requireNonNull(path), UnwantedNodesAssert.class);
this.level = level;
if (!Files.isDirectory(path)) {
fail("The source directory %s does not exist", path); //$NON-NLS-1$
}
}

/**
* Creates an unwanted node assertion object for all project source files.
* <p>
* The project source directory gets extracted from the build configuration, and
* a <code>pom.xml</code> or <code>build.gradle</code> in the execution path is
* the default build configuration location. The configuration here is the same
* as the one in the structural tests and uses {@link AresConfiguration}.
*
* @return An unwanted node assertion object (for chaining)
*/
public static UnwantedNodesAssert assertThatProjectSources() {
var path = ProjectSourcesFinder.findProjectSourcesPath().orElseThrow(() -> new AssertionError("" //$NON-NLS-1$
+ "Could not find project sources folder." //$NON-NLS-1$
+ " Make sure the build file is configured correctly." //$NON-NLS-1$
+ " If it is not located in the execution folder directly," //$NON-NLS-1$
+ " set the location using AresConfiguration methods.")); //$NON-NLS-1$
return new UnwantedNodesAssert(path, null);
}

/**
* Creates an unwanted node assertion object for all source files at and below
* the given directory path.
*
* @param directory Path to a directory under which all files are considered
* @return An unwanted node assertion object (for chaining)
*/
public static UnwantedNodesAssert assertThatSourcesIn(Path directory) {
Objects.requireNonNull(directory, "The given source path must not be null."); //$NON-NLS-1$
return new UnwantedNodesAssert(directory, null);
}

/**
* Creates an unwanted node assertion object for all source files in the given
* package, including all of its sub-packages.
*
* @param packageName Java package name in the form of, e.g.,
* <code>de.tum.in.test.api</code>, which is resolved
* relative to the path of this UnwantedNodesAssert.
* @return An unwanted node assertion object (for chaining)
* @implNote The package is split at "." with the resulting segments being
* interpreted as directory structure. So
* <code>assertThatSourcesIn(Path.of("src/main/java")).withinPackage("net.example.test")</code>
* will yield an assert for all source files located at and below the
* relative path <code>src/main/java/net/example/test</code>
*/
public UnwantedNodesAssert withinPackage(String packageName) {
Objects.requireNonNull(packageName, "The package name must not be null."); //$NON-NLS-1$
var newPath = actual.resolve(Path.of("", packageName.split("\\."))); //$NON-NLS-1$ //$NON-NLS-2$
return new UnwantedNodesAssert(newPath, level);
}

/**
* Configures the language level used by the Java parser
*
* @param level The language level for the Java parser
* @return An unwanted node assertion object (for chaining)
*/
public UnwantedNodesAssert withLanguageLevel(LanguageLevel level) {
return new UnwantedNodesAssert(actual, level);
}

/**
* Verifies that the selected Java files do not contain any syntax tree nodes
* (statements, expressions, ...) of the given type.
*
* @param type Unwanted statements
* @return This unwanted node assertion object (for chaining)
* @see ClassType
* @see ConditionalType
* @see ExceptionHandlingType
* @see LoopType
*/
public UnwantedNodesAssert hasNo(Type type) {
if (level == null) {
failWithMessage("The 'level' is not set. Please use UnwantedNodesAssert.withLanguageLevel(LanguageLevel)."); //$NON-NLS-1$
}
StaticJavaParser.getParserConfiguration().setLanguageLevel(level);
Optional<String> errorMessage = UnwantedNode.getMessageForUnwantedNodesForAllFilesBelow(actual,
type.getNodeNameNodeMap());
errorMessage.ifPresent(unwantedNodeMessageForAllJavaFiles -> failWithMessage(
localized("ast.method.has_no") + System.lineSeparator() + unwantedNodeMessageForAllJavaFiles)); //$NON-NLS-1$
return this;
}
}
79 changes: 79 additions & 0 deletions src/main/java/de/tum/in/test/api/ast/model/JavaFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package de.tum.in.test.api.ast.model;

import static de.tum.in.test.api.localization.Messages.localized;

import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.slf4j.*;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;

/**
* Stores all required information about a Java file to be analyzed
*/
@API(status = Status.INTERNAL)
public class JavaFile {

private static final PathMatcher JAVAFILEMATCHER = FileSystems.getDefault().getPathMatcher("glob:*.java"); //$NON-NLS-1$
private static final Logger LOG = LoggerFactory.getLogger(JavaFile.class);

private final Path javaFilePath;
private final CompilationUnit javaFileAST;

public JavaFile(Path javaFilePath, CompilationUnit javaFileAST) {
this.javaFilePath = javaFilePath;
this.javaFileAST = javaFileAST;
}

public Path getJavaFilePath() {
return javaFilePath;
}

public CompilationUnit getJavaFileAST() {
return javaFileAST;
}

/**
* Turns the Java-file into an AST in case the provided path points to a
* Java-file
*
* @param pathOfFile Path to the Java-file
* @return The information of the Java-file packed into a JavaFile object (null
* if the file is not a Java-file)
*/
public static JavaFile convertFromFile(Path pathOfFile) {
if (!JAVAFILEMATCHER.matches(pathOfFile.getFileName())) {
return null;
}
try {
return new JavaFile(pathOfFile, StaticJavaParser.parse(pathOfFile));
} catch (IOException e) {
LOG.error("Error reading Java file '{}'", pathOfFile.toAbsolutePath(), e); //$NON-NLS-1$
throw new AssertionError(localized("ast.method.convert_from_file", pathOfFile.toAbsolutePath()));
}
}

/**
* Turns all Java-file below a certain path into ASTs
*
* @param pathOfDirectory Path to the highest analysis level
* @return List of Java-file information packed into JavaFile objects (empty
* list if the directory does not exist or if none of the files in the
* directory or its subdirectories is a Java-file)
*/
public static List<JavaFile> readFromDirectory(Path pathOfDirectory) {
try (Stream<Path> directoryContentStream = Files.walk(pathOfDirectory)) {
return directoryContentStream.map(JavaFile::convertFromFile).filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (IOException e) {
LOG.error("Error reading Java files in '{}'", pathOfDirectory.toAbsolutePath(), e); //$NON-NLS-1$
throw new AssertionError(localized("ast.method.read_from_directory", pathOfDirectory.toAbsolutePath()));
}
}
}
61 changes: 61 additions & 0 deletions src/main/java/de/tum/in/test/api/ast/model/NodePosition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package de.tum.in.test.api.ast.model;

import static de.tum.in.test.api.localization.Messages.localized;

import java.util.*;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

import com.github.javaparser.Position;
import com.github.javaparser.ast.Node;
import com.github.javaparser.utils.Pair;

/**
* Stores information about the beginning and the end of a node
*/
@API(status = Status.INTERNAL)
public class NodePosition implements Comparable<NodePosition> {

private static final Comparator<NodePosition> COMPARATOR = Comparator.comparing(NodePosition::getBeginLine)
.thenComparing(NodePosition::getBeginColumn);

private final boolean hasBegin;
private final Pair<Integer, Integer> begin;
private final boolean hasEnd;
private final Pair<Integer, Integer> end;

public NodePosition(Node node) {
Optional<Position> nodeBegin = node.getBegin();
hasBegin = nodeBegin.isPresent();
begin = hasBegin ? new Pair<>(nodeBegin.get().line, nodeBegin.get().column) : null;
Optional<Position> nodeEnd = node.getEnd();
hasEnd = nodeEnd.isPresent();
end = hasEnd ? new Pair<>(nodeEnd.get().line, nodeEnd.get().column) : null;
}

public int getBeginLine() {
return begin.a;
}

public int getBeginColumn() {
return begin.b;
}

@Override
public String toString() {
return localized("ast.method.to_string",
(hasBegin ? localized("ast.check.has_begin_end", begin.a, begin.b)
: localized("ast.check.not_has_begin")),
(hasEnd ? localized("ast.check.has_begin_end", end.a, end.b) : localized("ast.check.not_has_end")));
}

public static NodePosition getPositionOf(Node node) {
return new NodePosition(node);
}

@Override
public int compareTo(NodePosition o) {
return COMPARATOR.compare(this, o);
}
}
Loading