diff --git a/.github/workflows/complete-e2e.yml b/.github/workflows/complete-e2e.yml
index 332cc834e1..40cec33bc8 100644
--- a/.github/workflows/complete-e2e.yml
+++ b/.github/workflows/complete-e2e.yml
@@ -43,7 +43,7 @@ jobs:
node-version: "18"
- name: Build Assembly
- run: mvn -Pwith-report-viewer -DskipTests clean package assembly:single
+ run: mvn -DskipTests clean package assembly:single
- name: Rename Jar
run: mv cli/target/jplag-*-jar-with-dependencies.jar cli/target/jplag.jar
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 9629427c80..ab2bb989cd 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -7,6 +7,7 @@ on:
- "**/pom.xml"
- "**.java"
- "**.g4"
+ - "report-viewer/**"
pull_request:
types: [opened, synchronize, reopened]
paths:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 3fcc4b7e3c..f71b7250c2 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -38,6 +38,19 @@ jobs:
with:
node-version: "18"
+ - name: Set version of Report Viewer
+ shell: bash
+ run: |
+ VERSION=$(grep "" pom.xml | grep -oPm1 "(?<=)[^-|<]+")
+ MAJOR=$(echo $VERSION | cut -d '.' -f 1)
+ MINOR=$(echo $VERSION | cut -d '.' -f 2)
+ PATCH=$(echo $VERSION | cut -d '.' -f 3)
+ json=$(cat report-viewer/src/version.json)
+ json=$(echo "$json" | jq --arg MAJOR "$MAJOR" --arg MINOR "$MINOR" --arg PATCH "$PATCH" '.report_viewer_version |= { "major": $MAJOR | tonumber, "minor": $MINOR | tonumber, "patch": $PATCH | tonumber }')
+ echo "$json" > report-viewer/src/version.json
+ echo "Version of Report Viewer:"
+ cat report-viewer/src/version.json
+
- name: Build JPlag
run: mvn -Pwith-report-viewer -U -B clean package assembly:single
diff --git a/.github/workflows/report-viewer-demo.yml b/.github/workflows/report-viewer-demo.yml
index f7a747813e..b6f0c02c2d 100644
--- a/.github/workflows/report-viewer-demo.yml
+++ b/.github/workflows/report-viewer-demo.yml
@@ -102,7 +102,7 @@ jobs:
npm run build-demo
- name: Deploy 🚀
- uses: JamesIves/github-pages-deploy-action@v4.6.1
+ uses: JamesIves/github-pages-deploy-action@v4.6.8
with:
branch: gh-pages
folder: report-viewer/dist
diff --git a/.github/workflows/report-viewer-dev.yml b/.github/workflows/report-viewer-dev.yml
index ef2bfd8074..c9e5b5a0fa 100644
--- a/.github/workflows/report-viewer-dev.yml
+++ b/.github/workflows/report-viewer-dev.yml
@@ -27,7 +27,7 @@ jobs:
npm run build-dev
- name: Deploy 🚀
- uses: JamesIves/github-pages-deploy-action@v4.6.1
+ uses: JamesIves/github-pages-deploy-action@v4.6.8
with:
branch: gh-pages
folder: report-viewer/dist
diff --git a/.github/workflows/report-viewer.yml b/.github/workflows/report-viewer.yml
deleted file mode 100644
index dfa0689e3d..0000000000
--- a/.github/workflows/report-viewer.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-name: Report Viewer Deployment Workflow
-
-on:
- workflow_dispatch: # Use this to dispatch from the Actions Tab
- push:
- branches:
- - main
-
-jobs:
- build-and-deploy:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout 🛎️
- uses: actions/checkout@v4
-
- - uses: actions/setup-node@v4
- with:
- node-version: "18"
-
- - name: Set version of Report Viewer
- shell: bash
- run: |
- VERSION=$(grep "" pom.xml | grep -oPm1 "(?<=)[^-|<]+")
- MAJOR=$(echo $VERSION | cut -d '.' -f 1)
- MINOR=$(echo $VERSION | cut -d '.' -f 2)
- PATCH=$(echo $VERSION | cut -d '.' -f 3)
- json=$(cat report-viewer/src/version.json)
- json=$(echo "$json" | jq --arg MAJOR "$MAJOR" --arg MINOR "$MINOR" --arg PATCH "$PATCH" '.report_viewer_version |= { "major": $MAJOR | tonumber, "minor": $MINOR | tonumber, "patch": $PATCH | tonumber }')
- echo "$json" > report-viewer/src/version.json
- echo "Version of Report Viewer:"
- cat report-viewer/src/version.json
-
- - name: Install and Build 🔧
- working-directory: report-viewer
- run: |
- npm install
- npm run build-prod
-
- - name: Deploy 🚀
- uses: JamesIves/github-pages-deploy-action@v4.6.1
- with:
- branch: gh-pages
- folder: report-viewer/dist
diff --git a/README.md b/README.md
index 87d38e709a..a1ccf587e0 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,7 @@ Subsequence Match Merging
--neighbor-length=
Minimal length of neighboring matches to be merged (between 1 and minTokenMatch, default: 2).
-Subcommands (supported languages):
+Languages:
c
cpp
csharp
diff --git a/cli/pom.xml b/cli/pom.xml
index f376667af7..fe351d8d08 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -183,7 +183,7 @@
org.codehaus.mojoexec-maven-plugin
- 3.3.0
+ 3.5.0npm install
diff --git a/cli/src/main/java/de/jplag/cli/CLI.java b/cli/src/main/java/de/jplag/cli/CLI.java
index a5be417426..263a9021ed 100644
--- a/cli/src/main/java/de/jplag/cli/CLI.java
+++ b/cli/src/main/java/de/jplag/cli/CLI.java
@@ -49,6 +49,7 @@ public CLI(String[] args) {
*/
public void executeCli() throws ExitException, IOException {
logger.debug("Your version of JPlag is {}", JPlag.JPLAG_VERSION);
+ JPlagVersionChecker.printVersionNotification();
if (!this.inputHandler.parse()) {
CollectedLogger.setLogLevel(this.inputHandler.getCliOptions().advanced.logLevel);
@@ -110,6 +111,7 @@ public File runJPlag() throws ExitException, FileNotFoundException {
* @throws IOException If something went wrong with the internal server
*/
public void runViewer(File zipFile) throws IOException {
+ finalizeLogger(); // Prints the errors. The later finalizeLogger will print any errors logged after this point.
JPlagRunner.runInternalServer(zipFile, this.inputHandler.getCliOptions().advanced.port);
}
diff --git a/cli/src/main/java/de/jplag/cli/JPlagRunner.java b/cli/src/main/java/de/jplag/cli/JPlagRunner.java
index ee47fdba4a..3346eec627 100644
--- a/cli/src/main/java/de/jplag/cli/JPlagRunner.java
+++ b/cli/src/main/java/de/jplag/cli/JPlagRunner.java
@@ -43,7 +43,11 @@ public static void runInternalServer(File zipFile, int port) throws IOException
ReportViewer reportViewer = new ReportViewer(zipFile, port);
int actualPort = reportViewer.start();
logger.info("ReportViewer started on port http://localhost:{}", actualPort);
- Desktop.getDesktop().browse(URI.create("http://localhost:" + actualPort + "/"));
+ if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+ Desktop.getDesktop().browse(URI.create("http://localhost:" + actualPort + "/"));
+ } else {
+ logger.info("Could not open browser. You can open the Report Viewer here: http://localhost:{}/", actualPort);
+ }
System.out.println("Press Enter key to exit...");
System.in.read();
diff --git a/cli/src/main/java/de/jplag/cli/JPlagVersionChecker.java b/cli/src/main/java/de/jplag/cli/JPlagVersionChecker.java
new file mode 100644
index 0000000000..2f8a3ce5cd
--- /dev/null
+++ b/cli/src/main/java/de/jplag/cli/JPlagVersionChecker.java
@@ -0,0 +1,91 @@
+package de.jplag.cli;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Optional;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.JPlag;
+import de.jplag.reporting.reportobject.model.Version;
+
+/**
+ * Handles the check for newer versions.
+ */
+public class JPlagVersionChecker {
+ private static final String API_URL = "https://api.github.com/repos/jplag/JPlag/releases";
+ private static final Logger logger = LoggerFactory.getLogger(JPlagVersionChecker.class);
+ private static final String EXPECTED_VERSION_FORMAT = "v\\d\\.\\d\\.\\d+";
+ private static final String WARNING_UNABLE_TO_FETCH = "Unable to fetch version information. New version notification will not work.";
+ private static final String NEWER_VERSION_AVAILABLE = "There is a newer version ({}) available. You can download the newest version here: https://github.com/jplag/JPlag/releases";
+ private static final String UNEXPECTED_ERROR = "There was an unexpected error, when checking for new versions. Please report this on: https://github.com/jplag/JPlag/issues";
+
+ private JPlagVersionChecker() {
+
+ }
+
+ /**
+ * Prints a warning if a newer version is available on GitHub.
+ */
+ public static void printVersionNotification() {
+ Optional newerVersion = checkForNewVersion();
+ newerVersion.ifPresent(version -> logger.warn(NEWER_VERSION_AVAILABLE, version));
+ }
+
+ private static Optional checkForNewVersion() {
+ try {
+ JsonArray array = fetchApi();
+ Version newest = getNewestVersion(array);
+ Version current = JPlag.JPLAG_VERSION;
+
+ if (newest.compareTo(current) > 0) {
+ return Optional.of(newest);
+ }
+ } catch (IOException | URISyntaxException e) {
+ logger.info(WARNING_UNABLE_TO_FETCH);
+ } catch (Exception e) {
+ logger.warn(UNEXPECTED_ERROR, e);
+ }
+
+ return Optional.empty();
+ }
+
+ private static JsonArray fetchApi() throws IOException, URISyntaxException {
+ URL url = new URI(API_URL).toURL();
+ URLConnection connection = url.openConnection();
+
+ try (JsonReader reader = Json.createReader(connection.getInputStream())) {
+ return reader.readArray();
+ }
+ }
+
+ private static Version getNewestVersion(JsonArray apiResult) {
+ return apiResult.stream().map(JsonObject.class::cast).map(version -> version.getString("name"))
+ .filter(versionName -> versionName.matches(EXPECTED_VERSION_FORMAT)).limit(1).map(JPlagVersionChecker::parseVersion).findFirst()
+ .orElse(JPlag.JPLAG_VERSION);
+ }
+
+ /**
+ * Parses the version name.
+ * @param versionName The version name. The expected format is: v[major].[minor].[patch]
+ * @return The parsed version
+ */
+ private static Version parseVersion(String versionName) {
+ String withoutPrefix = versionName.substring(1);
+ String[] parts = withoutPrefix.split("\\.");
+ return parseVersionParts(parts);
+ }
+
+ private static Version parseVersionParts(String[] parts) {
+ return new Version(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
+ }
+}
diff --git a/cli/src/main/java/de/jplag/cli/logger/CollectedLogger.java b/cli/src/main/java/de/jplag/cli/logger/CollectedLogger.java
index b93e8cacf7..54fdb9d304 100644
--- a/cli/src/main/java/de/jplag/cli/logger/CollectedLogger.java
+++ b/cli/src/main/java/de/jplag/cli/logger/CollectedLogger.java
@@ -1,5 +1,6 @@
package de.jplag.cli.logger;
+import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -19,7 +20,6 @@ public class CollectedLogger extends AbstractLogger {
private static final String JPLAG_LOGGER_PREFIX = "de.jplag.";
private static final Level LOG_LEVEL_FOR_EXTERNAL_LIBRARIES = Level.ERROR;
private static final int MAXIMUM_MESSAGE_LENGTH = 32;
- private static final PrintStream TARGET_STREAM = System.out;
private static Level currentLogLevel = Level.INFO;
private final transient SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-hh:mm:ss_SSS");
@@ -148,11 +148,10 @@ private StringBuilder prepareLogOutput(LogEntry entry) {
private void printLogEntry(LogEntry entry) {
StringBuilder output = prepareLogOutput(entry);
- TARGET_STREAM.println(output);
+ DelayablePrinter.getInstance().println(output.toString());
if (entry.cause() != null) {
- entry.cause().printStackTrace(TARGET_STREAM);
+ this.printStackTrace(entry.cause());
}
- TARGET_STREAM.flush();
}
public static Level getLogLevel() {
@@ -162,4 +161,11 @@ public static Level getLogLevel() {
public static void setLogLevel(Level logLevel) {
currentLogLevel = logLevel;
}
+
+ private void printStackTrace(Throwable error) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ error.printStackTrace(new PrintStream(outputStream));
+ String stackTrace = outputStream.toString();
+ DelayablePrinter.getInstance().println(stackTrace);
+ }
}
diff --git a/cli/src/main/java/de/jplag/cli/logger/DelayablePrinter.java b/cli/src/main/java/de/jplag/cli/logger/DelayablePrinter.java
new file mode 100644
index 0000000000..f8b1d5fcee
--- /dev/null
+++ b/cli/src/main/java/de/jplag/cli/logger/DelayablePrinter.java
@@ -0,0 +1,73 @@
+package de.jplag.cli.logger;
+
+import java.io.PrintStream;
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+/**
+ * Prints strings to stdout. Provides the option to delay the actual printing.
+ */
+public class DelayablePrinter {
+ private final Queue outputQueue;
+ private PrintStream targetStream;
+
+ private boolean isDelayed;
+
+ private static final class InstanceHolder {
+ private static final DelayablePrinter instance = new DelayablePrinter();
+ }
+
+ /**
+ * Threadsafe singleton getter
+ * @return The singleton instance
+ */
+ public static DelayablePrinter getInstance() {
+ return InstanceHolder.instance;
+ }
+
+ private DelayablePrinter() {
+ this.outputQueue = new PriorityQueue<>();
+ this.targetStream = System.out;
+ this.isDelayed = false;
+ }
+
+ /**
+ * Prints the given string to the terminal appending a line-break
+ * @param output The string to print
+ */
+ public synchronized void println(String output) {
+ this.outputQueue.offer(output);
+ this.printQueue();
+ }
+
+ /**
+ * Stops printing to the terminal until {@link #resume()} is called
+ */
+ public synchronized void delay() {
+ this.isDelayed = true;
+ }
+
+ /**
+ * Resumes printing if {@link #delay()} was called
+ */
+ public synchronized void resume() {
+ this.isDelayed = false;
+ this.printQueue();
+ }
+
+ /**
+ * Changes the output stream messages are written to
+ */
+ public void setOutputStream(PrintStream printStream) {
+ this.targetStream = printStream;
+ }
+
+ private synchronized void printQueue() {
+ if (!this.isDelayed) {
+ while (!this.outputQueue.isEmpty()) {
+ this.targetStream.println(this.outputQueue.poll());
+ }
+ this.targetStream.flush();
+ }
+ }
+}
diff --git a/cli/src/main/java/de/jplag/cli/logger/IdleBar.java b/cli/src/main/java/de/jplag/cli/logger/IdleBar.java
index e5b7f0d3da..c9aee2da8c 100644
--- a/cli/src/main/java/de/jplag/cli/logger/IdleBar.java
+++ b/cli/src/main/java/de/jplag/cli/logger/IdleBar.java
@@ -7,12 +7,10 @@
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
-import de.jplag.logging.ProgressBar;
-
/**
* Prints an idle progress bar, that does not count upwards.
*/
-public class IdleBar implements ProgressBar {
+public class IdleBar extends LogDelayingProgressBar {
private final PrintStream output;
private final Thread runner;
@@ -27,6 +25,7 @@ public class IdleBar implements ProgressBar {
private boolean running = false;
public IdleBar(String text) {
+ super();
this.output = System.out;
this.runner = new Thread(this::run);
this.length = 50;
@@ -61,6 +60,7 @@ public void dispose() {
}
this.output.print('\r');
this.output.println(this.text + ": complete");
+ super.dispose();
}
private void run() {
diff --git a/cli/src/main/java/de/jplag/cli/logger/LogDelayingProgressBar.java b/cli/src/main/java/de/jplag/cli/logger/LogDelayingProgressBar.java
new file mode 100644
index 0000000000..b572086f86
--- /dev/null
+++ b/cli/src/main/java/de/jplag/cli/logger/LogDelayingProgressBar.java
@@ -0,0 +1,17 @@
+package de.jplag.cli.logger;
+
+import de.jplag.logging.ProgressBar;
+
+/**
+ * Superclass for progress bars, that delay the log output until the bar is done
+ */
+public abstract class LogDelayingProgressBar implements ProgressBar {
+ protected LogDelayingProgressBar() {
+ DelayablePrinter.getInstance().delay();
+ }
+
+ @Override
+ public void dispose() {
+ DelayablePrinter.getInstance().resume();
+ }
+}
diff --git a/cli/src/main/java/de/jplag/cli/logger/TongfeiProgressBar.java b/cli/src/main/java/de/jplag/cli/logger/TongfeiProgressBar.java
index 4305a497e0..d50ed1f181 100644
--- a/cli/src/main/java/de/jplag/cli/logger/TongfeiProgressBar.java
+++ b/cli/src/main/java/de/jplag/cli/logger/TongfeiProgressBar.java
@@ -1,14 +1,13 @@
package de.jplag.cli.logger;
-import de.jplag.logging.ProgressBar;
-
/**
* A ProgressBar, that used the tongfei progress bar library underneath, to show progress bars on the cli.
*/
-public class TongfeiProgressBar implements ProgressBar {
+public class TongfeiProgressBar extends LogDelayingProgressBar {
private final me.tongfei.progressbar.ProgressBar progressBar;
public TongfeiProgressBar(me.tongfei.progressbar.ProgressBar progressBar) {
+ super();
this.progressBar = progressBar;
}
@@ -20,5 +19,6 @@ public void step(int number) {
@Override
public void dispose() {
this.progressBar.close();
+ super.dispose();
}
}
diff --git a/cli/src/main/java/de/jplag/cli/options/CliOptions.java b/cli/src/main/java/de/jplag/cli/options/CliOptions.java
index 73f95f91bc..748a8f6c8b 100644
--- a/cli/src/main/java/de/jplag/cli/options/CliOptions.java
+++ b/cli/src/main/java/de/jplag/cli/options/CliOptions.java
@@ -54,7 +54,7 @@ public class CliOptions implements Runnable {
public String resultFile = "results";
@Option(names = {"-M", "--mode"}, description = "The mode of JPlag. One of: ${COMPLETION-CANDIDATES} (default: ${DEFAULT_VALUE})")
- public JPlagMode mode = JPlagMode.RUN;
+ public JPlagMode mode = JPlagMode.RUN_AND_VIEW;
@Option(names = {"--normalize"}, description = "Activate the normalization of tokens. Supported for languages: Java, C++.")
public boolean normalize = false;
diff --git a/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java b/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java
index 77389f3f39..4bc388a35d 100644
--- a/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java
+++ b/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java
@@ -1,5 +1,6 @@
package de.jplag.cli.picocli;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST_HEADING;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_DESCRIPTION_HEADING;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS;
@@ -70,6 +71,7 @@ private CommandLine buildCommandLine() {
}
return it;
}).collect(Collectors.joining(System.lineSeparator())) + System.lineSeparator());
+ cli.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST_HEADING, help -> "Languages:" + System.lineSeparator());
buildSubcommands().forEach(cli::addSubcommand);
diff --git a/cli/src/main/java/de/jplag/cli/server/ReportViewer.java b/cli/src/main/java/de/jplag/cli/server/ReportViewer.java
index 09d88857f8..32be964853 100644
--- a/cli/src/main/java/de/jplag/cli/server/ReportViewer.java
+++ b/cli/src/main/java/de/jplag/cli/server/ReportViewer.java
@@ -59,12 +59,14 @@ public int start() throws IOException {
throw new IllegalStateException("Server already started");
}
+ System.setProperty("java.net.preferIPv4Stack", "true");
+
int currentPort = this.port;
int remainingLookups = MAX_PORT_LOOKUPS;
BindException lastException = new BindException("Could not create server. Probably due to no free port found.");
while (server == null && remainingLookups-- > 0) {
try {
- server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), currentPort), 0);
+ server = HttpServer.create(new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), currentPort), 0);
} catch (BindException e) {
logger.info("Port {} is not available. Trying to find a different one.", currentPort);
lastException = e;
diff --git a/cli/src/test/java/de/jplag/cli/DebugTest.java b/cli/src/test/java/de/jplag/cli/DebugTest.java
new file mode 100644
index 0000000000..a6f22f74c0
--- /dev/null
+++ b/cli/src/test/java/de/jplag/cli/DebugTest.java
@@ -0,0 +1,27 @@
+package de.jplag.cli;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import de.jplag.cli.test.CliArgument;
+import de.jplag.cli.test.CliTest;
+import de.jplag.exceptions.ExitException;
+import de.jplag.options.JPlagOptions;
+
+class DebugTest extends CliTest {
+ @Test
+ void testDefaultDebug() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions();
+ assertFalse(options.debugParser());
+ }
+
+ @Test
+ void testSetDebug() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions(args -> args.with(CliArgument.DEBUG, true));
+ assertTrue(options.debugParser());
+ }
+}
diff --git a/cli/src/test/java/de/jplag/cli/ExcludeFileTest.java b/cli/src/test/java/de/jplag/cli/ExcludeFileTest.java
new file mode 100644
index 0000000000..e2cb116699
--- /dev/null
+++ b/cli/src/test/java/de/jplag/cli/ExcludeFileTest.java
@@ -0,0 +1,29 @@
+package de.jplag.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import de.jplag.cli.test.CliArgument;
+import de.jplag.cli.test.CliTest;
+import de.jplag.exceptions.ExitException;
+import de.jplag.options.JPlagOptions;
+
+class ExcludeFileTest extends CliTest {
+ private static final String EXCLUDE_FILE_NAME = "exclusions";
+
+ @Test
+ void testNoDefaultExcludeFile() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions();
+ assertNull(options.exclusionFileName());
+ }
+
+ @Test
+ void testSetExcludeFile() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions(args -> args.with(CliArgument.EXCLUDE_FILES, EXCLUDE_FILE_NAME));
+ assertEquals(EXCLUDE_FILE_NAME, options.exclusionFileName());
+ }
+}
diff --git a/cli/src/test/java/de/jplag/cli/LogLevelTest.java b/cli/src/test/java/de/jplag/cli/LogLevelTest.java
new file mode 100644
index 0000000000..9994de5fa9
--- /dev/null
+++ b/cli/src/test/java/de/jplag/cli/LogLevelTest.java
@@ -0,0 +1,28 @@
+package de.jplag.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+import org.slf4j.event.Level;
+
+import de.jplag.cli.test.CliArgument;
+import de.jplag.cli.test.CliTest;
+import de.jplag.exceptions.ExitException;
+
+class LogLevelTest extends CliTest {
+ private static final Level DEFAULT_LOG_LEVEL = Level.INFO;
+
+ @Test
+ void testDefaultLogLevel() throws IOException, ExitException {
+ Level level = runCliForLogLevel();
+ assertEquals(DEFAULT_LOG_LEVEL, level);
+ }
+
+ @Test
+ void testSetLogLevel() throws IOException, ExitException {
+ Level level = runCliForLogLevel(args -> args.with(CliArgument.LOG_LEVEL, Level.ERROR.name()));
+ assertEquals(Level.ERROR, level);
+ }
+}
diff --git a/cli/src/test/java/de/jplag/cli/ResultFileTest.java b/cli/src/test/java/de/jplag/cli/ResultFileTest.java
new file mode 100644
index 0000000000..888d166fd3
--- /dev/null
+++ b/cli/src/test/java/de/jplag/cli/ResultFileTest.java
@@ -0,0 +1,68 @@
+package de.jplag.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import org.junit.jupiter.api.Test;
+
+import de.jplag.cli.test.CliArgument;
+import de.jplag.cli.test.CliTest;
+import de.jplag.exceptions.ExitException;
+
+class ResultFileTest extends CliTest {
+ private static final String DEFAULT_RESULT_FILE = "results.zip";
+ private static final String TEST_RESULT_FILE = "customResults.zip";
+ private static final String TEST_RESULT_FILE_WITH_AVOIDANCE = "customResults(1).zip";
+ private static final String TEST_RESULT_FILE_WITHOUT_ZIP = "customResults";
+
+ @Test
+ void testDefaultResultFolder() throws IOException, ExitException {
+ String targetPath = runCliForTargetPath();
+ assertEquals(DEFAULT_RESULT_FILE, targetPath);
+ }
+
+ @Test
+ void testSetResultFolder() throws IOException, ExitException {
+ String targetPath = runCliForTargetPath(args -> args.with(CliArgument.RESULT_FILE, TEST_RESULT_FILE));
+ assertEquals(TEST_RESULT_FILE, targetPath);
+ }
+
+ @Test
+ void testSetResultFolderWithoutZip() throws IOException, ExitException {
+ String targetPath = runCliForTargetPath(args -> args.with(CliArgument.RESULT_FILE, TEST_RESULT_FILE_WITHOUT_ZIP));
+ assertEquals(TEST_RESULT_FILE, targetPath);
+ }
+
+ @Test
+ void testResultFileOverrideAvoidance() throws IOException, ExitException {
+ File testDir = Files.createTempDirectory("JPlagResultFileTest").toFile();
+ File targetFile = new File(testDir, TEST_RESULT_FILE);
+ File expectedTargetFile = new File(testDir, TEST_RESULT_FILE_WITH_AVOIDANCE);
+ targetFile.createNewFile();
+
+ String actualTargetPath = runCliForTargetPath(args -> args.with(CliArgument.RESULT_FILE, targetFile.getAbsolutePath()));
+
+ targetFile.delete();
+ testDir.delete();
+
+ assertEquals(expectedTargetFile.getAbsolutePath(), actualTargetPath);
+ }
+
+ @Test
+ void testResultFileOverwrite() throws IOException, ExitException {
+ File testDir = Files.createTempDirectory("JPlagResultFileTest").toFile();
+ File targetFile = new File(testDir, TEST_RESULT_FILE);
+ targetFile.createNewFile();
+
+ String actualTargetPath = runCliForTargetPath(
+ args -> args.with(CliArgument.RESULT_FILE, targetFile.getAbsolutePath()).with(CliArgument.OVERWRITE_RESULT_FILE, true));
+
+ targetFile.delete();
+ testDir.delete();
+
+ assertEquals(targetFile.getAbsolutePath(), actualTargetPath);
+ }
+}
diff --git a/cli/src/test/java/de/jplag/cli/SubdirectoryTest.java b/cli/src/test/java/de/jplag/cli/SubdirectoryTest.java
new file mode 100644
index 0000000000..e2d319551f
--- /dev/null
+++ b/cli/src/test/java/de/jplag/cli/SubdirectoryTest.java
@@ -0,0 +1,29 @@
+package de.jplag.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import de.jplag.cli.test.CliArgument;
+import de.jplag.cli.test.CliTest;
+import de.jplag.exceptions.ExitException;
+import de.jplag.options.JPlagOptions;
+
+class SubdirectoryTest extends CliTest {
+ private static final String TEST_SUBDIRECTORY = "dir";
+
+ @Test
+ void testDefaultSubdirectory() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions();
+ assertNull(options.subdirectoryName());
+ }
+
+ @Test
+ void testSetSubdirectory() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions(args -> args.with(CliArgument.SUBDIRECTORY, TEST_SUBDIRECTORY));
+ assertEquals(TEST_SUBDIRECTORY, options.subdirectoryName());
+ }
+}
diff --git a/cli/src/test/java/de/jplag/cli/SuffixesTest.java b/cli/src/test/java/de/jplag/cli/SuffixesTest.java
new file mode 100644
index 0000000000..ab095224b9
--- /dev/null
+++ b/cli/src/test/java/de/jplag/cli/SuffixesTest.java
@@ -0,0 +1,30 @@
+package de.jplag.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import de.jplag.cli.test.CliArgument;
+import de.jplag.cli.test.CliTest;
+import de.jplag.exceptions.ExitException;
+import de.jplag.options.JPlagOptions;
+
+class SuffixesTest extends CliTest {
+ private static final List JAVA_SUFFIXES = List.of(".java", ".JAVA");
+ private static final List CUSTOM_SUFFIXES = List.of(".j", ".jva");
+
+ @Test
+ void testDefaultSuffixes() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions();
+ assertEquals(JAVA_SUFFIXES, options.fileSuffixes());
+ }
+
+ @Test
+ void testSetSuffixes() throws IOException, ExitException {
+ JPlagOptions options = runCliForOptions(args -> args.with(CliArgument.SUFFIXES, CUSTOM_SUFFIXES.toArray(new String[0])));
+ assertEquals(CUSTOM_SUFFIXES, options.fileSuffixes());
+ }
+}
diff --git a/cli/src/test/java/de/jplag/cli/logger/DelayablePrinterTest.java b/cli/src/test/java/de/jplag/cli/logger/DelayablePrinterTest.java
new file mode 100644
index 0000000000..540f9ca083
--- /dev/null
+++ b/cli/src/test/java/de/jplag/cli/logger/DelayablePrinterTest.java
@@ -0,0 +1,51 @@
+package de.jplag.cli.logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+class DelayablePrinterTest {
+ private static final String TEST_MESSAGE = "Hello World";
+
+ private static ByteArrayOutputStream outputStream;
+
+ @BeforeAll
+ static void setUp() {
+ outputStream = new ByteArrayOutputStream();
+ DelayablePrinter.getInstance().setOutputStream(new PrintStream(outputStream));
+ }
+
+ @AfterAll
+ static void tearDown() {
+ DelayablePrinter.getInstance().setOutputStream(System.out);
+ }
+
+ @AfterEach
+ void cleanUpAfterTest() {
+ DelayablePrinter.getInstance().resume();
+ outputStream.reset();
+ }
+
+ @Test
+ void testDelay() {
+ DelayablePrinter.getInstance().delay();
+ DelayablePrinter.getInstance().println(TEST_MESSAGE);
+
+ Assertions.assertEquals("", outputStream.toString());
+
+ DelayablePrinter.getInstance().resume();
+
+ Assertions.assertEquals(TEST_MESSAGE + System.lineSeparator(), outputStream.toString());
+ }
+
+ @Test
+ void testDirectPrinting() {
+ DelayablePrinter.getInstance().println(TEST_MESSAGE);
+ Assertions.assertEquals(TEST_MESSAGE + System.lineSeparator(), outputStream.toString());
+ }
+}
diff --git a/cli/src/test/java/de/jplag/cli/test/CliArgument.java b/cli/src/test/java/de/jplag/cli/test/CliArgument.java
index 6ac22a524c..3b90977ab4 100644
--- a/cli/src/test/java/de/jplag/cli/test/CliArgument.java
+++ b/cli/src/test/java/de/jplag/cli/test/CliArgument.java
@@ -28,4 +28,10 @@ public record CliArgument(String name, boolean isPositional) {
public static CliArgument RESULT_FILE = new CliArgument<>("r", false);
public static CliArgument OVERWRITE_RESULT_FILE = new CliArgument<>("overwrite", false);
+
+ public static CliArgument LOG_LEVEL = new CliArgument<>("log-level", false);
+ public static CliArgument DEBUG = new CliArgument<>("d", false);
+
+ public static CliArgument SUBDIRECTORY = new CliArgument<>("subdirectory", false);
+ public static CliArgument EXCLUDE_FILES = new CliArgument<>("x", false);
}
diff --git a/cli/src/test/java/de/jplag/cli/test/CliResult.java b/cli/src/test/java/de/jplag/cli/test/CliResult.java
index 4978a5195e..7051a6061f 100644
--- a/cli/src/test/java/de/jplag/cli/test/CliResult.java
+++ b/cli/src/test/java/de/jplag/cli/test/CliResult.java
@@ -1,6 +1,8 @@
package de.jplag.cli.test;
+import org.slf4j.event.Level;
+
import de.jplag.options.JPlagOptions;
-public record CliResult(JPlagOptions jPlagOptions, String targetPath) {
+public record CliResult(JPlagOptions jPlagOptions, String targetPath, Level logLevel) {
}
diff --git a/cli/src/test/java/de/jplag/cli/test/CliTest.java b/cli/src/test/java/de/jplag/cli/test/CliTest.java
index d26a1fb477..bff7528a60 100644
--- a/cli/src/test/java/de/jplag/cli/test/CliTest.java
+++ b/cli/src/test/java/de/jplag/cli/test/CliTest.java
@@ -11,9 +11,11 @@
import org.junit.jupiter.api.BeforeEach;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
+import org.slf4j.event.Level;
import de.jplag.JPlagResult;
import de.jplag.cli.*;
+import de.jplag.cli.logger.CollectedLogger;
import de.jplag.cli.picocli.CliInputHandler;
import de.jplag.exceptions.ExitException;
import de.jplag.options.JPlagOptions;
@@ -105,6 +107,29 @@ protected String runCliForTargetPath(Consumer additionalOpti
return runCli(additionalOptionsBuilder).targetPath();
}
+ /**
+ * Runs the cli
+ * @return The log level set by the cli
+ * @throws ExitException If JPlag throws an exception
+ * @throws IOException If JPlag throws an exception
+ * @see #runCli()
+ */
+ protected Level runCliForLogLevel() throws IOException, ExitException {
+ return runCli().logLevel();
+ }
+
+ /**
+ * Runs the cli using custom options
+ * @param additionalOptionsBuilder May modify the {@link CliArgumentBuilder} object to set custom options for this run.
+ * @return The log level set by the cli
+ * @throws ExitException If JPlag throws an exception
+ * @throws IOException If JPlag throws an exception
+ * @see #runCli()
+ */
+ protected Level runCliForLogLevel(Consumer additionalOptionsBuilder) throws IOException, ExitException {
+ return runCli(additionalOptionsBuilder).logLevel();
+ }
+
/**
* Runs the cli
* @return The options returned by the cli
@@ -129,7 +154,7 @@ protected CliResult runCli(Consumer additionalOptionsBuilder
String targetPath = (String) getWritableFileMethod.invoke(cli);
- return new CliResult(optionsBuilder.buildOptions(), targetPath);
+ return new CliResult(optionsBuilder.buildOptions(), targetPath, CollectedLogger.getLogLevel());
} catch (IllegalAccessException | InvocationTargetException e) {
Assumptions.abort("Could not access private field in CLI for test.");
return null; // will not be executed
diff --git a/core/pom.xml b/core/pom.xml
index 0d1f9480cd..ce0214dc22 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -37,6 +37,12 @@
${revision}test
+
+ io.soabase.record-builder
+ record-builder-processor
+ 43
+ provided
+
@@ -46,5 +52,15 @@
src/main/resources
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.10.1
+
+ src/main/java;target/generated-sources/annotations
+
+
+
diff --git a/core/src/main/java/de/jplag/Submission.java b/core/src/main/java/de/jplag/Submission.java
index 92c8fd5c8d..5610a19873 100644
--- a/core/src/main/java/de/jplag/Submission.java
+++ b/core/src/main/java/de/jplag/Submission.java
@@ -24,7 +24,7 @@
import org.slf4j.LoggerFactory;
import de.jplag.exceptions.LanguageException;
-import de.jplag.normalization.TokenStringNormalizer;
+import de.jplag.normalization.TokenSequenceNormalizer;
import de.jplag.options.JPlagOptions;
/**
@@ -259,7 +259,7 @@ private static File createErrorDirectory(String... subdirectoryNames) {
*/
void normalize() {
List originalOrder = getOrder(tokenList);
- tokenList = TokenStringNormalizer.normalize(tokenList);
+ tokenList = TokenSequenceNormalizer.normalize(tokenList);
List normalizedOrder = getOrder(tokenList);
logger.debug("original line order: {}", originalOrder);
diff --git a/core/src/main/java/de/jplag/SubmissionSet.java b/core/src/main/java/de/jplag/SubmissionSet.java
index f7c1438bbb..884d800450 100644
--- a/core/src/main/java/de/jplag/SubmissionSet.java
+++ b/core/src/main/java/de/jplag/SubmissionSet.java
@@ -99,6 +99,11 @@ public List getInvalidSubmissions() {
return invalidSubmissions;
}
+ /**
+ * Normalizes the token sequences of all submissions (including basecode). This makes the token sequence invariant to
+ * dead code insertion and independent statement reordering by removing dead tokens and optionally reordering tokens to
+ * a deterministic order.
+ */
public void normalizeSubmissions() {
if (baseCodeSubmission != null) {
baseCodeSubmission.normalize();
diff --git a/core/src/main/java/de/jplag/normalization/MultipleEdge.java b/core/src/main/java/de/jplag/normalization/MultipleEdge.java
index b10fda2ea5..732d3d3cf2 100644
--- a/core/src/main/java/de/jplag/normalization/MultipleEdge.java
+++ b/core/src/main/java/de/jplag/normalization/MultipleEdge.java
@@ -6,7 +6,7 @@
import de.jplag.semantics.Variable;
/**
- * Models a multiple edge in the normalization graph. Contains multiple edges.
+ * Models multiple edges between two nodes in the normalization graph.
*/
class MultipleEdge {
private final Set edges;
diff --git a/core/src/main/java/de/jplag/normalization/NormalizationGraphConstructor.java b/core/src/main/java/de/jplag/normalization/NormalizationGraph.java
similarity index 78%
rename from core/src/main/java/de/jplag/normalization/NormalizationGraphConstructor.java
rename to core/src/main/java/de/jplag/normalization/NormalizationGraph.java
index fc995e69d7..e07c873b50 100644
--- a/core/src/main/java/de/jplag/normalization/NormalizationGraphConstructor.java
+++ b/core/src/main/java/de/jplag/normalization/NormalizationGraph.java
@@ -14,21 +14,28 @@
import de.jplag.semantics.Variable;
/**
- * Constructs the normalization graph.
+ * Token normalization graph, which is a directed graph based on nodes of type {@link Statement} and edges of type
+ * {@link MultipleEdge}. This class class inherits from {@link SimpleDirectedGraph} to provide a data structure for the
+ * token sequence normalization.
*/
-class NormalizationGraphConstructor {
- private final SimpleDirectedGraph graph;
+public class NormalizationGraph extends SimpleDirectedGraph {
+
+ private static final long serialVersionUID = -8407465274643809647L; // generated
+
private int bidirectionalBlockDepth;
- private final Collection fullPositionSignificanceIncoming;
- private Statement lastFullPositionSignificance;
- private Statement lastPartialPositionSignificance;
- private final Map> variableReads;
- private final Map> variableWrites;
- private final Set inCurrentBidirectionalBlock;
- private Statement current;
-
- NormalizationGraphConstructor(List tokens) {
- graph = new SimpleDirectedGraph<>(MultipleEdge.class);
+ private final transient Collection fullPositionSignificanceIncoming;
+ private transient Statement lastFullPositionSignificance;
+ private transient Statement lastPartialPositionSignificance;
+ private final transient Map> variableReads;
+ private final transient Map> variableWrites;
+ private final transient Set inCurrentBidirectionalBlock;
+ private transient Statement current;
+
+ /**
+ * Creates a new normalization graph.
+ */
+ public NormalizationGraph(List tokens) {
+ super(MultipleEdge.class);
bidirectionalBlockDepth = 0;
fullPositionSignificanceIncoming = new ArrayList<>();
variableReads = new HashMap<>();
@@ -45,12 +52,8 @@ class NormalizationGraphConstructor {
addStatement(builderForCurrent.build());
}
- SimpleDirectedGraph get() {
- return graph;
- }
-
private void addStatement(Statement statement) {
- graph.addVertex(statement);
+ addVertex(statement);
this.current = statement;
processBidirectionalBlock();
processFullPositionSignificance();
@@ -123,10 +126,10 @@ private void processWrites() {
* @param cause the variable that caused the edge, may be null
*/
private void addIncomingEdgeToCurrent(Statement start, EdgeType type, Variable cause) {
- MultipleEdge multipleEdge = graph.getEdge(start, current);
+ MultipleEdge multipleEdge = getEdge(start, current);
if (multipleEdge == null) {
multipleEdge = new MultipleEdge();
- graph.addEdge(start, current, multipleEdge);
+ addEdge(start, current, multipleEdge);
}
multipleEdge.addEdge(type, cause);
}
@@ -135,4 +138,5 @@ private void addVariableToMap(Map> variableMap,
variableMap.putIfAbsent(variable, new ArrayList<>());
variableMap.get(variable).add(current);
}
+
}
diff --git a/core/src/main/java/de/jplag/normalization/Statement.java b/core/src/main/java/de/jplag/normalization/Statement.java
index a749a57740..81f9b33640 100644
--- a/core/src/main/java/de/jplag/normalization/Statement.java
+++ b/core/src/main/java/de/jplag/normalization/Statement.java
@@ -8,7 +8,7 @@
import de.jplag.semantics.CodeSemantics;
/**
- * Models statements, which are the nodes of the normalization graph.
+ * Models statements, which are the nodes of the normalization graph. A statement refers to one or more tokens.
*/
class Statement implements Comparable {
@@ -16,6 +16,11 @@ class Statement implements Comparable {
private final int lineNumber;
private final CodeSemantics semantics;
+ /**
+ * Constructs a new Statement.
+ * @param tokens the list of tokens that represent this statement.
+ * @param lineNumber the line number where this statement occurs in the source code.
+ */
Statement(List tokens, int lineNumber) {
this.tokens = Collections.unmodifiableList(tokens);
this.lineNumber = lineNumber;
@@ -30,8 +35,8 @@ CodeSemantics semantics() {
return semantics;
}
- void markKeep() {
- semantics.markKeep();
+ void markAsCritical() {
+ semantics.markAsCritical();
}
private int tokenOrdinal(Token token) {
diff --git a/core/src/main/java/de/jplag/normalization/StatementBuilder.java b/core/src/main/java/de/jplag/normalization/StatementBuilder.java
index eef5d0c821..f9f3bd5008 100644
--- a/core/src/main/java/de/jplag/normalization/StatementBuilder.java
+++ b/core/src/main/java/de/jplag/normalization/StatementBuilder.java
@@ -13,6 +13,10 @@ class StatementBuilder {
private final List tokens;
private final int lineNumber;
+ /**
+ * Constructs a new StatementBuilder.
+ * @param lineNumber the line number where the statement starts in the source code.
+ */
StatementBuilder(int lineNumber) {
this.lineNumber = lineNumber;
this.tokens = new ArrayList<>();
diff --git a/core/src/main/java/de/jplag/normalization/TokenStringNormalizer.java b/core/src/main/java/de/jplag/normalization/TokenSequenceNormalizer.java
similarity index 55%
rename from core/src/main/java/de/jplag/normalization/TokenStringNormalizer.java
rename to core/src/main/java/de/jplag/normalization/TokenSequenceNormalizer.java
index 8ffafffbf7..9a1256300e 100644
--- a/core/src/main/java/de/jplag/normalization/TokenStringNormalizer.java
+++ b/core/src/main/java/de/jplag/normalization/TokenSequenceNormalizer.java
@@ -1,7 +1,6 @@
package de.jplag.normalization;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
@@ -9,29 +8,35 @@
import java.util.stream.Collectors;
import org.jgrapht.Graphs;
-import org.jgrapht.graph.SimpleDirectedGraph;
import de.jplag.Token;
/**
* Performs token sequence normalization.
*/
-public class TokenStringNormalizer {
+public final class TokenSequenceNormalizer {
- private TokenStringNormalizer() {
+ private TokenSequenceNormalizer() {
+ // private constructor for non-instantiability.
}
/**
* Performs token sequence normalization. Tokens representing dead code have been eliminated and tokens representing
- * subsequent independent statements have been put in a fixed order. Works by first constructing a Normalization Graph
- * and then turning it back into a token sequence.
+ * subsequent independent statements have been put in a fixed order if sorting is true. Works by first constructing a
+ * Normalization Graph and then turning it back into a token sequence. For more information refer to the
+ * corresponding paper
* @param tokens The original token sequence, remains unaltered.
- * @return The normalized token sequence as unmodifiable list.
+ * @return The normalized token sequence.
*/
public static List normalize(List tokens) {
- SimpleDirectedGraph normalizationGraph = new NormalizationGraphConstructor(tokens).get();
+ NormalizationGraph graph = new NormalizationGraph(tokens);
+ propagateCriticalityStatus(graph);
+ return normalizeWithSorting(tokens, graph);
+ }
+
+ // Add tokens in normalized original order, removing dead tokens
+ private static List normalizeWithSorting(List tokens, NormalizationGraph normalizationGraph) {
List normalizedTokens = new ArrayList<>(tokens.size());
- spreadKeep(normalizationGraph);
PriorityQueue roots = normalizationGraph.vertexSet().stream() //
.filter(v -> !Graphs.vertexHasPredecessors(normalizationGraph, v)) //
.collect(Collectors.toCollection(PriorityQueue::new));
@@ -39,7 +44,7 @@ public static List normalize(List tokens) {
PriorityQueue newRoots = new PriorityQueue<>();
do {
Statement statement = roots.poll();
- if (statement.semantics().keep()) {
+ if (statement.semantics().isCritical()) {
normalizedTokens.addAll(statement.tokens());
}
for (Statement successor : Graphs.successorListOf(normalizationGraph, statement)) {
@@ -51,26 +56,29 @@ public static List normalize(List tokens) {
} while (!roots.isEmpty());
roots = newRoots;
}
- return Collections.unmodifiableList(normalizedTokens);
+ return normalizedTokens;
}
/**
- * Spread keep status to every node that does not represent dead code. Nodes without keep status are later eliminated.
+ * Spread criticality status to every node that does not represent dead code. Nodes without keep criticality are later
+ * eliminated (dead nodes). Before calling this method, only the statements that directly affect the behavior are marked
+ * as critical. After calling this method, this also holds true for statement that (transitively) depend (read/write) on
+ * the critical ones.
*/
- private static void spreadKeep(SimpleDirectedGraph normalizationGraph) {
+ private static void propagateCriticalityStatus(NormalizationGraph normalizationGraph) {
Queue visit = new LinkedList<>(normalizationGraph.vertexSet().stream() //
- .filter(tl -> tl.semantics().keep()).toList());
+ .filter(tl -> tl.semantics().isCritical()).toList());
while (!visit.isEmpty()) {
Statement current = visit.remove();
for (Statement predecessor : Graphs.predecessorListOf(normalizationGraph, current)) { // performance of iteration?
- if (!predecessor.semantics().keep() && normalizationGraph.getEdge(predecessor, current).isVariableFlow()) {
- predecessor.markKeep();
+ if (!predecessor.semantics().isCritical() && normalizationGraph.getEdge(predecessor, current).isVariableFlow()) {
+ predecessor.markAsCritical();
visit.add(predecessor);
}
}
for (Statement successor : Graphs.successorListOf(normalizationGraph, current)) {
- if (!successor.semantics().keep() && normalizationGraph.getEdge(current, successor).isVariableReverseFlow()) {
- successor.markKeep();
+ if (!successor.semantics().isCritical() && normalizationGraph.getEdge(current, successor).isVariableReverseFlow()) {
+ successor.markAsCritical();
visit.add(successor);
}
}
diff --git a/core/src/main/java/de/jplag/options/JPlagOptions.java b/core/src/main/java/de/jplag/options/JPlagOptions.java
index 7dd35c0660..2a27e53c6c 100644
--- a/core/src/main/java/de/jplag/options/JPlagOptions.java
+++ b/core/src/main/java/de/jplag/options/JPlagOptions.java
@@ -3,8 +3,6 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -25,6 +23,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.soabase.recordbuilder.core.RecordBuilder;
/**
* This record defines the options to configure {@link JPlag}.
@@ -49,6 +48,7 @@
* @param clusteringOptions Clustering options
* @param debugParser If true, submissions that cannot be parsed will be stored in a separate directory.
*/
+@RecordBuilder()
public record JPlagOptions(@JsonSerialize(using = LanguageSerializer.class) Language language,
@JsonProperty("min_token_match") Integer minimumTokenMatch, @JsonProperty("submission_directories") Set submissionDirectories,
@JsonProperty("old_directories") Set oldSubmissionDirectories, @JsonProperty("base_directory") File baseCodeSubmissionDirectory,
@@ -56,15 +56,24 @@ public record JPlagOptions(@JsonSerialize(using = LanguageSerializer.class) Lang
@JsonProperty("exclusion_file_name") String exclusionFileName, @JsonProperty("similarity_metric") SimilarityMetric similarityMetric,
@JsonProperty("similarity_threshold") double similarityThreshold, @JsonProperty("max_comparisons") int maximumNumberOfComparisons,
@JsonProperty("cluster") ClusteringOptions clusteringOptions, boolean debugParser, @JsonProperty("merging") MergingOptions mergingOptions,
- @JsonProperty("normalize") boolean normalize) {
+ @JsonProperty("normalize") boolean normalize) implements JPlagOptionsBuilder.With {
public static final double DEFAULT_SIMILARITY_THRESHOLD = 0;
- public static final int DEFAULT_SHOWN_COMPARISONS = 500;
+ public static final int DEFAULT_SHOWN_COMPARISONS = 2500;
public static final int SHOW_ALL_COMPARISONS = 0;
public static final SimilarityMetric DEFAULT_SIMILARITY_METRIC = SimilarityMetric.AVG;
- public static final Charset CHARSET = StandardCharsets.UTF_8;
public static final String ERROR_FOLDER = "errors";
+ /**
+ * @param lang The new language
+ * @return The modified options
+ * @deprecated Use withLanguage instead
+ */
+ @Deprecated(forRemoval = true)
+ public JPlagOptions withLanguageOption(Language lang) {
+ return this.withLanguage(lang);
+ }
+
private static final Logger logger = LoggerFactory.getLogger(JPlagOptions.class);
public JPlagOptions(Language language, Set submissionDirectories, Set oldSubmissionDirectories) {
@@ -93,96 +102,6 @@ public JPlagOptions(Language language, Integer minimumTokenMatch, Set subm
this.normalize = normalize;
}
- public JPlagOptions withLanguageOption(Language language) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withDebugParser(boolean debugParser) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withFileSuffixes(List fileSuffixes) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withSimilarityThreshold(double similarityThreshold) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withMaximumNumberOfComparisons(int maximumNumberOfComparisons) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withSimilarityMetric(SimilarityMetric similarityMetric) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withMinimumTokenMatch(Integer minimumTokenMatch) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withExclusionFileName(String exclusionFileName) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withSubmissionDirectories(Set submissionDirectories) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withOldSubmissionDirectories(Set oldSubmissionDirectories) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withBaseCodeSubmissionDirectory(File baseCodeSubmissionDirectory) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withSubdirectoryName(String subdirectoryName) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withClusteringOptions(ClusteringOptions clusteringOptions) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withMergingOptions(MergingOptions mergingOptions) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
- public JPlagOptions withNormalize(boolean normalize) {
- return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory,
- subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons,
- clusteringOptions, debugParser, mergingOptions, normalize);
- }
-
public boolean hasBaseCode() {
return baseCodeSubmissionDirectory != null;
}
diff --git a/core/src/main/java/de/jplag/reporting/jsonfactory/BaseCodeReportWriter.java b/core/src/main/java/de/jplag/reporting/jsonfactory/BaseCodeReportWriter.java
index e5217330dd..1f4a9a279d 100644
--- a/core/src/main/java/de/jplag/reporting/jsonfactory/BaseCodeReportWriter.java
+++ b/core/src/main/java/de/jplag/reporting/jsonfactory/BaseCodeReportWriter.java
@@ -66,9 +66,11 @@ private BaseCodeMatch convertToBaseCodeMatch(Submission submission, Match match,
List tokens = submission.getTokenList().subList(takeLeft ? match.startOfFirst() : match.startOfSecond(),
(takeLeft ? match.endOfFirst() : match.endOfSecond()) + 1);
- Comparator super Token> lineComparator = Comparator.comparingInt(Token::getLine);
- Token start = tokens.stream().min(lineComparator).orElseThrow();
- Token end = tokens.stream().max(lineComparator).orElseThrow();
+ Comparator super Token> lineStartComparator = Comparator.comparingInt(Token::getLine).thenComparingInt(Token::getColumn);
+ Comparator super Token> lineEndComparator = Comparator.comparingInt(Token::getLine)
+ .thenComparingInt((Token t) -> t.getColumn() + t.getLength());
+ Token start = tokens.stream().min(lineStartComparator).orElseThrow();
+ Token end = tokens.stream().max(lineEndComparator).orElseThrow();
CodePosition startPosition = new CodePosition(start.getLine(), start.getColumn() - 1,
takeLeft ? match.startOfFirst() : match.startOfSecond());
diff --git a/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java b/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java
index f8732dac44..35ef87de59 100644
--- a/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java
+++ b/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java
@@ -99,12 +99,14 @@ private Match convertMatchToReportMatch(JPlagComparison comparison, de.jplag.Mat
List tokensFirst = comparison.firstSubmission().getTokenList().subList(match.startOfFirst(), match.endOfFirst() + 1);
List tokensSecond = comparison.secondSubmission().getTokenList().subList(match.startOfSecond(), match.endOfSecond() + 1);
- Comparator super Token> lineComparator = Comparator.comparingInt(Token::getLine).thenComparingInt(Token::getColumn);
-
- Token startOfFirst = tokensFirst.stream().min(lineComparator).orElseThrow();
- Token endOfFirst = tokensFirst.stream().max(lineComparator).orElseThrow();
- Token startOfSecond = tokensSecond.stream().min(lineComparator).orElseThrow();
- Token endOfSecond = tokensSecond.stream().max(lineComparator).orElseThrow();
+ Comparator super Token> lineStartComparator = Comparator.comparingInt(Token::getLine).thenComparingInt(Token::getColumn);
+ Comparator super Token> lineEndComparator = Comparator.comparingInt(Token::getLine)
+ .thenComparingInt((Token t) -> t.getColumn() + t.getLength());
+
+ Token startOfFirst = tokensFirst.stream().min(lineStartComparator).orElseThrow();
+ Token endOfFirst = tokensFirst.stream().max(lineEndComparator).orElseThrow();
+ Token startOfSecond = tokensSecond.stream().min(lineStartComparator).orElseThrow();
+ Token endOfSecond = tokensSecond.stream().max(lineEndComparator).orElseThrow();
String firstFileName = FilePathUtil.getRelativeSubmissionPath(startOfFirst.getFile(), comparison.firstSubmission(), submissionToIdFunction)
.toString();
diff --git a/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java b/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
index bb1fb4d399..a1520f92bb 100644
--- a/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
+++ b/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
@@ -149,7 +149,7 @@ private void writeOverview(JPlagResult result) {
missingComparisons);
OverviewReport overviewReport = new OverviewReport(REPORT_VIEWER_VERSION, folders.stream().map(File::getPath).toList(), // submissionFolderPath
baseCodePath, // baseCodeFolderPath
- result.getOptions().language().getName(), // language
+ result.getOptions().language().getIdentifier(), // language
result.getOptions().fileSuffixes(), // fileExtensions
submissionNameToIdMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)), // submissionIds
submissionNameToNameToComparisonFileName, // result.getOptions().getMinimumTokenMatch(),
diff --git a/core/src/main/java/de/jplag/reporting/reportobject/model/Version.java b/core/src/main/java/de/jplag/reporting/reportobject/model/Version.java
index ddf0fe1a99..7442e310d7 100644
--- a/core/src/main/java/de/jplag/reporting/reportobject/model/Version.java
+++ b/core/src/main/java/de/jplag/reporting/reportobject/model/Version.java
@@ -1,5 +1,7 @@
package de.jplag.reporting.reportobject.model;
+import java.util.Comparator;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,7 +14,8 @@
* @param minor MINOR version when you add functionality in a backwards compatible manner
* @param patch PATCH version when you make backwards compatible bug fixes
*/
-public record Version(@JsonProperty("major") int major, @JsonProperty("minor") int minor, @JsonProperty("patch") int patch) {
+public record Version(@JsonProperty("major") int major, @JsonProperty("minor") int minor, @JsonProperty("patch") int patch)
+ implements Comparable {
/**
* The default version for development (0.0.0).
@@ -42,4 +45,9 @@ public static Version parseVersion(String version) {
public String toString() {
return String.format("%d.%d.%d", major, minor, patch);
}
+
+ @Override
+ public int compareTo(Version other) {
+ return Comparator.comparing(Version::major).thenComparing(Version::minor).thenComparing(Version::patch).compare(this, other);
+ }
}
diff --git a/core/src/test/java/de/jplag/NewJavaFeaturesTest.java b/core/src/test/java/de/jplag/NewJavaFeaturesTest.java
index 6f14ebf068..88b0ecfcba 100644
--- a/core/src/test/java/de/jplag/NewJavaFeaturesTest.java
+++ b/core/src/test/java/de/jplag/NewJavaFeaturesTest.java
@@ -7,7 +7,6 @@
import org.junit.jupiter.api.Test;
import de.jplag.exceptions.ExitException;
-import de.jplag.java.JavaLanguage;
public class NewJavaFeaturesTest extends TestBase {
@@ -20,17 +19,17 @@ public class NewJavaFeaturesTest extends TestBase {
private static final String CHANGE_MESSAGE = "Number of %s changed! If intended, modify the test case!";
private static final String VERSION_MISMATCH_MESSAGE = "Using Java version %s instead of %s may skew the results.";
private static final String VERSION_MATCH_MESSAGE = "Java version matches, but results deviate from expected values";
- private static final String JAVA_VERSION_KEY = "java.version";
private static final String CI_VARIABLE = "CI";
+ public static final int EXPECTED_JAVA_VERSION = 21;
+
@Test
@DisplayName("test comparison of Java files with modern language features")
public void testJavaFeatureDuplicates() throws ExitException {
// pre-condition
- String actualJavaVersion = System.getProperty(JAVA_VERSION_KEY);
+ int javaVersion = Runtime.version().feature();
boolean isCiRun = System.getenv(CI_VARIABLE) != null;
- boolean isCorrectJavaVersion = actualJavaVersion.startsWith(String.valueOf(JavaLanguage.JAVA_VERSION));
- assumeTrue(isCorrectJavaVersion || isCiRun, VERSION_MISMATCH_MESSAGE.formatted(actualJavaVersion, JavaLanguage.JAVA_VERSION));
+ assumeTrue(javaVersion == EXPECTED_JAVA_VERSION || isCiRun, VERSION_MISMATCH_MESSAGE.formatted(javaVersion, EXPECTED_JAVA_VERSION));
JPlagResult result = runJPlagWithExclusionFile(ROOT_DIRECTORY, EXCLUSION_FILE_NAME);
diff --git a/core/src/test/java/de/jplag/NormalizationTest.java b/core/src/test/java/de/jplag/NormalizationTest.java
index c6a9db9ed1..f1ba200194 100644
--- a/core/src/test/java/de/jplag/NormalizationTest.java
+++ b/core/src/test/java/de/jplag/NormalizationTest.java
@@ -39,4 +39,4 @@ void testReorderingNormalization() {
void testInsertionReorderingNormalization() {
Assertions.assertIterableEquals(originalTokenString, tokenStringMap.get("SquaresInsertedReordered.java"));
}
-}
+}
\ No newline at end of file
diff --git a/core/src/test/java/de/jplag/options/JPlagOptionsTest.java b/core/src/test/java/de/jplag/options/JPlagOptionsTest.java
new file mode 100644
index 0000000000..90bb54c153
--- /dev/null
+++ b/core/src/test/java/de/jplag/options/JPlagOptionsTest.java
@@ -0,0 +1,20 @@
+package de.jplag.options;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import de.jplag.java.JavaLanguage;
+
+class JPlagOptionsTest {
+ @Test
+ void testWithLanguageOption() {
+ JavaLanguage lang = new JavaLanguage();
+ JPlagOptions options = new JPlagOptions(null, Set.of(), Set.of());
+ options = options.withLanguageOption(lang);
+
+ assertEquals(lang, options.language());
+ }
+}
diff --git a/core/src/test/java/de/jplag/reporting/jsonfactory/ReportTokenPositionTestTest.java b/core/src/test/java/de/jplag/reporting/jsonfactory/ReportTokenPositionTestTest.java
new file mode 100644
index 0000000000..d186863e98
--- /dev/null
+++ b/core/src/test/java/de/jplag/reporting/jsonfactory/ReportTokenPositionTestTest.java
@@ -0,0 +1,150 @@
+package de.jplag.reporting.jsonfactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import de.jplag.JPlagComparison;
+import de.jplag.JPlagResult;
+import de.jplag.Match;
+import de.jplag.Submission;
+import de.jplag.Token;
+import de.jplag.options.JPlagOptions;
+import de.jplag.reporting.FilePathUtil;
+import de.jplag.reporting.reportobject.model.BaseCodeMatch;
+import de.jplag.reporting.reportobject.model.CodePosition;
+import de.jplag.reporting.reportobject.model.ComparisonReport;
+
+class ReportTokenPositionTestTest {
+
+ @Test
+ void testCorrectTokenPositionsInComparisonReport() {
+ JPlagResult result = mock(JPlagResult.class);
+ JPlagOptions mockOptions = createMockOptions();
+ when(result.getOptions()).thenReturn(mockOptions);
+ JPlagComparison comparison = mock(JPlagComparison.class);
+ String firstID = "first";
+ String secondID = "second";
+ Submission firstSubmission = createMockSubmission(firstID);
+ Submission secondSubmission = createMockSubmission(secondID);
+ when(comparison.firstSubmission()).thenReturn(firstSubmission);
+ when(comparison.secondSubmission()).thenReturn(secondSubmission);
+ Match mockMatch = createMockMatch(0, 2, 0, 1);
+ when(comparison.matches()).thenReturn(List.of(mockMatch));
+
+ when(result.getComparisons(1)).thenReturn(List.of(comparison));
+
+ TestableReportWriter resultWriter = new TestableReportWriter();
+ Map> comparisonReportOutput;
+
+ try (MockedStatic mockedFilePathUtil = Mockito.mockStatic(FilePathUtil.class)) {
+ mockedFilePathUtil.when(() -> FilePathUtil.getRelativeSubmissionPath(any(), any(), any())).thenReturn(Path.of("file.java"));
+ comparisonReportOutput = new ComparisonReportWriter(Submission::getName, resultWriter).writeComparisonReports(result);
+ }
+ ComparisonReport comparisonReport = (ComparisonReport) resultWriter.getJsonEntry(Path.of(comparisonReportOutput.get(firstID).get(secondID)));
+
+ assertEquals(1, comparisonReport.matches().size());
+
+ CodePosition startInFirst = comparisonReport.matches().get(0).startInFirst();
+ CodePosition endInFirst = comparisonReport.matches().get(0).endInFirst();
+ CodePosition startInSecond = comparisonReport.matches().get(0).startInSecond();
+ CodePosition endInSecond = comparisonReport.matches().get(0).endInSecond();
+
+ assertEquals(1, startInFirst.lineNumber());
+ assertEquals(0, startInFirst.column());
+ assertEquals(0, startInFirst.tokenListIndex());
+
+ assertEquals(2, endInFirst.lineNumber());
+ assertEquals(10, endInFirst.column());
+ assertEquals(2, endInFirst.tokenListIndex());
+
+ assertEquals(1, startInSecond.lineNumber());
+ assertEquals(0, startInSecond.column());
+ assertEquals(0, startInSecond.tokenListIndex());
+
+ assertEquals(2, endInSecond.lineNumber());
+ assertEquals(10, endInSecond.column());
+ assertEquals(1, endInSecond.tokenListIndex());
+ }
+
+ @Test
+ void testCorrectTokenPositionsInBasecodeReport() {
+ JPlagResult result = mock(JPlagResult.class);
+ JPlagOptions mockOptions = createMockOptions();
+ when(result.getOptions()).thenReturn(mockOptions);
+ JPlagComparison comparison = mock(JPlagComparison.class);
+ String submissionID = "first";
+ String basecodeID = "basecode";
+ Submission submission = createMockSubmission(submissionID);
+ Submission baseCodeSubmission = createMockSubmission(basecodeID);
+ when(comparison.firstSubmission()).thenReturn(submission);
+ when(comparison.secondSubmission()).thenReturn(baseCodeSubmission);
+ Match mockMatch = createMockMatch(0, 2, 0, 1);
+ when(comparison.matches()).thenReturn(List.of(mockMatch));
+ when(submission.getBaseCodeComparison()).thenReturn(comparison);
+
+ when(result.getComparisons(1)).thenReturn(List.of(comparison));
+
+ TestableReportWriter resultWriter = new TestableReportWriter();
+ try (MockedStatic mockedFilePathUtil = Mockito.mockStatic(FilePathUtil.class)) {
+ mockedFilePathUtil.when(() -> FilePathUtil.getRelativeSubmissionPath(any(), any(), any())).thenReturn(Path.of("file.java"));
+ new BaseCodeReportWriter(Submission::getName, resultWriter).writeBaseCodeReport(result);
+ }
+
+ List baseCodeMatches = (List) resultWriter.getJsonEntry(Path.of("basecode", submissionID + ".json"));
+
+ assertEquals(1, baseCodeMatches.size());
+
+ CodePosition start = baseCodeMatches.get(0).start();
+ CodePosition end = baseCodeMatches.get(0).end();
+
+ assertEquals(1, start.lineNumber());
+ assertEquals(0, start.column());
+ assertEquals(0, start.tokenListIndex());
+
+ assertEquals(2, end.lineNumber());
+ assertEquals(10, end.column());
+ assertEquals(2, end.tokenListIndex());
+ }
+
+ JPlagOptions createMockOptions() {
+ JPlagOptions options = mock(JPlagOptions.class);
+ when(options.maximumNumberOfComparisons()).thenReturn(1);
+ return options;
+ }
+
+ Submission createMockSubmission(String name) {
+ Submission submission = mock(Submission.class);
+ when(submission.getName()).thenReturn(name);
+ List tokens = List.of(createMockToken(1, 1, 10), createMockToken(2, 1, 10), createMockToken(2, 3, 2), createMockToken(2, 10, 2));
+ when(submission.getTokenList()).thenReturn(tokens);
+ return submission;
+ }
+
+ Match createMockMatch(int startOfFirst, int endOfFirst, int startOfSecond, int endOfSecond) {
+ Match match = mock(Match.class);
+ when(match.startOfFirst()).thenReturn(startOfFirst);
+ when(match.endOfFirst()).thenReturn(endOfFirst);
+ when(match.startOfSecond()).thenReturn(startOfSecond);
+ when(match.endOfSecond()).thenReturn(endOfSecond);
+ return match;
+ }
+
+ Token createMockToken(int line, int column, int length) {
+ Token token = mock(Token.class);
+ when(token.getLine()).thenReturn(line);
+ when(token.getColumn()).thenReturn(column);
+ when(token.getLength()).thenReturn(length);
+ return token;
+ }
+
+}
diff --git a/core/src/test/java/de/jplag/reporting/jsonfactory/TestableReportWriter.java b/core/src/test/java/de/jplag/reporting/jsonfactory/TestableReportWriter.java
new file mode 100644
index 0000000000..77362106fd
--- /dev/null
+++ b/core/src/test/java/de/jplag/reporting/jsonfactory/TestableReportWriter.java
@@ -0,0 +1,25 @@
+package de.jplag.reporting.jsonfactory;
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import de.jplag.reporting.reportobject.writer.DummyResultWriter;
+
+public class TestableReportWriter extends DummyResultWriter {
+
+ public final Map jsonEntries;
+
+ public TestableReportWriter() {
+ jsonEntries = new HashMap<>();
+ }
+
+ @Override
+ public void addJsonEntry(Object jsonContent, Path path) {
+ jsonEntries.put(path, jsonContent);
+ }
+
+ public Object getJsonEntry(Path path) {
+ return jsonEntries.get(path);
+ }
+}
diff --git a/docs/1.-How-to-Use-JPlag.md b/docs/1.-How-to-Use-JPlag.md
index a6e52ff669..c940816d7e 100644
--- a/docs/1.-How-to-Use-JPlag.md
+++ b/docs/1.-How-to-Use-JPlag.md
@@ -6,8 +6,8 @@ JPlag can be used via the Command Line Interface by executing the JAR file.
Example: `java -jar jplag.jar path/to/the/submissions`
The language can either be set with the -l parameter or as a subcommand. If both a subcommand and the -l option are specified, the subcommand will take priority.
-When using the subcommand language specific arguments can be set.
-A list of language specific options can be obtained by requesting the help page of a subcommand (e.g. "jplag java -h").
+When using the subcommand, language-specific arguments can be set.
+A list of language-specific options can be obtained by requesting the help page of a subcommand (e.g., "jplag java -h").
The following arguments can be used to control JPlag:
```
@@ -86,7 +86,7 @@ Subcommands (supported languages):
*Note that the [legacy CLI](https://github.com/jplag/jplag/blob/legacy/README.md) is varying slightly.*
## Using JPlag programmatically
-The new API makes it easy to integrate JPlag's plagiarism detection into external Java projects.
+The API makes it easy to integrate JPlag's plagiarism detection into external Java projects.
**Example:**
@@ -110,16 +110,24 @@ try {
## Report File Generation
-After a JPlag run a zipped result report is automatically created.
+After a JPlag run, a zipped result report is automatically created.
The target location of the report can be specified with the `-r` flag.
If the `-r` is not specified, the location defaults `result.zip`. Specifying the `-r` flag with a path `/path/to/desiredFolder` results in the report being created as `/path/to/desiredFolder.zip`.
-Unless there is an error during the zipping process, the report will always be zipped. If the zipping process fails, the report will be available as unzipped under the specified location.
+The report will always be zipped unless there is an error during the zipping process. If the zipping process fails, the report will be available in an unzipped version under the specified location.
## Viewing Reports
-The newest version of the report viewer is always accessible at https://jplag.github.io/JPlag/. Simply drop your `result.zip` folder on the page to start inspecting the results of your JPlag run. Your submissions will neither be uploaded to a server nor stored permanently. They are saved in the application as long as you view them. Once you refresh the page, all information will be erased.
+Starting with version v6.0.0, the report viewer is bundled with JPlag and will be launched automatically. The `--mode` option controls this behavior.
+By default, JPlag will process the input files and produce a zipped result file. After that, the report viewer is launched (on localhost), and the report will be shown in your browser.
+
+The option `--mode show` will only open the report viewer.
+This allows you to view existing reports.
+You can optionally provide the path to a report file to immediately display it in the viewer; otherwise, the viewer will require you to select a report, just like the online version.
+By specifying `--mode run`, JPlag will run but generate the zipped report but will not open the report viewer.
+
+An online version of the viewer is still hosted at https://jplag.github.io/JPlag/ in order to view pre-v6.0.0 reports. Your submissions will neither be uploaded to a server nor stored permanently. They are stored as long as you view them. Once you refresh the page, all information will be erased.
## Basic Concepts
@@ -127,7 +135,7 @@ The newest version of the report viewer is always accessible at https://jplag.gi
This section explains some fundamental concepts about JPlag that make it easier to understand and use.
* **Root directory:** This is the directory in which JPlag will scan for submissions.
-* **Submissions:** Submissions contain the source code that JPlag will parse and compare. They have to be direct children of the root directory and can either be single files or directories.
+* **Submissions:** Submissions contain the source code JPlag will parse and compare. They have to be direct children of the root directory and can either be single files or directories.
### Single-file submissions
@@ -140,7 +148,7 @@ This section explains some fundamental concepts about JPlag that make it easier
### Directory submissions
-JPlag will read submission directories recursively, so they can contain multiple (nested) source code files.
+JPlag will read submission directories recursively, thus they can contain multiple (nested) source code files.
```
/path/to/root-directory
@@ -155,7 +163,7 @@ JPlag will read submission directories recursively, so they can contain multiple
└── Utils.java
```
-If you want JPlag to scan only one specific subdirectory of the submissions for source code files (e.g. `src`), can configure that with the argument `-S`:
+If you want JPlag to scan only one specific subdirectory of the submissions for source code files (e.g., `src`), you can configure that with the argument `-s`:
```
/path/to/root-directory
@@ -173,7 +181,7 @@ If you want JPlag to scan only one specific subdirectory of the submissions for
### Base Code
-The base code is a special kind of submission. It is the template that all other submissions are based on. JPlag will ignore all matches between two submissions, where the matches are also part of the base code. Like any other submission, the base code has to be a single file or directory in the root directory.
+The base code is a special kind of submission. It is the template that all other submissions are based on. JPlag will ignore all matches between two submissions where the matches are also part of the base code. Like any other submission, the base code has to be a single file or directory in the root directory.
```
/path/to/root-directory
@@ -186,7 +194,7 @@ The base code is a special kind of submission. It is the template that all other
└── Solution.java
```
-In this example, students have to solve a given problem by implementing the `run` method in the template below. Because they are not supposed to modify the `main` function, it will be identical for each student.
+In this example, students must solve a problem by implementing the `run` method in the template below. Because they are not supposed to modify the `main` function, it will be identical for each student.
```java
// BaseCode/Solution.java
@@ -204,15 +212,15 @@ public class Solution {
}
```
-To prevent JPlag from detecting similarities in the `main` function (and other parts of the template), we can instruct JPlag to ignore matches with the given base code by providing the `--bc=` option.
+To prevent JPlag from detecting similarities in the `main` function (and other parts of the template), we can instruct JPlag to ignore matches with the given base code by providing the `-bc=` option.
The `` in the example above is `BaseCode`.
### Multiple Root Directories
-* You can run JPlag with multiple root directories, JPlag compares submissions from all of them
+* You can run JPlag with multiple root directories; JPlag compares submissions from all of them
* JPlag distinguishes between old and new root directories
** Submissions in new root directories are checked amongst themselves and against submissions from other root directories
** Submissions in old root directories are only checked against submissions from other new root directories
-* You need at least one new root directory to run JPlag
+* You need at least one (new) root directory to run JPlag
This allows you to check submissions against those of previous years:
```
diff --git a/docs/2.-Supported-Languages.md b/docs/2.-Supported-Languages.md
index c89bb8ee39..69bbcfc5a0 100644
--- a/docs/2.-Supported-Languages.md
+++ b/docs/2.-Supported-Languages.md
@@ -1,11 +1,11 @@
-JPlag currently supports Java, C, C++, C#, Go, Kotlin, Python, R, Rust, Scala, Swift, and Scheme. Additionally, it has primitive support for text and prototypical support for EMF metamodels. A detailed list, including the supported language versions can be found in the [project readme](https://github.com/jplag/JPlag/blob/main/README.md#supported-languages).
+JPlag currently supports Java, C, C++, C#, Go, Kotlin, Python, R, Rust, Scala, Swift, Javascript, Typescript, LLVM IR and Scheme. Additionally, it has primitive support for text and prototypical support for EMF metamodels. A detailed list, including the supported language versions, can be found in the [project readme](https://github.com/jplag/JPlag/blob/main/README.md#supported-languages).
-The language modules differ in their maturity due to their age and different usage frequencies.
+The language modules differ in maturity due to their age and differing usage frequencies.
Thus, each frontend has a state label:
-- `mature`: This module is tried and tested, as well as up to date with a current language version.
-- `beta`: This module is relatively new and up to date. However, it is not as well tested. **Feedback welcome!**
-- `alpha`: This module is very new and not yet finished. Use with caution!
+- `mature`: This module is tried and tested and is (somewhat) up to date with a current language version.
+- `beta`: This module is relatively new and (somewhat) up to date. However, it has not been tested as well. **Feedback welcome!**
+- `alpha`: This module is very new and has not yet been finished. Use with caution!
- `legacy`: This module is from JPlag legacy (pre-v3.0.0) and may only support outdated language versions. It needs an update.
-- `unknown`: It is very much unclear in which state this module is.
+- `unknown`: It is very unclear which state this module is in.
All language modules can be found [here](https://github.com/jplag/JPlag/tree/master/languages).
diff --git a/docs/3.-Contributing-to-JPlag.md b/docs/3.-Contributing-to-JPlag.md
index dc967f659e..5f770753fd 100644
--- a/docs/3.-Contributing-to-JPlag.md
+++ b/docs/3.-Contributing-to-JPlag.md
@@ -1,22 +1,39 @@
We're happy to incorporate all improvements to JPlag into this codebase. Feel free to fork the project and send pull requests.
If you are new to JPlag, maybe check the [good first issues](https://github.com/jplag/jplag/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
-Please try to make well-documented and clear structured submissions:
+Please try to make well-documented and clearly structured submissions:
* All artifacts (code, comments...) should be in English
* Please avoid abbreviations!
-* Make use of JavaDoc to document classes and public methods
-* We provide a [formatter configuration](https://github.com/jplag/JPlag/blob/master/formatter.xml), which is enforced by spotless
- * Eclipse/IntelliJ users can use it directly
- * It can always be applied via maven with `mvn spotless:apply`
+* Make use of JavaDoc/TsDoc to document classes and public methods
+* We provide a formatter configurations
+ * For java code we use this [formatter configuration](https://github.com/jplag/JPlag/blob/master/formatter.xml), which is enforced by spotless
+ * Eclipse/IntelliJ users can use it directly
+ * It can always be applied via maven with `mvn spotless:apply`
+ * For typescript/vue code we use prettier und eslint
+ * They can both be executed with `npm run lint` and `npm run format`
+ * They can also be executed automatically on commit
* Use well-explained pull requests to propose your features
-* When re-using code from other projects mark them accordingly and make sure their license allows the re-use
+* When re-using code from other projects, mark them accordingly and make sure their license allows the re-use
* Your changes should always improve the code quality of the codebase, especially when working on older components
-* Your git messages should be concise but more importantly descriptive
+* Your git messages should be concise but, more importantly, descriptive
* Ensure your git history is clean, or else your PR may get squashed while merging
+* When creating a PR, make sure to provide a detailed description of you changes and what purpose they serve
## Building from sources
1. Download or clone the code from this repository.
+
+### Core
2. Run `mvn clean package` from the root of the repository to compile and build all submodules.
- Run `mvn clean package assembly:single` instead if you need the full jar which includes all dependencies.
-5. You will find the generated JARs in the subdirectory `jplag.cli/target`.
+ Run `mvn clean package assembly:single -P with-report-viewer` instead if you need the full jar, which includes all dependencies.
+3. You will find the generated JARs in the subdirectory `jplag.cli/target`.
+
+### Report Viewer
+2. Run `npm install` to install all dependencies.
+3. Run `npm run dev` to launch the development server. The report viewer will be available at `http://localhost:8080/`.
+ Different versions of the build command are described in the [report-viewer README](../report-viewer/README.md).
+
+### Git hooks
+
+The repository contains a pre-commit hook that prevents commits if they fail spotless and executes prettier and eslint on report-viewer code.
+To set up the hooks, call `git config --local core.hooksPath gitHooks/hooks` once within your local repository or run `npm i`/`npm run prepare` in the report-viewer package.
diff --git a/docs/4.-Adding-New-Languages.md b/docs/4.-Adding-New-Languages.md
index a4dbf71f00..5fd6f876c3 100644
--- a/docs/4.-Adding-New-Languages.md
+++ b/docs/4.-Adding-New-Languages.md
@@ -1,11 +1,11 @@
-# JPlag Frontend Design
+# JPlag Language Module Design
-To add support for a new language to JPlag, a JPlag frontend needs to be created for that specific language. The core purpose of a frontend is to transform each submission to a list of _Tokens_, an abstraction of the content of the submission files independent of the language of the submissions.
+To add support for a new language to JPlag, a JPlag language module needs to be created for that specific language. The core purpose of a language module is to transform each submission into a list of _Tokens_, an abstraction of the content of the submission files independent of the language of the submissions.
The token lists of the different submissions are then passed on to a comparison algorithm that checks the token lists for matching sequences.
## How are submissions represented? — Notion of _Token_
-In the context of JPlag, a Token does not represent a lexical unit, as identifiers, keywords or operators. Instead, Tokens represent syntactic entities, like statements, or control structures. More than one token might be needed to represent the nested structure of a statement or expression in a linear token list.
+In the context of JPlag, a Token does not represent a lexical unit, as identifiers, keywords or operators. Instead, Tokens represent syntactic entities, like statements or control structures. More than one token might be needed to represent the nested structure of a statement or expression in a linear token list.
```java
class MyClass extends SuperClass { private String name; }
@@ -18,12 +18,12 @@ Each comment is intended to represent one token.
From this example in Java, you may be able to see the following things:
- a class declaration is represented by three tokens of different _types_: `CLASS_DECLARATION`, `CLASS_BODY_BEGIN` and `CLASS_BODY_END`
- a token is associated with a _position_ in a code file.
- - the abstraction is incomplete, many details of the code are omitted. The original code cannot be reconstructed from the token list, but its structure can.
+ - the abstraction is incomplete, and many details of the code are omitted. The original code cannot be reconstructed from the token list, but its structure can.
A few more points about Tokens in JPlag:
- a token list contains the Tokens from _all files of one submission_. For that reason, Tokens save the _filename_ of their origin in addition to their position.
- Token types are represented by the `TokenType` interface which has to be adapted for each language individually.
- - For brevity, each token type is also associated with a String description, usually shorter than their name. Looking at the String representations used in existing frontends, you may recognize a kind of convention about how they are formed. The example above uses the full names of token types.
+ - For brevity, each token type is also associated with a String description, usually shorter than their name. Looking at the String representations used in existing language modules, you may recognize a kind of convention about how they are formed. The example above uses the full names of token types.
## How does the transformation work?
@@ -31,7 +31,7 @@ Here is an outline of the transformation process.
- each submitted file is _parsed_. The result is a set of ASTs for each submission.
- each AST is now _traversed_ depth-first. The nodes of the AST represent the grammatical units of the language.
- upon entering and exiting a node, Tokens can be created that match the type of the node. They are added to the current token list.
- - for block-type nodes like bodies of classes or if expressions, the point of entry and exit correspond to the respective `BEGIN` and `END` token types. If done correctly, the token list should contain balanced pairs of matching `BEGIN` and `END` tokens.
+ - for block-type nodes like bodies of classes or if expressions, the point of entry and exit corresponds to the respective `BEGIN` and `END` token types. If done correctly, the token list should contain balanced pairs of matching `BEGIN` and `END` tokens.
```java
@Override
@@ -57,20 +57,20 @@ public void enterClassDeclaration(ClassBodyContext context) {
addToken(token);
}
```
-The way the traversal works and how you can interact with the process depends on the parser technology used. In the example above, **ANTLR-generated parsers** were used, as was in most of the current JPlag frontends. We recommend to use ANTLR for any new frontend.
+The way the traversal works and how you can interact with the process depends on the parser technology used. In the example above, **ANTLR-generated parsers** were used, as was in most of the current JPlag language modules. We recommend using ANTLR for any new language module.
If a hard-coded (as opposed to dynamically generated) parser library is available for your language, it may make sense to use it. An implementation of the visitor pattern for the resulting AST should be included.
-# Frontend Structure
+# Language Module Structure
-A frontend consists of these parts:
+A language module consists of these parts:
| Component/Class | Superclass | Function | How to get there |
|-----------------------------------------|---------------------------|--------------------------------------------------|-------------------------------------------------------------|
-| Language class | `de.jplag.Language` | access point for the frontend | copy with small adjustments |
+| Language class | `de.jplag.Language` | access point for the language module | copy with small adjustments |
| `pom.xml` | - | Maven submodule descriptor | copy with small adjustments; add dependencies for parser |
-| `README.md` | - | documentation for the frontend | copy for consistent structure; adjust from there |
-| TokenType class | `de.jplag.TokenType` | contains the language-specific token types | **implement new** |
+| `README.md` | - | documentation for the language module | copy for consistent structure; adjust from there |
+| TokenType class | `de.jplag.TokenType` | contains the language-specific token types | **implement new** |
| | | |
| Lexer and Parser | - | transform code into AST | depends on technology |
| ParserAdapter class | `de.jplag.AbstractParser` | sets up Parser and calls Traverser | depends on technology |
@@ -84,23 +84,146 @@ For example, if ANTLR is used, the setup is as follows:
| Lexer and Parser | `Lexer`, `Parser` (ANTLR) | transform code into AST | generated from grammar files by antlr4-maven-plugin |
| Traverser | `ParseTreeWalker` (ANTLR) | traverses AST and calls listener | included in antlr4-runtime library, can be used as is |
| TraverserListener class | `ParseTreeListener` (ANTLR) | creates tokens when called | **implement new** |
-| ParserAdapter class | `de.jplag.AbstractParser` | sets up Parser and calls Traverser | copy with small adjustments |
+| ParserAdapter class | `de.jplag.AbstractAntlrParser` | sets up Parser and calls Traverser | copy with small adjustments |
-As the table shows, much of a frontend can be reused, especially when using ANTLR. The only parts left to implement specifically for each frontend are
+As the table shows, much of a language module can be reused, especially when using ANTLR. The only parts left to implement specifically for each language module are
- the ParserAdapter (for custom parsers)
- the TokenTypes, and
- the TraverserListener.
**Note** for parser libraries other than ANTLR:
- It should still be rather easy to implement the ParserAdapter from the library documentation.
- - Instead of using a listener pattern, the library may require you to do the token extraction in a _Visitor subclass_. In that case, there is only one method call per element, called e.g. `traverseClassDeclaration`. The advantage of this version is that the traversal of the subtrees can be controlled freely. See the Scala frontend for an example.
+ - Instead of using a listener pattern, the library may require you to do the token extraction in a _Visitor subclass_. In that case, there is only one method call per element, called e.g. `traverseClassDeclaration`. The advantage of this version is that the traversal of the subtrees can be controlled freely. See the Scala language module for an example.
-### Basic procedure outline
+## Setting up a new language module with ANTLR
+
+JPlag provides a small framework to make it easier to implement language modules with ANTLR
+
+### Create the Language class
+
+Extends the AbstractAntlrLanguage class and implements all required methods. There are two options for creating the parser.
+It can either be passed to the superclass in the constructor, as shown below, or created later by overriding the initializeParser method.
+The latter option should be used if the parser requires dynamic parameters.
+
+```java
+public class TestLanguage extends AbstractAntlrLanguage {
+ public TestLanguage() {
+ super(new TestParserAdapter());
+ }
+
+ @Override
+ public String[] suffixes() {
+ return new String[] {"expression"}; //return a list of file suffixes for your language
+ }
+
+ @Override
+ public String getName() {
+ return "Test"; //return the name of the language (e.g. Java). Can be anything that describes the language module shorty
+ }
+
+ @Override
+ public String getIdentifier() {
+ return "test"; //return the identifier for the language (e.g. java). Should be something simple and unique
+ }
+
+ @Override
+ public int minimumTokenMatch() {
+ return 9; //The minimum number of tokens required to form a match. Leave this at 9 if your module doesn't require anything different
+ }
+}
+```
+
+### Implement the parser adapter
+
+The generated code by ANTLR always looks slightly different. The AbstractAntlrParserAdapter class is able to perform most of the required steps automatically.
+The implementation only needs to call the correct generated methods. They should be named roughly the same as the example. The javadoc of each method contains additional information.
+
+```java
+public class TestParserAdapter extends AbstractAntlrParserAdapter {
+ private static final TestListener listener = new TestListener();
+
+ @Override
+ protected Lexer createLexer(CharStream input) {
+ return new TestLexer(input);
+ }
+
+ @Override
+ protected TestParser createParser(CommonTokenStream tokenStream) {
+ return new TestParser(tokenStream);
+ }
+
+ @Override
+ protected ParserRuleContext getEntryContext(TestParser parser) {
+ return parser.expressionFile();
+ }
+
+ @Override
+ protected AbstractAntlrListener getListener() {
+ return listener;
+ }
+}
+```
+
+### Implement the token type enum
+
+This is the same as non ANTLR modules. The enum should look something like this:
+
+```java
+public enum TestTokenType implements TokenType {
+ TOKEN_NAME("TOKEN_DESCRIPTION"); //the description works as a visual name. Look at other language modules for examples
+
+ private final String description;
+
+ TestTokenType(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+}
+```
+
+### Implement the listener
+
+In contrast to the java module, the framework for the ANTLR module a set of extraction rules has to be defined instead of a traditional listener.
+All rules are independent of each other, which makes it easier to debug the token extraction.
+
+The basic structure looks like this:
+
+```java
+class TestListener extends AbstractAntlrListener {
+
+ TestListener() {
+ //add rules
+ }
+}
+```
+
+To make the class easier to read the constructor should only call methods which contain the rules. These methods shouldn't be too long and contain the rules for a specific category of token.
+
+Extraction rules can be very complicated, but in most cases simple ones will suffice. The easiest option is to directly map antlr tokens to JPlag tokens:
+
+```java
+visit(VarDefContext.class).map(VARDEF);
+```
+
+There are some different variants of map, which determine the length of the tokens. The javadoc contains details on that. Map can also receive two JPlag token types, which creates one JPlag token for the start of the context and one for the end.
+visit can also receive a type of ANTLR terminal node to create tokens from terminal nodes.
+
+Additional features for rules:
+
+1. Condition - Can be passed as a second argument to visit. The rule only applies if the condition returns true (see CPP language module for examples)
+2. Semantics - Can be passed by using withSemantics after the map call (see CPP language module for examples)
+3. Delegate - To have more precise control over the token position and length a delegated visitor can be used (see Go language module for examples)
+
+## Basic procedure outline
```mermaid
flowchart LR
JPlag -->|"parse(files)"| Language
- subgraph frontend[LanguageFrontend]
+ subgraph module[LanguageModule]
Language -->|"parse(files)"| ParserAdapter
ParserAdapter -->|"parse(files)"| Parser -.->|ASTs| ParserAdapter
ParserAdapter -->|"walk(ASTs)"| Traverser
@@ -110,49 +233,37 @@ flowchart LR
end
```
-Note: In existing frontends, the token list is managed by the ParserAdapter, and from there it is returned to the
+Note: In existing language modules, the token list is managed by the ParserAdapter, and from there it is returned to the
Language class and then to JPlag.
-### Integration into JPlag
+## Integration into JPlag
-The following adjustments have to be made beyond creating the frontend submodule itself:
+The following adjustments have to be made beyond creating the language module submodule itself:
- Register the submodule in the aggregator POM for every build profile.
```xml
-
+
...
- jplag.frontend.my-frontend
+ newlanguage/module>
...
```
-- Add a dependency from the aggregator module to the new frontend
-- Add a dependency from the jplag module to the new frontend
-```xml
-
-
-
- ...
-
- de.jplag
- jplag.frontend.my-frontend
- ${revision}
-
- ...
-
-```
+- Add a dependency for the new language module to the coverage-report module
+- Add a dependency for the new language module to the cli module
+- Update the documentation in the main readme file and the `docs` folder
-That's it! The new frontend should now be usable as described in the main README. The name of the frontend used with the CLI `-l` option is the `IDENTIFIER` set in the Language class.
+That's it! The new language module should now be usable as described in the main README. The name of the language module used with the CLI `-l` option is the `IDENTIFIER` set in the Language class.
# Token Selection
-Apart from extracting the tokens correctly, the task of deciding which syntactical elements should be assigned a token is the essential part when designing a frontend.
+Apart from extracting the tokens correctly, the task of deciding which syntactical elements should be assigned a token is an essential part of designing a language module.
This guideline is solely based on experience and intuition – this "worked well" so far. More research might hint towards a more systematic process of token selection.
The goal of the abstraction is to create a token list that is
- _accurate_: a fair representation of the code as input to the comparison algorithm
- _consistent per se_: insensitive to small changes in the code that might obfuscate plagiarism; constructs are represented equally throughout the file
- - _consistent_ with the output of other trusted frontends—only to the extent that their respective languages are comparable, naturally.
+ - _consistent_ with the output of other trusted language modules — only to the extent that their respective languages are comparable, naturally.
To create a set of tokens in line with these objectives, we offer the tips below.
@@ -222,7 +333,7 @@ Note: If lists of the same type are nested, the end of the inner list may become
# Token Extraction
-The token extraction is the most time-consuming part of the frontend design.
+The token extraction is the most time-consuming part of the language module design.
How difficult it is is largely dependent on the underlying **grammar** of the parser.
This article deals with the implementation of the listener which is called at every stage of traversal of the AST. The examples center around tokenizing the Java language, using a grammar written in ANTLR4.
@@ -424,9 +535,9 @@ options.sensitivity.getValue();
You should pass the options to your parser if neccesary.
-# Frontend Test
+# Language Module Test
-To check the output of your frontend against the input, the `TokenPrinter` can be helpful. The `TokenPrinter` prints the input line by line, and the tokens of each line below it.
+To check the output of your language module against the input, the `TokenPrinter` can be helpful. The `TokenPrinter` prints the input line by line, and the tokens of each line below it.
```java
10 public class Example {
@@ -447,11 +558,11 @@ To check the output of your frontend against the input, the `TokenPrinter` can b
15 }
|}CLASS
```
-To test a frontend, set up a JUnit test class where the `TokenPrinter` prints the output of the `parse` method of the frontend. Read through the output and check whether the `List` satisfies the given requirements.
+To test a language module, set up a JUnit test class where the `TokenPrinter` prints the output of the `parse` method of the language module. Read through the output and check whether the `List` satisfies the given requirements.
### Test files
-The frontend should be tested with 'authentic' sample code as well as a 'complete' test file that covers all syntactic elements that the frontend should take into account. If you are using an ANTLR parser, such a complete test file may be included in the parser test files in the ANTLRv4 Grammar Repository.
+The language module should be tested with 'authentic' sample code as well as a 'complete' test file that covers all syntactic elements that the language module should take into account. If you are using an ANTLR parser, such a complete test file may be included in the parser test files in the ANTLRv4 Grammar Repository.
### Sanity check suggestions
@@ -461,7 +572,7 @@ The frontend should be tested with 'authentic' sample code as well as a 'complet
- The token list represents the input code with an acceptable coverage
—how that can be measured and what coverage is acceptable depends on the language. One approach would be line coverage, e.g. 90 percent of code lines should contain a token.
-- There are no `TokenTypes` that can never be produced by the frontend for any input.
+- There are no `TokenTypes` that can never be produced by the language module for any input.
- Put another way, the complete test code produces a token list that contains every type of token.
### Writing tests using the test api
@@ -510,7 +621,32 @@ protected File getTestFileLocation() {
}
```
+## Testing token positions
+
+The precise position of a token can be relevant for the visualization in the report viewer. To make sure the token positions are extracted correctly language modules should include some tests for that.
+
+Writing such tests can be done using a specific syntax in the test sources directly.
+Such a file can look like this:
+```java
+>class Test {
+> int test;
+$ | J_VARDEF 8
+>}
+```
+
+Every line that is prefixed with '>' will be interpreted as a line of test source code.
+
+Every line starting with '$' contains information about one expected token. The token is expected in the first source line above this one.
+The '|' marks the column the token should be in. It is followed by one space, then the name of the token (The name of the enum constant).
+Finally, separated by another space is the length of the token.
+A single file may contain any number of tokens.
+The test will verify that at least one token with those exact properties exists.
+
+These test files have to be added in the TestDataCollector. Put all test files in a single directory and specify it through collector.addTokenPositionTests("").
+If the directory is in the default location for test files, a relative path is enough, otherwise a full path has to be specified.
+
+
# Adding code highlighting to the report-viewer
To ensure your language gets properly registered and its code is correctly highlighted in the report-viewer:
-1) Add your language to the `ParserLanguage` enum in 'src/model/Language.ts'. As the value for the entry use its frontend name.
-2) Add your language to the switch-case in `src/utils/CodeHighlighter.ts` and return the correct [highlight.js name](https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md). If your language is not supported by default, also register the language here.
\ No newline at end of file
+1) Add your language to the `ParserLanguage` enum in 'src/model/Language.ts'. As the value for the entry use its language module name.
+2) Add your language to the switch-case in `src/utils/CodeHighlighter.ts` and return the correct [highlight.js name](https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md). If your language is not supported by default, also register the language here.
diff --git a/docs/5.-End-to-End-Testing.md b/docs/5.-End-to-End-Testing.md
index 35bfd882c5..db9080e804 100644
--- a/docs/5.-End-to-End-Testing.md
+++ b/docs/5.-End-to-End-Testing.md
@@ -1,433 +1,8 @@
-## Basics
-The basic structure of the end-to-end testing module is discussed in the [corresponding readme file](https://github.com/jplag/JPlag/blob/master/endtoend-testing/README.md).
-
-## Rationale behind the test data
-To be able to create the test data, some examples from science, which have addressed the concealment of plagiarism, were used to ensure the greatest possible coverage of the JPlag functionality.
-Here, the changes were split as finely as possible and applied to the levies.
-The following elaborations were used for this:
-- [Detecting source code plagiarism on introductory programming course assignments using a bytecode approach](https://ieeexplore.ieee.org/abstract/document/7910274)
-- [Detecting Disguised Plagiarism](https://arxiv.org/abs/1711.02149)
-
-These elaborations provide basic ideas on how a modification of the plagiarized source code can look like or be adapted.
-These code adaptations refer to a wide range of changes starting from
-adding/removing comments to architectural changes in the deliverables.
-
-- (1) Inserting comments or empty lines
-- (2) Changing variable names or function names
-- (3) Insertion of unnecessary or changed code lines
-- (4) Changing the program flow (statements and functions must be independent from each other)
- - (1) Variable declaration at the beginning of the program
- - (2) Combining declarations of variables
- - (3) Reuse of the same variable for other functions
-- (5) Changing control structures
- - (1) for(...) to while(...)
- - (2) if(...) to switch-case
-- (6) Modification of expressions
- - (1) (X < Y) to !(X >= Y) and ++x to x = x + 1
-- (7) Splitting and merging statements
- - (1) x = getSomeValue(); y = x- z; to y = (getSomeValue() - Z;
-- (8) Inserting unnecessary casts
-
-These changes were now transferred to a base class and thus the plagiarism was created. The named base class was provided with the individual changes. The numbers in the list shown above are intended for the traceability of the test data. Here the test data filenames were named with the respective changes. Example: SortAlgo4d1 contains the changes "Variable declaration at the beginning of the program". If several points are combined, this is separated by "_" e.g.: SortAlgo1_3 contains "(1) Inserting comments or empty lines" and "(3) Insertion of unnecessary or changed code lines".
-
-The following code examples show how these changes affect the program code and also how the detection of JPLag behaves.
-All the code examples shown and more can be found at [testdata-resources-SortAlgo](https://github.com/jplag/JPlag/tree/main/endtoend-testing/src/test/resources/data/sortAlgo).
-
-### (1) Inserting comments or empty lines
-
-Adding empty lines or comments affects the normalization of the output. If the End-To-End tests fail with these changes, it means that something has changed in the normalization, e.g. removing empty lines or recognizing comments no longer works.
-
-In the following, the modified base class looks like this:
-
-Original:
-``` java
-
- public void BubbleSortWithoutRecursion(Integer arr[]) {
- for(int i = arr.length; i > 1 ; i--) {
-```
-
-Plagiarized:
-``` java
-/*
-
- Unnecessary comment
- */
- public void BubbleSortWithoutRecursion(Integer arr[]) {
- //Unnecessary comment
- for(int i = arr.length; i > 1 ; i--) {
-```
-
-As expected, the resulting outputs have a match of 100% (JPLag result):
-
-``` json
-"SortAlgo-SortAlgo1" : {
- "minimal_similarity" : 100.0,
- "maximum_similarity" : 100.0,
- "matched_token_number" : 56
- },
-```
-
-### (2) Changing variable names or function names
-
-Changing variable names and function names has, like point 1, also the goal of detecting adjustments in the normalization level.
-If the End-To-End tests fail with these changes, it means that something has changed in the normalization, e.g. creating constants function and variable names.
-
-Orginal:
-
-``` java
- private final void swap(T[] arr, int i, int j) {
- T t = arr[i];
- arr[i] = arr[j];
- arr[j] = t;
- }
-```
-
-Plagiarized:
-
-``` java
- private final void paws(T[] otherArr, int i, int j) {
- T t = otherArr[i];
- otherArr[i] = otherArr[j];
- otherArr[j] = t;
- }
-```
-
-As expected, the resulting outputs have a match of 100% (JPLag result):
-
-``` json
-"SortAlgo-SortAlgo2" : {
- "minimal_similarity" : 100.0,
- "maximum_similarity" : 100.0,
- "matched_token_number" : 56
- },
-```
-
-### (3) Insertion of unnecessary or changed code lines
-
-In contrast to points 1 and 2, adding unnecessary code lines reduces the token generation. This has the consequence that the recognition can no longer be 100% sure whether plagiarism is present or not. The failure of the end-to-end tests in these cases means that either the tokens have been adjusted, the normalization has changed the function separation or something has changed in the minimum token numbers. This can be easily seen by running the end-to-end tests in different options. this will be shown in the next result examples.
-
-Original:
-``` java
- private final void swap(T[] arr, int i, int j) {
- T t = arr[i];
- arr[i] = arr[j];
- arr[j] = t;
- }
-```
-Plagiarized:
-``` java
-private final void swap(T[] arr, int i, int j) {
- var tempVar1 = 0;
- if (true) {
- T t = arr[i];
- arr[i] = arr[j];
- arr[j] = t;
- var tempVar2 = 0;
- tempVar2++;
- tempVar2 = tempVar2 + 1;
- }
- }
-```
-
-The results for the recognition already allow first recognition changes. Here the change of the `minimum_token_match` also has an effect on the result, which was not the case with (1) and (2).
-
-``` json
-[{"options" : {
- "minimum_token_match" : 1
- },
-"SortAlgo-SortAlgo3" : {
- "minimal_similarity" : 81.159424,
- "maximum_similarity" : 100.0,
- "matched_token_number" : 56
- },
-}]
-```
-
-``` json
-[{"options" : {
- "minimum_token_match" : 15
- },
-"SortAlgo-SortAlgo3" : {
- "minimal_similarity" : 57.971016,
- "maximum_similarity" : 71.42857,
- "matched_token_number" : 40
- },
-}]
-```
-
-### (4) Changing the program flow (statements and functions must be independent from each other)
-
-This subitem breaks down into three more change methods to maintain fine granularity:
-- (1) Variable declaration at the beginning of the program
-```java
-public class SortAlgo4d1 {
- private int firstCounter;
- private int arrayLenght;
- private int swapVarI;
- private int swapVarJ;
-
-```
-
-- (2) Combining declarations of variables
-``` java
-public class SortAlgo4d2 {
- private int firstCounter,swapVarJ,arrayLenght ,swapVarI;
-```
-
-- (3) Reuse of the same variable for other functions
-``` java
-public class SortAlgo4d3 {
- private int firstCounterAndArrayLenghtAndswapVarJ ,swapVarI;
-```
-
-The adjustments to the program flow with the previous instantiation of the variables were also made:
-Original:
-``` java
- if (n == 1)
- {
- return;
- }
-```
-
-Plagiarized:
-``` java
- firstCounter = n;
- if (firstCounter == 1) {
- return;
- }
-```
-
-The results of the individual adjustment are as follows:
-```json
- "SortAlgo-SortAlgo4d1" : {
- "minimal_similarity" : 87.30159,
- "maximum_similarity" : 98.21429,
- "matched_token_number" : 55
- },
- "SortAlgo-SortAlgo4d2" : {
- "minimal_similarity" : 87.5,
- "maximum_similarity" : 100.0,
- "matched_token_number" : 56
- },
- "SortAlgo-SortAlgo4d3" : {
- "minimal_similarity" : 90.32258,
- "maximum_similarity" : 100.0,
- "matched_token_number" : 56
- },
-```
-
-### (5) Changing control structures
-
-The change of the control structure in the program also indicates a change of the token generation in case of faulty tests. In contrast to (4), however, these are specially designed for other tokens that are made for if, else, ... structures.
-
-These changes were made to the SortAlgo test data in a plagiarized form.
-
-Original:
-``` java
- public void BubbleSortRecursion(Integer arr[], int n) {
- if (n == 1)
- {
- return;
- }
-
- for (int i = 0; i < n - 1; i++)
- {
- if (arr[i] > arr[i + 1])
- {
- swap(arr, i , i+1);
- }
- }
- BubbleSortRecursion(arr, n - 1);
- }
-```
-
-Plagiarized:
-``` java
- public void BubbleSortRecursion(Integer arr[], int n) {
- switch (n) {
- case 1:
- return;
- }
-
- int i = 0;
- while(i < n-1)
- {
- var tempBool = arr[i] > arr[i + 1];
- if (tempBool) {
- swap(arr, i, i + 1);
- }
- i++;
- }
-
- BubbleSortRecursion(arr, n - 1);
- }
-```
-
-Here it is remarkable which affects the adjustment of the `minimum_token_match` has on the recognition of the plagiarism.
-Changes of the token generation as well as the `minimum_token_match` have an effect on this kind of End-To-End test.
-
-``` json
- "options" : {
- "minimum_token_match" : 1
- },
- "tests" : {
- "SortAlgo-SortAlgo5" : {
- "minimal_similarity" : 82.14286,
- "maximum_similarity" : 82.14286,
- "matched_token_number" : 46
- },
-```
-
-``` json
- "options" : {
- "minimum_token_match" : 15
- },
- "tests" : {
- "SortAlgo-SortAlgo5" : {
- "minimal_similarity" : 0.0,
- "maximum_similarity" : 0.0,
- "matched_token_number" : 0
- },
-```
-
-### (6) Modification of expressions
-Changing the order of compare also changes the order of the program flow which is difficult to determine the exact effect of plagiarism. Here the statements (X < Y) to !(X >= Y) and ++x to x = x + 1 are changed. Since the syntax should be recognized however as expression, the pure change of the expression has little effect on their plagiarism recognition.
-
-Orginal:
-``` java
- public void BubbleSortRecursion(Integer arr[], int n) {
- if (n == 1)
- {
- return;
- }
-
- for (int i = 0; i < n - 1; i++)
- {
- if (arr[i] > arr[i + 1])
- {
- swap(arr, i , i+1);
- }
- }
- BubbleSortRecursion(arr, n - 1);
- }
-```
-
-Plagiarized:
-``` java
-public void BubbleSortRecursion(Integer arr[], int n) {
- if (n != 1)
- {
- for (int i = 0; !(i >= (n - 1));)
- {
- if (!(arr[i] <= arr[i + 1]))
- {
- swap(arr, i , i+1);
- }
- i = i + 1;
- }
- BubbleSortRecursion(arr, n - 1);
- }
- else
- {
- return;
- }
- }
-```
-
-Results:
-``` json
- {
- "options" : {
- "minimum_token_match" : 1
- },
- "SortAlgo-SortAlgo6" : {
- "minimal_similarity" : 83.58209,
- "maximum_similarity" : 100.0,
- "matched_token_number" : 56
- },
-```
-
-``` json
- "options" : {
- "minimum_token_match" : 15
- },
- "SortAlgo-SortAlgo6" : {
- "minimal_similarity" : 43.28358,
- "maximum_similarity" : 51.785713,
- "matched_token_number" : 29
- },
-```
-
-### (7) Splitting and merging statements
-The merging or splitting of statements results in changing the token for the respective plagiarism detection.
-Here code lines are either fetched from functions or stored in functions like `x = getSomeValue(); y = x- z;` to `y = (getSomeValue() - Z`.
-
-Original:
-``` java
-[...]
- swap(arr, i , i+1);
-[...]
- if (arr[innerCounter] > arr[innerCounter + 1]) {
-[...]
-```
-
-Plagiarized:
-``` java
-[...]
- swap(arr, i, add(i , 1));
-[...]
- if (arr[innerCounter] > arr[add(innerCounter , 1)]) {
-[...]
- private int add(int value1, int value2)
- {
- return value1 + value2;
- }
-
- private int subtract(int value1, int value2)
- {
- return value1 - value2;
- }
-```
-
-Results:
-``` json
- "options" : {
- "minimum_token_match" : 1
- },
- "tests" : {
- "SortAlgo-SortAlgo7" : {
- "minimal_similarity" : 76.712326,
- "maximum_similarity" : 100.0,
- "matched_token_number" : 56
- },
-```
-
-``` json
- "options" : {
- "minimum_token_match" : 15
- },
- "tests" : {
- "SortAlgo-SortAlgo7" : {
- "minimal_similarity" : 49.315067,
- "maximum_similarity" : 64.28571,
- "matched_token_number" : 36
- },
-```
-
-## Summary
-
-The results and the test coverage of the end-to-end tests strongly depend on the tested plagiarisms. It is also important to use and test many different options of the API offered by JPlag, as these have a direct influence on the detection and are therefore also important for the change detection.
-
-To summarize
-- (1) and (2) test normalization level
-- (3) to (7) the token generation level
-
-If a result differs only in the options, it is very likely that the change is in the configuration of the `minimum_token_match`.
-This means that if Option1 does not change in the result of the detection, but the result in Option2 does, this is the basis of the `minimum_token_match`.
-```
-java: (1)SortAlgo-SortAlgo5 --> passed
-java: (15)SortAlgo-SortAlgo5 --> failed
-```
-
+JPlag has two different types of end-to-end tests.
+# Maven end-to-end-tests
+There is a module inside the maven project, that runs e2e tests on the core of Jplag.
+The basic structure of the end-to-end testing module is discussed in the [corresponding readme file](https://github.com/jplag/JPlag/blob/master/endtoend-testing/README.md).
+# Complete end-to-end-tests
+The complete end-to-end tests are executed by the [complete e2e tests workflow](../../../.github/workflows/complete-e2e.yml) and are meant to check the entire process from building and executing JPlag to viewing the report in the report viewer. Details are specified in the [corresponding readme file](../report-viewer/tests/e2e/README.md).
\ No newline at end of file
diff --git a/docs/6.-Report-File-Generation.md b/docs/6.-Report-File-Generation.md
index 406ce95cc9..27f8178c7c 100644
--- a/docs/6.-Report-File-Generation.md
+++ b/docs/6.-Report-File-Generation.md
@@ -32,6 +32,11 @@ result.zip
│ submission2-submission....json
│ submission2-submissionN.json
│ ...
+│
+└───basecode
+│ └───submissionId1.json
+│ └───submissionId2.json
+│ ...
```
The report zip contains
@@ -41,20 +46,21 @@ The report zip contains
- The `overview.json` encapsulates the main information from a JPlagResult such as base directory path, language, min- and max-metric, etc. The `overview.json` provides data to the `OverviewView.vue` that is first displayed after the report is dropped into the viewer. Corresponds to the Java record `OverviewReport`.
- submissionFileIndex.json
- - The `submissionFileIndex.json` stores a list of all files in the submission for each submission id.
+ - The `submissionFileIndex.json` stores a list of all files in the submission for each submission id. This file is also used to track the tokens per file. It is represented by a Map from the submission id to an instance of `SubmissionFile`.
- options.json
- - This File contains all options given to JPlag either over the CLI or programmatically
+ - This File contains all options given to JPlag either over the CLI or programmatically. It is represented directly by the `JPlagOptions` class.
- submissions
-
- This folder contains all files of all submissions JPlag was run with. For each submission the `submissions` folder contains a subfolder with the name of the corresponding submission id. A subfolder for a submission contains all files of said submission.
These files are displayed in the `ComparisonView.vue`
- comparison files
-
- For each submission pair submission1 submission2 with ids submissionId1 and submissionId2, the report contains either submissionId1-submissionId2.json or submissionId2-submissionId1.json. This file contains information the comparison between the two submissions, such as the similarity and concrete matches. Corresponds to the Java record `ComparisonReport`.
+- base code
+ - Each JSON file in the `basecode` folder contains the data where the provided basecode was found in each submission. Each submission has its own file. If no basecode was provided, each file contains an empty array of matches. Each JSON file corresponds to an array of the Java record `BaseCodeMatch`.
+
## Submission ids
### Report Viewer
@@ -62,7 +68,7 @@ The `overview.json` contains a map that associates a submission id to its displa
For internal use in the report viewer use only(!) the submission id. Whenever the name of a submission has to be displayed in the report viewer, the id has to be resolved to its display name first. The report viewer's vuex store provides a getter for this resolution.
### JPlag
-At the beginning of report generation a map and a function that associate a JPlag `Submission` to a submission id is built. Whenever you reference a submission in a report viewer DTO use this map/function to resolve the submission to its id.
+At the beginning of report generation a map and a function that associates a JPlag `Submission` to a submission id is built. Whenever you reference a submission in a report viewer DTO use this map/function to resolve the submission to its id.
## Adding and displaying new attributes from JPlagResult
@@ -89,5 +95,5 @@ Task: Adding the number of tokens in a match, which has to be displayed in the M
2. Modify the existing component `ComparisonReportWriter.java` to additionally extract the number of tokens in a match from the `JPlagResult.java`
and save it in the Match DTO
3. Add `tokens: number` to `Match.ts`
-4. Edit `ComparisonFactory.ts` to get the number of tokens from the JSON report file. [report-viewer]
-5. Edit `MatchTable.vue` to display the tokens number in the `ComparisonView.vue`.
+4. Edit `ComparisonFactory.ts` to get the number of tokens from the JSON report file.
+5. Edit `MatchList.vue` to display the tokens number in the `ComparisonView.vue`.
\ No newline at end of file
diff --git a/docs/7.-Clustering-of-Submissions.md b/docs/7.-Clustering-of-Submissions.md
index be1aee66a5..0875cf0dc6 100644
--- a/docs/7.-Clustering-of-Submissions.md
+++ b/docs/7.-Clustering-of-Submissions.md
@@ -1,6 +1,6 @@
## Clustering Usage
-By default, JPlag is configured to perform a clustering of the submissions.
+By default, JPlag is configured to cluster the submissions.
The clustering partitions the set of submissions into groups of similar submissions.
The found clusters can be used candidates for _potentially_ colluding groups. Each cluster has a strength score, that measures how _suspicious_ the cluster is compared to other clusters.
@@ -20,18 +20,18 @@ __The clustering is designed to work out-of-the-box for running within the magni
| General | Enable | Controls whether the clustering is run at all. | `true` |
| General | Algorithm | Which clustering algorithm to use.
Agglomerative Clustering
Agglomerative Clustering iteratively merges similar submissions bottom up. It usually requires manual tuning for its parameters to yield helpful clusters.
Spectral Clustering
Spectral Clustering is combined with Bayesian Optimization to execute the k-Means clustering algorithm multiple times, hopefully finding a \"good\" clustering automatically. Its default parameters should work O.K. in most cases.
| Agglomerative Clustering |
| General | Metric | The similarity score between submissions to use during clustering. Each score is expressed in terms of the size of the submissions `A` and `B` and the size of their matched intersection `A ∩ B`.
AVG (aka. Dice's coefficient)
`AVG = 2 * (A ∩ B) / (A + B)`
MAX (aka. overlap coefficient)
`MAX = (A ∩ B) / min(A, B)` Compared to MAX, this prevents obfuscation when a collaborator bloats his submission with unrelated code.
MIN (_deprecated_)
`MIN = (A ∩ B) / max(A, B)`
INTERSECTION (_experimental_)
`INTERSECTION = A ∩ B`
| AVG |
-| Spectral | Bandwidth | For Spectral Clustering, Baysian Optimization is used to determine a fitting number of clusters. If a good clustering result is found during the search, numbers of clusters that differ by something in range of the bandwidth are also expected to good. Low values result in more exploration of the search space, high values in more exploitation of known results. | 20.0 |
+| Spectral | Bandwidth | For Spectral Clustering, Bayesian Optimization determines a fitting number of clusters. If a good clustering result is found during the search, numbers of clusters that differ by something in range of the bandwidth are also expected to good. Low values result in more search space exploration, and high values result in more exploitation of known results. | 20.0 |
| Spectral | Noise | The result of each k-Means run in the search for good clusterings is random. The noise level models the variance in the \"worth\" of these results. It also acts as a regularization constant. | 0.0025 |
-| Spectral | Min-Runs | Minimum number of k-Means executions for spectral clustering. With these initial runs clustering sizes are explored. | 5 |
-| Spectral | Max-Runs | Maximum number of k-Means executions during spectral clustering. Any execution after the initial (min-) runs tries to balance between exploration of unknown clustering sizes and exploitation of clustering sizes known as good. | 50 |
+| Spectral | Min-Runs | Minimum number of k-Means executions for spectral clustering. With these initial runs, clustering sizes are explored. | 5 |
+| Spectral | Max-Runs | Maximum number of k-Means executions during spectral clustering. Any execution after the initial (min-) runs tries to balance between exploring unknown clustering sizes and exploiting clustering sizes known as good. | 50 |
| Spectral | K-Means Iterations | Maximum number of iterations during each execution of the k-Means algorithm. | 200 |
-| Agglomerative | Threshold | Only clusters with an inter-cluster-similarity greater than this threshold are merged during agglomerative clustering. | 0.2 |
+| Agglomerative | Threshold | Only clusters with an inter-cluster similarity greater than this threshold are merged during agglomerative clustering. | 0.2 |
| Agglomerative | inter-cluster-similarity | How to measure the similarity of two clusters during agglomerative clustering.
MIN (aka. complete-linkage)
Clusters are merged if all their submissions are similar.
MAX (aka. single-linkage)
Clusters are merged if there is a similar submission in both.
AVERAGE (aka. average-linkage)
Clusters are merged if their submissions are similar on average.
| AVERAGE |
-| Preprocessing | Pre-Processor | How the similarities are preprocessed prior to clustering. Spectral Clustering will probably not have good results without it.
None
No preprocessing.
Cumulative Distribution Function (CDF)
Before clustering, the value of the cumulative distribution function of all similarities is estimated. The similarities are multiplied with these estimates. This has the effect of suppressing similarities that are low compared to other similarities.
Percentile
Any similarity smaller than the given percentile will be suppressed during clustering.
Threshold
Any similarity smaller than the given threshold will be suppressed during clustering.
| CDF |
+| Preprocessing | Pre-Processor | How the similarities are preprocessed before clustering. Spectral Clustering will probably not have good results without it.
None
No preprocessing.
Cumulative Distribution Function (CDF)
Before clustering, the value of the cumulative distribution function of all similarities is estimated. The similarities are multiplied by these estimates. This has the effect of suppressing low similarities compared to other similarities.
Percentile
Any similarity smaller than the given percentile will be suppressed during clustering.
Threshold
Any similarity smaller than the given threshold will be suppressed during clustering.
| CDF |
## Clustering Architecture
-All clustering related classes are contained within the `de.jplag.clustering(.*)` packages in the core project.
+All clustering-related classes are contained within the core project's `de.jplag.clustering(.*)` packages.
The central idea behind the structure of clustering is the ease of use: To use the clustering calling code should only ever interact with the `ClusteringOptions`, `ClusteringFactory`, and `ClusteringResult` classes:
@@ -85,7 +85,7 @@ New clustering algorithms and preprocessors can be implemented using the `Generi
### Integration Tests
-There are integration tests for the Spectral Clustering to verify, that a least in the case of two known sets of similarities the groups known to be colluders are found. However, these are considered to be sensitive data. The datasets are not available to the public and these tests can only be run by maintainers with access.
+There are integration tests for the Spectral Clustering to verify that, at least in the case of two known sets of similarities, the groups known to be colluders are found. However, these are considered to be sensitive data. The datasets are not available to the public and these tests can only be run by maintainers with access.
To run these tests the contents of the [PseudonymizedReports](https://github.com/jplag/PseudonymizedReports) repository must added in the folder `jplag/src/test/resources/de/jplag/PseudonymizedReports`.
diff --git a/docs/Home.md b/docs/Home.md
index d6388bd543..94cedf59c1 100644
--- a/docs/Home.md
+++ b/docs/Home.md
@@ -3,9 +3,9 @@
## What is JPlag
-JPlag finds pairwise similarities among a set of multiple programs. It can reliably detect software plagiarism and collusion in software development. All similarities are calculated locally, and no source code or plagiarism results are ever uploaded to the internet. JPlag supports a large number of programming and modeling languages. JPlag does not merely compare bytes of text but is aware of programming language syntax and program structure and hence is robust against many kinds of attempts to disguise similarities (_obfusction_) between plagiarized files.
+JPlag finds pairwise similarities among a set of multiple programs. It can reliably detect software plagiarism and collusion in software development. All similarities are calculated locally; no source code or plagiarism results are ever uploaded online. JPlag supports a large number of programming and modeling languages. JPlag does not merely compare bytes of text but is aware of programming language syntax and program structure and hence is robust against many kinds of attempts to disguise similarities (_obfusction_) between plagiarized files.
-JPlag is typically used to detect and thus discourage the unallowed copying of student exercise programs in programming education. But in principle, it can also be used to detect stolen software parts among large amounts of source text or modules that have been duplicated (and only slightly modified). JPlag has already played a part in several intellectual property cases where it has been successfully used by expert witnesses.
+JPlag is typically used to detect and thus discourage the unallowed copying of student exercise programs in programming education. However, in principle, it can also detect stolen software parts among large amounts of source text or modules that have been duplicated (and only slightly modified). JPlag has already played a part in several intellectual property cases where expert witnesses have successfully used it.
**Just to make it clear**: JPlag does not compare to the internet! It is designed to find similarities among the student solutions, which is usually sufficient for computer programs.
@@ -16,7 +16,7 @@ JPlag is typically used to detect and thus discourage the unallowed copying of s
* 🤩 [Give us Feedback in a **short (<5 min) survey**](https://docs.google.com/forms/d/e/1FAIpQLSckqUlXhIlJ-H2jtu2VmGf_mJt4hcnHXaDlwhpUL3XG1I8UYw/viewform?usp=sf_link)
## History
-Originally, JPlag was developed in 1996 by Guido Mahlpohl and others at the chair of Prof. Walter Tichy at Karlsruhe Institute of Technology (KIT). It was first documented in a [Tech Report](https://publikationen.bibliothek.kit.edu/542000) in 2000 and later more formally in the [Journal of Universal Computer Science](http://www.ipd.kit.edu/tichy/uploads/publikationen/16/finding_plagiarisms_among_a_set_of_progr_638847.pdf). Since 2015 JPlag is hosted here on GitHub. After 25 years of its creation, JPlag is still used frequently in many universities in different countries around the world.
+Originally, JPlag was developed in 1996 by Guido Mahlpohl and others at the chair of Prof. Walter Tichy at Karlsruhe Institute of Technology (KIT). It was first documented in a [Tech Report](https://publikationen.bibliothek.kit.edu/542000) in 2000 and later more formally in the [Journal of Universal Computer Science](http://www.ipd.kit.edu/tichy/uploads/publikationen/16/finding_plagiarisms_among_a_set_of_progr_638847.pdf). Since 2015 JPlag is hosted here on GitHub. After 25 years of its creation, JPlag is still used frequently in many universities in different countries worldwide.
## Download JPlag
Download the latest version of JPlag [here](https://github.com/jplag/jplag/releases). If you encounter bugs or other issues, please report them [here](https://github.com/jplag/jplag/issues).
diff --git a/endtoend-testing/README.md b/endtoend-testing/README.md
index 4a82070bdf..f27e7019d8 100644
--- a/endtoend-testing/README.md
+++ b/endtoend-testing/README.md
@@ -1,114 +1,73 @@
-
# JPlag - End-To-End Testing
-With the help of the end-to-end module, changes to the detection of JPlag are to be tested.
-With the help of elaborated plagiarism, which has been worked out from suggestions in the literature on the topic of "plagiarism detection and avoidance", a wide range of detectable changes can be covered. The selected plagiarisms are the decisive factor here as to whether a change in recognition can be perceived.
-## References
-These elaborations provide basic ideas on how a modification of the plagiarized source code can look or be adapted.
-These code adaptations refer to various changes, from
-adding/removing comments to architectural changes in the deliverables.
+The end-to-end test module contains tests that report any chance in the similarities reported by JPlag.
+There are two kinds of tests:
+1. Simple tests that fail if the similarity between two submissions changed
+2. Gold standard tests
-The following elaborations were used to be able to create the plagiarisms with the broadest coverage:
-- [Detecting Source Code Plagiarism on Introductory Programming Course Assignments Using a Bytecode Approach - Oscar Karnalim](https://ieeexplore.ieee.org/abstract/document/7910274 "Detecting Source Code Plagiarism on Introductory Programming Course Assignments Using a Bytecode Approach - Oscar Karnalim")
-- [Detecting Disguised Plagiarism - Hatem A. Mahmoud](https://arxiv.org/abs/1711.02149 "Detecting Disguised Plagiarism - Hatem A. Mahmoud")
-- [Instructor-centric source code plagiarism detection and plagiarism corpus](https://dl.acm.org/doi/abs/10.1145/2325296.2325328 "Instructor-centric source code plagiarism detection and plagiarism corpus")
+## Gold standard tests
-## Steps Towards Plagiarism
-The following changes were applied to sample tasks to create test cases:
-
-
Inserting comments or empty lines (normalization level)
-
Changing variable names or function names (normalization level)
-
Insertion of unnecessary or changed code lines (token generation)
-
Changing the program flow (token generation) (statements and functions must be independent of each other)
-
-
Variable declaration at the beginning of the program
-
Combining declarations of variables
-
Reuse of the same variable for other functions
-
-
Changing control structures
-
-
for(...) to while(...)
-
if(...) to switch-case
-
-
Modification of expressions
-
-
(X < Y) to !(X >= Y) and ++x to x = x + 1
-
-
Splitting and merging statements
-
-
x = getSomeValue(); y = x- z; to y = (getSomeValue() - Z;
-
-
+A gold standard test serves as a metric for the change in detection quality. It needs a list of plagiarism instances in the data set.
+JPlag outputs comparisons split into those that should be reported as plagiarism and those that shouldn't.
+The test will fail if the average similarity on one of those groups changed. In contrast to the other kind of test, this offers a rough way to check if the changes made JPlag better or worse.
-More detailed information about the creation as well as about the subject of the issue can be found in the issue [Develop an end-to-end testing strategy](https://github.com/jplag/JPlag/issues/193 "Develop an end-to-end testing strategy").
+## Updating tests
-**The changes listed above have been developed and evaluated for purely scientific purposes and are not intended to be used for plagiarism in the public or private domain.**
+If the similarities reported by JPlag change and these changes are wanted, the reference values for the end-to-end tests need to be updated.
+To do that the test in [EndToEndGeneratorTest.java](src/test/java/de/jplag/endtoend/EndToEndGeneratorTest.java) have to be executed.
+This will generate new reference files.
-## JPlag - End-To-End TestSuite Structure
-The construction of an end-to-end test is done with the help of the JPlag API.
-The tests are generated dynamically according to the existing test data and allow the creation of end-to-end tests for all supported languages of JPlag without making any changes to the code.
-The helper loads the existing test data from the designated directory and creates dynamic tests for the individual directories. It is possible to create different test classes for the other languages.
-
-To distinguish which domain of the recognition changes have occurred, fine granular test cases are used. These are composed of the changes already mentioned above. The plagiarism is compared with the original delivery; thus, detecting and testing small sections of the recognition is possible.
-
-The comparative values were discussed and tested. The following results of the JPlag scan are used for the comparison:
-1. minimal similarity as `double`
-2. maximum similarity as `double`
-3. matched token number as `int`
-
-The comparative values were discussed and elaborated in the issue [End-to-end testing - "comparative values"](https://github.com/jplag/JPlag/issues/548 "End-to-end testing - \"comparative values\"").
-
-Additionally, it is possible to create several options for the test data. More information about the test options can be found at [JPlag - option variants for the end-to-end tests #590](https://github.com/jplag/JPlag/issues/590 "JPlag - option variants for the end-to-end tests #590"). Currently, various settings are supported by the `minimumTokenMatch`. This can be extended as desired in the record class `Options`.
-
-The current JPlag scans will be compared with the stored ones.
-This was done by storing the data in a *.json file which is read at the beginning of each test run.
-
-### JSON Result Structure
-
-The structures of the JSON file can be traced using the individual record classes, which can be found under `de.jplag.endtoend.model`.
-The outer structure of the JSON file is recorded in the `ResultDescription` record.
-The record contains a map of several options and the corresponding results.
-The internal structure consists of several `Option` records, each containing information about the test run's current configuration.
-Thus the results can be kept apart from the other configurations.
-The test results for the specified options are also specified in the object. This consists of the `ExpectedResult` record, which contains the detection results.
-
-Here the hierarchy is as follows:
-
-```JSON
-[{
- "options":{
- "minimum_token_match":"int"
- },
- "tests":{
- "languageIdentifier":{
- "minimal_similarity":"double",
- "maximum_similarity":"double",
- "matched_token_number":"int"
- },
- "/..."
- }
-},
-{
- "options":{
- "minimum_token_match":"int"
- },
- "tests":{
- "languageIdentifier":{
- "minimal_similarity":"double",
- "maximum_similarity":"double",
- "matched_token_number":"int"
- },
- "/..."
- }
-}]
-```
+## Adding new tests
+
+This segment explains the steps for adding new test data
+
+### Obtain test data
+
+New test data can be obtained in multiple ways.
+
+Ideally, real-world data is used. To use gold standard tests, real-world data needs to contain information about which submission pairs are plagiarism and which aren't.
+
+Alternatively, test data can be generated using various methods. One such method is explained below.
----
+The test data should be placed under [data](src/test/resources/data). It can either be added as a directory containing submissions or as a zip file.
-## Create New Language End-To-End Tests
+### Defining the data set for the tests
+
+This is done in [dataSets](src/test/resources/dataSets). To add a new data set a new json file needs to be placed here.
+
+A minimum example for a configuration can be found in [progpedia.json](src/test/resources/dataSets/progpedia.json). A full example using all options in [sortAlgo.json](src/test/resources/dataSets/sortAlgo.json).
+
+For available options look at [dataSetTemplate.json](src/test/resources/dataSetTemplate.json).
+
+### Generating the reference results
+
+See Updating tests above
+
+## Creating test data manually
+
+The following changes were applied to sample tasks to create the sortAlgo data set:
+
+* Inserting comments or empty lines (normalization level)
+* Changing variable names or function names (normalization level)
+* Insertion of unnecessary or changed code lines (token generation)
+* Changing the program flow (token generation) (statements and functions must be independent of each other)
+ * Variable declaration at the beginning of the program
+ * Combining declarations of variables
+ * Reuse of the same variable for other functions
+* Changing control structures
+ * for(...) to while(...)
+ * if(...) to switch-case
+* Modification of expressions
+ * (X < Y) to !(X >= Y) and ++x to x = x + 1
+* Splitting and merging statements
+ * x = getSomeValue(); y = x- z; to y = (getSomeValue() - Z;
+
+More detailed information about the creation as well as about the subject of the issue can be found in the issue [Develop an end-to-end testing strategy](https://github.com/jplag/JPlag/issues/193 "Develop an end-to-end testing strategy").
+
+**The changes listed above have been developed and evaluated for purely scientific purposes and are not intended to be used for plagiarism in the public or private domain.**
-This section explains how to create new end-to-end tests in the existing test suite.
### Creating The Plagiarism
+
Before you add a new language to the end-to-end tests, I would like to point out that the quality of the tests depends dreadfully on the plagiarism techniques you choose, which were explained in section [Steps Towards Plagiarism](#steps-towards-plagiarism).
If you need more information about creating plans for this purpose, you can also read the elaborations that can be found under [References](#references).
The more various changes you apply, the more accurate the end-to-end tests for the language will be.
@@ -157,69 +116,13 @@ public void BubbleSortWithoutRecursion(Integer arr[]) {
//...
}
```
-### Copying Plagiarism To The Resources
-
-The plagiarisms created in [Creating The Plagiarism](#creating-the-plagiarism) must now be copied to the corresponding resources folder. For each test suite, the resources must be placed in `JPlag/jplag.endToEndTesting/src/test/resources/languageTestFiles//`. For example, for the existing test suite `sortAlgo` of language `java`, the path is `JPlag/jplag.endToEndTesting/src/test/resources/languageTestFiles/java/sortAlgo`.
-It is important to note that the language identifier must match `Language#getIdentifier` to load the language during testing correctly.
-
-To automatically generate expected results, the test in `EndToEndGeneratorTest` can be executed to generate a JSON result description file. This file has to be copied to `JPlag/jplag.endToEndTesting/src/test/resources/results//.json`.
-Once the test data has been copied, the end-to-end tests can be successfully tested. As soon as a change in the detection takes place, the results will differ from the stored results, and the tests will fail if the results have changed.
-
-### Extending The Comparison Value
-As already described, the current comparisons in the end-to-end test treat the values of `minimal similarity`, `maximum similarity`, and `matched token number`.
-As soon as there is a need to extend these comparison values, this section describes how this can be achieved.
-Beforehand, however, this should be discussed in a new issue about this need.
-
-- For new comparison values, these properties must be extended in the `ExpectedResult` record at the package `de.jplag.endtoend.model`. Here it is sufficient to add the values in the record and to enter the JSON name as `@JsonProperty("json_name")`.
-
-```JAVA
-public record ExpectedResult(
- @JsonProperty("minimal_similarity") float resultSimilarityMinimum,
- @JsonProperty("maximum_similarity") float resultSimilarityMaximum,
- @JsonProperty("matched_token_number") int resultMatchedTokenNumber) {
-}
-```
-
-- To include the new value in the tests, they must be added to the `EndToEndSuiteTest` as a comparison operation at the package `de.jplag.endtoend`. The `runJPlagTestSuite()` function provided for this purpose must be extended to include the new comparison value. To do this, create the comparison as shown in the code example below.
-
-```JAVA
-//...
- if (areDoublesDifferent(result.resultSimilarityMaximum(), jPlagComparison.maximalSimilarity())) {
- addToValidationErrors("maximal similarity", String.valueOf(result.resultSimilarityMaximum()),
- String.valueOf(jPlagComparison.maximalSimilarity()));
- }
-//...
-```
-
-- Once the tests run the first time, they will fail due to the missing values in the old JSON result file used for the test cases. The old results must then be replaced with new ones.
-For this purpose, the last section of the chapter [Copying Plagiarism To The Resources](#copying-plagiarism-to-the-resources) can help.
-
-### Extending JPlag Test Run Options
-The end-to-end tests support the possible scan options of the JPlag API. Currently, `minimumTokenMatch` is used in the end-to-end tests. These values are also stored in the JSON as configuration to keep the test cases at the options apart. Likewise, also changes in the logic of the different options are to be determined to be able.
-
-- To extend new options to the end-to-end tests, they must be added to the record object `Options` in the package `de.jplag.endtoend.model`. Here it is sufficient to add the values in the record and to enter the JSON name as `@JsonProperty("json_name")`.
-
-```JAVA
-public record Options(
-@JsonProperty("minimum_token_match") Integer minimumTokenMatch) {
-}
-```
-
-- After the new value has been added to the record, the creation of the object must now also be adjusted in the `EndToEndSuiteTest`. The 'setRunOptions' function is provided for this purpose. The options can be added in any order and combination. It should be noted that each test case is run with these options.
-
-```JAVA
- private void setRunOptions() {
- options = new ArrayList<>();
- options.add(new Options(1));
- options.add(new Options(15));
- }
-```
-
-- If you want to create individual test cases by testing the options only on a specific dataset, a new test case must be created for this purpose. The transfer parameter options can be adjusted and specified for the new test cases. This can then be tested with the function `runTests`.
- ```JAVA
- runTests(directoryName, option, currentLanguageIdentifier, testCase, currentResultDescription);
-```
+## References
+These elaborations provide basic ideas on how a modification of the plagiarized source code can look or be adapted.
+These code adaptations refer to various changes, from
+adding/removing comments to architectural changes in the deliverables.
-- Once the tests run the first time, they will fail due to the missing values in the old JSON result file used for the test cases. The old results must then be replaced with new ones.
-For this purpose, the last section of the chapter [Copying Plagiarism To The Resources](#copying-plagiarism-to-the-resources) can be used as help.
+The following elaborations were used to be able to create the plagiarisms with the broadest coverage:
+- [Detecting Source Code Plagiarism on Introductory Programming Course Assignments Using a Bytecode Approach - Oscar Karnalim](https://ieeexplore.ieee.org/abstract/document/7910274 "Detecting Source Code Plagiarism on Introductory Programming Course Assignments Using a Bytecode Approach - Oscar Karnalim")
+- [Detecting Disguised Plagiarism - Hatem A. Mahmoud](https://arxiv.org/abs/1711.02149 "Detecting Disguised Plagiarism - Hatem A. Mahmoud")
+- [Instructor-centric source code plagiarism detection and plagiarism corpus](https://dl.acm.org/doi/abs/10.1145/2325296.2325328 "Instructor-centric source code plagiarism detection and plagiarism corpus")
diff --git a/gitHooks/hooks/pre-commit b/gitHooks/hooks/pre-commit
new file mode 100755
index 0000000000..7796838649
--- /dev/null
+++ b/gitHooks/hooks/pre-commit
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+echo "Running pre commit checks"
+
+hasJavaFiles=0
+hasJsFiles=0
+
+files=`git diff --name-only --cached`
+while read name
+do
+ if [[ $name == report-viewer* ]]
+ then
+ hasJsFiles=1
+ fi
+
+ if [[ $name == *.java ]]
+ then
+ hasJavaFiles=1
+ fi
+done <<< "$files"
+
+if [[ $hasJsFiles -gt 0 ]]
+then
+ echo "Running report viewer pre commit checks"
+ cd report-viewer
+ ../gitHooks/scripts/reportViewerPreCommit
+ if [ $? -gt 0 ]
+ then
+ exit 1
+ fi
+ cd ..
+fi
+
+if [[ $hasJavaFiles -gt 0 ]]
+then
+ echo "Running java pre commit checks"
+ gitHooks/scripts/javaPreCommit
+ if [ $? -gt 0 ]
+ then
+ exit 1
+ fi
+fi
diff --git a/gitHooks/scripts/javaPreCommit b/gitHooks/scripts/javaPreCommit
new file mode 100755
index 0000000000..19076694d7
--- /dev/null
+++ b/gitHooks/scripts/javaPreCommit
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+if ! command -v mvn &> /dev/null #checks if maven is installed
+then
+ echo "Maven is not installed. Spotless will not be checked"
+ exit 0
+fi
+
+#prevents the shell from aborting if maven returns a non zero exit code
+set +e
+echo Checking spotless
+mvn spotless:check &> /dev/null
+exitCode=$?
+
+if [ $exitCode -gt 0 ]
+then
+ echo "Spotless failed. Please run 'mvn spotless:apply' to fix."
+fi
+exit $exitCode
diff --git a/gitHooks/scripts/reportViewerPreCommit b/gitHooks/scripts/reportViewerPreCommit
new file mode 100755
index 0000000000..502b47cb33
--- /dev/null
+++ b/gitHooks/scripts/reportViewerPreCommit
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+if ! command -v npm &> /dev/null #checks if npm is installed
+then
+ echo "Npm is not installed. Linter check will be skipped"
+ exit 0
+fi
+
+npx lint-staged
diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java
index 8ba51cea0e..440d4a7aaf 100644
--- a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java
+++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrParserAdapter.java
@@ -62,6 +62,8 @@ private void parseFile(File file, TokenCollector collector) throws ParsingExcept
Lexer lexer = this.createLexer(CharStreams.fromReader(reader));
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
T parser = this.createParser(tokenStream);
+ parser.removeErrorListeners();
+ parser.addErrorListener(new AntlrLoggerErrorListener());
ParserRuleContext entryContext = this.getEntryContext(parser);
ParseTreeWalker treeWalker = new ParseTreeWalker();
InternalListener listener = new InternalListener(this.getListener(), collector);
diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AntlrLoggerErrorListener.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AntlrLoggerErrorListener.java
new file mode 100644
index 0000000000..2fa820ca64
--- /dev/null
+++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AntlrLoggerErrorListener.java
@@ -0,0 +1,21 @@
+package de.jplag.antlr;
+
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Writes error messages from ANTLR to a logger
+ */
+public class AntlrLoggerErrorListener extends BaseErrorListener {
+ private static final Logger logger = LoggerFactory.getLogger(AntlrLoggerErrorListener.class);
+ private static final String ERROR_TEMPLATE = "ANTLR error - line {}:{} {}";
+
+ @Override
+ public void syntaxError(Recognizer, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg,
+ RecognitionException e) {
+ logger.error(ERROR_TEMPLATE, line, charPositionInLine, msg);
+ }
+}
diff --git a/language-api/src/main/java/de/jplag/semantics/CodeSemantics.java b/language-api/src/main/java/de/jplag/semantics/CodeSemantics.java
index 2eb99262d5..7da3304a92 100644
--- a/language-api/src/main/java/de/jplag/semantics/CodeSemantics.java
+++ b/language-api/src/main/java/de/jplag/semantics/CodeSemantics.java
@@ -7,11 +7,11 @@
import java.util.Set;
/**
- * Contains semantic information about a code snippet, in our case either a token or a statement.
+ * Contains semantic information about a code fragment, in our case either a token or a statement.
*/
public class CodeSemantics {
- private boolean keep;
+ private boolean critical;
private PositionSignificance positionSignificance;
private final int bidirectionalBlockDepthChange;
private final Set reads;
@@ -19,47 +19,47 @@ public class CodeSemantics {
/**
* Creates new semantics. reads and writes, which each contain the variables which were (potentially) read from/written
- * to in this code snippet, are created empty.
- * @param keep Whether the code snippet must be kept or if it may be removed.
- * @param positionSignificance In which way the position of the code snippet relative to other code snippets of the same
- * type is significant. For the possible options see {@link PositionSignificance}.
- * @param bidirectionalBlockDepthChange How the code snippet affects the depth of bidirectional blocks, meaning blocks
+ * to in this code fragment, are created empty.
+ * @param critical Whether the code fragment must be kept as it affects the program behavior or if it may be removed.
+ * @param positionSignificance In which way the position of the code fragment relative to other tokens of the same type
+ * is significant. For the possible options see {@link PositionSignificance}.
+ * @param bidirectionalBlockDepthChange How the code fragment affects the depth of bidirectional blocks, meaning blocks
* where any statement within it may be executed after any other. This will typically be a loop.
- * @param reads A set of the variables which were (potentially) read from in the code snippet.
- * @param writes A set of the variables which were (potentially) written to in the code snippet.
+ * @param reads A set of the variables which were (potentially) read from in the code fragment.
+ * @param writes A set of the variables which were (potentially) written to in the code fragment.
*/
- private CodeSemantics(boolean keep, PositionSignificance positionSignificance, int bidirectionalBlockDepthChange, Set reads,
+ private CodeSemantics(boolean critical, PositionSignificance positionSignificance, int bidirectionalBlockDepthChange, Set reads,
Set writes) {
- this.keep = keep;
+ this.critical = critical;
this.positionSignificance = positionSignificance;
this.bidirectionalBlockDepthChange = bidirectionalBlockDepthChange;
this.reads = reads;
this.writes = writes;
}
- private CodeSemantics(boolean keep, PositionSignificance positionSignificance, int bidirectionalBlockDepthChange) {
- this(keep, positionSignificance, bidirectionalBlockDepthChange, new HashSet<>(), new HashSet<>());
+ private CodeSemantics(boolean critical, PositionSignificance positionSignificance, int bidirectionalBlockDepthChange) {
+ this(critical, positionSignificance, bidirectionalBlockDepthChange, new HashSet<>(), new HashSet<>());
}
/**
- * Creates new semantics with the following meaning: The code snippet may be removed, and its position relative to other
- * code snippets may change. Example: An assignment to a local variable.
+ * Creates new semantics with the following meaning: The code fragment may be removed, and its position relative to
+ * other code fragments may change. Example: An assignment to a local variable.
*/
public CodeSemantics() {
this(false, PositionSignificance.NONE, 0);
}
/**
- * @return new semantics with the following meaning: The code snippet may not be removed, and its position relative to
- * other code snippets may change. Example: An attribute declaration.
+ * @return new semantics with the following meaning: The code fragment may not be removed, and its position relative to
+ * other code fragments may change. Example: An attribute declaration.
*/
public static CodeSemantics createKeep() {
return new CodeSemantics(true, PositionSignificance.NONE, 0);
}
/**
- * @return new semantics with the following meaning: The code snippet may not be removed, and its position must stay
- * invariant to other code snippets of the same type. Example: A method call which is guaranteed to not result in an
+ * @return new semantics with the following meaning: The code fragment may not be removed, and its position must stay
+ * invariant to other code fragments of the same type. Example: A method call which is guaranteed to not result in an
* exception.
*/
public static CodeSemantics createCritical() {
@@ -67,16 +67,16 @@ public static CodeSemantics createCritical() {
}
/**
- * @return new semantics with the following meaning: The code snippet may not be removed, and its position must stay
- * invariant to all other code snippets. Example: A return statement.
+ * @return new semantics with the following meaning: The code fragment may not be removed, and its position must stay
+ * invariant to all other code fragments. Example: A return statement.
*/
public static CodeSemantics createControl() {
return new CodeSemantics(true, PositionSignificance.FULL, 0);
}
/**
- * @return new semantics with the following meaning: The code snippet may not be removed, and its position must stay
- * invariant to all other code snippets, which also begins a bidirectional block. Example: The beginning of a while
+ * @return new semantics with the following meaning: The code fragment may not be removed, and its position must stay
+ * invariant to all other code fragments, which also begins a bidirectional block. Example: The beginning of a while
* loop.
*/
public static CodeSemantics createLoopBegin() {
@@ -84,71 +84,71 @@ public static CodeSemantics createLoopBegin() {
}
/**
- * @return new semantics with the following meaning: The code snippet may not be removed, and its position must stay
- * invariant to all other code snippets, which also ends a bidirectional block. Example: The end of a while loop.
+ * @return new semantics with the following meaning: The code fragment may not be removed, and its position must stay
+ * invariant to all other code fragments, which also ends a bidirectional block. Example: The end of a while loop.
*/
public static CodeSemantics createLoopEnd() {
return new CodeSemantics(true, PositionSignificance.FULL, -1);
}
/**
- * @return whether this code snippet must be kept.
+ * @return whether this token is critical to the program behavior.
*/
- public boolean keep() {
- return keep;
+ public boolean isCritical() {
+ return critical;
}
/**
- * Mark this code snippet as having to be kept.
+ * Mark this token as critical to the program behavior.
*/
- public void markKeep() {
- keep = true;
+ public void markAsCritical() {
+ critical = true;
}
/**
- * @return the change this code snippet causes in the depth of bidirectional loops.
+ * @return the change this code fragment causes in the depth of bidirectional loops.
*/
public int bidirectionalBlockDepthChange() {
return bidirectionalBlockDepthChange;
}
/**
- * @return whether this code snippet has partial position significance.
+ * @return whether this code fragment has partial position significance.
*/
public boolean hasPartialPositionSignificance() {
return positionSignificance == PositionSignificance.PARTIAL;
}
/**
- * @return whether this code snippet has full position significance.
+ * @return whether this code fragment has full position significance.
*/
public boolean hasFullPositionSignificance() {
return positionSignificance == PositionSignificance.FULL;
}
/**
- * Mark this code snippet as having full position significance.
+ * Mark this code fragment as having full position significance.
*/
public void markFullPositionSignificance() {
positionSignificance = PositionSignificance.FULL;
}
/**
- * @return an unmodifiable set of the variables which were (potentially) read from in this code snippet.
+ * @return an unmodifiable set of the variables which were (potentially) read from in this code fragment.
*/
public Set reads() {
return Collections.unmodifiableSet(reads);
}
/**
- * @return an unmodifiable set of the variables which were (potentially) written to in this code snippet.
+ * @return an unmodifiable set of the variables which were (potentially) written to in this code fragment.
*/
public Set writes() {
return Collections.unmodifiableSet(writes);
}
/**
- * Add a variable to the set of variables which were (potentially) read from in this code snippet.
+ * Add a variable to the set of variables which were (potentially) read from in this code fragment.
* @param variable The variable which is added.
*/
public void addRead(Variable variable) {
@@ -156,7 +156,7 @@ public void addRead(Variable variable) {
}
/**
- * Add a variable to the set of variables which were (potentially) written to in this code snippet.
+ * Add a variable to the set of variables which were (potentially) written to in this code fragment.
* @param variable The variable which is added.
*/
public void addWrite(Variable variable) {
@@ -182,7 +182,7 @@ public static CodeSemantics join(List semanticsList) {
Set reads = new HashSet<>();
Set writes = new HashSet<>();
for (CodeSemantics semantics : semanticsList) {
- keep = keep || semantics.keep;
+ keep = keep || semantics.critical;
if (semantics.positionSignificance.compareTo(positionSignificance) > 0) {
positionSignificance = semantics.positionSignificance;
}
@@ -196,7 +196,7 @@ public static CodeSemantics join(List semanticsList) {
@Override
public String toString() {
List properties = new LinkedList<>();
- if (keep) {
+ if (critical) {
properties.add("keep");
}
if (positionSignificance != PositionSignificance.NONE) {
diff --git a/language-testutils/src/test/java/de/jplag/testutils/LanguageModuleTest.java b/language-testutils/src/test/java/de/jplag/testutils/LanguageModuleTest.java
index 36d997f7be..f659ba2b22 100644
--- a/language-testutils/src/test/java/de/jplag/testutils/LanguageModuleTest.java
+++ b/language-testutils/src/test/java/de/jplag/testutils/LanguageModuleTest.java
@@ -14,6 +14,7 @@
import java.util.Collection;
import java.util.List;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
@@ -32,6 +33,7 @@
import de.jplag.testutils.datacollector.TestData;
import de.jplag.testutils.datacollector.TestDataCollector;
import de.jplag.testutils.datacollector.TestSourceIgnoredLinesCollector;
+import de.jplag.testutils.datacollector.TokenPositionTestData;
/**
* Base class for language module tests. Automatically adds all common tests types for jplag languages.
@@ -174,7 +176,7 @@ final List testTokensContainedData() {
final void testTokenSequence(TestDataCollector.TokenListTest test) throws ParsingException, IOException {
List actual = extractTokenTypes(test.data());
List expected = new ArrayList<>(test.tokens());
- if (expected.get(expected.size() - 1) != SharedTokenType.FILE_END) {
+ if (expected.getLast() != SharedTokenType.FILE_END) {
expected.add(SharedTokenType.FILE_END);
}
assertTokensMatch(expected, actual, "Extracted token from " + test.data().describeTestSource() + " does not match expected sequence.");
@@ -196,6 +198,45 @@ final List testTokenSequenceData() {
return ignoreEmptyTestType(this.collector.getTokenSequenceTest());
}
+ /**
+ * Tests if the tokens specified for the token position tests are present in the sources
+ * @param testData The specifications of the expected tokens and the test source
+ * @throws ParsingException If the parsing fails
+ * @throws IOException If IO operations fail. If this happens, that should be unrelated to the test itself.
+ */
+ @ParameterizedTest
+ @MethodSource("getTokenPositionTestData")
+ @DisplayName("Tests if the extracted tokens contain the tokens specified in the test files.")
+ final void testTokenPositions(TokenPositionTestData testData) throws ParsingException, IOException {
+ List extractedTokens = parseTokens(testData);
+ List failedTokens = new ArrayList<>();
+
+ for (TokenPositionTestData.TokenData expectedToken : testData.getExpectedTokens()) {
+ TokenType expectedType = this.languageTokens.stream().filter(type -> type.toString().equals(expectedToken.typeName())).findFirst()
+ .orElseThrow(() -> new IOException(String.format("The token type %s does not exist.", expectedToken.typeName())));
+
+ if (extractedTokens.stream().noneMatch(token -> token.getType() == expectedType && token.getLine() == expectedToken.lineNumber()
+ && token.getColumn() == expectedToken.columnNumber() && token.getLength() == expectedToken.length())) {
+ failedTokens.add(expectedToken);
+ }
+ }
+
+ if (!failedTokens.isEmpty()) {
+ String failureDescriptors = String.join(System.lineSeparator(),
+ failedTokens.stream().map(
+ token -> token.typeName() + " at (" + token.lineNumber() + ":" + token.columnNumber() + ") with length " + token.length())
+ .toList());
+ fail("Some tokens weren't extracted with the correct properties:" + System.lineSeparator() + failureDescriptors);
+ }
+ }
+
+ /**
+ * @return All token positions tests that are configured
+ */
+ final List getTokenPositionTestData() {
+ return ignoreEmptyTestType(this.collector.getTokenPositionTestData());
+ }
+
/**
* Tests all configured test sources for a monotone order of tokens
* @param data The test source
@@ -231,8 +272,7 @@ final void testMonotoneTokenOrder(TestData data) throws ParsingException, IOExce
final void testTokenSequencesEndsWithFileEnd(TestData data) throws ParsingException, IOException {
List tokens = parseTokens(data);
- assertEquals(SharedTokenType.FILE_END, tokens.get(tokens.size() - 1).getType(),
- "Last token in " + data.describeTestSource() + " is not file end.");
+ assertEquals(SharedTokenType.FILE_END, tokens.getLast().getType(), "Last token in " + data.describeTestSource() + " is not file end.");
}
/**
@@ -251,6 +291,11 @@ final void collectTestData() {
collectTestData(this.collector);
}
+ @AfterAll
+ final void deleteTemporaryFiles() {
+ TemporaryFileHolder.deleteTemporaryFiles();
+ }
+
private List parseTokens(TestData source) throws ParsingException, IOException {
List tokens = source.parseTokens(this.language);
logger.info(TokenPrinter.printTokens(tokens));
diff --git a/language-testutils/src/test/java/de/jplag/testutils/TemporaryFileHolder.java b/language-testutils/src/test/java/de/jplag/testutils/TemporaryFileHolder.java
new file mode 100644
index 0000000000..98a95fb7fb
--- /dev/null
+++ b/language-testutils/src/test/java/de/jplag/testutils/TemporaryFileHolder.java
@@ -0,0 +1,20 @@
+package de.jplag.testutils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Stores all temporary files that are created for a {@link LanguageModuleTest} and provides the option to delete them
+ */
+public class TemporaryFileHolder {
+ public static List temporaryFiles = new ArrayList<>();
+
+ /**
+ * Deletes all temporary files that have been created up to this point
+ */
+ public static void deleteTemporaryFiles() {
+ temporaryFiles.forEach(File::delete);
+ temporaryFiles.clear();
+ }
+}
diff --git a/language-testutils/src/test/java/de/jplag/testutils/datacollector/InlineTestData.java b/language-testutils/src/test/java/de/jplag/testutils/datacollector/InlineTestData.java
index 8d93f7e156..6a87110234 100644
--- a/language-testutils/src/test/java/de/jplag/testutils/datacollector/InlineTestData.java
+++ b/language-testutils/src/test/java/de/jplag/testutils/datacollector/InlineTestData.java
@@ -8,6 +8,7 @@
import de.jplag.Language;
import de.jplag.ParsingException;
import de.jplag.Token;
+import de.jplag.testutils.TemporaryFileHolder;
import de.jplag.util.FileUtils;
/**
@@ -25,7 +26,7 @@ public List parseTokens(Language language) throws ParsingException, IOExc
File file = File.createTempFile("testSource", language.suffixes()[0]);
FileUtils.write(file, this.testData);
List tokens = language.parse(Collections.singleton(file));
- file.delete();
+ TemporaryFileHolder.temporaryFiles.add(file);
return tokens;
}
diff --git a/language-testutils/src/test/java/de/jplag/testutils/datacollector/TestDataCollector.java b/language-testutils/src/test/java/de/jplag/testutils/datacollector/TestDataCollector.java
index d5a929d06f..9b45e8d8b6 100644
--- a/language-testutils/src/test/java/de/jplag/testutils/datacollector/TestDataCollector.java
+++ b/language-testutils/src/test/java/de/jplag/testutils/datacollector/TestDataCollector.java
@@ -1,10 +1,13 @@
package de.jplag.testutils.datacollector;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -18,6 +21,7 @@ public class TestDataCollector {
private final List tokenCoverageData;
private final List containedTokenData;
private final List tokenSequenceTest;
+ private final List tokenPositionTestData;
private final List allTestData;
@@ -34,6 +38,7 @@ public TestDataCollector(File testFileLocation) {
this.tokenCoverageData = new ArrayList<>();
this.containedTokenData = new ArrayList<>();
this.tokenSequenceTest = new ArrayList<>();
+ this.tokenPositionTestData = new ArrayList<>();
this.allTestData = new ArrayList<>();
}
@@ -73,6 +78,28 @@ public TestDataContext inlineSource(String... sources) {
return new TestDataContext(data);
}
+ /**
+ * Adds all files from the given directory for token position tests. The sources can still be used for other tests,
+ * using the returned {@link TestDataContext}
+ * @param directoryName The name of the directory containing the token position tests.
+ * @return The context containing the added sources
+ * @throws IOException If the files cannot be read
+ */
+ public TestDataContext addTokenPositionTests(String directoryName) {
+ File directory = new File(this.testFileLocation, directoryName);
+ Set allTestsInDirectory = new HashSet<>();
+ for (File file : Objects.requireNonNull(directory.listFiles())) {
+ try {
+ TokenPositionTestData data = new TokenPositionTestData(file);
+ allTestsInDirectory.add(data);
+ this.tokenPositionTestData.add(data);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return new TestDataContext(allTestsInDirectory);
+ }
+
/**
* @return The test data that should be checked for source coverage
*/
@@ -101,6 +128,10 @@ public List getTokenSequenceTest() {
return Collections.unmodifiableList(tokenSequenceTest);
}
+ public List getTokenPositionTestData() {
+ return Collections.unmodifiableList(this.tokenPositionTestData);
+ }
+
/**
* @return The list of all test data
*/
diff --git a/language-testutils/src/test/java/de/jplag/testutils/datacollector/TokenPositionTestData.java b/language-testutils/src/test/java/de/jplag/testutils/datacollector/TokenPositionTestData.java
new file mode 100644
index 0000000000..fb18c5deb0
--- /dev/null
+++ b/language-testutils/src/test/java/de/jplag/testutils/datacollector/TokenPositionTestData.java
@@ -0,0 +1,99 @@
+package de.jplag.testutils.datacollector;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import de.jplag.Language;
+import de.jplag.ParsingException;
+import de.jplag.Token;
+import de.jplag.testutils.TemporaryFileHolder;
+import de.jplag.util.FileUtils;
+
+/**
+ * Test sources with token information Reads token position test specifications form a file and provides the token
+ * information for tests. The sources can be used as regular test sources.
+ */
+public class TokenPositionTestData implements TestData {
+ private final List sourceLines;
+ private final List expectedTokens;
+
+ private final String descriptor;
+ private final String fileName;
+
+ /**
+ * @param testFile The file containing the test specifications
+ * @throws IOException If the file cannot be read
+ */
+ public TokenPositionTestData(File testFile) throws IOException {
+ this.sourceLines = new ArrayList<>();
+ this.expectedTokens = new ArrayList<>();
+ this.descriptor = "(Token position file: " + testFile.getName() + ")";
+ this.fileName = testFile.getName();
+ this.readFile(testFile);
+ }
+
+ private void readFile(File testFile) throws IOException {
+ List testFileLines = FileUtils.readFileContent(testFile).lines().toList();
+ int currentLine = 0;
+
+ for (String sourceLine : testFileLines) {
+ if (sourceLine.charAt(0) == '>') {
+ this.sourceLines.add(sourceLine.substring(1));
+ currentLine++;
+ }
+
+ if (sourceLine.charAt(0) == '$') {
+ int column = sourceLine.indexOf('|');
+ String[] tokenDescriptionParts = sourceLine.split(" ", 0);
+
+ String typeName = tokenDescriptionParts[tokenDescriptionParts.length - 2];
+ int length = Integer.parseInt(tokenDescriptionParts[tokenDescriptionParts.length - 1]);
+ this.expectedTokens.add(new TokenData(typeName, currentLine, column, length));
+ }
+ }
+ }
+
+ @Override
+ public List parseTokens(Language language) throws ParsingException, IOException {
+ File file = File.createTempFile("testSource", language.suffixes()[0]);
+ FileUtils.write(file, String.join(System.lineSeparator(), sourceLines));
+ List tokens = language.parse(Collections.singleton(file));
+ TemporaryFileHolder.temporaryFiles.add(file);
+ return tokens;
+ }
+
+ @Override
+ public String[] getSourceLines() {
+ return this.sourceLines.toArray(new String[0]);
+ }
+
+ @Override
+ public String describeTestSource() {
+ return this.descriptor;
+ }
+
+ /**
+ * @return A list of the expected tokens for this test source
+ */
+ public List getExpectedTokens() {
+ return expectedTokens;
+ }
+
+ /**
+ * Information about a single token
+ * @param typeName The name of the token type
+ * @param lineNumber The line the token is in (1 based)
+ * @param columnNumber The column the token is in (1 based)
+ * @param length The length of the token
+ */
+ public record TokenData(String typeName, int lineNumber, int columnNumber, int length) {
+ }
+
+ @Override
+ public String toString() {
+ return this.fileName;
+ }
+}
diff --git a/languages/c/src/main/java/de/jplag/c/CLanguage.java b/languages/c/src/main/java/de/jplag/c/CLanguage.java
index ba99dbf498..c55dbf60b3 100644
--- a/languages/c/src/main/java/de/jplag/c/CLanguage.java
+++ b/languages/c/src/main/java/de/jplag/c/CLanguage.java
@@ -12,6 +12,7 @@
@MetaInfServices(de.jplag.Language.class)
public class CLanguage implements Language {
+ private static final String NAME = "C";
private static final String IDENTIFIER = "c";
private final Scanner scanner; // c code is scanned not parsed
@@ -27,7 +28,7 @@ public String[] suffixes() {
@Override
public String getName() {
- return "C Scanner";
+ return NAME;
}
@Override
diff --git a/languages/cpp/src/main/java/de/jplag/cpp/CPPLanguage.java b/languages/cpp/src/main/java/de/jplag/cpp/CPPLanguage.java
index c08e53dce1..b76dfc823e 100644
--- a/languages/cpp/src/main/java/de/jplag/cpp/CPPLanguage.java
+++ b/languages/cpp/src/main/java/de/jplag/cpp/CPPLanguage.java
@@ -10,6 +10,7 @@
*/
@MetaInfServices(Language.class)
public class CPPLanguage extends AbstractAntlrLanguage {
+ private static final String NAME = "C++";
private static final String IDENTIFIER = "cpp";
public CPPLanguage() {
@@ -23,7 +24,7 @@ public String[] suffixes() {
@Override
public String getName() {
- return "C++ Parser";
+ return NAME;
}
@Override
diff --git a/languages/cpp/src/main/java/de/jplag/cpp/CPPListener.java b/languages/cpp/src/main/java/de/jplag/cpp/CPPListener.java
index 6bfa81c098..9cea3eed7f 100644
--- a/languages/cpp/src/main/java/de/jplag/cpp/CPPListener.java
+++ b/languages/cpp/src/main/java/de/jplag/cpp/CPPListener.java
@@ -100,10 +100,11 @@
class CPPListener extends AbstractAntlrListener {
CPPListener() {
- visit(ClassSpecifierContext.class, rule -> rule.classHead().Union() != null).map(UNION_BEGIN, UNION_END).withSemantics(CodeSemantics::new);
+ visit(ClassSpecifierContext.class, rule -> rule.classHead().Union() != null).map(UNION_BEGIN, UNION_END).addClassScope()
+ .withSemantics(CodeSemantics::createControl);
mapClass(ClassKeyContext::Class, CLASS_BEGIN, CLASS_END);
mapClass(ClassKeyContext::Struct, STRUCT_BEGIN, STRUCT_END); // structs are basically just classes
- visit(EnumSpecifierContext.class).map(ENUM_BEGIN, ENUM_END).withSemantics(CodeSemantics::createControl);
+ visit(EnumSpecifierContext.class).map(ENUM_BEGIN, ENUM_END).addClassScope().withSemantics(CodeSemantics::createControl);
visit(FunctionDefinitionContext.class).map(FUNCTION_BEGIN, FUNCTION_END).addLocalScope().withSemantics(CodeSemantics::createControl);
diff --git a/languages/csharp/src/main/java/de/jplag/csharp/CSharpLanguage.java b/languages/csharp/src/main/java/de/jplag/csharp/CSharpLanguage.java
index aeeb53728f..67d24c60aa 100644
--- a/languages/csharp/src/main/java/de/jplag/csharp/CSharpLanguage.java
+++ b/languages/csharp/src/main/java/de/jplag/csharp/CSharpLanguage.java
@@ -9,7 +9,7 @@
*/
@MetaInfServices(de.jplag.Language.class)
public class CSharpLanguage extends AbstractAntlrLanguage {
- private static final String NAME = "C# 6 Parser";
+ private static final String NAME = "C#";
private static final String IDENTIFIER = "csharp";
private static final String[] FILE_ENDINGS = new String[] {".cs", ".CS"};
private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
diff --git a/languages/golang/src/main/java/de/jplag/golang/GoLanguage.java b/languages/golang/src/main/java/de/jplag/golang/GoLanguage.java
index e14926b43e..cb2d08eace 100644
--- a/languages/golang/src/main/java/de/jplag/golang/GoLanguage.java
+++ b/languages/golang/src/main/java/de/jplag/golang/GoLanguage.java
@@ -6,7 +6,7 @@
@MetaInfServices(de.jplag.Language.class)
public class GoLanguage extends AbstractAntlrLanguage {
- private static final String NAME = "Go Parser";
+ private static final String NAME = "Go";
private static final String IDENTIFIER = "go";
private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
private static final String[] FILE_EXTENSIONS = {".go"};
diff --git a/languages/java/src/main/java/de/jplag/java/JavaLanguage.java b/languages/java/src/main/java/de/jplag/java/JavaLanguage.java
index 4db88ef015..79d32f502c 100644
--- a/languages/java/src/main/java/de/jplag/java/JavaLanguage.java
+++ b/languages/java/src/main/java/de/jplag/java/JavaLanguage.java
@@ -14,8 +14,8 @@
*/
@MetaInfServices(de.jplag.Language.class)
public class JavaLanguage implements de.jplag.Language {
+ private static final String NAME = "Java";
private static final String IDENTIFIER = "java";
- public static final int JAVA_VERSION = 21;
private final Parser parser;
@@ -30,7 +30,7 @@ public String[] suffixes() {
@Override
public String getName() {
- return "Javac based AST plugin";
+ return NAME;
}
@Override
diff --git a/languages/java/src/main/java/de/jplag/java/JavacAdapter.java b/languages/java/src/main/java/de/jplag/java/JavacAdapter.java
index 1815c4b811..0af39bf6b6 100644
--- a/languages/java/src/main/java/de/jplag/java/JavacAdapter.java
+++ b/languages/java/src/main/java/de/jplag/java/JavacAdapter.java
@@ -29,6 +29,10 @@
public class JavacAdapter {
+ private static final String NO_ANNOTATION_PROCESSING = "-proc:none";
+ private static final String PREVIEW_FLAG = "--enable-preview";
+ private static final String RELEASE_VERSION_OPTION = "--release=";
+
private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
public void parseFiles(Set files, final Parser parser) throws ParsingException {
@@ -39,10 +43,10 @@ public void parseFiles(Set files, final Parser parser) throws ParsingExcep
try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(listener, null, guessedCharset)) {
var javaFiles = fileManager.getJavaFileObjectsFromFiles(files);
- // We need to disable annotation processing, see
- // https://stackoverflow.com/questions/72737445/system-java-compiler-behaves-different-depending-on-dependencies-defined-in-mave
- final CompilationTask task = compiler.getTask(null, fileManager, listener,
- List.of("-proc:none", "--enable-preview", "--release=" + JavaLanguage.JAVA_VERSION), null, javaFiles);
+ // We need to disable annotation processing, see https://stackoverflow.com/q/72737445
+ String releaseVersion = RELEASE_VERSION_OPTION + Runtime.version().feature(); // required for preview flag
+ List options = List.of(NO_ANNOTATION_PROCESSING, PREVIEW_FLAG, releaseVersion);
+ final CompilationTask task = compiler.getTask(null, fileManager, listener, options, null, javaFiles);
final Trees trees = Trees.instance(task);
final SourcePositions positions = new FixedSourcePositions(trees.getSourcePositions());
for (final CompilationUnitTree ast : executeCompilationTask(task, parser.logger)) {
diff --git a/languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java b/languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java
index 28bd5838a0..9589da81b7 100644
--- a/languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java
+++ b/languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java
@@ -380,10 +380,11 @@ public Void visitThrow(ThrowTree node, Void unused) {
@Override
public Void visitNewClass(NewClassTree node, Void unused) {
long start = positions.getStartPosition(ast, node);
+ long end = positions.getEndPosition(ast, node.getIdentifier());
if (!node.getTypeArguments().isEmpty()) {
addToken(JavaTokenType.J_GENERIC, start, 3 + node.getIdentifier().toString().length(), new CodeSemantics());
}
- addToken(JavaTokenType.J_NEWCLASS, start, 3, new CodeSemantics());
+ addToken(JavaTokenType.J_NEWCLASS, start, end, new CodeSemantics());
super.visitNewClass(node, null);
return null;
}
@@ -399,8 +400,8 @@ public Void visitTypeParameter(TypeParameterTree node, Void unused) {
@Override
public Void visitNewArray(NewArrayTree node, Void unused) {
long start = positions.getStartPosition(ast, node);
- long end = positions.getEndPosition(ast, node) - 1;
- addToken(JavaTokenType.J_NEWARRAY, start, 3, new CodeSemantics());
+ long end = node.getType() == null ? start + 1 : positions.getEndPosition(ast, node.getType());
+ addToken(JavaTokenType.J_NEWARRAY, start, end, new CodeSemantics());
scan(node.getType(), null);
scan(node.getDimensions(), null);
boolean hasInit = node.getInitializers() != null && !node.getInitializers().isEmpty();
@@ -411,6 +412,7 @@ public Void visitNewArray(NewArrayTree node, Void unused) {
scan(node.getInitializers(), null);
// super method has annotation processing but we have it disabled anyways
if (hasInit) {
+ end = positions.getEndPosition(ast, node.getInitializers().getLast()) - 1;
addToken(JavaTokenType.J_ARRAY_INIT_END, end, 1, new CodeSemantics());
}
return null;
@@ -419,7 +421,8 @@ public Void visitNewArray(NewArrayTree node, Void unused) {
@Override
public Void visitAssignment(AssignmentTree node, Void unused) {
long start = positions.getStartPosition(ast, node);
- addToken(JavaTokenType.J_ASSIGN, start, 1, new CodeSemantics());
+ long end = positions.getStartPosition(ast, node.getExpression()) - 1;
+ addToken(JavaTokenType.J_ASSIGN, start, end, new CodeSemantics());
variableRegistry.setNextVariableAccessType(VariableAccessType.WRITE);
return super.visitAssignment(node, null);
}
@@ -427,7 +430,8 @@ public Void visitAssignment(AssignmentTree node, Void unused) {
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, Void unused) {
long start = positions.getStartPosition(ast, node);
- addToken(JavaTokenType.J_ASSIGN, start, 1, new CodeSemantics());
+ long end = positions.getStartPosition(ast, node.getExpression()) - 1;
+ addToken(JavaTokenType.J_ASSIGN, start, end, new CodeSemantics());
variableRegistry.setNextVariableAccessType(VariableAccessType.READ_WRITE);
return super.visitCompoundAssignment(node, null);
}
@@ -437,7 +441,7 @@ public Void visitUnary(UnaryTree node, Void unused) {
if (Set.of(Tree.Kind.PREFIX_INCREMENT, Tree.Kind.POSTFIX_INCREMENT, Tree.Kind.PREFIX_DECREMENT, Tree.Kind.POSTFIX_DECREMENT)
.contains(node.getKind())) {
long start = positions.getStartPosition(ast, node);
- addToken(JavaTokenType.J_ASSIGN, start, 1, new CodeSemantics());
+ addToken(JavaTokenType.J_ASSIGN, start, node.toString().length(), new CodeSemantics());
variableRegistry.setNextVariableAccessType(VariableAccessType.READ_WRITE);
}
return super.visitUnary(node, null);
@@ -454,6 +458,8 @@ public Void visitAssert(AssertTree node, Void unused) {
public Void visitVariable(VariableTree node, Void unused) {
if (!node.getName().contentEquals(ANONYMOUS_VARIABLE_NAME)) {
long start = positions.getStartPosition(ast, node);
+ long end = positions.getEndPosition(ast, node) - 1;
+ end -= node.getInitializer() == null ? 0 : node.getInitializer().toString().length();
String name = node.getName().toString();
boolean inLocalScope = variableRegistry.inLocalScope();
// this presents a problem when classes are declared in local scopes, which can happen in ad-hoc implementations
@@ -465,7 +471,7 @@ public Void visitVariable(VariableTree node, Void unused) {
} else {
semantics = CodeSemantics.createKeep();
}
- addToken(JavaTokenType.J_VARDEF, start, node.toString().length(), semantics);
+ addToken(JavaTokenType.J_VARDEF, start, end, semantics);
// manually add variable to semantics since identifier isn't visited
variableRegistry.setNextVariableAccessType(VariableAccessType.WRITE);
variableRegistry.registerVariableAccess(name, !inLocalScope);
@@ -483,7 +489,7 @@ public Void visitConditionalExpression(ConditionalExpressionTree node, Void unus
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
long start = positions.getStartPosition(ast, node);
- long end = positions.getEndPosition(ast, node.getMethodSelect()) - start;
+ long end = positions.getEndPosition(ast, node.getMethodSelect());
CodeSemantics codeSemantics = CRITICAL_METHODS.contains(node.getMethodSelect().toString()) ? CodeSemantics.createCritical()
: CodeSemantics.createControl();
addToken(JavaTokenType.J_APPLY, start, end, codeSemantics);
diff --git a/languages/java/src/test/java/de/jplag/java/JavaLanguageTest.java b/languages/java/src/test/java/de/jplag/java/JavaLanguageTest.java
index e54955781a..a4f89d9bb0 100644
--- a/languages/java/src/test/java/de/jplag/java/JavaLanguageTest.java
+++ b/languages/java/src/test/java/de/jplag/java/JavaLanguageTest.java
@@ -73,6 +73,8 @@ protected void collectTestData(TestDataCollector collector) {
collector.testFile("AnonymousVariables.java").testTokenSequence(J_CLASS_BEGIN, J_METHOD_BEGIN, J_VARDEF, J_IF_BEGIN, J_IF_END, J_METHOD_END,
J_CLASS_END);
+
+ collector.addTokenPositionTests("tokenPositions");
}
@Override
diff --git a/languages/java/src/test/resources/de/jplag/java/tokenPositions/VarDef_1.java b/languages/java/src/test/resources/de/jplag/java/tokenPositions/VarDef_1.java
new file mode 100644
index 0000000000..276f17351b
--- /dev/null
+++ b/languages/java/src/test/resources/de/jplag/java/tokenPositions/VarDef_1.java
@@ -0,0 +1,4 @@
+>class Test {
+> int test;
+$ | J_VARDEF 8
+>}
diff --git a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinLanguage.java b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinLanguage.java
index 487effaa24..9b7678681e 100644
--- a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinLanguage.java
+++ b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinLanguage.java
@@ -10,7 +10,7 @@
@MetaInfServices(de.jplag.Language.class)
public class KotlinLanguage extends AbstractAntlrLanguage {
- private static final String NAME = "Kotlin Parser";
+ private static final String NAME = "Kotlin";
private static final String IDENTIFIER = "kotlin";
private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
private static final String[] FILE_EXTENSIONS = {".kt"};
diff --git a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRLanguage.java b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRLanguage.java
index 846a047e68..0d9ad7f150 100644
--- a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRLanguage.java
+++ b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRLanguage.java
@@ -11,7 +11,7 @@
@MetaInfServices(Language.class)
public class LLVMIRLanguage extends AbstractAntlrLanguage {
- private static final String NAME = "LLVMIR Parser";
+ private static final String NAME = "LLVM IR";
private static final String IDENTIFIER = "llvmir";
private static final int DEFAULT_MIN_TOKEN_MATCH = 70;
private static final String[] FILE_EXTENSIONS = {".ll"};
diff --git a/languages/python-3/src/main/java/de/jplag/python3/PythonLanguage.java b/languages/python-3/src/main/java/de/jplag/python3/PythonLanguage.java
index 3df6587284..2a70481276 100644
--- a/languages/python-3/src/main/java/de/jplag/python3/PythonLanguage.java
+++ b/languages/python-3/src/main/java/de/jplag/python3/PythonLanguage.java
@@ -6,7 +6,7 @@
@MetaInfServices(de.jplag.Language.class)
public class PythonLanguage extends AbstractAntlrLanguage {
-
+ private static final String NAME = "Python";
private static final String IDENTIFIER = "python3";
public PythonLanguage() {
@@ -20,7 +20,7 @@ public String[] suffixes() {
@Override
public String getName() {
- return "Python3 Parser";
+ return NAME;
}
@Override
diff --git a/languages/rlang/src/main/java/de/jplag/rlang/RLanguage.java b/languages/rlang/src/main/java/de/jplag/rlang/RLanguage.java
index d1d02cfa9c..3e7e3ab7f5 100644
--- a/languages/rlang/src/main/java/de/jplag/rlang/RLanguage.java
+++ b/languages/rlang/src/main/java/de/jplag/rlang/RLanguage.java
@@ -9,7 +9,7 @@
*/
@MetaInfServices(de.jplag.Language.class)
public class RLanguage extends AbstractAntlrLanguage {
- private static final String NAME = "R Parser";
+ private static final String NAME = "R";
private static final String IDENTIFIER = "rlang";
private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
private static final String[] FILE_EXTENSION = {".R", ".r"};
diff --git a/languages/rust/src/main/java/de/jplag/rust/RustLanguage.java b/languages/rust/src/main/java/de/jplag/rust/RustLanguage.java
index 50f0826e04..1a5cfa4f81 100644
--- a/languages/rust/src/main/java/de/jplag/rust/RustLanguage.java
+++ b/languages/rust/src/main/java/de/jplag/rust/RustLanguage.java
@@ -16,7 +16,7 @@
public class RustLanguage implements de.jplag.Language {
protected static final String[] FILE_EXTENSIONS = {".rs"};
- private static final String NAME = "Rust Language Module";
+ private static final String NAME = "Rust";
private static final String IDENTIFIER = "rust";
private static final int MINIMUM_TOKEN_MATCH = 8;
diff --git a/languages/scala/pom.xml b/languages/scala/pom.xml
index 72861aea51..dac8a912ab 100644
--- a/languages/scala/pom.xml
+++ b/languages/scala/pom.xml
@@ -10,7 +10,7 @@
scala
- 2.13.14
+ 2.13.152.13
@@ -25,7 +25,7 @@
org.scalametascalameta_${scala.compat.version}
- 4.9.7
+ 4.11.0
diff --git a/languages/scala/src/main/scala/de/jplag/scala/ScalaLanguage.scala b/languages/scala/src/main/scala/de/jplag/scala/ScalaLanguage.scala
index 424b0f7334..bf226a5ced 100644
--- a/languages/scala/src/main/scala/de/jplag/scala/ScalaLanguage.scala
+++ b/languages/scala/src/main/scala/de/jplag/scala/ScalaLanguage.scala
@@ -14,7 +14,7 @@ class ScalaLanguage extends de.jplag.Language {
override def suffixes: Array[String] = fileExtensions
- override def getName = "Scala parser"
+ override def getName = "Scala"
override def getIdentifier = "scala"
diff --git a/languages/scheme/src/main/java/de/jplag/scheme/SchemeLanguage.java b/languages/scheme/src/main/java/de/jplag/scheme/SchemeLanguage.java
index 0ebbf4ef97..13e0c89f72 100644
--- a/languages/scheme/src/main/java/de/jplag/scheme/SchemeLanguage.java
+++ b/languages/scheme/src/main/java/de/jplag/scheme/SchemeLanguage.java
@@ -12,6 +12,7 @@
@MetaInfServices(de.jplag.Language.class)
public class SchemeLanguage implements de.jplag.Language {
+ private static final String NAME = "Scheme";
private static final String IDENTIFIER = "scheme";
private final de.jplag.scheme.Parser parser;
@@ -26,7 +27,7 @@ public String[] suffixes() {
@Override
public String getName() {
- return "SchemeR4RS Parser [basic markup]";
+ return NAME;
}
@Override
diff --git a/languages/scxml/pom.xml b/languages/scxml/pom.xml
index b91e5adcd6..6e30202c70 100644
--- a/languages/scxml/pom.xml
+++ b/languages/scxml/pom.xml
@@ -12,7 +12,7 @@
org.assertjassertj-core
- 3.26.0
+ 3.26.3test
diff --git a/languages/scxml/src/main/java/de/jplag/scxml/ScxmlLanguage.java b/languages/scxml/src/main/java/de/jplag/scxml/ScxmlLanguage.java
index ec6316f4dd..ba4af3e8eb 100644
--- a/languages/scxml/src/main/java/de/jplag/scxml/ScxmlLanguage.java
+++ b/languages/scxml/src/main/java/de/jplag/scxml/ScxmlLanguage.java
@@ -26,7 +26,7 @@ public class ScxmlLanguage implements de.jplag.Language {
*/
public static final String VIEW_FILE_SUFFIX = ".scxmlview";
- private static final String NAME = "SCXML (Statechart XML)";
+ private static final String NAME = "SCXML";
private static final String IDENTIFIER = "scxml";
private static final int DEFAULT_MIN_TOKEN_MATCH = 6;
diff --git a/languages/swift/src/main/java/de/jplag/swift/SwiftLanguage.java b/languages/swift/src/main/java/de/jplag/swift/SwiftLanguage.java
index 87e13269fa..82a56d11e4 100644
--- a/languages/swift/src/main/java/de/jplag/swift/SwiftLanguage.java
+++ b/languages/swift/src/main/java/de/jplag/swift/SwiftLanguage.java
@@ -17,7 +17,7 @@ public class SwiftLanguage implements de.jplag.Language {
private static final String IDENTIFIER = "swift";
- private static final String NAME = "Swift Parser";
+ private static final String NAME = "Swift";
private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
private static final String[] FILE_EXTENSIONS = {".swift"};
private final SwiftParserAdapter parserAdapter;
diff --git a/languages/text/src/main/java/de/jplag/text/NaturalLanguage.java b/languages/text/src/main/java/de/jplag/text/NaturalLanguage.java
index 5727130097..057c06e56e 100644
--- a/languages/text/src/main/java/de/jplag/text/NaturalLanguage.java
+++ b/languages/text/src/main/java/de/jplag/text/NaturalLanguage.java
@@ -18,6 +18,7 @@
public class NaturalLanguage implements de.jplag.Language {
private static final String IDENTIFIER = "text";
+ private static final String NAME = "Text (naive)";
private final ParserAdapter parserAdapter;
public NaturalLanguage() {
@@ -31,7 +32,7 @@ public String[] suffixes() {
@Override
public String getName() {
- return "Text Parser (naive)";
+ return NAME;
}
@Override
diff --git a/languages/text/src/main/java/de/jplag/text/ParserAdapter.java b/languages/text/src/main/java/de/jplag/text/ParserAdapter.java
index c3d1ccc831..09f15e15b9 100644
--- a/languages/text/src/main/java/de/jplag/text/ParserAdapter.java
+++ b/languages/text/src/main/java/de/jplag/text/ParserAdapter.java
@@ -51,7 +51,7 @@ public List parse(Set files) throws ParsingException {
private void parseFile(File file) throws ParsingException {
this.currentFile = file;
this.currentLine = 1; // lines start at 1
- this.currentLineBreakIndex = 0;
+ this.currentLineBreakIndex = -1;
String content = readFile(file);
int lastTokenEnd = 0;
CoreDocument coreDocument = pipeline.processToCoreDocument(content);
diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguage.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguage.java
index 9fa5ad514c..8dda2b3486 100644
--- a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguage.java
+++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguage.java
@@ -11,6 +11,7 @@
public class TypeScriptLanguage extends AbstractAntlrLanguage {
private static final String IDENTIFIER = "typescript";
+ private static final String NAME = "TypeScript";
private final TypeScriptLanguageOptions options = new TypeScriptLanguageOptions();
@Override
@@ -20,7 +21,7 @@ public String[] suffixes() {
@Override
public String getName() {
- return "Typescript Parser";
+ return NAME;
}
@Override
diff --git a/pom.xml b/pom.xml
index 1b8f58ecb5..de038e5a61 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,15 +75,15 @@
21212.43.0
- 2.0.13
- 5.10.3
+ 2.0.16
+ 5.11.32.7.7
- 4.13.1
- 2.36.0
- 2.30.0
- 2.37.0
- 3.20.200
+ 4.13.2
+ 2.37.0
+ 2.31.0
+ 2.38.0
+ 3.21.01.1.0
@@ -141,7 +141,7 @@
com.fasterxml.jackson.corejackson-databind
- 2.17.1
+ 2.18.0
@@ -168,7 +168,7 @@
org.mockitomockito-core
- 5.12.0
+ 5.14.2test
@@ -240,7 +240,7 @@
org.apache.maven.pluginsmaven-surefire-plugin
- 3.3.0
+ 3.5.1org.jacoco
@@ -269,12 +269,12 @@
org.apache.maven.pluginsmaven-gpg-plugin
- 3.2.4
+ 3.2.7org.apache.maven.pluginsmaven-deploy-plugin
- 3.1.2
+ 3.1.3
@@ -331,7 +331,7 @@
org.apache.maven.pluginsmaven-javadoc-plugin
- 3.7.0
+ 3.10.1attach-javadocs
diff --git a/report-viewer/.eslintrc.cjs b/report-viewer/.eslintrc.cjs
index dd9e51bf7f..894ad63d50 100644
--- a/report-viewer/.eslintrc.cjs
+++ b/report-viewer/.eslintrc.cjs
@@ -14,7 +14,7 @@ module.exports = {
},
plugins: ['@typescript-eslint', 'vue'],
rules: {
- 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ 'no-console': ['error', { allow: ['warn', 'error', 'info'] }],
'no-restricted-exports': ['error', { restrictDefaultExports: { direct: true } }],
'vue/no-setup-props-reactivity-loss': 'error'
},
diff --git a/report-viewer/.husky/pre-commit b/report-viewer/.husky/pre-commit
deleted file mode 100644
index b4eb63f65d..0000000000
--- a/report-viewer/.husky/pre-commit
+++ /dev/null
@@ -1,2 +0,0 @@
-cd ./report-viewer
-npx lint-staged
\ No newline at end of file
diff --git a/report-viewer/README.md b/report-viewer/README.md
index c39bf4f8ef..f1d633c300 100644
--- a/report-viewer/README.md
+++ b/report-viewer/README.md
@@ -1,37 +1,52 @@
# JPlag Report Viewer
-The JPlag Report Viewer is a Vue 3 + Typescript standalone application that can be used to display the JSON files generated by the JPlag reporting. The application requires Node.js and npm to be installed on the system.
+The JPlag Report Viewer is a web application that can be used to display the zip file generated by JPlag.
-Before the first run execute:
-
-- Install necessary dependencies by running `npm install` in the /report-viewer folder.
-- Start the application by running the `npm run dev` command in the /report-viewer folder.
-- The report viewer is now accessible in your browser under http://localhost:8080/
## Project setup
+The application requires Node.js and npm to be installed on the system.
```
npm install
```
-### Compiles and hot-reloads for development
+### Run the development server
```
npm run dev
```
-### Compiles and minifies for production
+### Compile and build
+There are different ways to build the report-viewer.
+
+The report viewer will be built and packaged with the cli in a jar file if built with the `with-report-viewer` profile:
+```
+mvn -Pwith-report-viewer clean package assembly:single
+```
+
+To build it in the standard way, without any base url, run:
```
npm run build
```
-### Lints and fixes files
+For production builds (for example to host on GitHub Pages in a repository called `JPlag`) run:
```
-npm run lint
+npm run build:prod
```
+When hosting this build it will need to be accessible under `yourdomain.tld/JPlag/`.
-### Format files with prettier
+To build the demo version run:
```
-npm run format
+npm run build:demo
```
+Similar to the production build, this build will have `demo` as its base url.
-### Customize configuration
-See [Configuration Reference](https://cli.vuejs.org/config/).
+
+## Contributing
+
+We're happy to incorporate all improvements to JPlag into this codebase. Feel free to fork the project and send pull requests. Please consider our guidelines for contributions.
+
+Before committing please run the following commands to ensure that the code is properly formatted and linted.
+```
+npm run format
+npm run lint
+```
+This can also be done automatically by the precommit hooks. They get automatically installed when running `npm install`.
diff --git a/report-viewer/package-lock.json b/report-viewer/package-lock.json
index a1bce2a994..30c9a435d6 100644
--- a/report-viewer/package-lock.json
+++ b/report-viewer/package-lock.json
@@ -8,48 +8,48 @@
"name": "report-viewer",
"version": "0.0.0",
"dependencies": {
- "@fortawesome/fontawesome-svg-core": "^6.5.2",
- "@fortawesome/free-brands-svg-icons": "^6.5.2",
- "@fortawesome/free-solid-svg-icons": "^6.5.2",
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-brands-svg-icons": "^6.6.0",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.8",
- "chart.js": "^4.4.3",
- "chartjs-chart-graph": "^4.3.1",
+ "chart.js": "^4.4.5",
+ "chartjs-chart-graph": "^4.3.3",
"chartjs-plugin-datalabels": "^2.2.0",
- "highlight.js": "^11.9.0",
+ "highlight.js": "^11.10.0",
"jszip": "^3.10.0",
- "pinia": "^2.1.7",
+ "pinia": "^2.2.4",
"slash": "^5.1.0",
- "vue": "^3.4.31",
+ "vue": "^3.5.12",
"vue-chartjs": "^5.3.1",
"vue-draggable-next": "^2.2.1",
- "vue-router": "^4.4.0",
+ "vue-router": "^4.4.5",
"vue-virtual-scroller": "^2.0.0-beta.8"
},
"devDependencies": {
- "@playwright/test": "^1.45.1",
- "@rushstack/eslint-patch": "^1.10.3",
+ "@pinia/testing": "^0.1.6",
+ "@playwright/test": "^1.48.0",
+ "@rushstack/eslint-patch": "^1.10.4",
"@types/jsdom": "^21.1.7",
- "@types/node": "^18.19.39",
- "@vitejs/plugin-vue": "^5.0.5",
- "@vue/eslint-config-prettier": "^9.0.0",
+ "@types/node": "^22.7.9",
+ "@vitejs/plugin-vue": "^5.1.4",
+ "@vue/eslint-config-prettier": "^10.1.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
- "autoprefixer": "^10.4.19",
- "eslint": "^8.57.0",
- "eslint-plugin-vue": "^9.26.0",
- "husky": "^9.0.11",
- "jsdom": "^24.1.0",
- "lint-staged": "^15.2.7",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^8.57.1",
+ "eslint-plugin-vue": "^9.29.1",
+ "jsdom": "^25.0.1",
+ "lint-staged": "^15.2.10",
"npm-run-all": "^4.1.5",
- "postcss": "^8.4.35",
- "prettier": "^3.3.2",
- "prettier-plugin-tailwindcss": "^0.6.5",
- "tailwindcss": "^3.4.4",
- "typescript": "^5.5.3",
- "vite": "^5.3.1",
- "vitest": "^1.6.0",
- "vue-tsc": "^2.0.21"
+ "postcss": "^8.4.45",
+ "prettier": "^3.3.3",
+ "prettier-plugin-tailwindcss": "^0.6.8",
+ "tailwindcss": "^3.4.13",
+ "typescript": "^5.5.4",
+ "vite": "^5.4.10",
+ "vitest": "^2.1.2",
+ "vue-tsc": "^2.1.6"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -73,10 +73,29 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
+ "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz",
+ "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/parser": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
- "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
+ "version": "7.25.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz",
+ "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==",
+ "dependencies": {
+ "@babel/types": "^7.25.8"
+ },
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -84,6 +103,19 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/types": {
+ "version": "7.25.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz",
+ "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.7",
+ "@babel/helper-validator-identifier": "^7.25.7",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -522,77 +554,55 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.57.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
- "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
- "node_modules/@fortawesome/fontawesome-svg-core": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz",
- "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==",
- "hasInstallScript": true,
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "6.5.2"
- },
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
+ "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
"engines": {
"node": ">=6"
}
},
- "node_modules/@fortawesome/fontawesome-svg-core/node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz",
- "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==",
- "hasInstallScript": true,
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
+ "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ },
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz",
- "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==",
- "hasInstallScript": true,
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
"dependencies": {
- "@fortawesome/fontawesome-common-types": "6.5.2"
+ "@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
- "node_modules/@fortawesome/free-brands-svg-icons/node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz",
- "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==",
- "hasInstallScript": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@fortawesome/free-solid-svg-icons": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
- "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
- "hasInstallScript": true,
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
"dependencies": {
- "@fortawesome/fontawesome-common-types": "6.5.2"
+ "@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
- "node_modules/@fortawesome/free-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz",
- "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==",
- "hasInstallScript": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@fortawesome/vue-fontawesome": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz",
@@ -603,12 +613,13 @@
}
},
"node_modules/@humanwhocodes/config-array": {
- "version": "0.11.14",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
- "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
"dev": true,
"dependencies": {
- "@humanwhocodes/object-schema": "^2.0.2",
+ "@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
@@ -652,9 +663,10 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
- "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
"dev": true
},
"node_modules/@isaacs/cliui": {
@@ -701,59 +713,47 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/@jest/schemas": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
- "dev": true,
- "dependencies": {
- "@sinclair/typebox": "^0.27.8"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
- "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
- "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.9"
+ "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
- "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
- "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.22",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
- "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -806,6 +806,47 @@
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"dev": true
},
+ "node_modules/@pinia/testing": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.6.tgz",
+ "integrity": "sha512-Q40s3kpjXpjmcnc61l84wyG83yVmkBi5rRdSoPpwQoRfSnNKKr52XjFFt6hP8iBxehYS9NR+D57T1uzgnEVPHg==",
+ "dev": true,
+ "dependencies": {
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "pinia": ">=2.2.3"
+ }
+ },
+ "node_modules/@pinia/testing/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -829,12 +870,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
- "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==",
+ "version": "1.48.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0.tgz",
+ "integrity": "sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==",
"dev": true,
"dependencies": {
- "playwright": "1.45.1"
+ "playwright": "1.48.0"
},
"bin": {
"playwright": "cli.js"
@@ -844,9 +885,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz",
- "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz",
+ "integrity": "sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==",
"cpu": [
"arm"
],
@@ -857,9 +898,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz",
- "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz",
+ "integrity": "sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==",
"cpu": [
"arm64"
],
@@ -870,9 +911,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz",
- "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz",
+ "integrity": "sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==",
"cpu": [
"arm64"
],
@@ -883,9 +924,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz",
- "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz",
+ "integrity": "sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==",
"cpu": [
"x64"
],
@@ -896,9 +937,22 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz",
- "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz",
+ "integrity": "sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz",
+ "integrity": "sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==",
"cpu": [
"arm"
],
@@ -909,9 +963,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz",
- "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz",
+ "integrity": "sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==",
"cpu": [
"arm64"
],
@@ -922,9 +976,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz",
- "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz",
+ "integrity": "sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==",
"cpu": [
"arm64"
],
@@ -935,11 +989,11 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz",
- "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz",
+ "integrity": "sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==",
"cpu": [
- "ppc64le"
+ "ppc64"
],
"dev": true,
"optional": true,
@@ -948,9 +1002,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz",
- "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz",
+ "integrity": "sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==",
"cpu": [
"riscv64"
],
@@ -961,9 +1015,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz",
- "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz",
+ "integrity": "sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==",
"cpu": [
"s390x"
],
@@ -974,9 +1028,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz",
- "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz",
+ "integrity": "sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==",
"cpu": [
"x64"
],
@@ -987,9 +1041,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz",
- "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz",
+ "integrity": "sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==",
"cpu": [
"x64"
],
@@ -1000,9 +1054,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz",
- "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz",
+ "integrity": "sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==",
"cpu": [
"arm64"
],
@@ -1013,9 +1067,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz",
- "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz",
+ "integrity": "sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==",
"cpu": [
"ia32"
],
@@ -1026,9 +1080,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz",
- "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz",
+ "integrity": "sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==",
"cpu": [
"x64"
],
@@ -1039,26 +1093,20 @@
]
},
"node_modules/@rushstack/eslint-patch": {
- "version": "1.10.3",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz",
- "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==",
- "dev": true
- },
- "node_modules/@sinclair/typebox": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "version": "1.10.4",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz",
+ "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==",
"dev": true
},
"node_modules/@types/d3-force": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz",
- "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA=="
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="
},
"node_modules/@types/d3-hierarchy": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz",
- "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw=="
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="
},
"node_modules/@types/estree": {
"version": "1.0.5",
@@ -1084,12 +1132,12 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "18.19.39",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz",
- "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==",
+ "version": "22.7.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz",
+ "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==",
"dev": true,
"dependencies": {
- "undici-types": "~5.26.4"
+ "undici-types": "~6.19.2"
}
},
"node_modules/@types/semver": {
@@ -1301,9 +1349,9 @@
"dev": true
},
"node_modules/@vitejs/plugin-vue": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz",
- "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==",
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz",
+ "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
"dev": true,
"engines": {
"node": "^18.0.0 || >=20.0.0"
@@ -1314,137 +1362,145 @@
}
},
"node_modules/@vitest/expect": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz",
- "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz",
+ "integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==",
"dev": true,
"dependencies": {
- "@vitest/spy": "1.6.0",
- "@vitest/utils": "1.6.0",
- "chai": "^4.3.10"
+ "@vitest/spy": "2.1.2",
+ "@vitest/utils": "2.1.2",
+ "chai": "^5.1.1",
+ "tinyrainbow": "^1.2.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/runner": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz",
- "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==",
+ "node_modules/@vitest/mocker": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz",
+ "integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==",
"dev": true,
"dependencies": {
- "@vitest/utils": "1.6.0",
- "p-limit": "^5.0.0",
- "pathe": "^1.1.1"
+ "@vitest/spy": "^2.1.0-beta.1",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.11"
},
"funding": {
"url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@vitest/spy": "2.1.2",
+ "msw": "^2.3.5",
+ "vite": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
}
},
- "node_modules/@vitest/runner/node_modules/p-limit": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
- "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+ "node_modules/@vitest/pretty-format": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz",
+ "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==",
"dev": true,
"dependencies": {
- "yocto-queue": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
+ "tinyrainbow": "^1.2.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/runner/node_modules/yocto-queue": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
- "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+ "node_modules/@vitest/runner": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz",
+ "integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==",
"dev": true,
- "engines": {
- "node": ">=12.20"
+ "dependencies": {
+ "@vitest/utils": "2.1.2",
+ "pathe": "^1.1.2"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz",
- "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz",
+ "integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==",
"dev": true,
"dependencies": {
- "magic-string": "^0.30.5",
- "pathe": "^1.1.1",
- "pretty-format": "^29.7.0"
+ "@vitest/pretty-format": "2.1.2",
+ "magic-string": "^0.30.11",
+ "pathe": "^1.1.2"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/spy": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz",
- "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz",
+ "integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==",
"dev": true,
"dependencies": {
- "tinyspy": "^2.2.0"
+ "tinyspy": "^3.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz",
- "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz",
+ "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==",
"dev": true,
"dependencies": {
- "diff-sequences": "^29.6.3",
- "estree-walker": "^3.0.3",
- "loupe": "^2.3.7",
- "pretty-format": "^29.7.0"
+ "@vitest/pretty-format": "2.1.2",
+ "loupe": "^3.1.1",
+ "tinyrainbow": "^1.2.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@volar/language-core": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.3.0.tgz",
- "integrity": "sha512-pvhL24WUh3VDnv7Yw5N1sjhPtdx7q9g+Wl3tggmnkMcyK8GcCNElF2zHiKznryn0DiUGk+eez/p2qQhz+puuHw==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.1.tgz",
+ "integrity": "sha512-9AKhC7Qn2mQYxj7Dz3bVxeOk7gGJladhWixUYKef/o0o7Bm4an+A3XvmcTHVqZ8stE6lBVH++g050tBtJ4TZPQ==",
"dev": true,
"dependencies": {
- "@volar/source-map": "2.3.0"
+ "@volar/source-map": "2.4.1"
}
},
"node_modules/@volar/source-map": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.3.0.tgz",
- "integrity": "sha512-G/228aZjAOGhDjhlyZ++nDbKrS9uk+5DMaEstjvzglaAw7nqtDyhnQAsYzUg6BMP9BtwZ59RIw5HGePrutn00Q==",
- "dev": true,
- "dependencies": {
- "muggle-string": "^0.4.0"
- }
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.1.tgz",
+ "integrity": "sha512-Xq6ep3OZg9xUqN90jEgB9ztX5SsTz1yiV8wiQbcYNjWkek+Ie3dc8l7AVt3EhDm9mSIR58oWczHkzM2H6HIsmQ==",
+ "dev": true
},
"node_modules/@volar/typescript": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.3.0.tgz",
- "integrity": "sha512-PtUwMM87WsKVeLJN33GSTUjBexlKfKgouWlOUIv7pjrOnTwhXHZNSmpc312xgXdTjQPpToK6KXSIcKu9sBQ5LQ==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.1.tgz",
+ "integrity": "sha512-UoRzC0PXcwajFQTu8XxKSYNsWNBtVja6Y9gC8eLv7kYm+UEKJCcZ8g7dialsOYA0HKs3Vpg57MeCsawFLC6m9Q==",
"dev": true,
"dependencies": {
- "@volar/language-core": "2.3.0",
+ "@volar/language-core": "2.4.1",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz",
- "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz",
+ "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==",
"dependencies": {
- "@babel/parser": "^7.24.7",
- "@vue/shared": "3.4.31",
+ "@babel/parser": "^7.25.3",
+ "@vue/shared": "3.5.12",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.0"
@@ -1456,27 +1512,27 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@vue/compiler-dom": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz",
- "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz",
+ "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==",
"dependencies": {
- "@vue/compiler-core": "3.4.31",
- "@vue/shared": "3.4.31"
+ "@vue/compiler-core": "3.5.12",
+ "@vue/shared": "3.5.12"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz",
- "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==",
- "dependencies": {
- "@babel/parser": "^7.24.7",
- "@vue/compiler-core": "3.4.31",
- "@vue/compiler-dom": "3.4.31",
- "@vue/compiler-ssr": "3.4.31",
- "@vue/shared": "3.4.31",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz",
+ "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==",
+ "dependencies": {
+ "@babel/parser": "^7.25.3",
+ "@vue/compiler-core": "3.5.12",
+ "@vue/compiler-dom": "3.5.12",
+ "@vue/compiler-ssr": "3.5.12",
+ "@vue/shared": "3.5.12",
"estree-walker": "^2.0.2",
- "magic-string": "^0.30.10",
- "postcss": "^8.4.38",
+ "magic-string": "^0.30.11",
+ "postcss": "^8.4.47",
"source-map-js": "^1.2.0"
}
},
@@ -1486,30 +1542,40 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz",
- "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz",
+ "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==",
"dependencies": {
- "@vue/compiler-dom": "3.4.31",
- "@vue/shared": "3.4.31"
+ "@vue/compiler-dom": "3.5.12",
+ "@vue/shared": "3.5.12"
+ }
+ },
+ "node_modules/@vue/compiler-vue2": {
+ "version": "2.7.16",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
+ "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
+ "dev": true,
+ "dependencies": {
+ "de-indent": "^1.0.2",
+ "he": "^1.2.0"
}
},
"node_modules/@vue/devtools-api": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
- "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
},
"node_modules/@vue/eslint-config-prettier": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
- "integrity": "sha512-z1ZIAAUS9pKzo/ANEfd2sO+v2IUalz7cM/cTLOZ7vRFOPk5/xuRKQteOu1DErFLAh/lYGXMVZ0IfYKlyInuDVg==",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.1.0.tgz",
+ "integrity": "sha512-J6wV91y2pXc0Phha01k0WOHBTPsoSTf4xlmMjoKaeSxBpAdsgTppGF5RZRdOHM7OA74zAXD+VLANrtYXpiPKkQ==",
"dev": true,
"dependencies": {
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-prettier": "^5.0.0"
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.2.1"
},
"peerDependencies": {
- "eslint": ">= 8.0.0",
+ "eslint": ">= 8.21.0",
"prettier": ">= 3.0.0"
}
},
@@ -1538,18 +1604,19 @@
}
},
"node_modules/@vue/language-core": {
- "version": "2.0.21",
- "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.21.tgz",
- "integrity": "sha512-vjs6KwnCK++kIXT+eI63BGpJHfHNVJcUCr3RnvJsccT3vbJnZV5IhHR2puEkoOkIbDdp0Gqi1wEnv3hEd3WsxQ==",
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.6.tgz",
+ "integrity": "sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==",
"dev": true,
"dependencies": {
- "@volar/language-core": "~2.3.0-alpha.15",
+ "@volar/language-core": "~2.4.1",
"@vue/compiler-dom": "^3.4.0",
+ "@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.4.0",
"computeds": "^0.0.1",
"minimatch": "^9.0.3",
- "path-browserify": "^1.0.1",
- "vue-template-compiler": "^2.7.14"
+ "muggle-string": "^0.4.1",
+ "path-browserify": "^1.0.1"
},
"peerDependencies": {
"typescript": "*"
@@ -1561,49 +1628,49 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz",
- "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz",
+ "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==",
"dependencies": {
- "@vue/shared": "3.4.31"
+ "@vue/shared": "3.5.12"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz",
- "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz",
+ "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==",
"dependencies": {
- "@vue/reactivity": "3.4.31",
- "@vue/shared": "3.4.31"
+ "@vue/reactivity": "3.5.12",
+ "@vue/shared": "3.5.12"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz",
- "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz",
+ "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==",
"dependencies": {
- "@vue/reactivity": "3.4.31",
- "@vue/runtime-core": "3.4.31",
- "@vue/shared": "3.4.31",
+ "@vue/reactivity": "3.5.12",
+ "@vue/runtime-core": "3.5.12",
+ "@vue/shared": "3.5.12",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz",
- "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz",
+ "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==",
"dependencies": {
- "@vue/compiler-ssr": "3.4.31",
- "@vue/shared": "3.4.31"
+ "@vue/compiler-ssr": "3.5.12",
+ "@vue/shared": "3.5.12"
},
"peerDependencies": {
- "vue": "3.4.31"
+ "vue": "3.5.12"
}
},
"node_modules/@vue/shared": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.31.tgz",
- "integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA=="
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz",
+ "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg=="
},
"node_modules/@vue/test-utils": {
"version": "2.4.6",
@@ -1651,15 +1718,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/acorn-walk": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
- "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
- "dev": true,
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
@@ -1689,12 +1747,15 @@
}
},
"node_modules/ansi-escapes": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz",
- "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
+ "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
"dev": true,
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
"engines": {
- "node": ">=14.16"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -1799,12 +1860,12 @@
}
},
"node_modules/assertion-error": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
- "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"engines": {
- "node": "*"
+ "node": ">=12"
}
},
"node_modules/asynckit": {
@@ -1814,9 +1875,9 @@
"dev": true
},
"node_modules/autoprefixer": {
- "version": "10.4.19",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
- "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
+ "version": "10.4.20",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
+ "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"dev": true,
"funding": [
{
@@ -1833,11 +1894,11 @@
}
],
"dependencies": {
- "browserslist": "^4.23.0",
- "caniuse-lite": "^1.0.30001599",
+ "browserslist": "^4.23.3",
+ "caniuse-lite": "^1.0.30001646",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
+ "picocolors": "^1.0.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
@@ -1905,9 +1966,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.23.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
- "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+ "version": "4.23.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
+ "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
"dev": true,
"funding": [
{
@@ -1924,10 +1985,10 @@
}
],
"dependencies": {
- "caniuse-lite": "^1.0.30001587",
- "electron-to-chromium": "^1.4.668",
- "node-releases": "^2.0.14",
- "update-browserslist-db": "^1.0.13"
+ "caniuse-lite": "^1.0.30001646",
+ "electron-to-chromium": "^1.5.4",
+ "node-releases": "^2.0.18",
+ "update-browserslist-db": "^1.1.0"
},
"bin": {
"browserslist": "cli.js"
@@ -1978,9 +2039,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001605",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz",
- "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==",
+ "version": "1.0.30001651",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
+ "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
"dev": true,
"funding": [
{
@@ -1998,21 +2059,19 @@
]
},
"node_modules/chai": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
- "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz",
+ "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==",
"dev": true,
"dependencies": {
- "assertion-error": "^1.1.0",
- "check-error": "^1.0.3",
- "deep-eql": "^4.1.3",
- "get-func-name": "^2.0.2",
- "loupe": "^2.3.6",
- "pathval": "^1.1.1",
- "type-detect": "^4.0.8"
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
},
"engines": {
- "node": ">=4"
+ "node": ">=12"
}
},
"node_modules/chalk": {
@@ -2032,9 +2091,9 @@
}
},
"node_modules/chart.js": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz",
- "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==",
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.5.tgz",
+ "integrity": "sha512-CVVjg1RYTJV9OCC8WeJPMx8gsV8K6WIyIEQUE3ui4AR9Hfgls9URri6Ja3hyMVBbTF8Q2KFa19PE815gWcWhng==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -2043,12 +2102,12 @@
}
},
"node_modules/chartjs-chart-graph": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/chartjs-chart-graph/-/chartjs-chart-graph-4.3.1.tgz",
- "integrity": "sha512-dZQcR+rYxg0zDG229Um59e/4MHoOqO2CYcY2VE5juTB+HYxycObxs1Kdgk5FvaWmiMSE7oNG9VkOLxh9n0oTvw==",
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/chartjs-chart-graph/-/chartjs-chart-graph-4.3.3.tgz",
+ "integrity": "sha512-34xE1bvZNEkIUYzfx06LQa+CAGNUoz3c+ihrMHh1/XUrBQga4MSDD7cMhCjWPfeLj+TPMu2EbN3WbWne63JDuA==",
"dependencies": {
- "@types/d3-force": "^3.0.9",
- "@types/d3-hierarchy": "^3.1.6",
+ "@types/d3-force": "^3.0.10",
+ "@types/d3-hierarchy": "^3.1.7",
"d3-dispatch": "^3.0.1",
"d3-force": "^3.0.0",
"d3-hierarchy": "^3.1.2",
@@ -2068,15 +2127,12 @@
}
},
"node_modules/check-error": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
- "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
"dev": true,
- "dependencies": {
- "get-func-name": "^2.0.2"
- },
"engines": {
- "node": "*"
+ "node": ">= 16"
}
},
"node_modules/chokidar": {
@@ -2119,15 +2175,15 @@
}
},
"node_modules/cli-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
- "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true,
"dependencies": {
- "restore-cursor": "^4.0.0"
+ "restore-cursor": "^5.0.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2168,9 +2224,9 @@
"dev": true
},
"node_modules/cli-truncate/node_modules/string-width": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
- "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"emoji-regex": "^10.3.0",
@@ -2298,12 +2354,12 @@
}
},
"node_modules/cssstyle": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz",
- "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz",
+ "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==",
"dev": true,
"dependencies": {
- "rrweb-cssom": "^0.6.0"
+ "rrweb-cssom": "^0.7.1"
},
"engines": {
"node": ">=18"
@@ -2379,9 +2435,9 @@
"dev": true
},
"node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+ "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
@@ -2402,13 +2458,10 @@
"dev": true
},
"node_modules/deep-eql": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
- "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
- "dependencies": {
- "type-detect": "^4.0.0"
- },
"engines": {
"node": ">=6"
}
@@ -2465,15 +2518,6 @@
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
},
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
- "dev": true,
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -2544,9 +2588,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.4.693",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.693.tgz",
- "integrity": "sha512-/if4Ueg0GUQlhCrW2ZlXwDAm40ipuKo+OgeHInlL8sbjt+hzISxZK949fZeJaVsheamrzANXvw1zQTvbxTvSHw==",
+ "version": "1.5.7",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.7.tgz",
+ "integrity": "sha512-6FTNWIWMxMy/ZY6799nBlPtF1DFDQ6VQJ7yyDP27SJNt5lwtQ5ufqVvHylb3fdQefvRcgA3fKcFMJi9OLwBRNw==",
"dev": true
},
"node_modules/emoji-regex": {
@@ -2566,6 +2610,18 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -2719,16 +2775,16 @@
}
},
"node_modules/eslint": {
- "version": "8.57.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
- "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.57.0",
- "@humanwhocodes/config-array": "^0.11.14",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
@@ -2786,13 +2842,13 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
- "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
+ "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.8.6"
+ "synckit": "^0.9.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -2816,9 +2872,9 @@
}
},
"node_modules/eslint-plugin-vue": {
- "version": "9.26.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.26.0.tgz",
- "integrity": "sha512-eTvlxXgd4ijE1cdur850G6KalZqk65k1JKoOI2d1kT3hr8sPD07j1q98FRFdNnpxBELGPWxZmInxeHGF/GxtqQ==",
+ "version": "9.29.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.1.tgz",
+ "integrity": "sha512-MH/MbVae4HV/tM8gKAVWMPJbYgW04CK7SuzYRrlNERpxbO0P3+Zdsa2oAcFBW6xNu7W6lIkGOsFAMCRTYmrlWQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
@@ -2826,8 +2882,8 @@
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.15",
- "semver": "^7.6.0",
- "vue-eslint-parser": "^9.4.2",
+ "semver": "^7.6.3",
+ "vue-eslint-parser": "^9.4.3",
"xml-name-validator": "^4.0.0"
},
"engines": {
@@ -3225,15 +3281,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/get-func-name": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
- "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
"node_modules/get-intrinsic": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
@@ -3485,9 +3532,9 @@
}
},
"node_modules/highlight.js": {
- "version": "11.9.0",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
- "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz",
+ "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==",
"engines": {
"node": ">=12.0.0"
}
@@ -3524,9 +3571,9 @@
}
},
"node_modules/https-proxy-agent": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
- "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+ "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
"dev": true,
"dependencies": {
"agent-base": "^7.0.2",
@@ -3545,21 +3592,6 @@
"node": ">=16.17.0"
}
},
- "node_modules/husky": {
- "version": "9.0.11",
- "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz",
- "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==",
- "dev": true,
- "bin": {
- "husky": "bin.mjs"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/typicode"
- }
- },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -3984,12 +4016,6 @@
"node": ">=14"
}
},
- "node_modules/js-tokens": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz",
- "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==",
- "dev": true
- },
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -4003,31 +4029,31 @@
}
},
"node_modules/jsdom": {
- "version": "24.1.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz",
- "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==",
+ "version": "25.0.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz",
+ "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
"dev": true,
"dependencies": {
- "cssstyle": "^4.0.1",
+ "cssstyle": "^4.1.0",
"data-urls": "^5.0.0",
"decimal.js": "^10.4.3",
"form-data": "^4.0.0",
"html-encoding-sniffer": "^4.0.0",
"http-proxy-agent": "^7.0.2",
- "https-proxy-agent": "^7.0.4",
+ "https-proxy-agent": "^7.0.5",
"is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.10",
+ "nwsapi": "^2.2.12",
"parse5": "^7.1.2",
- "rrweb-cssom": "^0.7.0",
+ "rrweb-cssom": "^0.7.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^4.1.4",
+ "tough-cookie": "^5.0.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0",
- "ws": "^8.17.0",
+ "ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
@@ -4042,12 +4068,6 @@
}
}
},
- "node_modules/jsdom/node_modules/rrweb-cssom": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.0.tgz",
- "integrity": "sha512-KlSv0pm9kgQSRxXEMgtivPJ4h826YHsuob8pSHcfSZsSXGtvpEAie8S0AnXuObEJ7nhikOb4ahwxDm0H2yW17g==",
- "dev": true
- },
"node_modules/jsdom/node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
@@ -4081,12 +4101,6 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
- "node_modules/jsonc-parser": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
- "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
- "dev": true
- },
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -4129,9 +4143,9 @@
}
},
"node_modules/lilconfig": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
- "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
+ "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true,
"engines": {
"node": ">=14"
@@ -4147,21 +4161,21 @@
"dev": true
},
"node_modules/lint-staged": {
- "version": "15.2.7",
- "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.7.tgz",
- "integrity": "sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==",
+ "version": "15.2.10",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz",
+ "integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==",
"dev": true,
"dependencies": {
"chalk": "~5.3.0",
"commander": "~12.1.0",
- "debug": "~4.3.4",
+ "debug": "~4.3.6",
"execa": "~8.0.1",
- "lilconfig": "~3.1.1",
- "listr2": "~8.2.1",
- "micromatch": "~4.0.7",
+ "lilconfig": "~3.1.2",
+ "listr2": "~8.2.4",
+ "micromatch": "~4.0.8",
"pidtree": "~0.6.0",
"string-argv": "~0.3.2",
- "yaml": "~2.4.2"
+ "yaml": "~2.5.0"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
@@ -4195,16 +4209,16 @@
}
},
"node_modules/listr2": {
- "version": "8.2.1",
- "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz",
- "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==",
+ "version": "8.2.4",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz",
+ "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==",
"dev": true,
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
- "log-update": "^6.0.0",
- "rfdc": "^1.3.1",
+ "log-update": "^6.1.0",
+ "rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
@@ -4242,9 +4256,9 @@
"dev": true
},
"node_modules/listr2/node_modules/string-width": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
- "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"emoji-regex": "^10.3.0",
@@ -4305,22 +4319,6 @@
"node": ">=4"
}
},
- "node_modules/local-pkg": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
- "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
- "dev": true,
- "dependencies": {
- "mlly": "^1.4.2",
- "pkg-types": "^1.0.3"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -4349,14 +4347,14 @@
"dev": true
},
"node_modules/log-update": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz",
- "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true,
"dependencies": {
- "ansi-escapes": "^6.2.0",
- "cli-cursor": "^4.0.0",
- "slice-ansi": "^7.0.0",
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
@@ -4429,9 +4427,9 @@
}
},
"node_modules/log-update/node_modules/string-width": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
- "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"emoji-regex": "^10.3.0",
@@ -4478,13 +4476,10 @@
}
},
"node_modules/loupe": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
- "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
- "dev": true,
- "dependencies": {
- "get-func-name": "^2.0.1"
- }
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+ "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+ "dev": true
},
"node_modules/lru-cache": {
"version": "10.2.0",
@@ -4496,11 +4491,11 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.10",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
- "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
+ "version": "0.30.11",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
+ "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
+ "@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/memorystream": {
@@ -4528,9 +4523,9 @@
}
},
"node_modules/micromatch": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
- "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.3",
@@ -4573,6 +4568,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
@@ -4602,18 +4609,6 @@
"resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz",
"integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg=="
},
- "node_modules/mlly": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz",
- "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==",
- "dev": true,
- "dependencies": {
- "acorn": "^8.11.3",
- "pathe": "^1.1.2",
- "pkg-types": "^1.0.3",
- "ufo": "^1.3.2"
- }
- },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -4667,9 +4662,9 @@
"dev": true
},
"node_modules/node-releases": {
- "version": "2.0.14",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
- "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
+ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"dev": true
},
"node_modules/nopt": {
@@ -4963,9 +4958,9 @@
}
},
"node_modules/nwsapi": {
- "version": "2.2.10",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz",
- "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==",
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz",
+ "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==",
"dev": true
},
"node_modules/object-assign": {
@@ -5206,18 +5201,18 @@
"dev": true
},
"node_modules/pathval": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
- "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+ "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
"dev": true,
"engines": {
- "node": "*"
+ "node": ">= 14.16"
}
},
"node_modules/picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
+ "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -5253,12 +5248,12 @@
}
},
"node_modules/pinia": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
- "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.4.tgz",
+ "integrity": "sha512-K7ZhpMY9iJ9ShTC0cR2+PnxdQRuwVIsXDO/WIEV/RnMC/vmSoKDTKW/exNQYPI+4ij10UjXqdNiEHwn47McANQ==",
"dependencies": {
- "@vue/devtools-api": "^6.5.0",
- "vue-demi": ">=0.14.5"
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
},
"funding": {
"url": "https://github.com/sponsors/posva"
@@ -5278,9 +5273,9 @@
}
},
"node_modules/pinia/node_modules/vue-demi": {
- "version": "0.14.7",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
- "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@@ -5311,24 +5306,13 @@
"node": ">= 6"
}
},
- "node_modules/pkg-types": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
- "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
- "dev": true,
- "dependencies": {
- "jsonc-parser": "^3.2.0",
- "mlly": "^1.2.0",
- "pathe": "^1.1.0"
- }
- },
"node_modules/playwright": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz",
- "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==",
+ "version": "1.48.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0.tgz",
+ "integrity": "sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==",
"dev": true,
"dependencies": {
- "playwright-core": "1.45.1"
+ "playwright-core": "1.48.0"
},
"bin": {
"playwright": "cli.js"
@@ -5341,9 +5325,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz",
- "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==",
+ "version": "1.48.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0.tgz",
+ "integrity": "sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -5353,9 +5337,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.38",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
- "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "version": "8.4.47",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+ "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"funding": [
{
"type": "opencollective",
@@ -5372,8 +5356,8 @@
],
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.2.0"
+ "picocolors": "^1.1.0",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -5498,9 +5482,9 @@
}
},
"node_modules/prettier": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
- "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
+ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
@@ -5525,9 +5509,9 @@
}
},
"node_modules/prettier-plugin-tailwindcss": {
- "version": "0.6.5",
- "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.5.tgz",
- "integrity": "sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==",
+ "version": "0.6.8",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz",
+ "integrity": "sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==",
"dev": true,
"engines": {
"node": ">=14.21.3"
@@ -5544,6 +5528,7 @@
"prettier-plugin-import-sort": "*",
"prettier-plugin-jsdoc": "*",
"prettier-plugin-marko": "*",
+ "prettier-plugin-multiline-arrays": "*",
"prettier-plugin-organize-attributes": "*",
"prettier-plugin-organize-imports": "*",
"prettier-plugin-sort-imports": "*",
@@ -5581,6 +5566,9 @@
"prettier-plugin-marko": {
"optional": true
},
+ "prettier-plugin-multiline-arrays": {
+ "optional": true
+ },
"prettier-plugin-organize-attributes": {
"optional": true
},
@@ -5598,32 +5586,6 @@
}
}
},
- "node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -5635,12 +5597,6 @@
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
"dev": true
},
- "node_modules/psl": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
- "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
- "dev": true
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5650,12 +5606,6 @@
"node": ">=6"
}
},
- "node_modules/querystringify": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
- "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
- "dev": true
- },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5676,12 +5626,6 @@
}
]
},
- "node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -5769,12 +5713,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/requires-port": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
- "dev": true
- },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -5802,51 +5740,36 @@
}
},
"node_modules/restore-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
- "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true,
"dependencies": {
- "onetime": "^5.1.0",
- "signal-exit": "^3.0.2"
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/restore-cursor/node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/restore-cursor/node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
"dependencies": {
- "mimic-fn": "^2.1.0"
+ "mimic-function": "^5.0.0"
},
"engines": {
- "node": ">=6"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/restore-cursor/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true
- },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -5858,9 +5781,9 @@
}
},
"node_modules/rfdc": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
- "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true
},
"node_modules/rimraf": {
@@ -5921,9 +5844,9 @@
}
},
"node_modules/rollup": {
- "version": "4.13.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz",
- "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.1.tgz",
+ "integrity": "sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -5936,28 +5859,29 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.13.2",
- "@rollup/rollup-android-arm64": "4.13.2",
- "@rollup/rollup-darwin-arm64": "4.13.2",
- "@rollup/rollup-darwin-x64": "4.13.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.13.2",
- "@rollup/rollup-linux-arm64-gnu": "4.13.2",
- "@rollup/rollup-linux-arm64-musl": "4.13.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.13.2",
- "@rollup/rollup-linux-s390x-gnu": "4.13.2",
- "@rollup/rollup-linux-x64-gnu": "4.13.2",
- "@rollup/rollup-linux-x64-musl": "4.13.2",
- "@rollup/rollup-win32-arm64-msvc": "4.13.2",
- "@rollup/rollup-win32-ia32-msvc": "4.13.2",
- "@rollup/rollup-win32-x64-msvc": "4.13.2",
+ "@rollup/rollup-android-arm-eabi": "4.21.1",
+ "@rollup/rollup-android-arm64": "4.21.1",
+ "@rollup/rollup-darwin-arm64": "4.21.1",
+ "@rollup/rollup-darwin-x64": "4.21.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.21.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.21.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.21.1",
+ "@rollup/rollup-linux-arm64-musl": "4.21.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.21.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.21.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.21.1",
+ "@rollup/rollup-linux-x64-gnu": "4.21.1",
+ "@rollup/rollup-linux-x64-musl": "4.21.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.21.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.21.1",
+ "@rollup/rollup-win32-x64-msvc": "4.21.1",
"fsevents": "~2.3.2"
}
},
"node_modules/rrweb-cssom": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
- "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==",
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
"dev": true
},
"node_modules/run-parallel": {
@@ -6048,13 +5972,10 @@
}
},
"node_modules/semver": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
"bin": {
"semver": "bin/semver.js"
},
@@ -6062,18 +5983,6 @@
"node": ">=10"
}
},
- "node_modules/semver/node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/set-function-length": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz",
@@ -6217,9 +6126,9 @@
"peer": true
},
"node_modules/source-map-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
- "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"engines": {
"node": ">=0.10.0"
}
@@ -6479,18 +6388,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/strip-literal": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz",
- "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==",
- "dev": true,
- "dependencies": {
- "js-tokens": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
@@ -6553,9 +6450,9 @@
"dev": true
},
"node_modules/synckit": {
- "version": "0.8.8",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
- "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
+ "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"dev": true,
"dependencies": {
"@pkgr/core": "^0.1.0",
@@ -6569,9 +6466,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "3.4.4",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
- "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
+ "version": "3.4.13",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
+ "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
"dev": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -6642,29 +6539,70 @@
}
},
"node_modules/tinybench": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz",
- "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==",
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz",
+ "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==",
"dev": true
},
"node_modules/tinypool": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
- "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz",
+ "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+ "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
"dev": true,
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tinyspy": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
- "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+ "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
"dev": true,
"engines": {
"node": ">=14.0.0"
}
},
+ "node_modules/tldts": {
+ "version": "6.1.52",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.52.tgz",
+ "integrity": "sha512-fgrDJXDjbAverY6XnIt0lNfv8A0cf7maTEaZxNykLGsLG7XP+5xhjBTrt/ieAsFjAlZ+G5nmXomLcZDkxXnDzw==",
+ "dev": true,
+ "dependencies": {
+ "tldts-core": "^6.1.52"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.52",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.52.tgz",
+ "integrity": "sha512-j4OxQI5rc1Ve/4m/9o2WhWSC4jGc4uVbCINdOEJRAraCi0YqTqgMcxUx7DbmuP0G3PCixoof/RZB0Q5Kh9tagw==",
+ "dev": true
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -6678,18 +6616,15 @@
}
},
"node_modules/tough-cookie": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
- "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
+ "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
"dev": true,
"dependencies": {
- "psl": "^1.1.33",
- "punycode": "^2.1.1",
- "universalify": "^0.2.0",
- "url-parse": "^1.5.3"
+ "tldts": "^6.1.32"
},
"engines": {
- "node": ">=6"
+ "node": ">=16"
}
},
"node_modules/tr46": {
@@ -6723,9 +6658,9 @@
"dev": true
},
"node_modules/tslib": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
- "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true
},
"node_modules/type-check": {
@@ -6740,15 +6675,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -6827,9 +6753,9 @@
}
},
"node_modules/typescript": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
- "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
@@ -6839,12 +6765,6 @@
"node": ">=14.17"
}
},
- "node_modules/ufo": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz",
- "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==",
- "dev": true
- },
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -6861,24 +6781,15 @@
}
},
"node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
- "node_modules/universalify": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
- "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
- "dev": true,
- "engines": {
- "node": ">= 4.0.0"
- }
- },
"node_modules/update-browserslist-db": {
- "version": "1.0.13",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
- "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
+ "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
"dev": true,
"funding": [
{
@@ -6895,8 +6806,8 @@
}
],
"dependencies": {
- "escalade": "^3.1.1",
- "picocolors": "^1.0.0"
+ "escalade": "^3.1.2",
+ "picocolors": "^1.0.1"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -6914,16 +6825,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/url-parse": {
- "version": "1.5.10",
- "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
- "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
- "dev": true,
- "dependencies": {
- "querystringify": "^2.1.1",
- "requires-port": "^1.0.0"
- }
- },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -6940,14 +6841,14 @@
}
},
"node_modules/vite": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz",
- "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==",
+ "version": "5.4.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
+ "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.21.3",
- "postcss": "^8.4.38",
- "rollup": "^4.13.0"
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -6966,6 +6867,7 @@
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
+ "sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
@@ -6983,6 +6885,9 @@
"sass": {
"optional": true
},
+ "sass-embedded": {
+ "optional": true
+ },
"stylus": {
"optional": true
},
@@ -6995,15 +6900,14 @@
}
},
"node_modules/vite-node": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz",
- "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz",
+ "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==",
"dev": true,
"dependencies": {
"cac": "^6.7.14",
- "debug": "^4.3.4",
- "pathe": "^1.1.1",
- "picocolors": "^1.0.0",
+ "debug": "^4.3.6",
+ "pathe": "^1.1.2",
"vite": "^5.0.0"
},
"bin": {
@@ -7031,31 +6935,30 @@
}
},
"node_modules/vitest": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
- "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
- "dev": true,
- "dependencies": {
- "@vitest/expect": "1.6.0",
- "@vitest/runner": "1.6.0",
- "@vitest/snapshot": "1.6.0",
- "@vitest/spy": "1.6.0",
- "@vitest/utils": "1.6.0",
- "acorn-walk": "^8.3.2",
- "chai": "^4.3.10",
- "debug": "^4.3.4",
- "execa": "^8.0.1",
- "local-pkg": "^0.5.0",
- "magic-string": "^0.30.5",
- "pathe": "^1.1.1",
- "picocolors": "^1.0.0",
- "std-env": "^3.5.0",
- "strip-literal": "^2.0.0",
- "tinybench": "^2.5.1",
- "tinypool": "^0.8.3",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz",
+ "integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==",
+ "dev": true,
+ "dependencies": {
+ "@vitest/expect": "2.1.2",
+ "@vitest/mocker": "2.1.2",
+ "@vitest/pretty-format": "^2.1.2",
+ "@vitest/runner": "2.1.2",
+ "@vitest/snapshot": "2.1.2",
+ "@vitest/spy": "2.1.2",
+ "@vitest/utils": "2.1.2",
+ "chai": "^5.1.1",
+ "debug": "^4.3.6",
+ "magic-string": "^0.30.11",
+ "pathe": "^1.1.2",
+ "std-env": "^3.7.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.0",
+ "tinypool": "^1.0.0",
+ "tinyrainbow": "^1.2.0",
"vite": "^5.0.0",
- "vite-node": "1.6.0",
- "why-is-node-running": "^2.2.2"
+ "vite-node": "2.1.2",
+ "why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
@@ -7069,8 +6972,8 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0",
- "@vitest/browser": "1.6.0",
- "@vitest/ui": "1.6.0",
+ "@vitest/browser": "2.1.2",
+ "@vitest/ui": "2.1.2",
"happy-dom": "*",
"jsdom": "*"
},
@@ -7102,15 +7005,15 @@
"dev": true
},
"node_modules/vue": {
- "version": "3.4.31",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz",
- "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==",
+ "version": "3.5.12",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz",
+ "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==",
"dependencies": {
- "@vue/compiler-dom": "3.4.31",
- "@vue/compiler-sfc": "3.4.31",
- "@vue/runtime-dom": "3.4.31",
- "@vue/server-renderer": "3.4.31",
- "@vue/shared": "3.4.31"
+ "@vue/compiler-dom": "3.5.12",
+ "@vue/compiler-sfc": "3.5.12",
+ "@vue/runtime-dom": "3.5.12",
+ "@vue/server-renderer": "3.5.12",
+ "@vue/shared": "3.5.12"
},
"peerDependencies": {
"typescript": "*"
@@ -7146,9 +7049,9 @@
}
},
"node_modules/vue-eslint-parser": {
- "version": "9.4.2",
- "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
- "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==",
+ "version": "9.4.3",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
+ "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
@@ -7186,11 +7089,11 @@
}
},
"node_modules/vue-router": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.0.tgz",
- "integrity": "sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==",
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",
+ "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==",
"dependencies": {
- "@vue/devtools-api": "^6.5.1"
+ "@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
@@ -7199,31 +7102,21 @@
"vue": "^3.2.0"
}
},
- "node_modules/vue-template-compiler": {
- "version": "2.7.16",
- "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
- "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
- "dev": true,
- "dependencies": {
- "de-indent": "^1.0.2",
- "he": "^1.2.0"
- }
- },
"node_modules/vue-tsc": {
- "version": "2.0.21",
- "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.21.tgz",
- "integrity": "sha512-E6x1p1HaHES6Doy8pqtm7kQern79zRtIewkf9fiv7Y43Zo4AFDS5hKi+iHi2RwEhqRmuiwliB1LCEFEGwvxQnw==",
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.6.tgz",
+ "integrity": "sha512-f98dyZp5FOukcYmbFpuSCJ4Z0vHSOSmxGttZJCsFeX0M4w/Rsq0s4uKXjcSRsZqsRgQa6z7SfuO+y0HVICE57Q==",
"dev": true,
"dependencies": {
- "@volar/typescript": "~2.3.0-alpha.15",
- "@vue/language-core": "2.0.21",
+ "@volar/typescript": "~2.4.1",
+ "@vue/language-core": "2.1.6",
"semver": "^7.5.4"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
},
"peerDependencies": {
- "typescript": "*"
+ "typescript": ">=5.0.0"
}
},
"node_modules/vue-virtual-scroller": {
@@ -7354,9 +7247,9 @@
}
},
"node_modules/why-is-node-running": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
- "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
"dev": true,
"dependencies": {
"siginfo": "^2.0.0",
@@ -7479,9 +7372,9 @@
"dev": true
},
"node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
"engines": {
"node": ">=10.0.0"
@@ -7514,16 +7407,10 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
- "node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
"node_modules/yaml": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
- "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
+ "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
diff --git a/report-viewer/package.json b/report-viewer/package.json
index 132c370b83..5119544289 100644
--- a/report-viewer/package.json
+++ b/report-viewer/package.json
@@ -16,50 +16,50 @@
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore --max-warnings 0",
"format": "prettier --write src/",
- "prepare": "cd .. && husky report-viewer/.husky"
+ "prepare": "git config --local core.hooksPath gitHooks/hooks"
},
"dependencies": {
- "@fortawesome/fontawesome-svg-core": "^6.5.2",
- "@fortawesome/free-brands-svg-icons": "^6.5.2",
- "@fortawesome/free-solid-svg-icons": "^6.5.2",
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-brands-svg-icons": "^6.6.0",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.8",
- "chart.js": "^4.4.3",
- "chartjs-chart-graph": "^4.3.1",
+ "chart.js": "^4.4.5",
+ "chartjs-chart-graph": "^4.3.3",
"chartjs-plugin-datalabels": "^2.2.0",
- "highlight.js": "^11.9.0",
+ "highlight.js": "^11.10.0",
"jszip": "^3.10.0",
- "pinia": "^2.1.7",
+ "pinia": "^2.2.4",
"slash": "^5.1.0",
- "vue": "^3.4.31",
+ "vue": "^3.5.12",
"vue-chartjs": "^5.3.1",
"vue-draggable-next": "^2.2.1",
- "vue-router": "^4.4.0",
+ "vue-router": "^4.4.5",
"vue-virtual-scroller": "^2.0.0-beta.8"
},
"devDependencies": {
- "@playwright/test": "^1.45.1",
- "@rushstack/eslint-patch": "^1.10.3",
+ "@pinia/testing": "^0.1.6",
+ "@playwright/test": "^1.48.0",
+ "@rushstack/eslint-patch": "^1.10.4",
"@types/jsdom": "^21.1.7",
- "@types/node": "^18.19.39",
- "@vitejs/plugin-vue": "^5.0.5",
- "@vue/eslint-config-prettier": "^9.0.0",
+ "@types/node": "^22.7.9",
+ "@vitejs/plugin-vue": "^5.1.4",
+ "@vue/eslint-config-prettier": "^10.1.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
- "autoprefixer": "^10.4.19",
- "eslint": "^8.57.0",
- "eslint-plugin-vue": "^9.26.0",
- "husky": "^9.0.11",
- "jsdom": "^24.1.0",
- "lint-staged": "^15.2.7",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^8.57.1",
+ "eslint-plugin-vue": "^9.29.1",
+ "jsdom": "^25.0.1",
+ "lint-staged": "^15.2.10",
"npm-run-all": "^4.1.5",
- "postcss": "^8.4.35",
- "prettier": "^3.3.2",
- "prettier-plugin-tailwindcss": "^0.6.5",
- "tailwindcss": "^3.4.4",
- "typescript": "^5.5.3",
- "vite": "^5.3.1",
- "vitest": "^1.6.0",
- "vue-tsc": "^2.0.21"
+ "postcss": "^8.4.45",
+ "prettier": "^3.3.3",
+ "prettier-plugin-tailwindcss": "^0.6.8",
+ "tailwindcss": "^3.4.13",
+ "typescript": "^5.5.4",
+ "vite": "^5.4.10",
+ "vitest": "^2.1.2",
+ "vue-tsc": "^2.1.6"
}
}
diff --git a/report-viewer/src/App.vue b/report-viewer/src/App.vue
index c5b5da48c6..58b62b9de5 100644
--- a/report-viewer/src/App.vue
+++ b/report-viewer/src/App.vue
@@ -1,7 +1,7 @@
@@ -14,6 +14,15 @@
:icon="store().uiState.useDarkMode ? ['fas', 'sun'] : ['fas', 'moon']"
/>
+
+ You are using an outdated version of the JPlag Report Viewer ({{
+ reportViewerVersion.toString()
+ }}).
+ Version {{ newestVersion.toString() }} is available on
+ GitHub.
+
@@ -25,7 +34,47 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'
import { store } from './stores/store'
+import ToastComponent from './components/ToastComponent.vue'
+import { Version, reportViewerVersion } from './model/Version'
+import { computed, ref } from 'vue'
library.add(faMoon)
library.add(faSun)
+
+const newestVersion = ref(new Version(-1, -1, -1))
+const isDemo = import.meta.env.MODE == 'demo'
+const hasShownToast = ref(sessionStorage.getItem('hasShownToast') == 'true')
+
+const showToast = computed(() => {
+ const value =
+ !isDemo &&
+ !newestVersion.value.isInvalid() &&
+ !reportViewerVersion.isDevVersion() &&
+ newestVersion.value.compareTo(reportViewerVersion) > 0 &&
+ !hasShownToast.value
+
+ if (value) {
+ sessionStorage.setItem('hasShownToast', 'true')
+ } else {
+ sessionStorage.removeItem('hasShownToast')
+ }
+
+ return value
+})
+
+fetch('https://api.github.com/repos/jplag/JPlag/releases/latest')
+ .then((response) => response.json())
+ .then((data) => {
+ const versionString = data.tag_name
+ // remove the 'v' from the version string and split it into an array
+ const versionArray = versionString.substring(1).split('.')
+ newestVersion.value = new Version(
+ parseInt(versionArray[0]),
+ parseInt(versionArray[1]),
+ parseInt(versionArray[2])
+ )
+ })
+ .catch(() => {
+ newestVersion.value = new Version(-1, -1, -1)
+ })
diff --git a/report-viewer/src/assets/jplag-dark-transparent.png b/report-viewer/src/assets/jplag-dark-transparent.png
index 6ce965f401..fe0bb58fec 100644
Binary files a/report-viewer/src/assets/jplag-dark-transparent.png and b/report-viewer/src/assets/jplag-dark-transparent.png differ
diff --git a/report-viewer/src/assets/jplag-light-transparent.png b/report-viewer/src/assets/jplag-light-transparent.png
index 732b433f21..78b171cf68 100644
Binary files a/report-viewer/src/assets/jplag-light-transparent.png and b/report-viewer/src/assets/jplag-light-transparent.png differ
diff --git a/report-viewer/src/components/ClusterGraph.vue b/report-viewer/src/components/ClusterGraph.vue
index b20d02b674..f2dd4d6c8d 100644
--- a/report-viewer/src/components/ClusterGraph.vue
+++ b/report-viewer/src/components/ClusterGraph.vue
@@ -83,7 +83,7 @@ const hoverableEdges = computed(() => {
const firstIndex = keys.value.indexOf(key)
const secondIndex = keys.value.indexOf(match.matchedWith)
if (firstIndex == -1 || secondIndex == -1) {
- console.log(`Could not find index for ${key} or ${match.matchedWith}`)
+ console.warn(`Could not find index for ${key} or ${match.matchedWith}`)
}
if (firstIndex < secondIndex) {
edges.push({
@@ -165,17 +165,20 @@ const maximumSimilarity = computed(() => {
return maximumSimilarity
})
-function getClampedSimilarityFromKeyIndex(firstIndex: number, secondIndex: number) {
+function getClampedSimilarityFromKeyIndex(
+ firstIndex: number,
+ secondIndex: number,
+ min: number,
+ max: number
+) {
const similarity = getSimilarityFromKeyIndex(firstIndex, secondIndex)
if (similarity == 0) {
return 0
}
- if (minimumSimilarity.value == maximumSimilarity.value) {
+ if (min == max) {
return 1
}
- return (
- (similarity - minimumSimilarity.value) / (maximumSimilarity.value - minimumSimilarity.value)
- )
+ return (similarity - min) / (max - min)
}
function getEdgeAlphaFromKeyIndex(firstIndex: number, secondIndex: number) {
@@ -183,7 +186,16 @@ function getEdgeAlphaFromKeyIndex(firstIndex: number, secondIndex: number) {
if (similarity == 0) {
return 1
}
- return getClampedSimilarityFromKeyIndex(firstIndex, secondIndex) * 0.7 + 0.3
+ return (
+ getClampedSimilarityFromKeyIndex(
+ firstIndex,
+ secondIndex,
+ Math.min(minimumSimilarity.value, 0.5),
+ maximumSimilarity.value
+ ) *
+ 0.7 +
+ 0.3
+ )
}
function getEdgeWidth(firstIndex: number, secondIndex: number) {
@@ -191,7 +203,7 @@ function getEdgeWidth(firstIndex: number, secondIndex: number) {
if (similarity == 0) {
return 0.5
}
- return getClampedSimilarityFromKeyIndex(firstIndex, secondIndex) * 5 + 1
+ return getClampedSimilarityFromKeyIndex(firstIndex, secondIndex, 0, 1) * 5 + 1
}
function getEdgeDashStyle(firstIndex: number, secondIndex: number) {
diff --git a/report-viewer/src/components/ClusterRadarChart.vue b/report-viewer/src/components/ClusterRadarChart.vue
index 22b2ae01e5..f3ea8df03d 100644
--- a/report-viewer/src/components/ClusterRadarChart.vue
+++ b/report-viewer/src/components/ClusterRadarChart.vue
@@ -132,7 +132,7 @@ const radarChartOptions = computed(() => {
}
})
-const chartData: Ref> = computed(() => {
+const chartData: Ref> = computed(() => {
return {
labels: labels.value,
datasets: [
diff --git a/report-viewer/src/components/ComparisonTableFilter.vue b/report-viewer/src/components/ComparisonTableFilter.vue
index 99a381b75e..b2e47a29b7 100644
--- a/report-viewer/src/components/ComparisonTableFilter.vue
+++ b/report-viewer/src/components/ComparisonTableFilter.vue
@@ -83,7 +83,7 @@ const searchStringValue = computed({
}
for (const submissionId of store().getSubmissionIds) {
- const submissionParts = submissionId.toLowerCase().split(/ +/g)
+ const submissionParts = store().submissionDisplayName(submissionId).toLowerCase().split(/ +/g)
if (submissionParts.every((part) => searchParts.includes(part))) {
store().state.anonymous.delete(submissionId)
}
diff --git a/report-viewer/src/components/ComparisonsTable.vue b/report-viewer/src/components/ComparisonsTable.vue
index 8e345ef30d..d1856c7708 100644
--- a/report-viewer/src/components/ComparisonsTable.vue
+++ b/report-viewer/src/components/ComparisonsTable.vue
@@ -173,7 +173,7 @@ import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faUserGroup } from '@fortawesome/free-solid-svg-icons'
-import { generateColors } from '@/utils/ColorUtils'
+import { generateHues } from '@/utils/ColorUtils'
import ToolTipComponent from './ToolTipComponent.vue'
import { MetricType, metricToolTips } from '@/model/MetricType'
import NameElement from './NameElement.vue'
@@ -213,10 +213,9 @@ const searchString = ref('')
/**
* This function gets called when the search bar for the comparison table has been updated.
- * It updates the displayed comparisons to only show the ones that have part of any search result in their id. The search is not case sensitive. The parts can be separated by commas or spaces.
- * It also updates the anonymous set to unhide a submission if its name was typed in the search bar at any point in time.
+ * It returns the input list, with the filter given in searchString applied.
*
- * @param newVal The new value of the search bar
+ * @param comparisons Sorted list of comparisons
*/
function getFilteredComparisons(comparisons: ComparisonListElement[]) {
const searches = searchString.value
@@ -237,9 +236,9 @@ function getFilteredComparisons(comparisons: ComparisonListElement[]) {
return comparisons.filter((c) => {
// name search
- const id1 = c.firstSubmissionId.toLowerCase()
- const id2 = c.secondSubmissionId.toLowerCase()
- if (searches.some((s) => id1.includes(s) || id2.includes(s))) {
+ const name1 = store().submissionDisplayName(c.firstSubmissionId).toLowerCase()
+ const name2 = store().submissionDisplayName(c.secondSubmissionId).toLowerCase()
+ if (searches.some((s) => name1.includes(s) || name2.includes(s))) {
return true
}
@@ -257,7 +256,7 @@ function getFilteredComparisons(comparisons: ComparisonListElement[]) {
[MetricType.MAXIMUM]: []
}
metricSearches.forEach((s) => {
- const regexResult = /^(?:(avg|max):)?((?:[<>])=?[0-9]+%?$)/.exec(s)
+ const regexResult = /^(?:(avg|max):)([<>]=?[0-9]+%?$)/.exec(s)
if (regexResult) {
const metricName = regexResult[1]
let metric = MetricType.AVERAGE
@@ -275,7 +274,7 @@ function getFilteredComparisons(comparisons: ComparisonListElement[]) {
})
for (const metric of [MetricType.AVERAGE, MetricType.MAXIMUM]) {
for (const search of searchPerMetric[metric]) {
- const regexResult = /((?:[<>])=?)([0-9]+)%?/.exec(search)!
+ const regexResult = /([<>]=?)([0-9]+)%?/.exec(search)!
const operator = regexResult[1]
const value = parseInt(regexResult[2])
if (evaluateMetricComparison(c.similarities[metric] * 100, operator, value)) {
@@ -340,10 +339,25 @@ function getClusterFor(clusterIndex: number) {
const displayClusters = props.clusters != undefined
-let clusterIconColors = [] as Array
+let clusterIconHues = [] as Array
+const lightmodeSaturation = 80
+const lightmodeLightness = 50
+const lightmodeAlpha = 0.3
+const darkmodeSaturation = 90
+const darkmodeLightness = 65
+const darkmodeAlpha = 0.6
if (props.clusters != undefined) {
- clusterIconColors = generateColors(props.clusters.length, 0.8, 0.5, 1)
+ clusterIconHues = generateHues(props.clusters.length)
}
+const clusterIconColors = computed(() =>
+ clusterIconHues.map((h) => {
+ return `hsla(${h}, ${
+ store().uiState.useDarkMode ? darkmodeSaturation : lightmodeSaturation
+ }%, ${
+ store().uiState.useDarkMode ? darkmodeLightness : lightmodeLightness
+ }%, ${store().uiState.useDarkMode ? darkmodeAlpha : lightmodeAlpha})`
+ })
+)
function isHighlightedRow(item: ComparisonListElement) {
return (
@@ -394,16 +408,4 @@ watch(
.tableCell {
@apply mx-3 flex flex-row items-center justify-center text-center;
}
-
-/* Tooltip arrow. Defined down here bacause of the content attribute */
-.tooltipArrow::after {
- content: ' ';
- position: absolute;
- top: 50%;
- left: 100%;
- margin-top: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: transparent transparent transparent rgba(0, 0, 0, 0.9);
-}
diff --git a/report-viewer/src/components/ContainerComponent.vue b/report-viewer/src/components/ContainerComponent.vue
index 073f3b50b8..c479b487b5 100644
--- a/report-viewer/src/components/ContainerComponent.vue
+++ b/report-viewer/src/components/ContainerComponent.vue
@@ -1,9 +1,9 @@
-
+
+
+
diff --git a/report-viewer/src/components/ToolTipComponent.vue b/report-viewer/src/components/ToolTipComponent.vue
index f80c29fce6..23240d0780 100644
--- a/report-viewer/src/components/ToolTipComponent.vue
+++ b/report-viewer/src/components/ToolTipComponent.vue
@@ -64,7 +64,7 @@ const tooltipPosition = computed(() => {
if (!contentDiv || !tooltipDiv) {
return style
}
- // zeros the tooltip on the topleft of the content
+ // zeros the tooltip on the top-left of the content
let top = -contentDiv.offsetHeight - props.scrollOffsetY
let left =
(props.toolTipContainerWillBeCentered ? -contentDiv.offsetWidth / 2 : 0) - props.scrollOffsetX
diff --git a/report-viewer/src/components/VersionInfoComponent.vue b/report-viewer/src/components/VersionInfoComponent.vue
index 7f3d1feec1..bc6d6b9e0a 100644
--- a/report-viewer/src/components/VersionInfoComponent.vue
+++ b/report-viewer/src/components/VersionInfoComponent.vue
@@ -7,17 +7,7 @@
-
- You are using an outdated version of the JPlag Report Viewer ({{
- reportViewerVersion.toString()
- }}).
- Version {{ newestVersion.toString() }} is available on
- GitHub.
-
-
-
JPlag v{{ reportViewerVersion.toString() }}
+ JPlag v{{ reportViewerVersion.toString() }}
The minimal version of JPlag that is supported by the viewer is v{{
@@ -32,32 +22,13 @@
Demo of JPlag v{{ reportViewerVersion.toString() }}
Displays the result of JPlag on the Progpedia dataset.
-
+
diff --git a/report-viewer/src/components/VersionRepositoryReference.vue b/report-viewer/src/components/VersionRepositoryReference.vue
new file mode 100644
index 0000000000..11f504c072
--- /dev/null
+++ b/report-viewer/src/components/VersionRepositoryReference.vue
@@ -0,0 +1,40 @@
+
+
+ JPlag v{{ reportViewerVersion.toString() }}
+
+ JPlag is open source. Bug reports and feature requests can be submitted on
+ GitHub
+
+
+
+
+
diff --git a/report-viewer/src/components/distributionDiagram/DistributionDiagram.vue b/report-viewer/src/components/distributionDiagram/DistributionDiagram.vue
index d62b14b093..65ee66ed85 100644
--- a/report-viewer/src/components/distributionDiagram/DistributionDiagram.vue
+++ b/report-viewer/src/components/distributionDiagram/DistributionDiagram.vue
@@ -90,11 +90,11 @@ const options = computed(() => {
: 10 ** Math.ceil(Math.log10(maxVal.value + 5)),
type: graphOptions.value.xScale,
ticks: {
- // ensures that in log mode tick labels are not overlappein
+ // ensures that in log mode tick labels are not overlapping
minRotation: graphOptions.value.xScale === 'logarithmic' ? 30 : 0,
autoSkipPadding: 10,
color: graphColors.ticksAndFont.value,
- // ensures that in log mode ticks are placed evenly appart
+ // ensures that in log mode ticks are placed evenly apart
callback: function (value: any) {
if (graphOptions.value.xScale === 'logarithmic' && (value + '').match(/1(0)*[^1-9.]/)) {
return value
diff --git a/report-viewer/src/components/fileDisplaying/CodeLine.vue b/report-viewer/src/components/fileDisplaying/CodeLine.vue
index bcced0c92b..ae21ba99cb 100644
--- a/report-viewer/src/components/fileDisplaying/CodeLine.vue
+++ b/report-viewer/src/components/fileDisplaying/CodeLine.vue
@@ -10,7 +10,7 @@
+
+ {{ file.fileName }}
+
+
+
import type { MatchInSingleFile } from '@/model/MatchInSingleFile'
-import { ref, nextTick, type PropType, computed, type Ref } from 'vue'
+import { ref, type PropType, computed, type Ref } from 'vue'
import Interactable from '../InteractableComponent.vue'
import type { SubmissionFile } from '@/model/File'
import { highlight } from '@/utils/CodeHighlighter'
@@ -99,7 +112,7 @@ const props = defineProps({
const emit = defineEmits(['matchSelected'])
const collapsed = ref(true)
-const lineRefs = ref<(typeof CodeLine)[]>([])
+const lineRefs = ref([])
const codeLines: Ref<{ line: string; matches: MatchInSingleFile[] }[]> = computed(() =>
highlight(props.file.data, props.highlightLanguage).map((line, index) => {
@@ -116,17 +129,6 @@ function matchSelected(match: Match) {
emit('matchSelected', match)
}
-/**
- * Scrolls to the line number in the file.
- * @param lineNumber line number in the file
- */
-function scrollTo(lineNumber: number) {
- collapsed.value = false
- nextTick(function () {
- lineRefs.value[lineNumber - 1].scrollTo()
- })
-}
-
/**
* Collapses the container.
*/
@@ -134,9 +136,18 @@ function collapse() {
collapsed.value = true
}
+function expand() {
+ collapsed.value = false
+}
+
+function getLineRect(lineNumber: number): DOMRect {
+ return lineRefs.value[lineNumber - 1].getBoundingClientRect()
+}
+
defineExpose({
- scrollTo,
- collapse
+ collapse,
+ expand,
+ getLineRect
})
/**
@@ -145,9 +156,10 @@ defineExpose({
* @return new path of file
*/
function getFileDisplayName(file: SubmissionFile): string {
- const filePathLength = file.fileName.length
+ const fileDisplayName = file.displayFileName ?? file.fileName
+ const filePathLength = fileDisplayName.length
return filePathLength > 40
- ? '...' + file.fileName.substring(filePathLength - 40, filePathLength)
- : file.fileName
+ ? '...' + fileDisplayName.substring(filePathLength - 40, filePathLength)
+ : fileDisplayName
}
diff --git a/report-viewer/src/components/fileDisplaying/FilesContainer.vue b/report-viewer/src/components/fileDisplaying/FilesContainer.vue
index fc02f0ed33..ba868150ff 100644
--- a/report-viewer/src/components/fileDisplaying/FilesContainer.vue
+++ b/report-viewer/src/components/fileDisplaying/FilesContainer.vue
@@ -15,16 +15,14 @@
>
-
-
+
+ $emit('matchSelected', match)"
class="mt-1 first:mt-0"
@@ -42,12 +40,14 @@ import Container from '../ContainerComponent.vue'
import Button from '../ButtonComponent.vue'
import ScrollableComponent from '../ScrollableComponent.vue'
import { VueDraggableNext } from 'vue-draggable-next'
-import { computed, ref, type PropType, type Ref } from 'vue'
+import { computed, nextTick, ref, type PropType, type Ref } from 'vue'
import type { MatchInSingleFile } from '@/model/MatchInSingleFile'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faCompressAlt } from '@fortawesome/free-solid-svg-icons'
import { library } from '@fortawesome/fontawesome-svg-core'
import type { Language } from '@/model/Language'
+import { FileSortingOptions } from '@/model/ui/FileSortingOptions'
+import { store } from '@/stores/store'
import type { BaseCodeMatch } from '@/model/BaseCodeReport'
import type { Match } from '@/model/Match'
@@ -91,9 +91,81 @@ const props = defineProps({
}
})
-defineEmits(['matchSelected'])
+const emit = defineEmits(['matchSelected', 'filesMoved'])
+
+const matchesPerFile = computed(() => {
+ const matches: Record = {}
+ for (const file of props.files) {
+ matches[file.fileName] = !props.matches.get(file.fileName)
+ ? []
+ : (props.matches.get(file.fileName) as MatchInSingleFile[])
+ }
+ return matches
+})
+
+const sortedFiles: Ref = ref([])
+sortFiles(store().uiState.fileSorting ?? FileSortingOptions.ALPHABETICAL)
+
+function sortFiles(fileSorting: FileSortingOptions) {
+ switch (fileSorting) {
+ case FileSortingOptions.ALPHABETICAL: {
+ sortedFiles.value = Array.from(props.files).sort((a, b) =>
+ a.fileName.localeCompare(b.fileName)
+ )
+ break
+ }
+
+ case FileSortingOptions.MATCH_SIZE: {
+ const largestMatch: Record = {}
+ for (const file of props.files) {
+ largestMatch[file.fileName] = Math.max(
+ ...matchesPerFile.value[file.fileName].map((match) => match.match.tokens)
+ )
+ }
+ sortedFiles.value = Array.from(props.files).sort(
+ (a, b) => largestMatch[b.fileName] - largestMatch[a.fileName]
+ )
+ break
+ }
+
+ case FileSortingOptions.MATCH_COUNT: {
+ const matchCount: Record = {}
+ for (const file of props.files) {
+ matchCount[file.fileName] = matchesPerFile.value[file.fileName].length
+ }
+ sortedFiles.value = Array.from(props.files).sort(
+ (a, b) => matchCount[b.fileName] - matchCount[a.fileName]
+ )
+ break
+ }
+
+ case FileSortingOptions.MATCH_COVERAGE: {
+ const matchCoverage: Record = {}
+ for (const file of props.files) {
+ const matches = matchesPerFile.value[file.fileName]
+ const totalTokens = matches.reduce((acc, match) => acc + match.match.tokens, 0)
+ matchCoverage[file.fileName] =
+ totalTokens / (file.tokenCount > 0 ? file.tokenCount : Infinity)
+ }
+ sortedFiles.value = Array.from(props.files).sort(
+ (a, b) => matchCoverage[b.fileName] - matchCoverage[a.fileName]
+ )
+ break
+ }
+ }
+}
+
+const shouldEmitFileMoving = ref(true)
+
+function emitFileMoving() {
+ if (!shouldEmitFileMoving.value) {
+ return
+ }
+ emit('filesMoved')
+}
const codePanels: Ref<(typeof CodePanel)[]> = ref([])
+const scrollContainer: Ref = ref(null)
const tokenCount = computed(() => {
return props.files.reduce((acc, file) => (file.tokenCount ?? 0) + acc - 1, 0)
@@ -105,20 +177,31 @@ const tokenCount = computed(() => {
* @param line Line to scroll to.
*/
function scrollTo(file: string, line: number) {
- const fileIndex = Array.from(props.files).findIndex((f) => f.fileName === file)
+ const fileIndex = sortedFiles.value.findIndex((f) => f.fileName === file)
if (fileIndex !== -1) {
- codePanels.value[fileIndex].scrollTo(line)
+ codePanels.value[fileIndex].expand()
+ nextTick(() => {
+ if (!scrollContainer.value) {
+ return
+ }
+ const childToScrollTo = codePanels.value[fileIndex].getLineRect(line) as DOMRect
+ const scrollBox = scrollContainer.value.getRoot() as HTMLElement
+ scrollBox.scrollTo({
+ top: childToScrollTo.top + scrollBox.scrollTop - (scrollBox.clientHeight * 2) / 3
+ })
+ })
}
}
/**
- * Collapses all of the code panels.
+ * Collapses all the code panels.
*/
function collapseAll() {
codePanels.value.forEach((panel) => panel.collapse())
}
defineExpose({
- scrollTo
+ scrollTo,
+ sortFiles
})
diff --git a/report-viewer/src/components/fileDisplaying/MatchList.vue b/report-viewer/src/components/fileDisplaying/MatchList.vue
index 5e83d793a8..4218171703 100644
--- a/report-viewer/src/components/fileDisplaying/MatchList.vue
+++ b/report-viewer/src/components/fileDisplaying/MatchList.vue
@@ -45,7 +45,7 @@
@@ -77,7 +77,7 @@
}})
Match is {{ match.tokens }} tokens long.
- Token indeces of match: {{ match.startInFirst.tokenListIndex }}-{{
+ Token indices of match: {{ match.startInFirst.tokenListIndex }}-{{
match.endInFirst.tokenListIndex
}}
and {{ match.startInSecond.tokenListIndex }}-{{ match.endInSecond.tokenListIndex }}.
@@ -104,7 +104,7 @@
v-for="[index, match] in matches?.entries()"
v-bind:key="index"
:style="{ background: getMatchColor(0.3, match.colorIndex) }"
- class="print-excact"
+ class="print-exact"
>
{{ getFileName(match.firstFile) }}
{{ match.startInFirst }} - {{ match.endInFirst }}
@@ -115,7 +115,7 @@
Basecode in submissions
diff --git a/report-viewer/src/components/optionsSelectors/OptionComponent.vue b/report-viewer/src/components/optionsSelectors/OptionComponent.vue
index d34a2c706b..26a5689200 100644
--- a/report-viewer/src/components/optionsSelectors/OptionComponent.vue
+++ b/report-viewer/src/components/optionsSelectors/OptionComponent.vue
@@ -1,6 +1,6 @@
diff --git a/report-viewer/src/components/optionsSelectors/OptionsSelectorComponent.vue b/report-viewer/src/components/optionsSelectors/OptionsSelectorComponent.vue
index 6be436228b..b8abfd9f42 100644
--- a/report-viewer/src/components/optionsSelectors/OptionsSelectorComponent.vue
+++ b/report-viewer/src/components/optionsSelectors/OptionsSelectorComponent.vue
@@ -73,4 +73,8 @@ function select(index: number) {
emit('selectionChanged', index)
selected.value = index
}
+
+defineExpose({
+ select
+})
diff --git a/report-viewer/src/main.ts b/report-viewer/src/main.ts
index e2355eaa5b..84f3780a0d 100644
--- a/report-viewer/src/main.ts
+++ b/report-viewer/src/main.ts
@@ -19,7 +19,7 @@ app.use(VueVirtualScroller)
app.config.errorHandler = (err, vm, info) => {
console.error(err)
console.error(info)
- alert('An unhandeled error occured. Please check the console for more details.')
+ alert('An unhandled error occurred. Please check the console for more details.')
}
app.mount('#app')
diff --git a/report-viewer/src/model/Comparison.ts b/report-viewer/src/model/Comparison.ts
index 308aec1fc6..1c84d7c4f5 100644
--- a/report-viewer/src/model/Comparison.ts
+++ b/report-viewer/src/model/Comparison.ts
@@ -10,9 +10,9 @@ export class Comparison {
private readonly _firstSubmissionId: string
private readonly _secondSubmissionId: string
private readonly _similarities: Record
- private _filesOfFirstSubmission: SubmissionFile[]
- private _filesOfSecondSubmission: SubmissionFile[]
- private _allMatches: Array
+ private readonly _filesOfFirstSubmission: SubmissionFile[]
+ private readonly _filesOfSecondSubmission: SubmissionFile[]
+ private readonly _allMatches: Array
private readonly _firstSimilarity: number
private readonly _secondSimilarity: number
@@ -103,7 +103,7 @@ export class Comparison {
private groupMatchesByFileName(index: 1 | 2): Map> {
const acc = new Map>()
this._allMatches.forEach((val) => {
- const name = index === 1 ? (val.firstFile as string) : (val.secondFile as string)
+ const name = index === 1 ? val.firstFile : val.secondFile
if (!acc.get(name)) {
acc.set(name, [])
diff --git a/report-viewer/src/model/File.ts b/report-viewer/src/model/File.ts
index ad56d5ca43..cd18dc30ce 100644
--- a/report-viewer/src/model/File.ts
+++ b/report-viewer/src/model/File.ts
@@ -28,4 +28,8 @@ export interface SubmissionFile extends File {
* Number of tokens in the file that are matched.
*/
matchedTokenCount: number
+ /**
+ * The name to be displayed in the report viewer
+ */
+ displayFileName: string
}
diff --git a/report-viewer/src/model/Language.ts b/report-viewer/src/model/Language.ts
index 198f28399d..4a206b49d3 100644
--- a/report-viewer/src/model/Language.ts
+++ b/report-viewer/src/model/Language.ts
@@ -2,28 +2,26 @@
* Enum for the language parsers JPlag supports
*/
enum ParserLanguage {
- JAVA = 'Javac based AST plugin',
- PYTHON = 'Python3 Parser',
- C = 'C Scanner',
- CPP_OLD = 'C/C++ Scanner [basic markup]',
- CPP = 'C++ Parser',
- CPP_2 = 'C/C++ Parser',
- C_SHARP = 'C# 6 Parser',
+ JAVA = 'java',
+ PYTHON = 'python3',
+ C = 'c',
+ CPP = 'cpp',
+ C_SHARP = 'csharp',
EMF_METAMODEL_DYNAMIC = 'emf-dynamic',
- EMF_METAMODEL = 'EMF metamodel',
- EMF_MODEL = 'EMF models (dynamically created token set)',
- GO = 'Go Parser',
- KOTLIN = 'Kotlin Parser',
- R_LANG = 'R Parser',
- RUST = 'Rust Language Module',
- SCALA = 'Scala parser',
- SCHEME = 'SchemeR4RS Parser [basic markup]',
- SWIFT = 'Swift Parser',
- TEXT = 'Text Parser (naive)',
- SCXML = 'SCXML (Statechart XML)',
- LLVM = 'LLVMIR Parser',
- JAVASCRIPT = 'JavaScript',
- TYPESCRIPT = 'Typescript Parser'
+ EMF_METAMODEL = 'emf',
+ EMF_MODEL = 'emf-model',
+ GO = 'go',
+ KOTLIN = 'kotlin',
+ R_LANG = 'rlang',
+ RUST = 'rust',
+ SCALA = 'scale',
+ SCHEME = 'scheme',
+ SWIFT = 'swift',
+ TEXT = 'text',
+ SCXML = 'scxml',
+ LLVM = 'llvmir',
+ JAVASCRIPT = 'javascript',
+ TYPESCRIPT = 'typescript'
}
type Language = ParserLanguage | 'unknown language'
diff --git a/report-viewer/src/model/MetricType.ts b/report-viewer/src/model/MetricType.ts
index 5b4ab874ed..5fc4897be1 100644
--- a/report-viewer/src/model/MetricType.ts
+++ b/report-viewer/src/model/MetricType.ts
@@ -1,5 +1,5 @@
/**
- * This enum maps the metric type to the index they have in the generated JSON and respectivly in the store.
+ * This enum maps the metric type to the index they have in the generated JSON and respectively in the store.
*/
export enum MetricType {
AVERAGE = 'AVG',
@@ -23,6 +23,6 @@ export const metricToolTips: Record = {
longName: 'Maximum Similarity',
shortName: 'MAX',
tooltip:
- 'The maximum similarity of the two files.\nUsefull if programms are very dfferent in size.'
+ 'The maximum similarity of the two files.\nUseful if programms are very different in size.'
}
}
diff --git a/report-viewer/src/model/Version.ts b/report-viewer/src/model/Version.ts
index 5df8770f7d..a6fe306db7 100644
--- a/report-viewer/src/model/Version.ts
+++ b/report-viewer/src/model/Version.ts
@@ -4,9 +4,9 @@ import versionJson from '@/version.json'
* Version of the report viewer.
*/
export class Version {
- private major: number
- private minor: number
- private patch: number
+ private readonly major: number
+ private readonly minor: number
+ private readonly patch: number
constructor(major: number, minor: number, patch: number) {
this.major = major
diff --git a/report-viewer/src/model/factories/BaseCodeReportFactory.ts b/report-viewer/src/model/factories/BaseCodeReportFactory.ts
index 5d1e1ac357..4ae3fb5fc0 100644
--- a/report-viewer/src/model/factories/BaseCodeReportFactory.ts
+++ b/report-viewer/src/model/factories/BaseCodeReportFactory.ts
@@ -7,10 +7,9 @@ export class BaseCodeReportFactory extends BaseFactory {
private static readonly basePath = 'basecode'
public static async getReport(submissionId: string): Promise {
- const a = await this.extractReport(
+ return this.extractReport(
JSON.parse(await this.getFile(slash(`${this.basePath}/${submissionId}.json`)))
)
- return a
}
private static async extractReport(json: Record[]): Promise {
diff --git a/report-viewer/src/model/factories/BaseFactory.ts b/report-viewer/src/model/factories/BaseFactory.ts
index 238d9b65f8..52f4633681 100644
--- a/report-viewer/src/model/factories/BaseFactory.ts
+++ b/report-viewer/src/model/factories/BaseFactory.ts
@@ -5,7 +5,7 @@ import { ZipFileHandler } from '@/model/fileHandling/ZipFileHandler'
* This class provides some basic functionality for the factories.
*/
export class BaseFactory {
- public static zipFileName = 'results.zip'
+ public static readonly zipFileName = 'results.zip'
/**
* Returns the content of a file through the stored loading type.
@@ -22,8 +22,6 @@ export class BaseFactory {
return await (await this.getLocalFile(`/files/${path}`)).text()
} else if (store().state.zipModeUsed) {
return this.getFileFromStore(path)
- } else if (store().state.singleModeUsed) {
- return store().state.singleFillRawContent
} else if (await this.useLocalZipMode()) {
await new ZipFileHandler().handleFile(await this.getLocalFile(this.zipFileName))
store().setLoadingType('zip')
diff --git a/report-viewer/src/model/factories/ComparisonFactory.ts b/report-viewer/src/model/factories/ComparisonFactory.ts
index 04ffbfdeec..9305ad630e 100644
--- a/report-viewer/src/model/factories/ComparisonFactory.ts
+++ b/report-viewer/src/model/factories/ComparisonFactory.ts
@@ -5,6 +5,7 @@ import { getMatchColorCount } from '@/utils/ColorUtils'
import slash from 'slash'
import { BaseFactory } from './BaseFactory'
import { MetricType } from '../MetricType'
+import type { SubmissionFile } from '../File'
/**
* Factory class for creating Comparison objects
@@ -15,7 +16,7 @@ export class ComparisonFactory extends BaseFactory {
}
/**
- * Creates a comparison object from a json object created by by JPlag
+ * Creates a comparison object from a json object created by JPlag
* @param json the json object
*/
private static async extractComparison(json: Record): Promise {
@@ -57,8 +58,8 @@ export class ComparisonFactory extends BaseFactory {
firstSubmissionId,
secondSubmissionId,
this.extractSimilarities(json.similarities as Record),
- filesOfFirstSubmission,
- filesOfSecondSubmission,
+ this.getFilesWithDisplayNames(filesOfFirstSubmission),
+ this.getFilesWithDisplayNames(filesOfSecondSubmission),
this.colorMatches(matches),
json.first_similarity as number,
json.second_similarity as number
@@ -91,11 +92,12 @@ export class ComparisonFactory extends BaseFactory {
submissionId: submissionId,
data: await this.getSubmissionFileContent(submissionId, slash(filePath)),
tokenCount: fileList[filePath].token_count,
- matchedTokenCount: 0
+ matchedTokenCount: 0,
+ displayFileName: slash(filePath)
})
}
} catch (e) {
- console.log(e)
+ console.error(e)
}
}
@@ -152,4 +154,27 @@ export class ComparisonFactory extends BaseFactory {
}
return sortedSize
}
+
+ private static getFilesWithDisplayNames(files: SubmissionFile[]): SubmissionFile[] {
+ if (files.length == 1) {
+ return files
+ }
+ let longestPrefix = files[0].fileName
+ for (let i = 1; i < files.length; i++) {
+ if (longestPrefix == '') {
+ break
+ }
+
+ while (!files[i].fileName.startsWith(longestPrefix)) {
+ longestPrefix = longestPrefix.substring(0, longestPrefix.length - 1)
+ }
+ }
+
+ return files.map((f) => {
+ return {
+ ...f,
+ displayFileName: f.fileName.substring(longestPrefix.length)
+ }
+ })
+ }
}
diff --git a/report-viewer/src/model/factories/OverviewFactory.ts b/report-viewer/src/model/factories/OverviewFactory.ts
index 2f7f56670e..e388353e1b 100644
--- a/report-viewer/src/model/factories/OverviewFactory.ts
+++ b/report-viewer/src/model/factories/OverviewFactory.ts
@@ -20,7 +20,7 @@ export class OverviewFactory extends BaseFactory {
}
/**
- * Creates an overview object from a json object created by by JPlag
+ * Creates an overview object from a json object created by JPlag
* @param json the json object
*/
private static extractOverview(json: Record): Overview {
@@ -152,6 +152,7 @@ export class OverviewFactory extends BaseFactory {
* Compares the two versions and shows an alert if they are not equal and puts out a warning if they are not
* @param jsonVersion the version of the json file
* @param reportViewerVersion the version of the report viewer
+ * @param minimalVersion the minimal report version expected
*/
static compareVersions(
jsonVersion: Version,
@@ -179,13 +180,13 @@ export class OverviewFactory extends BaseFactory {
sessionStorage.setItem('versionAlert', 'true')
}
if (jsonVersion.compareTo(minimalVersion) < 0) {
- throw (
+ throw new Error(
"The result's version(" +
- jsonVersion.toString() +
- ') is older than the minimal support version of the report viewer(' +
- minimalVersion.toString() +
- '). ' +
- 'Can not read the report.'
+ jsonVersion.toString() +
+ ') is older than the minimal support version of the report viewer(' +
+ minimalVersion.toString() +
+ '). ' +
+ 'Can not read the report.'
)
}
}
diff --git a/report-viewer/src/model/fileHandling/JsonFileHandler.ts b/report-viewer/src/model/fileHandling/JsonFileHandler.ts
deleted file mode 100644
index 9c8b8d736f..0000000000
--- a/report-viewer/src/model/fileHandling/JsonFileHandler.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { store } from '@/stores/store'
-import { FileHandler } from './FileHandler'
-
-/**
- * Class for handling single json files.
- */
-export class JsonFileHandler extends FileHandler {
- public async handleFile(file: Blob) {
- const content = await file.text()
- const json = JSON.parse(content)
-
- store().setSingleFileRawContent(content)
- if (!json['submission_folder_path']) {
- throw new Error(`Invalid JSON: File is not an overview file.`)
- }
- }
-}
diff --git a/report-viewer/src/model/fileHandling/ZipFileHandler.ts b/report-viewer/src/model/fileHandling/ZipFileHandler.ts
index 69990b1f6c..7aa604d010 100644
--- a/report-viewer/src/model/fileHandling/ZipFileHandler.ts
+++ b/report-viewer/src/model/fileHandling/ZipFileHandler.ts
@@ -8,7 +8,7 @@ import { FileHandler } from './FileHandler'
*/
export class ZipFileHandler extends FileHandler {
public async handleFile(file: Blob) {
- console.log('Start handling zip file and storing necessary data...')
+ console.info('Start handling zip file and storing necessary data...')
return jszip.loadAsync(file).then(async (zip) => {
for (const originalFileName of Object.keys(zip.files)) {
const unixFileName = slash(originalFileName)
@@ -29,7 +29,8 @@ export class ZipFileHandler extends FileHandler {
data: data,
// These two properties will be determined at a later time (when loading the submission file index)
tokenCount: NaN,
- matchedTokenCount: NaN
+ matchedTokenCount: NaN,
+ displayFileName: slash(fullPathFileName)
})
})
} else {
@@ -85,7 +86,7 @@ export class ZipFileHandler extends FileHandler {
filesOrSubmissionsIndex_originalFileName +
(rootName === 'files' ? 'files'.length : 'submissions'.length)
)
- if (originalPathWithoutSubmissions.charAt(0) === '\\') {
+ if (originalPathWithoutSubmissions.startsWith('\\')) {
fullPath = unixSubfolderPathAfterSubmissions + '\\' + fileBase
while (fullPath.includes('/')) {
fullPath = fullPath.replace('/', '\\')
diff --git a/report-viewer/src/model/ui/FileSortingOptions.ts b/report-viewer/src/model/ui/FileSortingOptions.ts
new file mode 100644
index 0000000000..8fe3d48373
--- /dev/null
+++ b/report-viewer/src/model/ui/FileSortingOptions.ts
@@ -0,0 +1,27 @@
+import type { ToolTipLabel } from './ToolTip'
+
+export enum FileSortingOptions {
+ ALPHABETICAL,
+ MATCH_COVERAGE,
+ MATCH_COUNT,
+ MATCH_SIZE
+}
+
+export const fileSortingTooltips: Record = {
+ [FileSortingOptions.ALPHABETICAL]: {
+ displayValue: 'Alphabetical',
+ tooltip: 'Sort files alphabetically, similar to package structure.'
+ },
+ [FileSortingOptions.MATCH_COVERAGE]: {
+ displayValue: 'Match Coverage',
+ tooltip: 'Sort files by the percentage of tokens included in a match.'
+ },
+ [FileSortingOptions.MATCH_COUNT]: {
+ displayValue: 'Match Count',
+ tooltip: 'Sort files by the number of matches found.'
+ },
+ [FileSortingOptions.MATCH_SIZE]: {
+ displayValue: 'Match Size',
+ tooltip: 'Sort files by match size, with the largest matches at the top.'
+ }
+}
diff --git a/report-viewer/src/stores/state.ts b/report-viewer/src/stores/state.ts
index 4e8c898fec..d12bdac29f 100644
--- a/report-viewer/src/stores/state.ts
+++ b/report-viewer/src/stores/state.ts
@@ -1,6 +1,7 @@
import type { SubmissionFile } from '@/model/File'
import type { MetricType } from '@/model/MetricType'
import type { DistributionChartConfig } from '@/model/ui/DistributionChartConfig'
+import type { FileSortingOptions } from '@/model/ui/FileSortingOptions'
/**
* Local store. Stores the state of the application.
@@ -27,14 +28,6 @@ export interface State {
* Indicates whether zip mode is used.
*/
zipModeUsed: boolean
- /**
- * Indicates whether single file mode is used.
- */
- singleModeUsed: boolean
- /**
- * Files string if single mode is used.
- */
- singleFillRawContent: string
fileIdToDisplayName: Map
submissionIdsToComparisonFileName: Map>
@@ -49,13 +42,5 @@ export interface UIState {
comparisonTableSortingMetric: MetricType
comparisonTableClusterSorting: boolean
distributionChartConfig: DistributionChartConfig
-}
-
-/**
- * Load configuration is used to indicate which mode is used.
- */
-export interface LoadConfiguration {
- local: boolean
- zip: boolean
- single: boolean
+ fileSorting: FileSortingOptions
}
diff --git a/report-viewer/src/stores/store.ts b/report-viewer/src/stores/store.ts
index e798cc207b..e722cbbd21 100644
--- a/report-viewer/src/stores/store.ts
+++ b/report-viewer/src/stores/store.ts
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import type { State, UIState } from './state'
import { MetricType } from '@/model/MetricType'
import type { SubmissionFile, File } from '@/model/File'
+import { FileSortingOptions } from '@/model/ui/FileSortingOptions'
/**
* The store is a global state management system. It is used to store the state of the application.
@@ -17,9 +18,6 @@ const store = defineStore('store', {
// Mode that was used to load the files
localModeUsed: false,
zipModeUsed: false,
- singleModeUsed: false,
- // only used in single mode
- singleFillRawContent: '',
fileIdToDisplayName: new Map(),
uploadedFileName: ''
},
@@ -31,7 +29,8 @@ const store = defineStore('store', {
metric: MetricType.AVERAGE,
xScale: 'linear',
bucketCount: 10
- }
+ },
+ fileSorting: FileSortingOptions.ALPHABETICAL
}
}),
getters: {
@@ -131,8 +130,6 @@ const store = defineStore('store', {
submissions: {},
localModeUsed: false,
zipModeUsed: false,
- singleModeUsed: false,
- singleFillRawContent: '',
fileIdToDisplayName: new Map(),
uploadedFileName: ''
}
@@ -199,17 +196,9 @@ const store = defineStore('store', {
* Sets the loading type
* @param payload Type used to input JPlag results
*/
- setLoadingType(loadingType: 'zip' | 'local' | 'single') {
+ setLoadingType(loadingType: 'zip' | 'local') {
this.state.localModeUsed = loadingType == 'local'
this.state.zipModeUsed = loadingType == 'zip'
- this.state.singleModeUsed = loadingType == 'single'
- },
- /**
- * Sets the raw content of the single file mode
- * @param payload Raw content of the single file mode
- */
- setSingleFileRawContent(payload: string) {
- this.state.singleFillRawContent = payload
},
/**
* Switches whether darkMode is being used for the UI
diff --git a/report-viewer/src/style.css b/report-viewer/src/style.css
index b59c5774b1..ce4126ae29 100644
--- a/report-viewer/src/style.css
+++ b/report-viewer/src/style.css
@@ -17,7 +17,7 @@
@apply h-[6px] w-[6px] cursor-pointer;
}
*::-webkit-scrollbar-track {
- @apply rounded-full border-2 bg-scrollbar-backgorund-light dark:bg-scrollbar-backgorund-dark;
+ @apply rounded-full border-2 bg-scrollbar-background-light dark:bg-scrollbar-background-dark;
}
*::-webkit-scrollbar-thumb {
@apply rounded-full border-2 bg-scrollbar-thumb-light dark:bg-scrollbar-thumb-dark;
@@ -29,7 +29,7 @@
overflow-wrap: anywhere;
}
- .print-excact {
+ .print-exact {
print-color-adjust: exact;
}
diff --git a/report-viewer/src/utils/CodeHighlighter.ts b/report-viewer/src/utils/CodeHighlighter.ts
index 54caa2a76f..3191840e03 100644
--- a/report-viewer/src/utils/CodeHighlighter.ts
+++ b/report-viewer/src/utils/CodeHighlighter.ts
@@ -5,8 +5,8 @@ import llvm from 'highlight.js/lib/languages/llvm'
import typescript from 'highlight.js/lib/languages/typescript'
/**
- * Hightlights the given code with the given language.
- * Splits the resulting html into seperate lines.
+ * Highlights the given code with the given language.
+ * Splits the resulting html into separate lines.
* The returned string is an array of html lines, consisting of spans with the hljs classes and the code.
* Source: https://stackoverflow.com/a/70656181
* @param code Code to highlight
@@ -16,7 +16,7 @@ import typescript from 'highlight.js/lib/languages/typescript'
export function highlight(code: string, lang: Language) {
const highlightedCode = hljs.highlight(code, { language: getHighlightLanguage(lang) }).value
const openTags: string[] = []
- const formattedCode = highlightedCode
+ return highlightedCode
.replace(/(]*>)|(<\/span>)|(\n)/g, (match: string) => {
if (match === '\n') {
return ''.repeat(openTags.length) + '\n' + openTags.join('')
@@ -31,7 +31,6 @@ export function highlight(code: string, lang: Language) {
return match
})
.split('\n')
- return formattedCode
}
function getHighlightLanguage(lang: Language) {
@@ -41,8 +40,6 @@ function getHighlightLanguage(lang: Language) {
case ParserLanguage.C:
return 'c'
case ParserLanguage.CPP:
- case ParserLanguage.CPP_OLD:
- case ParserLanguage.CPP_2:
return 'cpp'
case ParserLanguage.C_SHARP:
return 'csharp'
diff --git a/report-viewer/src/utils/ColorUtils.ts b/report-viewer/src/utils/ColorUtils.ts
index 8b1f978a14..bed155fc37 100644
--- a/report-viewer/src/utils/ColorUtils.ts
+++ b/report-viewer/src/utils/ColorUtils.ts
@@ -4,35 +4,20 @@ import { computed } from 'vue'
/**
* Generates an array of HSL-Colors
* @param numberOfColors Number of colors to generate
- * @param saturation Saturation of the colors [0,1]
- * @param lightness Lightness of the colors [0,1]
- * @param alpha Alpha value of the colors [0,1]
*/
-function generateColors(
- numberOfColors: number,
- saturation: number,
- lightness: number,
- alpha: number
-) {
+function generateHues(numberOfColors: number) {
const numberOfColorsInFirstInterval = Math.round(
((80 - 20) / (80 - 20 + (340 - 160))) * numberOfColors
) // number of colors from the first interval
const numberOfColorsInSecondInterval = numberOfColors - numberOfColorsInFirstInterval // number of colors from the second interval
- const colors: Array = generateColorsForInterval(
- 20,
- 80,
- numberOfColorsInFirstInterval,
- saturation,
- lightness,
- alpha
- )
- colors.push(...generateColorsForInterval(160, 340, numberOfColorsInSecondInterval, 0.8, 0.5, 0.3))
+ const colors: Array = generateColorsForInterval(20, 80, numberOfColorsInFirstInterval)
+ colors.push(...generateColorsForInterval(160, 340, numberOfColorsInSecondInterval))
return colors
}
/**
- * Genertes an array of HSL-Colors for a given interval
+ * Generates an array of HSL-Colors for a given interval
* @param intervalStart start of the interval [0,360]
* @param intervalEnd end of the interval [0,360] and > intervalStart
* @param numberOfColorsInInterval Number of colors to generate in the interval
@@ -44,22 +29,19 @@ function generateColors(
function generateColorsForInterval(
intervalStart: number,
intervalEnd: number,
- numberOfColorsInInterval: number,
- saturation: number,
- lightness: number,
- alpha: number
+ numberOfColorsInInterval: number
) {
- const colors: Array = []
+ const hues: Array = []
const interval = intervalEnd - intervalStart
const hueDelta = Math.trunc(interval / numberOfColorsInInterval)
for (let i = 0; i < numberOfColorsInInterval; i++) {
const hue = intervalStart + i * hueDelta
- colors.push(`hsla(${hue}, ${saturation * 100}%, ${lightness * 100}%, ${alpha})`)
+ hues.push(hue)
}
- return colors
+ return hues
}
-/** This is the list of colors that are used as the background colot of matches in the comparison view */
+/** This is the list of colors that are used as the background color of matches in the comparison view */
const matchColors: { red: number; green: number; blue: number }[] = [
{ red: 255, green: 122, blue: 0 },
{ red: 0, green: 133, blue: 255 },
@@ -113,4 +95,4 @@ const graphColors = {
}
}
-export { generateColors, graphColors, getMatchColorCount, getMatchColor, type MatchColorIndex }
+export { generateHues, graphColors, getMatchColorCount, getMatchColor, type MatchColorIndex }
diff --git a/report-viewer/src/utils/ComparisonUtils.ts b/report-viewer/src/utils/ComparisonUtils.ts
deleted file mode 100644
index 6dde383d25..0000000000
--- a/report-viewer/src/utils/ComparisonUtils.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * Generates the link for a line of code.
- * @param index Index of the files container where the line is.
- * @param fileName File in which the line is contained.
- * @param line Line number.
- */
-export function generateLineCodeLink(index: number, fileName: string, line: number): string {
- return String(index).concat(fileName).concat(String(line))
-}
diff --git a/report-viewer/src/viewWrapper/ClusterViewWrapper.vue b/report-viewer/src/viewWrapper/ClusterViewWrapper.vue
index 234569c676..51876a6f08 100644
--- a/report-viewer/src/viewWrapper/ClusterViewWrapper.vue
+++ b/report-viewer/src/viewWrapper/ClusterViewWrapper.vue
@@ -8,7 +8,7 @@
-
+
@@ -19,7 +19,7 @@ import ClusterView from '@/views/ClusterView.vue'
import LoadingCircle from '@/components/LoadingCircle.vue'
import type { Overview } from '@/model/Overview'
import { redirectOnError } from '@/router'
-import RepositoryReference from '@/components/RepositoryReference.vue'
+import VersionRepositoryReference from '@/components/VersionRepositoryReference.vue'
const props = defineProps({
clusterIndex: {
diff --git a/report-viewer/src/viewWrapper/ComparisonViewWrapper.vue b/report-viewer/src/viewWrapper/ComparisonViewWrapper.vue
index 6f75241e96..47f04aac03 100644
--- a/report-viewer/src/viewWrapper/ComparisonViewWrapper.vue
+++ b/report-viewer/src/viewWrapper/ComparisonViewWrapper.vue
@@ -14,7 +14,7 @@
-
+
@@ -27,7 +27,7 @@ import { ComparisonFactory } from '@/model/factories/ComparisonFactory'
import LoadingCircle from '@/components/LoadingCircle.vue'
import { redirectOnError } from '@/router'
import type { Language } from '@/model/Language'
-import RepositoryReference from '@/components/RepositoryReference.vue'
+import VersionRepositoryReference from '@/components/VersionRepositoryReference.vue'
import type { BaseCodeMatch } from '@/model/BaseCodeReport'
import { BaseCodeReportFactory } from '@/model/factories/BaseCodeReportFactory'
@@ -43,7 +43,7 @@ const language: Ref = ref(null)
const firstBaseCodeMatches: Ref = ref(null)
const secondBaseCodeMatches: Ref = ref(null)
-// This eslint rule is disabled to allow the use of await in the setup function. Disabling this rule is safe, because the props are gathered from the url, so changing them would reload the pafe anyway.
+// This eslint rule is disabled to allow the use of await in the setup function. Disabling this rule is safe, because the props are gathered from the url, so changing them would reload the page anyway.
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
const comparisonPromise = ComparisonFactory.getComparison(props.comparisonFileName)
.then((comp) => {
@@ -59,7 +59,7 @@ OverviewFactory.getOverview()
language.value = overview.language
})
.catch((error) => {
- redirectOnError(error, 'Could not load coparison:\n')
+ redirectOnError(error, 'Could not load comparison:\n')
})
comparisonPromise
diff --git a/report-viewer/src/viewWrapper/InformationViewWrapper.vue b/report-viewer/src/viewWrapper/InformationViewWrapper.vue
index 56d6e79529..a939749582 100644
--- a/report-viewer/src/viewWrapper/InformationViewWrapper.vue
+++ b/report-viewer/src/viewWrapper/InformationViewWrapper.vue
@@ -8,7 +8,7 @@
-
+
@@ -21,7 +21,7 @@ import LoadingCircle from '@/components/LoadingCircle.vue'
import { redirectOnError } from '@/router'
import { OptionsFactory } from '@/model/factories/OptionsFactory'
import type { CliOptions } from '@/model/CliOptions'
-import RepositoryReference from '@/components/RepositoryReference.vue'
+import VersionRepositoryReference from '@/components/VersionRepositoryReference.vue'
const overview: Ref = ref(null)
const cliOptions: Ref = ref(undefined)
@@ -36,5 +36,5 @@ OverviewFactory.getOverview()
OptionsFactory.getCliOptions()
.then((o) => (cliOptions.value = o))
- .catch((error) => console.log('Could not load full options.', error))
+ .catch((error) => console.error('Could not load full options.', error))
diff --git a/report-viewer/src/viewWrapper/OverviewViewWrapper.vue b/report-viewer/src/viewWrapper/OverviewViewWrapper.vue
index 13c0008439..e73d074db5 100644
--- a/report-viewer/src/viewWrapper/OverviewViewWrapper.vue
+++ b/report-viewer/src/viewWrapper/OverviewViewWrapper.vue
@@ -8,7 +8,7 @@
-
+
@@ -19,7 +19,7 @@ import OverviewView from '@/views/OverviewView.vue'
import type { Overview } from '@/model/Overview'
import LoadingCircle from '@/components/LoadingCircle.vue'
import { redirectOnError } from '@/router'
-import RepositoryReference from '@/components/RepositoryReference.vue'
+import VersionRepositoryReference from '@/components/VersionRepositoryReference.vue'
const overview: Ref = ref(null)
diff --git a/report-viewer/src/views/ComparisonView.vue b/report-viewer/src/views/ComparisonView.vue
index c7387d8364..e0a0860db3 100644
--- a/report-viewer/src/views/ComparisonView.vue
+++ b/report-viewer/src/views/ComparisonView.vue
@@ -73,6 +73,14 @@
:basecode-in-second="secondBaseCodeMatches"
@match-selected="showMatch"
/>
+ changeFileSorting(index)"
+ :default-selected="sortingOptions.indexOf(store().uiState.fileSorting)"
+ />
@@ -87,6 +95,7 @@
:highlight-language="language"
:base-code-matches="firstBaseCodeMatches"
@match-selected="showMatchInSecond"
+ @files-moved="filesMoved()"
class="max-h-0 min-h-full flex-1 overflow-hidden print:max-h-none print:overflow-y-visible"
/>
@@ -122,6 +132,8 @@ import { MetricType } from '@/model/MetricType'
import { Comparison } from '@/model/Comparison'
import { redirectOnError } from '@/router'
import ToolTipComponent from '@/components/ToolTipComponent.vue'
+import { FileSortingOptions, fileSortingTooltips } from '@/model/ui/FileSortingOptions'
+import OptionsSelectorComponent from '@/components/optionsSelectors/OptionsSelectorComponent.vue'
import type { BaseCodeMatch } from '@/model/BaseCodeReport'
library.add(faPrint)
@@ -154,18 +166,16 @@ const panel1: Ref = ref(null)
const panel2: Ref = ref(null)
/**
- * Shows a match in the first files container when clicked on a line in the second files container.
- * @param file (file name)
- * @param line (line number)
+ * Shows a match in the first files container when clicked on a line in the second file container.
+ * @param match The match to scroll to
*/
function showMatchInFirst(match: Match) {
panel1.value?.scrollTo(match.firstFile, match.startInFirst.line)
}
/**
- * Shows a match in the second files container, when clicked on a line in the second files container.
- * @param file (file name)
- * @param line (line number)
+ * Shows a match in the second files container, when clicked on a line in the second file container.
+ * @param match The match to scroll to
*/
function showMatchInSecond(match: Match) {
panel2.value?.scrollTo(match.secondFile, match.startInSecond.line)
@@ -173,7 +183,6 @@ function showMatchInSecond(match: Match) {
/**
* Shows a match in the first and second files container.
- * @param e The click event
* @param match The match to show
*/
function showMatch(match: Match) {
@@ -181,12 +190,38 @@ function showMatch(match: Match) {
showMatchInSecond(match)
}
+const sortingOptions = [
+ FileSortingOptions.ALPHABETICAL,
+ FileSortingOptions.MATCH_COVERAGE,
+ FileSortingOptions.MATCH_COUNT,
+ FileSortingOptions.MATCH_SIZE
+]
+const movedAfterSorting = ref(false)
+const sortingOptionSelector: Ref = ref(null)
+
+function changeFileSorting(index: number) {
+ movedAfterSorting.value = false
+ if (index < 0) {
+ return
+ }
+ store().uiState.fileSorting = sortingOptions[index]
+ panel1.value?.sortFiles(store().uiState.fileSorting)
+ panel2.value?.sortFiles(store().uiState.fileSorting)
+}
+
+function filesMoved() {
+ movedAfterSorting.value = true
+ if (sortingOptionSelector.value) {
+ sortingOptionSelector.value.select(-2)
+ }
+}
+
function print() {
window.print()
}
// This code is responsible for changing the theme of the highlighted code depending on light/dark mode
-// Changing the used style itsself is the desired solution (https://github.com/highlightjs/highlight.js/issues/2115)
+// Changing the used style itself is the desired solution (https://github.com/highlightjs/highlight.js/issues/2115)
const styleholder: Ref = ref(null)
onMounted(() => {
diff --git a/report-viewer/src/views/ErrorView.vue b/report-viewer/src/views/ErrorView.vue
index 99e284591a..1fce99bb14 100644
--- a/report-viewer/src/views/ErrorView.vue
+++ b/report-viewer/src/views/ErrorView.vue
@@ -60,7 +60,7 @@ defineProps({
onErrorCaptured((error) => {
console.error(error)
alert(
- 'An error occured that could not be handeled. Please check the console for more information.'
+ 'An error occurred that could not be handled. Please check the console for more information.'
)
return false
})
diff --git a/report-viewer/src/views/FileUploadView.vue b/report-viewer/src/views/FileUploadView.vue
index 18fb14b05f..02191f8e49 100644
--- a/report-viewer/src/views/FileUploadView.vue
+++ b/report-viewer/src/views/FileUploadView.vue
@@ -11,12 +11,16 @@