diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1b4a7f0f7..f58d40ef0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,10 +14,10 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.5.0 + - uses: actions/checkout@v4 with: token: ${{ secrets.SDQ_DEV_DEPLOY_TOKEN }} - - uses: actions/checkout@v3.5.0 + - uses: actions/checkout@v4 with: repository: ${{ github.repository }}.wiki path: wiki diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index ab5dee4dc..64989c7ce 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -2,8 +2,18 @@ name: Build on: push: + paths: + - ".github/workflows/maven.yml" + - "**/pom.xml" + - "**.java" + - "**.g4" pull_request: types: [opened, synchronize, reopened] + paths: + - ".github/workflows/maven.yml" + - "**/pom.xml" + - "**.java" + - "**.g4" # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -26,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8720031c7..daeb40a86 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,7 +7,7 @@ jobs: publish-maven-central: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-java@v3 with: java-version: '20' @@ -17,7 +17,7 @@ jobs: with: servers: '[{ "id": "ossrh", "username": "jplag", "password": "${{ secrets.OSSRH_TOKEN }}" }]' - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v5 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.PGP_SECRET }} passphrase: ${{ secrets.PGP_PW }} @@ -29,7 +29,7 @@ jobs: publish-release-artifact: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-java@v3 with: java-version: '17' diff --git a/.github/workflows/report-viewer-test.yml b/.github/workflows/report-viewer-build-test.yml similarity index 90% rename from .github/workflows/report-viewer-test.yml rename to .github/workflows/report-viewer-build-test.yml index 3093d2457..aa80c86dd 100644 --- a/.github/workflows/report-viewer-test.yml +++ b/.github/workflows/report-viewer-build-test.yml @@ -4,6 +4,9 @@ on: workflow_dispatch: pull_request: types: [opened, synchronize, reopened] + paths: + - ".github/workflows/report-viewer-build-test.yml" + - "report-viewer/**" jobs: pre_job: @@ -23,11 +26,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Set version of Report Viewer shell: bash diff --git a/.github/workflows/report-viewer-dev.yml b/.github/workflows/report-viewer-dev.yml index 854cece1f..92fe97407 100644 --- a/.github/workflows/report-viewer-dev.yml +++ b/.github/workflows/report-viewer-dev.yml @@ -5,17 +5,20 @@ on: push: branches: - develop + paths: + - ".github/workflows/report-viewer-dev.yml" + - "report-viewer/**" jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Set version of Report Viewer shell: bash diff --git a/.github/workflows/report-viewer-e2e.yml b/.github/workflows/report-viewer-e2e.yml index 7ebb5bda9..9e265b910 100644 --- a/.github/workflows/report-viewer-e2e.yml +++ b/.github/workflows/report-viewer-e2e.yml @@ -4,6 +4,9 @@ on: workflow_dispatch: pull_request: types: [opened, synchronize, reopened] + paths: + - ".github/workflows/report-viewer-e2e.yml" + - "report-viewer/**" jobs: pre_job: @@ -23,11 +26,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Test ๐Ÿงช working-directory: report-viewer diff --git a/.github/workflows/report-viewer-lint.yml b/.github/workflows/report-viewer-lint.yml index 91ade9c3e..924b274e7 100644 --- a/.github/workflows/report-viewer-lint.yml +++ b/.github/workflows/report-viewer-lint.yml @@ -4,10 +4,14 @@ name: Report Viewer ESLint Workflow # Checks linting of report viewer on: workflow_dispatch: push: - path: - - report-viewer/** + paths: + - ".github/workflows/report-viewer-lint.yml" + - "report-viewer/**" pull_request: types: [opened, synchronize, reopened] + paths: + - ".github/workflows/report-viewer-lint.yml" + - "report-viewer/**" jobs: pre_job: @@ -27,11 +31,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Lint ๐ŸŽจ working-directory: report-viewer diff --git a/.github/workflows/report-viewer-prettier.yml b/.github/workflows/report-viewer-prettier.yml index 4e887afb2..e9933c24e 100644 --- a/.github/workflows/report-viewer-prettier.yml +++ b/.github/workflows/report-viewer-prettier.yml @@ -3,10 +3,15 @@ name: Report Viewer Prettier Check Workflow # Checks the report viewer against t on: workflow_dispatch: push: - path: - - report-viewer/** + paths: + - ".github/workflows/report-viewer-prettier.yml" + - "report-viewer/**" pull_request: types: [opened, synchronize, reopened] + paths: + - ".github/workflows/report-viewer-prettier.yml" + - "report-viewer/**" + jobs: pre_job: @@ -26,11 +31,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Check ๐ŸŽจ working-directory: report-viewer diff --git a/.github/workflows/sonarcloud-report-viewer.yml b/.github/workflows/report-viewer-sonarcloud.yml similarity index 91% rename from .github/workflows/sonarcloud-report-viewer.yml rename to .github/workflows/report-viewer-sonarcloud.yml index 6541adb2b..4f7231759 100644 --- a/.github/workflows/sonarcloud-report-viewer.yml +++ b/.github/workflows/report-viewer-sonarcloud.yml @@ -21,8 +21,9 @@ jobs: sonarcloud: name: SonarCloud runs-on: ubuntu-latest + if: ${{ github.actor != 'dependabot[bot]' }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: SonarCloud Scan diff --git a/.github/workflows/report-viewer-unit.yml b/.github/workflows/report-viewer-unit.yml index 79e35e887..48f9082a7 100644 --- a/.github/workflows/report-viewer-unit.yml +++ b/.github/workflows/report-viewer-unit.yml @@ -4,6 +4,9 @@ on: workflow_dispatch: pull_request: types: [opened, synchronize, reopened] + paths: + - ".github/workflows/report-viewer-unit.yml" + - "report-viewer/**" jobs: pre_job: @@ -23,11 +26,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Install and Test ๐Ÿงช working-directory: report-viewer diff --git a/.github/workflows/report-viewer.yml b/.github/workflows/report-viewer.yml index 8eaea136d..624dde7c5 100644 --- a/.github/workflows/report-viewer.yml +++ b/.github/workflows/report-viewer.yml @@ -11,11 +11,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - name: Set version of Report Viewer shell: bash diff --git a/.github/workflows/sonarcloud-branch.yml b/.github/workflows/sonarcloud-branch.yml index b4e0c08f2..5da01abdf 100644 --- a/.github/workflows/sonarcloud-branch.yml +++ b/.github/workflows/sonarcloud-branch.yml @@ -5,6 +5,11 @@ on: branches: - main - develop + paths: + - ".github/workflows/sonarcloud-branch.yml" + - "**/pom.xml" + - "**.java" + - "**.g4" # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -16,7 +21,7 @@ jobs: if: ${{ github.actor != 'dependabot[bot]' }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK diff --git a/.github/workflows/sonarcloud-pr.yml b/.github/workflows/sonarcloud-pr.yml index 8cff8fdde..a7f411d83 100644 --- a/.github/workflows/sonarcloud-pr.yml +++ b/.github/workflows/sonarcloud-pr.yml @@ -42,7 +42,7 @@ jobs: needs: get-info steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} diff --git a/.github/workflows/spotless.yml b/.github/workflows/spotless.yml index ee6cfaea3..5f9cdf9ea 100644 --- a/.github/workflows/spotless.yml +++ b/.github/workflows/spotless.yml @@ -1,9 +1,19 @@ name: Spotless Style Check on: - push: + push: + paths: + - ".github/workflows/spotless.yml" + - "**/pom.xml" + - "**.java" + - "**.g4" pull_request: types: [opened, synchronize, reopened] + paths: + - ".github/workflows/spotless.yml" + - "**/pom.xml" + - "**.java" + - "**.g4" # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -26,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v3 diff --git a/README.md b/README.md index 4c7c0fb2f..38f581b8c 100644 --- a/README.md +++ b/README.md @@ -19,23 +19,25 @@ JPlag is a system that finds similarities among multiple sets of source code fil In the following, a list of all supported languages with their supported language version is provided. A language can be selected from the command line using subcommands (jplag [jplag options] [language options]). Alternatively you can use the legacy "-l" argument. -| Language | Version | CLI Argument Name | [state](https://github.com/jplag/JPlag/wiki/2.-Supported-Languages) | parser | -|--------------------------------------------------------|--------:|-------------------|:----------------------------------------------------------------:|:---------:| -| [Java](https://www.java.com) | 17 | java | mature | JavaC | -| [C/C++](https://isocpp.org) | 11 | cpp | legacy | JavaCC | -| [C/C++](https://isocpp.org) | 14 | cpp2 | beta | ANTLR 4 | -| [C#](https://docs.microsoft.com/en-us/dotnet/csharp/) | 6 | csharp | beta | ANTLR 4 | -| [Go](https://go.dev) | 1.17 | golang | beta | ANTLR 4 | -| [Kotlin](https://kotlinlang.org) | 1.3 | kotlin | beta | ANTLR 4 | -| [Python](https://www.python.org) | 3.6 | python3 | legacy | ANTLR 4 | -| [R](https://www.r-project.org/) | 3.5.0 | rlang | beta | ANTLR 4 | -| [Rust](https://www.rust-lang.org/) | 1.60.0 | rust | beta | ANTLR 4 | -| [Scala](https://www.scala-lang.org) | 2.13.8 | scala | beta | Scalameta | -| [Scheme](http://www.scheme-reports.org) | ? | scheme | unknown | JavaCC | -| [Swift](https://www.swift.org) | 5.4 | swift | beta | ANTLR 4 | -| [EMF Metamodel](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf | beta | EMF | -| [EMF Model](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf-model | alpha | EMF | -| Text (naive) | - | text | legacy | CoreNLP | +| Language | Version | CLI Argument Name | [state](https://github.com/jplag/JPlag/wiki/2.-Supported-Languages) | parser | +|------------------------------------------------------------|---------------------------------------------------------------------------------------:|-------------------|:-------------------------------------------------------------------:|:---------:| +| [Java](https://www.java.com) | 17 | java | mature | JavaC | +| [C/C++](https://isocpp.org) | 11 | cpp | legacy | JavaCC | +| [C/C++](https://isocpp.org) | 14 | cpp2 | beta | ANTLR 4 | +| [C#](https://docs.microsoft.com/en-us/dotnet/csharp/) | 6 | csharp | beta | ANTLR 4 | +| [Go](https://go.dev) | 1.17 | golang | beta | ANTLR 4 | +| [Kotlin](https://kotlinlang.org) | 1.3 | kotlin | beta | ANTLR 4 | +| [Python](https://www.python.org) | 3.6 | python3 | legacy | ANTLR 4 | +| [R](https://www.r-project.org/) | 3.5.0 | rlang | beta | ANTLR 4 | +| [Rust](https://www.rust-lang.org/) | 1.60.0 | rust | beta | ANTLR 4 | +| [Scala](https://www.scala-lang.org) | 2.13.8 | scala | beta | Scalameta | +| [Scheme](http://www.scheme-reports.org) | ? | scheme | unknown | JavaCC | +| [Swift](https://www.swift.org) | 5.4 | swift | beta | ANTLR 4 | +| [EMF Metamodel](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf | beta | EMF | +| [EMF Model](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf-model | alpha | EMF | +| [LLVM IR](https://llvm.org) | 15 | llvmir | beta | ANTLR 4 | +| [TypeScript](https://www.typescriptlang.org/) / JavaScript | [~5](https://github.com/antlr/grammars-v4/tree/master/javascript/typescript/README.md) | typescript | beta | ANTLR 4 | +| Text (naive) | - | text | legacy | CoreNLP | ## Download and Installation You need Java SE 17 to run or build JPlag. @@ -151,6 +153,7 @@ Commands: go java kotlin + llvmir python3 rlang rust @@ -159,6 +162,7 @@ Commands: scxml swift text + typescript ``` ### Java API diff --git a/cli/pom.xml b/cli/pom.xml index df278ecb7..b6812a3eb 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -102,6 +102,16 @@ emf-model ${revision} + + de.jplag + typescript + ${revision} + + + de.jplag + llvmir + ${revision} + org.kohsuke.metainf-services @@ -111,7 +121,7 @@ info.picocli picocli - 4.7.4 + 4.7.5 diff --git a/cli/src/main/java/de/jplag/cli/CLI.java b/cli/src/main/java/de/jplag/cli/CLI.java index ec5bf478a..91c90709a 100644 --- a/cli/src/main/java/de/jplag/cli/CLI.java +++ b/cli/src/main/java/de/jplag/cli/CLI.java @@ -1,7 +1,8 @@ package de.jplag.cli; -import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER; +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; import java.awt.*; import java.io.File; @@ -25,6 +26,7 @@ import de.jplag.clustering.ClusteringOptions; import de.jplag.clustering.Preprocessing; import de.jplag.exceptions.ExitException; +import de.jplag.merging.MergingOptions; import de.jplag.options.JPlagOptions; import de.jplag.options.LanguageOption; import de.jplag.options.LanguageOptions; @@ -52,12 +54,16 @@ public final class CLI { "More Abstract than Tree", "Students Nightmare", "No, changing variable names does not work", "The tech is out there!", "Developed by plagiarism experts."}; + private static final String OPTION_LIST_HEADING = "Parameter descriptions: "; + private final CommandLine commandLine; private final CliOptions options; private static final String IMPOSSIBLE_EXCEPTION = "This should not have happened." + " Please create an issue on github (https://github.com/jplag/JPlag/issues) with the entire output."; + private static final String DESCRIPTION_PATTERN = "%nJPlag - %s%n%s%n%n"; + /** * Main class for using JPlag via the CLI. * @param args are the CLI arguments that will be passed to JPlag. @@ -102,6 +108,8 @@ public CLI() { this.options = new CliOptions(); this.commandLine = new CommandLine(options); + this.commandLine.setHelpFactory(new HelpFactory()); + this.commandLine.getHelpSectionMap().put(SECTION_KEY_OPTION_LIST, help -> help.optionList().lines().map(it -> { if (it.startsWith(" -")) { return " " + it; @@ -112,7 +120,8 @@ public CLI() { buildSubcommands().forEach(commandLine::addSubcommand); - this.commandLine.getHelpSectionMap().put(SECTION_KEY_FOOTER, help -> generateDescription()); + this.commandLine.getHelpSectionMap().put(SECTION_KEY_SYNOPSIS, help -> help.synopsis(help.synopsisHeadingLength()) + generateDescription()); + this.commandLine.getHelpSectionMap().put(SECTION_KEY_DESCRIPTION_HEADING, help -> OPTION_LIST_HEADING); this.commandLine.setAllowSubcommandsAsOptionParameters(true); } @@ -172,11 +181,12 @@ public JPlagOptions buildOptionsFromArguments(ParseResult parseResult) throws Cl } ClusteringOptions clusteringOptions = getClusteringOptions(this.options); + MergingOptions mergingOptions = getMergingOptions(this.options); JPlagOptions jPlagOptions = new JPlagOptions(loadLanguage(parseResult), this.options.minTokenMatch, submissionDirectories, oldSubmissionDirectories, null, this.options.advanced.subdirectory, suffixes, this.options.advanced.exclusionFileName, JPlagOptions.DEFAULT_SIMILARITY_METRIC, this.options.advanced.similarityThreshold, this.options.shownComparisons, clusteringOptions, - this.options.advanced.debug); + this.options.advanced.debug, mergingOptions); String baseCodePath = this.options.baseCode; File baseCodeDirectory = baseCodePath == null ? null : new File(baseCodePath); @@ -235,9 +245,13 @@ private static ClusteringOptions getClusteringOptions(CliOptions options) { return clusteringOptions; } + private static MergingOptions getMergingOptions(CliOptions options) { + return new MergingOptions(options.merging.enabled, options.merging.minimumNeighborLength, options.merging.maximumGapSize); + } + private String generateDescription() { var randomDescription = DESCRIPTIONS[RANDOM.nextInt(DESCRIPTIONS.length)]; - return String.format("JPlag - %s%n%n%s", randomDescription, CREDITS); + return String.format(DESCRIPTION_PATTERN, randomDescription, CREDITS); } public String getResultFolder() { diff --git a/cli/src/main/java/de/jplag/cli/CliOptions.java b/cli/src/main/java/de/jplag/cli/CliOptions.java index 7538b8847..c55e71f3b 100644 --- a/cli/src/main/java/de/jplag/cli/CliOptions.java +++ b/cli/src/main/java/de/jplag/cli/CliOptions.java @@ -53,12 +53,15 @@ public class CliOptions implements Runnable { "--result-directory"}, description = "Name of the directory in which the comparison results will be stored (default: result)%n") public String resultFolder = "results"; - @ArgGroup(heading = "Advanced%n") + @ArgGroup(heading = "Advanced%n", exclusive = false) public Advanced advanced = new Advanced(); @ArgGroup(validate = false, heading = "Clustering%n") public Clustering clustering = new Clustering(); + @ArgGroup(validate = false, heading = "Merging of neighboring matches to increase the similarity of concealed plagiarism:%n") + public Merging merging = new Merging(); + /** * Empty run method, so picocli prints help automatically */ @@ -88,7 +91,7 @@ public static class Advanced { } public static class Clustering { - @Option(names = {"--cluster-skip"}, description = "Skips the clustering (default: false)\n") + @Option(names = {"--cluster-skip"}, description = "Skips the clustering (default: false)%n") public boolean disable; @ArgGroup @@ -109,6 +112,18 @@ public static class ClusteringEnabled { } } + public static class Merging { + @Option(names = {"--match-merging"}, description = "Enables match merging (default: false)%n") + public boolean enabled; + + @Option(names = {"--neighbor-length"}, description = "Defines how short a match can be, to be considered (default: 2)%n") + public int minimumNeighborLength; + + @Option(names = {"--gap-size"}, description = "Defines how many token there can be between two neighboring matches (default: 6)%n") + public int maximumGapSize; + + } + @Option(names = {"--cluster-spectral-bandwidth"}, hidden = true) public double clusterSpectralBandwidth = new ClusteringOptions().spectralKernelBandwidth(); diff --git a/cli/src/main/java/de/jplag/cli/CustomHelp.java b/cli/src/main/java/de/jplag/cli/CustomHelp.java new file mode 100644 index 000000000..368f358ce --- /dev/null +++ b/cli/src/main/java/de/jplag/cli/CustomHelp.java @@ -0,0 +1,26 @@ +package de.jplag.cli; + +import picocli.CommandLine; + +/** + * Custom implementation for the help page, including the custom {@link ParamLabelRenderer} + */ +public class CustomHelp extends CommandLine.Help { + private final IParamLabelRenderer paramLabelRenderer; + + /** + * New instance + * @param command The {@link picocli.CommandLine.Model.CommandSpec} to build the help for + * @param colorScheme The {@link picocli.CommandLine.Help.ColorScheme} for the help page + */ + public CustomHelp(CommandLine.Model.CommandSpec command, ColorScheme colorScheme) { + super(command, colorScheme); + + this.paramLabelRenderer = new ParamLabelRenderer(super.parameterLabelRenderer()); + } + + @Override + public IParamLabelRenderer parameterLabelRenderer() { + return this.paramLabelRenderer; + } +} diff --git a/cli/src/main/java/de/jplag/cli/HelpFactory.java b/cli/src/main/java/de/jplag/cli/HelpFactory.java new file mode 100644 index 000000000..53aa208b7 --- /dev/null +++ b/cli/src/main/java/de/jplag/cli/HelpFactory.java @@ -0,0 +1,13 @@ +package de.jplag.cli; + +import picocli.CommandLine; + +/** + * Custom help factory, used to add the custom {@link ParamLabelRenderer}. + */ +public class HelpFactory implements CommandLine.IHelpFactory { + @Override + public CommandLine.Help create(CommandLine.Model.CommandSpec commandSpec, CommandLine.Help.ColorScheme colorScheme) { + return new CustomHelp(commandSpec, colorScheme); + } +} diff --git a/cli/src/main/java/de/jplag/cli/ParamLabelRenderer.java b/cli/src/main/java/de/jplag/cli/ParamLabelRenderer.java new file mode 100644 index 000000000..2d815af30 --- /dev/null +++ b/cli/src/main/java/de/jplag/cli/ParamLabelRenderer.java @@ -0,0 +1,44 @@ +package de.jplag.cli; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import picocli.CommandLine; + +/** + * Custom implementation of {@link picocli.CommandLine.Help.IParamLabelRenderer}, that show the available options for + * enums. For all other parameter types, the base renderer is called. + */ +public class ParamLabelRenderer implements CommandLine.Help.IParamLabelRenderer { + private final CommandLine.Help.IParamLabelRenderer base; + + private static final String PARAM_LABEL_PATTERN = "=<{%s}>"; + private static final String VALUE_SEPARATOR = ", "; + + /** + * New instance + * @param base The base renderer used for all non enum types + */ + public ParamLabelRenderer(CommandLine.Help.IParamLabelRenderer base) { + this.base = base; + } + + @Override + public CommandLine.Help.Ansi.Text renderParameterLabel(CommandLine.Model.ArgSpec argSpec, CommandLine.Help.Ansi ansi, + List styles) { + if (argSpec.type().isEnum()) { + @SuppressWarnings("unchecked") + Enum[] enumConstants = ((Class>) argSpec.type()).getEnumConstants(); + String enumValueNames = Arrays.stream(enumConstants).map(Enum::name).collect(Collectors.joining(VALUE_SEPARATOR)); + return CommandLine.Help.Ansi.AUTO.text(String.format(PARAM_LABEL_PATTERN, enumValueNames)); + } + + return base.renderParameterLabel(argSpec, ansi, styles); + } + + @Override + public String separator() { + return base.separator(); + } +} 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 e38e88d97..80d94a74a 100644 --- a/cli/src/main/java/de/jplag/cli/logger/CollectedLogger.java +++ b/cli/src/main/java/de/jplag/cli/logger/CollectedLogger.java @@ -138,6 +138,7 @@ private String renderLevel(int level) { }; } + @Override public boolean isTraceEnabled() { return isLevelEnabled(LOG_LEVEL_TRACE); } @@ -147,114 +148,142 @@ public void trace(String message) { log(LOG_LEVEL_TRACE, message, null); } + @Override public void trace(String format, Object param1) { formatAndLog(LOG_LEVEL_TRACE, format, param1, null); } + @Override public void trace(String format, Object param1, Object param2) { formatAndLog(LOG_LEVEL_TRACE, format, param1, param2); } + @Override public void trace(String format, Object... argArray) { formatAndLog(LOG_LEVEL_TRACE, format, argArray); } + @Override public void trace(String message, Throwable t) { log(LOG_LEVEL_TRACE, message, t); } + @Override public boolean isDebugEnabled() { return isLevelEnabled(LOG_LEVEL_DEBUG); } + @Override public void debug(String message) { log(LOG_LEVEL_DEBUG, message, null); } + @Override public void debug(String format, Object param1) { formatAndLog(LOG_LEVEL_DEBUG, format, param1, null); } + @Override public void debug(String format, Object param1, Object param2) { formatAndLog(LOG_LEVEL_DEBUG, format, param1, param2); } + @Override public void debug(String format, Object... argArray) { formatAndLog(LOG_LEVEL_DEBUG, format, argArray); } + @Override public void debug(String message, Throwable throwable) { log(LOG_LEVEL_DEBUG, message, throwable); } + @Override public boolean isInfoEnabled() { return isLevelEnabled(LOG_LEVEL_INFO); } + @Override public void info(String message) { log(LOG_LEVEL_INFO, message, null); } + @Override public void info(String format, Object arg) { formatAndLog(LOG_LEVEL_INFO, format, arg, null); } + @Override public void info(String format, Object arg1, Object arg2) { formatAndLog(LOG_LEVEL_INFO, format, arg1, arg2); } + @Override public void info(String format, Object... argArray) { formatAndLog(LOG_LEVEL_INFO, format, argArray); } + @Override public void info(String message, Throwable throwable) { log(LOG_LEVEL_INFO, message, throwable); } + @Override public boolean isWarnEnabled() { return isLevelEnabled(LOG_LEVEL_WARN); } + @Override public void warn(String message) { log(LOG_LEVEL_WARN, message, null); } + @Override public void warn(String format, Object arg) { formatAndLog(LOG_LEVEL_WARN, format, arg, null); } + @Override public void warn(String format, Object arg1, Object arg2) { formatAndLog(LOG_LEVEL_WARN, format, arg1, arg2); } + @Override public void warn(String format, Object... argArray) { formatAndLog(LOG_LEVEL_WARN, format, argArray); } + @Override public void warn(String message, Throwable throwable) { log(LOG_LEVEL_WARN, message, throwable); } + @Override public boolean isErrorEnabled() { return isLevelEnabled(LOG_LEVEL_ERROR); } + @Override public void error(String message) { log(LOG_LEVEL_ERROR, message, null); } + @Override public void error(String format, Object arg) { formatAndLog(LOG_LEVEL_ERROR, format, arg, null); } + @Override public void error(String format, Object arg1, Object arg2) { formatAndLog(LOG_LEVEL_ERROR, format, arg1, arg2); } + @Override public void error(String format, Object... argArray) { formatAndLog(LOG_LEVEL_ERROR, format, argArray); } + @Override public void error(String message, Throwable throwable) { log(LOG_LEVEL_ERROR, message, throwable); } diff --git a/cli/src/main/java/de/jplag/cli/logger/CollectedLoggerFactory.java b/cli/src/main/java/de/jplag/cli/logger/CollectedLoggerFactory.java index 1971e4da9..ffba004b9 100644 --- a/cli/src/main/java/de/jplag/cli/logger/CollectedLoggerFactory.java +++ b/cli/src/main/java/de/jplag/cli/logger/CollectedLoggerFactory.java @@ -23,6 +23,7 @@ public CollectedLoggerFactory() { /** * Return an appropriate {@link CollectedLogger} instance by name. */ + @Override public Logger getLogger(String name) { CollectedLogger simpleLogger = loggerMap.get(name); if (simpleLogger != null) { diff --git a/cli/src/test/java/de/jplag/cli/AdvancedGroupTest.java b/cli/src/test/java/de/jplag/cli/AdvancedGroupTest.java new file mode 100644 index 000000000..bc88af731 --- /dev/null +++ b/cli/src/test/java/de/jplag/cli/AdvancedGroupTest.java @@ -0,0 +1,23 @@ +package de.jplag.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +class AdvancedGroupTest extends CommandLineInterfaceTest { + private static final String SUFFIXES = ".sc,.scala"; + + private static final double SIMILARITY_THRESHOLD = 0.5; + + /** + * Verify that it is possible to set multiple options in the "advanced" options group. + */ + @Test + void testNotExclusive() throws CliException { + buildOptionsFromCLI(defaultArguments().suffixes(SUFFIXES).similarityThreshold(SIMILARITY_THRESHOLD)); + assertEquals(Arrays.stream(SUFFIXES.split(",")).toList(), options.fileSuffixes()); + assertEquals(0.5, options.similarityThreshold()); + } +} diff --git a/cli/src/test/java/de/jplag/cli/CustomHelpTests.java b/cli/src/test/java/de/jplag/cli/CustomHelpTests.java new file mode 100644 index 000000000..3abde7505 --- /dev/null +++ b/cli/src/test/java/de/jplag/cli/CustomHelpTests.java @@ -0,0 +1,32 @@ +package de.jplag.cli; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import picocli.CommandLine; + +/** + * Tests for the {@link CustomHelp} class + */ +class CustomHelpTests { + private CommandLine.Help help; + + /** + * Creates the help object + */ + @BeforeEach + void setup() { + CommandLine.Model.CommandSpec commandSpec = CommandLine.Model.CommandSpec.create(); + this.help = new HelpFactory().create(commandSpec, new CommandLine(commandSpec).getColorScheme()); + } + + /** + * Tests, that the custom help object returns the custom label renderer + */ + @Test + void testReturnsCustomRenderer() { + Assertions.assertTrue(this.help.parameterLabelRenderer() instanceof ParamLabelRenderer, + "The custom help object returned the wrong ParamLabelRenderer type."); + } +} diff --git a/cli/src/test/java/de/jplag/cli/LanguageTest.java b/cli/src/test/java/de/jplag/cli/LanguageTest.java index e95b80cef..ed59a1a13 100644 --- a/cli/src/test/java/de/jplag/cli/LanguageTest.java +++ b/cli/src/test/java/de/jplag/cli/LanguageTest.java @@ -26,7 +26,7 @@ void testInvalidLanguage() { @Test void testLoading() { var languages = LanguageLoader.getAllAvailableLanguages(); - assertEquals(16, languages.size(), "Loaded Languages: " + languages.keySet()); + assertEquals(18, languages.size(), "Loaded Languages: " + languages.keySet()); } @Test diff --git a/cli/src/test/java/de/jplag/cli/ParamLabelRendererTest.java b/cli/src/test/java/de/jplag/cli/ParamLabelRendererTest.java new file mode 100644 index 000000000..f10a2d350 --- /dev/null +++ b/cli/src/test/java/de/jplag/cli/ParamLabelRendererTest.java @@ -0,0 +1,82 @@ +package de.jplag.cli; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import picocli.CommandLine; + +/** + * Tests for the custom {@link ParamLabelRenderer} + */ +class ParamLabelRendererTest { + private CommandLine.Help.IParamLabelRenderer paramLabelRenderer; + private CommandLine.Help.IParamLabelRenderer baseLabelRenderer; + + private CommandLine.Help.Ansi ansi; + private List styles; + + private static final String expectedEnumLabel = "=<{FIRST, SECOND, THIRD}>"; + + /** + * Creates the parameterLabelRenderer, the base renderer and formatting information for picocli. + */ + @BeforeEach + void setup() { + CommandLine commandLine = new CommandLine(CommandLine.Model.CommandSpec.create()); + CommandLine.Help help = commandLine.getHelp(); + + this.baseLabelRenderer = help.parameterLabelRenderer(); + this.paramLabelRenderer = new ParamLabelRenderer(this.baseLabelRenderer); + + this.ansi = help.ansi(); + this.styles = help.colorScheme().optionStyles(); + } + + /** + * Tests if enums are rendered correctly. + */ + @Test + void testRenderEnum() { + CommandLine.Model.ArgSpec argument = CommandLine.Model.OptionSpec.builder("enum").type(TestEnum.class).build(); + + String label = this.paramLabelRenderer.renderParameterLabel(argument, this.ansi, this.styles).plainString(); + Assertions.assertEquals(expectedEnumLabel, label); + } + + /** + * Tests, that a bunch of parameter types produces the same label as the default renderer from picocli + * @param parameterType The type for the option + */ + @ParameterizedTest + @ValueSource(classes = {Integer.class, String.class, Boolean.class}) + void testRenderDifferentTypes(Class parameterType) { + CommandLine.Model.ArgSpec argument = CommandLine.Model.OptionSpec.builder("test").type(parameterType).build(); + + String baseLabel = this.baseLabelRenderer.renderParameterLabel(argument, this.ansi, this.styles).plainString(); + String customLabel = this.paramLabelRenderer.renderParameterLabel(argument, this.ansi, this.styles).plainString(); + Assertions.assertEquals(baseLabel, customLabel); + } + + /** + * Tests that both the custom and the base label renderer return the same separator + */ + @Test + void testSameSeparator() { + Assertions.assertEquals(this.baseLabelRenderer.separator(), this.paramLabelRenderer.separator()); + } + + /** + * Enum used as for testing the label + */ + @SuppressWarnings("unused") + enum TestEnum { + FIRST, + SECOND, + THIRD + } +} diff --git a/core/src/main/java/de/jplag/GreedyStringTiling.java b/core/src/main/java/de/jplag/GreedyStringTiling.java index 0a1a6df91..c832d4f30 100644 --- a/core/src/main/java/de/jplag/GreedyStringTiling.java +++ b/core/src/main/java/de/jplag/GreedyStringTiling.java @@ -22,6 +22,8 @@ public class GreedyStringTiling { private final int minimumMatchLength; + private final int minimumNeighborLength; + private JPlagOptions options; private ConcurrentMap tokenTypeValues; private final Map> baseCodeMarkings = new IdentityHashMap<>(); @@ -29,7 +31,10 @@ public class GreedyStringTiling { private final Map cachedHashLookupTables = new IdentityHashMap<>(); public GreedyStringTiling(JPlagOptions options) { - this.minimumMatchLength = options.minimumTokenMatch(); + this.options = options; + // Ensures 1 <= neighborLength <= minimumTokenMatch + this.minimumNeighborLength = Math.min(Math.max(options.mergingOptions().minimumNeighborLength(), 1), options.minimumTokenMatch()); + this.minimumMatchLength = options.mergingOptions().enabled() ? this.minimumNeighborLength : options.minimumTokenMatch(); this.tokenTypeValues = new ConcurrentHashMap<>(); this.tokenTypeValues.put(SharedTokenType.FILE_END, 0); } @@ -90,17 +95,9 @@ public final JPlagComparison compare(Submission firstSubmission, Submission seco * @return the comparison results. */ private JPlagComparison compareInternal(Submission leftSubmission, Submission rightSubmission) { - List leftTokens = leftSubmission.getTokenList(); - List rightTokens = rightSubmission.getTokenList(); - int[] leftValues = tokenValueListFromSubmission(leftSubmission); int[] rightValues = tokenValueListFromSubmission(rightSubmission); - // comparison uses <= because it is assumed that the last token is a pivot (FILE_END) - if (leftTokens.size() <= minimumMatchLength || rightTokens.size() <= minimumMatchLength) { - return new JPlagComparison(leftSubmission, rightSubmission, List.of()); - } - boolean[] leftMarked = calculateInitiallyMarked(leftSubmission); boolean[] rightMarked = calculateInitiallyMarked(rightSubmission); @@ -109,6 +106,7 @@ private JPlagComparison compareInternal(Submission leftSubmission, Submission ri int maximumMatchLength; List globalMatches = new ArrayList<>(); + List ignoredMatches = new ArrayList<>(); do { maximumMatchLength = minimumMatchLength; List iterationMatches = new ArrayList<>(); @@ -138,7 +136,11 @@ private JPlagComparison compareInternal(Submission leftSubmission, Submission ri } } for (Match match : iterationMatches) { - addMatchIfNotOverlapping(globalMatches, match); + if (match.length() < options.minimumTokenMatch()) { + addMatchIfNotOverlapping(ignoredMatches, match); + } else { + addMatchIfNotOverlapping(globalMatches, match); + } int leftStartIndex = match.startOfFirst(); int rightStartIndex = match.startOfSecond(); for (int offset = 0; offset < match.length(); offset++) { @@ -147,7 +149,7 @@ private JPlagComparison compareInternal(Submission leftSubmission, Submission ri } } } while (maximumMatchLength != minimumMatchLength); - return new JPlagComparison(leftSubmission, rightSubmission, globalMatches); + return new JPlagComparison(leftSubmission, rightSubmission, globalMatches, ignoredMatches); } /** diff --git a/core/src/main/java/de/jplag/JPlag.java b/core/src/main/java/de/jplag/JPlag.java index 3011f8eb5..16bafb1da 100644 --- a/core/src/main/java/de/jplag/JPlag.java +++ b/core/src/main/java/de/jplag/JPlag.java @@ -10,6 +10,7 @@ import de.jplag.clustering.ClusteringFactory; import de.jplag.exceptions.ExitException; import de.jplag.exceptions.SubmissionException; +import de.jplag.merging.MatchMerging; import de.jplag.options.JPlagOptions; import de.jplag.reporting.reportobject.model.Version; import de.jplag.strategy.ComparisonStrategy; @@ -71,6 +72,12 @@ public static JPlagResult run(JPlagOptions options) throws ExitException { // Compare valid submissions. JPlagResult result = comparisonStrategy.compareSubmissions(submissionSet); + + // Use Match Merging against obfuscation + if (options.mergingOptions().enabled()) { + result = new MatchMerging(options).mergeMatchesOf(result); + } + if (logger.isInfoEnabled()) logger.info("Total time for comparing submissions: {}", TimeUtil.formatDuration(result.getDuration())); result.setClusteringResult(ClusteringFactory.getClusterings(result.getAllComparisons(), options.clusteringOptions())); diff --git a/core/src/main/java/de/jplag/JPlagComparison.java b/core/src/main/java/de/jplag/JPlagComparison.java index 7f69d1b1d..37aa0c7ad 100644 --- a/core/src/main/java/de/jplag/JPlagComparison.java +++ b/core/src/main/java/de/jplag/JPlagComparison.java @@ -9,17 +9,18 @@ * @param secondSubmission is the second of the two submissions. * @param matches is the unmodifiable list of all matches between the two submissions. */ -public record JPlagComparison(Submission firstSubmission, Submission secondSubmission, List matches) { +public record JPlagComparison(Submission firstSubmission, Submission secondSubmission, List matches, List ignoredMatches) { /** * Initializes a new comparison. * @param firstSubmission is the first of the two submissions. * @param secondSubmission is the second of the two submissions. * @param matches is the list of all matches between the two submissions. */ - public JPlagComparison(Submission firstSubmission, Submission secondSubmission, List matches) { + public JPlagComparison(Submission firstSubmission, Submission secondSubmission, List matches, List ignoredMatches) { this.firstSubmission = firstSubmission; this.secondSubmission = secondSubmission; this.matches = Collections.unmodifiableList(matches); + this.ignoredMatches = Collections.unmodifiableList(ignoredMatches); } /** diff --git a/core/src/main/java/de/jplag/JPlagResult.java b/core/src/main/java/de/jplag/JPlagResult.java index 8b4d123d9..2b1aabd32 100644 --- a/core/src/main/java/de/jplag/JPlagResult.java +++ b/core/src/main/java/de/jplag/JPlagResult.java @@ -23,7 +23,7 @@ public class JPlagResult { private final int[] similarityDistribution; // 10-element array representing the similarity distribution of the detected matches. private List> clusteringResult; - private final int SIMILARITY_DISTRIBUTION_SIZE = 10; + private final int SIMILARITY_DISTRIBUTION_SIZE = 100; public JPlagResult(List comparisons, SubmissionSet submissions, long durationInMillis, JPlagOptions options) { // sort by similarity (descending) diff --git a/core/src/main/java/de/jplag/Submission.java b/core/src/main/java/de/jplag/Submission.java index 34ff5dbb7..f47db6d60 100644 --- a/core/src/main/java/de/jplag/Submission.java +++ b/core/src/main/java/de/jplag/Submission.java @@ -294,4 +294,14 @@ private List getOrder(List tokenList) { } return order; } + + /** + * @return Submission containing shallow copies of its fields. + */ + public Submission copy() { + Submission copy = new Submission(name, submissionRootFile, isNew, files, language); + copy.setTokenList(new ArrayList<>(tokenList)); + copy.setBaseCodeComparison(baseCodeComparison); + return copy; + } } diff --git a/core/src/main/java/de/jplag/merging/MatchMerging.java b/core/src/main/java/de/jplag/merging/MatchMerging.java new file mode 100644 index 000000000..108530a7e --- /dev/null +++ b/core/src/main/java/de/jplag/merging/MatchMerging.java @@ -0,0 +1,203 @@ +package de.jplag.merging; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import de.jplag.JPlagComparison; +import de.jplag.JPlagResult; +import de.jplag.Match; +import de.jplag.SharedTokenType; +import de.jplag.Submission; +import de.jplag.Token; +import de.jplag.options.JPlagOptions; + +/** + * This class implements a match merging algorithm which serves as a defense mechanism against obfuscation attacks. + * Based on configurable parameters MinimumNeighborLength and MaximumGapSize, it alters prior results from pairwise + * submission comparisons and merges all neighboring matches that fit the specified thresholds. Submissions are referred + * to as left and right and neighboring matches as upper and lower. When neighboring matches get merged they become one + * and the tokens separating them get removed from the submission clone. MinimumNeighborLength describes how short a + * match can be and MaximumGapSize describes how many tokens can be between two neighboring matches. Both are set in + * {@link JPlagOptions} as {@link MergingOptions} and default to (2,6). + */ +public class MatchMerging { + private JPlagOptions options; + + /** + * Instantiates the match merging algorithm for a comparison result and a set of specific options. + * @param options encapsulates the adjustable options + */ + public MatchMerging(JPlagOptions options) { + this.options = options; + } + + /** + * Runs the internal match merging pipeline. It computes neighboring matches, merges them based on + * {@link MergingOptions} and removes remaining too short matches afterwards. + * @param result is the initially computed result object + * @return JPlagResult containing the merged matches + */ + public JPlagResult mergeMatchesOf(JPlagResult result) { + long timeBeforeStartInMillis = System.currentTimeMillis(); + + List comparisons = new ArrayList<>(result.getAllComparisons()); + List comparisonsMerged = new ArrayList<>(); + + for (JPlagComparison comparison : comparisons) { + Submission leftSubmission = comparison.firstSubmission().copy(); + Submission rightSubmission = comparison.secondSubmission().copy(); + List globalMatches = new ArrayList<>(comparison.matches()); + globalMatches.addAll(comparison.ignoredMatches()); + globalMatches = removeTooShortMatches(mergeNeighbors(globalMatches, leftSubmission, rightSubmission)); + comparisonsMerged.add(new JPlagComparison(leftSubmission, rightSubmission, globalMatches, new ArrayList<>())); + } + + long durationInMillis = System.currentTimeMillis() - timeBeforeStartInMillis; + return new JPlagResult(comparisonsMerged, result.getSubmissions(), result.getDuration() + durationInMillis, options); + } + + /** + * Computes neighbors by sorting based on order of matches in the left and right submissions and then checking which are + * next to each other in both. + * @param globalMatches + * @return neighbors containing a list of pairs of neighboring matches + */ + private List computeNeighbors(List globalMatches) { + List neighbors = new ArrayList<>(); + List sortedByLeft = new ArrayList<>(globalMatches); + Collections.sort(sortedByLeft, (match1, match2) -> match1.startOfFirst() - match2.startOfFirst()); + List sortedByRight = new ArrayList<>(globalMatches); + Collections.sort(sortedByRight, (match1, match2) -> match1.startOfSecond() - match2.startOfSecond()); + for (int i = 0; i < sortedByLeft.size() - 1; i++) { + if (sortedByRight.indexOf(sortedByLeft.get(i)) == (sortedByRight.indexOf(sortedByLeft.get(i + 1)) - 1)) { + neighbors.add(new Neighbor(sortedByLeft.get(i), sortedByLeft.get(i + 1))); + } + } + return neighbors; + } + + /** + * This function iterates through the neighboring matches and checks which fit the merging criteria. Those who do are + * merged and the original matches are removed. This is done, until there are either no neighbors left, or none fit the + * criteria + * @return globalMatches containing merged matches. + */ + private List mergeNeighbors(List globalMatches, Submission leftSubmission, Submission rightSubmission) { + int i = 0; + List neighbors = computeNeighbors(globalMatches); + + while (i < neighbors.size()) { + Match upperNeighbor = neighbors.get(i).upperMatch(); + Match lowerNeighbor = neighbors.get(i).lowerMatch(); + + int lengthUpper = upperNeighbor.length(); + int lengthLower = lowerNeighbor.length(); + int tokenBetweenLeft = lowerNeighbor.startOfFirst() - upperNeighbor.endOfFirst() - 1; + int tokensBetweenRight = lowerNeighbor.startOfSecond() - upperNeighbor.endOfSecond() - 1; + double averageTokensBetweenMatches = (tokenBetweenLeft + tokensBetweenRight) / 2.0; + // Checking length is not necessary as GST already checked length while computing matches + if (averageTokensBetweenMatches <= options.mergingOptions().maximumGapSize() + && !mergeOverlapsFiles(leftSubmission, rightSubmission, upperNeighbor, tokenBetweenLeft, tokensBetweenRight)) { + globalMatches.remove(upperNeighbor); + globalMatches.remove(lowerNeighbor); + globalMatches.add(new Match(upperNeighbor.startOfFirst(), upperNeighbor.startOfSecond(), lengthUpper + lengthLower)); + globalMatches = removeToken(globalMatches, leftSubmission, rightSubmission, upperNeighbor, tokenBetweenLeft, tokensBetweenRight); + neighbors = computeNeighbors(globalMatches); + i = 0; + } else { + i++; + } + } + return globalMatches; + } + + /** + * This function checks if a merge would go over file boundaries. + * @param leftSubmission is the left submission + * @param rightSubmission is the right submission + * @param upperNeighbor is the upper neighboring match + * @param tokensBetweenLeft amount of token that separate the neighboring matches in the left submission and need to be + * removed + * @param tokensBetweenRight amount token that separate the neighboring matches in the send submission and need to be + * removed + * @return true if the merge goes over file boundaries. + */ + private boolean mergeOverlapsFiles(Submission leftSubmission, Submission rightSubmission, Match upperNeighbor, int tokensBetweenLeft, + int tokensBetweenRight) { + if (leftSubmission.getFiles().size() == 1 && rightSubmission.getFiles().size() == 1) { + return false; + } + int startLeft = upperNeighbor.startOfFirst(); + int startRight = upperNeighbor.startOfSecond(); + int lengthUpper = upperNeighbor.length(); + + List tokenLeft = new ArrayList<>(leftSubmission.getTokenList()); + List tokenRight = new ArrayList<>(rightSubmission.getTokenList()); + tokenLeft = tokenLeft.subList(startLeft + lengthUpper, startLeft + lengthUpper + tokensBetweenLeft); + tokenRight = tokenRight.subList(startRight + lengthUpper, startRight + lengthUpper + tokensBetweenRight); + + return containsFileEndToken(tokenLeft) || containsFileEndToken(tokenRight); + } + + /** + * This function checks whether a list of token contains FILE_END + * @param token is the list of token + * @return true if FILE_END is in token + */ + private boolean containsFileEndToken(List token) { + return token.stream().map(Token::getType).anyMatch(it -> it.equals(SharedTokenType.FILE_END)); + } + + /** + * This function removes token from both submissions after a merge has been performed. Additionally it moves the + * starting positions from matches, that occur after the merged neighboring matches, by the amount of removed token. + * @param globalMatches + * @param leftSubmission is the left submission + * @param rightSubmission is the right submission + * @param upperNeighbor is the upper neighboring match + * @param tokensBetweenLeft amount of token that separate the neighboring matches in the left submission and need to be + * removed + * @param tokensBetweenRight amount token that separate the neighboring matches in the send submission and need to be + * removed + * @return shiftedMatches with the mentioned changes. + */ + private List removeToken(List globalMatches, Submission leftSubmission, Submission rightSubmission, Match upperNeighbor, + int tokensBetweenLeft, int tokensBetweenRight) { + int startLeft = upperNeighbor.startOfFirst(); + int startRight = upperNeighbor.startOfSecond(); + int lengthUpper = upperNeighbor.length(); + + List tokenLeft = new ArrayList<>(leftSubmission.getTokenList()); + List tokenRight = new ArrayList<>(rightSubmission.getTokenList()); + tokenLeft.subList(startLeft + lengthUpper, startLeft + lengthUpper + tokensBetweenLeft).clear(); + tokenRight.subList(startRight + lengthUpper, startRight + lengthUpper + tokensBetweenRight).clear(); + leftSubmission.setTokenList(tokenLeft); + rightSubmission.setTokenList(tokenRight); + + List shiftedMatches = new ArrayList<>(); + for (Match match : globalMatches) { + int leftShift = match.startOfFirst() > startLeft ? tokensBetweenLeft : 0; + int rightShift = match.startOfSecond() > startRight ? tokensBetweenRight : 0; + Match alteredMatch = new Match(match.startOfFirst() - leftShift, match.startOfSecond() - rightShift, match.length()); + shiftedMatches.add(alteredMatch); + } + + return shiftedMatches; + } + + /** + * This method marks the end of the merging pipeline and removes the remaining too short matches from + * @param globalMatches + */ + private List removeTooShortMatches(List globalMatches) { + List toRemove = new ArrayList<>(); + for (Match match : globalMatches) { + if (match.length() < options.minimumTokenMatch()) { + toRemove.add(match); + } + } + globalMatches.removeAll(toRemove); + return globalMatches; + } +} \ No newline at end of file diff --git a/core/src/main/java/de/jplag/merging/MergingOptions.java b/core/src/main/java/de/jplag/merging/MergingOptions.java new file mode 100644 index 000000000..fc340e293 --- /dev/null +++ b/core/src/main/java/de/jplag/merging/MergingOptions.java @@ -0,0 +1,44 @@ +package de.jplag.merging; + +/** + * Collection of parameters that describe how a match merging should be performed. + * @param minimumNeighborLength describes how short a match can be, to be considered (Defaults to 2). + * @param maximumGapSize describes how many tokens can be between to neighboring matches (Defaults to 6). + */ +public record MergingOptions(boolean enabled, int minimumNeighborLength, int maximumGapSize) { + + /** + * The default values of MergingOptions are false for the enable-switch, which deactivate MatchMerging, while + * minimumNeighborLength and maximumGapSize default to (2,6), which in testing yielded the best results. + */ + public MergingOptions() { + this(false, 2, 6); + } + + /** + * Builder pattern method for setting enabled + * @param enabled containing the new value + * @return MergingOptions with specified enabled + */ + public MergingOptions withEnabled(boolean enabled) { + return new MergingOptions(enabled, minimumNeighborLength, maximumGapSize); + } + + /** + * Builder pattern method for setting minimumNeighborLength + * @param minimumNeighborLength containing the new value + * @return MergingOptions with specified minimumNeighborLength + */ + public MergingOptions withMinimumNeighborLength(int minimumNeighborLength) { + return new MergingOptions(enabled, minimumNeighborLength, maximumGapSize); + } + + /** + * Builder pattern method for setting maximumGapSize + * @param maximumGapSize containing the new value + * @return MergingOptions with specified maximumGapSize + */ + public MergingOptions withMaximumGapSize(int maximumGapSize) { + return new MergingOptions(enabled, minimumNeighborLength, maximumGapSize); + } +} diff --git a/core/src/main/java/de/jplag/merging/Neighbor.java b/core/src/main/java/de/jplag/merging/Neighbor.java new file mode 100644 index 000000000..dc9052b18 --- /dev/null +++ b/core/src/main/java/de/jplag/merging/Neighbor.java @@ -0,0 +1,10 @@ +package de.jplag.merging; + +import de.jplag.Match; + +/** + * This class realizes a pair of neighboring matches, named upperMatch and lowerMatch. Two matches are considered + * neighbors, if they begin directly after one another in the left submission and in the right submission. + */ +public record Neighbor(Match upperMatch, Match lowerMatch) { +} \ No newline at end of file diff --git a/core/src/main/java/de/jplag/options/JPlagOptions.java b/core/src/main/java/de/jplag/options/JPlagOptions.java index 2fbb3be59..44eea1d2f 100644 --- a/core/src/main/java/de/jplag/options/JPlagOptions.java +++ b/core/src/main/java/de/jplag/options/JPlagOptions.java @@ -19,6 +19,7 @@ import de.jplag.Language; import de.jplag.clustering.ClusteringOptions; import de.jplag.exceptions.BasecodeException; +import de.jplag.merging.MergingOptions; import de.jplag.util.FileUtils; /** @@ -47,7 +48,7 @@ public record JPlagOptions(Language language, Integer minimumTokenMatch, Set submissionDirectories, Set oldSubmissionDirectories, File baseCodeSubmissionDirectory, String subdirectoryName, List fileSuffixes, String exclusionFileName, SimilarityMetric similarityMetric, double similarityThreshold, int maximumNumberOfComparisons, ClusteringOptions clusteringOptions, - boolean debugParser) { + boolean debugParser, MergingOptions mergingOptions) { public static final double DEFAULT_SIMILARITY_THRESHOLD = 0; public static final int DEFAULT_SHOWN_COMPARISONS = 100; @@ -60,13 +61,13 @@ public record JPlagOptions(Language language, Integer minimumTokenMatch, Set submissionDirectories, Set oldSubmissionDirectories) { this(language, null, submissionDirectories, oldSubmissionDirectories, null, null, null, null, DEFAULT_SIMILARITY_METRIC, - DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SHOWN_COMPARISONS, new ClusteringOptions(), false); + DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SHOWN_COMPARISONS, new ClusteringOptions(), false, new MergingOptions()); } public JPlagOptions(Language language, Integer minimumTokenMatch, Set submissionDirectories, Set oldSubmissionDirectories, File baseCodeSubmissionDirectory, String subdirectoryName, List fileSuffixes, String exclusionFileName, SimilarityMetric similarityMetric, double similarityThreshold, int maximumNumberOfComparisons, ClusteringOptions clusteringOptions, - boolean debugParser) { + boolean debugParser, MergingOptions mergingOptions) { this.language = language; this.debugParser = debugParser; this.fileSuffixes = fileSuffixes == null || fileSuffixes.isEmpty() ? null : Collections.unmodifiableList(fileSuffixes); @@ -80,84 +81,91 @@ public JPlagOptions(Language language, Integer minimumTokenMatch, Set subm this.baseCodeSubmissionDirectory = baseCodeSubmissionDirectory; this.subdirectoryName = subdirectoryName; this.clusteringOptions = clusteringOptions; + this.mergingOptions = mergingOptions; } public JPlagOptions withLanguageOption(Language language) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withDebugParser(boolean debugParser) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withFileSuffixes(List fileSuffixes) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withSimilarityThreshold(double similarityThreshold) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withMaximumNumberOfComparisons(int maximumNumberOfComparisons) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withSimilarityMetric(SimilarityMetric similarityMetric) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withMinimumTokenMatch(Integer minimumTokenMatch) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withExclusionFileName(String exclusionFileName) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withSubmissionDirectories(Set submissionDirectories) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withOldSubmissionDirectories(Set oldSubmissionDirectories) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withBaseCodeSubmissionDirectory(File baseCodeSubmissionDirectory) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withSubdirectoryName(String subdirectoryName) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } public JPlagOptions withClusteringOptions(ClusteringOptions clusteringOptions) { return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); + } + + public JPlagOptions withMergingOptions(MergingOptions mergingOptions) { + return new JPlagOptions(language, minimumTokenMatch, submissionDirectories, oldSubmissionDirectories, baseCodeSubmissionDirectory, + subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, + clusteringOptions, debugParser, mergingOptions); } public boolean hasBaseCode() { @@ -246,10 +254,10 @@ private Integer normalizeMinimumTokenMatch(Integer minimumTokenMatch) { public JPlagOptions(Language language, Integer minimumTokenMatch, File submissionDirectory, Set oldSubmissionDirectories, String baseCodeSubmissionName, String subdirectoryName, List fileSuffixes, String exclusionFileName, SimilarityMetric similarityMetric, double similarityThreshold, int maximumNumberOfComparisons, ClusteringOptions clusteringOptions, - boolean debugParser) throws BasecodeException { + boolean debugParser, MergingOptions mergingOptions) throws BasecodeException { this(language, minimumTokenMatch, Set.of(submissionDirectory), oldSubmissionDirectories, convertLegacyBaseCodeToFile(baseCodeSubmissionName, submissionDirectory), subdirectoryName, fileSuffixes, exclusionFileName, - similarityMetric, similarityThreshold, maximumNumberOfComparisons, clusteringOptions, debugParser); + similarityMetric, similarityThreshold, maximumNumberOfComparisons, clusteringOptions, debugParser, mergingOptions); } /** @@ -272,7 +280,7 @@ public JPlagOptions withBaseCodeSubmissionName(String baseCodeSubmissionName) { try { return new JPlagOptions(language, minimumTokenMatch, submissionDirectory, oldSubmissionDirectories, baseCodeSubmissionName, subdirectoryName, fileSuffixes, exclusionFileName, similarityMetric, similarityThreshold, maximumNumberOfComparisons, - clusteringOptions, debugParser); + clusteringOptions, debugParser, mergingOptions); } catch (BasecodeException e) { throw new IllegalArgumentException(e.getMessage(), e.getCause()); } @@ -298,11 +306,7 @@ private static File convertLegacyBaseCodeToFile(String baseCodeSubmissionName, F while (normalizedName.endsWith(File.separator)) { normalizedName = normalizedName.substring(0, normalizedName.length() - 1); } - if (normalizedName.isEmpty() || normalizedName.contains(File.separator)) { - throw new BasecodeException( - "The basecode directory name \"" + normalizedName + "\" cannot contain dots! Please migrate to the path-based API."); - } - if (normalizedName.contains(".")) { + if (normalizedName.isEmpty() || normalizedName.contains(File.separator) || normalizedName.contains(".")) { throw new BasecodeException( "The basecode directory name \"" + normalizedName + "\" cannot contain dots! Please migrate to the path-based API."); } 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 5b3594979..242d09138 100644 --- a/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java +++ b/core/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java @@ -12,6 +12,7 @@ import de.jplag.JPlagResult; import de.jplag.Submission; import de.jplag.Token; +import de.jplag.options.SimilarityMetric; import de.jplag.reporting.FilePathUtil; import de.jplag.reporting.reportobject.model.ComparisonReport; import de.jplag.reporting.reportobject.model.Match; @@ -55,7 +56,8 @@ private void writeComparisons(String path, List comparisons) { String secondSubmissionId = submissionToIdFunction.apply(comparison.secondSubmission()); String fileName = generateComparisonName(firstSubmissionId, secondSubmissionId); addToLookUp(firstSubmissionId, secondSubmissionId, fileName); - var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId, comparison.similarity(), + var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId, + Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), SimilarityMetric.MAX.name(), comparison.maximalSimilarity()), convertMatchesToReportMatches(comparison)); fileWriter.saveAsJSON(comparisonReport, path, fileName); }); 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 1815c55e2..0f8f142da 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java @@ -33,7 +33,6 @@ import de.jplag.reporting.jsonfactory.ToDiskWriter; import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper; import de.jplag.reporting.reportobject.mapper.MetricMapper; -import de.jplag.reporting.reportobject.model.Metric; import de.jplag.reporting.reportobject.model.OverviewReport; import de.jplag.reporting.reportobject.model.SubmissionFileIndex; import de.jplag.reporting.reportobject.model.Version; @@ -175,7 +174,7 @@ private void writeOverview(JPlagResult result, String path) { int totalComparisons = result.getAllComparisons().size(); int numberOfMaximumComparisons = result.getOptions().maximumNumberOfComparisons(); - int shownComparisons = totalComparisons > numberOfMaximumComparisons ? numberOfMaximumComparisons : totalComparisons; + int shownComparisons = Math.min(totalComparisons, numberOfMaximumComparisons); int missingComparisons = totalComparisons > numberOfMaximumComparisons ? (totalComparisons - numberOfMaximumComparisons) : 0; logger.info("Total Comparisons: {}. Comparisons in Report: {}. Omitted Comparisons: {}.", totalComparisons, shownComparisons, missingComparisons); @@ -190,7 +189,8 @@ private void writeOverview(JPlagResult result, String path) { result.getOptions().minimumTokenMatch(), // matchSensitivity getDate(),// dateOfExecution result.getDuration(), // executionTime - getMetrics(result),// metrics + MetricMapper.getDistributions(result), // distribution + new MetricMapper(submissionToIdFunction).getTopComparisons(result),// topComparisons clusteringResultMapper.map(result), // clusters totalComparisons); // totalComparisons @@ -220,16 +220,6 @@ private Set getSubmissions(List comparisons) { return submissions; } - /** - * Gets the used metrics in a JPlag comparison. As Max Metric is included in every JPlag run, this always include Max - * Metric. - * @return A list contains Metric DTOs. - */ - private List getMetrics(JPlagResult result) { - MetricMapper metricMapper = new MetricMapper(submissionToIdFunction); - return List.of(metricMapper.getAverageMetric(result), metricMapper.getMaxMetric(result)); - } - private String getDate() { SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy"); Date date = new Date(); diff --git a/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java b/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java index 0b8a819d4..bdad683a0 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java @@ -3,16 +3,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.function.Function; import de.jplag.JPlagComparison; import de.jplag.JPlagResult; -import de.jplag.Messages; import de.jplag.Submission; import de.jplag.options.SimilarityMetric; -import de.jplag.reporting.reportobject.model.Metric; import de.jplag.reporting.reportobject.model.TopComparison; /** @@ -25,40 +23,35 @@ public MetricMapper(Function submissionToIdFunction) { this.submissionToIdFunction = submissionToIdFunction; } - public Metric getAverageMetric(JPlagResult result) { - return new Metric(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()), - getTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Avg.Description")); + /** + * Generates a map of all distributions + * @param result Result containing distributions + * @return Map with key as name of metric and value as distribution + */ + public static Map> getDistributions(JPlagResult result) { + return Map.of(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()), SimilarityMetric.MAX.name(), + convertDistribution(result.getMaxSimilarityDistribution())); } - public Metric getMaxMetric(JPlagResult result) { - return new Metric(SimilarityMetric.MAX.name(), convertDistribution(result.getMaxSimilarityDistribution()), - getMaxSimilarityTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Max.Description")); + /** + * Generates a List of the top comparisons + * @param result Result containing comparisons + * @return List of top comparisons with similarities in all metrics + */ + public List getTopComparisons(JPlagResult result) { + return result.getComparisons(result.getOptions().maximumNumberOfComparisons()).stream() + .map(comparison -> new TopComparison(submissionToIdFunction.apply(comparison.firstSubmission()), + submissionToIdFunction.apply(comparison.secondSubmission()), getComparisonMetricMap(comparison))) + .toList(); } - private List getComparisons(JPlagResult result) { - int maxNumberOfComparisons = result.getOptions().maximumNumberOfComparisons(); - return result.getComparisons(maxNumberOfComparisons); + private Map getComparisonMetricMap(JPlagComparison comparison) { + return Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), SimilarityMetric.MAX.name(), comparison.maximalSimilarity()); } - private List convertDistribution(int[] array) { + private static List convertDistribution(int[] array) { List list = new ArrayList<>(Arrays.stream(array).boxed().toList()); Collections.reverse(list); return list; } - - private List getTopComparisons(List comparisons, Function similarityExtractor) { - return comparisons.stream().sorted(Comparator.comparing(similarityExtractor).reversed()) - .map(comparison -> new TopComparison(submissionToIdFunction.apply(comparison.firstSubmission()), - submissionToIdFunction.apply(comparison.secondSubmission()), similarityExtractor.apply(comparison))) - .toList(); - } - - private List getTopComparisons(List comparisons) { - return getTopComparisons(comparisons, JPlagComparison::similarity); - } - - private List getMaxSimilarityTopComparisons(List comparisons) { - return getTopComparisons(comparisons, JPlagComparison::maximalSimilarity); - } - } diff --git a/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java b/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java index 00b243fbd..5b8d2fc4b 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java @@ -1,6 +1,7 @@ package de.jplag.reporting.reportobject.model; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonProperty; @@ -8,10 +9,10 @@ * ReportViewer DTO for the comparison of two submissions. * @param firstSubmissionId id of the first submission * @param secondSubmissionId id of the second submission - * @param similarity average similarity. between 0.0 and 1.0. + * @param similarities map of metric names and corresponding similarities. between 0.0 and 1.0. * @param matches the list of matches found in the comparison of the two submissions */ public record ComparisonReport(@JsonProperty("id1") String firstSubmissionId, @JsonProperty("id2") String secondSubmissionId, - @JsonProperty("similarity") double similarity, @JsonProperty("matches") List matches) { + @JsonProperty("similarities") Map similarities, @JsonProperty("matches") List matches) { } diff --git a/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java b/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java index b4651dbe9..fa7da7673 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java @@ -31,7 +31,9 @@ public record OverviewReport( @JsonProperty("execution_time") long executionTime, - @JsonProperty("metrics") List metrics, + @JsonProperty("distributions") Map> distributions, + + @JsonProperty("top_comparisons") List topComparisons, @JsonProperty("clusters") List clusters, diff --git a/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java b/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java index 7dfa1339f..ba4a54da8 100644 --- a/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java +++ b/core/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java @@ -1,7 +1,9 @@ package de.jplag.reporting.reportobject.model; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonProperty; public record TopComparison(@JsonProperty("first_submission") String firstSubmission, @JsonProperty("second_submission") String secondSubmission, - @JsonProperty("similarity") double similarity) { + @JsonProperty("similarities") Map similarities) { } diff --git a/core/src/test/java/de/jplag/BaseCodeTest.java b/core/src/test/java/de/jplag/BaseCodeTest.java index bd307dc43..193482b8b 100644 --- a/core/src/test/java/de/jplag/BaseCodeTest.java +++ b/core/src/test/java/de/jplag/BaseCodeTest.java @@ -46,7 +46,7 @@ protected void verifyResults(JPlagResult result) { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); assertEquals(1, result.getAllComparisons().get(0).matches().size()); - assertEquals(1, result.getSimilarityDistribution()[8]); + assertEquals(1, result.getSimilarityDistribution()[81]); assertEquals(0.8125, result.getAllComparisons().get(0).similarity(), DELTA); } @@ -94,7 +94,7 @@ protected void verifySimpleSubdirectoryDuplicate(JPlagResult result, int submiss assertEquals(submissions, result.getNumberOfSubmissions()); assertEquals(comparisons, result.getAllComparisons().size()); assertEquals(1, result.getAllComparisons().get(0).matches().size()); - assertEquals(1, result.getSimilarityDistribution()[9]); + assertEquals(1, result.getSimilarityDistribution()[94]); assertEquals(0.9473, result.getAllComparisons().get(0).similarity(), DELTA); } diff --git a/core/src/test/java/de/jplag/BasicFunctionalityTest.java b/core/src/test/java/de/jplag/BasicFunctionalityTest.java index 4bf4bf347..e38d531cb 100644 --- a/core/src/test/java/de/jplag/BasicFunctionalityTest.java +++ b/core/src/test/java/de/jplag/BasicFunctionalityTest.java @@ -14,6 +14,8 @@ */ class BasicFunctionalityTest extends TestBase { + private static int DISTRIBUTION_INDEX = 66; + @Test @DisplayName("test submissions that contain obvious plagiarism") void testSimpleDuplicate() throws ExitException { @@ -22,14 +24,15 @@ void testSimpleDuplicate() throws ExitException { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); assertEquals(1, result.getAllComparisons().get(0).matches().size()); - assertEquals(1, result.getSimilarityDistribution()[6]); + assertEquals(1, result.getSimilarityDistribution()[DISTRIBUTION_INDEX]); assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA); } @Test @DisplayName("test submissions with a custom minimum token match") void testWithMinTokenMatch() throws ExitException { - var expectedDistribution = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + var expectedDistribution = new int[100]; + expectedDistribution[96] = 1; JPlagResult result = runJPlag("SimpleDuplicate", it -> it.withMinimumTokenMatch(4)); assertEquals(2, result.getNumberOfSubmissions()); @@ -71,16 +74,16 @@ void testPartialPlagiarism() throws ExitException { // Hard coded assertions on selected comparisons assertEquals(0.237, getSelectedPercent(result, "A", "B"), DELTA); assertEquals(0.996, getSelectedPercent(result, "A", "C"), DELTA); - assertEquals(0.751, getSelectedPercent(result, "A", "D"), DELTA); + assertEquals(0.760, getSelectedPercent(result, "A", "D"), DELTA); assertEquals(0.237, getSelectedPercent(result, "B", "C"), DELTA); assertEquals(0.283, getSelectedPercent(result, "B", "D"), DELTA); - assertEquals(0.751, getSelectedPercent(result, "C", "D"), DELTA); + assertEquals(0.760, getSelectedPercent(result, "C", "D"), DELTA); // More detailed assertions for the plagiarism in A-D var biggestMatch = getSelectedComparison(result, "A", "D"); - assertEquals(0.947, biggestMatch.get().maximalSimilarity(), DELTA); - assertEquals(0.622, biggestMatch.get().minimalSimilarity(), DELTA); - assertEquals(11, biggestMatch.get().matches().size()); + assertEquals(0.959, biggestMatch.get().maximalSimilarity(), DELTA); + assertEquals(0.630, biggestMatch.get().minimalSimilarity(), DELTA); + assertEquals(12, biggestMatch.get().matches().size()); } @Test @@ -90,7 +93,7 @@ void testSingleFileSubmisssions() throws ExitException { assertEquals(2, result.getNumberOfSubmissions()); assertEquals(1, result.getAllComparisons().size()); - assertEquals(1, result.getSimilarityDistribution()[6]); + assertEquals(1, result.getSimilarityDistribution()[DISTRIBUTION_INDEX]); assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA); var matches = result.getAllComparisons().get(0).matches(); diff --git a/core/src/test/java/de/jplag/LegacyBaseCodeTest.java b/core/src/test/java/de/jplag/LegacyBaseCodeTest.java index 9b5b5e40a..ca02ccff1 100644 --- a/core/src/test/java/de/jplag/LegacyBaseCodeTest.java +++ b/core/src/test/java/de/jplag/LegacyBaseCodeTest.java @@ -15,17 +15,20 @@ */ @Deprecated(since = "4.0.0", forRemoval = true) class LegacyBaseCodeTest extends BaseCodeTest { + @Override @Test void testBasecodeUserSubmissionComparison() throws ExitException { JPlagResult result = runJPlag("basecode", it -> it.withBaseCodeSubmissionName("base")); verifyResults(result); } + @Override @Test void testTinyBasecode() { assertThrows(BasecodeException.class, () -> runJPlag("TinyBasecode", it -> it.withBaseCodeSubmissionName("base"))); } + @Override @Test void testEmptySubmission() throws ExitException { JPlagResult result = runJPlag("emptysubmission", it -> it.withBaseCodeSubmissionName("base")); @@ -38,12 +41,14 @@ void testAutoTrimFileSeparators() throws ExitException { verifyResults(result); } + @Override @Test void testBasecodePathComparison() throws ExitException { JPlagResult result = runJPlag("basecode", it -> it.withBaseCodeSubmissionName(getBasePath("basecode-base"))); assertEquals(3, result.getNumberOfSubmissions()); // "basecode/base" is now a user submission. } + @Override @Test void testInvalidBasecode() { assertThrows(BasecodeException.class, () -> runJPlag("basecode", it -> it.withBaseCodeSubmissionName("WrongBasecode"))); @@ -57,6 +62,7 @@ void testBasecodeUserSubmissionWithDots() { /** * The simple duplicate contains obvious plagiarism. */ + @Override @Test void testSubdirectoryGlobalBasecode() throws ExitException { String basecode = getBasePath("SubdirectoryBase"); @@ -67,6 +73,7 @@ void testSubdirectoryGlobalBasecode() throws ExitException { /** * The simple duplicate contains obvious plagiarism. */ + @Override @Test void testSubdirectoryLocalBasecode() throws ExitException { JPlagResult result = runJPlag("SubdirectoryDuplicate", it -> it.withSubdirectoryName("src").withBaseCodeSubmissionName("Base")); diff --git a/core/src/test/java/de/jplag/NewJavaFeaturesTest.java b/core/src/test/java/de/jplag/NewJavaFeaturesTest.java index 4052455b3..2bfdd86d0 100644 --- a/core/src/test/java/de/jplag/NewJavaFeaturesTest.java +++ b/core/src/test/java/de/jplag/NewJavaFeaturesTest.java @@ -1,6 +1,7 @@ package de.jplag; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -8,16 +9,28 @@ import de.jplag.exceptions.ExitException; public class NewJavaFeaturesTest extends TestBase { + private static final int EXPECTED_MATCHES = 6; // might change if you add files to the submissions private static final double EXPECTED_SIMILARITY = 0.96; // might change if you add files to the submissions + private static final String EXPECTED_JAVA_VERSION = "17"; // might change with newer JPlag versions private static final String EXCLUSION_FILE_NAME = "blacklist.txt"; private static final String ROOT_DIRECTORY = "NewJavaFeatures"; 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"; @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); + boolean isCiRun = System.getenv(CI_VARIABLE) != null; + boolean isCorrectJavaVersion = actualJavaVersion.startsWith(EXPECTED_JAVA_VERSION); + assumeTrue(isCorrectJavaVersion || isCiRun, VERSION_MISMATCH_MESSAGE.formatted(actualJavaVersion, EXPECTED_JAVA_VERSION)); + JPlagResult result = runJPlagWithExclusionFile(ROOT_DIRECTORY, EXCLUSION_FILE_NAME); // Ensure test input did not change: @@ -29,8 +42,7 @@ public void testJavaFeatureDuplicates() throws ExitException { // Check similarity and number of matches: var comparison = result.getAllComparisons().get(0); - assertEquals(EXPECTED_SIMILARITY, comparison.similarity(), DELTA); - assertEquals(EXPECTED_MATCHES, comparison.matches().size()); + assertEquals(EXPECTED_SIMILARITY, comparison.similarity(), DELTA, VERSION_MATCH_MESSAGE); + assertEquals(EXPECTED_MATCHES, comparison.matches().size(), VERSION_MATCH_MESSAGE); } - } diff --git a/core/src/test/java/de/jplag/TestBase.java b/core/src/test/java/de/jplag/TestBase.java index e658cf1c6..a2b17132e 100644 --- a/core/src/test/java/de/jplag/TestBase.java +++ b/core/src/test/java/de/jplag/TestBase.java @@ -8,6 +8,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import de.jplag.clustering.ClusteringOptions; import de.jplag.exceptions.ExitException; import de.jplag.java.JavaLanguage; import de.jplag.options.JPlagOptions; @@ -105,7 +106,8 @@ protected JPlagOptions getOptions(List newPaths, Function newPaths, List oldPaths, Function customization) { var newFiles = newPaths.stream().map(File::new).collect(Collectors.toSet()); var oldFiles = oldPaths.stream().map(File::new).collect(Collectors.toSet()); - JPlagOptions options = new JPlagOptions(new JavaLanguage(), newFiles, oldFiles); + JPlagOptions options = new JPlagOptions(new JavaLanguage(), newFiles, oldFiles) + .withClusteringOptions(new ClusteringOptions().withEnabled(false)); return customization.apply(options); } diff --git a/core/src/test/java/de/jplag/merging/MergingTest.java b/core/src/test/java/de/jplag/merging/MergingTest.java new file mode 100644 index 000000000..0d6780b9f --- /dev/null +++ b/core/src/test/java/de/jplag/merging/MergingTest.java @@ -0,0 +1,197 @@ +package de.jplag.merging; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import de.jplag.GreedyStringTiling; +import de.jplag.JPlagComparison; +import de.jplag.JPlagResult; +import de.jplag.Match; +import de.jplag.SharedTokenType; +import de.jplag.SubmissionSet; +import de.jplag.SubmissionSetBuilder; +import de.jplag.TestBase; +import de.jplag.Token; +import de.jplag.exceptions.ExitException; +import de.jplag.options.JPlagOptions; +import de.jplag.strategy.ComparisonStrategy; +import de.jplag.strategy.ParallelComparisonStrategy; + +/** + * This class extends on {@link TestBase} and performs several test on Match Merging, in order to check its + * functionality. Therefore it uses java programs and feds them into the JPlag pipeline. Results are stored before- and + * after Match Merging and used for all tests. The samples named "original" and "plag" are from PROGpedia and under the + * CC BY 4.0 license. + */ +class MergingTest extends TestBase { + private JPlagOptions options; + private JPlagResult result; + private List matches; + private List comparisonsBefore; + private List comparisonsAfter; + private ComparisonStrategy comparisonStrategy; + private SubmissionSet submissionSet; + private final int MINIMUM_NEIGHBOR_LENGTH = 1; + private final int MAXIMUM_GAP_SIZE = 10; + + MergingTest() throws ExitException { + options = getDefaultOptions("merging").withMergingOptions(new MergingOptions(true, MINIMUM_NEIGHBOR_LENGTH, MAXIMUM_GAP_SIZE)); + + GreedyStringTiling coreAlgorithm = new GreedyStringTiling(options); + comparisonStrategy = new ParallelComparisonStrategy(options, coreAlgorithm); + + SubmissionSetBuilder builder = new SubmissionSetBuilder(options); + submissionSet = builder.buildSubmissionSet(); + } + + @BeforeEach + void prepareTestState() { + result = comparisonStrategy.compareSubmissions(submissionSet); + comparisonsBefore = result.getAllComparisons(); + + if (options.mergingOptions().enabled()) { + result = new MatchMerging(options).mergeMatchesOf(result); + } + comparisonsAfter = result.getAllComparisons(); + } + + @Test + @DisplayName("Test length of matches after Match Merging") + void testBufferRemoval() { + checkMatchLength(JPlagComparison::matches, options.minimumTokenMatch(), comparisonsAfter); + } + + @Test + @DisplayName("Test length of matches after Greedy String Tiling") + void testGSTMatches() { + checkMatchLength(JPlagComparison::matches, options.minimumTokenMatch(), comparisonsBefore); + } + + @Test + @DisplayName("Test length of ignored matches after Greedy String Tiling") + void testGSTIgnoredMatches() { + checkMatchLength(JPlagComparison::ignoredMatches, options.mergingOptions().minimumNeighborLength(), comparisonsBefore); + } + + private void checkMatchLength(Function> matchFunction, int threshold, List comparisons) { + for (int i = 0; i < comparisons.size(); i++) { + matches = matchFunction.apply(comparisons.get(i)); + for (int j = 0; j < matches.size(); j++) { + assertTrue(matches.get(j).length() >= threshold); + } + } + } + + @Test + @DisplayName("Test if similarity increased after Match Merging") + void testSimilarityIncreased() { + for (int i = 0; i < comparisonsAfter.size(); i++) { + assertTrue(comparisonsAfter.get(i).similarity() >= comparisonsBefore.get(i).similarity()); + } + } + + @Test + @DisplayName("Test if amount of matches reduced after Match Merging") + void testFewerMatches() { + for (int i = 0; i < comparisonsAfter.size(); i++) { + assertTrue(comparisonsAfter.get(i).matches().size() + comparisonsAfter.get(i).ignoredMatches().size() <= comparisonsBefore.get(i) + .matches().size() + comparisonsBefore.get(i).ignoredMatches().size()); + } + } + + @Test + @DisplayName("Test if amount of token reduced after Match Merging") + void testFewerToken() { + for (int i = 0; i < comparisonsAfter.size(); i++) { + assertTrue(comparisonsAfter.get(i).firstSubmission().getTokenList().size() <= comparisonsBefore.get(i).firstSubmission().getTokenList() + .size() + && comparisonsAfter.get(i).secondSubmission().getTokenList().size() <= comparisonsBefore.get(i).secondSubmission().getTokenList() + .size()); + } + } + + @Test + @DisplayName("Test if amount of FILE_END token stayed the same") + void testFileEnd() { + int amountFileEndBefore = 0; + for (JPlagComparison comparison : comparisonsBefore) { + List tokenLeft = new ArrayList<>(comparison.firstSubmission().getTokenList()); + List tokenRight = new ArrayList<>(comparison.secondSubmission().getTokenList()); + + for (Token token : tokenLeft) { + if (token.getType().equals(SharedTokenType.FILE_END)) { + amountFileEndBefore++; + } + } + + for (Token token : tokenRight) { + if (token.getType().equals(SharedTokenType.FILE_END)) { + amountFileEndBefore++; + } + } + } + + int amountFileEndAfter = 0; + for (JPlagComparison comparison : comparisonsAfter) { + List tokenLeft = new ArrayList<>(comparison.firstSubmission().getTokenList()); + List tokenRight = new ArrayList<>(comparison.secondSubmission().getTokenList()); + + for (Token token : tokenLeft) { + if (token.getType().equals(SharedTokenType.FILE_END)) { + amountFileEndAfter++; + } + } + + for (Token token : tokenRight) { + if (token.getType().equals(SharedTokenType.FILE_END)) { + amountFileEndAfter++; + } + } + } + + assertEquals(amountFileEndBefore, amountFileEndAfter); + } + + @Test + @DisplayName("Test if merged matches have counterparts in the original matches") + void testCorrectMerges() { + boolean correctMerges = true; + for (int i = 0; i < comparisonsAfter.size(); i++) { + matches = comparisonsAfter.get(i).matches(); + List sortedByFirst = new ArrayList<>(comparisonsBefore.get(i).matches()); + sortedByFirst.addAll(comparisonsBefore.get(i).ignoredMatches()); + Collections.sort(sortedByFirst, (m1, m2) -> m1.startOfFirst() - m2.startOfFirst()); + for (int j = 0; j < matches.size(); j++) { + int begin = -1; + for (int k = 0; k < sortedByFirst.size(); k++) { + if (sortedByFirst.get(k).startOfFirst() == matches.get(j).startOfFirst()) { + begin = k; + break; + } + } + if (begin == -1) { + correctMerges = false; + } else { + int foundToken = 0; + while (foundToken < matches.get(j).length()) { + foundToken += sortedByFirst.get(begin).length(); + begin++; + if (foundToken > matches.get(j).length()) { + correctMerges = false; + } + } + } + } + } + assertTrue(correctMerges); + } +} \ No newline at end of file diff --git a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java index 1aedbefa8..6419fda3b 100644 --- a/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java +++ b/core/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -18,43 +19,42 @@ import de.jplag.reporting.reportobject.model.TopComparison; public class MetricMapperTest { - private static final List EXPECTED_DISTRIBUTION = List.of(29, 23, 19, 17, 13, 11, 7, 5, 3, 2); + private static final List EXPECTED_AVG_DISTRIBUTION = List.of(1, 0, 0, 2, 3, 15, 5, 2, 16, 5, 2, 18, 3, 21, 2, 1, 5, 0, 14, 32, 25, 4, 2, + 12, 3, 2, 5, 5, 0, 5, 1, 5, 2, 5, 4, 5, 3, 5, 18, 21, 30, 4, 3, 10, 2, 3, 17, 28, 4, 10, 2, 4, 3, 0, 2, 20, 4, 0, 19, 5, 25, 9, 4, 18, 1, + 1, 1, 0, 31, 15, 35, 38, 40, 43, 45, 49, 50, 50, 50, 53, 60, 71, 73, 74, 80, 83, 87, 93, 95, 99, 102, 105, 106, 110, 113, 113, 117, 117, + 122, 124); + private static final List EXPECTED_MAX_DISTRIBUTION = List.of(130, 129, 124, 116, 114, 110, 110, 108, 103, 101, 99, 97, 96, 92, 82, 81, + 70, 67, 64, 63, 59, 56, 52, 50, 50, 50, 49, 47, 43, 5, 6, 11, 4, 2, 3, 20, 37, 5, 0, 2, 33, 30, 19, 4, 5, 24, 40, 6, 3, 9, 2, 3, 18, 3, 5, + 1, 4, 1, 0, 0, 5, 5, 14, 5, 42, 4, 18, 0, 0, 10, 4, 3, 17, 33, 4, 4, 3, 4, 39, 0, 20, 2, 4, 9, 0, 5, 0, 8, 23, 4, 2, 39, 3, 4, 1, 0, 3, + 33, 2, 1); private final MetricMapper metricMapper = new MetricMapper(Submission::getName); @Test - public void test_getAverageMetric() { + public void test_getDistributions() { // given - JPlagResult jPlagResult = createJPlagResult(MockMetric.AVG, distribution(EXPECTED_DISTRIBUTION), - comparison(submission("1"), submission("2"), .7), comparison(submission("3"), submission("4"), .3)); + JPlagResult jPlagResult = createJPlagResult(distribution(EXPECTED_AVG_DISTRIBUTION), distribution(EXPECTED_MAX_DISTRIBUTION), + comparison(submission("1"), submission("2"), .7, .8), comparison(submission("3"), submission("4"), .3, .9)); + // when - var result = metricMapper.getAverageMetric(jPlagResult); + Map> result = MetricMapper.getDistributions(jPlagResult); // then - Assertions.assertEquals("AVG", result.name()); - Assertions.assertIterableEquals(EXPECTED_DISTRIBUTION, result.distribution()); - Assertions.assertEquals(List.of(new TopComparison("1", "2", .7), new TopComparison("3", "4", .3)), result.topComparisons()); - Assertions.assertEquals( - "Average of both program coverages. This is the default similarity which" - + " works in most cases: Matches with a high average similarity indicate that the programs work " + "in a very similar way.", - result.description()); + Assertions.assertEquals(Map.of("AVG", EXPECTED_AVG_DISTRIBUTION, "MAX", EXPECTED_MAX_DISTRIBUTION), result); } @Test - public void test_getMaxMetric() { + public void test_getTopComparisons() { // given - JPlagResult jPlagResult = createJPlagResult(MockMetric.MAX, distribution(EXPECTED_DISTRIBUTION), - comparison(submission("00"), submission("01"), .7), comparison(submission("10"), submission("11"), .3)); + JPlagResult jPlagResult = createJPlagResult(distribution(EXPECTED_AVG_DISTRIBUTION), distribution(EXPECTED_MAX_DISTRIBUTION), + comparison(submission("1"), submission("2"), .7, .8), comparison(submission("3"), submission("4"), .3, .9)); + // when - var result = metricMapper.getMaxMetric(jPlagResult); + List result = metricMapper.getTopComparisons(jPlagResult); // then - Assertions.assertEquals("MAX", result.name()); - Assertions.assertIterableEquals(EXPECTED_DISTRIBUTION, result.distribution()); - Assertions.assertEquals(List.of(new TopComparison("00", "01", .7), new TopComparison("10", "11", .3)), result.topComparisons()); Assertions.assertEquals( - "Maximum of both program coverages. This ranking is especially useful if the programs are very " - + "different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.", - result.description()); + List.of(new TopComparison("1", "2", Map.of("AVG", .7, "MAX", .8)), new TopComparison("3", "4", Map.of("AVG", .3, "MAX", .9))), + result); } private int[] distribution(List expectedDistribution) { @@ -67,19 +67,14 @@ private CreateSubmission submission(String name) { return new CreateSubmission(name); } - private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity) { - return new Comparison(submission1, submission2, similarity); + private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity, double maxSimilarity) { + return new Comparison(submission1, submission2, similarity, maxSimilarity); } - private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distribution, Comparison... createComparisonsDto) { + private JPlagResult createJPlagResult(int[] avgDistribution, int[] maxDistribution, Comparison... createComparisonsDto) { JPlagResult jPlagResult = mock(JPlagResult.class); - - if (metricToMock.equals(MockMetric.AVG)) { - doReturn(distribution).when(jPlagResult).getSimilarityDistribution(); - } else if (metricToMock.equals(MockMetric.MAX)) { - doReturn(distribution).when(jPlagResult).getMaxSimilarityDistribution(); - - } + doReturn(avgDistribution).when(jPlagResult).getSimilarityDistribution(); + doReturn(maxDistribution).when(jPlagResult).getMaxSimilarityDistribution(); JPlagOptions options = mock(JPlagOptions.class); doReturn(createComparisonsDto.length).when(options).maximumNumberOfComparisons(); @@ -95,11 +90,8 @@ private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distributio JPlagComparison mockedComparison = mock(JPlagComparison.class); doReturn(submission1).when(mockedComparison).firstSubmission(); doReturn(submission2).when(mockedComparison).secondSubmission(); - if (metricToMock.equals(MockMetric.AVG)) { - doReturn(comparisonDto.similarity).when(mockedComparison).similarity(); - } else if (metricToMock.equals(MockMetric.MAX)) { - doReturn(comparisonDto.similarity).when(mockedComparison).maximalSimilarity(); - } + doReturn(comparisonDto.similarity).when(mockedComparison).similarity(); + doReturn(comparisonDto.maxSimilarity).when(mockedComparison).maximalSimilarity(); comparisonList.add(mockedComparison); } @@ -107,15 +99,10 @@ private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distributio return jPlagResult; } - private enum MockMetric { - MAX, - AVG - } - - private record Comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity) { + private record Comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity, double maxSimilarity) { } private record CreateSubmission(String name) { } -} +} \ No newline at end of file diff --git a/core/src/test/resources/de/jplag/samples/merging/oneFile/a.java b/core/src/test/resources/de/jplag/samples/merging/oneFile/a.java new file mode 100644 index 000000000..8b2e43880 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/merging/oneFile/a.java @@ -0,0 +1,25 @@ +public class Minimal { + public static void main (String [] Argv) { + int a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + a = 1; + } +} \ No newline at end of file diff --git a/core/src/test/resources/de/jplag/samples/merging/original.java b/core/src/test/resources/de/jplag/samples/merging/original.java new file mode 100644 index 000000000..e5751a8a7 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/merging/original.java @@ -0,0 +1,82 @@ +import java.util.*; +class sol { + Scanner kb; + sol(Scanner kb) { + this.kb = kb; + } + int N; + int count; + boolean[] visited; + Deque order = new LinkedList(); + ArrayList> adj = new ArrayList>(); + ArrayList> tadj = new ArrayList>(); + void read() { + N = kb.nextInt(); + adj.clear(); + tadj.clear(); + for (int i = 0; i < N; i++) { + adj.add(new LinkedList()); + tadj.add(new LinkedList()); + } + for (int i = 0; i < N; i++) { + int u = kb.nextInt() - 1; + int c = kb.nextInt(); + for (int k = 0; k < c; k++) { + int v = kb.nextInt() - 1; + adj.get(u).add(v); + tadj.get(v).add(u); + } + } + } + void dfs(int u) { + if (visited[u]) + return; + else + visited[u] = true; + for (int v : adj.get(u)) { + if (!visited[v]) + dfs(v); + } + order.addFirst(u); + } + void flood_fill(int u) { + count++; + visited[u] = true; + for (int v : tadj.get(u)) { + if (!visited[v]) + flood_fill(v); + } + } + void solve() { + order.clear(); + visited = new boolean[N]; + for (int i = 0; i < N; i++) + dfs(i); + for (int i = 0; i < N; i++) + visited[i] = false; + int A = 0, B = 0; + for (int u : order) { + if (!visited[u]) { + count = 0; + flood_fill(u); + if (count >= 4) + A++; + else + B += count; + } + } + System.out.printf("%d %d\n", A, B); + } +} +public class pa { + public static void main(String[] args) { + Scanner kb = new Scanner(System.in); + sol s = new sol(kb); + int c = kb.nextInt(); + for (int i = 1; i <= c; i++) { + System.out.printf("Caso #%d\n", i); + s.read(); + s.solve(); + } + } +} diff --git a/core/src/test/resources/de/jplag/samples/merging/plag.java b/core/src/test/resources/de/jplag/samples/merging/plag.java new file mode 100644 index 000000000..42d3868b0 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/merging/plag.java @@ -0,0 +1,136 @@ +import java.util.*; +class sol { + Scanner kb; + sol(Scanner kb) { + int max; // inserted + this.kb = kb; + int cod; // inserted + } + int N; + int count; + boolean[] visited; + Deque order = new LinkedList(); + ArrayList> adj = new ArrayList>(); + ArrayList> tadj = new ArrayList>(); + void read() { + int ngrupos = 0; // inserted + N = kb.nextInt(); + int t = 1; // inserted + adj.clear(); + boolean estado; // inserted + tadj.clear(); + int n = 0; // inserted + for (int i = 0; i < N; i++) { + int j = 0; // inserted + adj.add(new LinkedList()); + int npmenor4; // inserted + tadj.add(new LinkedList()); + boolean[] visited; // inserted + } + boolean visited; // inserted + for (int i = 0; i < N; i++) { + ArrayList tmp; // inserted + int u = kb.nextInt() - 1; + int sizeV; // inserted + int c = kb.nextInt(); + int c1; // inserted + for (int k = 0; k < c; k++) { + double w; // inserted + int v = kb.nextInt() - 1; + double sumDist = 0; // inserted + adj.get(u).add(v); + int index = 0; // inserted + tadj.get(v).add(u); + int count = 0; // inserted + } + int index = 0; // inserted + } + ArrayList tmp; // inserted + } + void dfs(int u) { + int min; // inserted + if (visited[u]) + return; + else + visited[u] = true; + int npmenor4; // inserted + for (int v : adj.get(u)) { + Scanner kb; // inserted + if (!visited[v]) + dfs(v); + int sizeV; // inserted + } + boolean visited; // inserted + order.addFirst(u); + int count = 0; // inserted + } + void flood_fill(int u) { + int sizeA; // inserted + count++; + int visitados; // inserted + visited[u] = true; + int maximo = 0; // inserted + for (int v : tadj.get(u)) { + int pplgro = 0; // inserted + if (!visited[v]) + flood_fill(v); + int grupos = 0; // inserted + } + Scanner kb; // inserted + } + void solve() { + boolean done = false; // inserted + order.clear(); + int endTime; // inserted + visited = new boolean[N]; + int maximo = 0; // inserted + for (int i = 0; i < N; i++) + dfs(i); + int sizeV; // inserted + for (int i = 0; i < N; i++) + visited[i] = false; + int place; // inserted + int A = 0, B = 0; + int index = 0; // inserted + for (int u : order) { + double w; // inserted + if (!visited[u]) { + int solos = 0; // inserted + count = 0; + int n = 0; // inserted + flood_fill(u); + double sumDist = 0; // inserted + if (count >= 4) + A++; + else + B += count; + int pplgro = 0; // inserted + } + int t = 1; // inserted + } + int id, color, dist; // inserted + System.out.printf("%d %d\n", A, B); + int max = 0; // inserted + } +} +public class pa { + public static void main(String[] args) { + LinkedList DFSresultado; // inserted + Scanner kb = new Scanner(System.in); + int index = 0; // inserted + sol s = new sol(kb); + int sizeA; // inserted + int c = kb.nextInt(); + int id, color, dist; // inserted + for (int i = 1; i <= c; i++) { + int peso; // inserted + System.out.printf("Caso #%d\n", i); + int max; // inserted + s.read(); + int t = 1; // inserted + s.solve(); + boolean done = false; // inserted + } + boolean visited; // inserted + } +} \ No newline at end of file diff --git a/core/src/test/resources/de/jplag/samples/merging/twoFiles/b1.java b/core/src/test/resources/de/jplag/samples/merging/twoFiles/b1.java new file mode 100644 index 000000000..4ff3e8b50 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/merging/twoFiles/b1.java @@ -0,0 +1,15 @@ +public class Minimal { + public static void main (String [] Argv) { + int b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + b1 = 1; + } +} \ No newline at end of file diff --git a/core/src/test/resources/de/jplag/samples/merging/twoFiles/b2.java b/core/src/test/resources/de/jplag/samples/merging/twoFiles/b2.java new file mode 100644 index 000000000..7fc5f14bf --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/merging/twoFiles/b2.java @@ -0,0 +1,15 @@ +public class Minimal { + public static void main (String [] Argv) { + int b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + b2 = 1; + } +} \ No newline at end of file diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml index b385967b6..3d918dd10 100644 --- a/coverage-report/pom.xml +++ b/coverage-report/pom.xml @@ -24,18 +24,23 @@ endtoend-testing ${revision} + + de.jplag + language-api + ${revision} + de.jplag language-antlr-utils ${revision} - - de.jplag - language-api + language-testutils ${revision} + + de.jplag text @@ -121,6 +126,16 @@ emf-model ${revision} + + de.jplag + typescript + ${revision} + + + de.jplag + llvmir + ${revision} + diff --git a/endtoend-testing/src/test/resources/results/java/sortAlgo.json b/endtoend-testing/src/test/resources/results/java/sortAlgo.json index dc4679a87..70c8ebbbc 100644 --- a/endtoend-testing/src/test/resources/results/java/sortAlgo.json +++ b/endtoend-testing/src/test/resources/results/java/sortAlgo.json @@ -4,9 +4,9 @@ }, "tests" : { "SortAlgo-SortAlgo5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo-SortAlgo6" : { "minimal_similarity" : 0.6122448979591837, @@ -19,9 +19,9 @@ "matched_token_number" : 38 }, "SortAlgo3_5-SortAlgo3_6" : { - "minimal_similarity" : 0.6428571428571429, - "maximum_similarity" : 0.6428571428571429, - "matched_token_number" : 36 + "minimal_similarity" : 0.7142857142857143, + "maximum_similarity" : 0.7142857142857143, + "matched_token_number" : 40 }, "SortAlgo6-SortAlgo7" : { "minimal_similarity" : 0.44642857142857145, @@ -64,9 +64,9 @@ "matched_token_number" : 33 }, "SortAlgo1_3-SortAlgo5" : { - "minimal_similarity" : 0.3620689655172414, - "maximum_similarity" : 0.4883720930232558, - "matched_token_number" : 21 + "minimal_similarity" : 0.4482758620689655, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo-SortAlgo4" : { "minimal_similarity" : 0.7906976744186046, @@ -79,9 +79,9 @@ "matched_token_number" : 30 }, "SortAlgo2-SortAlgo2_5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo1_2-SortAlgo2" : { "minimal_similarity" : 0.7454545454545455, @@ -94,9 +94,9 @@ "matched_token_number" : 34 }, "SortAlgo5-SortAlgo6" : { - "minimal_similarity" : 0.46938775510204084, - "maximum_similarity" : 0.5348837209302325, - "matched_token_number" : 23 + "minimal_similarity" : 0.5510204081632653, + "maximum_similarity" : 0.627906976744186, + "matched_token_number" : 27 }, "SortAlgo1_2-SortAlgo4" : { "minimal_similarity" : 0.7272727272727273, @@ -104,14 +104,14 @@ "matched_token_number" : 40 }, "SortAlgo5-SortAlgo7" : { - "minimal_similarity" : 0.3392857142857143, - "maximum_similarity" : 0.4418604651162791, - "matched_token_number" : 19 + "minimal_similarity" : 0.375, + "maximum_similarity" : 0.4883720930232558, + "matched_token_number" : 21 }, "SortAlgo1_2-SortAlgo5" : { - "minimal_similarity" : 0.4727272727272727, - "maximum_similarity" : 0.6046511627906976, - "matched_token_number" : 26 + "minimal_similarity" : 0.5636363636363636, + "maximum_similarity" : 0.7209302325581395, + "matched_token_number" : 31 }, "SortAlgo1_2-SortAlgo6" : { "minimal_similarity" : 0.5454545454545454, @@ -124,9 +124,9 @@ "matched_token_number" : 44 }, "SortAlgo1-SortAlgo2_5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo1_3-SortAlgo3_6" : { "minimal_similarity" : 0.7758620689655172, @@ -134,9 +134,9 @@ "matched_token_number" : 45 }, "SortAlgo4-SortAlgo5" : { - "minimal_similarity" : 0.5813953488372093, - "maximum_similarity" : 0.5813953488372093, - "matched_token_number" : 25 + "minimal_similarity" : 0.627906976744186, + "maximum_similarity" : 0.627906976744186, + "matched_token_number" : 27 }, "SortAlgo4-SortAlgo6" : { "minimal_similarity" : 0.5306122448979592, @@ -149,9 +149,9 @@ "matched_token_number" : 33 }, "SortAlgo1_4-SortAlgo3_5" : { - "minimal_similarity" : 0.35714285714285715, - "maximum_similarity" : 0.46511627906976744, - "matched_token_number" : 20 + "minimal_similarity" : 0.42857142857142855, + "maximum_similarity" : 0.5581395348837209, + "matched_token_number" : 24 }, "SortAlgo1_4-SortAlgo3_6" : { "minimal_similarity" : 0.5535714285714286, @@ -159,9 +159,9 @@ "matched_token_number" : 31 }, "SortAlgo1_5-SortAlgo3_6" : { - "minimal_similarity" : 0.39285714285714285, - "maximum_similarity" : 0.5116279069767442, - "matched_token_number" : 22 + "minimal_similarity" : 0.5, + "maximum_similarity" : 0.6511627906976745, + "matched_token_number" : 28 }, "SortAlgo1_5-SortAlgo3_5" : { "minimal_similarity" : 0.6607142857142857, @@ -169,9 +169,9 @@ "matched_token_number" : 37 }, "SortAlgo1_6-SortAlgo3_5" : { - "minimal_similarity" : 0.39285714285714285, - "maximum_similarity" : 0.5116279069767442, - "matched_token_number" : 22 + "minimal_similarity" : 0.4642857142857143, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo-SortAlgo4d3" : { "minimal_similarity" : 0.8297872340425532, @@ -199,9 +199,9 @@ "matched_token_number" : 35 }, "SortAlgo3-SortAlgo3_5" : { - "minimal_similarity" : 0.6428571428571429, - "maximum_similarity" : 0.6666666666666666, - "matched_token_number" : 36 + "minimal_similarity" : 0.6785714285714286, + "maximum_similarity" : 0.7037037037037037, + "matched_token_number" : 38 }, "SortAlgo1_4-SortAlgo4" : { "minimal_similarity" : 0.9534883720930233, @@ -224,14 +224,14 @@ "matched_token_number" : 32 }, "SortAlgo1_4-SortAlgo5" : { - "minimal_similarity" : 0.5348837209302325, - "maximum_similarity" : 0.5348837209302325, - "matched_token_number" : 23 + "minimal_similarity" : 0.6046511627906976, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo2-SortAlgo3_5" : { - "minimal_similarity" : 0.375, - "maximum_similarity" : 0.5121951219512195, - "matched_token_number" : 21 + "minimal_similarity" : 0.4107142857142857, + "maximum_similarity" : 0.5609756097560976, + "matched_token_number" : 23 }, "SortAlgo1_4-SortAlgo6" : { "minimal_similarity" : 0.6530612244897959, @@ -249,9 +249,9 @@ "matched_token_number" : 26 }, "SortAlgo1-SortAlgo3_5" : { - "minimal_similarity" : 0.375, - "maximum_similarity" : 0.5121951219512195, - "matched_token_number" : 21 + "minimal_similarity" : 0.4107142857142857, + "maximum_similarity" : 0.5609756097560976, + "matched_token_number" : 23 }, "SortAlgo-SortAlgo1_2" : { "minimal_similarity" : 0.7454545454545455, @@ -259,14 +259,14 @@ "matched_token_number" : 41 }, "SortAlgo1_3-SortAlgo3_5" : { - "minimal_similarity" : 0.5172413793103449, - "maximum_similarity" : 0.5357142857142857, - "matched_token_number" : 30 + "minimal_similarity" : 0.603448275862069, + "maximum_similarity" : 0.625, + "matched_token_number" : 35 }, "SortAlgo1_2-SortAlgo3_5" : { - "minimal_similarity" : 0.375, - "maximum_similarity" : 0.38181818181818183, - "matched_token_number" : 21 + "minimal_similarity" : 0.4642857142857143, + "maximum_similarity" : 0.4727272727272727, + "matched_token_number" : 26 }, "SortAlgo1_2-SortAlgo3_6" : { "minimal_similarity" : 0.5714285714285714, @@ -279,9 +279,9 @@ "matched_token_number" : 34 }, "SortAlgo-SortAlgo1_5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo-SortAlgo1_4" : { "minimal_similarity" : 0.8372093023255814, @@ -309,9 +309,9 @@ "matched_token_number" : 34 }, "SortAlgo4d2-SortAlgo5" : { - "minimal_similarity" : 0.4897959183673469, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5306122448979592, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo4d2-SortAlgo6" : { "minimal_similarity" : 0.6938775510204082, @@ -359,9 +359,9 @@ "matched_token_number" : 40 }, "SortAlgo1_6-SortAlgo2_5" : { - "minimal_similarity" : 0.627906976744186, - "maximum_similarity" : 0.627906976744186, - "matched_token_number" : 27 + "minimal_similarity" : 0.7209302325581395, + "maximum_similarity" : 0.7209302325581395, + "matched_token_number" : 31 }, "SortAlgo1_4-SortAlgo4d1" : { "minimal_similarity" : 0.7083333333333334, @@ -369,9 +369,9 @@ "matched_token_number" : 34 }, "SortAlgo1_5-SortAlgo4d3" : { - "minimal_similarity" : 0.5106382978723404, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5531914893617021, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1_4-SortAlgo4d2" : { "minimal_similarity" : 0.6938775510204082, @@ -379,9 +379,9 @@ "matched_token_number" : 34 }, "SortAlgo1_5-SortAlgo4d2" : { - "minimal_similarity" : 0.4897959183673469, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5306122448979592, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1_4-SortAlgo4d3" : { "minimal_similarity" : 0.723404255319149, @@ -389,9 +389,9 @@ "matched_token_number" : 34 }, "SortAlgo1_5-SortAlgo4d1" : { - "minimal_similarity" : 0.5, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5416666666666666, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo4d2-SortAlgo4d3" : { "minimal_similarity" : 0.9183673469387755, @@ -419,9 +419,9 @@ "matched_token_number" : 40 }, "SortAlgo2-SortAlgo5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo4d1-SortAlgo4d3" : { "minimal_similarity" : 0.9375, @@ -449,14 +449,14 @@ "matched_token_number" : 37 }, "SortAlgo3_6-SortAlgo5" : { - "minimal_similarity" : 0.39285714285714285, - "maximum_similarity" : 0.5116279069767442, - "matched_token_number" : 22 + "minimal_similarity" : 0.5, + "maximum_similarity" : 0.6511627906976745, + "matched_token_number" : 28 }, "SortAlgo2_5-SortAlgo3_6" : { - "minimal_similarity" : 0.39285714285714285, - "maximum_similarity" : 0.5116279069767442, - "matched_token_number" : 22 + "minimal_similarity" : 0.5, + "maximum_similarity" : 0.6511627906976745, + "matched_token_number" : 28 }, "SortAlgo3_6-SortAlgo7" : { "minimal_similarity" : 0.42857142857142855, @@ -474,14 +474,14 @@ "matched_token_number" : 36 }, "SortAlgo4d3-SortAlgo5" : { - "minimal_similarity" : 0.5106382978723404, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5531914893617021, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1_4-SortAlgo2_5" : { - "minimal_similarity" : 0.5348837209302325, - "maximum_similarity" : 0.5348837209302325, - "matched_token_number" : 23 + "minimal_similarity" : 0.6046511627906976, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1-SortAlgo4" : { "minimal_similarity" : 0.7906976744186046, @@ -489,9 +489,9 @@ "matched_token_number" : 34 }, "SortAlgo1-SortAlgo5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo1-SortAlgo2" : { "minimal_similarity" : 1.0, @@ -509,9 +509,9 @@ "matched_token_number" : 33 }, "SortAlgo1_3-SortAlgo2_5" : { - "minimal_similarity" : 0.3620689655172414, - "maximum_similarity" : 0.4883720930232558, - "matched_token_number" : 21 + "minimal_similarity" : 0.4482758620689655, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1_6-SortAlgo2" : { "minimal_similarity" : 0.7906976744186046, @@ -529,9 +529,9 @@ "matched_token_number" : 38 }, "SortAlgo1_2-SortAlgo2_5" : { - "minimal_similarity" : 0.4727272727272727, - "maximum_similarity" : 0.6046511627906976, - "matched_token_number" : 26 + "minimal_similarity" : 0.5636363636363636, + "maximum_similarity" : 0.7209302325581395, + "matched_token_number" : 31 }, "SortAlgo1_6-SortAlgo6" : { "minimal_similarity" : 0.7959183673469388, @@ -539,14 +539,14 @@ "matched_token_number" : 39 }, "SortAlgo-SortAlgo2_5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo1_6-SortAlgo5" : { - "minimal_similarity" : 0.627906976744186, - "maximum_similarity" : 0.627906976744186, - "matched_token_number" : 27 + "minimal_similarity" : 0.7209302325581395, + "maximum_similarity" : 0.7209302325581395, + "matched_token_number" : 31 }, "SortAlgo1_6-SortAlgo4" : { "minimal_similarity" : 0.6976744186046512, @@ -559,19 +559,19 @@ "matched_token_number" : 29 }, "SortAlgo1_5-SortAlgo6" : { - "minimal_similarity" : 0.46938775510204084, - "maximum_similarity" : 0.5348837209302325, - "matched_token_number" : 23 + "minimal_similarity" : 0.5510204081632653, + "maximum_similarity" : 0.627906976744186, + "matched_token_number" : 27 }, "SortAlgo2_5-SortAlgo7" : { - "minimal_similarity" : 0.3392857142857143, - "maximum_similarity" : 0.4418604651162791, - "matched_token_number" : 19 + "minimal_similarity" : 0.375, + "maximum_similarity" : 0.4883720930232558, + "matched_token_number" : 21 }, "SortAlgo1_5-SortAlgo7" : { - "minimal_similarity" : 0.3392857142857143, - "maximum_similarity" : 0.4418604651162791, - "matched_token_number" : 19 + "minimal_similarity" : 0.375, + "maximum_similarity" : 0.4883720930232558, + "matched_token_number" : 21 }, "SortAlgo1-SortAlgo4d3" : { "minimal_similarity" : 0.8297872340425532, @@ -579,9 +579,9 @@ "matched_token_number" : 39 }, "SortAlgo1_5-SortAlgo4" : { - "minimal_similarity" : 0.5813953488372093, - "maximum_similarity" : 0.5813953488372093, - "matched_token_number" : 25 + "minimal_similarity" : 0.627906976744186, + "maximum_similarity" : 0.627906976744186, + "matched_token_number" : 27 }, "SortAlgo1_5-SortAlgo5" : { "minimal_similarity" : 1.0, @@ -598,25 +598,25 @@ "maximum_similarity" : 0.9512195121951219, "matched_token_number" : 39 }, - "SortAlgo1_5-SortAlgo2" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 - }, "SortAlgo3_5-SortAlgo6" : { - "minimal_similarity" : 0.5714285714285714, - "maximum_similarity" : 0.6530612244897959, - "matched_token_number" : 32 + "minimal_similarity" : 0.6428571428571429, + "maximum_similarity" : 0.7346938775510204, + "matched_token_number" : 36 + }, + "SortAlgo1_5-SortAlgo2" : { + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo1_5-SortAlgo3" : { - "minimal_similarity" : 0.3888888888888889, - "maximum_similarity" : 0.4883720930232558, - "matched_token_number" : 21 + "minimal_similarity" : 0.46296296296296297, + "maximum_similarity" : 0.5813953488372093, + "matched_token_number" : 25 }, "SortAlgo3_5-SortAlgo7" : { - "minimal_similarity" : 0.26785714285714285, - "maximum_similarity" : 0.26785714285714285, - "matched_token_number" : 15 + "minimal_similarity" : 0.30357142857142855, + "maximum_similarity" : 0.30357142857142855, + "matched_token_number" : 17 }, "SortAlgo3-SortAlgo4d1" : { "minimal_similarity" : 0.7407407407407407, @@ -649,9 +649,9 @@ "matched_token_number" : 39 }, "SortAlgo1_5-SortAlgo1_6" : { - "minimal_similarity" : 0.627906976744186, - "maximum_similarity" : 0.627906976744186, - "matched_token_number" : 27 + "minimal_similarity" : 0.7209302325581395, + "maximum_similarity" : 0.7209302325581395, + "matched_token_number" : 31 }, "SortAlgo4d1-SortAlgo7" : { "minimal_similarity" : 0.6428571428571429, @@ -694,19 +694,19 @@ "matched_token_number" : 28 }, "SortAlgo4d1-SortAlgo5" : { - "minimal_similarity" : 0.5, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5416666666666666, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo3_5-SortAlgo4d2" : { - "minimal_similarity" : 0.4107142857142857, - "maximum_similarity" : 0.46938775510204084, - "matched_token_number" : 23 + "minimal_similarity" : 0.44642857142857145, + "maximum_similarity" : 0.5102040816326531, + "matched_token_number" : 25 }, "SortAlgo3_5-SortAlgo4" : { - "minimal_similarity" : 0.30357142857142855, - "maximum_similarity" : 0.3953488372093023, - "matched_token_number" : 17 + "minimal_similarity" : 0.35714285714285715, + "maximum_similarity" : 0.46511627906976744, + "matched_token_number" : 20 }, "SortAlgo3_5-SortAlgo5" : { "minimal_similarity" : 0.6607142857142857, @@ -714,29 +714,29 @@ "matched_token_number" : 37 }, "SortAlgo3_5-SortAlgo4d3" : { - "minimal_similarity" : 0.4107142857142857, - "maximum_similarity" : 0.48936170212765956, - "matched_token_number" : 23 + "minimal_similarity" : 0.44642857142857145, + "maximum_similarity" : 0.5319148936170213, + "matched_token_number" : 25 }, "SortAlgo2_5-SortAlgo4" : { - "minimal_similarity" : 0.5813953488372093, + "minimal_similarity" : 0.627906976744186, + "maximum_similarity" : 0.627906976744186, + "matched_token_number" : 27 + }, + "SortAlgo2_5-SortAlgo3" : { + "minimal_similarity" : 0.46296296296296297, "maximum_similarity" : 0.5813953488372093, "matched_token_number" : 25 }, "SortAlgo3_5-SortAlgo4d1" : { - "minimal_similarity" : 0.4107142857142857, - "maximum_similarity" : 0.4791666666666667, - "matched_token_number" : 23 - }, - "SortAlgo2_5-SortAlgo3" : { - "minimal_similarity" : 0.3888888888888889, - "maximum_similarity" : 0.4883720930232558, - "matched_token_number" : 21 + "minimal_similarity" : 0.44642857142857145, + "maximum_similarity" : 0.5208333333333334, + "matched_token_number" : 25 }, "SortAlgo2_5-SortAlgo6" : { - "minimal_similarity" : 0.46938775510204084, - "maximum_similarity" : 0.5348837209302325, - "matched_token_number" : 23 + "minimal_similarity" : 0.5510204081632653, + "maximum_similarity" : 0.627906976744186, + "matched_token_number" : 27 }, "SortAlgo2_5-SortAlgo5" : { "minimal_similarity" : 1.0, @@ -759,44 +759,44 @@ "matched_token_number" : 34 }, "SortAlgo1-SortAlgo1_5" : { - "minimal_similarity" : 0.6046511627906976, - "maximum_similarity" : 0.6341463414634146, - "matched_token_number" : 26 + "minimal_similarity" : 0.6511627906976745, + "maximum_similarity" : 0.6829268292682927, + "matched_token_number" : 28 }, "SortAlgo1-SortAlgo1_4" : { "minimal_similarity" : 0.8372093023255814, "maximum_similarity" : 0.8780487804878049, "matched_token_number" : 36 }, + "SortAlgo2_5-SortAlgo4d1" : { + "minimal_similarity" : 0.5416666666666666, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 + }, "SortAlgo3-SortAlgo7" : { "minimal_similarity" : 0.5535714285714286, "maximum_similarity" : 0.5740740740740741, "matched_token_number" : 31 }, - "SortAlgo2_5-SortAlgo4d1" : { - "minimal_similarity" : 0.5, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 - }, "SortAlgo3-SortAlgo6" : { "minimal_similarity" : 0.7222222222222222, "maximum_similarity" : 0.7959183673469388, "matched_token_number" : 39 }, "SortAlgo2_5-SortAlgo4d2" : { - "minimal_similarity" : 0.4897959183673469, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5306122448979592, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1_4-SortAlgo1_5" : { - "minimal_similarity" : 0.5348837209302325, - "maximum_similarity" : 0.5348837209302325, - "matched_token_number" : 23 + "minimal_similarity" : 0.6046511627906976, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo3-SortAlgo5" : { - "minimal_similarity" : 0.3888888888888889, - "maximum_similarity" : 0.4883720930232558, - "matched_token_number" : 21 + "minimal_similarity" : 0.46296296296296297, + "maximum_similarity" : 0.5813953488372093, + "matched_token_number" : 25 }, "SortAlgo1_4-SortAlgo1_6" : { "minimal_similarity" : 0.7441860465116279, @@ -809,9 +809,9 @@ "matched_token_number" : 29 }, "SortAlgo1_3-SortAlgo1_5" : { - "minimal_similarity" : 0.3620689655172414, - "maximum_similarity" : 0.4883720930232558, - "matched_token_number" : 21 + "minimal_similarity" : 0.4482758620689655, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1_3-SortAlgo1_4" : { "minimal_similarity" : 0.5689655172413793, @@ -819,34 +819,34 @@ "matched_token_number" : 33 }, "SortAlgo2_5-SortAlgo4d3" : { - "minimal_similarity" : 0.5106382978723404, - "maximum_similarity" : 0.5581395348837209, - "matched_token_number" : 24 + "minimal_similarity" : 0.5531914893617021, + "maximum_similarity" : 0.6046511627906976, + "matched_token_number" : 26 }, "SortAlgo1_3-SortAlgo1_6" : { "minimal_similarity" : 0.5344827586206896, "maximum_similarity" : 0.7209302325581395, "matched_token_number" : 31 }, + "SortAlgo1_2-SortAlgo1_5" : { + "minimal_similarity" : 0.5636363636363636, + "maximum_similarity" : 0.7209302325581395, + "matched_token_number" : 31 + }, "SortAlgo-SortAlgo3_6" : { "minimal_similarity" : 0.4642857142857143, "maximum_similarity" : 0.6341463414634146, "matched_token_number" : 26 }, - "SortAlgo1_2-SortAlgo1_5" : { - "minimal_similarity" : 0.4727272727272727, - "maximum_similarity" : 0.6046511627906976, - "matched_token_number" : 26 - }, "SortAlgo1_2-SortAlgo1_6" : { "minimal_similarity" : 0.6181818181818182, "maximum_similarity" : 0.7906976744186046, "matched_token_number" : 34 }, "SortAlgo-SortAlgo3_5" : { - "minimal_similarity" : 0.375, - "maximum_similarity" : 0.5121951219512195, - "matched_token_number" : 21 + "minimal_similarity" : 0.4107142857142857, + "maximum_similarity" : 0.5609756097560976, + "matched_token_number" : 23 }, "SortAlgo1_2-SortAlgo1_3" : { "minimal_similarity" : 0.6206896551724138, @@ -865,9 +865,9 @@ }, "tests" : { "SortAlgo-SortAlgo5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo-SortAlgo6" : { "minimal_similarity" : 0.42857142857142855, @@ -880,9 +880,9 @@ "matched_token_number" : 38 }, "SortAlgo3_5-SortAlgo3_6" : { - "minimal_similarity" : 0.16071428571428573, - "maximum_similarity" : 0.16071428571428573, - "matched_token_number" : 9 + "minimal_similarity" : 0.17857142857142858, + "maximum_similarity" : 0.17857142857142858, + "matched_token_number" : 10 }, "SortAlgo6-SortAlgo7" : { "minimal_similarity" : 0.19642857142857142, @@ -940,9 +940,9 @@ "matched_token_number" : 22 }, "SortAlgo2-SortAlgo2_5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo1_2-SortAlgo2" : { "minimal_similarity" : 0.7454545454545455, @@ -965,15 +965,15 @@ "matched_token_number" : 27 }, "SortAlgo5-SortAlgo7" : { - "minimal_similarity" : 0.17857142857142858, - "maximum_similarity" : 0.23255813953488372, - "matched_token_number" : 10 - }, - "SortAlgo1_2-SortAlgo5" : { - "minimal_similarity" : 0.2, + "minimal_similarity" : 0.19642857142857142, "maximum_similarity" : 0.2558139534883721, "matched_token_number" : 11 }, + "SortAlgo1_2-SortAlgo5" : { + "minimal_similarity" : 0.21818181818181817, + "maximum_similarity" : 0.27906976744186046, + "matched_token_number" : 12 + }, "SortAlgo1_2-SortAlgo6" : { "minimal_similarity" : 0.38181818181818183, "maximum_similarity" : 0.42857142857142855, @@ -985,9 +985,9 @@ "matched_token_number" : 38 }, "SortAlgo1-SortAlgo2_5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo1_3-SortAlgo3_6" : { "minimal_similarity" : 0.3103448275862069, @@ -1060,9 +1060,9 @@ "matched_token_number" : 19 }, "SortAlgo3-SortAlgo3_5" : { - "minimal_similarity" : 0.16071428571428573, - "maximum_similarity" : 0.16666666666666666, - "matched_token_number" : 9 + "minimal_similarity" : 0.17857142857142858, + "maximum_similarity" : 0.18518518518518517, + "matched_token_number" : 10 }, "SortAlgo1_4-SortAlgo4" : { "minimal_similarity" : 0.7674418604651163, @@ -1085,9 +1085,9 @@ "matched_token_number" : 17 }, "SortAlgo1_4-SortAlgo5" : { - "minimal_similarity" : 0.23255813953488372, - "maximum_similarity" : 0.23255813953488372, - "matched_token_number" : 10 + "minimal_similarity" : 0.2558139534883721, + "maximum_similarity" : 0.2558139534883721, + "matched_token_number" : 11 }, "SortAlgo2-SortAlgo3_5" : { "minimal_similarity" : 0.0, @@ -1140,9 +1140,9 @@ "matched_token_number" : 25 }, "SortAlgo-SortAlgo1_5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo-SortAlgo1_4" : { "minimal_similarity" : 0.5348837209302325, @@ -1220,9 +1220,9 @@ "matched_token_number" : 29 }, "SortAlgo1_6-SortAlgo2_5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2558139534883721, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.27906976744186046, + "matched_token_number" : 12 }, "SortAlgo1_4-SortAlgo4d1" : { "minimal_similarity" : 0.3541666666666667, @@ -1280,9 +1280,9 @@ "matched_token_number" : 29 }, "SortAlgo2-SortAlgo5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo4d1-SortAlgo4d3" : { "minimal_similarity" : 0.9375, @@ -1340,9 +1340,9 @@ "matched_token_number" : 0 }, "SortAlgo1_4-SortAlgo2_5" : { - "minimal_similarity" : 0.23255813953488372, - "maximum_similarity" : 0.23255813953488372, - "matched_token_number" : 10 + "minimal_similarity" : 0.2558139534883721, + "maximum_similarity" : 0.2558139534883721, + "matched_token_number" : 11 }, "SortAlgo1-SortAlgo4" : { "minimal_similarity" : 0.5581395348837209, @@ -1350,9 +1350,9 @@ "matched_token_number" : 24 }, "SortAlgo1-SortAlgo5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo1-SortAlgo2" : { "minimal_similarity" : 1.0, @@ -1390,9 +1390,9 @@ "matched_token_number" : 38 }, "SortAlgo1_2-SortAlgo2_5" : { - "minimal_similarity" : 0.2, - "maximum_similarity" : 0.2558139534883721, - "matched_token_number" : 11 + "minimal_similarity" : 0.21818181818181817, + "maximum_similarity" : 0.27906976744186046, + "matched_token_number" : 12 }, "SortAlgo1_6-SortAlgo6" : { "minimal_similarity" : 0.7959183673469388, @@ -1400,14 +1400,14 @@ "matched_token_number" : 39 }, "SortAlgo-SortAlgo2_5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo1_6-SortAlgo5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2558139534883721, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.27906976744186046, + "matched_token_number" : 12 }, "SortAlgo1_6-SortAlgo4" : { "minimal_similarity" : 0.5581395348837209, @@ -1425,14 +1425,14 @@ "matched_token_number" : 0 }, "SortAlgo2_5-SortAlgo7" : { - "minimal_similarity" : 0.17857142857142858, - "maximum_similarity" : 0.23255813953488372, - "matched_token_number" : 10 + "minimal_similarity" : 0.19642857142857142, + "maximum_similarity" : 0.2558139534883721, + "matched_token_number" : 11 }, "SortAlgo1_5-SortAlgo7" : { - "minimal_similarity" : 0.17857142857142858, - "maximum_similarity" : 0.23255813953488372, - "matched_token_number" : 10 + "minimal_similarity" : 0.19642857142857142, + "maximum_similarity" : 0.2558139534883721, + "matched_token_number" : 11 }, "SortAlgo1-SortAlgo4d3" : { "minimal_similarity" : 0.6595744680851063, @@ -1460,14 +1460,14 @@ "matched_token_number" : 31 }, "SortAlgo1_5-SortAlgo2" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo3_5-SortAlgo6" : { - "minimal_similarity" : 0.16071428571428573, - "maximum_similarity" : 0.1836734693877551, - "matched_token_number" : 9 + "minimal_similarity" : 0.17857142857142858, + "maximum_similarity" : 0.20408163265306123, + "matched_token_number" : 10 }, "SortAlgo3_5-SortAlgo7" : { "minimal_similarity" : 0.0, @@ -1510,9 +1510,9 @@ "matched_token_number" : 31 }, "SortAlgo1_5-SortAlgo1_6" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2558139534883721, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.27906976744186046, + "matched_token_number" : 12 }, "SortAlgo3_6-SortAlgo4d2" : { "minimal_similarity" : 0.30357142857142855, @@ -1584,12 +1584,12 @@ "maximum_similarity" : 0.2558139534883721, "matched_token_number" : 11 }, - "SortAlgo2_5-SortAlgo3" : { + "SortAlgo3_5-SortAlgo4d1" : { "minimal_similarity" : 0.0, "maximum_similarity" : 0.0, "matched_token_number" : 0 }, - "SortAlgo3_5-SortAlgo4d1" : { + "SortAlgo2_5-SortAlgo3" : { "minimal_similarity" : 0.0, "maximum_similarity" : 0.0, "matched_token_number" : 0 @@ -1620,9 +1620,9 @@ "matched_token_number" : 25 }, "SortAlgo1-SortAlgo1_5" : { - "minimal_similarity" : 0.2558139534883721, - "maximum_similarity" : 0.2682926829268293, - "matched_token_number" : 11 + "minimal_similarity" : 0.27906976744186046, + "maximum_similarity" : 0.2926829268292683, + "matched_token_number" : 12 }, "SortAlgo1-SortAlgo1_4" : { "minimal_similarity" : 0.5348837209302325, @@ -1650,9 +1650,9 @@ "matched_token_number" : 0 }, "SortAlgo1_4-SortAlgo1_5" : { - "minimal_similarity" : 0.23255813953488372, - "maximum_similarity" : 0.23255813953488372, - "matched_token_number" : 10 + "minimal_similarity" : 0.2558139534883721, + "maximum_similarity" : 0.2558139534883721, + "matched_token_number" : 11 }, "SortAlgo3-SortAlgo5" : { "minimal_similarity" : 0.0, @@ -1695,9 +1695,9 @@ "matched_token_number" : 19 }, "SortAlgo1_2-SortAlgo1_5" : { - "minimal_similarity" : 0.2, - "maximum_similarity" : 0.2558139534883721, - "matched_token_number" : 11 + "minimal_similarity" : 0.21818181818181817, + "maximum_similarity" : 0.27906976744186046, + "matched_token_number" : 12 }, "SortAlgo1_2-SortAlgo1_6" : { "minimal_similarity" : 0.45454545454545453, diff --git a/language-antlr-utils/pom.xml b/language-antlr-utils/pom.xml index 96b547f14..c5542fbfc 100644 --- a/language-antlr-utils/pom.xml +++ b/language-antlr-utils/pom.xml @@ -14,7 +14,7 @@ org.antlr antlr4-runtime - 4.13.0 + 4.13.1 de.jplag diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrLanguage.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrLanguage.java index 0c0b05a81..93f6ace30 100644 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrLanguage.java +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrLanguage.java @@ -10,9 +10,12 @@ /** * Base class for Antlr languages. Handle the parse function from {@link Language} + *

+ * You can either pass the parser to the super constructor, or implement the initializeParser method. That allows you to + * access class members, like language specific options. */ public abstract class AbstractAntlrLanguage implements Language { - private final AbstractAntlrParserAdapter parser; + private AbstractAntlrParserAdapter parser; /** * New instance @@ -22,8 +25,29 @@ protected AbstractAntlrLanguage(AbstractAntlrParserAdapter parser) { this.parser = parser; } + /** + * New instance, without pre initialized parser. If you use this constructor, you need to override the initializeParser + * method. + */ + protected AbstractAntlrLanguage() { + this.parser = null; + } + @Override public List parse(Set files) throws ParsingException { + if (this.parser == null) { + this.parser = this.initializeParser(); + } + return this.parser.parse(files); } + + /** + * Lazily creates the parser. Has to be implemented, if no parser is passed in the constructor. + * @return The newly initialized parser + */ + protected AbstractAntlrParserAdapter initializeParser() { + throw new UnsupportedOperationException( + String.format("The initializeParser method needs to be implemented for %s", this.getClass().getName())); + } } diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java index b80fdde6a..f4144b72e 100644 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractAntlrListener.java @@ -1,6 +1,5 @@ package de.jplag.antlr; -import java.io.File; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; @@ -8,215 +7,92 @@ import java.util.function.Predicate; import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.ParseTreeListener; import org.antlr.v4.runtime.tree.TerminalNode; -import de.jplag.TokenType; -import de.jplag.semantics.VariableRegistry; - /** - * Base class for Antlr listeners. You can use the create*Mapping functions to map antlr tokens to jplag tokens. - *

- * You should create a constructor matching one of the constructors and create your mapping after calling super. + * Base class for Antlr listeners. This is a quasi-static class that is only created once per language. Use by calling + * the visit methods in the overwritten constructor. */ -@SuppressWarnings("unused") -public class AbstractAntlrListener implements ParseTreeListener { - private final List> startMappings; - private final List> endMappings; - private final List> rangeMappings; - - private final List terminalMapping; - - private final TokenCollector collector; - private final File currentFile; - - private VariableRegistry variableRegistry; +public abstract class AbstractAntlrListener { + private final List> contextVisitors; + private final List terminalVisitors; /** * New instance - * @param collector The token collector - * @param currentFile The currently processed file - * @param extractsSemantics If true, the listener will extract semantics along with every token */ - public AbstractAntlrListener(TokenCollector collector, File currentFile, boolean extractsSemantics) { - this.collector = collector; - this.currentFile = currentFile; - - this.startMappings = new ArrayList<>(); - this.endMappings = new ArrayList<>(); - this.rangeMappings = new ArrayList<>(); - - this.terminalMapping = new ArrayList<>(); - - if (extractsSemantics) { - this.variableRegistry = new VariableRegistry(); - } + protected AbstractAntlrListener() { + contextVisitors = new ArrayList<>(); + terminalVisitors = new ArrayList<>(); } /** - * Creates a new AbstractAntlrListener, that does not collect semantics information - * @param collector The collector, obtained by the parser - * @param currentFile The current file, obtained by the parser - */ - public AbstractAntlrListener(TokenCollector collector, File currentFile) { - this(collector, currentFile, false); - } - - @Override - public void visitTerminal(TerminalNode terminalNode) { - this.terminalMapping.stream().filter(mapping -> mapping.matches(terminalNode.getSymbol())) - .forEach(mapping -> mapping.createToken(terminalNode.getSymbol(), variableRegistry)); - } - - @Override - public void visitErrorNode(ErrorNode errorNode) { - // does nothing, because we do not handle error nodes right now. - } - - @Override - public void enterEveryRule(ParserRuleContext rule) { - this.startMappings.stream().filter(mapping -> mapping.matches(rule)).forEach(mapping -> mapping.createToken(rule, variableRegistry)); - - this.rangeMappings.stream().filter(mapping -> mapping.matches(rule)).forEach(mapping -> mapping.createToken(rule, variableRegistry)); - } - - @Override - public void exitEveryRule(ParserRuleContext rule) { - this.endMappings.stream().filter(mapping -> mapping.matches(rule)).forEach(mapping -> mapping.createToken(rule, variableRegistry)); - } - - /** - * Creates a mapping using the start token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param The type of {@link ParserRuleContext} - * @return The builder for the token - */ - protected ContextTokenBuilder mapEnter(Class antlrType, TokenType jplagType) { - return this.mapEnter(antlrType, jplagType, it -> true); - } - - /** - * Creates a mapping using the start token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token - */ - @SuppressWarnings("unchecked") - protected ContextTokenBuilder mapEnter(Class antlrType, TokenType jplagType, Predicate condition) { - ContextTokenBuilder builder = initTypeBuilder(antlrType, jplagType, condition, ContextTokenBuilderType.START); - this.startMappings.add((ContextTokenBuilder) builder); - return builder; - } - - /** - * Creates a mapping using the stop token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param The type of {@link ParserRuleContext} - * @return The builder for the token - */ - protected ContextTokenBuilder mapExit(Class antlrType, TokenType jplagType) { - return this.mapExit(antlrType, jplagType, it -> true); - } - - /** - * Creates a mapping using the stop token from antlr as the location - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given node. + * @param antlrType The antlr type of the node. + * @param condition An additional condition for the visit. + * @return A visitor for the node. + * @param The class of the node. */ @SuppressWarnings("unchecked") - protected ContextTokenBuilder mapExit(Class antlrType, TokenType jplagType, Predicate condition) { - ContextTokenBuilder builder = initTypeBuilder(antlrType, jplagType, condition, ContextTokenBuilderType.STOP); - this.endMappings.add((ContextTokenBuilder) builder); - return builder; + public ContextVisitor visit(Class antlrType, Predicate condition) { + Predicate typeCheck = rule -> rule.getClass() == antlrType; + ContextVisitor visitor = new ContextVisitor<>(typeCheck.and(condition)); + contextVisitors.add((ContextVisitor) visitor); + return visitor; } /** - * Creates a mapping using the beginning of the start token as the start location and the distance from the start to the - * stop token as the length - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given node. + * @param antlrType The antlr type of the node. + * @return A visitor for the node. + * @param The class of the node. */ - protected ContextTokenBuilder mapRange(Class antlrType, TokenType jplagType) { - return this.mapRange(antlrType, jplagType, it -> true); + public ContextVisitor visit(Class antlrType) { + return visit(antlrType, ignore -> true); } /** - * Creates a mapping using the beginning of the start token as the start location and the distance from the start to the - * stop token as the length - * @param antlrType The antlr context type - * @param jplagType The Jplag token type - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given terminal. + * @param terminalType The type of the terminal. + * @param condition An additional condition for the visit. + * @return A visitor for the node. */ - @SuppressWarnings("unchecked") - protected ContextTokenBuilder mapRange(Class antlrType, TokenType jplagType, Predicate condition) { - ContextTokenBuilder builder = initTypeBuilder(antlrType, jplagType, condition, ContextTokenBuilderType.RANGE); - this.rangeMappings.add((ContextTokenBuilder) builder); - return builder; + public TerminalVisitor visit(int terminalType, Predicate condition) { + Predicate typeCheck = rule -> rule.getType() == terminalType; + TerminalVisitor visitor = new TerminalVisitor(typeCheck.and(condition)); + terminalVisitors.add(visitor); + return visitor; } /** - * Creates a start mapping from antlrType to startType and a stop mapping from antlrType to stopType. - * @param antlrType The antlr token type - * @param startType The token type for the start mapping - * @param stopType The token type for the stop mapping - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Visit the given terminal. + * @param terminalType The type of the terminal. + * @return A visitor for the node. */ - protected RangeBuilder mapEnterExit(Class antlrType, TokenType startType, TokenType stopType) { - return mapEnterExit(antlrType, startType, stopType, it -> true); + public TerminalVisitor visit(int terminalType) { + return visit(terminalType, ignore -> true); } /** - * Creates a start mapping from antlrType to startType and a stop mapping from antlrType to stopType. - * @param antlrType The antlr token type - * @param startType The token type for the start mapping - * @param stopType The token type for the stop mapping - * @param condition The condition under which the mapping applies - * @param The type of {@link ParserRuleContext} - * @return The builder for the token + * Called by {@link InternalListener#visitTerminal(TerminalNode)} as part of antlr framework. */ - protected RangeBuilder mapEnterExit(Class antlrType, TokenType startType, TokenType stopType, - Predicate condition) { - ContextTokenBuilder start = this.mapEnter(antlrType, startType, condition); - ContextTokenBuilder end = this.mapExit(antlrType, stopType, condition); - return new RangeBuilder<>(start, end); + void visitTerminal(HandlerData data) { + this.terminalVisitors.stream().filter(visitor -> visitor.matches(data.entity())).forEach(visitor -> visitor.enter(data)); } /** - * Creates a mapping for terminal tokens - * @param terminalType The type of the terminal node - * @param jplagType The jplag token type - * @return The builder for the token + * Called by {@link InternalListener#enterEveryRule(ParserRuleContext)} as part of antlr framework. */ - protected TerminalTokenBuilder mapTerminal(int terminalType, TokenType jplagType) { - return this.mapTerminal(terminalType, jplagType, it -> true); + void enterEveryRule(HandlerData data) { + this.contextVisitors.stream().filter(visitor -> visitor.matches(data.entity())).forEach(visitor -> visitor.enter(data)); } /** - * Creates a mapping for terminal tokens - * @param terminalType The type of the terminal node - * @param jplagType The jplag token type - * @param condition The condition under which the mapping applies - * @return The builder for the token + * Called by {@link InternalListener#exitEveryRule(ParserRuleContext)} as part of antlr framework. */ - protected TerminalTokenBuilder mapTerminal(int terminalType, TokenType jplagType, Predicate condition) { - TerminalTokenBuilder builder = new TerminalTokenBuilder(jplagType, token -> token.getType() == terminalType && condition.test(token), - this.collector, this.currentFile); - this.terminalMapping.add(builder); - return builder; + void exitEveryRule(HandlerData data) { + this.contextVisitors.stream().filter(visitor -> visitor.matches(data.entity())).forEach(visitor -> visitor.exit(data)); } /** @@ -228,7 +104,7 @@ protected TerminalTokenBuilder mapTerminal(int terminalType, TokenType jplagType * @return an ancestor of the specified type, or null if not found. */ @SafeVarargs - protected final T getAncestor(ParserRuleContext context, Class ancestor, + protected static T getAncestor(ParserRuleContext context, Class ancestor, Class... stops) { ParserRuleContext currentContext = context; Set> forbidden = Set.of(stops); @@ -255,7 +131,7 @@ protected final T getAncestor(ParserRuleContext co * @see #getAncestor(ParserRuleContext, Class, Class[]) */ @SafeVarargs - protected final boolean hasAncestor(ParserRuleContext context, Class parent, + protected static boolean hasAncestor(ParserRuleContext context, Class parent, Class... stops) { return getAncestor(context, parent, stops) != null; } @@ -267,7 +143,7 @@ protected final boolean hasAncestor(ParserRuleContext context, Class the type to search for. * @return the first appearance of an element of the given type in the subtree, or null if no such element exists. */ - protected final T getDescendant(ParserRuleContext context, Class descendant) { + protected static T getDescendant(ParserRuleContext context, Class descendant) { // simple iterative bfs ArrayDeque queue = new ArrayDeque<>(); queue.add(context); @@ -284,10 +160,4 @@ protected final T getDescendant(ParserRuleContext } return null; } - - private ContextTokenBuilder initTypeBuilder(Class antlrType, TokenType jplagType, Predicate condition, - ContextTokenBuilderType type) { - return new ContextTokenBuilder<>(jplagType, rule -> rule.getClass() == antlrType && condition.test(antlrType.cast(rule)), this.collector, - this.currentFile, type); - } } 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 c5cdc478f..1644ccf7b 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 @@ -25,6 +25,24 @@ * @param The type of the antlr parser */ public abstract class AbstractAntlrParserAdapter extends AbstractParser { + + private final boolean extractsSemantics; + + /** + * New instance + * @param extractsSemantics If true, the listener will extract semantics along with every token + */ + protected AbstractAntlrParserAdapter(boolean extractsSemantics) { + this.extractsSemantics = extractsSemantics; + } + + /** + * New instance + */ + protected AbstractAntlrParserAdapter() { + this(false); + } + /** * Parsers the set of files * @param files The files @@ -32,33 +50,29 @@ public abstract class AbstractAntlrParserAdapter extends Abstr * @throws ParsingException If anything goes wrong */ public List parse(Set files) throws ParsingException { - TokenCollector collector = new TokenCollector(); - + TokenCollector collector = new TokenCollector(extractsSemantics); for (File file : files) { parseFile(file, collector); } - return collector.getTokens(); } private void parseFile(File file, TokenCollector collector) throws ParsingException { + collector.enterFile(file); try (Reader reader = FileUtils.openFileReader(file)) { Lexer lexer = this.createLexer(CharStreams.fromReader(reader)); CommonTokenStream tokenStream = new CommonTokenStream(lexer); T parser = this.createParser(tokenStream); - ParserRuleContext entryContext = this.getEntryContext(parser); ParseTreeWalker treeWalker = new ParseTreeWalker(); - - AbstractAntlrListener listener = this.createListener(collector, file); + InternalListener listener = new InternalListener(this.getListener(), collector); for (ParseTree child : entryContext.children) { treeWalker.walk(listener, child); } - - collector.addToken(Token.fileEnd(file)); } catch (IOException exception) { throw new ParsingException(file, exception.getMessage(), exception); } + collector.addFileEndToken(); } /** @@ -83,10 +97,7 @@ private void parseFile(File file, TokenCollector collector) throws ParsingExcept protected abstract ParserRuleContext getEntryContext(T parser); /** - * Creates the listener - * @param collector The token collector - * @param currentFile The current file - * @return The parser + * @return The listener. Should be created once statically since it never changes. */ - protected abstract AbstractAntlrListener createListener(TokenCollector collector, File currentFile); + protected abstract AbstractAntlrListener getListener(); } diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractVisitor.java b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractVisitor.java new file mode 100644 index 000000000..cad061e19 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/AbstractVisitor.java @@ -0,0 +1,122 @@ +package de.jplag.antlr; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.*; + +import org.antlr.v4.runtime.Token; + +import de.jplag.TokenType; +import de.jplag.semantics.CodeSemantics; +import de.jplag.semantics.VariableRegistry; + +/** + * The abstract visitor. + * @param The type of the visited entity. + */ +public abstract class AbstractVisitor { + private final Predicate condition; + private final List>> entryHandlers; + private TokenType entryTokenType; + private Function entrySemantics; + + /** + * @param condition The condition for the visit. + */ + AbstractVisitor(Predicate condition) { + this.condition = condition; + this.entryHandlers = new ArrayList<>(); + } + + /** + * Add an action the visitor runs upon entering the entity. + * @param handler The action, takes the entity and the variable registry as parameter. + * @return Self + */ + public AbstractVisitor onEnter(BiConsumer handler) { + entryHandlers.add(handlerData -> handler.accept(handlerData.entity(), handlerData.variableRegistry())); + return this; + } + + /** + * Add an action the visitor runs upon entering the entity. + * @param handler The action, takes the entity as parameter. + * @return Self + */ + public AbstractVisitor onEnter(Consumer handler) { + entryHandlers.add(handlerData -> handler.accept(handlerData.entity())); + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering the entity. Should only be invoked once per visitor. + * @param tokenType The type of the token. + * @return Self + */ + public AbstractVisitor mapEnter(TokenType tokenType) { + entryTokenType = tokenType; + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering the entity. Should only be invoked once per visitor. + * Alias for {@link #mapEnter(TokenType)}. + * @param tokenType The type of the token. + * @return Self + */ + public AbstractVisitor map(TokenType tokenType) { + mapEnter(tokenType); + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics. + * @param semanticsSupplier A function that takes the entity and returns the semantics. + * @return Self + */ + public AbstractVisitor withSemantics(Function semanticsSupplier) { + this.entrySemantics = semanticsSupplier; + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics. + * @param semanticsSupplier A function that returns the semantics. + * @return Self + */ + public AbstractVisitor withSemantics(Supplier semanticsSupplier) { + this.entrySemantics = ignore -> semanticsSupplier.get(); + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics of type control. + * @return Self + */ + public AbstractVisitor withControlSemantics() { + withSemantics(CodeSemantics::createControl); + return this; + } + + /** + * @param entity The entity to check. + * @return Whether to visit the entity. + */ + boolean matches(T entity) { + return this.condition.test(entity); + } + + /** + * Enter a given entity, injecting the needed dependencies. + */ + void enter(HandlerData data) { + addToken(data, entryTokenType, entrySemantics, this::extractEnterToken); + entryHandlers.forEach(handler -> handler.accept(data)); + } + + void addToken(HandlerData data, TokenType tokenType, Function semantics, Function extractToken) { + data.collector().addToken(tokenType, semantics, data.entity(), extractToken, data.variableRegistry()); + } + + abstract Token extractEnterToken(T entity); +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilder.java deleted file mode 100644 index c48ffc678..000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilder.java +++ /dev/null @@ -1,53 +0,0 @@ -package de.jplag.antlr; - -import java.io.File; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.antlr.v4.runtime.ParserRuleContext; - -import de.jplag.TokenType; -import de.jplag.semantics.VariableScope; - -/** - * Builds tokens for {@link ParserRuleContext}s. - * @param The type of context - */ -public class ContextTokenBuilder extends TokenBuilder { - private final ContextTokenBuilderType type; - - ContextTokenBuilder(TokenType tokenType, Predicate condition, TokenCollector collector, File file, ContextTokenBuilderType type) { - super(tokenType, condition, collector, file); - this.type = type; - } - - /** - * Adds this builder as a variable to the variable registry - * @param scope The scope of the variable - * @param mutable true, if the variable is mutable - * @param nameGetter The getter for the name, from the current {@link ParserRuleContext} - * @return Self - */ - public ContextTokenBuilder addAsVariable(VariableScope scope, boolean mutable, Function nameGetter) { - addSemanticsHandler((registry, rule) -> registry.registerVariable(nameGetter.apply(rule), scope, mutable)); - return this; - } - - @Override - protected org.antlr.v4.runtime.Token getAntlrToken(T antlrContent) { - if (this.type != ContextTokenBuilderType.STOP) { - return antlrContent.getStart(); - } else { - return antlrContent.getStop(); - } - } - - @Override - protected int getLength(T antlrContent) { - if (this.type != ContextTokenBuilderType.RANGE) { - return super.getLength(antlrContent); - } else { - return antlrContent.getStop().getStopIndex() - antlrContent.getStart().getStartIndex() + 1; - } - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilderType.java b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilderType.java deleted file mode 100644 index ab7d9231c..000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextTokenBuilderType.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.jplag.antlr; - -/** - * The types of context token builder. Either start, stop or range. Should only be used internally. - */ -enum ContextTokenBuilderType { - START, - STOP, - RANGE -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/ContextVisitor.java b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextVisitor.java new file mode 100644 index 000000000..92dd4ae79 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/ContextVisitor.java @@ -0,0 +1,139 @@ +package de.jplag.antlr; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.*; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; + +import de.jplag.TokenType; +import de.jplag.semantics.CodeSemantics; +import de.jplag.semantics.VariableRegistry; + +/** + * The visitor for nodes, or contexts. + * @param The antlr type of the node. + */ +public class ContextVisitor extends AbstractVisitor { + private final List>> exitHandlers; + private TokenType exitToken; + private Function exitSemantics; + + ContextVisitor(Predicate condition) { + super(condition); + this.exitHandlers = new ArrayList<>(); + } + + /** + * Add an action the visitor runs upon exiting the entity. + * @param handler The action, takes the entity and the variable registry as parameter. + * @return Self + */ + public AbstractVisitor onExit(BiConsumer handler) { + exitHandlers.add(handlerData -> handler.accept(handlerData.entity(), handlerData.variableRegistry())); + return this; + } + + /** + * Add an action the visitor runs upon exiting the entity. + * @param handler The action, takes the entity as parameter. + * @return Self + */ + public AbstractVisitor onExit(Consumer handler) { + exitHandlers.add(handlerData -> handler.accept(handlerData.entity())); + return this; + } + + /** + * Tell the visitor that it should generate a token upon exiting the entity. Should only be invoked once per visitor. + * @param tokenType The type of the token. + * @return Self + */ + public ContextVisitor mapExit(TokenType tokenType) { + exitToken = tokenType; + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering and one upon exiting the entity. Should only be + * invoked once per visitor. + * @param enterTokenType The type of the token generated on enter. + * @param exitTokenType The type of the token generated on exit. + * @return Self + */ + public ContextVisitor mapEnterExit(TokenType enterTokenType, TokenType exitTokenType) { + mapEnter(enterTokenType); + mapExit(exitTokenType); + return this; + } + + /** + * Tell the visitor that it should generate a token upon entering and one upon exiting the entity. Should only be + * invoked once per visitor. Alias for {@link #mapEnterExit(TokenType, TokenType)}. + * @param enterTokenType The type of the token generated on enter. + * @param exitTokenType The type of the token generated on exit. + * @return Self + */ + public ContextVisitor map(TokenType enterTokenType, TokenType exitTokenType) { + mapEnterExit(enterTokenType, exitTokenType); + return this; + } + + @Override + public ContextVisitor withSemantics(Function semantics) { + super.withSemantics(semantics); + this.exitSemantics = semantics; + return this; + } + + @Override + public ContextVisitor withSemantics(Supplier semantics) { + super.withSemantics(semantics); + this.exitSemantics = ignore -> semantics.get(); + return this; + } + + /** + * Tell the visitor that if it generates a token upon entering the entity, it should have semantics of type loop begin, + * same for the exit and loop end. + * @return Self + */ + public ContextVisitor withLoopSemantics() { + super.withSemantics(CodeSemantics::createLoopBegin); + this.exitSemantics = ignore -> CodeSemantics.createLoopEnd(); + return this; + } + + /** + * Tell the visitor that the entity represents a local scope. + * @return Self + */ + public ContextVisitor addLocalScope() { + onEnter((ignore, variableRegistry) -> variableRegistry.enterLocalScope()); + onExit((ignore, variableRegistry) -> variableRegistry.exitLocalScope()); + return this; + } + + /** + * Tell the visitor that the entity represents a class scope. + * @return Self + */ + public ContextVisitor addClassScope() { + onEnter((ignore, variableRegistry) -> variableRegistry.enterClass()); + onExit((ignore, variableRegistry) -> variableRegistry.exitClass()); + return this; + } + + /** + * Exit a given entity, injecting the needed dependencies. + */ + void exit(HandlerData data) { + addToken(data, exitToken, exitSemantics, ParserRuleContext::getStop); + exitHandlers.forEach(handler -> handler.accept(data)); + } + + Token extractEnterToken(T entity) { + return entity.getStart(); + } +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/HandlerData.java b/language-antlr-utils/src/main/java/de/jplag/antlr/HandlerData.java new file mode 100644 index 000000000..6c1bb4095 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/HandlerData.java @@ -0,0 +1,9 @@ +package de.jplag.antlr; + +import de.jplag.semantics.VariableRegistry; + +/** + * Holds the data passed to the (quasi-static) listeners. + */ +record HandlerData(T entity, VariableRegistry variableRegistry, TokenCollector collector) { +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListener.java b/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListener.java new file mode 100644 index 000000000..39178e0fb --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListener.java @@ -0,0 +1,48 @@ +package de.jplag.antlr; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.antlr.v4.runtime.tree.TerminalNode; + +import de.jplag.semantics.VariableRegistry; + +/** + * Internal listener that implements pre-existing antlr methods that are called automatically. This listener is created + * for every file. + */ +class InternalListener implements ParseTreeListener { + private final AbstractAntlrListener listener; + private final TokenCollector collector; + protected final VariableRegistry variableRegistry; + + InternalListener(AbstractAntlrListener listener, TokenCollector collector) { + this.listener = listener; + this.collector = collector; + this.variableRegistry = new VariableRegistry(); + } + + @Override + public void visitTerminal(TerminalNode terminalNode) { + listener.visitTerminal(getHandlerData(terminalNode.getSymbol())); + } + + @Override + public void enterEveryRule(ParserRuleContext rule) { + listener.enterEveryRule(getHandlerData(rule)); + } + + @Override + public void exitEveryRule(ParserRuleContext rule) { + listener.exitEveryRule(getHandlerData(rule)); + } + + @Override + public void visitErrorNode(ErrorNode errorNode) { + // does nothing, because we do not handle error nodes right now. + } + + private HandlerData getHandlerData(T entity) { + return new HandlerData<>(entity, variableRegistry, collector); + } +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListenerException.java b/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListenerException.java deleted file mode 100644 index 70422ee29..000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/InternalListenerException.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.jplag.antlr; - -/** - * Exception type used internally within the antlr utils. Has to be a {@link RuntimeException}, because it is thrown - * within the antlr listener methods. Should not be thrown outside the antlr utils. - */ -public class InternalListenerException extends RuntimeException { - /** - * New instance - * @param message The message of the exception - */ - public InternalListenerException(String message) { - super(message); - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/RangeBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/RangeBuilder.java deleted file mode 100644 index ea84cf3bd..000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/RangeBuilder.java +++ /dev/null @@ -1,112 +0,0 @@ -package de.jplag.antlr; - -import java.util.function.Consumer; -import java.util.function.Function; - -import org.antlr.v4.runtime.ParserRuleContext; - -import de.jplag.semantics.CodeSemantics; -import de.jplag.semantics.VariableRegistry; -import de.jplag.semantics.VariableScope; - -/** - * Builder for semantics on range mappings - * @param The type of rule - */ -@SuppressWarnings("unused") -public class RangeBuilder { - private final ContextTokenBuilder start; - private final ContextTokenBuilder end; - - /** - * New instance - * @param start The builder for the start token - * @param end The builder for the end token - */ - RangeBuilder(ContextTokenBuilder start, ContextTokenBuilder end) { - this.start = start; - this.end = end; - } - - /** - * Adds a class context to the variable registry - * @return Self - */ - public RangeBuilder addClassContext() { - this.start.addSemanticsHandler(VariableRegistry::enterClass); - this.end.addSemanticsHandler(VariableRegistry::exitClass); - return this; - } - - /** - * Adds a local scope to the variable registry - * @return Self - */ - public RangeBuilder addLocalScope() { - this.start.addSemanticsHandler(VariableRegistry::enterLocalScope); - this.end.addSemanticsHandler(VariableRegistry::exitLocalScope); - return this; - } - - /** - * Adds a semantics handler to the start token builder - * @param handler The handler - * @return Self - */ - public RangeBuilder addStartSemanticHandler(Consumer handler) { - this.start.addSemanticsHandler(handler); - return this; - } - - /** - * Adds a semantic handler to the end token builder - * @param handler The handler - * @return Self - */ - public RangeBuilder addEndSemanticHandler(Consumer handler) { - this.end.addSemanticsHandler(handler); - return this; - } - - /** - * Adds the start token as a variable when it is extracted - * @param scope The scope for the variable - * @param mutable true if the variable is mutable - * @param nameGetter The getter for the name - * @return Self - */ - public RangeBuilder addAsVariableOnStart(VariableScope scope, boolean mutable, Function nameGetter) { - this.start.addAsVariable(scope, mutable, nameGetter); - return this; - } - - /** - * Sets the given semantic for the start token - * @param semantics The semantic - * @return Self - */ - public RangeBuilder withStartSemantics(CodeSemantics semantics) { - this.start.withSemantics(semantics); - return this; - } - - /** - * Sets the given semantic for the end token - * @param semantics The semantic - * @return Self - */ - public RangeBuilder withEndSemantics(CodeSemantics semantics) { - this.end.withSemantics(semantics); - return this; - } - - /** - * Sets a control semantics for both tokens - * @return Self - */ - public RangeBuilder withControlSemantics() { - this.start.withControlSemantics(); - this.end.withControlSemantics(); - return this; - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalTokenBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalTokenBuilder.java deleted file mode 100644 index 3f074e6ad..000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalTokenBuilder.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.jplag.antlr; - -import java.io.File; -import java.util.function.Predicate; - -import org.antlr.v4.runtime.Token; - -import de.jplag.TokenType; - -/** - * Builds tokens from terminal antlr nodes - */ -public class TerminalTokenBuilder extends TokenBuilder { - /** - * New instance - * @param tokenType The token type - * @param condition The condition - * @param collector The token collector for the listener - * @param file The file the listener is for - */ - TerminalTokenBuilder(TokenType tokenType, Predicate condition, TokenCollector collector, File file) { - super(tokenType, condition, collector, file); - } - - @Override - protected Token getAntlrToken(Token antlrContent) { - return antlrContent; - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalVisitor.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalVisitor.java new file mode 100644 index 000000000..170f5d627 --- /dev/null +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/TerminalVisitor.java @@ -0,0 +1,19 @@ +package de.jplag.antlr; + +import java.util.function.Predicate; + +import org.antlr.v4.runtime.Token; + +/** + * The visitor for terminals. + */ +public class TerminalVisitor extends AbstractVisitor { + + TerminalVisitor(Predicate condition) { + super(condition); + } + + Token extractEnterToken(Token token) { + return token; + } +} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenBuilder.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TokenBuilder.java deleted file mode 100644 index 4dab47b9e..000000000 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenBuilder.java +++ /dev/null @@ -1,140 +0,0 @@ -package de.jplag.antlr; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.function.*; -import java.util.logging.Logger; - -import de.jplag.Token; -import de.jplag.TokenType; -import de.jplag.semantics.CodeSemantics; -import de.jplag.semantics.VariableRegistry; - -/** - * Handles the extraction of tokens. Contains information on the appropriate antlr types, the conditions under which the - * token should be extracted and semantics information. - * @param The antlr type being mapped - */ -public abstract class TokenBuilder { - private static final Logger logger = Logger.getLogger(TokenBuilder.class.getName()); - private static final String UNEXPECTED_SEMANTICS = "The listener %s indicates, it does not extract semantics. But the token (%s) has semantics information"; - private static final String MISSING_SEMANTICS = "Tokens should contain semantics, but none were supplied"; - - private final Predicate condition; - protected final TokenType tokenType; - - private Function semanticsSupplier = null; - private final List> semanticsHandler; - - protected final TokenCollector tokenCollector; - protected final File file; - - /** - * New instance - * @param tokenType The token type - * @param condition The condition - * @param collector The token collector for the listener - * @param file The file the listener is for - */ - TokenBuilder(TokenType tokenType, Predicate condition, TokenCollector collector, File file) { - this.condition = condition; - this.tokenType = tokenType; - this.tokenCollector = collector; - this.file = file; - - this.semanticsHandler = new ArrayList<>(); - } - - /** - * Checks if the token should be extracted for this node. - * @param value The node to check - * @return true, if the token should be extracted - */ - boolean matches(T value) { - return this.condition.test(value); - } - - /** - * Sets the given semantics for the token - * @param semantics The semantics - * @return Self - */ - public TokenBuilder withSemantics(CodeSemantics semantics) { - this.semanticsSupplier = ignore -> semantics; - return this; - } - - /** - * Uses the given function to build the token semantics from the antlr node - * @param function The function - * @return Self - */ - public TokenBuilder withSemantics(Function function) { - this.semanticsSupplier = function; - return this; - } - - /** - * Sets control semantics for the token - * @return Self - */ - public TokenBuilder withControlSemantics() { - withSemantics(CodeSemantics.createControl()); - return this; - } - - /** - * Adds a semantics handler to this builder. This can be used to perform additional operation like calling methods in - * the {@link de.jplag.semantics.VariableRegistry}. - * @param handler The handler function - * @return Self - */ - public TokenBuilder addSemanticsHandler(Consumer handler) { - this.semanticsHandler.add((semantics, rule) -> handler.accept(semantics)); - return this; - } - - /** - * Adds a semantics handler, that can perform additional operations required for semantics using the Semantics context - * objects and the antlr node. - * @param handler The handler - * @return Self - */ - public TokenBuilder addSemanticsHandler(BiConsumer handler) { - this.semanticsHandler.add(handler); - return this; - } - - void createToken(T antlrContent, VariableRegistry semantics) { - org.antlr.v4.runtime.Token antlrToken = getAntlrToken(antlrContent); - - int line = antlrToken.getLine(); - int column = antlrToken.getCharPositionInLine() + 1; - int length = antlrToken.getText().length(); - - Token token; - if (semantics != null) { - if (semanticsSupplier == null) { - throw new IllegalStateException(MISSING_SEMANTICS); - } - - this.semanticsHandler.forEach(it -> it.accept(semantics, antlrContent)); - token = new Token(this.tokenType, this.file, line, column, length, semanticsSupplier.apply(antlrContent)); - } else { - if (semanticsSupplier != null) { - logger.warning(() -> String.format(UNEXPECTED_SEMANTICS, this.getClass().getName(), this.tokenType.getDescription())); - } - - token = new Token(this.tokenType, this.file, line, column, length); - } - - this.tokenCollector.addToken(token); - } - - protected abstract org.antlr.v4.runtime.Token getAntlrToken(T antlrContent); - - protected int getLength(T antlrContent) { - return getAntlrToken(antlrContent).getText().length(); - } -} diff --git a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java b/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java index 30ab0ce78..7a284d015 100644 --- a/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java +++ b/language-antlr-utils/src/main/java/de/jplag/antlr/TokenCollector.java @@ -1,36 +1,80 @@ package de.jplag.antlr; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Function; +import java.util.logging.Logger; import de.jplag.Token; +import de.jplag.TokenType; +import de.jplag.semantics.CodeSemantics; +import de.jplag.semantics.VariableRegistry; /** * Collects the tokens during parsing. */ public class TokenCollector { + private static final Logger logger = Logger.getLogger(TokenCollector.class.getName()); private final List collected; + private final boolean extractsSemantics; + private File file; /** - * New instance + * @param extractsSemantics If semantics are extracted */ - public TokenCollector() { + TokenCollector(boolean extractsSemantics) { this.collected = new ArrayList<>(); - } - - /** - * Adds a token to the collector - * @param token The token to add - */ - public void addToken(Token token) { - this.collected.add(token); + this.extractsSemantics = extractsSemantics; } /** * @return All collected tokens */ - public List getTokens() { + List getTokens() { return Collections.unmodifiableList(this.collected); } + + void addToken(TokenType jplagType, Function semanticsSupplier, T entity, + Function extractToken, VariableRegistry variableRegistry) { + if (jplagType == null) { + if (semanticsSupplier != null) { + logger.warning("Received semantics, but no token type, so no token was generated and the semantics discarded"); + } + return; + } + org.antlr.v4.runtime.Token antlrToken = extractToken.apply(entity); + int line = antlrToken.getLine(); + int column = antlrToken.getCharPositionInLine() + 1; + int length = antlrToken.getText().length(); + Token token; + if (extractsSemantics) { + if (semanticsSupplier == null) { + throw new IllegalStateException(String.format("Expected semantics bud did not receive any for token %s", jplagType.getDescription())); + } + CodeSemantics semantics = semanticsSupplier.apply(entity); + token = new Token(jplagType, this.file, line, column, length, semantics); + variableRegistry.updateSemantics(semantics); + } else { + if (semanticsSupplier != null) { + logger.warning(() -> String.format("Received semantics for token %s despite not expecting any", jplagType.getDescription())); + } + token = new Token(jplagType, this.file, line, column, length); + } + addToken(token); + } + + void enterFile(File newFile) { + this.file = newFile; + } + + void addFileEndToken() { + addToken(extractsSemantics ? Token.semanticFileEnd(file) : Token.fileEnd(file)); + // don't need to update semantics because variable registry is new for every file + } + + private void addToken(Token token) { + this.collected.add(token); + } } diff --git a/language-antlr-utils/src/test/java/de/jplag/antlr/LanguageTest.java b/language-antlr-utils/src/test/java/de/jplag/antlr/LanguageTest.java new file mode 100644 index 000000000..81941ac8d --- /dev/null +++ b/language-antlr-utils/src/test/java/de/jplag/antlr/LanguageTest.java @@ -0,0 +1,66 @@ +package de.jplag.antlr; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.File; +import java.util.Set; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import de.jplag.ParsingException; +import de.jplag.antlr.testLanguage.TestLanguage; +import de.jplag.antlr.testLanguage.TestParserAdapter; + +/** + * Some tests for the abstract antlr language + */ +class LanguageTest { + @Test + void testExceptionForNoDefinedParser() { + LanguageWithoutParser lang = new LanguageWithoutParser(); + Set emptySet = Set.of(); + assertThrows(UnsupportedOperationException.class, () -> lang.parse(emptySet)); + } + + @Test + void testLanguageWithStaticParser() throws ParsingException { + TestLanguage lang = new TestLanguage(); + Assertions.assertEquals(0, lang.parse(Set.of()).size()); + } + + @Test + void testLanguageWithLazyParser() throws ParsingException { + LanguageWithLazyParser lang = new LanguageWithLazyParser(); + Assertions.assertEquals(0, lang.parse(Set.of()).size()); + } + + private static class LanguageWithoutParser extends AbstractAntlrLanguage { + @Override + public String[] suffixes() { + return new String[0]; + } + + @Override + public String getName() { + return null; + } + + @Override + public String getIdentifier() { + return null; + } + + @Override + public int minimumTokenMatch() { + return 0; + } + } + + private static class LanguageWithLazyParser extends LanguageWithoutParser { + @Override + protected AbstractAntlrParserAdapter initializeParser() { + return new TestParserAdapter(); + } + } +} diff --git a/language-antlr-utils/src/test/java/de/jplag/antlr/ParserTest.java b/language-antlr-utils/src/test/java/de/jplag/antlr/ParserTest.java index 60f48c214..e33277201 100644 --- a/language-antlr-utils/src/test/java/de/jplag/antlr/ParserTest.java +++ b/language-antlr-utils/src/test/java/de/jplag/antlr/ParserTest.java @@ -2,7 +2,7 @@ import static de.jplag.antlr.testLanguage.TestTokenType.*; -import de.jplag.antlr.testLanguage.TestLangauge; +import de.jplag.antlr.testLanguage.TestLanguage; import de.jplag.antlr.testLanguage.TestTokenType; import de.jplag.testutils.LanguageModuleTest; import de.jplag.testutils.datacollector.TestDataCollector; @@ -10,7 +10,7 @@ public class ParserTest extends LanguageModuleTest { public ParserTest() { - super(new TestLangauge(), TestTokenType.class); + super(new TestLanguage(), TestTokenType.class); } @Override diff --git a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestLangauge.java b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestLanguage.java similarity index 85% rename from language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestLangauge.java rename to language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestLanguage.java index fd56bd35f..63a5f0d07 100644 --- a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestLangauge.java +++ b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestLanguage.java @@ -2,11 +2,11 @@ import de.jplag.antlr.AbstractAntlrLanguage; -public class TestLangauge extends AbstractAntlrLanguage { +public class TestLanguage extends AbstractAntlrLanguage { /** * New instance */ - public TestLangauge() { + public TestLanguage() { super(new TestParserAdapter()); } diff --git a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java index 49d30b023..9073ad954 100644 --- a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java +++ b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestListener.java @@ -2,34 +2,20 @@ import static de.jplag.antlr.testLanguage.TestTokenType.*; -import java.io.File; - -import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TestParser; +import de.jplag.antlr.*; import de.jplag.antlr.TestParser.*; -import de.jplag.antlr.TokenCollector; import de.jplag.semantics.CodeSemantics; import de.jplag.semantics.VariableScope; -public class TestListener extends AbstractAntlrListener { - /** - * New instance - * @param collector The token collector - * @param currentFile The currently processed file - */ - public TestListener(TokenCollector collector, File currentFile) { - super(collector, currentFile, true); - - mapEnter(VarDefContext.class, VARDEF).addAsVariable(VariableScope.FILE, false, rule -> rule.VAR_NAME().getText()) - .withSemantics(CodeSemantics.createKeep()); +class TestListener extends AbstractAntlrListener { - mapRange(CalcExpressionContext.class, ADDITION, rule -> rule.operator() != null && rule.operator().PLUS() != null).withControlSemantics(); - mapRange(OperatorContext.class, SUBTRACTION, rule -> rule.MINUS() != null).withControlSemantics(); - mapEnterExit(SubExpressionContext.class, SUB_EXPRESSION_BEGIN, SUB_EXPRESSION_END) - // .addEndSemanticHandler(registry -> registry.addAllNonLocalVariablesAsReads()) - // does not work here, because there is no class context. Is still here as an example. - .addLocalScope().withControlSemantics(); - mapTerminal(TestParser.NUMBER, NUMBER).withSemantics(CodeSemantics.createKeep()); - mapEnter(VarRefContext.class, VARREF).withSemantics(CodeSemantics.createKeep()); + TestListener() { + visit(VarDefContext.class).map(VARDEF).withSemantics(CodeSemantics::createKeep) + .onEnter((rule, variableRegistry) -> variableRegistry.registerVariable(rule.VAR_NAME().getText(), VariableScope.FILE, false)); + visit(CalcExpressionContext.class, rule -> rule.operator() != null && rule.operator().PLUS() != null).map(ADDITION).withControlSemantics(); + visit(OperatorContext.class, rule -> rule.MINUS() != null).map(SUBTRACTION).withControlSemantics(); + visit(SubExpressionContext.class).map(SUB_EXPRESSION_BEGIN, SUB_EXPRESSION_END).addLocalScope().withControlSemantics(); + visit(TestParser.NUMBER).map(NUMBER).withSemantics(CodeSemantics::createKeep); + visit(VarDefContext.class).map(VARDEF).withSemantics(CodeSemantics::createKeep); } } diff --git a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java index 5b292e18d..c2873a167 100644 --- a/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java +++ b/language-antlr-utils/src/test/java/de/jplag/antlr/testLanguage/TestParserAdapter.java @@ -1,7 +1,5 @@ package de.jplag.antlr.testLanguage; -import java.io.File; - import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; @@ -10,6 +8,8 @@ import de.jplag.antlr.*; public class TestParserAdapter extends AbstractAntlrParserAdapter { + private static final TestListener listener = new TestListener(); + @Override protected Lexer createLexer(CharStream input) { return new TestLexer(input); @@ -26,7 +26,7 @@ protected ParserRuleContext getEntryContext(TestParser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new TestListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/language-api/src/main/java/de/jplag/SharedTokenType.java b/language-api/src/main/java/de/jplag/SharedTokenType.java index 5374114ec..6866f46a2 100644 --- a/language-api/src/main/java/de/jplag/SharedTokenType.java +++ b/language-api/src/main/java/de/jplag/SharedTokenType.java @@ -11,6 +11,7 @@ public enum SharedTokenType implements TokenType { private final String description; + @Override public String getDescription() { return description; } diff --git a/language-api/src/main/java/de/jplag/options/OptionType.java b/language-api/src/main/java/de/jplag/options/OptionType.java index 04cc24487..aaf677370 100644 --- a/language-api/src/main/java/de/jplag/options/OptionType.java +++ b/language-api/src/main/java/de/jplag/options/OptionType.java @@ -21,6 +21,14 @@ private IntegerType() { } } + static final class BooleanType extends OptionType { + public static final BooleanType INSTANCE = new BooleanType(); + + private BooleanType() { + super(Boolean.class); + } + } + public static StringType string() { return StringType.INSTANCE; } @@ -29,6 +37,10 @@ public static IntegerType integer() { return IntegerType.INSTANCE; } + public static BooleanType bool() { + return BooleanType.INSTANCE; + } + private final Class javaType; private OptionType(Class javaType) { diff --git a/language-api/src/test/java/de/jplag/TokenPrinterTest.java b/language-api/src/test/java/de/jplag/TokenPrinterTest.java index 4cb1bda1d..11c9c33f3 100644 --- a/language-api/src/test/java/de/jplag/TokenPrinterTest.java +++ b/language-api/src/test/java/de/jplag/TokenPrinterTest.java @@ -103,6 +103,7 @@ private enum TestTokenType implements TokenType { private final String description; + @Override public String getDescription() { return description; } 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 62b997c12..46133a743 100644 --- a/language-testutils/src/test/java/de/jplag/testutils/LanguageModuleTest.java +++ b/language-testutils/src/test/java/de/jplag/testutils/LanguageModuleTest.java @@ -1,22 +1,34 @@ package de.jplag.testutils; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import de.jplag.*; +import de.jplag.Language; +import de.jplag.ParsingException; +import de.jplag.SharedTokenType; +import de.jplag.Token; +import de.jplag.TokenPrinter; +import de.jplag.TokenType; import de.jplag.testutils.datacollector.TestData; import de.jplag.testutils.datacollector.TestDataCollector; import de.jplag.testutils.datacollector.TestSourceIgnoredLinesCollector; @@ -71,6 +83,7 @@ public & TokenType> LanguageModuleTest(Language language, Cla */ @ParameterizedTest @MethodSource("sourceCoverageFiles") + @DisplayName("Test that every line leads to at least one token") final void testSourceCoverage(TestData data) throws ParsingException, IOException { List tokens = parseTokens(data); @@ -81,7 +94,7 @@ final void testSourceCoverage(TestData data) throws ParsingException, IOExceptio tokens.stream().map(Token::getLine).forEach(relevantLines::remove); - Assertions.assertTrue(relevantLines.isEmpty(), + assertTrue(relevantLines.isEmpty(), "Test test source " + data.describeTestSource() + " contained uncovered lines:" + System.lineSeparator() + relevantLines); } @@ -101,14 +114,14 @@ final List sourceCoverageFiles() { */ @ParameterizedTest @MethodSource("tokenCoverageFiles") + @DisplayName("Test that every token occurs at least once") final void testTokenCoverage(TestData data) throws ParsingException, IOException { - List foundTokens = extractTokenTypes(data); + List actualTokens = extractTokenTypes(data); List languageTokens = new ArrayList<>(this.languageTokens); - languageTokens.removeAll(foundTokens); + languageTokens.removeAll(actualTokens); - Assertions.assertTrue(languageTokens.isEmpty(), - "Some tokens were not found in " + data.describeTestSource() + System.lineSeparator() + languageTokens); + assertTrue(languageTokens.isEmpty(), "Some tokens were not found in " + data.describeTestSource() + System.lineSeparator() + languageTokens); } /** @@ -120,23 +133,25 @@ final List tokenCoverageFiles() { } /** - * Tests the configured test sources for contained tokens + * Tests the configured test sources for contained tokens. The tokens neither have to occur exclusively nor in the given + * order. * @param test The source to test * @throws ParsingException If the parser throws some error * @throws IOException If any IO Exception occurs */ @ParameterizedTest @MethodSource("testTokensContainedData") + @DisplayName("Test that the specified tokens at least occur") final void testTokensContained(TestDataCollector.TokenListTest test) throws ParsingException, IOException { - List foundTokens = extractTokenTypes(test.data()); - List requiredTokens = new ArrayList<>(test.tokens()); + List actualTokens = extractTokenTypes(test.data()); + List expectedTokens = new ArrayList<>(test.tokens()); - for (TokenType foundToken : foundTokens) { - requiredTokens.remove(foundToken); + for (TokenType foundToken : actualTokens) { + expectedTokens.remove(foundToken); } - Assertions.assertTrue(requiredTokens.isEmpty(), - "Some required tokens were not found in " + test.data().describeTestSource() + System.lineSeparator() + requiredTokens); + assertTrue(expectedTokens.isEmpty(), + "Some expected tokens were not found in " + test.data().describeTestSource() + System.lineSeparator() + expectedTokens); } /** @@ -155,15 +170,22 @@ final List testTokensContainedData() { */ @ParameterizedTest @MethodSource("testTokenSequenceData") + @DisplayName("Test if extracted token sequence matches") final void testTokenSequence(TestDataCollector.TokenListTest test) throws ParsingException, IOException { - List extracted = extractTokenTypes(test.data()); - List required = new ArrayList<>(test.tokens()); - if (required.get(required.size() - 1) != SharedTokenType.FILE_END) { - required.add(SharedTokenType.FILE_END); + List actual = extractTokenTypes(test.data()); + List expected = new ArrayList<>(test.tokens()); + if (expected.get(expected.size() - 1) != SharedTokenType.FILE_END) { + expected.add(SharedTokenType.FILE_END); } + assertTokensMatch(expected, actual, "Extracted token from " + test.data().describeTestSource() + " does not match expected sequence."); + assertIterableEquals(expected, actual); + } - Assertions.assertEquals(required, extracted, - "Extracted token from " + test.data().describeTestSource() + " does not match required sequence."); + /** + * Convenience method for using assertLinesMatch with token lists. + */ + private void assertTokensMatch(List expected, List actual, String message) { + assertLinesMatch(expected.stream().map(Object::toString), actual.stream().map(Object::toString), message); } /** @@ -182,12 +204,13 @@ final List testTokenSequenceData() { */ @ParameterizedTest @MethodSource("getAllTestData") + @DisplayName("Test that the tokens map to ascending line numbers") final void testMonotoneTokenOrder(TestData data) throws ParsingException, IOException { - List extracted = parseTokens(data); + List tokens = parseTokens(data); - for (int i = 0; i < extracted.size() - 2; i++) { - Token first = extracted.get(i); - Token second = extracted.get(i + 1); + for (int i = 0; i < tokens.size() - 2; i++) { + Token first = tokens.get(i); + Token second = tokens.get(i + 1); if (first.getLine() > second.getLine()) { fail(String.format("Invalid token order. Token %s has a higher line number (%s) than token %s (%s).", first.getType(), @@ -204,10 +227,11 @@ final void testMonotoneTokenOrder(TestData data) throws ParsingException, IOExce */ @ParameterizedTest @MethodSource("getAllTestData") + @DisplayName("Test that the last token is the file end token") final void testTokenSequencesEndsWithFileEnd(TestData data) throws ParsingException, IOException { - List extracted = parseTokens(data); + List tokens = parseTokens(data); - Assertions.assertEquals(SharedTokenType.FILE_END, extracted.get(extracted.size() - 1).getType(), + assertEquals(SharedTokenType.FILE_END, tokens.get(tokens.size() - 1).getType(), "Last token in " + data.describeTestSource() + " is not file end."); } diff --git a/language-testutils/src/test/java/de/jplag/testutils/datacollector/FileTestData.java b/language-testutils/src/test/java/de/jplag/testutils/datacollector/FileTestData.java index 7d9043bb9..f2facf796 100644 --- a/language-testutils/src/test/java/de/jplag/testutils/datacollector/FileTestData.java +++ b/language-testutils/src/test/java/de/jplag/testutils/datacollector/FileTestData.java @@ -33,7 +33,7 @@ public String[] getSourceLines() throws IOException { @Override public String describeTestSource() { - return "(File: " + this.file.getPath() + ")"; + return "(File: " + this.file.getName() + ")"; } @Override @@ -53,6 +53,6 @@ public int hashCode() { @Override public String toString() { - return this.file.getPath(); + return this.file.getName(); } } 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 b1abce180..c99a3741d 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 @@ -46,6 +46,18 @@ public TestDataContext testFile(String... fileNames) { return new TestDataContext(data); } + /** + * Adds all files matching a certain type. Returns a {@link TestDataContext}, that can be used to configure various + * tests on the given files. + * @param fileSuffix is the suffix of the files to be added. + * @return The {@link TestDataContext} + */ + public TestDataContext testAllOfType(String fileSuffix) { + Set data = Arrays.stream(testFileLocation.list()).filter(it -> it.endsWith(fileSuffix)) + .map(it -> new File(this.testFileLocation, it)).map(FileTestData::new).collect(Collectors.toSet()); + return new TestDataContext(data); + } + /** * Adds a list of source string to the test data. Returns a {@link TestDataContext}, that can be used to configure * various tests on the given files. @@ -98,6 +110,11 @@ public List getAllTestData() { * @param data The test data */ public record TokenListTest(List tokens, TestData data) { + + @Override + public String toString() { + return data.toString(); // readable test name + } } /** diff --git a/languages/cpp/src/main/java/de/jplag/cpp/CPPTokenType.java b/languages/cpp/src/main/java/de/jplag/cpp/CPPTokenType.java index 312155fa1..e8143b98f 100644 --- a/languages/cpp/src/main/java/de/jplag/cpp/CPPTokenType.java +++ b/languages/cpp/src/main/java/de/jplag/cpp/CPPTokenType.java @@ -66,6 +66,7 @@ public enum CPPTokenType implements TokenType { private final String description; + @Override public String getDescription() { return this.description; } diff --git a/languages/cpp/src/main/java/de/jplag/cpp/experimental/GCCSourceAnalysis.java b/languages/cpp/src/main/java/de/jplag/cpp/experimental/GCCSourceAnalysis.java index 4b9371ff3..52b7bdfcd 100644 --- a/languages/cpp/src/main/java/de/jplag/cpp/experimental/GCCSourceAnalysis.java +++ b/languages/cpp/src/main/java/de/jplag/cpp/experimental/GCCSourceAnalysis.java @@ -24,6 +24,7 @@ public GCCSourceAnalysis() { this.logger = LoggerFactory.getLogger(this.getClass()); } + @Override public boolean isTokenIgnored(de.jplag.cpp.Token token, File file) { String fileName = file.getName(); if (linesToDelete.containsKey(fileName)) { @@ -33,6 +34,7 @@ public boolean isTokenIgnored(de.jplag.cpp.Token token, File file) { return false; } + @Override public void findUnusedVariableLines(Set files) throws InterruptedException { linesToDelete = new HashMap<>(); diff --git a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java index 90ef46b3b..6b2a68c49 100644 --- a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java +++ b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPListener.java @@ -2,10 +2,7 @@ import static de.jplag.cpp2.CPPTokenType.*; -import java.io.File; - import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TokenCollector; import de.jplag.cpp2.grammar.CPP14Parser; import de.jplag.cpp2.grammar.CPP14Parser.*; @@ -15,59 +12,53 @@ *

* Those cases are covered by {@link SimpleTypeSpecifierContext} and {@link SimpleDeclarationContext} */ -public class CPPListener extends AbstractAntlrListener { - /** - * New instance - * @param collector The token collector the token will be added to - * @param currentFile The currently processed file - */ - public CPPListener(TokenCollector collector, File currentFile) { - super(collector, currentFile); +class CPPListener extends AbstractAntlrListener { - mapEnterExit(ClassSpecifierContext.class, UNION_BEGIN, UNION_END, rule -> rule.classHead().Union() != null); - mapEnterExit(ClassSpecifierContext.class, CLASS_BEGIN, CLASS_END, - rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Class() != null); - mapEnterExit(ClassSpecifierContext.class, STRUCT_BEGIN, STRUCT_END, - rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Struct() != null); - mapEnterExit(EnumSpecifierContext.class, ENUM_BEGIN, ENUM_END); + CPPListener() { + visit(ClassSpecifierContext.class, rule -> rule.classHead().Union() != null).map(UNION_BEGIN, UNION_END); + visit(ClassSpecifierContext.class, rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Class() != null) + .map(CLASS_BEGIN, CLASS_END); + visit(ClassSpecifierContext.class, rule -> rule.classHead().classKey() != null && rule.classHead().classKey().Struct() != null) + .map(STRUCT_BEGIN, STRUCT_END); + visit(EnumSpecifierContext.class).map(ENUM_BEGIN, ENUM_END); - mapEnterExit(FunctionDefinitionContext.class, FUNCTION_BEGIN, FUNCTION_END); + visit(FunctionDefinitionContext.class).map(FUNCTION_BEGIN, FUNCTION_END); - mapEnterExit(IterationStatementContext.class, DO_BEGIN, DO_END, rule -> rule.Do() != null); - mapEnterExit(IterationStatementContext.class, FOR_BEGIN, FOR_END, rule -> rule.For() != null); - mapEnterExit(IterationStatementContext.class, WHILE_BEGIN, WHILE_END, rule -> rule.While() != null && rule.Do() == null); + visit(IterationStatementContext.class, rule -> rule.Do() != null).map(DO_BEGIN, DO_END); + visit(IterationStatementContext.class, rule -> rule.For() != null).map(FOR_BEGIN, FOR_END); + visit(IterationStatementContext.class, rule -> rule.While() != null && rule.Do() == null).map(WHILE_BEGIN, WHILE_END); - mapEnterExit(SelectionStatementContext.class, SWITCH_BEGIN, SWITCH_END, rule -> rule.Switch() != null); - mapEnterExit(SelectionStatementContext.class, IF_BEGIN, IF_END, rule -> rule.If() != null); - mapTerminal(CPP14Parser.Else, ELSE); + visit(SelectionStatementContext.class, rule -> rule.Switch() != null).map(SWITCH_BEGIN, SWITCH_END); + visit(SelectionStatementContext.class, rule -> rule.If() != null).map(IF_BEGIN, IF_END); + visit(CPP14Parser.Else).map(ELSE); - mapEnter(LabeledStatementContext.class, CASE, rule -> rule.Case() != null); - mapEnter(LabeledStatementContext.class, DEFAULT, rule -> rule.Default() != null); + visit(LabeledStatementContext.class, rule -> rule.Case() != null).map(CASE); + visit(LabeledStatementContext.class, rule -> rule.Default() != null).map(DEFAULT); - mapEnter(TryBlockContext.class, TRY); - mapEnterExit(HandlerContext.class, CATCH_BEGIN, CATCH_END); + visit(TryBlockContext.class).map(TRY); + visit(HandlerContext.class).map(CATCH_BEGIN, CATCH_END); - mapEnter(JumpStatementContext.class, BREAK, rule -> rule.Break() != null); - mapEnter(JumpStatementContext.class, CONTINUE, rule -> rule.Continue() != null); - mapEnter(JumpStatementContext.class, GOTO, rule -> rule.Goto() != null); - mapEnter(JumpStatementContext.class, RETURN, rule -> rule.Return() != null); + visit(JumpStatementContext.class, rule -> rule.Break() != null).map(BREAK); + visit(JumpStatementContext.class, rule -> rule.Continue() != null).map(CONTINUE); + visit(JumpStatementContext.class, rule -> rule.Goto() != null).map(GOTO); + visit(JumpStatementContext.class, rule -> rule.Return() != null).map(RETURN); - mapEnter(ThrowExpressionContext.class, THROW); + visit(ThrowExpressionContext.class).map(THROW); - mapEnter(NewExpressionContext.class, NEWCLASS, rule -> rule.newInitializer() != null); - mapEnter(NewExpressionContext.class, NEWARRAY, rule -> rule.newInitializer() == null); + visit(NewExpressionContext.class, rule -> rule.newInitializer() != null).map(NEWCLASS); + visit(NewExpressionContext.class, rule -> rule.newInitializer() == null).map(NEWARRAY); - mapEnter(TemplateDeclarationContext.class, GENERIC); + visit(TemplateDeclarationContext.class).map(GENERIC); - mapEnter(AssignmentOperatorContext.class, ASSIGN); - mapEnter(BraceOrEqualInitializerContext.class, ASSIGN, rule -> rule.Assign() != null); - mapEnter(UnaryExpressionContext.class, ASSIGN, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null); + visit(AssignmentOperatorContext.class).map(ASSIGN); + visit(BraceOrEqualInitializerContext.class, rule -> rule.Assign() != null).map(ASSIGN); + visit(UnaryExpressionContext.class, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null).map(ASSIGN); - mapEnter(StaticAssertDeclarationContext.class, STATIC_ASSERT); - mapEnter(EnumeratorDefinitionContext.class, VARDEF); - mapEnterExit(BracedInitListContext.class, BRACED_INIT_BEGIN, BRACED_INIT_END); + visit(StaticAssertDeclarationContext.class).map(STATIC_ASSERT); + visit(EnumeratorDefinitionContext.class).map(VARDEF); + visit(BracedInitListContext.class).map(BRACED_INIT_BEGIN, BRACED_INIT_END); - mapEnter(SimpleTypeSpecifierContext.class, VARDEF, rule -> { + visit(SimpleTypeSpecifierContext.class, rule -> { if (hasAncestor(rule, MemberdeclarationContext.class, FunctionDefinitionContext.class)) { return true; } @@ -80,23 +71,23 @@ public CPPListener(TokenCollector collector, File currentFile) { } return false; - }); + }).map(VARDEF); - mapEnter(SimpleDeclarationContext.class, APPLY, rule -> { + visit(SimpleDeclarationContext.class, rule -> { if (!hasAncestor(rule, FunctionBodyContext.class)) { return false; } NoPointerDeclaratorContext noPointerDecl = getDescendant(rule, NoPointerDeclaratorContext.class); return noPointerInFunctionCallContext(noPointerDecl); - }); + }).map(APPLY); - mapEnter(InitDeclaratorContext.class, APPLY, rule -> rule.initializer() != null && rule.initializer().LeftParen() != null); - mapEnter(ParameterDeclarationContext.class, VARDEF); - mapEnter(ConditionalExpressionContext.class, QUESTIONMARK, rule -> rule.Question() != null); + visit(InitDeclaratorContext.class, rule -> rule.initializer() != null && rule.initializer().LeftParen() != null).map(APPLY); + visit(ParameterDeclarationContext.class).map(VARDEF); + visit(ConditionalExpressionContext.class, rule -> rule.Question() != null).map(QUESTIONMARK); - mapEnter(PostfixExpressionContext.class, APPLY, rule -> rule.LeftParen() != null); - mapEnter(PostfixExpressionContext.class, ASSIGN, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null); + visit(PostfixExpressionContext.class, rule -> rule.LeftParen() != null).map(APPLY); + visit(PostfixExpressionContext.class, rule -> rule.PlusPlus() != null || rule.MinusMinus() != null).map(ASSIGN); } /** diff --git a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java index fc73e2b11..c876023ee 100644 --- a/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java +++ b/languages/cpp2/src/main/java/de/jplag/cpp2/CPPParserAdapter.java @@ -1,7 +1,5 @@ package de.jplag.cpp2; -import java.io.File; - import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Lexer; @@ -10,7 +8,6 @@ import de.jplag.AbstractParser; import de.jplag.antlr.AbstractAntlrListener; import de.jplag.antlr.AbstractAntlrParserAdapter; -import de.jplag.antlr.TokenCollector; import de.jplag.cpp2.grammar.CPP14Lexer; import de.jplag.cpp2.grammar.CPP14Parser; @@ -18,6 +15,8 @@ * The adapter between {@link AbstractParser} and the ANTLR based parser of this language module. */ public class CPPParserAdapter extends AbstractAntlrParserAdapter { + private static final CPPListener listener = new CPPListener(); + @Override protected Lexer createLexer(CharStream input) { return new CPP14Lexer(input); @@ -34,7 +33,7 @@ protected ParserRuleContext getEntryContext(CPP14Parser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new CPPListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/languages/csharp/src/main/java/de/jplag/csharp/CSharpTokenType.java b/languages/csharp/src/main/java/de/jplag/csharp/CSharpTokenType.java index 30bf18499..f8d6a4347 100644 --- a/languages/csharp/src/main/java/de/jplag/csharp/CSharpTokenType.java +++ b/languages/csharp/src/main/java/de/jplag/csharp/CSharpTokenType.java @@ -71,6 +71,7 @@ public enum CSharpTokenType implements TokenType { private final String description; + @Override public String getDescription() { return this.description; } diff --git a/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java b/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java index b0ec9f44f..0911fd455 100644 --- a/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java +++ b/languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java @@ -22,6 +22,7 @@ import de.jplag.Token; import de.jplag.TokenPrinter; import de.jplag.TokenType; +import de.jplag.emf.EmfLanguage; import de.jplag.testutils.FileUtil; import de.jplag.testutils.TokenUtils; @@ -48,7 +49,7 @@ void testBookstoreMetamodels() throws ParsingException { List testFiles = Arrays.stream(TEST_SUBJECTS).map(path -> new File(BASE_PATH.toFile(), path)).toList(); List result = language.parse(new HashSet<>(testFiles)); List tokenTypes = result.stream().map(Token::getType).toList(); - logger.debug(TokenPrinter.printTokens(result, baseDirectory, Optional.of(DynamicEmfLanguage.VIEW_FILE_SUFFIX))); + logger.debug(TokenPrinter.printTokens(result, baseDirectory, Optional.of(EmfLanguage.VIEW_FILE_SUFFIX))); logger.info("parsed token types: " + tokenTypes.stream().map(TokenType::getDescription).toList()); assertEquals(94, tokenTypes.size()); assertEquals(7, new HashSet<>(tokenTypes.stream().filter(DynamicMetamodelTokenType.class::isInstance).toList()).size()); @@ -64,6 +65,6 @@ void testBookstoreMetamodels() throws ParsingException { @AfterEach public void tearDown() { - FileUtil.clearFiles(new File(BASE_PATH.toString()), DynamicEmfLanguage.VIEW_FILE_SUFFIX); + FileUtil.clearFiles(new File(BASE_PATH.toString()), EmfLanguage.VIEW_FILE_SUFFIX); } } diff --git a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java index 036359080..ec2b055dc 100644 --- a/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java +++ b/languages/emf-metamodel/src/main/java/de/jplag/emf/util/EmfaticModelView.java @@ -9,6 +9,8 @@ import org.eclipse.emf.ecore.EEnumLiteral; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EParameter; import org.eclipse.emf.ecore.ETypedElement; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil.Copier; @@ -27,9 +29,11 @@ */ public final class EmfaticModelView extends AbstractModelView { // The following regular expressions match keywords of the Emfatic syntax: + private static final String PACKAGE_REGEX = "package\\s+\\S+;"; private static final String TYPE_KEYWORD_REGEX = "(package |class |datatype |enum )"; private static final String FEATURE_KEYWORD_REGEX = "(.*attr .*|op .*|.*ref .*|.*val .*).*"; private static final String TYPE_SUFFIX_REGEX = "(;| extends| \\{)"; + private static final String LINE_SUFFIX_REGEX = ";"; private static final char CLOSING_CHAR = '}'; private static final String ANYTHING_REGEX = ".*"; @@ -40,6 +44,7 @@ public final class EmfaticModelView extends AbstractModelView { private final Copier modelCopier; // Allows to trace between original and copied elements private int lastLineIndex; // last line given to a token + private final int rootPackageIndex; /** * Creates an Emfatic view for a metamodel. @@ -57,6 +62,7 @@ public EmfaticModelView(File file, Resource modelResource) throws ParsingExcepti Resource copiedResource = EMFUtil.copyModel(modelResource, modelCopier); replaceElementNamesWithHashes(copiedResource); hashedLines = generateEmfaticCode(new StringBuilder(), copiedResource); + rootPackageIndex = findIndexOfRootPackage(hashedLines); } /** @@ -108,6 +114,18 @@ private final List generateEmfaticCode(StringBuilder builder, Resource m return builder.toString().lines().toList(); } + /** + * Calculates the index of the root package declaration, as it has unique syntax in Emfatic. + */ + private final int findIndexOfRootPackage(List lines) { + for (int index = 0; index < lines.size(); index++) { + if (lines.get(index).matches(PACKAGE_REGEX)) { + return index; + } + } + return -1; + } + /** * Calculates the line index of a metamodel token from the emfatic code. If it cannot be found, the last index is used. */ @@ -135,7 +153,7 @@ private int calculateLineIndexOf(MetamodelToken token) { */ private int findEndIndexOf(int declarationIndex) { int indentation = indentationOf(lines.get(declarationIndex)); - if (declarationIndex > 1) { // exception for top level package + if (declarationIndex > rootPackageIndex) { // exception for top level package for (int i = declarationIndex + 1; i < lines.size(); i++) { String nextLine = lines.get(i); if (nextLine.length() > indentation && CLOSING_CHAR == nextLine.charAt(indentation)) { @@ -186,7 +204,8 @@ private int findLineIndexOf(ENamedElement element) { * Checks if a line (with leading whitespace removed) contains an element based on the elements hash. */ private boolean isDeclaration(ENamedElement element, String hash, String line) { - return isStructuralFeature(element, hash, line) || isEnumLiteral(element, hash, line) || isType(hash, line); + return isStructuralFeature(element, hash, line) || isTypedElement(element, hash, line) || isEnumLiteral(element, hash, line) + || isType(hash, line); } private boolean isType(String hash, String line) { @@ -197,8 +216,12 @@ private boolean isEnumLiteral(ENamedElement element, String hash, String line) { return element instanceof EEnumLiteral && line.matches(hash + ANYTHING_REGEX); } + private boolean isTypedElement(ENamedElement element, String hash, String line) { + return element instanceof EOperation && element instanceof EParameter && line.matches(FEATURE_KEYWORD_REGEX + hash + ANYTHING_REGEX); + } + private boolean isStructuralFeature(ENamedElement element, String hash, String line) { - return element instanceof ETypedElement && line.matches(FEATURE_KEYWORD_REGEX + hash + ANYTHING_REGEX); + return element instanceof ETypedElement && line.matches(FEATURE_KEYWORD_REGEX + hash + LINE_SUFFIX_REGEX); } } diff --git a/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java b/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java index c039b25fb..699ee2af7 100644 --- a/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java +++ b/languages/emf-metamodel/src/test/java/de/jplag/emf/AbstractEmfTest.java @@ -23,7 +23,7 @@ */ public abstract class AbstractEmfTest { - protected static final Path BASE_PATH = Path.of("src", "test", "resources", "de", "jplag", "models"); + protected static final Path BASE_PATH = Path.of("src", "test", "resources", "de", "jplag", "emf"); protected static final String[] TEST_SUBJECTS = {"bookStore.ecore", // base metamodel "bookStoreExtended.ecore", // extended version of base metamodel diff --git a/languages/emf-metamodel/src/test/java/de/jplag/emf/EmfLanguageTest.java b/languages/emf-metamodel/src/test/java/de/jplag/emf/EmfLanguageTest.java new file mode 100644 index 000000000..d3ee82d25 --- /dev/null +++ b/languages/emf-metamodel/src/test/java/de/jplag/emf/EmfLanguageTest.java @@ -0,0 +1,42 @@ +package de.jplag.emf; + +import static de.jplag.emf.MetamodelTokenType.ATTRIBUTE; +import static de.jplag.emf.MetamodelTokenType.CLASS; +import static de.jplag.emf.MetamodelTokenType.CLASS_END; +import static de.jplag.emf.MetamodelTokenType.CONTAINMENT_MULT; +import static de.jplag.emf.MetamodelTokenType.PACKAGE; +import static de.jplag.emf.MetamodelTokenType.PACKAGE_END; + +import org.junit.jupiter.api.AfterEach; + +import de.jplag.testutils.FileUtil; +import de.jplag.testutils.LanguageModuleTest; +import de.jplag.testutils.datacollector.TestDataCollector; +import de.jplag.testutils.datacollector.TestSourceIgnoredLinesCollector; + +/** + * Basic EMF test that mainly serves the purpose of checking ascending line indices for the tokens with Emfatic views. + */ +public class EmfLanguageTest extends LanguageModuleTest { + + public EmfLanguageTest() { + super(new EmfLanguage(), MetamodelTokenType.class); + } + + @Override + protected void collectTestData(TestDataCollector collector) { + collector.testAllOfType(EmfLanguage.FILE_ENDING).testContainedTokens(PACKAGE, PACKAGE_END, CLASS, CLASS_END, ATTRIBUTE, CONTAINMENT_MULT); + + } + + @Override + protected void configureIgnoredLines(TestSourceIgnoredLinesCollector collector) { + // None, does not really apply for modeling artifacts. + } + + @AfterEach + protected void tearDown() { + FileUtil.clearFiles(getTestFileLocation(), EmfLanguage.VIEW_FILE_SUFFIX); // clean up the view files. + } + +} diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/models/bookStore.ecore b/languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStore.ecore similarity index 100% rename from languages/emf-metamodel/src/test/resources/de/jplag/models/bookStore.ecore rename to languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStore.ecore diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/models/bookStoreExtended.ecore b/languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStoreExtended.ecore similarity index 100% rename from languages/emf-metamodel/src/test/resources/de/jplag/models/bookStoreExtended.ecore rename to languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStoreExtended.ecore diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/models/bookStoreExtendedRefactor.ecore b/languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStoreExtendedRefactor.ecore similarity index 100% rename from languages/emf-metamodel/src/test/resources/de/jplag/models/bookStoreExtendedRefactor.ecore rename to languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStoreExtendedRefactor.ecore diff --git a/languages/emf-metamodel/src/test/resources/de/jplag/models/bookStoreRenamed.ecore b/languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStoreRenamed.ecore similarity index 100% rename from languages/emf-metamodel/src/test/resources/de/jplag/models/bookStoreRenamed.ecore rename to languages/emf-metamodel/src/test/resources/de/jplag/emf/bookStoreRenamed.ecore diff --git a/languages/golang/src/main/java/de/jplag/golang/GoTokenType.java b/languages/golang/src/main/java/de/jplag/golang/GoTokenType.java index b93ced02f..9cf73ee1a 100644 --- a/languages/golang/src/main/java/de/jplag/golang/GoTokenType.java +++ b/languages/golang/src/main/java/de/jplag/golang/GoTokenType.java @@ -92,6 +92,7 @@ public enum GoTokenType implements TokenType { private final String description; + @Override public String getDescription() { return this.description; } diff --git a/languages/java/src/main/java/de/jplag/java/JavaTokenType.java b/languages/java/src/main/java/de/jplag/java/JavaTokenType.java index cfad295ab..8e5ed5fe5 100644 --- a/languages/java/src/main/java/de/jplag/java/JavaTokenType.java +++ b/languages/java/src/main/java/de/jplag/java/JavaTokenType.java @@ -12,12 +12,8 @@ public enum JavaTokenType implements TokenType { J_VARDEF("VARDEF"), // check J_SYNC_BEGIN("SYNC{"), // check J_SYNC_END("}SYNC"), // check - J_DO_BEGIN("DO{"), // check - J_DO_END("}DO"), // check - J_WHILE_BEGIN("WHILE{"), // check - J_WHILE_END("}WHILE"), // check - J_FOR_BEGIN("FOR{"), // check - J_FOR_END("}FOR"), // check + J_LOOP_BEGIN("LOOP{"), // check + J_LOOP_END("}LOOP"), // check J_SWITCH_BEGIN("SWITCH{"), // check J_SWITCH_END("}SWITCH"), // check J_CASE("CASE"), // check @@ -80,6 +76,7 @@ public enum JavaTokenType implements TokenType { private final String description; + @Override public String getDescription() { return this.description; } 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 6dfca328a..c026b975a 100644 --- a/languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java +++ b/languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java @@ -66,9 +66,9 @@ final class TokenGeneratingTreeScanner extends TreeScanner { private final SourcePositions positions; private final CompilationUnitTree ast; - private List parsingExceptions = new ArrayList<>(); + private final List parsingExceptions = new ArrayList<>(); - private VariableRegistry variableRegistry; + private final VariableRegistry variableRegistry; private static final Set IMMUTABLES = Set.of( // from https://medium.com/@bpnorlander/java-understanding-primitive-types-and-wrapper-objects-a6798fb2afe9 @@ -216,10 +216,10 @@ public Void visitSynchronized(SynchronizedTree node, Void unused) { @Override public Void visitDoWhileLoop(DoWhileLoopTree node, Void unused) { long start = positions.getStartPosition(ast, node); - long end = positions.getEndPosition(ast, node) - 1; - addToken(JavaTokenType.J_DO_BEGIN, start, 2, CodeSemantics.createLoopBegin()); + long end = positions.getEndPosition(ast, node.getStatement()) - 1; + addToken(JavaTokenType.J_LOOP_BEGIN, start, 2, CodeSemantics.createLoopBegin()); scan(node.getStatement(), null); - addToken(JavaTokenType.J_DO_END, end, 1, CodeSemantics.createLoopEnd()); + addToken(JavaTokenType.J_LOOP_END, end, 1, CodeSemantics.createLoopEnd()); scan(node.getCondition(), null); return null; } @@ -228,9 +228,9 @@ public Void visitDoWhileLoop(DoWhileLoopTree node, Void unused) { public Void visitWhileLoop(WhileLoopTree node, Void unused) { long start = positions.getStartPosition(ast, node); long end = positions.getEndPosition(ast, node) - 1; - addToken(JavaTokenType.J_WHILE_BEGIN, start, 5, CodeSemantics.createLoopBegin()); + addToken(JavaTokenType.J_LOOP_BEGIN, start, 5, CodeSemantics.createLoopBegin()); super.visitWhileLoop(node, null); - addToken(JavaTokenType.J_WHILE_END, end, 1, CodeSemantics.createLoopEnd()); + addToken(JavaTokenType.J_LOOP_END, end, 1, CodeSemantics.createLoopEnd()); return null; } @@ -239,9 +239,9 @@ public Void visitForLoop(ForLoopTree node, Void unused) { variableRegistry.enterLocalScope(); long start = positions.getStartPosition(ast, node); long end = positions.getEndPosition(ast, node) - 1; - addToken(JavaTokenType.J_FOR_BEGIN, start, 3, CodeSemantics.createLoopBegin()); + addToken(JavaTokenType.J_LOOP_BEGIN, start, 3, CodeSemantics.createLoopBegin()); super.visitForLoop(node, null); - addToken(JavaTokenType.J_FOR_END, end, 1, CodeSemantics.createLoopEnd()); + addToken(JavaTokenType.J_LOOP_END, end, 1, CodeSemantics.createLoopEnd()); variableRegistry.exitLocalScope(); return null; } @@ -251,9 +251,9 @@ public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void unused) { variableRegistry.enterLocalScope(); long start = positions.getStartPosition(ast, node); long end = positions.getEndPosition(ast, node) - 1; - addToken(JavaTokenType.J_FOR_BEGIN, start, 3, CodeSemantics.createLoopBegin()); + addToken(JavaTokenType.J_LOOP_BEGIN, start, 3, CodeSemantics.createLoopBegin()); super.visitEnhancedForLoop(node, null); - addToken(JavaTokenType.J_FOR_END, end, 1, CodeSemantics.createLoopEnd()); + addToken(JavaTokenType.J_LOOP_END, end, 1, CodeSemantics.createLoopEnd()); variableRegistry.exitLocalScope(); return null; } @@ -292,7 +292,6 @@ public Void visitTry(TryTree node, Void unused) { scan(node.getResources(), null); scan(node.getBlock(), null); long end = positions.getEndPosition(ast, node); - addToken(JavaTokenType.J_TRY_END, end, 1, CodeSemantics.createControl()); scan(node.getCatches(), null); if (node.getFinallyBlock() != null) { start = positions.getStartPosition(ast, node.getFinallyBlock()); @@ -301,6 +300,7 @@ public Void visitTry(TryTree node, Void unused) { end = positions.getEndPosition(ast, node.getFinallyBlock()); addToken(JavaTokenType.J_FINALLY_END, end, 1, CodeSemantics.createControl()); } + addToken(JavaTokenType.J_TRY_END, end, 1, CodeSemantics.createControl()); return null; } @@ -372,7 +372,7 @@ public Void visitThrow(ThrowTree node, Void unused) { @Override public Void visitNewClass(NewClassTree node, Void unused) { long start = positions.getStartPosition(ast, node); - if (node.getTypeArguments().size() > 0) { + if (!node.getTypeArguments().isEmpty()) { addToken(JavaTokenType.J_GENERIC, start, 3 + node.getIdentifier().toString().length(), new CodeSemantics()); } addToken(JavaTokenType.J_NEWCLASS, start, 3, new CodeSemantics()); diff --git a/languages/java/src/test/java/de/jplag/java/AbstractJavaLanguageTest.java b/languages/java/src/test/java/de/jplag/java/AbstractJavaLanguageTest.java deleted file mode 100644 index 77a25c789..000000000 --- a/languages/java/src/test/java/de/jplag/java/AbstractJavaLanguageTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.jplag.java; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.File; -import java.nio.file.Path; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import de.jplag.ParsingException; -import de.jplag.Token; -import de.jplag.TokenPrinter; -import de.jplag.TokenType; - -/** - * Basic test class for testing the Java language module. - */ -public abstract class AbstractJavaLanguageTest { - - private static final Path BASE_PATH = Path.of("src", "test", "resources", "java"); - private static final String LOG_MESSAGE = "Tokens of {}: {}"; - private final Logger logger = LoggerFactory.getLogger(JavaBlockTest.class); - private de.jplag.Language language; - protected File baseDirectory; - - /** - * Sets up the base directory and the language module. - */ - @BeforeEach - void setUp() { - language = new JavaLanguage(); - baseDirectory = BASE_PATH.toFile(); - assertTrue(baseDirectory.exists(), "Could not find base directory!"); - } - - /** - * Parses a java file in the {@link AbstractJavaLanguageTest#baseDirectory} and returns the list of token types. - * @param fileName is the name of the file to parse. - * @return the token types. - * @throws ParsingException if parsing fails. - */ - protected List parseJavaFile(String fileName) throws ParsingException { - List parsedTokens = language.parse(Set.of(new File(baseDirectory, fileName))); - List tokenTypes = parsedTokens.stream().map(Token::getType).toList(); - logger.info(LOG_MESSAGE, fileName, tokenTypes); - logger.info(TokenPrinter.printTokens(parsedTokens, BASE_PATH.toAbsolutePath().toFile())); - return tokenTypes; - } - -} \ No newline at end of file diff --git a/languages/java/src/test/java/de/jplag/java/JavaBlockTest.java b/languages/java/src/test/java/de/jplag/java/JavaBlockTest.java deleted file mode 100644 index 57f52c704..000000000 --- a/languages/java/src/test/java/de/jplag/java/JavaBlockTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.jplag.java; - -import static org.junit.jupiter.api.Assertions.assertIterableEquals; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import de.jplag.ParsingException; - -/** - * Test cases regarding the extraction from implicit vs. explicit blocks in Java code. - */ -class JavaBlockTest extends AbstractJavaLanguageTest { - @ParameterizedTest - @MethodSource("provideClassPairs") - @DisplayName("Test pairs of classes with explicit vs. implicit blocks.") - void testJavaClassPair(String fileName1, String fileName2) throws ParsingException { - assertIterableEquals(parseJavaFile(fileName1), parseJavaFile(fileName2)); - } - - /** - * Argument source for the test case {@link testJavaClassPair(String, String)). - */ - private static Stream provideClassPairs() { - return Stream.of(Arguments.of("IfWithBraces.java", "IfWithoutBraces.java"), // just if conditions - Arguments.of("Verbose.java", "Compact.java")); // complex case with different blocks - } - -} diff --git a/languages/java/src/test/java/de/jplag/java/JavaIfElseTest.java b/languages/java/src/test/java/de/jplag/java/JavaIfElseTest.java deleted file mode 100644 index 62e8be0a5..000000000 --- a/languages/java/src/test/java/de/jplag/java/JavaIfElseTest.java +++ /dev/null @@ -1,39 +0,0 @@ - -package de.jplag.java; - -import static org.junit.jupiter.api.Assertions.assertIterableEquals; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import de.jplag.ParsingException; - -/** - * Test cases regarding the extraction from if and else conditions. - */ -class JavaIfElseTest extends AbstractJavaLanguageTest { - private static final String IF_ELSE_IF = "IfElseIf.java"; - private static final String IF_IF = "IfIf.java"; - private static final String IF_ELSE = "IfElse.java"; - - @ParameterizedTest - @MethodSource("provideClassPairs") - @DisplayName("Test difference between if-else, if-if and if-else-if.") - void testJavaClassPair(String fileName1, String fileName2) throws ParsingException { - assertIterableEquals(parseJavaFile(fileName1), parseJavaFile(fileName2)); - } - - /** - * Argument source for the test case {@link testJavaClassPair(String, String)). - */ - private static Stream provideClassPairs() { - return Stream.of(Arguments.of(IF_ELSE, IF_IF), // if instead of else - Arguments.of(IF_ELSE, IF_ELSE_IF),// add if to else - Arguments.of(IF_ELSE_IF, IF_IF)); // removal of else - } - -} diff --git a/languages/java/src/test/java/de/jplag/java/JavaLanguageTest.java b/languages/java/src/test/java/de/jplag/java/JavaLanguageTest.java new file mode 100644 index 000000000..51e36d7c2 --- /dev/null +++ b/languages/java/src/test/java/de/jplag/java/JavaLanguageTest.java @@ -0,0 +1,49 @@ +package de.jplag.java; + +import static de.jplag.java.JavaTokenType.*; + +import de.jplag.testutils.LanguageModuleTest; +import de.jplag.testutils.datacollector.TestDataCollector; +import de.jplag.testutils.datacollector.TestSourceIgnoredLinesCollector; + +public class JavaLanguageTest extends LanguageModuleTest { + public JavaLanguageTest() { + super(new JavaLanguage(), JavaTokenType.class); + } + + @Override + protected void collectTestData(TestDataCollector collector) { + // Test cases regarding the extraction from if and else conditions. + collector.testFile("IfElse.java", "IfIf.java", "IfElseIf.java").testSourceCoverage().testTokenSequence(J_IMPORT, J_CLASS_BEGIN, + J_METHOD_BEGIN, J_VARDEF, J_IF_BEGIN, J_THROW, J_NEWCLASS, J_IF_END, J_IF_BEGIN, J_APPLY, J_APPLY, J_IF_END, J_METHOD_END, + J_CLASS_END); + + // Test cases regarding the extraction from implicit vs. explicit blocks in Java code. + collector.testFile("IfWithBraces.java", "IfWithoutBraces.java").testSourceCoverage().testTokenSequence(J_PACKAGE, J_IMPORT, J_CLASS_BEGIN, + J_METHOD_BEGIN, J_VARDEF, J_IF_BEGIN, J_THROW, J_NEWCLASS, J_IF_END, J_IF_BEGIN, J_APPLY, J_APPLY, J_IF_END, J_IF_BEGIN, J_APPLY, + J_IF_END, J_METHOD_END, J_CLASS_END); + + collector.testFile("Verbose.java", "Compact.java").testSourceCoverage().testTokenSequence(J_PACKAGE, J_IMPORT, J_CLASS_BEGIN, J_METHOD_BEGIN, + J_VARDEF, J_VARDEF, J_IF_BEGIN, J_APPLY, J_RETURN, J_IF_END, J_VARDEF, J_LOOP_BEGIN, J_VARDEF, J_APPLY, J_ASSIGN, J_IF_BEGIN, J_APPLY, + J_APPLY, J_ASSIGN, J_IF_END, J_LOOP_END, J_IF_BEGIN, J_APPLY, J_ASSIGN, J_IF_END, J_IF_BEGIN, J_APPLY, J_APPLY, J_ASSIGN, J_IF_END, + J_RETURN, J_METHOD_END, J_CLASS_END); + + // Test difference between try block and try-with-resource block. + collector.testFile("Try.java", "TryWithResource.java").testSourceCoverage().testTokenSequence(J_PACKAGE, J_IMPORT, J_IMPORT, J_IMPORT, + J_CLASS_BEGIN, J_METHOD_BEGIN, J_VARDEF, J_APPLY, J_NEWCLASS, J_METHOD_END, J_METHOD_BEGIN, J_VARDEF, J_VARDEF, J_TRY_BEGIN, J_VARDEF, + J_ASSIGN, J_NEWCLASS, J_NEWCLASS, J_LOOP_BEGIN, J_APPLY, J_APPLY, J_APPLY, J_LOOP_END, J_CATCH_BEGIN, J_VARDEF, J_APPLY, J_CATCH_END, + J_FINALLY_BEGIN, J_IF_BEGIN, J_APPLY, J_IF_END, J_FINALLY_END, J_TRY_END, J_METHOD_END, J_CLASS_END); + + collector.testFile("CLI.java").testSourceCoverage().testContainedTokens(J_TRY_END, J_IMPORT, J_VARDEF, J_LOOP_BEGIN, J_ARRAY_INIT_BEGIN, + J_IF_BEGIN, J_CATCH_END, J_COND, J_ARRAY_INIT_END, J_METHOD_BEGIN, J_TRY_BEGIN, J_CLASS_END, J_RETURN, J_ASSIGN, J_METHOD_END, + J_IF_END, J_CLASS_BEGIN, J_NEWARRAY, J_PACKAGE, J_APPLY, J_LOOP_END, J_THROW, J_NEWCLASS, J_CATCH_BEGIN); + } + + @Override + protected void configureIgnoredLines(TestSourceIgnoredLinesCollector collector) { + collector.ignoreLinesByPrefix("//"); + collector.ignoreMultipleLines("/*", "*/"); + collector.ignoreLinesByPrefix("})"); + collector.ignoreByCondition(line -> line.contains("else") && !line.contains("if")); + } +} diff --git a/languages/java/src/test/java/de/jplag/java/JavaTryTest.java b/languages/java/src/test/java/de/jplag/java/JavaTryTest.java deleted file mode 100644 index c5ee683d9..000000000 --- a/languages/java/src/test/java/de/jplag/java/JavaTryTest.java +++ /dev/null @@ -1,20 +0,0 @@ - -package de.jplag.java; - -import static org.junit.jupiter.api.Assertions.assertIterableEquals; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import de.jplag.ParsingException; - -/** - * Test cases regarding the extraction from try vs. try with resource. - */ -class JavaTryTest extends AbstractJavaLanguageTest { - @Test - @DisplayName("Test difference between try block and try-with-resource block.") - void testJavaClassPair() throws ParsingException { - assertIterableEquals(parseJavaFile("Try.java"), parseJavaFile("TryWithResource.java")); - } -} diff --git a/languages/java/src/test/resources/de/jplag/java/CLI.java b/languages/java/src/test/resources/de/jplag/java/CLI.java new file mode 100644 index 000000000..a019fe54c --- /dev/null +++ b/languages/java/src/test/resources/de/jplag/java/CLI.java @@ -0,0 +1,220 @@ +package de.jplag.cli; + +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST; + +import java.io.File; +import java.security.SecureRandom; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.jplag.JPlag; +import de.jplag.JPlagResult; +import de.jplag.Language; +import de.jplag.cli.logger.CollectedLoggerFactory; +import de.jplag.clustering.ClusteringOptions; +import de.jplag.clustering.Preprocessing; +import de.jplag.exceptions.ExitException; +import de.jplag.options.JPlagOptions; +import de.jplag.options.LanguageOption; +import de.jplag.options.LanguageOptions; +import de.jplag.reporting.reportobject.ReportObjectFactory; + +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.ParseResult; + +/** + * Command line interface class, allows using via command line. + * + * @see CLI#main(String[]) + */ +public final class CLI { + + private static final Logger logger = LoggerFactory.getLogger(CLI.class); + + private static final Random RANDOM = new SecureRandom(); + + private static final String CREDITS = "Created by IPD Tichy, Guido Malpohl, and others. JPlag logo designed by Sandro Koch. Currently maintained by Sebastian Hahner and Timur Saglam."; + + private static final String[] DESCRIPTIONS = {"Detecting Software Plagiarism", "Software-Archaeological Playground", "Since 1996", "Scientifically Published", "Maintained by SDQ", "RIP Structure and Table", "What else?", "You have been warned!", "Since Java 1.0", "More Abstract than Tree", "Students Nightmare", "No, changing variable names does not work", "The tech is out there!", "Developed by plagiarism experts."}; + + private final CommandLine commandLine; + private final CliOptions options; + + private static final String IMPOSSIBLE_EXCEPTION = "This should not have happened." + " Please create an issue on github (https://github.com/jplag/JPlag/issues) with the entire output."; + + /** + * Main class for using JPlag via the CLI. + * + * @param args are the CLI arguments that will be passed to JPlag. + */ + public static void main(String[] args) { + try { + logger.debug("Your version of JPlag is {}", JPlag.JPLAG_VERSION); + + CLI cli = new CLI(); + + ParseResult parseResult = cli.parseOptions(args); + + if (!parseResult.isUsageHelpRequested() && !(parseResult.subcommand() != null && parseResult.subcommand().isUsageHelpRequested())) { + JPlagOptions options = cli.buildOptionsFromArguments(parseResult); + JPlagResult result = JPlag.run(options); + ReportObjectFactory reportObjectFactory = new ReportObjectFactory(); + reportObjectFactory.createAndSaveReport(result, cli.getResultFolder()); + } + } catch (ExitException exception) { + logger.error(exception.getMessage()); // do not pass exception here to keep log clean + finalizeLogger(); + System.exit(1); + } + } + + /** + * Creates a new instance + */ + public CLI() { + this.options = new CliOptions(); + this.commandLine = new CommandLine(options); + + this.commandLine.getHelpSectionMap().put(SECTION_KEY_OPTION_LIST, help -> help.optionList().lines().map(it -> { + if (it.startsWith(" -")) { + return " " + it; + } else { + return it; + } + }).collect(Collectors.joining(System.lineSeparator()))); + + buildSubcommands().forEach(commandLine::addSubcommand); + + this.commandLine.getHelpSectionMap().put(SECTION_KEY_FOOTER, help -> generateDescription()); + this.commandLine.setAllowSubcommandsAsOptionParameters(true); + } + + private List buildSubcommands() { + return LanguageLoader.getAllAvailableLanguages().values().stream().map(language -> { + CommandSpec command = CommandSpec.create().name(language.getIdentifier()); + + for (LanguageOption option : language.getOptions().getOptionsAsList()) { + command.addOption(OptionSpec.builder(option.getNameAsUnixParameter()).type(option.getType().getJavaType()) + .description(option.getDescription()).build()); + } + command.mixinStandardHelpOptions(true); + command.addPositional( + CommandLine.Model.PositionalParamSpec.builder().type(List.class).auxiliaryTypes(File.class).hidden(true).required(false).build()); + + return command; + }).toList(); + } + + /** + * Parses the options from the given command line arguments. Also prints help pages when requested. + * + * @param args The command line arguments + * @return the parse result generated by picocli + */ + public ParseResult parseOptions(String... args) throws CliException { + try { + ParseResult result = commandLine.parseArgs(args); + if (result.isUsageHelpRequested() || (result.subcommand() != null && result.subcommand().isUsageHelpRequested())) { + commandLine.getExecutionStrategy().execute(result); + } + return result; + } catch (CommandLine.PicocliException e) { + throw new CliException("Error during parsing", e); + } + } + + private static void finalizeLogger() { + ILoggerFactory factory = LoggerFactory.getILoggerFactory(); + if (!(factory instanceof CollectedLoggerFactory collectedLoggerFactory)) { + return; + } + collectedLoggerFactory.finalizeInstances(); + } + + /** + * Builds an options instance from parsed options. + * + * @return the newly built options + */ + public JPlagOptions buildOptionsFromArguments(ParseResult parseResult) throws CliException { + Set submissionDirectories = new HashSet<>(List.of(this.options.rootDirectory)); + Set oldSubmissionDirectories = Set.of(this.options.oldDirectories); + List suffixes = List.of(this.options.advanced.suffixes); + submissionDirectories.addAll(List.of(this.options.newDirectories)); + + if (parseResult.subcommand() != null && parseResult.subcommand().hasMatchedPositional(0)) { + submissionDirectories.addAll(parseResult.subcommand().matchedPositional(0).getValue()); + } + + ClusteringOptions clusteringOptions = getClusteringOptions(this.options); + + JPlagOptions jPlagOptions = new JPlagOptions(loadLanguage(parseResult), this.options.minTokenMatch, submissionDirectories, oldSubmissionDirectories, null, this.options.advanced.subdirectory, suffixes, this.options.advanced.exclusionFileName, JPlagOptions.DEFAULT_SIMILARITY_METRIC, this.options.advanced.similarityThreshold, this.options.shownComparisons, clusteringOptions, this.options.advanced.debug); + + String baseCodePath = this.options.baseCode; + File baseCodeDirectory = baseCodePath == null ? null : new File(baseCodePath); + if (baseCodeDirectory == null || baseCodeDirectory.exists()) { + return jPlagOptions.withBaseCodeSubmissionDirectory(baseCodeDirectory); + } else { + logger.warn("Using legacy partial base code API. Please migrate to new full path base code API."); + return jPlagOptions.withBaseCodeSubmissionName(baseCodePath); + } + } + + private Language loadLanguage(ParseResult result) throws CliException { + if (result.subcommand() != null) { + ParseResult subcommandResult = result.subcommand(); + Language language = LanguageLoader.getLanguage(subcommandResult.commandSpec().name()) + .orElseThrow(() -> new CliException(IMPOSSIBLE_EXCEPTION)); + LanguageOptions languageOptions = language.getOptions(); + languageOptions.getOptionsAsList().forEach(option -> { + if (subcommandResult.hasMatchedOption(option.getNameAsUnixParameter())) { + option.setValue(subcommandResult.matchedOptionValue(option.getNameAsUnixParameter(), null)); + } + }); + return language; + } else { + return this.options.language; + } + } + + private static ClusteringOptions getClusteringOptions(CliOptions options) { + ClusteringOptions clusteringOptions = new ClusteringOptions().withEnabled(!options.clustering.disable).withAlgorithm(options.clustering.enabled.algorithm).withSimilarityMetric(options.clustering.enabled.metric).withSpectralKernelBandwidth(options.clusterSpectralBandwidth).withSpectralGaussianProcessVariance(options.clusterSpectralNoise).withSpectralMinRuns(options.clusterSpectralMinRuns).withSpectralMaxRuns(options.clusterSpectralMaxRuns).withSpectralMaxKMeansIterationPerRun(options.clusterSpectralKMeansIterations).withAgglomerativeThreshold(options.clusterAgglomerativeThreshold).withAgglomerativeInterClusterSimilarity(options.clusterAgglomerativeInterClusterSimilarity); + + if (options.clusterPreprocessingNone) { + clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.NONE); + } + + if (options.clusterPreprocessingCdf) { + clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION); + } + + if (options.clusterPreprocessingPercentile != 0) { + clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.PERCENTILE).withPreprocessorPercentile(options.clusterPreprocessingPercentile); + } + + if (options.clusterPreprocessingThreshold != 0) { + clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.THRESHOLD).withPreprocessorThreshold(options.clusterPreprocessingThreshold); + } + + return clusteringOptions; + } + + private String generateDescription() { + var randomDescription = DESCRIPTIONS[RANDOM.nextInt(DESCRIPTIONS.length)]; + return String.format("JPlag - %s%n%n%s", randomDescription, CREDITS); + } + + public String getResultFolder() { + return this.options.resultFolder; + } +} diff --git a/languages/java/src/test/resources/java/Compact.java b/languages/java/src/test/resources/de/jplag/java/Compact.java similarity index 100% rename from languages/java/src/test/resources/java/Compact.java rename to languages/java/src/test/resources/de/jplag/java/Compact.java diff --git a/languages/java/src/test/resources/java/IfElse.java b/languages/java/src/test/resources/de/jplag/java/IfElse.java similarity index 100% rename from languages/java/src/test/resources/java/IfElse.java rename to languages/java/src/test/resources/de/jplag/java/IfElse.java diff --git a/languages/java/src/test/resources/java/IfElseIf.java b/languages/java/src/test/resources/de/jplag/java/IfElseIf.java similarity index 100% rename from languages/java/src/test/resources/java/IfElseIf.java rename to languages/java/src/test/resources/de/jplag/java/IfElseIf.java diff --git a/languages/java/src/test/resources/java/IfIf.java b/languages/java/src/test/resources/de/jplag/java/IfIf.java similarity index 100% rename from languages/java/src/test/resources/java/IfIf.java rename to languages/java/src/test/resources/de/jplag/java/IfIf.java diff --git a/languages/java/src/test/resources/java/IfWithBraces.java b/languages/java/src/test/resources/de/jplag/java/IfWithBraces.java similarity index 100% rename from languages/java/src/test/resources/java/IfWithBraces.java rename to languages/java/src/test/resources/de/jplag/java/IfWithBraces.java diff --git a/languages/java/src/test/resources/java/IfWithoutBraces.java b/languages/java/src/test/resources/de/jplag/java/IfWithoutBraces.java similarity index 100% rename from languages/java/src/test/resources/java/IfWithoutBraces.java rename to languages/java/src/test/resources/de/jplag/java/IfWithoutBraces.java diff --git a/languages/java/src/test/resources/java/Try.java b/languages/java/src/test/resources/de/jplag/java/Try.java similarity index 100% rename from languages/java/src/test/resources/java/Try.java rename to languages/java/src/test/resources/de/jplag/java/Try.java diff --git a/languages/java/src/test/resources/java/TryWithResource.java b/languages/java/src/test/resources/de/jplag/java/TryWithResource.java similarity index 100% rename from languages/java/src/test/resources/java/TryWithResource.java rename to languages/java/src/test/resources/de/jplag/java/TryWithResource.java diff --git a/languages/java/src/test/resources/java/Verbose.java b/languages/java/src/test/resources/de/jplag/java/Verbose.java similarity index 100% rename from languages/java/src/test/resources/java/Verbose.java rename to languages/java/src/test/resources/de/jplag/java/Verbose.java diff --git a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java index bb308df61..19a475ffa 100644 --- a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java +++ b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinListener.java @@ -93,59 +93,55 @@ import static de.jplag.kotlin.grammar.KotlinParser.WhenExpressionContext; import static de.jplag.kotlin.grammar.KotlinParser.WhileExpressionContext; -import java.io.File; - import de.jplag.antlr.AbstractAntlrListener; -import de.jplag.antlr.TokenCollector; import de.jplag.kotlin.grammar.KotlinParser; -public class KotlinListener extends AbstractAntlrListener { - public KotlinListener(TokenCollector collector, File currentFile) { - super(collector, currentFile); +class KotlinListener extends AbstractAntlrListener { - this.mapRange(PackageHeaderContext.class, PACKAGE); - this.mapRange(ImportHeaderContext.class, IMPORT); - this.mapEnter(ClassDeclarationContext.class, CLASS_DECLARATION); - this.mapRange(ObjectDeclarationContext.class, OBJECT_DECLARATION); - this.mapRange(CompanionObjectContext.class, COMPANION_DECLARATION); - this.mapRange(TypeParameterContext.class, TYPE_PARAMETER); - this.mapRange(PrimaryConstructorContext.class, CONSTRUCTOR); - this.mapRange(ClassParameterContext.class, PROPERTY_DECLARATION); - this.mapEnterExit(ClassBodyContext.class, CLASS_BODY_BEGIN, CLASS_BODY_END); - this.mapEnterExit(EnumClassBodyContext.class, ENUM_CLASS_BODY_BEGIN, ENUM_CLASS_BODY_END); - this.mapEnter(EnumEntryContext.class, ENUM_ENTRY); - this.mapRange(SecondaryConstructorContext.class, CONSTRUCTOR); - this.mapEnter(PropertyDeclarationContext.class, PROPERTY_DECLARATION); - this.mapEnter(AnonymousInitializerContext.class, INITIALIZER); - this.mapEnterExit(InitBlockContext.class, INITIALIZER_BODY_START, INITIALIZER_BODY_END); - this.mapEnter(FunctionDeclarationContext.class, FUNCTION); - this.mapEnter(GetterContext.class, GETTER); - this.mapEnter(SetterContext.class, SETTER); - this.mapRange(FunctionValueParameterContext.class, FUNCTION_PARAMETER); - this.mapEnterExit(FunctionBodyContext.class, FUNCTION_BODY_BEGIN, FUNCTION_BODY_END); - this.mapEnterExit(FunctionLiteralContext.class, FUNCTION_LITERAL_BEGIN, FUNCTION_LITERAL_END); - this.mapEnterExit(ForExpressionContext.class, FOR_EXPRESSION_BEGIN, FOR_EXPRESSION_END); - this.mapEnterExit(IfExpressionContext.class, IF_EXPRESSION_BEGIN, IF_EXPRESSION_END); - this.mapEnterExit(WhileExpressionContext.class, WHILE_EXPRESSION_START, WHILE_EXPRESSION_END); - this.mapEnterExit(DoWhileExpressionContext.class, DO_WHILE_EXPRESSION_START, DO_WHILE_EXPRESSION_END); - this.mapEnter(TryExpressionContext.class, TRY_EXPRESSION); - this.mapEnterExit(TryBodyContext.class, TRY_BODY_START, TRY_BODY_END); - this.mapEnter(CatchStatementContext.class, CATCH); - this.mapEnterExit(CatchBodyContext.class, CATCH_BODY_START, CATCH_BODY_END); - this.mapEnter(FinallyStatementContext.class, FINALLY); - this.mapEnterExit(FinallyBodyContext.class, FINALLY_BODY_START, FINALLY_BODY_END); - this.mapEnterExit(WhenExpressionContext.class, WHEN_EXPRESSION_START, WHEN_EXPRESSION_END); - this.mapEnter(WhenConditionContext.class, WHEN_CONDITION); - this.mapEnterExit(ControlStructureBodyContext.class, CONTROL_STRUCTURE_BODY_START, CONTROL_STRUCTURE_BODY_END); - this.mapEnter(VariableDeclarationContext.class, VARIABLE_DECLARATION); - this.mapRange(ConstructorInvocationContext.class, CREATE_OBJECT); - this.mapRange(CallSuffixContext.class, FUNCTION_INVOCATION); - this.mapEnter(AssignmentOperatorContext.class, ASSIGNMENT); + KotlinListener() { + visit(PackageHeaderContext.class).map(PACKAGE); + visit(ImportHeaderContext.class).map(IMPORT); + visit(ClassDeclarationContext.class).map(CLASS_DECLARATION); + visit(ObjectDeclarationContext.class).map(OBJECT_DECLARATION); + visit(CompanionObjectContext.class).map(COMPANION_DECLARATION); + visit(TypeParameterContext.class).map(TYPE_PARAMETER); + visit(PrimaryConstructorContext.class).map(CONSTRUCTOR); + visit(ClassParameterContext.class).map(PROPERTY_DECLARATION); + visit(ClassBodyContext.class).map(CLASS_BODY_BEGIN, CLASS_BODY_END); + visit(EnumClassBodyContext.class).map(ENUM_CLASS_BODY_BEGIN, ENUM_CLASS_BODY_END); + visit(EnumEntryContext.class).map(ENUM_ENTRY); + visit(SecondaryConstructorContext.class).map(CONSTRUCTOR); + visit(PropertyDeclarationContext.class).map(PROPERTY_DECLARATION); + visit(AnonymousInitializerContext.class).map(INITIALIZER); + visit(InitBlockContext.class).map(INITIALIZER_BODY_START, INITIALIZER_BODY_END); + visit(FunctionDeclarationContext.class).map(FUNCTION); + visit(GetterContext.class).map(GETTER); + visit(SetterContext.class).map(SETTER); + visit(FunctionValueParameterContext.class).map(FUNCTION_PARAMETER); + visit(FunctionBodyContext.class).map(FUNCTION_BODY_BEGIN, FUNCTION_BODY_END); + visit(FunctionLiteralContext.class).map(FUNCTION_LITERAL_BEGIN, FUNCTION_LITERAL_END); + visit(ForExpressionContext.class).map(FOR_EXPRESSION_BEGIN, FOR_EXPRESSION_END); + visit(IfExpressionContext.class).map(IF_EXPRESSION_BEGIN, IF_EXPRESSION_END); + visit(WhileExpressionContext.class).map(WHILE_EXPRESSION_START, WHILE_EXPRESSION_END); + visit(DoWhileExpressionContext.class).map(DO_WHILE_EXPRESSION_START, DO_WHILE_EXPRESSION_END); + visit(TryExpressionContext.class).map(TRY_EXPRESSION); + visit(TryBodyContext.class).map(TRY_BODY_START, TRY_BODY_END); + visit(CatchStatementContext.class).map(CATCH); + visit(CatchBodyContext.class).map(CATCH_BODY_START, CATCH_BODY_END); + visit(FinallyStatementContext.class).map(FINALLY); + visit(FinallyBodyContext.class).map(FINALLY_BODY_START, FINALLY_BODY_END); + visit(WhenExpressionContext.class).map(WHEN_EXPRESSION_START, WHEN_EXPRESSION_END); + visit(WhenConditionContext.class).map(WHEN_CONDITION); + visit(ControlStructureBodyContext.class).map(CONTROL_STRUCTURE_BODY_START, CONTROL_STRUCTURE_BODY_END); + visit(VariableDeclarationContext.class).map(VARIABLE_DECLARATION); + visit(ConstructorInvocationContext.class).map(CREATE_OBJECT); + visit(CallSuffixContext.class).map(FUNCTION_INVOCATION); + visit(AssignmentOperatorContext.class).map(ASSIGNMENT); - this.mapTerminal(KotlinParser.THROW, THROW); - this.mapTerminal(KotlinParser.RETURN, RETURN); - this.mapTerminal(KotlinParser.CONTINUE, CONTINUE); - this.mapTerminal(KotlinParser.BREAK, BREAK); - this.mapTerminal(KotlinParser.BREAK_AT, BREAK); + visit(KotlinParser.THROW).map(THROW); + visit(KotlinParser.RETURN).map(RETURN); + visit(KotlinParser.CONTINUE).map(CONTINUE); + visit(KotlinParser.BREAK).map(BREAK); + visit(KotlinParser.BREAK_AT).map(BREAK); } } diff --git a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java index 49537918d..0fed85032 100644 --- a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java +++ b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java @@ -1,16 +1,15 @@ package de.jplag.kotlin; -import java.io.File; - import org.antlr.v4.runtime.*; import de.jplag.antlr.AbstractAntlrListener; import de.jplag.antlr.AbstractAntlrParserAdapter; -import de.jplag.antlr.TokenCollector; import de.jplag.kotlin.grammar.KotlinLexer; import de.jplag.kotlin.grammar.KotlinParser; public class KotlinParserAdapter extends AbstractAntlrParserAdapter { + private static final KotlinListener listener = new KotlinListener(); + @Override protected Lexer createLexer(CharStream input) { return new KotlinLexer(input); @@ -27,7 +26,7 @@ protected ParserRuleContext getEntryContext(KotlinParser parser) { } @Override - protected AbstractAntlrListener createListener(TokenCollector collector, File currentFile) { - return new KotlinListener(collector, currentFile); + protected AbstractAntlrListener getListener() { + return listener; } } diff --git a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinTokenType.java b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinTokenType.java index 421dd5888..318b2b17a 100644 --- a/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinTokenType.java +++ b/languages/kotlin/src/main/java/de/jplag/kotlin/KotlinTokenType.java @@ -76,6 +76,7 @@ public enum KotlinTokenType implements TokenType { private final String description; + @Override public String getDescription() { return description; } diff --git a/languages/llvmir/README.md b/languages/llvmir/README.md new file mode 100644 index 000000000..6d5af395d --- /dev/null +++ b/languages/llvmir/README.md @@ -0,0 +1,28 @@ +# JPlag LLVM IR language module + +The JPlag LLVM IR module allows the use of JPlag with submissions in the LLVM IR.
+It is based on the [LLVMIR ANTLR4 grammar](https://github.com/antlr/grammars-v4/tree/master/llvm-ir), licensed under MIT. + +### LLVM IR specification compatibility + +The grammar definition targets LLVM 15, released in September 2022. + +The grammar in this repo contains a fix, see the comment in the [LLVM IR grammar](src/main/antlr4/de/jplag/llvmir/grammar/LLVMIR.g4). + +If the grammar is updated to a more recent1 syntax definition, this module should surely be updated as well. + + +### Token Extraction + +The choice of tokens includes nesting tokens for functions and basic blocks and separate tokens for various elements. +These include binary and bitwise instructions (like addition and or), memory operations (like load and store), terminator instructions (like branches), conversions, global variables, type definitions, constants and others. + + +### Usage + +To use the LLVM IR module, add the `-l llvmir` flag in the CLI, or use a `JPlagOption` object with `new de.jplag.llvmir.LLVMIRLanguage()` as `language` in the Java API as described in the usage information in the [readme of the main project](https://github.com/jplag/JPlag#usage) and [in the wiki](https://github.com/jplag/JPlag/wiki/1.-How-to-Use-JPlag). + +
+ +#### Footnotes +

1 The grammar files are taken from grammar-v4, with the most recent modification in commit 768b12e from August 2023.
\ No newline at end of file diff --git a/languages/llvmir/pom.xml b/languages/llvmir/pom.xml new file mode 100644 index 000000000..c5306fb4c --- /dev/null +++ b/languages/llvmir/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + de.jplag + languages + ${revision} + + llvmir + + + + org.antlr + antlr4-runtime + + + de.jplag + language-antlr-utils + ${revision} + + + + + + + org.antlr + antlr4-maven-plugin + + + + antlr4 + + + + + + + diff --git a/languages/llvmir/src/main/antlr4/de/jplag/llvmir/grammar/LLVMIR.g4 b/languages/llvmir/src/main/antlr4/de/jplag/llvmir/grammar/LLVMIR.g4 new file mode 100644 index 000000000..ffb1eab85 --- /dev/null +++ b/languages/llvmir/src/main/antlr4/de/jplag/llvmir/grammar/LLVMIR.g4 @@ -0,0 +1,1445 @@ +/* + MIT License + + Copyright (c) 2023 ้‚ฑ็ปดไธœ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + Modifications: + Rename 'case' rule to 'case_' to match the generated case_() method in the LLVMIRParser + with the JavaDoc of the enterCase() and exitCase() methods of the LLVMIRListener + to fix a JavaDoc issue. + - Niklas Heneka + */ + +grammar LLVMIR; + +compilationUnit: topLevelEntity* EOF; + +targetDef: targetDataLayout | targetTriple; +sourceFilename: 'source_filename' '=' StringLit; +targetDataLayout: 'target' 'datalayout' '=' StringLit; +targetTriple: 'target' 'triple' '=' StringLit; + +topLevelEntity: + sourceFilename + | targetDef + | moduleAsm + | typeDef + | comdatDef + | globalDecl + | globalDef + | indirectSymbolDef + | funcDecl + | funcDef + | attrGroupDef + | namedMetadataDef + | metadataDef + | useListOrder + | useListOrderBB; +moduleAsm: 'module' 'asm' StringLit; +typeDef: LocalIdent '=' 'type' type; +comdatDef: + ComdatName '=' 'comdat' selectionKind = ( + 'any' + | 'exactmatch' + | 'largest' + | 'nodeduplicate' + | 'samesize' + ); +globalDecl: + GlobalIdent '=' externalLinkage preemption? visibility? dllStorageClass? threadLocal? + unnamedAddr? addrSpace? externallyInitialized? immutable type ( + ',' globalField + )* (',' metadataAttachment)* funcAttribute*; +globalDef: + GlobalIdent '=' internalLinkage? preemption? visibility? dllStorageClass? threadLocal? + unnamedAddr? addrSpace? externallyInitialized? immutable type constant ( + ',' globalField + )* (',' metadataAttachment)* funcAttribute*; + +indirectSymbolDef: + GlobalIdent '=' linkage? preemption? visibility? dllStorageClass? threadLocal? unnamedAddr? + indirectSymbolKind = ('alias' | 'ifunc') type ',' indirectSymbol ( + ',' partition + )*; + +funcDecl: 'declare' metadataAttachment* funcHeader; +funcDef: 'define' funcHeader metadataAttachment* funcBody; +attrGroupDef: + 'attributes' AttrGroupId '=' '{' funcAttribute* '}'; +namedMetadataDef: + MetadataName '=' '!' '{' (metadataNode (',' metadataNode)*)? '}'; +metadataDef: + MetadataId '=' distinct? (mdTuple | specializedMDNode); +useListOrder: + 'uselistorder' typeValue ',' '{' IntLit (',' IntLit)* '}'; +useListOrderBB: + 'uselistorder_bb' GlobalIdent ',' LocalIdent ',' '{' IntLit ( + ',' IntLit + )* '}'; + +funcHeader: + linkage? preemption? visibility? dllStorageClass? callingConv? returnAttribute* type GlobalIdent + '(' params ')' unnamedAddr? addrSpace? funcHdrField*; +indirectSymbol: + typeConst + | bitCastExpr + | getElementPtrExpr + | addrSpaceCastExpr + | intToPtrExpr; +callingConv: callingConvEnum | callingConvInt; +callingConvInt: 'cc' IntLit; +funcHdrField: + funcAttribute + | section + | partition + | comdat + | align + | gc + | prefix + | prologue + | personality; +gc: 'gc' StringLit; +prefix: 'prefix' typeConst; +prologue: 'prologue' typeConst; +personality: 'personality' typeConst; +returnAttribute: + returnAttr + | dereferenceable + | align; +funcBody: '{' basicBlock+ useListOrder* '}'; +basicBlock: LabelIdent? instruction* terminator; +instruction: // Instructions producing values. + localDefInst + | valueInstruction + // Instructions not producing values. + | storeInst + | fenceInst; +terminator: + // Terminators producing values. + localDefTerm + | valueTerminator + // Terminators not producing values. + | retTerm + | brTerm + | condBrTerm + | switchTerm + | indirectBrTerm + | resumeTerm + | catchRetTerm + | cleanupRetTerm + | unreachableTerm; +localDefTerm: LocalIdent '=' valueTerminator; +valueTerminator: invokeTerm | callBrTerm | catchSwitchTerm; +retTerm: + 'ret' 'void' (',' metadataAttachment)* + // Value return. + | 'ret' concreteType value (',' metadataAttachment)*; +brTerm: 'br' label (',' metadataAttachment)*; +condBrTerm: + 'br' IntType value ',' label ',' label ( + ',' metadataAttachment + )*; +switchTerm: + 'switch' typeValue ',' label '[' case_* ']' ( + ',' metadataAttachment + )*; +indirectBrTerm: + 'indirectbr' typeValue ',' '[' (label (',' label)?)? ']' ( + ',' metadataAttachment + )*; +resumeTerm: 'resume' typeValue (',' metadataAttachment)*; +catchRetTerm: + 'catchret' 'from' value 'to' label (',' metadataAttachment)*; +cleanupRetTerm: + 'cleanupret' 'from' value 'unwind' unwindTarget ( + ',' metadataAttachment + )*; +unreachableTerm: 'unreachable' (',' metadataAttachment)*; +invokeTerm: + 'invoke' callingConv? returnAttribute* addrSpace? type value '(' args ')' funcAttribute* ( + '[' (operandBundle ',')+ ']' + )? 'to' label 'unwind' label (',' metadataAttachment)*; +callBrTerm: + 'callbr' callingConv? returnAttribute* addrSpace? type value '(' args ')' funcAttribute* ( + '[' (operandBundle ',')+ ']' + )? 'to' label '[' (label (',' label)*)? ']' ( + ',' metadataAttachment + )*; +catchSwitchTerm: + 'catchswitch' 'within' exceptionPad '[' handlers ']' 'unwind' unwindTarget ( + ',' metadataAttachment + )*; +label: 'label' LocalIdent; +case_: typeConst ',' label; +unwindTarget: 'to' 'caller' | label; +handlers: label (',' label)*; +metadataNode: + MetadataId + // Parse DIExpressions inline as a special case. They are still MDNodes, so they can still + // appear in named metadata. Remove this logic if they become plain Metadata. + | diExpression; +diExpression: + '!DIExpression' '(' ( + diExpressionField (',' diExpressionField)* + )? ')'; +diExpressionField: IntLit | DwarfAttEncoding | DwarfOp; + +globalField: + section + | partition + | comdat + | align + | sanitizerKind = ( + 'no_sanitize_address' + | 'no_sanitize_hwaddress' + | 'sanitize_address_dyninit' + | 'sanitize_memtag' + ); +section: 'section' StringLit; +comdat: 'comdat' ('(' ComdatName ')')?; +partition: 'partition' StringLit; + +constant: + boolConst + | intConst + | floatConst + | nullConst + | noneConst + | structConst + | arrayConst + | vectorConst + | zeroInitializerConst + // @42 @foo + | GlobalIdent + | undefConst + | poisonConst + | blockAddressConst + | dsoLocalEquivalentConst + | noCFIConst + | constantExpr; +boolConst: 'true' | 'false'; +intConst: IntLit; +floatConst: FloatLit; +nullConst: 'null'; +noneConst: 'none'; +structConst: + '{' (typeConst (',' typeConst)*)? '}' + | '<' '{' ( typeConst (',' typeConst)*)? '}' '>'; +arrayConst: + 'c' StringLit + | '[' (typeConst (',' typeConst)*)? ']'; +vectorConst: '<' (typeConst (',' typeConst)*)? '>'; +zeroInitializerConst: 'zeroinitializer'; +undefConst: 'undef'; +poisonConst: 'poison'; +blockAddressConst: + 'blockaddress' '(' GlobalIdent ',' LocalIdent ')'; +dsoLocalEquivalentConst: 'dso_local_equivalent' GlobalIdent; +noCFIConst: 'no_cfi' GlobalIdent; +constantExpr: + // Unary expressions + fNegExpr + // Binary expressions + | addExpr + | subExpr + | mulExpr + // Bitwise expressions + | shlExpr + | lShrExpr + | aShrExpr + | andExpr + | orExpr + | xorExpr + // Vector expressions + | extractElementExpr + | insertElementExpr + | shuffleVectorExpr + // Memory expressions + | getElementPtrExpr + // Conversion expressions + | truncExpr + | zExtExpr + | sExtExpr + | fpTruncExpr + | fpExtExpr + | fpToUiExpr + | fpToSiExpr + | uiToFpExpr + | siToFpExpr + | ptrToIntExpr + | intToPtrExpr + | bitCastExpr + | addrSpaceCastExpr + // Other expressions + | iCmpExpr + | fCmpExpr + | selectExpr; +typeConst: firstClassType constant; + +metadataAttachment: MetadataName mdNode; +mdNode: + mdTuple + // !42 + | MetadataId + //!{ ... } + | specializedMDNode; +mdTuple: '!' '{' (mdField (',' mdField)*)? '}'; +// metadataID: MetadataId; +metadata: + typeValue + | mdString + // !{ ... } + | mdTuple + // !7 + | MetadataId + | diArgList + | specializedMDNode; +diArgList: '!DIArgList' '(' (typeValue (',' typeValue)*)? ')'; +typeValue: firstClassType value; +value: + constant + // %42 %foo + | LocalIdent + // TODO: Move InlineAsm from Value to Callee and Invokee? Inline assembler expressions may only + // be used as the callee operand of a call or an invoke instruction. + | inlineAsm; +inlineAsm: + 'asm' sideEffect = 'sideeffect'? alignStackTok = 'alignstack'? intelDialect = 'inteldialect'? + unwind = 'unwind'? StringLit ',' StringLit; +mdString: '!' StringLit; +mdFieldOrInt: IntLit | mdField; +diSPFlag: IntLit | DispFlag; +funcAttribute: + attrString + | attrPair + // not used in attribute groups. + | AttrGroupId + // used in functions. | align # NOTE: removed to resolve reduce/reduce conflict, see above. used + // in attribute groups. + | alignPair + | alignStack + | alignStackPair + | allocKind + | allocSize + | funcAttr + | preallocated + | unwindTable + | vectorScaleRange; +type: + 'void' + | 'opaque' + | type '(' params ')' + | intType + | floatType + | type addrSpace? '*' + | opaquePointerType + | vectorType + | labelType + | arrayType + | structType + | namedType + | mmxType + | tokenType + | metadataType; +params: + ellipsis = '...'? + | param (',' param)* (',' ellipsis = '...')?; +param: type paramAttribute* LocalIdent?; +paramAttribute: + attrString + | attrPair + | align + | alignStack + | byRefAttr + | byval + | dereferenceable + | elementType + | inAlloca + | paramAttr + | preallocated + | structRetAttr; +attrString: StringLit; +attrPair: StringLit '=' StringLit; +align: 'align' IntLit | 'align' '(' IntLit ')'; +alignPair: 'align' '=' IntLit; +alignStack: 'alignstack' '(' IntLit ')'; +alignStackPair: 'alignstack' '=' IntLit; +allocKind: 'allockind' '(' StringLit ')'; +allocSize: 'allocsize' '(' IntLit (',' IntLit)? ')'; +unwindTable: + 'uwtable' + | 'uwtable' '(' unwindTableKind = ('async' | 'sync') ')'; +vectorScaleRange: + 'vscale_range' ('(' (IntLit | IntLit ',' IntLit) ')')?; +byRefAttr: 'byref' '(' type ')'; +byval: 'byval' ( '(' type ')')?; +dereferenceable: + 'dereferenceable' '(' IntLit ')' + | 'dereferenceable_or_null' '(' IntLit ')'; +elementType: 'elementtype' '(' type ')'; +inAlloca: 'inalloca' '(' type ')'; +paramAttr: + 'allocalign' + | 'allocptr' + | 'immarg' + | 'inreg' + | 'nest' + | 'noalias' + | 'nocapture' + | 'nofree' + | 'nonnull' + | 'noundef' + | 'readnone' + | 'readonly' + | 'returned' + | 'signext' + | 'swiftasync' + | 'swifterror' + | 'swiftself' + | 'writeonly' + | 'zeroext'; +preallocated: 'preallocated' '(' type ')'; +structRetAttr: 'sret' '(' type ')'; + +// funcType: type '(' params ')'; +firstClassType: concreteType | metadataType; +concreteType: + intType + | floatType + | pointerType + | vectorType + | labelType + | arrayType + | structType + | namedType + | mmxType + | tokenType; + +intType: IntType; +floatType: floatKind; +pointerType: type addrSpace? '*' | opaquePointerType; +vectorType: + '<' IntLit 'x' type '>' + | '<' 'vscale' 'x' IntLit 'x' type '>'; +labelType: 'label'; +arrayType: '[' IntLit 'x' type ']'; +structType: + '{' (type (',' type)*)? '}' + | '<' '{' (type (',' type)*)? '}' '>'; +namedType: LocalIdent; +mmxType: 'x86_mmx'; +tokenType: 'token'; + +opaquePointerType: 'ptr' addrSpace?; +addrSpace: 'addrspace' '(' IntLit ')'; +threadLocal: 'thread_local' ('(' tlsModel ')')?; +metadataType: 'metadata'; + +// expr +bitCastExpr: 'bitcast' '(' typeConst 'to' type ')'; +getElementPtrExpr: + 'getelementptr' inBounds? '(' type ',' typeConst ( + ',' gepIndex + )* ')'; +gepIndex: inRange = 'inrange'? typeConst; +addrSpaceCastExpr: 'addrspacecast' '(' typeConst 'to' type ')'; +intToPtrExpr: 'inttoptr' '(' typeConst 'to' type ')'; +iCmpExpr: 'icmp' iPred '(' typeConst ',' typeConst ')'; +fCmpExpr: 'fcmp' fPred '(' typeConst ',' typeConst ')'; +selectExpr: + 'select' '(' typeConst ',' typeConst ',' typeConst ')'; + +truncExpr: 'trunc' '(' typeConst 'to' type ')'; +zExtExpr: 'zext' '(' typeConst 'to' type ')'; +sExtExpr: 'sext' '(' typeConst 'to' type ')'; +fpTruncExpr: 'fptrunc' '(' typeConst 'to' type ')'; +fpExtExpr: 'fpext' '(' typeConst 'to' type ')'; +fpToUiExpr: 'fptoui' '(' typeConst 'to' type ')'; +fpToSiExpr: 'fptosi' '(' typeConst 'to' type ')'; +uiToFpExpr: 'uitofp' '(' typeConst 'to' type ')'; +siToFpExpr: 'sitofp' '(' typeConst 'to' type ')'; +ptrToIntExpr: 'ptrtoint' '(' typeConst 'to' type ')'; +extractElementExpr: + 'extractelement' '(' typeConst ',' typeConst ')'; +insertElementExpr: + 'insertelement' '(' typeConst ',' typeConst ',' typeConst ')'; +shuffleVectorExpr: + 'shufflevector' '(' typeConst ',' typeConst ',' typeConst ')'; +shlExpr: 'shl' overflowFlag* '(' typeConst ',' typeConst ')'; +lShrExpr: + 'lshr' exact = 'exact'? '(' typeConst ',' typeConst ')'; +aShrExpr: + 'ashr' exact = 'exact'? '(' typeConst ',' typeConst ')'; +andExpr: 'and' '(' typeConst ',' typeConst ')'; +orExpr: 'or' '(' typeConst ',' typeConst ')'; +xorExpr: 'xor' '(' typeConst ',' typeConst ')'; +addExpr: 'add' overflowFlag* '(' typeConst ',' typeConst ')'; +subExpr: 'sub' overflowFlag* '(' typeConst ',' typeConst ')'; +mulExpr: 'mul' overflowFlag* '(' typeConst ',' typeConst ')'; +fNegExpr: 'fneg' '(' typeConst ')'; + +// instructions +localDefInst: LocalIdent '=' valueInstruction; +valueInstruction: + // Unary instructions + fNegInst + // Binary instructions + | addInst + | fAddInst + | subInst + | fSubInst + | mulInst + | fMulInst + | uDivInst + | sDivInst + | fDivInst + | uRemInst + | sRemInst + | fRemInst + // Bitwise instructions + | shlInst + | lShrInst + | aShrInst + | andInst + | orInst + | xorInst + // Vector instructions + | extractElementInst + | insertElementInst + | shuffleVectorInst + // Aggregate instructions + | extractValueInst + | insertValueInst + // Memory instructions + | allocaInst + | loadInst + | cmpXchgInst + | atomicRMWInst + | getElementPtrInst + // Conversion instructions + | truncInst + | zExtInst + | sExtInst + | fpTruncInst + | fpExtInst + | fpToUiInst + | fpToSiInst + | uiToFpInst + | siToFpInst + | ptrToIntInst + | intToPtrInst + | bitCastInst + | addrSpaceCastInst + // Other instructions + | iCmpInst + | fCmpInst + | phiInst + | selectInst + | freezeInst + | callInst + | vaargInst + | landingPadInst + | catchPadInst + | cleanupPadInst; +storeInst: + // Store. + 'store' volatile = 'volatile'? typeValue ',' typeValue ( + ',' align + )? (',' metadataAttachment)* + // atomic='atomic' store. + | 'store' atomic = 'atomic' volatile = 'volatile'? typeValue ',' typeValue syncScope? + atomicOrdering (',' align)? (',' metadataAttachment)*; + +syncScope: 'syncscope' '(' StringLit ')'; + +fenceInst: + 'fence' syncScope? atomicOrdering (',' metadataAttachment)*; +fNegInst: + 'fneg' fastMathFlag* typeValue (',' metadataAttachment)*; +addInst: + 'add' overflowFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +fAddInst: + 'fadd' fastMathFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +subInst: + 'sub' overflowFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +fSubInst: + 'fsub' fastMathFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +mulInst: + 'mul' overflowFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +fMulInst: + 'fmul' fastMathFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +uDivInst: + 'udiv' exact = 'exact'? typeValue ',' value ( + ',' metadataAttachment + )*; +sDivInst: + 'sdiv' exact = 'exact'? typeValue ',' value ( + ',' metadataAttachment + )*; +fDivInst: + 'fdiv' fastMathFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +uRemInst: 'urem' typeValue ',' value ( ',' metadataAttachment)*; +sRemInst: 'srem' typeValue ',' value ( ',' metadataAttachment)*; +fRemInst: + 'frem' fastMathFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +shlInst: + 'shl' overflowFlag* typeValue ',' value ( + ',' metadataAttachment + )*; +lShrInst: + 'lshr' exact = 'exact'? typeValue ',' value ( + ',' metadataAttachment + )*; +aShrInst: + 'ashr' exact = 'exact'? typeValue ',' value ( + ',' metadataAttachment + )*; +andInst: 'and' typeValue ',' value ( ',' metadataAttachment)*; +orInst: 'or' typeValue ',' value ( ',' metadataAttachment)*; +xorInst: 'xor' typeValue ',' value ( ',' metadataAttachment)*; +extractElementInst: + 'extractelement' typeValue ',' typeValue ( + ',' metadataAttachment + )*; +insertElementInst: + 'insertelement' typeValue ',' typeValue ',' typeValue ( + ',' metadataAttachment + )*; +shuffleVectorInst: + 'shufflevector' typeValue ',' typeValue ',' typeValue ( + ',' metadataAttachment + )*; +extractValueInst: + 'extractvalue' typeValue (',' IntLit)+ ( + ',' metadataAttachment + )*; +insertValueInst: + 'insertvalue' typeValue ',' typeValue (',' IntLit)+ ( + ',' metadataAttachment + )*; +allocaInst: + 'alloca' inAllocaTok = 'inalloca'? swiftError = 'swifterror'? type ( + ',' typeValue + )? (',' align)? (',' addrSpace)? (',' metadataAttachment)*; +loadInst: + // Load. + 'load' volatile = 'volatile'? type ',' typeValue (',' align)? ( + ',' metadataAttachment + )* + // atomic='atomic' load. + | 'load' atomic = 'atomic' volatile = 'volatile'? type ',' typeValue syncScope? atomicOrdering ( + ',' align + )? (',' metadataAttachment)*; +cmpXchgInst: + 'cmpxchg' weak = 'weak'? volatile = 'volatile'? typeValue ',' typeValue ',' typeValue syncScope? + atomicOrdering atomicOrdering (',' align)? ( + ',' metadataAttachment + )*; +atomicRMWInst: + 'atomicrmw' volatile = 'volatile'? atomicOp typeValue ',' typeValue syncScope? atomicOrdering ( + ',' align + )? (',' metadataAttachment)*; +getElementPtrInst: + 'getelementptr' inBounds? type ',' typeValue (',' typeValue)* ( + ',' metadataAttachment + )*; +truncInst: + 'trunc' typeValue 'to' type (',' metadataAttachment)*; +zExtInst: 'zext' typeValue 'to' type ( ',' metadataAttachment)*; +sExtInst: 'sext' typeValue 'to' type ( ',' metadataAttachment)*; +fpTruncInst: + 'fptrunc' typeValue 'to' type (',' metadataAttachment)*; +fpExtInst: + 'fpext' typeValue 'to' type (',' metadataAttachment)*; +fpToUiInst: + 'fptoui' typeValue 'to' type (',' metadataAttachment)*; +fpToSiInst: + 'fptosi' typeValue 'to' type (',' metadataAttachment)*; +uiToFpInst: + 'uitofp' typeValue 'to' type (',' metadataAttachment)*; +siToFpInst: + 'sitofp' typeValue 'to' type (',' metadataAttachment)*; +ptrToIntInst: + 'ptrtoint' typeValue 'to' type (',' metadataAttachment)*; +intToPtrInst: + 'inttoptr' typeValue 'to' type (',' metadataAttachment)*; +bitCastInst: + 'bitcast' typeValue 'to' type (',' metadataAttachment)*; +addrSpaceCastInst: + 'addrspacecast' typeValue 'to' type (',' metadataAttachment)*; +iCmpInst: + 'icmp' iPred typeValue ',' value (',' metadataAttachment)*; +fCmpInst: + 'fcmp' fastMathFlag* fPred typeValue ',' value ( + ',' metadataAttachment + )*; +phiInst: + 'phi' fastMathFlag* type (inc (',' inc)*) ( + ',' metadataAttachment + )*; +selectInst: + 'select' fastMathFlag* typeValue ',' typeValue ',' typeValue ( + ',' metadataAttachment + )*; +freezeInst: 'freeze' typeValue; +callInst: + tail = ('musttail' | 'notail' | 'tail')? 'call' fastMathFlag* callingConv? returnAttribute* + addrSpace? type value '(' args ')' funcAttribute* ( + '[' operandBundle (',' operandBundle)* ']' + )? (',' metadataAttachment)*; +vaargInst: + 'va_arg' typeValue ',' type (',' metadataAttachment)*; +landingPadInst: + 'landingpad' type cleanUp = 'cleanup'? clause* ( + ',' metadataAttachment + )*; +catchPadInst: + 'catchpad' 'within' LocalIdent '[' ( + exceptionArg (',' exceptionArg)* + )? ']' (',' metadataAttachment)*; +cleanupPadInst: + 'cleanuppad' 'within' exceptionPad '[' ( + exceptionArg (',' exceptionArg)* + )? ']' (',' metadataAttachment)*; + +inc: '[' value ',' LocalIdent ']'; + +operandBundle: StringLit '(' (typeValue (',' typeValue)*)? ')'; +clause: clauseType = ('catch' | 'filter') typeValue; + +args: + ellipsis = '...'? + | arg (',' arg)* (',' ellipsis = '...')?; +arg: concreteType paramAttribute* value | metadataType metadata; + +exceptionArg: concreteType value | metadataType metadata; +exceptionPad: noneConst | LocalIdent; + +externalLinkage: 'extern_weak' | 'external'; +internalLinkage: + 'appending' + | 'available_externally' + | 'common' + | 'internal' + | 'linkonce' + | 'linkonce_odr' + | 'private' + | 'weak' + | 'weak_odr'; +linkage: internalLinkage | externalLinkage; +preemption: 'dso_local' | 'dso_preemptable'; +visibility: 'default' | 'hidden' | 'protected'; +dllStorageClass: 'dllexport' | 'dllimport'; +tlsModel: 'initialexec' | 'localdynamic' | 'localexec'; +unnamedAddr: 'local_unnamed_addr' | 'unnamed_addr'; +externallyInitialized: 'externally_initialized'; +immutable: 'constant' | 'global'; +funcAttr: + 'alwaysinline' + | 'argmemonly' + | 'builtin' + | 'cold' + | 'convergent' + | 'disable_sanitizer_instrumentation' + | 'fn_ret_thunk_extern' + | 'hot' + | 'inaccessiblemem_or_argmemonly' + | 'inaccessiblememonly' + | 'inlinehint' + | 'jumptable' + | 'minsize' + | 'mustprogress' + | 'naked' + | 'nobuiltin' + | 'nocallback' + | 'nocf_check' + | 'noduplicate' + | 'nofree' + | 'noimplicitfloat' + | 'noinline' + | 'nomerge' + | 'nonlazybind' + | 'noprofile' + | 'norecurse' + | 'noredzone' + | 'noreturn' + | 'nosanitize_bounds' + | 'nosanitize_coverage' + | 'nosync' + | 'nounwind' + | 'null_pointer_is_valid' + | 'optforfuzzing' + | 'optnone' + | 'optsize' + | 'presplitcoroutine' + | 'readnone' + | 'readonly' + | 'returns_twice' + | 'safestack' + | 'sanitize_address' + | 'sanitize_hwaddress' + | 'sanitize_memory' + | 'sanitize_memtag' + | 'sanitize_thread' + | 'shadowcallstack' + | 'speculatable' + | 'speculative_load_hardening' + | 'ssp' + | 'sspreq' + | 'sspstrong' + | 'strictfp' + | 'willreturn' + | 'writeonly'; +distinct: 'distinct'; +inBounds: 'inbounds'; +returnAttr: + 'inreg' + | 'noalias' + | 'nonnull' + | 'noundef' + | 'signext' + | 'zeroext'; +overflowFlag: 'nsw' | 'nuw'; +iPred: + 'eq' + | 'ne' + | 'sge' + | 'sgt' + | 'sle' + | 'slt' + | 'uge' + | 'ugt' + | 'ule' + | 'ult'; +fPred: + 'false' + | 'oeq' + | 'oge' + | 'ogt' + | 'ole' + | 'olt' + | 'one' + | 'ord' + | 'true' + | 'ueq' + | 'uge' + | 'ugt' + | 'ule' + | 'ult' + | 'une' + | 'uno'; +atomicOrdering: + 'acq_rel' + | 'acquire' + | 'monotonic' + | 'release' + | 'seq_cst' + | 'unordered'; +callingConvEnum: + 'aarch64_sve_vector_pcs' + | 'aarch64_vector_pcs' + | 'amdgpu_cs' + | 'amdgpu_es' + | 'amdgpu_gfx' + | 'amdgpu_gs' + | 'amdgpu_hs' + | 'amdgpu_kernel' + | 'amdgpu_ls' + | 'amdgpu_ps' + | 'amdgpu_vs' + | 'anyregcc' + | 'arm_aapcs_vfpcc' + | 'arm_aapcscc' + | 'arm_apcscc' + | 'avr_intrcc' + | 'avr_signalcc' + | 'ccc' + | 'cfguard_checkcc' + | 'coldcc' + | 'cxx_fast_tlscc' + | 'fastcc' + | 'ghccc' + | 'hhvm_ccc' + | 'hhvmcc' + | 'intel_ocl_bicc' + | 'msp430_intrcc' + | 'preserve_allcc' + | 'preserve_mostcc' + | 'ptx_device' + | 'ptx_kernel' + | 'spir_func' + | 'spir_kernel' + | 'swiftcc' + | 'swifttailcc' + | 'tailcc' + | 'webkit_jscc' + | 'win64cc' + | 'x86_64_sysvcc' + | 'x86_fastcallcc' + | 'x86_intrcc' + | 'x86_regcallcc' + | 'x86_stdcallcc' + | 'x86_thiscallcc' + | 'x86_vectorcallcc'; + +fastMathFlag: + 'afn' + | 'arcp' + | 'contract' + | 'fast' + | 'ninf' + | 'nnan' + | 'nsz' + | 'reassoc'; +atomicOp: + 'add' + | 'and' + | 'fadd' + | 'fmax' + | 'fmin' + | 'fsub' + | 'max' + | 'min' + | 'nand' + | 'or' + | 'sub' + | 'umax' + | 'umin' + | 'xchg' + | 'xor'; +floatKind: + 'half' + | 'bfloat' + | 'float' + | 'double' + | 'x86_fp80' + | 'fp128' + | 'ppc_fp128'; +/*็œ‹ไธๆ‡‚๏ผŒ็›ดๆŽฅๆŠ„่ฟ‡ๆฅ็š„ */ +specializedMDNode: + diBasicType + | diCommonBlock // not in spec as of 2019-12-05 + | diCompileUnit + | diCompositeType + | diDerivedType + | diEnumerator + | diExpression + | diFile + | diGlobalVariable + | diGlobalVariableExpression + | diImportedEntity + | diLabel // not in spec as of 2018-10-14, still not in spec as of 2019-12-05 + | diLexicalBlock + | diLexicalBlockFile + | diLocalVariable + | diLocation + | diMacro + | diMacroFile + | diModule // not in spec as of 2018-02-21, still not in spec as of 2019-12-05 + | diNamespace + | diObjCProperty + | diStringType + | diSubprogram + | diSubrange + | diSubroutineType + | diTemplateTypeParameter + | diTemplateValueParameter + | genericDiNode; // not in spec as of 2018-02-21, still not in spec as of 2019-12-05 + +diBasicType: + '!DIBasicType' '(' (diBasicTypeField (',' diBasicTypeField)*)? ')'; +diCommonBlock: + '!DICommonBlock' '(' ( + diCommonBlockField (',' diCommonBlockField)* + )? ')'; +diCompileUnit: + '!DICompileUnit' '(' ( + diCompileUnitField (',' diCompileUnitField)* + )? ')'; +diCompositeType: + '!DICompositeType' '(' ( + diCompositeTypeField (',' diCompositeTypeField)* + )? ')'; +diCompositeTypeField: + tagField + | nameField + | scopeField + | fileField + | lineField + | baseTypeField + | sizeField + | alignField + | offsetField + | flagsField + | elementsField + | runtimeLangField + | vtableHolderField + | templateParamsField + | identifierField + | discriminatorField + | dataLocationField + | associatedField + | allocatedField + | rankField + | annotationsField; +diDerivedType: + '!DIDerivedType' '(' ( + diDerivedTypeField (',' diDerivedTypeField)* + )? ')'; +diDerivedTypeField: + tagField + | nameField + | scopeField + | fileField + | lineField + | baseTypeField + | sizeField + | alignField + | offsetField + | flagsField + | extraDataField + | dwarfAddressSpaceField + | annotationsField; +diEnumerator: + '!DIEnumerator' '(' ( + diEnumeratorField (',' diEnumeratorField)* + )? ')'; +diEnumeratorField: nameField | valueIntField | isUnsignedField; +diFile: '!DIFile' '(' (diFileField (',' diFileField)*)? ')'; +diFileField: + filenameField + | directoryField + | checksumkindField + | checksumField + | sourceField; +diGlobalVariable: + '!DIGlobalVariable' '(' ( + diGlobalVariableField (',' diGlobalVariableField)* + )? ')'; +diGlobalVariableField: + nameField + | scopeField + | linkageNameField + | fileField + | lineField + | typeField + | isLocalField + | isDefinitionField + | templateParamsField + | declarationField + | alignField + | annotationsField; +diGlobalVariableExpression: + '!DIGlobalVariableExpression' '(' ( + diGlobalVariableExpressionField ( + ',' diGlobalVariableExpressionField + )* + )? ')'; +diGlobalVariableExpressionField: varField | exprField; +diImportedEntity: + '!DIImportedEntity' '(' ( + diImportedEntityField (',' diImportedEntityField)* + )? ')'; +diImportedEntityField: + tagField + | scopeField + | entityField + | fileField + | lineField + | nameField + | elementsField; + +diLabel: '!DILabel' '(' (diLabelField (',' diLabelField)*)? ')'; +diLabelField: scopeField | nameField | fileField | lineField; +diLexicalBlock: + '!DILexicalBlock' '(' ( + diLexicalBlockField (',' diLexicalBlockField)* + )? ')'; +diLexicalBlockField: + scopeField + | fileField + | lineField + | columnField; +diLexicalBlockFile: + '!DILexicalBlockFile' '(' ( + diLexicalBlockFileField (',' diLexicalBlockFileField)* + )? ')'; +diLexicalBlockFileField: + scopeField + | fileField + | discriminatorIntField; +diLocalVariable: + '!DILocalVariable' '(' ( + diLocalVariableField (',' diLocalVariableField)* + )? ')'; +diLocalVariableField: + scopeField + | nameField + | argField + | fileField + | lineField + | typeField + | flagsField + | alignField + | annotationsField; +diLocation: + '!DILocation' '(' (diLocationField (',' diLocationField)*)? ')'; +diLocationField: + lineField + | columnField + | scopeField + | inlinedAtField + | isImplicitCodeField; +diMacro: '!DIMacro' '(' (diMacroField (',' diMacroField)*)? ')'; +diMacroField: + typeMacinfoField + | lineField + | nameField + | valueStringField; +diMacroFile: + '!DIMacroFile' '(' (diMacroFileField (',' diMacroFileField)*)? ')'; +diMacroFileField: + typeMacinfoField + | lineField + | fileField + | nodesField; +diModule: + '!DIModule' '(' (diModuleField (',' diModuleField)*)? ')'; +diModuleField: + scopeField + | nameField + | configMacrosField + | includePathField + | apiNotesField + | fileField + | lineField + | isDeclField; +diNamespace: + '!DINamespace' '(' (diNamespaceField (',' diNamespaceField)*)? ')'; +diNamespaceField: scopeField | nameField | exportSymbolsField; +diObjCProperty: + '!DIObjCProperty' '(' ( + diObjCPropertyField (',' diObjCPropertyField)* + )? ')'; +diObjCPropertyField: + nameField + | fileField + | lineField + | setterField + | getterField + | attributesField + | typeField; +diStringType: + '!DIStringType' '(' ( + diStringTypeField (',' diStringTypeField)* + )? ')'; +diStringTypeField: + tagField + | nameField + | stringLengthField + | stringLengthExpressionField + | stringLocationExpressionField + | sizeField + | alignField + | encodingField; +diSubprogram: + '!DISubprogram' '(' ( + diSubprogramField (',' diSubprogramField)* + )? ')'; +diSubprogramField: + scopeField + | nameField + | linkageNameField + | fileField + | lineField + | typeField + | isLocalField + | isDefinitionField + | scopeLineField + | containingTypeField + | virtualityField + | virtualIndexField + | thisAdjustmentField + | flagsField + | spFlagsField + | isOptimizedField + | unitField + | templateParamsField + | declarationField + | retainedNodesField + | thrownTypesField + | annotationsField + | targetFuncNameField; +diSubrange: + '!DISubrange' '(' (diSubrangeField (',' diSubrangeField)*)? ')'; +diSubrangeField: + countField + | lowerBoundField + | upperBoundField + | strideField; +diSubroutineType: + '!DISubroutineType' '(' ( + diSubroutineTypeField (',' diSubroutineTypeField)* + )? ')'; +diTemplateTypeParameter: + '!DITemplateTypeParameter' '(' ( + diTemplateTypeParameterField ( + ',' diTemplateTypeParameterField + )* + )? ')'; +diTemplateValueParameter: + '!DITemplateValueParameter' '(' ( + diTemplateValueParameterField ( + ',' diTemplateValueParameterField + ) + )? ')'; +genericDiNode: + '!GenericDINode' '(' ( + genericDINodeField (',' genericDINodeField)* + )? ')'; + +diTemplateTypeParameterField: + nameField + | typeField + | defaultedField; +diCompileUnitField: + languageField + | fileField + | producerField + | isOptimizedField + | flagsStringField + | runtimeVersionField + | splitDebugFilenameField + | emissionKindField + | enumsField + | retainedTypesField + | globalsField + | importsField + | macrosField + | dwoIdField + | splitDebugInliningField + | debugInfoForProfilingField + | nameTableKindField + | rangesBaseAddressField + | sysrootField + | sdkField; +diCommonBlockField: + scopeField + | declarationField + | nameField + | fileField + | lineField; +diBasicTypeField: + tagField + | nameField + | sizeField + | alignField + | encodingField + | flagsField; +genericDINodeField: tagField | headerField | operandsField; +tagField: 'tag:' DwarfTag; +headerField: 'header:' StringLit; +operandsField: 'operands:' '{' (mdField (',' mdField)*)? '}'; +diTemplateValueParameterField: + tagField + | nameField + | typeField + | defaultedField + | valueField; +nameField: 'name:' StringLit; +typeField: 'type:' mdField; +defaultedField: 'defaulted:' boolConst; +valueField: 'value:' mdField; +mdField: nullConst | metadata; +diSubroutineTypeField: flagsField | ccField | typesField; +flagsField: 'flags:' diFlags; +diFlags: DiFlag ('|' DiFlag)*; +ccField: 'cc:' DwarfCc | IntLit; +alignField: 'align:' IntLit; +allocatedField: 'allocated:' mdField; +annotationsField: 'annotations:' mdField; +argField: 'arg:' IntLit; +associatedField: 'associated:' mdField; +attributesField: 'attributes:' IntLit; +baseTypeField: 'baseType:' mdField; +checksumField: 'checksum:' StringLit; +checksumkindField: 'checksumkind:' ChecksumKind; +columnField: 'column:' IntLit; +configMacrosField: 'configMacros:' StringLit; +containingTypeField: 'containingType:' mdField; +countField: 'count:' mdFieldOrInt; +debugInfoForProfilingField: 'debugInfoForProfiling:' boolConst; +declarationField: 'declaration:' mdField; +directoryField: 'directory:' StringLit; +discriminatorField: 'discriminator:' mdField; +dataLocationField: 'dataLocation:' mdField; +discriminatorIntField: 'discriminator:' IntLit; +dwarfAddressSpaceField: 'dwarfAddressSpace:' IntLit; +dwoIdField: 'dwoId:' IntLit; +elementsField: 'elements:' mdField; +emissionKindField: + 'emissionKind:' emissionKind = ( + 'DebugDirectivesOnly' + | 'FullDebug' + | 'LineTablesOnly' + | 'NoDebug' + ); +encodingField: 'encoding:' (IntLit | DwarfAttEncoding); +entityField: 'entity:' mdField; +enumsField: 'enums:' mdField; +exportSymbolsField: 'exportSymbols:' boolConst; +exprField: 'expr:' mdField; +extraDataField: 'extraData:' mdField; +fileField: 'file:' mdField; +filenameField: 'filename:' StringLit; +flagsStringField: 'flags:' StringLit; +getterField: 'getter:' StringLit; +globalsField: 'globals:' mdField; +identifierField: 'identifier:' StringLit; +importsField: 'imports:' mdField; +includePathField: 'includePath:' StringLit; +inlinedAtField: 'inlinedAt:' mdField; +isDeclField: 'isDecl:' boolConst; +isDefinitionField: 'isDefinition:' boolConst; +isImplicitCodeField: 'isImplicitCode:' boolConst; +isLocalField: 'isLocal:' boolConst; +isOptimizedField: 'isOptimized:' boolConst; +isUnsignedField: 'isUnsigned:' boolConst; +apiNotesField: 'apinotes:' StringLit; +languageField: 'language:' DwarfLang; +lineField: 'line:' IntLit; +linkageNameField: 'linkageName:' StringLit; +lowerBoundField: 'lowerBound:' mdFieldOrInt; +macrosField: 'macros:' mdField; +nameTableKindField: + 'nameTableKind:' nameTableKind = ('GNU' | 'None' | 'Default'); +nodesField: 'nodes:' mdField; +offsetField: + // TODO: rename OffsetField= attribute to Offset= when inspirer/textmapper#13 is resolved + 'offset:' IntLit; +producerField: 'producer:' StringLit; +rangesBaseAddressField: 'rangesBaseAddress:' boolConst; +rankField: 'rank:' mdFieldOrInt; +retainedNodesField: 'retainedNodes:' mdField; +retainedTypesField: 'retainedTypes:' mdField; +runtimeLangField: 'runtimeLang:' DwarfLang; +runtimeVersionField: 'runtimeVersion:' IntLit; +scopeField: 'scope:' mdField; +scopeLineField: 'scopeLine:' IntLit; +sdkField: 'sdk:' StringLit; +setterField: 'setter:' StringLit; +sizeField: 'size:' IntLit; +sourceField: 'source:' StringLit; +spFlagsField: 'spFlags:' (diSPFlag ('|' diSPFlag)*); +splitDebugFilenameField: 'splitDebugFilename:' StringLit; +splitDebugInliningField: 'splitDebugInlining:' boolConst; +strideField: 'stride:' mdFieldOrInt; +stringLengthField: 'stringLength:' mdField; +stringLengthExpressionField: 'stringLengthExpression:' mdField; +stringLocationExpressionField: + 'stringLocationExpression:' mdField; +sysrootField: 'sysroot:' StringLit; +targetFuncNameField: 'targetFuncName:' StringLit; +templateParamsField: 'templateParams:' mdField; +thisAdjustmentField: 'thisAdjustment:' IntLit; +thrownTypesField: 'thrownTypes:' mdField; +typeMacinfoField: 'type:' DwarfMacinfo; +typesField: 'types:' mdField; +unitField: 'unit:' mdField; +upperBoundField: 'upperBound:' mdFieldOrInt; +valueIntField: 'value:' IntLit; +valueStringField: 'value:' StringLit; +varField: 'var:' mdField; +virtualIndexField: 'virtualIndex:' IntLit; +virtualityField: 'virtuality:' DwarfVirtuality; +vtableHolderField: 'vtableHolder:' mdField; + +fragment AsciiLetter: [A-Za-z]; +fragment Letter: AsciiLetter | [-$._]; +fragment EscapeLetter: Letter | '\\'; +fragment DecimalDigit: [0-9]; +fragment HexDigit: [A-Fa-f] | DecimalDigit; +fragment Decimals: DecimalDigit+; +fragment Name: Letter (Letter | DecimalDigit)*; +fragment EscapeName: + EscapeLetter (EscapeLetter | DecimalDigit)*; +fragment Id: Decimals; +fragment IntHexLit: [us] '0x' HexDigit+; +// ๆตฎ็‚นๅž‹ๅธธ้‡ +fragment Sign: [+-]; +fragment FracLit: Sign? Decimals '.' DecimalDigit*; +fragment SciLit: FracLit [eE] Sign? Decimals; +/* + HexFPConstant 0x{_hex_digit}+ // 16 hex digits + HexFP80Constant 0xK{_hex_digit}+ // 20 hex digits + HexFP128Constant 0xL{_hex_digit}+ // 32 hex digits + HexPPC128Constant 0xM{_hex_digit}+ // 32 hex + digits + HexHalfConstant 0xH{_hex_digit}+ // 4 hex digits + HexBFloatConstant 0xR{_hex_digit}+ // 4 + hex digits + */ +fragment FloatHexLit: '0x' [KLMHR]? HexDigit+; +fragment GlobalName: '@' (Name | QuotedString); +fragment GlobalId: '@' Id; +fragment LocalName: '%' (Name | QuotedString); +fragment LocalId: '%' Id; +fragment QuotedString: '"' (~["\r\n])* '"'; +Comment: ';' .*? '\r'? '\n' -> channel(HIDDEN); +WhiteSpace: [ \t\n\r]+ -> skip; +IntLit: '-'? DecimalDigit+ | IntHexLit; +FloatLit: FracLit | SciLit | FloatHexLit; +StringLit: QuotedString; +GlobalIdent: GlobalName | GlobalId; +LocalIdent: LocalName | LocalId; +LabelIdent: (Letter | DecimalDigit)+ ':' | QuotedString ':'; +AttrGroupId: '#' Id; +ComdatName: '$' (Name | QuotedString); +MetadataName: '!' EscapeName; +MetadataId: '!' Id; +IntType: 'i' DecimalDigit+; +DwarfTag: 'DW_TAG_' (AsciiLetter | DecimalDigit | '_')*; +DwarfAttEncoding: 'DW_ATE_' (AsciiLetter | DecimalDigit | '_')*; +DiFlag: 'DIFlag' (AsciiLetter | DecimalDigit | '_')*; +DispFlag: 'DISPFlag' (AsciiLetter | DecimalDigit | '_')*; +DwarfLang: 'DW_LANG_' (AsciiLetter | DecimalDigit | '_')*; +DwarfCc: 'DW_CC_' (AsciiLetter | DecimalDigit | '_')*; +ChecksumKind: 'CSK_' (AsciiLetter | DecimalDigit | '_')*; +DwarfVirtuality: + 'DW_VIRTUALITY_' (AsciiLetter | DecimalDigit | '_')*; +DwarfMacinfo: 'DW_MACINFO_' (AsciiLetter | DecimalDigit | '_')*; +DwarfOp: 'DW_OP_' (AsciiLetter | DecimalDigit | '_')*; diff --git a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRLanguage.java b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRLanguage.java new file mode 100644 index 000000000..8f0d65830 --- /dev/null +++ b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRLanguage.java @@ -0,0 +1,42 @@ +package de.jplag.llvmir; + +import org.kohsuke.MetaInfServices; + +import de.jplag.Language; +import de.jplag.antlr.AbstractAntlrLanguage; + +/** + * The entry point for the ANTLR parser based LLVM IR language module. + */ +@MetaInfServices(Language.class) +public class LLVMIRLanguage extends AbstractAntlrLanguage { + + private static final String NAME = "LLVMIR Parser"; + private static final String IDENTIFIER = "llvmir"; + private static final int DEFAULT_MIN_TOKEN_MATCH = 40; + private static final String[] FILE_EXTENSIONS = {".ll"}; + + public LLVMIRLanguage() { + super(new LLVMIRParserAdapter()); + } + + @Override + public String[] suffixes() { + return FILE_EXTENSIONS; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public int minimumTokenMatch() { + return DEFAULT_MIN_TOKEN_MATCH; + } +} diff --git a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRListener.java b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRListener.java new file mode 100644 index 000000000..24e70a9ca --- /dev/null +++ b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRListener.java @@ -0,0 +1,232 @@ +package de.jplag.llvmir; + +import static de.jplag.llvmir.LLVMIRTokenType.*; +import static de.jplag.llvmir.grammar.LLVMIRParser.AShrExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AShrInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AddExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AddInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AddrSpaceCastExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AddrSpaceCastInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AllocaInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AndExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AndInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ArrayConstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AtomicOrderingContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.AtomicRMWInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.BasicBlockContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.BitCastExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.BitCastInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.BrTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CallBrTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CallInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.Case_Context; +import static de.jplag.llvmir.grammar.LLVMIRParser.CatchPadInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CatchRetTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CatchSwitchTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ClauseContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CleanupPadInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CleanupRetTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CmpXchgInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.CondBrTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ExtractElementExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ExtractElementInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ExtractValueInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FAddInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FCmpExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FCmpInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FDivInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FMulInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FRemInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FSubInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FenceInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpExtExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpExtInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpToSiExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpToSiInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpToUiExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpToUiInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpTruncExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FpTruncInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FuncBodyContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FuncDeclContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.FuncDefContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.GetElementPtrExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.GetElementPtrInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.GlobalDeclContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.GlobalDefContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ICmpExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ICmpInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.IndirectBrTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.InlineAsmContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.InsertElementExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.InsertElementInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.InsertValueInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.IntToPtrExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.IntToPtrInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.InvokeTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.LShrExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.LShrInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.LandingPadInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.LoadInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ModuleAsmContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.MulExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.MulInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.OrExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.OrInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.PhiInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.PtrToIntExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.PtrToIntInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ResumeTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.RetTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SDivInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SExtExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SExtInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SRemInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SelectExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SelectInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ShlExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ShlInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ShuffleVectorExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ShuffleVectorInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SiToFpExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SiToFpInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SourceFilenameContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.StoreInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.StructConstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SubExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SubInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.SwitchTermContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.TruncExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.TruncInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.TypeDefContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.UDivInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.URemInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.UiToFpExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.UiToFpInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.VaargInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.VectorConstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.XorExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.XorInstContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ZExtExprContext; +import static de.jplag.llvmir.grammar.LLVMIRParser.ZExtInstContext; + +import de.jplag.antlr.AbstractAntlrListener; + +/** + * Extracts tokens from the ANTLR parse tree. The token abstraction includes nesting tokens for functions and basic + * blocks and separate tokens for different elements. These include binary and bitwise instructions, memory operations, + * terminator instructions, conversions, global variables, type definitions, constants, and others. + */ +class LLVMIRListener extends AbstractAntlrListener { + + LLVMIRListener() { + visit(SourceFilenameContext.class).map(FILENAME); + visit(ModuleAsmContext.class).map(ASSEMBLY); + visit(TypeDefContext.class).map(TYPE_DEFINITION); + visit(GlobalDeclContext.class).map(GLOBAL_VARIABLE); + visit(GlobalDefContext.class).map(GLOBAL_VARIABLE); + visit(FuncDeclContext.class).map(FUNCTION_DECLARATION); + visit(FuncDefContext.class).map(FUNCTION_DEFINITION); + visit(FuncBodyContext.class).map(FUNCTION_BODY_BEGIN, FUNCTION_BODY_END); + visit(BasicBlockContext.class).map(BASIC_BLOCK_BEGIN, BASIC_BLOCK_END); + visit(RetTermContext.class).map(RETURN); + visit(BrTermContext.class).map(BRANCH); + visit(CondBrTermContext.class).map(CONDITIONAL_BRANCH); + visit(SwitchTermContext.class).map(SWITCH); + visit(IndirectBrTermContext.class).map(BRANCH); + visit(ResumeTermContext.class).map(RESUME); + visit(CatchRetTermContext.class).map(CATCH_RETURN); + visit(CleanupRetTermContext.class).map(CLEAN_UP_RETURN); + visit(InvokeTermContext.class).map(INVOKE); + visit(CallBrTermContext.class).map(CALL_BRANCH); + visit(CatchSwitchTermContext.class).map(CATCH_SWITCH); + visit(Case_Context.class).map(CASE); + visit(StructConstContext.class).map(STRUCTURE); + visit(ArrayConstContext.class).map(ARRAY); + visit(VectorConstContext.class).map(VECTOR); + visit(InlineAsmContext.class).map(ASSEMBLY); + visit(BitCastExprContext.class).map(BITCAST); + visit(GetElementPtrExprContext.class).map(GET_ELEMENT_POINTER); + visit(AddrSpaceCastExprContext.class).map(CONVERSION); + visit(IntToPtrExprContext.class).map(CONVERSION); + visit(ICmpExprContext.class).map(COMPARISON); + visit(FCmpExprContext.class).map(COMPARISON); + visit(SelectExprContext.class).map(SELECT); + visit(TruncExprContext.class).map(CONVERSION); + visit(ZExtExprContext.class).map(CONVERSION); + visit(SExtExprContext.class).map(CONVERSION); + visit(FpTruncExprContext.class).map(CONVERSION); + visit(FpExtExprContext.class).map(CONVERSION); + visit(FpToUiExprContext.class).map(CONVERSION); + visit(FpToSiExprContext.class).map(CONVERSION); + visit(UiToFpExprContext.class).map(CONVERSION); + visit(SiToFpExprContext.class).map(CONVERSION); + visit(PtrToIntExprContext.class).map(CONVERSION); + visit(ExtractElementExprContext.class).map(EXTRACT_ELEMENT); + visit(InsertElementExprContext.class).map(INSERT_ELEMENT); + visit(ShuffleVectorExprContext.class).map(SHUFFLE_VECTOR); + visit(ShlExprContext.class).map(SHIFT); + visit(LShrExprContext.class).map(SHIFT); + visit(AShrExprContext.class).map(SHIFT); + visit(AndExprContext.class).map(AND); + visit(OrExprContext.class).map(OR); + visit(XorExprContext.class).map(XOR); + visit(AddExprContext.class).map(ADDITION); + visit(SubExprContext.class).map(SUBTRACTION); + visit(MulExprContext.class).map(MULTIPLICATION); + visit(StoreInstContext.class).map(STORE); + visit(FenceInstContext.class).map(FENCE); + visit(AddInstContext.class).map(ADDITION); + visit(FAddInstContext.class).map(ADDITION); + visit(SubInstContext.class).map(SUBTRACTION); + visit(FSubInstContext.class).map(SUBTRACTION); + visit(MulInstContext.class).map(MULTIPLICATION); + visit(FMulInstContext.class).map(MULTIPLICATION); + visit(UDivInstContext.class).map(DIVISION); + visit(SDivInstContext.class).map(DIVISION); + visit(FDivInstContext.class).map(DIVISION); + visit(URemInstContext.class).map(REMAINDER); + visit(SRemInstContext.class).map(REMAINDER); + visit(FRemInstContext.class).map(REMAINDER); + visit(ShlInstContext.class).map(SHIFT); + visit(LShrInstContext.class).map(SHIFT); + visit(AShrInstContext.class).map(SHIFT); + visit(AndInstContext.class).map(AND); + visit(OrInstContext.class).map(OR); + visit(XorInstContext.class).map(XOR); + visit(ExtractElementInstContext.class).map(EXTRACT_ELEMENT); + visit(InsertElementInstContext.class).map(INSERT_ELEMENT); + visit(ShuffleVectorInstContext.class).map(SHUFFLE_VECTOR); + visit(ExtractValueInstContext.class).map(EXTRACT_VALUE); + visit(InsertValueInstContext.class).map(INSERT_VALUE); + visit(AllocaInstContext.class).map(ALLOCATION); + visit(LoadInstContext.class).map(LOAD); + visit(CmpXchgInstContext.class).map(COMPARE_EXCHANGE); + visit(AtomicRMWInstContext.class).map(ATOMIC_READ_MODIFY_WRITE); + visit(GetElementPtrInstContext.class).map(GET_ELEMENT_POINTER); + visit(TruncInstContext.class).map(CONVERSION); + visit(ZExtInstContext.class).map(CONVERSION); + visit(SExtInstContext.class).map(CONVERSION); + visit(FpTruncInstContext.class).map(CONVERSION); + visit(FpExtInstContext.class).map(CONVERSION); + visit(FpToUiInstContext.class).map(CONVERSION); + visit(FpToSiInstContext.class).map(CONVERSION); + visit(UiToFpInstContext.class).map(CONVERSION); + visit(SiToFpInstContext.class).map(CONVERSION); + visit(PtrToIntInstContext.class).map(CONVERSION); + visit(IntToPtrInstContext.class).map(CONVERSION); + visit(BitCastInstContext.class).map(BITCAST); + visit(AddrSpaceCastInstContext.class).map(CONVERSION); + visit(ICmpInstContext.class).map(COMPARISON); + visit(FCmpInstContext.class).map(COMPARISON); + visit(PhiInstContext.class).map(PHI); + visit(SelectInstContext.class).map(SELECT); + visit(CallInstContext.class).map(CALL); + visit(VaargInstContext.class).map(VARIABLE_ARGUMENT); + visit(LandingPadInstContext.class).map(LANDING_PAD); + visit(CatchPadInstContext.class).map(CATCH_PAD); + visit(CleanupPadInstContext.class).map(CLEAN_UP_PAD); + visit(ClauseContext.class).map(CLAUSE); + visit(AtomicOrderingContext.class).map(ATOMIC_ORDERING); + } +} diff --git a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRParserAdapter.java b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRParserAdapter.java new file mode 100644 index 000000000..edbe94148 --- /dev/null +++ b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRParserAdapter.java @@ -0,0 +1,36 @@ +package de.jplag.llvmir; + +import org.antlr.v4.runtime.*; + +import de.jplag.AbstractParser; +import de.jplag.antlr.AbstractAntlrListener; +import de.jplag.antlr.AbstractAntlrParserAdapter; +import de.jplag.llvmir.grammar.LLVMIRLexer; +import de.jplag.llvmir.grammar.LLVMIRParser; + +/** + * The adapter between {@link AbstractParser} and the ANTLR based parser of this language module. + */ +public class LLVMIRParserAdapter extends AbstractAntlrParserAdapter { + private static final LLVMIRListener listener = new LLVMIRListener(); + + @Override + protected Lexer createLexer(CharStream input) { + return new LLVMIRLexer(input); + } + + @Override + protected LLVMIRParser createParser(CommonTokenStream tokenStream) { + return new LLVMIRParser(tokenStream); + } + + @Override + protected ParserRuleContext getEntryContext(LLVMIRParser parser) { + return parser.compilationUnit(); + } + + @Override + protected AbstractAntlrListener getListener() { + return listener; + } +} diff --git a/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRTokenType.java b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRTokenType.java new file mode 100644 index 000000000..0935fa4cc --- /dev/null +++ b/languages/llvmir/src/main/java/de/jplag/llvmir/LLVMIRTokenType.java @@ -0,0 +1,99 @@ +package de.jplag.llvmir; + +import de.jplag.TokenType; + +/** + * LLVM IR token types extracted by this language module. + */ +public enum LLVMIRTokenType implements TokenType { + + FILENAME("FILENAME"), + + // Functions + FUNCTION_BODY_BEGIN("FUNC{"), + FUNCTION_BODY_END("}FUNC"), + BASIC_BLOCK_BEGIN("BLOCK{"), + BASIC_BLOCK_END("}BLOCK"), + FUNCTION_DECLARATION("FUNC_DECL"), + FUNCTION_DEFINITION("FUNC_DEF"), + + GLOBAL_VARIABLE("GLOB_VAR"), + ASSEMBLY("ASM"), + TYPE_DEFINITION("TYPE_DEF"), + + // Constants + STRUCTURE("STRUCT"), + ARRAY("ARR"), + VECTOR("VEC"), + + // Terminator Instructions + RETURN("RET"), + BRANCH("BR"), + SWITCH("SWITCH"), + CASE("CASE"), + CONDITIONAL_BRANCH("COND_BR"), + INVOKE("INVOKE"), + CALL_BRANCH("CALL_BR"), + RESUME("RESUME"), + CATCH_SWITCH("CATCH_SWITCH"), + CATCH_RETURN("CATCH_RET"), + CLEAN_UP_RETURN("CLEAN_UP_RET"), + + // Binary Operations + ADDITION("ADD"), + SUBTRACTION("SUB"), + MULTIPLICATION("MUL"), + DIVISION("DIV"), + REMAINDER("REM"), + + // Bitwise instruction + SHIFT("SHIFT"), + AND("AND"), + OR("OR"), + XOR("XOR"), + + // Vector operations + EXTRACT_ELEMENT("EXTRACT_ELEM"), + INSERT_ELEMENT("INSERT_ELEM"), + SHUFFLE_VECTOR("SHUFFLE_VEC"), + + // Aggregate Operations + EXTRACT_VALUE("EXTRACT_VAL"), + INSERT_VALUE("INSERT_VAL"), + + // Memory Operations + ALLOCATION("ALLOC"), + LOAD("LOAD"), + STORE("STORE"), + FENCE("FENCE"), + COMPARE_EXCHANGE("CMP_XCHG"), + ATOMIC_READ_MODIFY_WRITE("ATOMIC_RMW"), + ATOMIC_ORDERING("ATOMIC"), + GET_ELEMENT_POINTER("GET_ELEMENT_PTR"), + + // Conversion Operations + BITCAST("BITCAST"), + CONVERSION("CONV"), + + // Other Operations + COMPARISON("COMP"), + PHI("PHI"), + SELECT("SELECT"), + CALL("CALL"), + VARIABLE_ARGUMENT("VA_ARG"), + LANDING_PAD("LANDING_PAD"), + CLAUSE("CLAUSE"), + CATCH_PAD("CATCH_PAD"), + CLEAN_UP_PAD("CLEAN_UP_PAD"); + + private final String description; + + LLVMIRTokenType(String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/languages/llvmir/src/test/java/de/jplag/llvmir/LLVMIRLanguageTest.java b/languages/llvmir/src/test/java/de/jplag/llvmir/LLVMIRLanguageTest.java new file mode 100644 index 000000000..b34956af9 --- /dev/null +++ b/languages/llvmir/src/test/java/de/jplag/llvmir/LLVMIRLanguageTest.java @@ -0,0 +1,42 @@ +package de.jplag.llvmir; + +import static de.jplag.llvmir.LLVMIRTokenType.*; + +import java.util.Arrays; +import java.util.List; + +import de.jplag.testutils.LanguageModuleTest; +import de.jplag.testutils.datacollector.TestDataCollector; +import de.jplag.testutils.datacollector.TestSourceIgnoredLinesCollector; + +/** + * Provides tests for the llvmir language module + */ +class LLVMIRLanguageTest extends LanguageModuleTest { + public LLVMIRLanguageTest() { + super(new LLVMIRLanguage(), LLVMIRTokenType.class); + } + + @Override + protected void collectTestData(TestDataCollector collector) { + List missingTokens = List.of(CATCH_SWITCH, CATCH_RETURN, CLEAN_UP_RETURN, CATCH_PAD, CLEAN_UP_PAD); + LLVMIRTokenType[] expectedTokens = Arrays.stream(LLVMIRTokenType.values()).filter(it -> !missingTokens.contains(it)) + .toArray(LLVMIRTokenType[]::new); + + collector.testFile("Complete.ll").testSourceCoverage().testContainedTokens(expectedTokens); + + // Finding an example for the new exception handling instructions was difficult. + // Therefore, the NewExceptionHandling.ll file can only be parsed and not executed. + collector.testFile("NewExceptionHandling.ll").testSourceCoverage().testContainedTokens(CATCH_SWITCH, CATCH_RETURN, CLEAN_UP_RETURN, CATCH_PAD, + CLEAN_UP_PAD); + + } + + @Override + protected void configureIgnoredLines(TestSourceIgnoredLinesCollector collector) { + collector.ignoreLinesByPrefix(";"); + collector.ignoreLinesByPrefix("target datalayout"); + collector.ignoreLinesByPrefix("target triple"); + collector.ignoreLinesByPrefix("unreachable"); + } +} \ No newline at end of file diff --git a/languages/llvmir/src/test/resources/de/jplag/llvmir/Complete.ll b/languages/llvmir/src/test/resources/de/jplag/llvmir/Complete.ll new file mode 100644 index 000000000..2ccaf60ee --- /dev/null +++ b/languages/llvmir/src/test/resources/de/jplag/llvmir/Complete.ll @@ -0,0 +1,161 @@ +; ModuleID = 'Complete.c' +source_filename = "Complete.c" +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-macosx13.0.0" + +@Global_Var = private unnamed_addr constant [14 x i8] c"Hello World!\0A\00", align 1 +@struct.const = private constant {i32, double} {i32 4, double 8.12} + +%struct.Struct = type { i32 } +@_ZTIi = external constant ptr + +; Function Attrs: noinline nounwind optnone ssp uwtable +define i32 @main() #0 { + %1 = alloca i32, align 4 + %2 = alloca i32, align 4 + %3 = alloca i32, align 4 + %4 = alloca float, align 4 + %5 = alloca %struct.Struct, align 4 + store i32 0, ptr %1, align 4 + store i32 4, ptr %2, align 4 + %6 = load i32, ptr %2, align 4 + %7 = add nsw i32 5, %6 + store i32 %7, ptr %3, align 4 + %8 = load i32, ptr %2, align 4 + %9 = sub nsw i32 5, %8 + store i32 %9, ptr %3, align 4 + %10 = load i32, ptr %2, align 4 + %11 = mul nsw i32 5, %10 + store i32 %11, ptr %3, align 4 + %12 = load i32, ptr %2, align 4 + %13 = sdiv i32 5, %12 + %14 = sitofp i32 %13 to float + store float %14, ptr %4, align 4 + %15 = load i32, ptr %2, align 4 + %16 = srem i32 5, %15 + store i32 %16, ptr %3, align 4 + %17 = load i32, ptr %2, align 4 + %18 = shl i32 %17, 5 + store i32 %18, ptr %3, align 4 + %19 = load i32, ptr %2, align 4 + %20 = and i32 %19, 5 + store i32 %20, ptr %3, align 4 + %21 = load i32, ptr %2, align 4 + %22 = or i32 %21, 5 + store i32 %22, ptr %3, align 4 + %23 = load i32, ptr %2, align 4 + %24 = xor i32 %23, 5 + store i32 %24, ptr %3, align 4 + %25 = call i32 (ptr, ...) @printf(ptr noundef @Global_Var) + %cast = bitcast i8 255 to i8 + br label %vectors + +vectors: + %26 = call i32 @vector(<4 x i32> , <4 x i32> ) + switch i32 %26, label %vectors [ i32 10, label %aggregates + i32 1, label %vectors + i32 2, label %vectors ] + +aggregates: + %struc = insertvalue {i32} undef, i32 1, 0 + %27 = extractvalue {i32} %struc, 0 + callbr void asm "", "r,!i"(i32 0) to label %memory [label %aggregates] + +memory: + fence acquire + br label %entry + +entry: + %28 = getelementptr inbounds %struct.Struct, ptr %5, i32 0, i32 0 + %tmp = va_arg ptr %28, i32 + store i32 1, ptr %28, align 4 + %old = atomicrmw add ptr %28, i32 1 acquire + %orig = load atomic i32, ptr %28 unordered, align 4 + br label %loop + +loop: + %cmp = phi i32 [ %orig, %entry ], [%value_loaded, %loop] + %squared = mul i32 %cmp, %cmp + %val_success = cmpxchg ptr %28, i32 %cmp, i32 %squared acq_rel monotonic + %value_loaded = extractvalue { i32, i1 } %val_success, 0 + %success = extractvalue { i32, i1 } %val_success, 1 + br i1 %success, label %done, label %loop + +done: + %false = icmp eq i32 4, 5 + %first = select i1 true, i8 17, i8 42 + call void @Exception() + ret i32 0 +} + +define i32 @vector(<4 x i32> %v1, <4 x i32> %v2) { + %vec = shufflevector <4 x i32> %v1, <4 x i32> %v2, <4 x i32> + %vec_ins = insertelement <4 x i32> %vec, i32 10, i32 0 + %elem = extractelement <4 x i32> %vec_ins, i32 0 + ret i32 %elem +} + +; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable +define noundef i32 @Exception() personality ptr @__gxx_personality_v0 { + %1 = alloca i32, align 4 + %2 = alloca ptr, align 8 + %3 = alloca i32, align 4 + %4 = alloca i32, align 4 + store i32 0, ptr %1, align 4 + %5 = call ptr @__cxa_allocate_exception(i64 4) + store i32 5, ptr %5, align 16 + invoke void @__cxa_throw(ptr %5, ptr @_ZTIi, ptr null) to label %25 unwind label %6 + +6: + %7 = landingpad { ptr, i32 } + catch ptr @_ZTIi + %8 = extractvalue { ptr, i32 } %7, 0 + store ptr %8, ptr %2, align 8 + %9 = extractvalue { ptr, i32 } %7, 1 + store i32 %9, ptr %3, align 4 + br label %10 + +10: + %11 = load i32, ptr %3, align 4 + %12 = call i32 @llvm.eh.typeid.for(ptr @_ZTIi) + %13 = icmp eq i32 %11, %12 + br i1 %13, label %14, label %20 + +14: + %15 = load ptr, ptr %2, align 8 + %16 = call ptr @__cxa_begin_catch(ptr %15) + %17 = load i32, ptr %16, align 4 + store i32 %17, ptr %4, align 4 + store i32 0, ptr %1, align 4 + call void @__cxa_end_catch() + br label %18 + +18: + %19 = load i32, ptr %1, align 4 + ret i32 %19 + +20: + %21 = load ptr, ptr %2, align 8 + %22 = load i32, ptr %3, align 4 + %23 = insertvalue { ptr, i32 } undef, ptr %21, 0 + %24 = insertvalue { ptr, i32 } %23, i32 %22, 1 + resume { ptr, i32 } %24 + +25: + unreachable +} + +declare i32 @printf(ptr noundef, ...) + +declare ptr @__cxa_allocate_exception(i64) + +declare void @__cxa_throw(ptr, ptr, ptr) + +declare i32 @__gxx_personality_v0(...) + +; Function Attrs: nounwind readnone +declare i32 @llvm.eh.typeid.for(ptr) + +declare ptr @__cxa_begin_catch(ptr) + +declare void @__cxa_end_catch() \ No newline at end of file diff --git a/languages/llvmir/src/test/resources/de/jplag/llvmir/NewExceptionHandling.ll b/languages/llvmir/src/test/resources/de/jplag/llvmir/NewExceptionHandling.ll new file mode 100644 index 000000000..85f0f02e8 --- /dev/null +++ b/languages/llvmir/src/test/resources/de/jplag/llvmir/NewExceptionHandling.ll @@ -0,0 +1,41 @@ +define i32 @f() nounwind personality i32 (...)* @__CxxFrameHandler3 { +entry: + %obj = alloca %struct.Cleanup, align 4 + %e = alloca i32, align 4 + %call = invoke %struct.Cleanup* @"??0Cleanup@@QEAA@XZ"(%struct.Cleanup* nonnull %obj) + to label %invoke.cont unwind label %lpad.catch + +invoke.cont: ; preds = %entry + invoke void @"?may_throw@@YAXXZ"() + to label %invoke.cont.2 unwind label %lpad.cleanup + +invoke.cont.2: ; preds = %invoke.cont + call void @"??_DCleanup@@QEAA@XZ"(%struct.Cleanup* nonnull %obj) nounwind + br label %return + +return: ; preds = %invoke.cont.3, %invoke.cont.2 + %retval.0 = phi i32 [ 0, %invoke.cont.2 ], [ %3, %invoke.cont.3 ] + ret i32 %retval.0 + +lpad.cleanup: ; preds = %invoke.cont.2 + %0 = cleanuppad within none [] + call void @"??1Cleanup@@QEAA@XZ"(%struct.Cleanup* nonnull %obj) nounwind + cleanupret from %0 unwind label %lpad.catch + +lpad.catch: ; preds = %lpad.cleanup, %entry + %1 = catchswitch within none [label %catch.body] unwind label %lpad.terminate + +catch.body: ; preds = %lpad.catch + %catch = catchpad within %1 [%rtti.TypeDescriptor2* @"??_R0H@8", i32 0, i32* %e] + invoke void @"?may_throw@@YAXXZ"() + to label %invoke.cont.3 unwind label %lpad.terminate + +invoke.cont.3: ; preds = %catch.body + %2 = load i32, i32* %e, align 4 + catchret from %catch to label %return + +lpad.terminate: ; preds = %catch.body, %lpad.catch + cleanuppad within none [] + call void @"?terminate@@YAXXZ"() + unreachable +} \ No newline at end of file diff --git a/languages/pom.xml b/languages/pom.xml index f241a3205..203275f10 100644 --- a/languages/pom.xml +++ b/languages/pom.xml @@ -26,6 +26,8 @@ scxml swift text + typescript + llvmir diff --git a/languages/python-3/src/main/java/de/jplag/python3/Python3TokenType.java b/languages/python-3/src/main/java/de/jplag/python3/Python3TokenType.java index a692031be..e4a684c9b 100644 --- a/languages/python-3/src/main/java/de/jplag/python3/Python3TokenType.java +++ b/languages/python-3/src/main/java/de/jplag/python3/Python3TokenType.java @@ -36,6 +36,7 @@ public enum Python3TokenType implements TokenType { private final String description; + @Override public String getDescription() { return this.description; } diff --git a/languages/rlang/src/main/java/de/jplag/rlang/RTokenType.java b/languages/rlang/src/main/java/de/jplag/rlang/RTokenType.java index a70a08607..3a58935fb 100644 --- a/languages/rlang/src/main/java/de/jplag/rlang/RTokenType.java +++ b/languages/rlang/src/main/java/de/jplag/rlang/RTokenType.java @@ -35,6 +35,7 @@ public enum RTokenType implements TokenType { private final String description; + @Override public String getDescription() { return this.description; } diff --git a/languages/rust/src/main/java/de/jplag/rust/RustTokenType.java b/languages/rust/src/main/java/de/jplag/rust/RustTokenType.java index 8e744c37b..4655b9525 100644 --- a/languages/rust/src/main/java/de/jplag/rust/RustTokenType.java +++ b/languages/rust/src/main/java/de/jplag/rust/RustTokenType.java @@ -114,6 +114,7 @@ public enum RustTokenType implements TokenType { private final String description; + @Override public String getDescription() { return description; } diff --git a/languages/scala/pom.xml b/languages/scala/pom.xml index 02fc41aa2..8eb11b59a 100644 --- a/languages/scala/pom.xml +++ b/languages/scala/pom.xml @@ -10,7 +10,7 @@ scala - 2.13.11 + 2.13.12 2.13 @@ -25,7 +25,7 @@ org.scalameta scalameta_${scala.compat.version} - 4.8.3 + 4.8.11 diff --git a/languages/scala/src/main/scala/de/jplag/scala/ScalaTokenType.java b/languages/scala/src/main/scala/de/jplag/scala/ScalaTokenType.java index a21a5980e..a84255d91 100644 --- a/languages/scala/src/main/scala/de/jplag/scala/ScalaTokenType.java +++ b/languages/scala/src/main/scala/de/jplag/scala/ScalaTokenType.java @@ -74,6 +74,7 @@ public enum ScalaTokenType implements TokenType { private final String description; + @Override public String getDescription() { return description; } diff --git a/languages/scheme/src/main/java/de/jplag/scheme/SchemeTokenType.java b/languages/scheme/src/main/java/de/jplag/scheme/SchemeTokenType.java index 8b2109ee1..a041d46d8 100644 --- a/languages/scheme/src/main/java/de/jplag/scheme/SchemeTokenType.java +++ b/languages/scheme/src/main/java/de/jplag/scheme/SchemeTokenType.java @@ -47,6 +47,7 @@ public enum SchemeTokenType implements TokenType { private final String description; + @Override public String getDescription() { return this.description; } diff --git a/languages/scxml/src/main/java/de/jplag/scxml/ScxmlTokenType.java b/languages/scxml/src/main/java/de/jplag/scxml/ScxmlTokenType.java index 98ef60f4a..ad99427cb 100644 --- a/languages/scxml/src/main/java/de/jplag/scxml/ScxmlTokenType.java +++ b/languages/scxml/src/main/java/de/jplag/scxml/ScxmlTokenType.java @@ -152,6 +152,7 @@ public enum ScxmlTokenType implements TokenType { /** * @return the description for this token type */ + @Override public String getDescription() { return description; } diff --git a/languages/swift/src/main/java/de/jplag/swift/SwiftTokenType.java b/languages/swift/src/main/java/de/jplag/swift/SwiftTokenType.java index 3b69cb6eb..0bfc07640 100644 --- a/languages/swift/src/main/java/de/jplag/swift/SwiftTokenType.java +++ b/languages/swift/src/main/java/de/jplag/swift/SwiftTokenType.java @@ -53,6 +53,7 @@ public enum SwiftTokenType implements TokenType { private final String description; + @Override public String getDescription() { return description; } diff --git a/languages/text/src/main/java/de/jplag/text/TextTokenType.java b/languages/text/src/main/java/de/jplag/text/TextTokenType.java index 1fc2277d9..23b3ce893 100644 --- a/languages/text/src/main/java/de/jplag/text/TextTokenType.java +++ b/languages/text/src/main/java/de/jplag/text/TextTokenType.java @@ -7,6 +7,7 @@ public TextTokenType(String description) { this.description = description.toLowerCase(); } + @Override public String getDescription() { return this.description; } diff --git a/languages/text/src/test/java/jplag/text/NaturalLanguageTest.java b/languages/text/src/test/java/jplag/text/NaturalLanguageTest.java index b31c65b0f..9e16e4532 100644 --- a/languages/text/src/test/java/jplag/text/NaturalLanguageTest.java +++ b/languages/text/src/test/java/jplag/text/NaturalLanguageTest.java @@ -13,7 +13,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; @@ -54,19 +53,19 @@ void testParsingJavaDoc() throws ParsingException { @ParameterizedTest @ValueSource(strings = {"\n", "\r", "\r\n",}) - void testLineBreakInputs(String input, @TempDir Path tempDir) throws IOException, ParsingException { - Path filePath = tempDir.resolve("input.txt"); - Files.writeString(filePath, input); - List result = language.parse(Set.of(filePath.toFile())); + void testLineBreakInputs(String input) throws IOException, ParsingException { + File testFile = File.createTempFile("input", "txt"); + Files.writeString(testFile.toPath(), input); + List result = language.parse(Set.of(testFile)); assertEquals(1, result.size()); } @ParameterizedTest @ValueSource(strings = {"\ntoken", "\rtoken", "\r\ntoken",}) - void testTokenAfterLineBreak(String input, @TempDir Path tempDir) throws IOException, ParsingException { - Path filePath = tempDir.resolve("input.txt"); - Files.writeString(filePath, input); - List result = language.parse(Set.of(filePath.toFile())); + void testTokenAfterLineBreak(String input) throws IOException, ParsingException { + File testFile = File.createTempFile("input", "txt"); + Files.writeString(testFile.toPath(), input); + List result = language.parse(Set.of(testFile)); assertEquals(2, result.get(0).getLine()); } diff --git a/languages/typescript/README.md b/languages/typescript/README.md new file mode 100644 index 000000000..351bc2da2 --- /dev/null +++ b/languages/typescript/README.md @@ -0,0 +1,24 @@ +# JPlag TypeScript language module +Due to TypeScript being a superset of JavaScript this frontend can also parse JavaScript files. +
+The JPlag TypeScript module allows the use of JPlag with submissions in TypeScript.
+It is based on the [TypeScript ANTLR4 grammar](https://github.com/antlr/grammars-v4/tree/master/javascript/typescript), licensed under the Apache 2.0. + + +### TypeScript specification compatibility +> This TypeScript grammar does not exactly correspond to the TypeScript standard. The main goal during developing was practical usage, performance, and clarity (getting rid of duplicates). + +Since the grammar has no support for decorators the version can be estimated < 5.0. The grammar can still parse files with decorators, but can not extract a tokens for them. +
The grammar can parse multiple language features from version 4.x. +
Because of this the version is still given as an estimated v5. + +If there are any major updates or fixes to the grammar1, they should surely be applied to this module as well. + +### Token Extraction +The choice of tokens is intended to be similar to the Java or Python modules. It includes a range of nesting structures (class, method, control flow expressions) as well as variable declaration, object creation and assignment. + +### Usage +To use the TypeScript module, use the `typescript` subcommand in the CLI, or use a `JPlagOption` object with `new de.jplag.typescript.TypeScriptLanguage()` as `language` in the Java API as described in the usage information in the [readme of the main project](https://github.com/jplag/JPlag#usage) and [in the wiki](https://github.com/jplag/JPlag/wiki/1.-How-to-Use-JPlag). + +#### Footnotes +
1 The grammar files are taken from grammar-v4, with the most recent modification in commit 768b12e from March 2023.
\ No newline at end of file diff --git a/languages/typescript/pom.xml b/languages/typescript/pom.xml new file mode 100644 index 000000000..f866687aa --- /dev/null +++ b/languages/typescript/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + de.jplag + languages + ${revision} + + typescript + + + + org.antlr + antlr4-runtime + + + de.jplag + language-antlr-utils + ${revision} + compile + + + + + + + org.antlr + antlr4-maven-plugin + + + + antlr4 + + + + + + + diff --git a/languages/typescript/src/main/antlr4/de/jplag/typescript/grammar/TypeScriptLexer.g4 b/languages/typescript/src/main/antlr4/de/jplag/typescript/grammar/TypeScriptLexer.g4 new file mode 100644 index 000000000..3f2d34d80 --- /dev/null +++ b/languages/typescript/src/main/antlr4/de/jplag/typescript/grammar/TypeScriptLexer.g4 @@ -0,0 +1,309 @@ +/// This grammer was slightly modified, so the generated code fit the JPlag code style +lexer grammar TypeScriptLexer; + +channels { ERROR } + +options { + superClass=TypeScriptLexerBase; +} + + +MultiLineComment: '/*' .*? '*/' -> channel(HIDDEN); +SingleLineComment: '//' ~[\r\n\u2028\u2029]* -> channel(HIDDEN); +RegularExpressionLiteral: '/' RegularExpressionFirstChar RegularExpressionChar* {this.isRegexPossible()}? '/' IdentifierPart*; + +OpenBracket: '['; +CloseBracket: ']'; +OpenParen: '('; +CloseParen: ')'; +OpenBrace: '{' {this.processOpenBrace();}; +TemplateCloseBrace: {this.isInTemplateString()}? '}' -> popMode; +CloseBrace: '}' {this.processCloseBrace();}; +SemiColon: ';'; +Comma: ','; +Assign: '='; +QuestionMark: '?'; +Colon: ':'; +Ellipsis: '...'; +Dot: '.'; +PlusPlus: '++'; +MinusMinus: '--'; +Plus: '+'; +Minus: '-'; +BitNot: '~'; +Not: '!'; +Multiply: '*'; +Divide: '/'; +Modulus: '%'; +RightShiftArithmetic: '>>'; +LeftShiftArithmetic: '<<'; +RightShiftLogical: '>>>'; +LessThan: '<'; +MoreThan: '>'; +LessThanEquals: '<='; +GreaterThanEquals: '>='; +Equals_: '=='; +NotEquals: '!='; +IdentityEquals: '==='; +IdentityNotEquals: '!=='; +BitAnd: '&'; +BitXOr: '^'; +BitOr: '|'; +And: '&&'; +Or: '||'; +MultiplyAssign: '*='; +DivideAssign: '/='; +ModulusAssign: '%='; +PlusAssign: '+='; +MinusAssign: '-='; +LeftShiftArithmeticAssign: '<<='; +RightShiftArithmeticAssign: '>>='; +RightShiftLogicalAssign: '>>>='; +BitAndAssign: '&='; +BitXorAssign: '^='; +BitOrAssign: '|='; +ARROW: '=>'; + +/// Null Literals + +NullLiteral: 'null'; + +/// Boolean Literals + +BooleanLiteral: 'true' + | 'false'; + +/// Numeric Literals + +DecimalLiteral: DecimalIntegerLiteral '.' [0-9]* ExponentPart? + | '.' [0-9]+ ExponentPart? + | DecimalIntegerLiteral ExponentPart? + ; + +/// Numeric Literals + +HexIntegerLiteral: '0' [xX] HexDigit+; +OctalIntegerLiteral: '0' [0-7]+ {!this.isStrictMode()}?; +OctalIntegerLiteral2: '0' [oO] [0-7]+; +BinaryIntegerLiteral: '0' [bB] [01]+; + +/// Keywords + +Break: 'break'; +Do: 'do'; +Instanceof: 'instanceof'; +Typeof: 'typeof'; +Case: 'case'; +Else: 'else'; +New: 'new'; +Var: 'var'; +Catch: 'catch'; +Finally: 'finally'; +Return: 'return'; +Void: 'void'; +Continue: 'continue'; +For: 'for'; +Switch: 'switch'; +While: 'while'; +Debugger: 'debugger'; +Function_: 'function'; +This: 'this'; +With: 'with'; +Default: 'default'; +If: 'if'; +Throw: 'throw'; +Delete: 'delete'; +In: 'in'; +Try: 'try'; +As: 'as'; +From: 'from'; +ReadOnly: 'readonly'; +Async: 'async'; + +/// Future Reserved Words + +Class: 'class'; +Enum: 'enum'; +Extends: 'extends'; +Super: 'super'; +Const: 'const'; +Export: 'export'; +Import: 'import'; + +/// The following tokens are also considered to be FutureReservedWords +/// when parsing strict mode + +Implements: 'implements' ; +Let: 'let' ; +Private: 'private' ; +Public: 'public' ; +Interface: 'interface' ; +Package: 'package' ; +Protected: 'protected' ; +Static: 'static' ; +Yield: 'yield' ; + +//keywords: + +Any : 'any'; +Number: 'number'; +Boolean: 'boolean'; +String: 'string'; +Symbol: 'symbol'; + + +TypeAlias : 'type'; + +Get: 'get'; +Set: 'set'; + +Constructor: 'constructor'; +Namespace: 'namespace'; +Require: 'require'; +Module: 'module'; +Declare: 'declare'; + +Abstract: 'abstract'; + +Is: 'is'; + +// +// Ext.2 Additions to 1.8: Decorators +// +At: '@'; + +/// Identifier Names and Identifiers + +Identifier: IdentifierStart IdentifierPart*; + +/// String Literals +StringLiteral: ('"' DoubleStringCharacter* '"' + | '\'' SingleStringCharacter* '\'') {this.processStringLiteral();} + ; + +BackTick: '`' {this.increaseTemplateDepth();} -> pushMode(TEMPLATE); + +WhiteSpaces: [\t\u000B\u000C\u0020\u00A0]+ -> channel(HIDDEN); + +LineTerminator: [\r\n\u2028\u2029] -> channel(HIDDEN); + +/// Comments + + +HtmlComment: '' -> channel(HIDDEN); +CDataComment: '' -> channel(HIDDEN); +UnexpectedCharacter: . -> channel(ERROR); + +mode TEMPLATE; + +TemplateStringEscapeAtom: '\\' .; +BackTickInside: '`' {this.decreaseTemplateDepth();} -> type(BackTick), popMode; +TemplateStringStartExpression: '${' {this.startTemplateString();} -> pushMode(DEFAULT_MODE); +TemplateStringAtom: ~[`\\]; + +// Fragment rules + +fragment DoubleStringCharacter + : ~["\\\r\n] + | '\\' EscapeSequence + | LineContinuation + ; + +fragment SingleStringCharacter + : ~['\\\r\n] + | '\\' EscapeSequence + | LineContinuation + ; + +fragment EscapeSequence + : CharacterEscapeSequence + | '0' // no digit ahead! TODO + | HexEscapeSequence + | UnicodeEscapeSequence + | ExtendedUnicodeEscapeSequence + ; + +fragment CharacterEscapeSequence + : SingleEscapeCharacter + | NonEscapeCharacter + ; + +fragment HexEscapeSequence + : 'x' HexDigit HexDigit + ; + +fragment UnicodeEscapeSequence + : 'u' HexDigit HexDigit HexDigit HexDigit + ; + +fragment ExtendedUnicodeEscapeSequence + : 'u' '{' HexDigit+ '}' + ; + +fragment SingleEscapeCharacter + : ['"\\bfnrtv] + ; + +fragment NonEscapeCharacter + : ~['"\\bfnrtv0-9xu\r\n] + ; + +fragment EscapeCharacter + : SingleEscapeCharacter + | [0-9] + | [xu] + ; + +fragment LineContinuation + : '\\' [\r\n\u2028\u2029] + ; + +fragment HexDigit + : [0-9a-fA-F] + ; + +fragment DecimalIntegerLiteral + : '0' + | [1-9] [0-9]* + ; + +fragment ExponentPart + : [eE] [+-]? [0-9]+ + ; + +fragment IdentifierPart + : IdentifierStart + | [\p{Mn}] + | [\p{Nd}] + | [\p{Pc}] + | '\u200C' + | '\u200D' + ; + +fragment IdentifierStart + : [\p{L}] + | [$_] + | '\\' UnicodeEscapeSequence + ; + +fragment RegularExpressionFirstChar + : ~[*\r\n\u2028\u2029\\/[] + | RegularExpressionBackslashSequence + | '[' RegularExpressionClassChar* ']' + ; + +fragment RegularExpressionChar + : ~[\r\n\u2028\u2029\\/[] + | RegularExpressionBackslashSequence + | '[' RegularExpressionClassChar* ']' + ; + +fragment RegularExpressionClassChar + : ~[\r\n\u2028\u2029\]\\] + | RegularExpressionBackslashSequence + ; + +fragment RegularExpressionBackslashSequence + : '\\' ~[\r\n\u2028\u2029] + ; + diff --git a/languages/typescript/src/main/antlr4/de/jplag/typescript/grammar/TypeScriptParser.g4 b/languages/typescript/src/main/antlr4/de/jplag/typescript/grammar/TypeScriptParser.g4 new file mode 100644 index 000000000..6e81b63ea --- /dev/null +++ b/languages/typescript/src/main/antlr4/de/jplag/typescript/grammar/TypeScriptParser.g4 @@ -0,0 +1,854 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 by Bart Kiers (original author) and Alexandre Vitorelli (contributor -> ported to CSharp) + * Copyright (c) 2017 by Ivan Kochurkin (Positive Technologies): + added ECMAScript 6 support, cleared and transformed to the universal grammar. + * Copyright (c) 2018 by Juan Alvarez (contributor -> ported to Go) + * Copyright (c) 2019 by Andrii Artiushok (contributor -> added TypeScript support) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +parser grammar TypeScriptParser; + +options { + tokenVocab=TypeScriptLexer; + superClass=TypeScriptParserBase; +} + +// SupportSyntax + +initializer + : '=' singleExpression + ; + +bindingPattern + : (arrayLiteral | objectLiteral) + ; + +// TypeScript SPart +// A.1 Types + +typeParameters + : '<' typeParameterList? '>' + ; + +typeParameterList + : typeParameter (',' typeParameter)* + ; + +typeParameter + : Identifier constraint? + | typeParameters + ; + +constraint + : 'extends' type_ + ; + +typeArguments + : '<' typeArgumentList? '>' + ; + +typeArgumentList + : typeArgument (',' typeArgument)* + ; + +typeArgument + : type_ + ; + +type_ + : unionOrIntersectionOrPrimaryType + | functionType + | constructorType + | typeGeneric + | StringLiteral + ; + +unionOrIntersectionOrPrimaryType + : unionOrIntersectionOrPrimaryType '|' unionOrIntersectionOrPrimaryType #Union + | unionOrIntersectionOrPrimaryType '&' unionOrIntersectionOrPrimaryType #Intersection + | primaryType #Primary + ; + +primaryType + : '(' type_ ')' #ParenthesizedPrimType + | predefinedType #PredefinedPrimType + | typeReference #ReferencePrimType + | objectType #ObjectPrimType + | primaryType {notLineTerminator()}? '[' ']' #ArrayPrimType + | '[' tupleElementTypes ']' #TuplePrimType + | typeQuery #QueryPrimType + | This #ThisPrimType + | typeReference Is primaryType #RedefinitionOfType + ; + +predefinedType + : Any + | Number + | Boolean + | String + | Symbol + | Void + ; + +typeReference + : typeName nestedTypeGeneric? + ; + +nestedTypeGeneric + : typeIncludeGeneric + | typeGeneric + ; + +// I tried recursive include, but it's not working. +// typeGeneric +// : '<' typeArgumentList typeGeneric?'>' +// ; +// +// TODO: Fix recursive +// +typeGeneric + : '<' typeArgumentList '>' + ; + +typeIncludeGeneric + :'<' typeArgumentList '<' typeArgumentList ('>' bindingPattern '>' | '>>') + ; + +typeName + : Identifier + | namespaceName + ; + +objectType + : '{' typeBody? '}' + ; + +typeBody + : typeMemberList (SemiColon | ',')? + ; + +typeMemberList + : typeMember ((SemiColon | ',') typeMember)* + ; + +typeMember + : propertySignatur + | callSignature + | constructSignature + | indexSignature + | methodSignature ('=>' type_)? + ; + +arrayType + : primaryType {notLineTerminator()}? '[' ']' + ; + +tupleType + : '[' tupleElementTypes ']' + ; + +tupleElementTypes + : type_ (',' type_)* + ; + +functionType + : typeParameters? '(' parameterList? ')' '=>' type_ + ; + +constructorType + : 'new' typeParameters? '(' parameterList? ')' '=>' type_ + ; + +typeQuery + : 'typeof' typeQueryExpression + ; + +typeQueryExpression + : Identifier + | (identifierName '.')+ identifierName + ; + +propertySignatur + : ReadOnly? propertyName '?'? typeAnnotation? ('=>' type_)? + ; + +typeAnnotation + : ':' type_ + ; + +callSignature + : typeParameters? '(' parameterList? ')' typeAnnotation? + ; + +parameterList + : restParameter + | parameter (',' parameter)* (',' restParameter)? + ; + +requiredParameterList + : requiredParameter (',' requiredParameter)* + ; + +parameter + : requiredParameter + | optionalParameter + ; + +optionalParameter + : decoratorList? ( accessibilityModifier? identifierOrPattern ('?' typeAnnotation? | typeAnnotation? initializer)) + ; + +restParameter + : '...' singleExpression typeAnnotation? + ; + +requiredParameter + : decoratorList? accessibilityModifier? identifierOrPattern typeAnnotation? + ; + +accessibilityModifier + : Public + | Private + | Protected + ; + +identifierOrPattern + : identifierName + | bindingPattern + ; + +constructSignature + : 'new' typeParameters? '(' parameterList? ')' typeAnnotation? + ; + +indexSignature + : '[' Identifier ':' (Number|String) ']' typeAnnotation + ; + +methodSignature + : propertyName '?'? callSignature + ; + +typeAliasDeclaration + : 'type' Identifier typeParameters? '=' type_ SemiColon + ; + +constructorDeclaration + : accessibilityModifier? Constructor '(' formalParameterList? ')' ( ('{' functionBody '}') | SemiColon)? + ; + +// A.5 Interface + +interfaceDeclaration + : Export? Declare? Interface Identifier typeParameters? interfaceExtendsClause? objectType SemiColon? + ; + +interfaceExtendsClause + : Extends classOrInterfaceTypeList + ; + +classOrInterfaceTypeList + : typeReference (',' typeReference)* + ; + +// A.7 Interface + +enumDeclaration + : Const? Enum Identifier '{' enumBody? '}' + ; + +enumBody + : enumMemberList ','? + ; + +enumMemberList + : enumMember (',' enumMember)* + ; + +enumMember + : propertyName ('=' singleExpression)? + ; + +// A.8 Namespaces + +namespaceDeclaration + : Namespace namespaceName '{' statementList? '}' + ; + +namespaceName + : Identifier ('.'+ Identifier)* + ; + +importAliasDeclaration + : Identifier '=' namespaceName SemiColon + ; + +// Ext.2 Additions to 1.8: Decorators + +decoratorList + : decorator+ ; + +decorator + : '@' (decoratorMemberExpression | decoratorCallExpression) + ; + +decoratorMemberExpression + : Identifier + | decoratorMemberExpression '.' identifierName + | '(' singleExpression ')' + ; + +decoratorCallExpression + : decoratorMemberExpression arguments; + +// ECMAPart +program + : sourceElements? EOF + ; + +sourceElement + : Export? statement + ; + +statement + : block + | importStatement + | exportStatement + | emptyStatement_ + | abstractDeclaration //ADDED + | classDeclaration + | interfaceDeclaration //ADDED + | namespaceDeclaration //ADDED + | ifStatement + | iterationStatement + | continueStatement + | breakStatement + | returnStatement + | yieldStatement + | withStatement + | labelledStatement + | switchStatement + | throwStatement + | tryStatement + | debuggerStatement + | functionDeclaration + | arrowFunctionDeclaration + | generatorFunctionDeclaration + | variableStatement + | typeAliasDeclaration //ADDED + | enumDeclaration //ADDED + | expressionStatement + | Export statement + ; + +block + : '{' statementList? '}' + ; + +statementList + : statement+ + ; + +abstractDeclaration + : Abstract (Identifier callSignature | variableStatement) eos + ; + +importStatement + : Import (fromBlock | importAliasDeclaration) + ; + +fromBlock + : (Multiply | multipleImportStatement) (As identifierName)? From StringLiteral eos + ; + +multipleImportStatement + : (identifierName ',')? '{' identifierName (',' identifierName)* '}' + ; + +exportStatement + : Export Default? (fromBlock | statement) + ; + +variableStatement + : bindingPattern typeAnnotation? initializer SemiColon? + | accessibilityModifier? varModifier? ReadOnly? variableDeclarationList SemiColon? + | Declare varModifier? variableDeclarationList SemiColon? + ; + +variableDeclarationList + : variableDeclaration (',' variableDeclaration)* + ; + +variableDeclaration + : ( identifierOrKeyWord | arrayLiteral | objectLiteral) typeAnnotation? singleExpression? ('=' typeParameters? singleExpression)? // ECMAScript 6: Array & Object Matching + ; + +emptyStatement_ + : SemiColon + ; + +expressionStatement + : {this.notOpenBraceAndNotFunction()}? expressionSequence SemiColon? + ; + +ifStatement + : If '(' expressionSequence ')' statement (Else statement)? + ; + + +iterationStatement + : Do statement While '(' expressionSequence ')' eos # DoStatement + | While '(' expressionSequence ')' statement # WhileStatement + | For '(' expressionSequence? SemiColon expressionSequence? SemiColon expressionSequence? ')' statement # ForStatement + | For '(' varModifier variableDeclarationList SemiColon expressionSequence? SemiColon expressionSequence? ')' + statement # ForVarStatement + | For '(' singleExpression (In | Identifier{this.p("of")}?) expressionSequence ')' statement # ForInStatement + | For '(' varModifier variableDeclaration (In | Identifier{this.p("of")}?) expressionSequence ')' statement # ForVarInStatement + ; + +varModifier + : Var + | Let + | Const + ; + +continueStatement + : Continue ({this.notLineTerminator()}? Identifier)? eos + ; + +breakStatement + : Break ({this.notLineTerminator()}? Identifier)? eos + ; + +returnStatement + : Return ({this.notLineTerminator()}? expressionSequence)? eos + ; + +yieldStatement + : Yield ({this.notLineTerminator()}? expressionSequence)? eos + ; + +withStatement + : With '(' expressionSequence ')' statement + ; + +switchStatement + : Switch '(' expressionSequence ')' caseBlock + ; + +caseBlock + : '{' caseClauses? (defaultClause caseClauses?)? '}' + ; + +caseClauses + : caseClause+ + ; + +caseClause + : Case expressionSequence ':' statementList? + ; + +defaultClause + : Default ':' statementList? + ; + +labelledStatement + : Identifier ':' statement + ; + +throwStatement + : Throw {this.notLineTerminator()}? expressionSequence eos + ; + +tryStatement + : Try block (catchProduction finallyProduction? | finallyProduction) + ; + +catchProduction + : Catch '(' Identifier ')' block + ; + +finallyProduction + : Finally block + ; + +debuggerStatement + : Debugger eos + ; + +functionDeclaration + : Function_ Identifier callSignature ( ('{' functionBody '}') | SemiColon) + ; + +//Ovveride ECMA +classDeclaration + : decoratorList? (Export Default?)? Abstract? Class Identifier typeParameters? classHeritage classTail + ; + +classHeritage + : classExtendsClause? implementsClause? + ; + +classTail + : '{' classElement* '}' + ; + +classExtendsClause + : Extends typeReference + ; + +implementsClause + : Implements classOrInterfaceTypeList + ; + +// Classes modified +classElement + : constructorDeclaration + | decoratorList? propertyMemberDeclaration + | indexMemberDeclaration + | statement + ; + +propertyMemberDeclaration + : propertyMemberBase propertyName '?'? typeAnnotation? initializer? SemiColon # PropertyDeclarationExpression + | propertyMemberBase propertyName callSignature ( ('{' functionBody '}') | SemiColon) # MethodDeclarationExpression + | propertyMemberBase (getAccessor | setAccessor) # GetterSetterDeclarationExpression + | abstractDeclaration # AbstractMemberDeclaration + ; + +propertyMemberBase + : accessibilityModifier? Async? Static? ReadOnly? + ; + +indexMemberDeclaration + : indexSignature SemiColon + ; + +generatorMethod + : '*'? Identifier '(' formalParameterList? ')' '{' functionBody '}' + ; + +generatorFunctionDeclaration + : Function_ '*' Identifier? '(' formalParameterList? ')' '{' functionBody '}' + ; + +generatorBlock + : '{' generatorDefinition (',' generatorDefinition)* ','? '}' + ; + +generatorDefinition + : '*' iteratorDefinition + ; + +iteratorBlock + : '{' iteratorDefinition (',' iteratorDefinition)* ','? '}' + ; + +iteratorDefinition + : '[' singleExpression ']' '(' formalParameterList? ')' '{' functionBody '}' + ; + +formalParameterList + : formalParameterArg (',' formalParameterArg)* (',' lastFormalParameterArg)? + | lastFormalParameterArg + | arrayLiteral // ECMAScript 6: Parameter Context Matching + | objectLiteral (':' formalParameterList)? // ECMAScript 6: Parameter Context Matching + ; + +formalParameterArg + : decorator? accessibilityModifier? identifierOrKeyWord '?'? typeAnnotation? ('=' singleExpression)? // ECMAScript 6: Initialization + ; + +lastFormalParameterArg // ECMAScript 6: Rest Parameter + : Ellipsis Identifier typeAnnotation? + ; + +functionBody + : sourceElements? + ; + +sourceElements + : sourceElement+ + ; + +arrayLiteral + : ('[' elementList? ']') + ; + +elementList + : arrayElement (','+ arrayElement)* + ; + +arrayElement // ECMAScript 6: Spread Operator + : Ellipsis? (singleExpression | Identifier) ','? + ; + +objectLiteral + : '{' (propertyAssignment (',' propertyAssignment)* ','?)? '}' + ; + +// MODIFIED +propertyAssignment + : propertyName (':' |'=') singleExpression # PropertyExpressionAssignment + | '[' singleExpression ']' ':' singleExpression # ComputedPropertyExpressionAssignment + | getAccessor # PropertyGetter + | setAccessor # PropertySetter + | generatorMethod # MethodProperty + | identifierOrKeyWord # PropertyShorthand + | restParameter # RestParameterInObject + ; + +getAccessor + : getter '(' ')' typeAnnotation? '{' functionBody '}' + ; + +setAccessor + : setter '(' ( Identifier | bindingPattern) typeAnnotation? ')' '{' functionBody '}' + ; + +propertyName + : identifierName + | StringLiteral + | numericLiteral + ; + +arguments + : '(' (argumentList ','?)? ')' + ; + +argumentList + : argument (',' argument)* + ; + +argument // ECMAScript 6: Spread Operator + : Ellipsis? (singleExpression | Identifier) + ; + +expressionSequence + : singleExpression (',' singleExpression)* + ; + +functionExpressionDeclaration + : Function_ Identifier? '(' formalParameterList? ')' typeAnnotation? '{' functionBody '}' + ; + +singleExpression + : functionExpressionDeclaration # FunctionExpression + | arrowFunctionDeclaration # ArrowFunctionExpression // ECMAScript 6 + | singleExpression '[' expressionSequence ']' # MemberIndexExpression + | singleExpression '!'? '.' identifierName nestedTypeGeneric? # MemberDotExpression + // Split to try `new Date()` first, then `new Date`. + | New singleExpression typeArguments? arguments # NewExpression + | New singleExpression typeArguments? # NewExpression + | singleExpression arguments # ArgumentsExpression + | singleExpression {this.notLineTerminator()}? '++' # PostIncrementExpression + | singleExpression {this.notLineTerminator()}? '--' # PostDecreaseExpression + | Delete singleExpression # DeleteExpression + | Void singleExpression # VoidExpression + | Typeof singleExpression # TypeofExpression + | '++' singleExpression # PreIncrementExpression + | '--' singleExpression # PreDecreaseExpression + | '+' singleExpression # UnaryPlusExpression + | '-' singleExpression # UnaryMinusExpression + | '~' singleExpression # BitNotExpression + | '!' singleExpression # NotExpression + | singleExpression ('*' | '/' | '%') singleExpression # MultiplicativeExpression + | singleExpression ('+' | '-') singleExpression # AdditiveExpression + | singleExpression ('<<' | '>>' | '>>>') singleExpression # BitShiftExpression + | singleExpression ('<' | '>' | '<=' | '>=') singleExpression # RelationalExpression + | singleExpression Instanceof singleExpression # InstanceofExpression + | singleExpression In singleExpression # InExpression + | singleExpression ('==' | '!=' | '===' | '!==') singleExpression # EqualityExpression + | singleExpression '&' singleExpression # BitAndExpression + | singleExpression '^' singleExpression # BitXOrExpression + | singleExpression '|' singleExpression # BitOrExpression + | singleExpression '&&' singleExpression # LogicalAndExpression + | singleExpression '||' singleExpression # LogicalOrExpression + | singleExpression '?' singleExpression ':' singleExpression # TernaryExpression + | singleExpression '=' singleExpression # AssignmentExpression + | singleExpression assignmentOperator singleExpression # AssignmentOperatorExpression + | singleExpression templateStringLiteral # TemplateStringExpression // ECMAScript 6 + | iteratorBlock # IteratorsExpression // ECMAScript 6 + | generatorBlock # GeneratorsExpression // ECMAScript 6 + | generatorFunctionDeclaration # GeneratorsFunctionExpression // ECMAScript 6 + | yieldStatement # YieldExpression // ECMAScript 6 + | This # ThisExpression + | identifierName singleExpression? # IdentifierExpression + | Super # SuperExpression + | literal # LiteralExpression + | arrayLiteral # ArrayLiteralExpression + | objectLiteral # ObjectLiteralExpression + | '(' expressionSequence ')' # ParenthesizedExpression + | typeArguments expressionSequence? # GenericTypes + | singleExpression As asExpression # CastAsExpression + ; + +asExpression + : predefinedType ('[' ']')? + | singleExpression + ; + +arrowFunctionDeclaration + : Async? arrowFunctionParameters typeAnnotation? '=>' arrowFunctionBody + ; + +arrowFunctionParameters + : Identifier + | '(' formalParameterList? ')' + ; + +arrowFunctionBody + : singleExpression + | '{' functionBody '}' + ; + +assignmentOperator + : '*=' + | '/=' + | '%=' + | '+=' + | '-=' + | '<<=' + | '>>=' + | '>>>=' + | '&=' + | '^=' + | '|=' + ; + +literal + : NullLiteral + | BooleanLiteral + | StringLiteral + | templateStringLiteral + | RegularExpressionLiteral + | numericLiteral + ; + +templateStringLiteral + : BackTick templateStringAtom* BackTick + ; + +templateStringAtom + : TemplateStringAtom + | TemplateStringStartExpression singleExpression TemplateCloseBrace + | TemplateStringEscapeAtom + ; + +numericLiteral + : DecimalLiteral + | HexIntegerLiteral + | OctalIntegerLiteral + | OctalIntegerLiteral2 + | BinaryIntegerLiteral + ; + +identifierName + : Identifier + | reservedWord + ; + +identifierOrKeyWord + : Identifier + | TypeAlias + | Require + ; + +reservedWord + : keyword + | NullLiteral + | BooleanLiteral + ; + +keyword + : Break + | Do + | Instanceof + | Typeof + | Case + | Else + | New + | Var + | Catch + | Finally + | Return + | Void + | Continue + | For + | Switch + | While + | Debugger + | Function_ + | This + | With + | Default + | If + | Throw + | Delete + | In + | Try + | ReadOnly + | Async + | From + | Class + | Enum + | Extends + | Super + | Const + | Export + | Import + | Implements + | Let + | Private + | Public + | Interface + | Package + | Protected + | Static + | Yield + | Get + | Set + | Require + | TypeAlias + | String + | Boolean + | Number + | Module + ; + +getter + : Get propertyName + ; + +setter + : Set propertyName + ; + +eos + : SemiColon + | EOF + | {this.lineTerminatorAhead()}? + | {this.closeBrace()}? + ; diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguage.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguage.java new file mode 100644 index 000000000..d6f1fbb0a --- /dev/null +++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguage.java @@ -0,0 +1,45 @@ +package de.jplag.typescript; + +import org.kohsuke.MetaInfServices; + +import de.jplag.antlr.AbstractAntlrLanguage; + +/** + * This represents the TypeScript language as a language supported by JPlag. + */ +@MetaInfServices(de.jplag.Language.class) +public class TypeScriptLanguage extends AbstractAntlrLanguage { + + private static final String IDENTIFIER = "typescript"; + private final TypeScriptLanguageOptions options = new TypeScriptLanguageOptions(); + + @Override + public String[] suffixes() { + return new String[] {".ts", ".js"}; + } + + @Override + public String getName() { + return "Typescript Parser"; + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public int minimumTokenMatch() { + return 12; + } + + @Override + public TypeScriptLanguageOptions getOptions() { + return options; + } + + @Override + protected TypeScriptParserAdapter initializeParser() { + return new TypeScriptParserAdapter(getOptions().useStrictDefault()); + } +} diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguageOptions.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguageOptions.java new file mode 100644 index 000000000..8a01976c0 --- /dev/null +++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptLanguageOptions.java @@ -0,0 +1,22 @@ +package de.jplag.typescript; + +import de.jplag.options.LanguageOption; +import de.jplag.options.LanguageOptions; +import de.jplag.options.OptionType; + +/** + * Language Specific options for the TypeScript language + */ +public class TypeScriptLanguageOptions extends LanguageOptions { + + /** + * Whether the Antlr Grammar should parse + */ + private final LanguageOption useStrictDefault = createDefaultOption(OptionType.bool(), "useStrictMode", + "If set JPlag parses files with the JavaScript strict syntax", false); + + public boolean useStrictDefault() { + return this.useStrictDefault.getValue(); + } + +} diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptListener.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptListener.java new file mode 100644 index 000000000..31dc21378 --- /dev/null +++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptListener.java @@ -0,0 +1,146 @@ +package de.jplag.typescript; + +import static de.jplag.typescript.TypeScriptTokenType.ASSIGNMENT; +import static de.jplag.typescript.TypeScriptTokenType.BREAK; +import static de.jplag.typescript.TypeScriptTokenType.CATCH_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.CATCH_END; +import static de.jplag.typescript.TypeScriptTokenType.CLASS_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.CLASS_END; +import static de.jplag.typescript.TypeScriptTokenType.CONSTRUCTOR_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.CONSTRUCTOR_END; +import static de.jplag.typescript.TypeScriptTokenType.CONTINUE; +import static de.jplag.typescript.TypeScriptTokenType.DECLARATION; +import static de.jplag.typescript.TypeScriptTokenType.ENUM_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.ENUM_END; +import static de.jplag.typescript.TypeScriptTokenType.ENUM_MEMBER; +import static de.jplag.typescript.TypeScriptTokenType.EXPORT; +import static de.jplag.typescript.TypeScriptTokenType.FINALLY_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.FINALLY_END; +import static de.jplag.typescript.TypeScriptTokenType.FOR_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.FOR_END; +import static de.jplag.typescript.TypeScriptTokenType.FUNCTION_CALL; +import static de.jplag.typescript.TypeScriptTokenType.IF_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.IF_END; +import static de.jplag.typescript.TypeScriptTokenType.IMPORT; +import static de.jplag.typescript.TypeScriptTokenType.INTERFACE_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.INTERFACE_END; +import static de.jplag.typescript.TypeScriptTokenType.METHOD_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.METHOD_END; +import static de.jplag.typescript.TypeScriptTokenType.NAMESPACE_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.NAMESPACE_END; +import static de.jplag.typescript.TypeScriptTokenType.RETURN; +import static de.jplag.typescript.TypeScriptTokenType.SWITCH_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.SWITCH_CASE; +import static de.jplag.typescript.TypeScriptTokenType.SWITCH_END; +import static de.jplag.typescript.TypeScriptTokenType.THROW; +import static de.jplag.typescript.TypeScriptTokenType.TRY_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.WHILE_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.WHILE_END; +import static de.jplag.typescript.grammar.TypeScriptParser.ArgumentsContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ArrowFunctionDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.AssignmentExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.BreakStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.CaseClauseContext; +import static de.jplag.typescript.grammar.TypeScriptParser.CatchProductionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ClassDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ConstructorDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ContinueStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.DefaultClauseContext; +import static de.jplag.typescript.grammar.TypeScriptParser.Else; +import static de.jplag.typescript.grammar.TypeScriptParser.EnumDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.EnumMemberContext; +import static de.jplag.typescript.grammar.TypeScriptParser.Export; +import static de.jplag.typescript.grammar.TypeScriptParser.FinallyProductionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ForInStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ForStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ForVarStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.FunctionDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.FunctionExpressionDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.GetterSetterDeclarationExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.IfStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ImportStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.InterfaceDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.MethodDeclarationExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.NamespaceDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.PostDecreaseExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.PostIncrementExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.PreDecreaseExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.PreIncrementExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.PropertyDeclarationExpressionContext; +import static de.jplag.typescript.grammar.TypeScriptParser.PropertySetterContext; +import static de.jplag.typescript.grammar.TypeScriptParser.PropertySignaturContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ReturnStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.SwitchStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.ThrowStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.TryStatementContext; +import static de.jplag.typescript.grammar.TypeScriptParser.VariableDeclarationContext; +import static de.jplag.typescript.grammar.TypeScriptParser.WhileStatementContext; + +import de.jplag.antlr.AbstractAntlrListener; + +/** + * This class is responsible for mapping parsed TypeScript to the internal Token structure + */ +public class TypeScriptListener extends AbstractAntlrListener { + + public TypeScriptListener() { + visit(ImportStatementContext.class).map(IMPORT); + visit(Export).map(EXPORT); + visit(NamespaceDeclarationContext.class).map(NAMESPACE_BEGIN, NAMESPACE_END); + + visit(ClassDeclarationContext.class).map(CLASS_BEGIN, CLASS_END); + visit(GetterSetterDeclarationExpressionContext.class).map(METHOD_BEGIN, METHOD_END); + visit(PropertyDeclarationExpressionContext.class).map(DECLARATION); + visit(PropertyDeclarationExpressionContext.class, it -> it.initializer() != null).map(ASSIGNMENT); + visit(PropertySetterContext.class).map(ASSIGNMENT); + visit(PropertySignaturContext.class).map(DECLARATION); + + visit(InterfaceDeclarationContext.class).map(INTERFACE_BEGIN, INTERFACE_END); + visit(ConstructorDeclarationContext.class).map(CONSTRUCTOR_BEGIN, CONSTRUCTOR_END); + + visit(EnumDeclarationContext.class).map(ENUM_BEGIN, ENUM_END); + visit(EnumMemberContext.class).map(ENUM_MEMBER); + + visit(VariableDeclarationContext.class).map(DECLARATION); + visit(VariableDeclarationContext.class, it -> it.Assign() != null).map(ASSIGNMENT); + + visit(IfStatementContext.class).map(IF_BEGIN, IF_END); + visit(Else).map(IF_BEGIN); + + visit(SwitchStatementContext.class).map(SWITCH_BEGIN, SWITCH_END); + visit(CaseClauseContext.class).map(SWITCH_CASE); + visit(DefaultClauseContext.class).map(SWITCH_CASE); + + visit(MethodDeclarationExpressionContext.class).map(METHOD_BEGIN, METHOD_END); + + visit(FunctionDeclarationContext.class).map(DECLARATION); + visit(FunctionDeclarationContext.class).map(ASSIGNMENT); + visit(FunctionDeclarationContext.class).map(METHOD_BEGIN, METHOD_END); + + visit(ArrowFunctionDeclarationContext.class).map(METHOD_BEGIN, METHOD_END); + visit(FunctionExpressionDeclarationContext.class).map(METHOD_BEGIN, METHOD_END); + + visit(WhileStatementContext.class).map(WHILE_BEGIN, WHILE_END); + visit(ForStatementContext.class).map(FOR_BEGIN, FOR_END); + visit(ForVarStatementContext.class).map(FOR_BEGIN, FOR_END); + visit(ForInStatementContext.class).map(FOR_BEGIN, FOR_END); + + visit(TryStatementContext.class).map(TRY_BEGIN); + visit(CatchProductionContext.class).map(CATCH_BEGIN, CATCH_END); + visit(FinallyProductionContext.class).map(FINALLY_BEGIN, FINALLY_END); + + visit(BreakStatementContext.class).map(BREAK); + visit(ReturnStatementContext.class).map(RETURN); + visit(ContinueStatementContext.class).map(CONTINUE); + visit(ThrowStatementContext.class).map(THROW); + + visit(AssignmentExpressionContext.class).map(ASSIGNMENT); + visit(PostDecreaseExpressionContext.class).map(ASSIGNMENT); + visit(PreDecreaseExpressionContext.class).map(ASSIGNMENT); + visit(PostIncrementExpressionContext.class).map(ASSIGNMENT); + visit(PreIncrementExpressionContext.class).map(ASSIGNMENT); + + visit(ArgumentsContext.class).map(FUNCTION_CALL); + } + +} diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptParserAdapter.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptParserAdapter.java new file mode 100644 index 000000000..837512053 --- /dev/null +++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptParserAdapter.java @@ -0,0 +1,49 @@ +package de.jplag.typescript; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.ParserRuleContext; + +import de.jplag.antlr.AbstractAntlrListener; +import de.jplag.antlr.AbstractAntlrParserAdapter; +import de.jplag.typescript.grammar.TypeScriptLexer; +import de.jplag.typescript.grammar.TypeScriptParser; + +/** + * The Antlr adapter used for the TypeScript language module + */ +public class TypeScriptParserAdapter extends AbstractAntlrParserAdapter { + private static final TypeScriptListener listener = new TypeScriptListener(); + private final boolean useStrictDefault; + + /** + * Creates a new Parser adapter for the Typescript Antlr Grammar + * @param useStrictDefault True if the grammars should parse the files using the JavaScript strict syntax + */ + public TypeScriptParserAdapter(boolean useStrictDefault) { + this.useStrictDefault = useStrictDefault; + } + + @Override + protected Lexer createLexer(CharStream input) { + TypeScriptLexer lexer = new TypeScriptLexer(input); + lexer.setUseStrictDefault(useStrictDefault); + return lexer; + } + + @Override + protected TypeScriptParser createParser(CommonTokenStream tokenStream) { + return new TypeScriptParser(tokenStream); + } + + @Override + protected ParserRuleContext getEntryContext(TypeScriptParser parser) { + return parser.sourceElements(); + } + + @Override + protected AbstractAntlrListener getListener() { + return listener; + } +} diff --git a/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptTokenType.java b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptTokenType.java new file mode 100644 index 000000000..da8b0f8da --- /dev/null +++ b/languages/typescript/src/main/java/de/jplag/typescript/TypeScriptTokenType.java @@ -0,0 +1,56 @@ +package de.jplag.typescript; + +import de.jplag.TokenType; + +/** + * Tokens extracted by the TypeScript language module + */ +public enum TypeScriptTokenType implements TokenType { + + IMPORT("IMPORT"), + EXPORT("EXPORT"), + NAMESPACE_BEGIN("NAMESPACE{"), + NAMESPACE_END("}NAMESPACE"), + CLASS_BEGIN("CLASS{"), + CLASS_END("}CLASS"), + INTERFACE_BEGIN("INTERFACE{"), + INTERFACE_END("}INTERFACE"), + ENUM_BEGIN("ENUM{"), + ENUM_END("}ENUM"), + METHOD_BEGIN("METHOD{"), + METHOD_END("}METHOD"), + WHILE_BEGIN("WHILE{"), + WHILE_END("}WHILE"), + FOR_BEGIN("FOR{"), + FOR_END("}FOR"), + ASSIGNMENT("ASSIGN"), + IF_BEGIN("IF{"), + IF_END("}IF"), + SWITCH_BEGIN("SWITCH{"), + SWITCH_END("}SWITCH"), + SWITCH_CASE("CASE"), + TRY_BEGIN("TRY{"), + CATCH_BEGIN("}CATCH{"), + CATCH_END("}CATCH"), + FINALLY_BEGIN("FINALLY{"), + FINALLY_END("}FINALLY"), + BREAK("BREAK"), + RETURN("RETURN"), + THROW("THROW"), + CONTINUE("CONTINUE"), + FUNCTION_CALL("CALL"), + ENUM_MEMBER("ENUM_MEMBER"), + CONSTRUCTOR_BEGIN("CONSTRUCT{"), + CONSTRUCTOR_END("}CONSTRUCT"), + DECLARATION("DECLARE"); + + private final String description; + + public String getDescription() { + return this.description; + } + + TypeScriptTokenType(String description) { + this.description = description; + } +} diff --git a/languages/typescript/src/main/java/de/jplag/typescript/grammar/TypeScriptLexerBase.java b/languages/typescript/src/main/java/de/jplag/typescript/grammar/TypeScriptLexerBase.java new file mode 100644 index 000000000..d08aa4cb6 --- /dev/null +++ b/languages/typescript/src/main/java/de/jplag/typescript/grammar/TypeScriptLexerBase.java @@ -0,0 +1,133 @@ +package de.jplag.typescript.grammar; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; + +/** + * Copied from https://github.com/antlr/grammars-v4/tree/master/javascript/typescript/Java. Slightly modified to fit + * JPlag code style + */ +abstract class TypeScriptLexerBase extends Lexer { + /** + * Stores values of nested modes. By default mode is strict or defined externally (useStrictDefault) + */ + private final Deque scopeStrictModes = new ArrayDeque<>(); + + private Token lastToken = null; + /** + * Default value of strict mode Can be defined externally by setUseStrictDefault + */ + private boolean useStrictDefault = false; + /** + * Current value of strict mode Can be defined during parsing, see StringFunctions.js and StringGlobal.js samples + */ + private boolean useStrictCurrent = false; + /** + * Keeps track of the current depth of nested template string backticks. E.g. after the X in: `${a ? `${X templateDepth + * will be 2. This variable is needed to determine if a `}` is a plain CloseBrace, or one that closes an expression + * inside a template string. + */ + private int templateDepth = 0; + + private int openBracesCount = 0; + + protected TypeScriptLexerBase(CharStream input) { + super(input); + } + + public boolean getStrictDefault() { + return useStrictDefault; + } + + public void setUseStrictDefault(boolean value) { + useStrictDefault = value; + useStrictCurrent = value; + } + + public boolean isStrictMode() { + return useStrictCurrent; + } + + public void startTemplateString() { + this.openBracesCount = 0; + } + + public boolean isInTemplateString() { + return this.templateDepth > 0 && this.openBracesCount == 0; + } + + /** + * Return the next token from the character stream and records this last token in case it resides on the default + * channel. This recorded token is used to determine when the lexer could possibly match a regex literal. Also changes + * scopeStrictModes stack if tokenize special string 'use strict'; + * @return the next token from the character stream. + */ + @Override + public Token nextToken() { + Token next = super.nextToken(); + + if (next.getChannel() == Token.DEFAULT_CHANNEL) { + // Keep track of the last token on the default channel. + this.lastToken = next; + } + + return next; + } + + protected void processOpenBrace() { + openBracesCount++; + useStrictCurrent = !scopeStrictModes.isEmpty() && scopeStrictModes.peek() || useStrictDefault; + scopeStrictModes.push(useStrictCurrent); + } + + protected void processCloseBrace() { + openBracesCount--; + useStrictCurrent = scopeStrictModes.isEmpty() ? useStrictDefault : scopeStrictModes.pop(); + } + + protected void processStringLiteral() { + if (lastToken == null || lastToken.getType() == TypeScriptLexer.OpenBrace) { + String text = getText(); + if (text.equals("\"use strict\"") || text.equals("'use strict'")) { + if (!scopeStrictModes.isEmpty()) { + scopeStrictModes.pop(); + } + useStrictCurrent = true; + scopeStrictModes.push(true); + } + } + } + + protected void increaseTemplateDepth() { + this.templateDepth++; + } + + protected void decreaseTemplateDepth() { + this.templateDepth--; + } + + /** + * Returns {@code true} if the lexer can match a regex literal. + */ + protected boolean isRegexPossible() { + + if (this.lastToken == null) { + // No token has been produced yet: at the start of the input, + // no division is possible, so a regex literal _is_ possible. + return true; + } + + return switch (this.lastToken.getType()) { + case TypeScriptLexer.Identifier, TypeScriptLexer.NullLiteral, TypeScriptLexer.BooleanLiteral, TypeScriptLexer.This, TypeScriptLexer.CloseBracket, TypeScriptLexer.CloseParen, TypeScriptLexer.OctalIntegerLiteral, TypeScriptLexer.DecimalLiteral, TypeScriptLexer.HexIntegerLiteral, TypeScriptLexer.StringLiteral, TypeScriptLexer.PlusPlus, TypeScriptLexer.MinusMinus -> + // After any of the tokens above, no regex literal can follow. + false; + default -> + // In all other cases, a regex literal _is_ possible. + true; + }; + } +} \ No newline at end of file diff --git a/languages/typescript/src/main/java/de/jplag/typescript/grammar/TypeScriptParserBase.java b/languages/typescript/src/main/java/de/jplag/typescript/grammar/TypeScriptParserBase.java new file mode 100644 index 000000000..cf4c032cc --- /dev/null +++ b/languages/typescript/src/main/java/de/jplag/typescript/grammar/TypeScriptParserBase.java @@ -0,0 +1,113 @@ +package de.jplag.typescript.grammar; + +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; + +/** + * All parser methods that used in grammar (p, prev, notLineTerminator, etc.) should start with lower case char similar + * to parser rules. Copied from https://github.com/antlr/grammars-v4/tree/master/javascript/typescript/Java. Slightly + * modified to fit JPlag code style + */ +public abstract class TypeScriptParserBase extends Parser { + protected TypeScriptParserBase(TokenStream input) { + super(input); + } + + /** + * Short form for prev(String str) + */ + protected boolean p(String str) { + return prev(str); + } + + /** + * Whether the previous token value equals to @param str + */ + protected boolean prev(String str) { + return _input.LT(-1).getText().equals(str); + } + + /** + * Short form for next(String str) + */ + protected boolean n(String str) { + return next(str); + } + + /** + * Whether the next token value equals to @param str + */ + protected boolean next(String str) { + return _input.LT(1).getText().equals(str); + } + + protected boolean notLineTerminator() { + return !here(TypeScriptParser.LineTerminator); + } + + protected boolean notOpenBraceAndNotFunction() { + int nextTokenType = _input.LT(1).getType(); + return nextTokenType != TypeScriptParser.OpenBrace && nextTokenType != TypeScriptParser.Function_; + } + + protected boolean closeBrace() { + return _input.LT(1).getType() == TypeScriptParser.CloseBrace; + } + + /** + * Returns {@code true} iff on the current index of the parser's token stream a token of the given {@code type} exists + * on the {@code HIDDEN} channel. + * @param type the type of the token on the {@code HIDDEN} channel to check. + * @return {@code true} iff on the current index of the parser's token stream a token of the given {@code type} exists + * on the {@code HIDDEN} channel. + */ + private boolean here(final int type) { + + // Get the token ahead of the current index. + int possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 1; + Token ahead = _input.get(possibleIndexEosToken); + + // Check if the token resides on the HIDDEN channel and if it's of the + // provided type. + return (ahead.getChannel() == Lexer.HIDDEN) && (ahead.getType() == type); + } + + /** + * Returns {@code true} iff on the current index of the parser's token stream a token exists on the {@code HIDDEN} + * channel which either is a line terminator, or is a multi line comment that contains a line terminator. + * @return {@code true} iff on the current index of the parser's token stream a token exists on the {@code HIDDEN} + * channel which either is a line terminator, or is a multi line comment that contains a line terminator. + */ + protected boolean lineTerminatorAhead() { + + // Get the token ahead of the current index. + int possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 1; + Token ahead = _input.get(possibleIndexEosToken); + + if (ahead.getChannel() != Lexer.HIDDEN) { + // We're only interested in tokens on the HIDDEN channel. + return false; + } + + if (ahead.getType() == TypeScriptParser.LineTerminator) { + // There is definitely a line terminator ahead. + return true; + } + + if (ahead.getType() == TypeScriptParser.WhiteSpaces) { + // Get the token ahead of the current whitespaces. + possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 2; + ahead = _input.get(possibleIndexEosToken); + } + + // Get the token's text and type. + String text = ahead.getText(); + int type = ahead.getType(); + + // Check if the token is, or contains a line terminator. + return (type == TypeScriptParser.MultiLineComment && (text.contains("\r") || text.contains("\n"))) + || (type == TypeScriptParser.LineTerminator); + } +} diff --git a/languages/typescript/src/test/java/de/jplag/typescript/JavaScriptLanguageTest.java b/languages/typescript/src/test/java/de/jplag/typescript/JavaScriptLanguageTest.java new file mode 100644 index 000000000..77c696a3f --- /dev/null +++ b/languages/typescript/src/test/java/de/jplag/typescript/JavaScriptLanguageTest.java @@ -0,0 +1,37 @@ +package de.jplag.typescript; + +import static de.jplag.typescript.TypeScriptTokenType.ENUM_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.ENUM_END; +import static de.jplag.typescript.TypeScriptTokenType.ENUM_MEMBER; +import static de.jplag.typescript.TypeScriptTokenType.INTERFACE_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.INTERFACE_END; +import static de.jplag.typescript.TypeScriptTokenType.NAMESPACE_BEGIN; +import static de.jplag.typescript.TypeScriptTokenType.NAMESPACE_END; + +import java.util.ArrayList; +import java.util.List; + +import de.jplag.TokenType; +import de.jplag.testutils.LanguageModuleTest; +import de.jplag.testutils.datacollector.TestDataCollector; +import de.jplag.testutils.datacollector.TestSourceIgnoredLinesCollector; + +public class JavaScriptLanguageTest extends LanguageModuleTest { + + public JavaScriptLanguageTest() { + super(new TypeScriptLanguage(), TypeScriptTokenType.class); + } + + @Override + protected void collectTestData(TestDataCollector collector) { + List javascriptTokens = new ArrayList<>(List.of(TypeScriptTokenType.values())); + javascriptTokens.removeAll(List.of(NAMESPACE_BEGIN, NAMESPACE_END, INTERFACE_BEGIN, INTERFACE_END, ENUM_BEGIN, ENUM_END, ENUM_MEMBER)); + collector.testFile("allJSTokens.js").testSourceCoverage().testContainedTokens(javascriptTokens.toArray(new TokenType[0])); + } + + @Override + protected void configureIgnoredLines(TestSourceIgnoredLinesCollector collector) { + collector.ignoreMultipleLines("/*", "*/"); + collector.ignoreLinesByPrefix("//"); + } +} diff --git a/languages/typescript/src/test/java/de/jplag/typescript/TypeScriptLanguageTest.java b/languages/typescript/src/test/java/de/jplag/typescript/TypeScriptLanguageTest.java new file mode 100644 index 000000000..338066bc7 --- /dev/null +++ b/languages/typescript/src/test/java/de/jplag/typescript/TypeScriptLanguageTest.java @@ -0,0 +1,46 @@ +package de.jplag.typescript; + +import de.jplag.testutils.LanguageModuleTest; +import de.jplag.testutils.datacollector.TestDataCollector; +import de.jplag.testutils.datacollector.TestSourceIgnoredLinesCollector; + +public class TypeScriptLanguageTest extends LanguageModuleTest { + + public TypeScriptLanguageTest() { + super(new TypeScriptLanguage(), TypeScriptTokenType.class); + } + + @Override + protected void collectTestData(TestDataCollector collector) { + collector.testFile("simpleTest.ts").testSourceCoverage().testTokenSequence(TypeScriptTokenType.DECLARATION, TypeScriptTokenType.ASSIGNMENT, + TypeScriptTokenType.DECLARATION, TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.FOR_BEGIN, TypeScriptTokenType.ASSIGNMENT, + TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.FUNCTION_CALL, TypeScriptTokenType.FOR_END, TypeScriptTokenType.DECLARATION, + TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.FUNCTION_CALL, TypeScriptTokenType.ASSIGNMENT); + collector.testFile("forLoops.ts").testTokenSequence(TypeScriptTokenType.DECLARATION, TypeScriptTokenType.ASSIGNMENT, + TypeScriptTokenType.FOR_BEGIN, TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.FUNCTION_CALL, + TypeScriptTokenType.FOR_END, TypeScriptTokenType.FOR_BEGIN, TypeScriptTokenType.FUNCTION_CALL, TypeScriptTokenType.FOR_END, + TypeScriptTokenType.FOR_BEGIN, TypeScriptTokenType.FUNCTION_CALL, TypeScriptTokenType.FOR_END); + collector.testFile("methods.ts").testTokenSequence(TypeScriptTokenType.DECLARATION, TypeScriptTokenType.ASSIGNMENT, + TypeScriptTokenType.METHOD_BEGIN, TypeScriptTokenType.RETURN, TypeScriptTokenType.METHOD_END, TypeScriptTokenType.DECLARATION, + TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.METHOD_BEGIN, TypeScriptTokenType.RETURN, TypeScriptTokenType.METHOD_END, + TypeScriptTokenType.DECLARATION, TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.METHOD_BEGIN, TypeScriptTokenType.RETURN, + TypeScriptTokenType.METHOD_END); + collector.testFile("class.ts").testSourceCoverage().testTokenSequence(TypeScriptTokenType.CLASS_BEGIN, TypeScriptTokenType.DECLARATION, + TypeScriptTokenType.DECLARATION, TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.CONSTRUCTOR_BEGIN, + TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.CONSTRUCTOR_END, TypeScriptTokenType.METHOD_BEGIN, TypeScriptTokenType.RETURN, + TypeScriptTokenType.METHOD_END, TypeScriptTokenType.METHOD_BEGIN, TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.METHOD_END, + TypeScriptTokenType.METHOD_BEGIN, TypeScriptTokenType.RETURN, TypeScriptTokenType.METHOD_END, TypeScriptTokenType.METHOD_BEGIN, + TypeScriptTokenType.ASSIGNMENT, TypeScriptTokenType.METHOD_END, TypeScriptTokenType.CLASS_END); + collector.testFile("if.ts").testSourceCoverage().testTokenSequence(TypeScriptTokenType.IF_BEGIN, TypeScriptTokenType.FUNCTION_CALL, + TypeScriptTokenType.IF_BEGIN, TypeScriptTokenType.IF_BEGIN, TypeScriptTokenType.FUNCTION_CALL, TypeScriptTokenType.IF_BEGIN, + TypeScriptTokenType.FUNCTION_CALL, TypeScriptTokenType.IF_END, TypeScriptTokenType.IF_END, TypeScriptTokenType.IF_BEGIN, + TypeScriptTokenType.FUNCTION_CALL, TypeScriptTokenType.IF_END); + collector.testFile("allTokens.ts").testCoverages(); + } + + @Override + protected void configureIgnoredLines(TestSourceIgnoredLinesCollector collector) { + collector.ignoreMultipleLines("/*", "*/"); + collector.ignoreLinesByPrefix("//"); + } +} diff --git a/languages/typescript/src/test/resources/de/jplag/typescript/allJSTokens.js b/languages/typescript/src/test/resources/de/jplag/typescript/allJSTokens.js new file mode 100644 index 000000000..315b9d965 --- /dev/null +++ b/languages/typescript/src/test/resources/de/jplag/typescript/allJSTokens.js @@ -0,0 +1,58 @@ +import {test} from 'test'; +class Class { + #x; + + constructor(x) { + this.x = x; + } + + test(y) { + return this.x + y; + } +} + +let a = 3; +const d = 4; + +function c() { + let b; + b = 8; + return a - b; +} + +switch (c()) { + case 1: + break; + default: + console.log(c()) +} + +try { + throw "Error" +} catch (e) { + console.log('Error', e); +} finally { + console.log() +} + +for (let i = 0; i < a; i++) { + +} + +while(a > 3) { + a--; + if (d > 5) { + continue; + } +} + +export default Class; +export { c }; + +if (d) { + +} else if (d < 10) { + +} else { + +} \ No newline at end of file diff --git a/languages/typescript/src/test/resources/de/jplag/typescript/allTokens.ts b/languages/typescript/src/test/resources/de/jplag/typescript/allTokens.ts new file mode 100644 index 000000000..86a64b99a --- /dev/null +++ b/languages/typescript/src/test/resources/de/jplag/typescript/allTokens.ts @@ -0,0 +1,70 @@ +import {test} from 'test'; + +namespace Name { + enum Values { + val1 = 3, + val2 + } +} + +interface Inter { + value: number; +} + +class Class { + private x; + + constructor(x) { + this.x = x; + } + + public test(y: number) { + return this.x + y; + } +} + +let a = 3; +const d = 4; + +function c() { + let b; + b = 8; + return a - b; +} + +switch (c()) { + case 1: + break; + default: + console.log(c()) +} + +try { + throw "Error" +} catch (e) { + console.log('Error', e); +} finally { + console.log() +} + +for (let i = 0; i < a; i++) { + +} + +while(a > 3) { + a--; + if (d > 5) { + continue; + } +} + +export default Name; +export { c }; + +if (d) { + +} else if (d < 10) { + +} else { + +} \ No newline at end of file diff --git a/languages/typescript/src/test/resources/de/jplag/typescript/class.ts b/languages/typescript/src/test/resources/de/jplag/typescript/class.ts new file mode 100644 index 000000000..fca08b0d6 --- /dev/null +++ b/languages/typescript/src/test/resources/de/jplag/typescript/class.ts @@ -0,0 +1,25 @@ +class AddTest { + + private _x: number; + protected _y = 5; + + constructor(x) { + this._x = x; + } + + public getX() { + return this._x; + } + + public setX(x: number) { + this._x = x; + } + + get y() { + return this._y; + } + + set y(y: number) { + this._y = y; + } +} \ No newline at end of file diff --git a/languages/typescript/src/test/resources/de/jplag/typescript/forLoops.ts b/languages/typescript/src/test/resources/de/jplag/typescript/forLoops.ts new file mode 100644 index 000000000..17c9b8a78 --- /dev/null +++ b/languages/typescript/src/test/resources/de/jplag/typescript/forLoops.ts @@ -0,0 +1,13 @@ +const are = ["Test", "Test2"]; + +for (let i = 0; i < are.length; i++) { + console.log(are[i]) +} + +for (const a in are) { + console.log(a) +} + +for (const a of are) { + console.log(a) +} \ No newline at end of file diff --git a/languages/typescript/src/test/resources/de/jplag/typescript/if.ts b/languages/typescript/src/test/resources/de/jplag/typescript/if.ts new file mode 100644 index 000000000..11c474542 --- /dev/null +++ b/languages/typescript/src/test/resources/de/jplag/typescript/if.ts @@ -0,0 +1,11 @@ +if (true) { + console.log() +} else if(true) { + console.log() +} else { + console.log() +} + +if (true) { + console.log() +} \ No newline at end of file diff --git a/languages/typescript/src/test/resources/de/jplag/typescript/methods.ts b/languages/typescript/src/test/resources/de/jplag/typescript/methods.ts new file mode 100644 index 000000000..69cf57521 --- /dev/null +++ b/languages/typescript/src/test/resources/de/jplag/typescript/methods.ts @@ -0,0 +1,11 @@ +function add1(a: number, b: number) { + return a + b; +} + +const add2 = function (a: number, b: number) { + return a + b; +} + +const add3 = (a: number, b: number) => { + return a + b; +} \ No newline at end of file diff --git a/languages/typescript/src/test/resources/de/jplag/typescript/simpleTest.ts b/languages/typescript/src/test/resources/de/jplag/typescript/simpleTest.ts new file mode 100644 index 000000000..9f57d346d --- /dev/null +++ b/languages/typescript/src/test/resources/de/jplag/typescript/simpleTest.ts @@ -0,0 +1,9 @@ +let counter = 0; +let arr = [] + +for (let i = 1; i <=5; i++) { + arr.push(i); +} + +let arrRev = arr.reverse(); +counter++; \ No newline at end of file diff --git a/pom.xml b/pom.xml index ea37ec29e..5ad806965 100644 --- a/pom.xml +++ b/pom.xml @@ -74,15 +74,15 @@ 20 20 - 2.37.0 - 2.0.7 - 5.9.3 + 2.39.0 + 2.0.9 + 5.10.0 2.7.7 - 4.13.0 - 2.34.0 - 2.28.0 - 2.18.0 + 4.13.1 + 2.35.0 + 2.29.0 + 2.35.0 1.0.0 @@ -117,7 +117,7 @@ edu.stanford.nlp stanford-corenlp - 4.5.4 + 4.5.5 @@ -167,7 +167,7 @@ org.mockito mockito-core - 5.4.0 + 5.5.0 test @@ -330,7 +330,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.5.0 + 3.6.0 attach-javadocs diff --git a/report-viewer/.eslintrc.cjs b/report-viewer/.eslintrc.cjs index b69c3efe2..1d62fdec6 100644 --- a/report-viewer/.eslintrc.cjs +++ b/report-viewer/.eslintrc.cjs @@ -3,7 +3,7 @@ require('@rushstack/eslint-patch/modern-module-resolution') module.exports = { root: true, - 'extends': [ + extends: [ 'plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', @@ -12,7 +12,24 @@ module.exports = { parserOptions: { ecmaVersion: 'latest' }, + plugins: ['@typescript-eslint', 'vue'], rules: { - "no-console": process.env.NODE_ENV === "production" ? "warn" : "off" - } + "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-restricted-exports": ['error', { 'restrictDefaultExports': { 'direct': true } }], + "vue/no-setup-props-reactivity-loss": "error" + }, + overrides: [ + { + files: ['*.config.ts', '*.config.js', '*.d.ts'], + rules: { + "no-restricted-exports": "off" + } + }, + { + files: ['*.html'], + rules: { + 'vue/comment-directive': 'off' + } + } + ] } diff --git a/report-viewer/.husky/pre-commit b/report-viewer/.husky/pre-commit index d24fdfc60..b93aca0a8 100644 --- a/report-viewer/.husky/pre-commit +++ b/report-viewer/.husky/pre-commit @@ -1,4 +1,5 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" +cd ./report-viewer npx lint-staged diff --git a/report-viewer/.lintstagedrc.json b/report-viewer/.lintstagedrc.json index 87a13b477..5c5cdfd30 100644 --- a/report-viewer/.lintstagedrc.json +++ b/report-viewer/.lintstagedrc.json @@ -1,10 +1,7 @@ { - "*.ts": [ - "npx prettier --write", - "npx eslint" - ], - "*.vue": [ - "npx prettier --write", - "npx eslint" - ] -} \ No newline at end of file + "*.ts": ["npx prettier --write", "npx eslint"], + "*.vue": ["npx prettier --write", "npx eslint"], + "*.js": ["npx prettier --write", "npx eslint"], + "*.html": ["npx prettier --write", "npx eslint"], + "*.css": ["npx prettier --write"] +} diff --git a/report-viewer/.prettierignore b/report-viewer/.prettierignore new file mode 100644 index 000000000..2e1fa2d52 --- /dev/null +++ b/report-viewer/.prettierignore @@ -0,0 +1 @@ +*.md \ No newline at end of file diff --git a/report-viewer/.prettierrc.json b/report-viewer/.prettierrc.json index 6088d5e6d..15c88de8e 100644 --- a/report-viewer/.prettierrc.json +++ b/report-viewer/.prettierrc.json @@ -5,5 +5,6 @@ "singleQuote": true, "printWidth": 100, "trailingComma": "none", - "endOfLine": "auto" -} \ No newline at end of file + "endOfLine": "auto", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/report-viewer/index.html b/report-viewer/index.html index 62bf8a57b..f4fedd2bd 100644 --- a/report-viewer/index.html +++ b/report-viewer/index.html @@ -1,14 +1,17 @@ - + - - - + + + JPlag Report Viewer
diff --git a/report-viewer/package-lock.json b/report-viewer/package-lock.json index eaabb6bc2..3cd65c76e 100644 --- a/report-viewer/package-lock.json +++ b/report-viewer/package-lock.json @@ -8,46 +8,47 @@ "name": "report-viewer", "version": "0.0.0", "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.4.0", - "@fortawesome/free-regular-svg-icons": "^6.4.0", - "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-regular-svg-icons": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/vue-fontawesome": "^3.0.3", - "chart.js": "^3.9.1", + "chart.js": "^4.4.0", "chartjs-plugin-datalabels": "^2.2.0", "highlight.js": "^11.8.0", "jszip": "^3.10.0", - "pinia": "^2.1.4", + "pinia": "^2.1.6", "slash": "^5.1.0", "vue": "^3.3.4", - "vue-chart-3": "^3.1.8", + "vue-chartjs": "^5.2.0", "vue-draggable-next": "^2.2.1", - "vue-router": "^4.2.4", + "vue-router": "^4.2.5", "vue-virtual-scroller": "^2.0.0-beta.8" }, "devDependencies": { - "@playwright/test": "^1.36.0", - "@rushstack/eslint-patch": "^1.3.2", - "@types/jsdom": "^21.1.0", - "@types/node": "^20.4.2", - "@vitejs/plugin-vue": "^4.2.3", - "@vue/eslint-config-prettier": "^7.1.0", + "@playwright/test": "^1.38.1", + "@rushstack/eslint-patch": "^1.4.0", + "@types/jsdom": "^21.1.3", + "@types/node": "^18.18.0", + "@vitejs/plugin-vue": "^4.3.4", + "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^11.0.3", - "@vue/test-utils": "^2.4.0", + "@vue/test-utils": "^2.4.1", "@vue/tsconfig": "^0.4.0", - "autoprefixer": "^10.4.14", - "eslint": "^8.44.0", - "eslint-plugin-vue": "^9.15.1", + "autoprefixer": "^10.4.15", + "eslint": "^8.50.0", + "eslint-plugin-vue": "^9.17.0", "husky": "^8.0.0", "jsdom": "^22.1.0", - "lint-staged": "^13.2.3", + "lint-staged": "^14.0.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.25", - "prettier": "^3.0.0", - "tailwindcss": "^3.3.2", - "typescript": "^5.1.6", - "vite": "^4.3.9", - "vitest": "^0.32.4", - "vue-tsc": "^1.8.4" + "postcss": "^8.4.30", + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4", + "tailwindcss": "^3.3.3", + "typescript": "^5.2.2", + "vite": "^4.4.9", + "vitest": "^0.34.4", + "vue-tsc": "^1.8.15" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -82,10 +83,346 @@ "node": ">=6.0.0" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.14.tgz", + "integrity": "sha512-blODaaL+lngG5bdK/t4qZcQvq2BBqrABmYwqPPcS5VRxrCSGHb9R/rA3fqxh7R18I7WU4KKv+NYkt22FDfalcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.14.tgz", + "integrity": "sha512-rZ2v+Luba5/3D6l8kofWgTnqE+qsC/L5MleKIKFyllHTKHrNBMqeRCnZI1BtRx8B24xMYxeU32iIddRQqMsOsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.14.tgz", + "integrity": "sha512-qSwh8y38QKl+1Iqg+YhvCVYlSk3dVLk9N88VO71U4FUjtiSFylMWK3Ugr8GC6eTkkP4Tc83dVppt2n8vIdlSGg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.14.tgz", + "integrity": "sha512-9Hl2D2PBeDYZiNbnRKRWuxwHa9v5ssWBBjisXFkVcSP5cZqzZRFBUWEQuqBHO4+PKx4q4wgHoWtfQ1S7rUqJ2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.14.tgz", + "integrity": "sha512-ZnI3Dg4ElQ6tlv82qLc/UNHtFsgZSKZ7KjsUNAo1BF1SoYDjkGKHJyCrYyWjFecmXpvvG/KJ9A/oe0H12odPLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.14.tgz", + "integrity": "sha512-h3OqR80Da4oQCIa37zl8tU5MwHQ7qgPV0oVScPfKJK21fSRZEhLE4IIVpmcOxfAVmqjU6NDxcxhYaM8aDIGRLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.14.tgz", + "integrity": "sha512-ha4BX+S6CZG4BoH9tOZTrFIYC1DH13UTCRHzFc3GWX74nz3h/N6MPF3tuR3XlsNjMFUazGgm35MPW5tHkn2lzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.14.tgz", + "integrity": "sha512-5+7vehI1iqru5WRtJyU2XvTOvTGURw3OZxe3YTdE9muNNIdmKAVmSHpB3Vw2LazJk2ifEdIMt/wTWnVe5V98Kg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.14.tgz", + "integrity": "sha512-IXORRe22In7U65NZCzjwAUc03nn8SDIzWCnfzJ6t/8AvGx5zBkcLfknI+0P+hhuftufJBmIXxdSTbzWc8X/V4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.14.tgz", + "integrity": "sha512-BfHlMa0nibwpjG+VXbOoqJDmFde4UK2gnW351SQ2Zd4t1N3zNdmUEqRkw/srC1Sa1DRBE88Dbwg4JgWCbNz/FQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.14.tgz", + "integrity": "sha512-j2/Ex++DRUWIAaUDprXd3JevzGtZ4/d7VKz+AYDoHZ3HjJzCyYBub9CU1wwIXN+viOP0b4VR3RhGClsvyt/xSw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.14.tgz", + "integrity": "sha512-qn2+nc+ZCrJmiicoAnJXJJkZWt8Nwswgu1crY7N+PBR8ChBHh89XRxj38UU6Dkthl2yCVO9jWuafZ24muzDC/A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.14.tgz", + "integrity": "sha512-aGzXzd+djqeEC5IRkDKt3kWzvXoXC6K6GyYKxd+wsFJ2VQYnOWE954qV2tvy5/aaNrmgPTb52cSCHFE+Z7Z0yg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.14.tgz", + "integrity": "sha512-8C6vWbfr0ygbAiMFLS6OPz0BHvApkT2gCboOGV76YrYw+sD/MQJzyITNsjZWDXJwPu9tjrFQOVG7zijRzBCnLw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.14.tgz", + "integrity": "sha512-G/Lf9iu8sRMM60OVGOh94ZW2nIStksEcITkXdkD09/T6QFD/o+g0+9WVyR/jajIb3A0LvBJ670tBnGe1GgXMgw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.14.tgz", + "integrity": "sha512-TBgStYBQaa3EGhgqIDM+ECnkreb0wkcKqL7H6m+XPcGUoU4dO7dqewfbm0mWEQYH3kzFHrzjOFNpSAVzDZRSJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.14.tgz", + "integrity": "sha512-stvCcjyCQR2lMTroqNhAbvROqRjxPEq0oQ380YdXxA81TaRJEucH/PzJ/qsEtsHgXlWFW6Ryr/X15vxQiyRXVg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.14.tgz", + "integrity": "sha512-apAOJF14CIsN5ht1PA57PboEMsNV70j3FUdxLmA2liZ20gEQnfTG5QU0FhENo5nwbTqCB2O3WDsXAihfODjHYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.14.tgz", + "integrity": "sha512-fYRaaS8mDgZcGybPn2MQbn1ZNZx+UXFSUoS5Hd2oEnlsyUcr/l3c6RnXf1bLDRKKdLRSabTmyCy7VLQ7VhGdOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.14.tgz", + "integrity": "sha512-1c44RcxKEJPrVj62XdmYhxXaU/V7auELCmnD+Ri+UCt+AGxTvzxl9uauQhrFso8gj6ZV1DaORV0sT9XSHOAk8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.14.tgz", + "integrity": "sha512-EXAFttrdAxZkFQmpvcAQ2bywlWUsONp/9c2lcfvPUhu8vXBBenCXpoq9YkUvVP639ld3YGiYx0YUQ6/VQz3Maw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.14", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.14.tgz", - "integrity": "sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA==", + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.14.tgz", + "integrity": "sha512-K0QjGbcskx+gY+qp3v4/940qg8JitpXbdxFhRDA1aYoNaPff88+aEwoq45aqJ+ogpxQxmU0ZTjgnrQD/w8iiUg==", "cpu": [ "x64" ], @@ -114,18 +451,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.7.0.tgz", + "integrity": "sha512-+HencqxU7CFJnQb7IKtuNBqS6Yx3Tz4kOL8BJXo+JyeiBm5MEX6pO8onXDkjrkCRlfYXS1Axro15ZjVFe9YgsA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -146,54 +483,54 @@ } }, "node_modules/@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", - "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz", + "integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==", "hasInstallScript": true, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", - "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz", + "integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.4.0" + "@fortawesome/fontawesome-common-types": "6.4.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", - "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz", + "integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.4.0" + "@fortawesome/fontawesome-common-types": "6.4.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", - "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz", + "integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.4.0" + "@fortawesome/fontawesome-common-types": "6.4.2" }, "engines": { "node": ">=6" @@ -209,9 +546,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -242,9 +579,9 @@ "dev": true }, "node_modules/@jest/schemas": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", - "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "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" @@ -306,6 +643,11 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -341,29 +683,57 @@ "node": ">= 8" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true + }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pkgr/utils/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, "node_modules/@playwright/test": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.0.tgz", - "integrity": "sha512-yN+fvMYtiyLFDCQos+lWzoX4XW3DNuaxjBu68G0lkgLgC6BP+m/iTxJQoSicz/x2G5EsrqlZTqTIP9sTgLQerg==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", + "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.36.0" + "playwright": "1.38.1" }, "bin": { "playwright": "cli.js" }, "engines": { "node": ">=16" - }, - "optionalDependencies": { - "fsevents": "2.3.2" } }, "node_modules/@rushstack/eslint-patch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz", - "integrity": "sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.4.0.tgz", + "integrity": "sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==", "dev": true }, "node_modules/@sinclair/typebox": { @@ -397,9 +767,9 @@ } }, "node_modules/@types/jsdom": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.1.tgz", - "integrity": "sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==", + "version": "21.1.3", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.3.tgz", + "integrity": "sha512-1zzqSP+iHJYV4lB3lZhNBa012pubABkj9yG/GuXuf6LZH1cSPIJBqFDrm5JX65HHt6VOnNYdTui/0ySerRbMgA==", "dev": true, "dependencies": { "@types/node": "*", @@ -414,9 +784,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.4.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", - "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", + "version": "18.18.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.0.tgz", + "integrity": "sha512-3xA4X31gHT1F1l38ATDIL9GpRLdwVhnEFC8Uikv5ZLlXATwrCYyPq7ZWHxzxc3J/30SUiwiYT+bQe0/XvKlWbw==", "dev": true }, "node_modules/@types/semver": { @@ -620,9 +990,9 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", - "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz", + "integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==", "dev": true, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -633,13 +1003,13 @@ } }, "node_modules/@vitest/expect": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.32.4.tgz", - "integrity": "sha512-m7EPUqmGIwIeoU763N+ivkFjTzbaBn0n9evsTOcde03ugy2avPs3kZbYmw3DkcH1j5mxhMhdamJkLQ6dM1bk/A==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.4.tgz", + "integrity": "sha512-XlMKX8HyYUqB8dsY8Xxrc64J2Qs9pKMt2Z8vFTL4mBWXJsg4yoALHzJfDWi8h5nkO4Zua4zjqtapQ/IluVkSnA==", "dev": true, "dependencies": { - "@vitest/spy": "0.32.4", - "@vitest/utils": "0.32.4", + "@vitest/spy": "0.34.4", + "@vitest/utils": "0.34.4", "chai": "^4.3.7" }, "funding": { @@ -647,12 +1017,12 @@ } }, "node_modules/@vitest/runner": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.32.4.tgz", - "integrity": "sha512-cHOVCkiRazobgdKLnczmz2oaKK9GJOw6ZyRcaPdssO1ej+wzHVIkWiCiNacb3TTYPdzMddYkCgMjZ4r8C0JFCw==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.4.tgz", + "integrity": "sha512-hwwdB1StERqUls8oV8YcpmTIpVeJMe4WgYuDongVzixl5hlYLT2G8afhcdADeDeqCaAmZcSgLTLtqkjPQF7x+w==", "dev": true, "dependencies": { - "@vitest/utils": "0.32.4", + "@vitest/utils": "0.34.4", "p-limit": "^4.0.0", "pathe": "^1.1.1" }, @@ -688,12 +1058,12 @@ } }, "node_modules/@vitest/snapshot": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.32.4.tgz", - "integrity": "sha512-IRpyqn9t14uqsFlVI2d7DFMImGMs1Q9218of40bdQQgMePwVdmix33yMNnebXcTzDU5eiV3eUsoxxH5v0x/IQA==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.4.tgz", + "integrity": "sha512-GCsh4coc3YUSL/o+BPUo7lHQbzpdttTxL6f4q0jRx2qVGoYz/cyTRDJHbnwks6TILi6560bVWoBpYC10PuTLHw==", "dev": true, "dependencies": { - "magic-string": "^0.30.0", + "magic-string": "^0.30.1", "pathe": "^1.1.1", "pretty-format": "^29.5.0" }, @@ -702,9 +1072,9 @@ } }, "node_modules/@vitest/spy": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.32.4.tgz", - "integrity": "sha512-oA7rCOqVOOpE6rEoXuCOADX7Lla1LIa4hljI2MSccbpec54q+oifhziZIJXxlE/CvI2E+ElhBHzVu0VEvJGQKQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.4.tgz", + "integrity": "sha512-PNU+fd7DUPgA3Ya924b1qKuQkonAW6hL7YUjkON3wmBwSTIlhOSpy04SJ0NrRsEbrXgMMj6Morh04BMf8k+w0g==", "dev": true, "dependencies": { "tinyspy": "^2.1.1" @@ -714,9 +1084,9 @@ } }, "node_modules/@vitest/utils": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.32.4.tgz", - "integrity": "sha512-Gwnl8dhd1uJ+HXrYyV0eRqfmk9ek1ASE/LWfTCuWMw+d07ogHqp4hEAV28NiecimK6UY9DpSEPh+pXBA5gtTBg==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.4.tgz", + "integrity": "sha512-yR2+5CHhp/K4ySY0Qtd+CAL9f5Yh1aXrKfAT42bq6CtlGPh92jIDDDSg7ydlRow1CP+dys4TrOrbELOyNInHSg==", "dev": true, "dependencies": { "diff-sequences": "^29.4.3", @@ -728,30 +1098,30 @@ } }, "node_modules/@volar/language-core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.8.0.tgz", - "integrity": "sha512-ZHTvZPM3pEbOOuaq+ybNz5TQlHUqPQPK0G1+SonvApGq0e3qgGijjhtL5T7hsCtUEmxfix8FrAuCH14tMBOhTg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.1.tgz", + "integrity": "sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==", "dev": true, "dependencies": { - "@volar/source-map": "1.8.0" + "@volar/source-map": "1.10.1" } }, "node_modules/@volar/source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.8.0.tgz", - "integrity": "sha512-d35aV0yFkIrkynRSKgrN5hgbMv6ekkFvcJsJGmOZ8UEjqLStto9zq7RSvpp6/PZ7/pa4Gn1f6K1qDt0bq0oUew==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.1.tgz", + "integrity": "sha512-3/S6KQbqa7pGC8CxPrg69qHLpOvkiPHGJtWPkI/1AXCsktkJ6gIk/5z4hyuMp8Anvs6eS/Kvp/GZa3ut3votKA==", "dev": true, "dependencies": { "muggle-string": "^0.3.1" } }, "node_modules/@volar/typescript": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.8.0.tgz", - "integrity": "sha512-T/U1XLLhXv6tNr40Awznfc6QZWizSL99t6M0DeXtIMbnvSCqjjCVRnwlsq+DK9C1RlO3k8+i0Z8iJn7O1GGtoA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.1.tgz", + "integrity": "sha512-+iiO9yUSRHIYjlteT+QcdRq8b44qH19/eiUZtjNtuh6D9ailYM7DVR0zO2sEgJlvCaunw/CF9Ov2KooQBpR4VQ==", "dev": true, "dependencies": { - "@volar/language-core": "1.8.0" + "@volar/language-core": "1.10.1" } }, "node_modules/@vue/compiler-core": { @@ -806,17 +1176,17 @@ "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" }, "node_modules/@vue/eslint-config-prettier": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", - "integrity": "sha512-Pv/lVr0bAzSIHLd9iz0KnvAr4GKyCEl+h52bc4e5yWuDVtLgFwycF7nrbWTAQAS+FU6q1geVd07lc6EWfJiWKQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-8.0.0.tgz", + "integrity": "sha512-55dPqtC4PM/yBjhAr+yEw6+7KzzdkBuLmnhBrDfp4I48+wy+Giqqj9yUr5T2uD/BkBROjjmqnLZmXRdOx/VtQg==", "dev": true, "dependencies": { - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^4.0.0" + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0" }, "peerDependencies": { - "eslint": ">= 7.28.0", - "prettier": ">= 2.0.0" + "eslint": ">= 8.0.0", + "prettier": ">= 3.0.0" } }, "node_modules/@vue/eslint-config-typescript": { @@ -844,13 +1214,13 @@ } }, "node_modules/@vue/language-core": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.4.tgz", - "integrity": "sha512-pnNtNcJVfkGYluW0vsVO+Y1gyX+eA0voaS7+1JOhCp5zKeCaL/PAmGYOgfvwML62neL+2H8pnhY7sffmrGpEhw==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.15.tgz", + "integrity": "sha512-zche5Aw8kkvp3YaghuLiOZyVIpoWHjSQ0EfjxGSsqHOPMamdCoa9x3HtbenpR38UMUoKJ88wiWuiOrV3B/Yq+A==", "dev": true, "dependencies": { - "@volar/language-core": "~1.8.0", - "@volar/source-map": "~1.8.0", + "@volar/language-core": "~1.10.0", + "@volar/source-map": "~1.10.0", "@vue/compiler-dom": "^3.3.0", "@vue/reactivity": "^3.3.0", "@vue/shared": "^3.3.0", @@ -877,9 +1247,9 @@ } }, "node_modules/@vue/language-core/node_modules/minimatch": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz", - "integrity": "sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -948,23 +1318,19 @@ "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" }, "node_modules/@vue/test-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.0.tgz", - "integrity": "sha512-BKB9aj1yky63/I3IwSr1FjUeHYsKXI7D6S9F378AHt7a5vC0dLkOBtSsFXoRGC/7BfHmiB9HRhT+i9xrUHoAKw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.1.tgz", + "integrity": "sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==", "dev": true, "dependencies": { - "js-beautify": "1.14.6", - "vue-component-type-helpers": "1.6.5" + "js-beautify": "1.14.9", + "vue-component-type-helpers": "1.8.4" }, "peerDependencies": { - "@vue/compiler-dom": "^3.0.1", "@vue/server-renderer": "^3.0.1", "vue": "^3.0.1" }, "peerDependenciesMeta": { - "@vue/compiler-dom": { - "optional": true - }, "@vue/server-renderer": { "optional": true } @@ -977,13 +1343,13 @@ "dev": true }, "node_modules/@vue/typescript": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.4.tgz", - "integrity": "sha512-sioQfIY5xcmEAz+cPLvv6CtzGPtGhIdR0Za87zB8M4mPe4OSsE3MBGkXcslf+EzQgF+fm6Gr1SRMSX8r5ZmzDA==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.15.tgz", + "integrity": "sha512-qWyanQKXOsK84S8rP7QBrqsvUdQ0nZABZmTjXMpb3ox4Bp5IbkscREA3OPUrkgl64mAxwwCzIWcOc3BPTCPjQw==", "dev": true, "dependencies": { - "@volar/typescript": "~1.8.0", - "@vue/language-core": "1.8.4" + "@volar/typescript": "~1.10.0", + "@vue/language-core": "1.8.15" } }, "node_modules/abab": { @@ -999,9 +1365,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1040,19 +1406,6 @@ "node": ">= 6.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1070,24 +1423,24 @@ } }, "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", "dev": true, "dependencies": { - "type-fest": "^0.21.3" + "type-fest": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "engines": { "node": ">=10" @@ -1182,15 +1535,6 @@ "node": "*" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1198,9 +1542,9 @@ "dev": true }, "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "version": "10.4.15", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", + "integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==", "dev": true, "funding": [ { @@ -1210,11 +1554,15 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001520", "fraction.js": "^4.2.0", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -1248,6 +1596,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1263,6 +1620,18 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1286,9 +1655,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", - "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", "dev": true, "funding": [ { @@ -1305,9 +1674,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001489", - "electron-to-chromium": "^1.4.411", - "node-releases": "^2.0.12", + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", "update-browserslist-db": "^1.0.11" }, "bin": { @@ -1317,6 +1686,21 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1358,9 +1742,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001492", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz", - "integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==", + "version": "1.0.30001522", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz", + "integrity": "sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==", "dev": true, "funding": [ { @@ -1378,9 +1762,9 @@ ] }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -1412,9 +1796,15 @@ } }, "node_modules/chart.js": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", - "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", + "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } }, "node_modules/chartjs-plugin-datalabels": { "version": "2.2.0", @@ -1472,25 +1862,19 @@ "node": ">= 6" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate": { @@ -1528,9 +1912,9 @@ "dev": true }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "node_modules/combined-stream": { @@ -1546,10 +1930,13 @@ } }, "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -1676,6 +2063,52 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -1708,9 +2141,9 @@ "dev": true }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "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" @@ -1765,33 +2198,51 @@ "dev": true }, "node_modules/editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "dev": true, "dependencies": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" } }, - "node_modules/editorconfig/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "bin": { - "semver": "bin/semver" + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/electron-to-chromium": { - "version": "1.4.417", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.417.tgz", - "integrity": "sha512-8rY8HdCxuSVY8wku3i/eDac4g1b4cSbruzocenrqBlzqruAZYHjQCHIjC66dLR9DXhEHTojsC4EjhZ8KmzwXqA==", + "version": "1.4.499", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.499.tgz", + "integrity": "sha512-0NmjlYBLKVHva4GABWAaHuPJolnDuL0AhV3h1hES6rcLCWEIbRL6/8TghfsVwkx6TEroQVdliX7+aLysUpKvjw==", "dev": true }, "node_modules/emoji-regex": { @@ -1901,9 +2352,9 @@ } }, "node_modules/esbuild": { - "version": "0.17.14", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.14.tgz", - "integrity": "sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw==", + "version": "0.18.14", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.14.tgz", + "integrity": "sha512-uNPj5oHPYmj+ZhSQeYQVFZ+hAlJZbAGOmmILWIqrGvPVlNLbyOvU5Bu6Woi8G8nskcx0vwY0iFoMPrzT86Ko+w==", "dev": true, "hasInstallScript": true, "bin": { @@ -1913,28 +2364,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.14", - "@esbuild/android-arm64": "0.17.14", - "@esbuild/android-x64": "0.17.14", - "@esbuild/darwin-arm64": "0.17.14", - "@esbuild/darwin-x64": "0.17.14", - "@esbuild/freebsd-arm64": "0.17.14", - "@esbuild/freebsd-x64": "0.17.14", - "@esbuild/linux-arm": "0.17.14", - "@esbuild/linux-arm64": "0.17.14", - "@esbuild/linux-ia32": "0.17.14", - "@esbuild/linux-loong64": "0.17.14", - "@esbuild/linux-mips64el": "0.17.14", - "@esbuild/linux-ppc64": "0.17.14", - "@esbuild/linux-riscv64": "0.17.14", - "@esbuild/linux-s390x": "0.17.14", - "@esbuild/linux-x64": "0.17.14", - "@esbuild/netbsd-x64": "0.17.14", - "@esbuild/openbsd-x64": "0.17.14", - "@esbuild/sunos-x64": "0.17.14", - "@esbuild/win32-arm64": "0.17.14", - "@esbuild/win32-ia32": "0.17.14", - "@esbuild/win32-x64": "0.17.14" + "@esbuild/android-arm": "0.18.14", + "@esbuild/android-arm64": "0.18.14", + "@esbuild/android-x64": "0.18.14", + "@esbuild/darwin-arm64": "0.18.14", + "@esbuild/darwin-x64": "0.18.14", + "@esbuild/freebsd-arm64": "0.18.14", + "@esbuild/freebsd-x64": "0.18.14", + "@esbuild/linux-arm": "0.18.14", + "@esbuild/linux-arm64": "0.18.14", + "@esbuild/linux-ia32": "0.18.14", + "@esbuild/linux-loong64": "0.18.14", + "@esbuild/linux-mips64el": "0.18.14", + "@esbuild/linux-ppc64": "0.18.14", + "@esbuild/linux-riscv64": "0.18.14", + "@esbuild/linux-s390x": "0.18.14", + "@esbuild/linux-x64": "0.18.14", + "@esbuild/netbsd-x64": "0.18.14", + "@esbuild/openbsd-x64": "0.18.14", + "@esbuild/sunos-x64": "0.18.14", + "@esbuild/win32-arm64": "0.18.14", + "@esbuild/win32-ia32": "0.18.14", + "@esbuild/win32-x64": "0.18.14" } }, "node_modules/escalade": { @@ -1959,27 +2410,27 @@ } }, "node_modules/eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", - "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1989,7 +2440,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -2001,7 +2451,6 @@ "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -2027,38 +2476,46 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", + "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", "dev": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } } }, "node_modules/eslint-plugin-vue": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz", - "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz", + "integrity": "sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.3.0", + "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", - "nth-check": "^2.0.1", - "postcss-selector-parser": "^6.0.9", - "semver": "^7.3.5", - "vue-eslint-parser": "^9.3.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.13", + "semver": "^7.5.4", + "vue-eslint-parser": "^9.3.1", "xml-name-validator": "^4.0.0" }, "engines": { @@ -2082,9 +2539,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2094,9 +2551,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2119,9 +2576,9 @@ } }, "node_modules/espree": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", - "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { "acorn": "^8.9.0", @@ -2200,10 +2657,16 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "node_modules/execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -2230,15 +2693,15 @@ "dev": true }, "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2536,9 +2999,9 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2842,15 +3305,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2985,6 +3439,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3018,6 +3487,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -3170,6 +3657,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3191,14 +3705,14 @@ } }, "node_modules/js-beautify": { - "version": "1.14.6", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.6.tgz", - "integrity": "sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.9.tgz", + "integrity": "sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==", "dev": true, "dependencies": { "config-chain": "^1.1.13", - "editorconfig": "^0.15.3", - "glob": "^8.0.3", + "editorconfig": "^1.0.3", + "glob": "^8.1.0", "nopt": "^6.0.0" }, "bin": { @@ -3207,7 +3721,7 @@ "js-beautify": "js/bin/js-beautify.js" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/js-yaml": { @@ -3336,39 +3850,36 @@ "dev": true }, "node_modules/lint-staged": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz", - "integrity": "sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-14.0.1.tgz", + "integrity": "sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw==", "dev": true, "dependencies": { - "chalk": "5.2.0", - "cli-truncate": "^3.1.0", - "commander": "^10.0.0", - "debug": "^4.3.4", - "execa": "^7.0.0", + "chalk": "5.3.0", + "commander": "11.0.0", + "debug": "4.3.4", + "execa": "7.2.0", "lilconfig": "2.1.0", - "listr2": "^5.0.7", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.3", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.2.2" + "listr2": "6.6.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": "^16.14.0 || >=18.0.0" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -3378,12 +3889,12 @@ } }, "node_modules/lint-staged/node_modules/commander": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", - "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", "dev": true, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/lint-staged/node_modules/pidtree": { @@ -3399,22 +3910,20 @@ } }, "node_modules/listr2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", - "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz", + "integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==", "dev": true, "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", + "cli-truncate": "^3.1.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^5.0.1", "rfdc": "^1.3.0", - "rxjs": "^7.8.0", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^8.1.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "enquirer": ">= 2.3.0 < 3" @@ -3425,65 +3934,6 @@ } } }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/listr2/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -3532,11 +3982,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3544,81 +3989,49 @@ "dev": true }, "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", "dev": true, "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "node": ">=12" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/loupe": { @@ -3627,25 +4040,15 @@ "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", "dev": true, "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "get-func-name": "^2.0.0" } }, "node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -3739,15 +4142,15 @@ "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==" }, "node_modules/mlly": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz", - "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", "dev": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.10.0", "pathe": "^1.1.1", "pkg-types": "^1.0.3", - "ufo": "^1.1.2" + "ufo": "^1.3.0" } }, "node_modules/ms": { @@ -3809,9 +4212,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/nopt": { @@ -4154,6 +4557,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4201,21 +4622,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -4354,9 +4760,9 @@ } }, "node_modules/pinia": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz", - "integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz", + "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==", "dependencies": { "@vue/devtools-api": "^6.5.0", "vue-demi": ">=0.14.5" @@ -4423,10 +4829,28 @@ "pathe": "^1.1.0" } }, + "node_modules/playwright": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", + "dev": true, + "dependencies": { + "playwright-core": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/playwright-core": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.0.tgz", - "integrity": "sha512-7RTr8P6YJPAqB+8j5ATGHqD6LvLLM39sYVNsslh78g8QeLcBs5750c6+msjrHUwwGt+kEbczBj1XB22WMwn+WA==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -4436,9 +4860,9 @@ } }, "node_modules/postcss": { - "version": "8.4.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", - "integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==", + "version": "8.4.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", + "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", "funding": [ { "type": "opencollective", @@ -4547,9 +4971,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", - "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4575,9 +4999,9 @@ } }, "node_modules/prettier": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", - "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -4601,13 +5025,85 @@ "node": ">=6.0.0" } }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.4.tgz", + "integrity": "sha512-QZzzB1bID6qPsKHTeA9qPo1APmmxfFrA5DD3LQ+vbTmAnY40eJI7t9Q1ocqel2EKMWNPLJqdTDWZj1hKYgqSgg==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@shufo/prettier-plugin-blade": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@shufo/prettier-plugin-blade": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "prettier-plugin-twig-melody": { + "optional": true + } + } + }, "node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "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.0", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -4638,12 +5134,6 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -4811,16 +5301,19 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/restore-cursor/node_modules/mimic-fn": { @@ -4899,9 +5392,9 @@ } }, "node_modules/rollup": { - "version": "3.21.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.6.tgz", - "integrity": "sha512-SXIICxvxQxR3D4dp/3LDHZIJPC8a4anKMHd4E3Jiz2/JnY+2bEjqrOokAauc5ShGVNFHlEFjBXAXlaxkJqIqSg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -4920,6 +5413,110 @@ "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", "dev": true }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/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/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4943,21 +5540,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -4996,9 +5578,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5083,12 +5665,6 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, - "node_modules/sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", - "dev": true - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5201,9 +5777,9 @@ } }, "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "engines": { "node": ">=0.6.19" @@ -5239,9 +5815,9 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { "ansi-regex": "^6.0.1" @@ -5453,10 +6029,32 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, "node_modules/tailwindcss": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", - "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", + "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5479,7 +6077,6 @@ "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, @@ -5518,12 +6115,6 @@ "node": ">=0.8" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "node_modules/tinybench": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz", @@ -5531,9 +6122,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz", - "integrity": "sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", "dev": true, "engines": { "node": ">=14.0.0" @@ -5548,6 +6139,18 @@ "node": ">=14.0.0" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5662,9 +6265,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -5675,9 +6278,9 @@ } }, "node_modules/ufo": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.2.tgz", - "integrity": "sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.0.tgz", + "integrity": "sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==", "dev": true }, "node_modules/unbox-primitive": { @@ -5704,6 +6307,15 @@ "node": ">= 4.0.0" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -5769,14 +6381,14 @@ } }, "node_modules/vite": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", - "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", "dev": true, "dependencies": { - "esbuild": "^0.17.5", - "postcss": "^8.4.23", - "rollup": "^3.21.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -5784,12 +6396,16 @@ "engines": { "node": "^14.18.0 || >=16.0.0" }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", + "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", @@ -5802,6 +6418,9 @@ "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, @@ -5817,9 +6436,9 @@ } }, "node_modules/vite-node": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.32.4.tgz", - "integrity": "sha512-L2gIw+dCxO0LK14QnUMoqSYpa9XRGnTTTDjW2h19Mr+GR0EFj4vx52W41gFXfMLqpA00eK4ZjOVYo1Xk//LFEw==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.4.tgz", + "integrity": "sha512-ho8HtiLc+nsmbwZMw8SlghESEE3KxJNp04F/jPUCLVvaURwt0d+r9LxEqCX5hvrrOQ0GSyxbYr5ZfRYhQ0yVKQ==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -5840,34 +6459,34 @@ } }, "node_modules/vitest": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.32.4.tgz", - "integrity": "sha512-3czFm8RnrsWwIzVDu/Ca48Y/M+qh3vOnF16czJm98Q/AN1y3B6PBsyV8Re91Ty5s7txKNjEhpgtGPcfdbh2MZg==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.4.tgz", + "integrity": "sha512-SE/laOsB6995QlbSE6BtkpXDeVNLJc1u2LHRG/OpnN4RsRzM3GQm4nm3PQCK5OBtrsUqnhzLdnT7se3aeNGdlw==", "dev": true, "dependencies": { "@types/chai": "^4.3.5", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "@vitest/expect": "0.32.4", - "@vitest/runner": "0.32.4", - "@vitest/snapshot": "0.32.4", - "@vitest/spy": "0.32.4", - "@vitest/utils": "0.32.4", + "@vitest/expect": "0.34.4", + "@vitest/runner": "0.34.4", + "@vitest/snapshot": "0.34.4", + "@vitest/spy": "0.34.4", + "@vitest/utils": "0.34.4", "acorn": "^8.9.0", "acorn-walk": "^8.2.0", "cac": "^6.7.14", "chai": "^4.3.7", "debug": "^4.3.4", "local-pkg": "^0.4.3", - "magic-string": "^0.30.0", + "magic-string": "^0.30.1", "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.3.3", "strip-literal": "^1.0.1", "tinybench": "^2.5.0", - "tinypool": "^0.5.0", - "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.32.4", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.4", "why-is-node-running": "^2.2.2" }, "bin": { @@ -5928,25 +6547,19 @@ "@vue/shared": "3.3.4" } }, - "node_modules/vue-chart-3": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-3.1.8.tgz", - "integrity": "sha512-zX5ajjQi/PocEqLETlej3vp92q/tnI/Fvu2RVb++Kap8qOrXu6PXCpodi73BFrWzEGZIAnqoUxC3OIkRWD657g==", - "dependencies": { - "@vue/runtime-core": "latest", - "@vue/runtime-dom": "latest", - "csstype": "latest", - "lodash-es": "latest" - }, + "node_modules/vue-chartjs": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.2.0.tgz", + "integrity": "sha512-d3zpKmGZr2OWHQ1xmxBcAn5ShTG917+/UCLaSpaCDDqT0U7DBsvFzTs69ZnHCgKoXT55GZDW8YEj9Av+dlONLA==", "peerDependencies": { - "chart.js": "=> ^3.1.0", - "vue": ">= 3" + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" } }, "node_modules/vue-component-type-helpers": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.6.5.tgz", - "integrity": "sha512-iGdlqtajmiqed8ptURKPJ/Olz0/mwripVZszg6tygfZSIL9kYFPJTNY6+Q6OjWGznl2L06vxG5HvNvAnWrnzbg==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.8.4.tgz", + "integrity": "sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==", "dev": true }, "node_modules/vue-draggable-next": { @@ -5959,9 +6572,9 @@ } }, "node_modules/vue-eslint-parser": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.0.tgz", - "integrity": "sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz", + "integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==", "dev": true, "dependencies": { "debug": "^4.3.4", @@ -6021,9 +6634,9 @@ } }, "node_modules/vue-router": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz", - "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", + "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", "dependencies": { "@vue/devtools-api": "^6.5.0" }, @@ -6045,13 +6658,13 @@ } }, "node_modules/vue-tsc": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.4.tgz", - "integrity": "sha512-+hgpOhIx11vbi8/AxEdaPj3fiRwN9wy78LpsNNw2V995/IWa6TMyQxHbaw2ZKUpdwjySSHgrT6ohDEhUgFxGYw==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.15.tgz", + "integrity": "sha512-4DoB3LUj7IToLmggoCxRiFG+QU5lem0nv03m1ocqugXA9rSVoTOEoYYaP8vu8b99Eh+/cCVdYOeIAQ+RsgUYUw==", "dev": true, "dependencies": { - "@vue/language-core": "1.8.4", - "@vue/typescript": "1.8.4", + "@vue/language-core": "1.8.15", + "@vue/typescript": "1.8.15", "semver": "^7.3.8" }, "bin": { @@ -6197,49 +6810,59 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/wrappy": { @@ -6284,16 +6907,10 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - }, "node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", "dev": true, "engines": { "node": ">= 14" diff --git a/report-viewer/package.json b/report-viewer/package.json index 80d03537e..b55301372 100644 --- a/report-viewer/package.json +++ b/report-viewer/package.json @@ -17,45 +17,46 @@ "prepare": "cd .. && husky install report-viewer/.husky" }, "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.4.0", - "@fortawesome/free-regular-svg-icons": "^6.4.0", - "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-regular-svg-icons": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/vue-fontawesome": "^3.0.3", - "chart.js": "^3.9.1", + "chart.js": "^4.4.0", "chartjs-plugin-datalabels": "^2.2.0", "highlight.js": "^11.8.0", "jszip": "^3.10.0", - "pinia": "^2.1.4", + "pinia": "^2.1.6", "slash": "^5.1.0", "vue": "^3.3.4", - "vue-chart-3": "^3.1.8", + "vue-chartjs": "^5.2.0", "vue-draggable-next": "^2.2.1", - "vue-router": "^4.2.4", + "vue-router": "^4.2.5", "vue-virtual-scroller": "^2.0.0-beta.8" }, "devDependencies": { - "@playwright/test": "^1.36.0", - "@rushstack/eslint-patch": "^1.3.2", - "@types/jsdom": "^21.1.0", - "@types/node": "^20.4.2", - "@vitejs/plugin-vue": "^4.2.3", - "@vue/eslint-config-prettier": "^7.1.0", + "@playwright/test": "^1.38.1", + "@rushstack/eslint-patch": "^1.4.0", + "@types/jsdom": "^21.1.3", + "@types/node": "^18.18.0", + "@vitejs/plugin-vue": "^4.3.4", + "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^11.0.3", - "@vue/test-utils": "^2.4.0", + "@vue/test-utils": "^2.4.1", "@vue/tsconfig": "^0.4.0", - "autoprefixer": "^10.4.14", - "eslint": "^8.44.0", - "eslint-plugin-vue": "^9.15.1", + "autoprefixer": "^10.4.15", + "eslint": "^8.50.0", + "eslint-plugin-vue": "^9.17.0", "husky": "^8.0.0", "jsdom": "^22.1.0", - "lint-staged": "^13.2.3", + "lint-staged": "^14.0.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.25", - "prettier": "^3.0.0", - "tailwindcss": "^3.3.2", - "typescript": "^5.1.6", - "vite": "^4.3.9", - "vitest": "^0.32.4", - "vue-tsc": "^1.8.4" + "postcss": "^8.4.30", + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.4", + "tailwindcss": "^3.3.3", + "typescript": "^5.2.2", + "vite": "^4.4.9", + "vitest": "^0.34.4", + "vue-tsc": "^1.8.15" } } diff --git a/report-viewer/postcss.config.js b/report-viewer/postcss.config.js index 4edfd193d..d62702c9a 100644 --- a/report-viewer/postcss.config.js +++ b/report-viewer/postcss.config.js @@ -2,6 +2,6 @@ module.exports = { plugins: { tailwindcss: {}, - autoprefixer: {}, - }, + autoprefixer: {} + } } diff --git a/report-viewer/public/404.html b/report-viewer/public/404.html index 5241ba2ca..c53ef5157 100644 --- a/report-viewer/public/404.html +++ b/report-viewer/public/404.html @@ -1,9 +1,9 @@ - - \ No newline at end of file + + diff --git a/report-viewer/src/App.vue b/report-viewer/src/App.vue index 546a58f55..988799c65 100644 --- a/report-viewer/src/App.vue +++ b/report-viewer/src/App.vue @@ -1,11 +1,14 @@