From 79c5d7b78efb5a38bde6f780a1b605e8a621046d Mon Sep 17 00:00:00 2001 From: Malte Hansen Date: Tue, 29 Sep 2026 10:46:07 +0200 Subject: [PATCH 1/6] Use CheckStyle and Spotless - remove PMD --- .github/workflows/ci.yaml | 10 +- .gitlab-ci.yml | 4 +- .pmd | 8 - build.gradle | 5 +- buildscripts/code-analysis.gradle | 61 +- code-analysis/code-analysis.gradle | 10 - code-analysis/pmd.xml | 97 -- code-analysis/pre-commit | 8 +- .../net/explorviz/code/analysis/FileIO.java | 14 +- .../code/analysis/FileLanguageResolver.java | 11 +- .../explorviz/code/analysis/GitAnalysis.java | 71 +- .../antlr/generated/cpp/Cpp14ParserBase.java | 6 +- .../antlr/generated/java/JavaParserBase.java | 5 +- .../code/analysis/api/AnalysisRequest.java | 70 +- .../code/analysis/api/AnalysisResource.java | 128 +-- .../code/analysis/api/HealthResource.java | 1 - .../AnalysisCancelledException.java | 4 +- .../analysis/exceptions/DebugFileWriter.java | 17 +- .../exceptions/MalformedPathException.java | 7 +- .../exceptions/NotFoundException.java | 7 +- .../PropertyNotDefinedException.java | 73 +- .../code/analysis/export/DataExporter.java | 113 ++- .../analysis/export/FileDataExportFilter.java | 14 +- .../code/analysis/export/GrpcExporter.java | 55 +- .../code/analysis/export/JsonExporter.java | 91 +- .../code/analysis/export/VoidExporter.java | 12 +- .../code/analysis/git/DirectoryFinder.java | 64 +- .../code/analysis/git/GitMetricCollector.java | 50 +- .../analysis/git/GitRepositoryHandler.java | 372 +++---- .../git/RepositoryFileUrlBuilder.java | 4 +- .../handler/AbstractFileDataHandler.java | 4 +- .../analysis/handler/CFileDataHandler.java | 4 +- .../handler/CSharpFileDataHandler.java | 4 +- .../analysis/handler/ClassDataHandler.java | 59 +- .../analysis/handler/CommitReportHandler.java | 22 +- .../analysis/handler/CppFileDataHandler.java | 16 +- .../FallbackFileDataHandlerFactory.java | 20 +- .../analysis/handler/GoFileDataHandler.java | 4 +- .../analysis/handler/JavaFileDataHandler.java | 17 +- .../handler/KotlinFileDataHandler.java | 4 +- .../analysis/handler/MethodDataHandler.java | 34 +- .../code/analysis/handler/MetricAppender.java | 204 ++-- .../handler/ParameterDataHandler.java | 4 +- .../analysis/handler/PhpFileDataHandler.java | 4 +- .../analysis/handler/ProtoBufConvertable.java | 1 - .../handler/PythonFileDataHandler.java | 4 +- .../analysis/handler/RustFileDataHandler.java | 4 +- .../handler/SwiftFileDataHandler.java | 4 +- .../handler/TypeScriptFileDataHandler.java | 23 +- .../analysis/listener/CFileDataListener.java | 64 +- .../listener/CSharpFileDataListener.java | 82 +- .../listener/CommonFileDataListener.java | 16 +- .../listener/CppFileDataListener.java | 203 ++-- .../analysis/listener/GoFileDataListener.java | 21 +- .../listener/JavaFileDataListener.java | 137 ++- .../listener/KotlinFileDataListener.java | 97 +- .../listener/PhpFileDataListener.java | 71 +- .../listener/PythonFileDataListener.java | 58 +- .../listener/RustFileDataListener.java | 36 +- .../listener/SwiftFileDataListener.java | 124 ++- .../listener/TypeScriptFileDataListener.java | 194 ++-- .../analysis/parser/AntlrCParserService.java | 25 +- .../parser/AntlrCSharpParserService.java | 20 +- .../parser/AntlrCppParserService.java | 26 +- .../analysis/parser/AntlrGoParserService.java | 20 +- .../parser/AntlrKotlinParserService.java | 26 +- .../analysis/parser/AntlrParseSettings.java | 7 +- .../analysis/parser/AntlrParserService.java | 32 +- .../analysis/parser/AntlrParserUtils.java | 124 ++- .../parser/AntlrPhpParserService.java | 20 +- .../parser/AntlrPythonParserService.java | 20 +- .../parser/AntlrRustParserService.java | 20 +- .../parser/AntlrSwiftParserService.java | 20 +- .../parser/AntlrTypeScriptParserService.java | 29 +- .../analysis/parser/JavaParserService.java | 68 +- .../analysis/parser/TsxJsxNormalizer.java | 20 +- .../service/AnalysisCancellationResult.java | 4 +- .../code/analysis/service/AnalysisConfig.java | 59 +- .../service/AnalysisProgressState.java | 7 +- .../analysis/service/AnalysisService.java | 919 +++++++++++------- .../service/AnalysisStatusService.java | 241 +++-- .../analysis/service/ApplicationPath.java | 3 +- .../code/analysis/service/CommitSampler.java | 11 +- .../service/ConcurrentAnalysisService.java | 68 +- .../service/GithubFetcherService.java | 536 +++++----- .../code/analysis/service/GithubPager.java | 22 +- .../analysis/service/LocalRepositoryInfo.java | 5 +- .../service/LocalRepositoryService.java | 32 +- .../service/TerminalProgressReporter.java | 74 +- .../code/analysis/types/FileDescriptor.java | 37 +- .../explorviz/code/analysis/types/Graph.java | 6 +- .../code/analysis/types/JavaTypes.java | 139 ++- .../types/RemoteRepositoryObject.java | 27 +- .../explorviz/code/analysis/types/Triple.java | 7 +- .../code/analysis/types/Verification.java | 7 +- .../explorviz/code/analysis/types/Vertex.java | 6 +- .../visitor/CyclomaticComplexityVisitor.java | 27 +- .../analysis/visitor/EmptyGenericVisitor.java | 8 +- .../analysis/visitor/FileDataVisitor.java | 107 +- .../visitor/LackOfCohesionMethodsVisitor.java | 70 +- .../code/analysis/visitor/NPathVisitor.java | 34 +- .../visitor/NestedBlockDepthVisitor.java | 8 +- .../code/analysis/visitor/VisitorStub.java | 16 +- .../analysis/FileLanguageResolverTest.java | 16 +- .../code/analysis/GitAnalysisTest.java | 8 +- .../export/DataExporterBatchingTest.java | 34 +- .../export/FileDataExportFilterTest.java | 10 +- .../analysis/git/DirectoryFinderTest.java | 85 +- .../git/GitRepositoryHandlerTest.java | 158 +-- .../FallbackFileDataHandlerFactoryTest.java | 16 +- .../listener/PythonFileDataListenerTest.java | 27 +- .../parser/JsConfigParsePerfTest.java | 20 +- .../analysis/parser/ParseTimeoutTest.java | 35 +- .../analysis/parser/TsxJsxNormalizerTest.java | 4 +- .../analysis/parser/Wsp31ParsePerfTest.java | 27 +- .../GoldenFileRegressionSupport.java | 43 +- .../ParserTestInputRegressionTest.java | 47 +- .../AnalysisServiceCommitRangeTest.java | 25 +- .../service/AnalysisServiceDiffBaseTest.java | 19 +- .../AnalysisServiceLandscapeParentTest.java | 9 +- .../service/AnalysisServiceLocLimitTest.java | 19 +- .../service/AnalysisStatusServiceTest.java | 3 +- .../analysis/service/CommitSamplerTest.java | 17 +- .../service/GithubFetcherParseTest.java | 16 +- .../service/GithubFetcherServiceTest.java | 4 +- .../service/LocalRepositoryServiceTest.java | 3 +- .../service/TerminalProgressReporterTest.java | 13 +- .../CyclomaticComplexityVisitorTest.java | 34 +- .../analysis/visitor/FileDataVisitorTest.java | 40 +- .../LackOfCohesionMethodsVisitorTest.java | 6 +- .../visitor/NestedBlockDepthVisitorTest.java | 25 +- 131 files changed, 3592 insertions(+), 3059 deletions(-) delete mode 100644 .pmd delete mode 100644 code-analysis/pmd.xml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 16a8a6f..c0f0bd6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,8 +30,8 @@ jobs: env: GRADLE_OPTS: "-Dorg.gradle.daemon=false" - pmd: - name: PMD Analysis + spotless: + name: Spotless Format Check runs-on: ubuntu-latest needs: build steps: @@ -47,8 +47,10 @@ jobs: with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - - name: Run PMD Analysis - run: ./gradlew pmdMain + - name: Run Spotless + run: ./gradlew spotlessCheck + env: + GRADLE_OPTS: "-Dorg.gradle.daemon=false" checkstyle: name: Checkstyle Analysis diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f9fad1f..6debb9b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,9 +32,9 @@ build: paths: - build -pmd: +spotless: stage: code-analysis - script: ./gradlew pmdMain + script: ./gradlew spotlessCheck artifacts: paths: - build diff --git a/.pmd b/.pmd deleted file mode 100644 index d2f4c69..0000000 --- a/.pmd +++ /dev/null @@ -1,8 +0,0 @@ - - - true - code-analysis/pmd.xml - false - false - true - diff --git a/build.gradle b/build.gradle index b50b1b8..b623631 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ plugins { id 'eclipse' id "org.kordamp.gradle.jandex" version "1.1.0" id 'antlr' + id 'com.diffplug.spotless' version '6.25.0' } apply from: 'buildscripts/code-analysis.gradle' @@ -114,8 +115,8 @@ tasks.named('checkstyleMain') { dependsOn 'jandex' } -tasks.named('pmdMain') { - dependsOn 'jandex', 'compileQuarkusGeneratedSourcesJava' +tasks.named('spotlessCheck') { + dependsOn 'jandex' } tasks.named('compileTestJava') { diff --git a/buildscripts/code-analysis.gradle b/buildscripts/code-analysis.gradle index eb3c9c4..af00595 100644 --- a/buildscripts/code-analysis.gradle +++ b/buildscripts/code-analysis.gradle @@ -1,4 +1,3 @@ -apply plugin: 'pmd' apply plugin: 'checkstyle' apply plugin: 'jacoco' @@ -12,15 +11,6 @@ jacocoTestReport { } } -pmd { - // Empty ruleset is necessary for exclude-pattern - // https://stackoverflow.com/questions/32247190/pmd-exclude-pattern-with-gradle - ruleSets = [] - ruleSetFiles = files("code-analysis/pmd.xml") - ignoreFailures = false - toolVersion = "6.53.0" -} - checkstyle { configDirectory = file("code-analysis") configFile = file("code-analysis/checkstyle.xml") @@ -29,24 +19,39 @@ checkstyle { toolVersion = "10.12.5" } +def antlrCheckstyleExcludes = [ + '**/TypeScriptLexerBase.java', + '**/TypeScriptParserBase.java', + '**/PythonLexerBase.java', + '**/PythonParserBase.java', + '**/PythonVersion.java', + '**/CParserBase.java', + '**/CLexerBase.java', + '**/Symbol.java', + '**/SymbolTable.java', + '**/TypeClassification.java', + '**/GoParserBase.java', + '**/CSharpLexerBase.java', + '**/CSharpParserBase.java', + '**/RustLexerBase.java', + '**/RustParserBase.java', + '**/PhpLexerBase.java', + '**/SwiftSupport.java', + '**/SwiftSupportLexer.java', +] + // Exclude ANTLR generated base classes from checkstyle (external files from ANTLR repository) tasks.withType(Checkstyle) { - exclude '**/TypeScriptLexerBase.java' - exclude '**/TypeScriptParserBase.java' - exclude '**/PythonLexerBase.java' - exclude '**/PythonParserBase.java' - exclude '**/PythonVersion.java' - exclude '**/CParserBase.java' - exclude '**/CLexerBase.java' - exclude '**/Symbol.java' - exclude '**/SymbolTable.java' - exclude '**/TypeClassification.java' - exclude '**/GoParserBase.java' - exclude '**/CSharpLexerBase.java' - exclude '**/CSharpParserBase.java' - exclude '**/RustLexerBase.java' - exclude '**/RustParserBase.java' - exclude '**/PhpLexerBase.java' - exclude '**/SwiftSupport.java' - exclude '**/SwiftSupportLexer.java' + antlrCheckstyleExcludes.each { exclude it } +} + +spotless { + java { + target 'src/*/java/**/*.java' + targetExclude antlrCheckstyleExcludes + googleJavaFormat() + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + } } diff --git a/code-analysis/code-analysis.gradle b/code-analysis/code-analysis.gradle index 74b2b51..907ec55 100644 --- a/code-analysis/code-analysis.gradle +++ b/code-analysis/code-analysis.gradle @@ -1,4 +1,3 @@ -apply plugin: 'pmd' apply plugin: 'checkstyle' apply plugin: 'jacoco' @@ -12,15 +11,6 @@ jacocoTestReport { } } -pmd { - // Empty ruleset is necessary for exclude-pattern - // https://stackoverflow.com/questions/32247190/pmd-exclude-pattern-with-gradle - ruleSets = [] - ruleSetFiles = files("code-analysis/pmd.xml") - ignoreFailures = false - toolVersion = "6.53.0" -} - checkstyle { configDirectory = file("code-analysis") configFile = file("code-analysis/checkstyle.xml") diff --git a/code-analysis/pmd.xml b/code-analysis/pmd.xml deleted file mode 100644 index 38ab891..0000000 --- a/code-analysis/pmd.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - CAU-SE PMD ruleset - - .*/io.opencensus/.* - .*/build/.* - .*/src/test/.* - .*/src/integrationTest/.* - .*/src/main/java/net/explorviz/span/hash/HighwayHash.java - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/code-analysis/pre-commit b/code-analysis/pre-commit index a6e9ca7..2e49fc7 100755 --- a/code-analysis/pre-commit +++ b/code-analysis/pre-commit @@ -2,15 +2,15 @@ echo "Running pre-commit hook" -./gradlew checkstyleMain +./gradlew spotlessApply if [ $? -ne 0 ]; then - echo "Checkstyle violations found, fix before commit" + echo "Spotless formatting failed" exit 1 fi -./gradlew pmdMain +./gradlew checkstyleMain if [ $? -ne 0 ]; then - echo "PMD violations found, fix before commit" + echo "Checkstyle violations found, fix before commit" exit 1 fi diff --git a/src/main/java/net/explorviz/code/analysis/FileIO.java b/src/main/java/net/explorviz/code/analysis/FileIO.java index 476f1ea..b4861cf 100644 --- a/src/main/java/net/explorviz/code/analysis/FileIO.java +++ b/src/main/java/net/explorviz/code/analysis/FileIO.java @@ -10,15 +10,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Class to bundle some file deletion methods. - */ -public final class FileIO { // NOCS +/** Class to bundle some file deletion methods. */ +public final class FileIO { private static final Logger LOGGER = LoggerFactory.getLogger(FileIO.class); - private FileIO() { - } + private FileIO() {} /** * Deletes a directory with all its children files and directories. @@ -34,15 +31,12 @@ public static void cleanDirectory(final String dir) throws IOException { } try (Stream walk = Files.walk(path)) { - walk - .filter(p -> !p.equals(path)) // keep the root directory + walk.filter(p -> !p.equals(path)) // keep the root directory .sorted(Comparator.reverseOrder()) .forEach(FileIO::deleteDirectoryExtract); } } - - // extract method to handle exception in lambda private static void deleteDirectoryExtract(final Path path) { try { diff --git a/src/main/java/net/explorviz/code/analysis/FileLanguageResolver.java b/src/main/java/net/explorviz/code/analysis/FileLanguageResolver.java index ffa5866..4a11888 100644 --- a/src/main/java/net/explorviz/code/analysis/FileLanguageResolver.java +++ b/src/main/java/net/explorviz/code/analysis/FileLanguageResolver.java @@ -3,9 +3,7 @@ import java.util.Locale; import net.explorviz.code.proto.Language; -/** - * Resolves the programming language of a source file from its name/extension. - */ +/** Resolves the programming language of a source file from its name/extension. */ public final class FileLanguageResolver { private FileLanguageResolver() {} @@ -71,8 +69,11 @@ private static Language resolveFromSingleFileName(final String fileName) { if (lower.endsWith(".c") || lower.endsWith(".h")) { return Language.C; } - if (lower.endsWith(".cpp") || lower.endsWith(".cxx") || lower.endsWith(".cc") - || lower.endsWith(".hpp") || lower.endsWith(".hxx")) { + if (lower.endsWith(".cpp") + || lower.endsWith(".cxx") + || lower.endsWith(".cc") + || lower.endsWith(".hpp") + || lower.endsWith(".hxx")) { return Language.CPP; } diff --git a/src/main/java/net/explorviz/code/analysis/GitAnalysis.java b/src/main/java/net/explorviz/code/analysis/GitAnalysis.java index 003156f..76397a3 100644 --- a/src/main/java/net/explorviz/code/analysis/GitAnalysis.java +++ b/src/main/java/net/explorviz/code/analysis/GitAnalysis.java @@ -23,89 +23,85 @@ /** * Entrypoint for this service. Expects a local path to a Git repository folder - * ("explorviz.repo.folder.path"). Sends - * the analysis's results to ExplorViz code service. + * ("explorviz.repo.folder.path"). Sends the analysis's results to ExplorViz code service. */ @ApplicationScoped -public class GitAnalysis { // NOPMD +public class GitAnalysis { private static final Logger LOGGER = LoggerFactory.getLogger(GitAnalysis.class); private static final int ONE_SECOND_IN_MILLISECONDS = 1000; @ConfigProperty(name = "explorviz.gitanalysis.run-mode") - /* default */ Optional runMode; // NOCS + /* default */ Optional runMode; @ConfigProperty(name = "explorviz.gitanalysis.local.storage-path") - /* default */ Optional repoPathProperty; // NOCS + /* default */ Optional repoPathProperty; @ConfigProperty(name = "explorviz.gitanalysis.remote.url") - /* default */ Optional repoRemoteUrlProperty; // NOCS + /* default */ Optional repoRemoteUrlProperty; @ConfigProperty(name = "explorviz.gitanalysis.remote.username") - /* default */ Optional usernameProperty; // NOCS + /* default */ Optional usernameProperty; @ConfigProperty(name = "explorviz.gitanalysis.remote.password") - /* default */ Optional passwordProperty; // NOCS + /* default */ Optional passwordProperty; @ConfigProperty(name = "explorviz.gitanalysis.branch") - /* default */ Optional repositoryBranchProperty; // NOCS + /* default */ Optional repositoryBranchProperty; @ConfigProperty(name = "explorviz.gitanalysis.include-in-analysis-expressions") - /* default */ Optional includeInAnalysisExpressionsProperty; // NOCS NOPMD + /* default */ Optional includeInAnalysisExpressionsProperty; @ConfigProperty(name = "explorviz.gitanalysis.exclude-from-analysis-expressions") - /* default */ Optional excludeFromAnalysisExpressionsProperty; // NOCS NOPMD + /* default */ Optional excludeFromAnalysisExpressionsProperty; @ConfigProperty(name = "explorviz.gitanalysis.application-root") - /* default */ Optional applicationRootProperty; // NOCS + /* default */ Optional applicationRootProperty; @ConfigProperty(name = "explorviz.gitanalysis.send-to-remote", defaultValue = "true") - /* default */ boolean sendToRemoteProperty; // NOCS + /* default */ boolean sendToRemoteProperty; @ConfigProperty(name = "explorviz.gitanalysis.include-data-structures", defaultValue = "true") - /* default */ boolean includeDataStructuresProperty; // NOCS + /* default */ boolean includeDataStructuresProperty; @ConfigProperty(name = "explorviz.gitanalysis.start-commit-sha1") - /* default */ Optional startCommitProperty; // NOCS + /* default */ Optional startCommitProperty; @ConfigProperty(name = "explorviz.gitanalysis.end-commit-sha1") - /* default */ Optional endCommitProperty; // NOCS + /* default */ Optional endCommitProperty; @ConfigProperty(name = "explorviz.gitanalysis.commit-analysis-limit") - /* default */ Optional commitAnalysisLimitProperty; // NOCS + /* default */ Optional commitAnalysisLimitProperty; @ConfigProperty(name = "explorviz.gitanalysis.commit-sampling-interval") - /* default */ Optional commitSamplingIntervalProperty; // NOCS + /* default */ Optional commitSamplingIntervalProperty; @ConfigProperty(name = "explorviz.gitanalysis.commit-sampling-period") - /* default */ Optional commitSamplingPeriodProperty; // NOCS + /* default */ Optional commitSamplingPeriodProperty; @ConfigProperty(name = "explorviz.gitanalysis.max-loc-for-full-analysis") - /* default */ Optional maxLocForFullAnalysisProperty; // NOCS + /* default */ Optional maxLocForFullAnalysisProperty; @ConfigProperty(name = "explorviz.gitanalysis.first-parent-commits-only", defaultValue = "true") - /* default */ boolean firstParentCommitsOnlyProperty; // NOCS + /* default */ boolean firstParentCommitsOnlyProperty; @ConfigProperty( name = "explorviz.gitanalysis.skip-commits-without-relevant-file-changes", defaultValue = "false") - /* default */ boolean skipCommitsWithoutRelevantFileChangesProperty; // NOCS + /* default */ boolean skipCommitsWithoutRelevantFileChangesProperty; @ConfigProperty(name = "explorviz.landscape.token", defaultValue = "mytokenvalue") - /* default */ String landscapeTokenProperty; // NOCS + /* default */ String landscapeTokenProperty; @ConfigProperty(name = "explorviz.gitanalysis.application-name") - /* default */ String applicationNameProperty; // NOCS + /* default */ String applicationNameProperty; - @Inject - /* package */ GrpcExporter grpcExporter; // NOCS + @Inject /* package */ GrpcExporter grpcExporter; - @Inject - /* package */ AnalysisService analysisService; // NOCS + @Inject /* package */ AnalysisService analysisService; - @Inject - /* package */ AnalysisStatusService analysisStatusService; // NOCS + @Inject /* package */ AnalysisStatusService analysisStatusService; /** * Creates an AnalysisConfig from the current properties. @@ -127,8 +123,8 @@ private AnalysisConfig createConfig() { .endCommit(endCommitProperty) .commitAnalysisLimit(commitAnalysisLimitProperty) .commitSamplingInterval(commitSamplingIntervalProperty) - .commitSamplingPeriod(CommitSamplingPeriod.fromConfigValue( - commitSamplingPeriodProperty.orElse(null))) + .commitSamplingPeriod( + CommitSamplingPeriod.fromConfigValue(commitSamplingPeriodProperty.orElse(null))) .maxLocForFullAnalysis(maxLocForFullAnalysisProperty) .firstParentCommitsOnly(firstParentCommitsOnlyProperty) .skipCommitsWithoutRelevantFileChanges(skipCommitsWithoutRelevantFileChangesProperty) @@ -137,8 +133,8 @@ private AnalysisConfig createConfig() { .build(); } - private void analyzeAndSendRepo(final DataExporter exporter) // NOCS NOPMD - throws IOException, GitAPIException, PropertyNotDefinedException, NotFoundException { // NOPMD + private void analyzeAndSendRepo(final DataExporter exporter) + throws IOException, GitAPIException, PropertyNotDefinedException, NotFoundException { final AnalysisConfig config = createConfig(); analysisService.analyzeAndSendRepo(config, exporter); } @@ -176,13 +172,14 @@ private void analyzeAndSendRepo(final DataExporter exporter) // NOCS NOPMD final long endTime = System.currentTimeMillis(); - LOGGER.atInfo().addArgument((endTime - startTime) / ONE_SECOND_IN_MILLISECONDS) + LOGGER + .atInfo() + .addArgument((endTime - startTime) / ONE_SECOND_IN_MILLISECONDS) .log("Analysis finished successfully and took {} seconds, exiting now. "); Quarkus.asyncExit(); // Quarkus.waitForExit(); - // System.exit(-1); // NOPMD + // System.exit(-1); } - } diff --git a/src/main/java/net/explorviz/code/analysis/antlr/generated/cpp/Cpp14ParserBase.java b/src/main/java/net/explorviz/code/analysis/antlr/generated/cpp/Cpp14ParserBase.java index 31cd1dc..4f3f9c8 100644 --- a/src/main/java/net/explorviz/code/analysis/antlr/generated/cpp/Cpp14ParserBase.java +++ b/src/main/java/net/explorviz/code/analysis/antlr/generated/cpp/Cpp14ParserBase.java @@ -3,9 +3,7 @@ import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.TokenStream; -/** - * Base class for the CPP14 parser that provides semantic predicates. - */ +/** Base class for the CPP14 parser that provides semantic predicates. */ public abstract class Cpp14ParserBase extends Parser { protected Cpp14ParserBase(final TokenStream input) { super(input); @@ -26,4 +24,4 @@ protected boolean isPureSpecifierAllowed() { } return false; } -} \ No newline at end of file +} diff --git a/src/main/java/net/explorviz/code/analysis/antlr/generated/java/JavaParserBase.java b/src/main/java/net/explorviz/code/analysis/antlr/generated/java/JavaParserBase.java index a83f595..033cc87 100644 --- a/src/main/java/net/explorviz/code/analysis/antlr/generated/java/JavaParserBase.java +++ b/src/main/java/net/explorviz/code/analysis/antlr/generated/java/JavaParserBase.java @@ -4,10 +4,7 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.TokenStream; -/** - * Base class for the Java parser that provides semantic predicates used by the - * grammar. - */ +/** Base class for the Java parser that provides semantic predicates used by the grammar. */ public abstract class JavaParserBase extends Parser { protected JavaParserBase(final TokenStream input) { super(input); diff --git a/src/main/java/net/explorviz/code/analysis/api/AnalysisRequest.java b/src/main/java/net/explorviz/code/analysis/api/AnalysisRequest.java index aae1410..3c9ac1f 100644 --- a/src/main/java/net/explorviz/code/analysis/api/AnalysisRequest.java +++ b/src/main/java/net/explorviz/code/analysis/api/AnalysisRequest.java @@ -7,14 +7,10 @@ import net.explorviz.code.analysis.service.ApplicationPath; import net.explorviz.code.analysis.service.CommitSamplingPeriod; -/** - * Request object for triggering a Git analysis. - */ +/** Request object for triggering a Git analysis. */ public class AnalysisRequest { - /** - * Optional multi-application configuration (name and repo-relative root per app). - */ + /** Optional multi-application configuration (name and repo-relative root per app). */ public static final class ApplicationSpec { private String name; private String root; @@ -65,8 +61,7 @@ public void setRoot(final String root) { private String fetchEndDate; private Integer socialDataTimeFrameDays; - public AnalysisRequest() { - } + public AnalysisRequest() {} public String getRepoPath() { return repoPath; @@ -286,41 +281,44 @@ public AnalysisConfig toConfig() { } } - final AnalysisConfig.Builder builder = new AnalysisConfig.Builder() - .repoPath(Optional.ofNullable(repoPath)) - .repoRemoteUrl(Optional.ofNullable(repoRemoteUrl)) - .gitUsername(Optional.ofNullable(username)) - .gitPassword(Optional.ofNullable(password)) - .branch(Optional.ofNullable(branch)) - .includeInAnalysisExpressions(Optional.ofNullable(includeInAnalysisExpressions)) - .excludeFromAnalysisExpressions(Optional.ofNullable(excludeFromAnalysisExpressions)) - .includeDataStructures(includeDataStructures) - .startCommit(Optional.ofNullable(startCommit)) - .endCommit(Optional.ofNullable(endCommit)) - .commitAnalysisLimit(Optional.ofNullable(commitAnalysisLimit)) - .commitSamplingInterval(Optional.ofNullable(commitSamplingInterval)) - .commitSamplingPeriod(CommitSamplingPeriod.fromConfigValue(commitSamplingPeriod)) - .maxLocForFullAnalysis(Optional.ofNullable(maxLocForFullAnalysis)) - .firstParentCommitsOnly(firstParentCommitsOnly) - .skipCommitsWithoutRelevantFileChanges(skipCommitsWithoutRelevantFileChanges) - .landscapeToken((landscapeToken != null && !landscapeToken.isBlank()) ? landscapeToken : "mytokenvalue") - .applicationRoot(Optional.ofNullable(applicationRoot)) - .fetchSocialData(fetchSocialData) - .syncSocialWindow(syncSocialWindow) - .socialDataTimeFrameDays(Optional.ofNullable(socialDataTimeFrameDays)) - .fetchEndDate(Optional.ofNullable(fetchEndDate).filter(s -> !s.isEmpty())); + final AnalysisConfig.Builder builder = + new AnalysisConfig.Builder() + .repoPath(Optional.ofNullable(repoPath)) + .repoRemoteUrl(Optional.ofNullable(repoRemoteUrl)) + .gitUsername(Optional.ofNullable(username)) + .gitPassword(Optional.ofNullable(password)) + .branch(Optional.ofNullable(branch)) + .includeInAnalysisExpressions(Optional.ofNullable(includeInAnalysisExpressions)) + .excludeFromAnalysisExpressions(Optional.ofNullable(excludeFromAnalysisExpressions)) + .includeDataStructures(includeDataStructures) + .startCommit(Optional.ofNullable(startCommit)) + .endCommit(Optional.ofNullable(endCommit)) + .commitAnalysisLimit(Optional.ofNullable(commitAnalysisLimit)) + .commitSamplingInterval(Optional.ofNullable(commitSamplingInterval)) + .commitSamplingPeriod(CommitSamplingPeriod.fromConfigValue(commitSamplingPeriod)) + .maxLocForFullAnalysis(Optional.ofNullable(maxLocForFullAnalysis)) + .firstParentCommitsOnly(firstParentCommitsOnly) + .skipCommitsWithoutRelevantFileChanges(skipCommitsWithoutRelevantFileChanges) + .landscapeToken( + (landscapeToken != null && !landscapeToken.isBlank()) + ? landscapeToken + : "mytokenvalue") + .applicationRoot(Optional.ofNullable(applicationRoot)) + .fetchSocialData(fetchSocialData) + .syncSocialWindow(syncSocialWindow) + .socialDataTimeFrameDays(Optional.ofNullable(socialDataTimeFrameDays)) + .fetchEndDate(Optional.ofNullable(fetchEndDate).filter(s -> !s.isEmpty())); if (!paths.isEmpty()) { builder.applicationPaths(paths); } else { String resolvedName = applicationName != null ? applicationName.trim() : ""; if (resolvedName.isEmpty()) { - resolvedName = AnalysisConfig.deriveRepositoryName( - Optional.ofNullable(repoPath), - Optional.ofNullable(repoRemoteUrl)); + resolvedName = + AnalysisConfig.deriveRepositoryName( + Optional.ofNullable(repoPath), Optional.ofNullable(repoRemoteUrl)); } - builder.applicationName(resolvedName) - .applicationRoot(Optional.ofNullable(applicationRoot)); + builder.applicationName(resolvedName).applicationRoot(Optional.ofNullable(applicationRoot)); } return builder.build(); } diff --git a/src/main/java/net/explorviz/code/analysis/api/AnalysisResource.java b/src/main/java/net/explorviz/code/analysis/api/AnalysisResource.java index 9717370..db72d8f 100644 --- a/src/main/java/net/explorviz/code/analysis/api/AnalysisResource.java +++ b/src/main/java/net/explorviz/code/analysis/api/AnalysisResource.java @@ -19,7 +19,6 @@ import net.explorviz.code.analysis.export.JsonExporter; import net.explorviz.code.analysis.service.AnalysisCancellationResult; import net.explorviz.code.analysis.service.AnalysisConfig; -import net.explorviz.code.analysis.service.AnalysisProgressState; import net.explorviz.code.analysis.service.AnalysisStatusService; import net.explorviz.code.analysis.service.ConcurrentAnalysisService; import net.explorviz.code.analysis.service.LocalRepositoryService; @@ -27,37 +26,29 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * REST resource for triggering Git analysis operations. - */ +/** REST resource for triggering Git analysis operations. */ @Path("/api/analysis") public class AnalysisResource { private static final Logger LOGGER = LoggerFactory.getLogger(AnalysisResource.class); @ConfigProperty(name = "explorviz.gitanalysis.send-to-remote", defaultValue = "true") - /* default */ boolean sendToRemoteProperty; // NOCS + /* default */ boolean sendToRemoteProperty; - @Inject - /* default */ ConcurrentAnalysisService analysisService; // NOCS + @Inject /* default */ ConcurrentAnalysisService analysisService; - @Inject - /* default */ GrpcExporter grpcExporter; // NOCS + @Inject /* default */ GrpcExporter grpcExporter; - @Inject - /* default */ AnalysisStatusService analysisStatusService; // NOCS + @Inject /* default */ AnalysisStatusService analysisStatusService; - @Inject - /* default */ LocalRepositoryService localRepositoryService; // NOCS + @Inject /* default */ LocalRepositoryService localRepositoryService; /** - * Triggers a Git repository analysis with the provided configuration. The - * request is queued and processed - * asynchronously to handle concurrent requests safely. + * Triggers a Git repository analysis with the provided configuration. The request is queued and + * processed asynchronously to handle concurrent requests safely. * * @param request The analysis request containing configuration - * @return Response indicating the request was accepted (202) or an error - * occurred + * @return Response indicating the request was accepted (202) or an error occurred */ @POST @Path("/trigger") @@ -72,8 +63,10 @@ public Response triggerAnalysis(final AnalysisRequest request) { } try { - final String repoInfo = request.getRepoPath() != null ? request.getRepoPath() - : (request.getRepoRemoteUrl() != null ? request.getRepoRemoteUrl() : "unknown"); + final String repoInfo = + request.getRepoPath() != null + ? request.getRepoPath() + : (request.getRepoRemoteUrl() != null ? request.getRepoRemoteUrl() : "unknown"); LOGGER.info("📥 Received analysis request for repository: {}", repoInfo); final String landscapeToken = request.getLandscapeToken(); @@ -85,31 +78,35 @@ public Response triggerAnalysis(final AnalysisRequest request) { if (request.isSendToRemote()) { exporter = grpcExporter; } else { - exporter = new JsonExporter(config.getRepositoryName(), config.primaryApplicationNameForExport()); + exporter = + new JsonExporter(config.getRepositoryName(), config.primaryApplicationNameForExport()); } // Submit to queue for async processing - analysisService.analyzeAndSendRepoAsync(config, exporter) - .whenComplete((result, error) -> { - if (analysisStatusService.getStatus(landscapeToken) - .map(analysisStatusService::isTerminalStatus) - .orElse(false)) { - return; - } - if (error != null) { - if (isCancelledError(error)) { - analysisStatusService.markCancelled(landscapeToken); - LOGGER.info("🛑 Async analysis cancelled for {}", repoInfo); - } else { - analysisStatusService.markFailed(landscapeToken); - LOGGER.error("❌ Async analysis failed for {}: {}", - repoInfo, error.getMessage()); - } - } else { - analysisStatusService.markFinished(landscapeToken); - LOGGER.info("✅ Async analysis completed for {}", repoInfo); - } - }); + analysisService + .analyzeAndSendRepoAsync(config, exporter) + .whenComplete( + (result, error) -> { + if (analysisStatusService + .getStatus(landscapeToken) + .map(analysisStatusService::isTerminalStatus) + .orElse(false)) { + return; + } + if (error != null) { + if (isCancelledError(error)) { + analysisStatusService.markCancelled(landscapeToken); + LOGGER.info("🛑 Async analysis cancelled for {}", repoInfo); + } else { + analysisStatusService.markFailed(landscapeToken); + LOGGER.error( + "❌ Async analysis failed for {}: {}", repoInfo, error.getMessage()); + } + } else { + analysisStatusService.markFinished(landscapeToken); + LOGGER.info("✅ Async analysis completed for {}", repoInfo); + } + }); LOGGER.info("✅ Analysis request queued for repository: {}", repoInfo); return Response.status(Response.Status.ACCEPTED) @@ -131,12 +128,16 @@ public Response cancelAnalysis(@PathParam("landscapeToken") final String landsca final AnalysisCancellationResult result = analysisService.cancelAnalysis(landscapeToken); return switch (result) { case CANCELLED -> Response.ok("Analysis cancellation requested").build(); - case NOT_FOUND -> Response.status(Response.Status.NOT_FOUND) - .entity("No analysis job found for landscapeToken=" + landscapeToken) - .build(); - case ALREADY_TERMINAL -> Response.status(Response.Status.CONFLICT) - .entity("Analysis job is already in a terminal state for landscapeToken=" + landscapeToken) - .build(); + case NOT_FOUND -> + Response.status(Response.Status.NOT_FOUND) + .entity("No analysis job found for landscapeToken=" + landscapeToken) + .build(); + case ALREADY_TERMINAL -> + Response.status(Response.Status.CONFLICT) + .entity( + "Analysis job is already in a terminal state for landscapeToken=" + + landscapeToken) + .build(); }; } @@ -168,29 +169,38 @@ public Response getLocalRepositories() { @GET @Path("/status/{landscapeToken}") @Produces(MediaType.TEXT_PLAIN) - public Response getStatusByLandscapeToken(@PathParam("landscapeToken") final String landscapeToken) { - return analysisStatusService.getStatus(landscapeToken) + public Response getStatusByLandscapeToken( + @PathParam("landscapeToken") final String landscapeToken) { + return analysisStatusService + .getStatus(landscapeToken) .map(status -> Response.ok(status).build()) - .orElseGet(() -> Response.status(Response.Status.NOT_FOUND) - .entity("No analysis status found for landscapeToken=" + landscapeToken) - .build()); + .orElseGet( + () -> + Response.status(Response.Status.NOT_FOUND) + .entity("No analysis status found for landscapeToken=" + landscapeToken) + .build()); } @GET @Path("/state/{landscapeToken}") @Produces(MediaType.APPLICATION_JSON) - public Response getStateByLandscapeToken(@PathParam("landscapeToken") final String landscapeToken) { - return analysisStatusService.getState(landscapeToken) + public Response getStateByLandscapeToken( + @PathParam("landscapeToken") final String landscapeToken) { + return analysisStatusService + .getState(landscapeToken) .map(state -> Response.ok(state).build()) - .orElseGet(() -> Response.status(Response.Status.NOT_FOUND) - .entity("No analysis state found for landscapeToken=" + landscapeToken) - .build()); + .orElseGet( + () -> + Response.status(Response.Status.NOT_FOUND) + .entity("No analysis state found for landscapeToken=" + landscapeToken) + .build()); } @GET @Path("/state/stream/{landscapeToken}") @Produces(MediaType.SERVER_SENT_EVENTS) - public void streamStateByLandscapeToken(@PathParam("landscapeToken") final String landscapeToken, + public void streamStateByLandscapeToken( + @PathParam("landscapeToken") final String landscapeToken, final SseEventSink eventSink, final Sse sse) { analysisStatusService.subscribeToStateUpdates(landscapeToken, eventSink, sse); diff --git a/src/main/java/net/explorviz/code/analysis/api/HealthResource.java b/src/main/java/net/explorviz/code/analysis/api/HealthResource.java index b580029..2beca2b 100644 --- a/src/main/java/net/explorviz/code/analysis/api/HealthResource.java +++ b/src/main/java/net/explorviz/code/analysis/api/HealthResource.java @@ -13,4 +13,3 @@ public String health() { return "OK"; } } - diff --git a/src/main/java/net/explorviz/code/analysis/exceptions/AnalysisCancelledException.java b/src/main/java/net/explorviz/code/analysis/exceptions/AnalysisCancelledException.java index 289cb51..2e7a492 100644 --- a/src/main/java/net/explorviz/code/analysis/exceptions/AnalysisCancelledException.java +++ b/src/main/java/net/explorviz/code/analysis/exceptions/AnalysisCancelledException.java @@ -1,8 +1,6 @@ package net.explorviz.code.analysis.exceptions; -/** - * Thrown when an analysis job is cancelled while running. - */ +/** Thrown when an analysis job is cancelled while running. */ public class AnalysisCancelledException extends RuntimeException { private final String landscapeToken; diff --git a/src/main/java/net/explorviz/code/analysis/exceptions/DebugFileWriter.java b/src/main/java/net/explorviz/code/analysis/exceptions/DebugFileWriter.java index b5e880e..1940e7a 100644 --- a/src/main/java/net/explorviz/code/analysis/exceptions/DebugFileWriter.java +++ b/src/main/java/net/explorviz/code/analysis/exceptions/DebugFileWriter.java @@ -9,25 +9,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Simple Class to write failing file content to files. - */ +/** Simple Class to write failing file content to files. */ public final class DebugFileWriter { private static final Logger LOGGER = LoggerFactory.getLogger(DebugFileWriter.class); - private DebugFileWriter() { - } + private DebugFileWriter() {} /** * Saves the given content to a file. * * @param directoryPath the path where the file should be created - * @param content the content that should be written - * @param filename the name of the file + * @param content the content that should be written + * @param filename the name of the file */ - public static void saveDebugFile(final String directoryPath, final String content, - final String filename) { + public static void saveDebugFile( + final String directoryPath, final String content, final String filename) { try { final Path path = Paths.get(directoryPath); if (!Files.exists(path)) { @@ -43,7 +40,7 @@ public static void saveDebugFile(final String directoryPath, final String conten * Saves the given compilationUnit as a YAML represented file. * * @param compilationUnit the Abstract syntax tree - * @param path the storage path + * @param path the storage path */ public static void saveAstAsYaml(final CompilationUnit compilationUnit, final String path) { final YamlPrinter printer = new YamlPrinter(true); diff --git a/src/main/java/net/explorviz/code/analysis/exceptions/MalformedPathException.java b/src/main/java/net/explorviz/code/analysis/exceptions/MalformedPathException.java index 2ad4822..f67ca95 100644 --- a/src/main/java/net/explorviz/code/analysis/exceptions/MalformedPathException.java +++ b/src/main/java/net/explorviz/code/analysis/exceptions/MalformedPathException.java @@ -2,13 +2,10 @@ import java.io.Serial; -/** - * Exception for malformed paths. - */ +/** Exception for malformed paths. */ public class MalformedPathException extends RuntimeException { - @Serial - private static final long serialVersionUID = 265745021; + @Serial private static final long serialVersionUID = 265745021; public MalformedPathException(final String reason) { super(reason); diff --git a/src/main/java/net/explorviz/code/analysis/exceptions/NotFoundException.java b/src/main/java/net/explorviz/code/analysis/exceptions/NotFoundException.java index 5fefc2f..25c2aa0 100644 --- a/src/main/java/net/explorviz/code/analysis/exceptions/NotFoundException.java +++ b/src/main/java/net/explorviz/code/analysis/exceptions/NotFoundException.java @@ -2,13 +2,10 @@ import java.io.Serial; -/** - * Exception for not found things. - */ +/** Exception for not found things. */ public class NotFoundException extends Exception { - @Serial - private static final long serialVersionUID = 987345021; + @Serial private static final long serialVersionUID = 987345021; public NotFoundException(final String reason) { super(reason); diff --git a/src/main/java/net/explorviz/code/analysis/exceptions/PropertyNotDefinedException.java b/src/main/java/net/explorviz/code/analysis/exceptions/PropertyNotDefinedException.java index 50966bd..b27d7cb 100644 --- a/src/main/java/net/explorviz/code/analysis/exceptions/PropertyNotDefinedException.java +++ b/src/main/java/net/explorviz/code/analysis/exceptions/PropertyNotDefinedException.java @@ -1,38 +1,35 @@ -package net.explorviz.code.analysis.exceptions; - -import java.io.Serial; - -/** - * Exception regarding not existing property. - */ -public class PropertyNotDefinedException extends Exception { - - @Serial - private static final long serialVersionUID = 765345121; - - public PropertyNotDefinedException(final String propertyName) { - this(propertyName, ""); - } - - public PropertyNotDefinedException(final String propertyName, final String errorMessage) { - super(toMessage(new String[] {propertyName}, errorMessage)); - } - - public PropertyNotDefinedException(final String[] propertyNames) { //NOPMD - this(propertyNames, ""); - } - - public PropertyNotDefinedException(final String[] propertyNames, final String errorMessage) { - super(PropertyNotDefinedException.toMessage(propertyNames, errorMessage)); - } - - private static String toMessage(final String[] propertyNames, final String errorMessage) { - final StringBuilder message = new StringBuilder(80); - message.append("Properties "); - for (final String item : propertyNames) { - message.append('<').append(item).append("> "); - } - message.append("not defined in application.properties\n").append(errorMessage); - return message.toString(); - } -} +package net.explorviz.code.analysis.exceptions; + +import java.io.Serial; + +/** Exception regarding not existing property. */ +public class PropertyNotDefinedException extends Exception { + + @Serial private static final long serialVersionUID = 765345121; + + public PropertyNotDefinedException(final String propertyName) { + this(propertyName, ""); + } + + public PropertyNotDefinedException(final String propertyName, final String errorMessage) { + super(toMessage(new String[] {propertyName}, errorMessage)); + } + + public PropertyNotDefinedException(final String[] propertyNames) { + this(propertyNames, ""); + } + + public PropertyNotDefinedException(final String[] propertyNames, final String errorMessage) { + super(PropertyNotDefinedException.toMessage(propertyNames, errorMessage)); + } + + private static String toMessage(final String[] propertyNames, final String errorMessage) { + final StringBuilder message = new StringBuilder(80); + message.append("Properties "); + for (final String item : propertyNames) { + message.append('<').append(item).append("> "); + } + message.append("not defined in application.properties\n").append(errorMessage); + return message.toString(); + } +} diff --git a/src/main/java/net/explorviz/code/analysis/export/DataExporter.java b/src/main/java/net/explorviz/code/analysis/export/DataExporter.java index c18f80d..24776e9 100644 --- a/src/main/java/net/explorviz/code/analysis/export/DataExporter.java +++ b/src/main/java/net/explorviz/code/analysis/export/DataExporter.java @@ -8,53 +8,58 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import net.explorviz.code.proto.CommitData; -import net.explorviz.code.proto.ContributorData; import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.StateData; import net.explorviz.code.proto.TrackableResourceEvent; /** - * A DataExporter handles the export of {@link FileData}, {@link CommitData} and - * request of {@link StateData}. + * A DataExporter handles the export of {@link FileData}, {@link CommitData} and request of {@link + * StateData}. */ public interface DataExporter { - StateData getStateData(final String repositoryName, final String branchName, final String token, - final Map applicationPaths, final String repositoryUrl, + StateData getStateData( + final String repositoryName, + final String branchName, + final String token, + final Map applicationPaths, + final String repositoryUrl, final boolean skipLatestCommitLookup); void persistFile(final FileData fileData); /** - * Persists a batch of files in a single operation. The default implementation - * calls {@link #persistFile} for each file sequentially; exporters that support - * client-streaming gRPC should override this to use {@code PersistFiles}. + * Persists a batch of files in a single operation. The default implementation calls {@link + * #persistFile} for each file sequentially; exporters that support client-streaming gRPC should + * override this to use {@code PersistFiles}. */ default void persistFilesBatch(final List files) { files.forEach(this::persistFile); } /** - * Drains {@code completedFiles} as files arrive and dispatches them to - * {@link #persistFilesBatch} in chunks of {@code batchSize}. Runs concurrently - * with the analysis pipeline; call {@code analysisFinished.countDown()} once - * all producers have finished to signal the final flush. + * Drains {@code completedFiles} as files arrive and dispatches them to {@link #persistFilesBatch} + * in chunks of {@code batchSize}. Runs concurrently with the analysis pipeline; call {@code + * analysisFinished.countDown()} once all producers have finished to signal the final flush. * - *

Uses a single batch accumulator so each commit produces at most - * {@code ceil(fileCount / batchSize)} batches. Concurrent sends are limited by - * {@code maxConcurrentSends}. + *

Uses a single batch accumulator so each commit produces at most {@code ceil(fileCount / + * batchSize)} batches. Concurrent sends are limited by {@code maxConcurrentSends}. */ - default void persistFilesFromQueueInBatches(final BlockingQueue completedFiles, - final CountDownLatch analysisFinished, final int batchSize) { + default void persistFilesFromQueueInBatches( + final BlockingQueue completedFiles, + final CountDownLatch analysisFinished, + final int batchSize) { persistFilesFromQueueInBatches(completedFiles, analysisFinished, batchSize, 1); } /** - * Like {@link #persistFilesFromQueueInBatches(BlockingQueue, CountDownLatch, int)} - * but allows up to {@code maxConcurrentSends} gRPC batch sends in flight. + * Like {@link #persistFilesFromQueueInBatches(BlockingQueue, CountDownLatch, int)} but allows up + * to {@code maxConcurrentSends} gRPC batch sends in flight. */ - default void persistFilesFromQueueInBatches(final BlockingQueue completedFiles, - final CountDownLatch analysisFinished, final int batchSize, + default void persistFilesFromQueueInBatches( + final BlockingQueue completedFiles, + final CountDownLatch analysisFinished, + final int batchSize, final int maxConcurrentSends) { final int effectiveBatchSize = Math.max(1, batchSize); final int effectiveConcurrency = Math.max(1, maxConcurrentSends); @@ -79,8 +84,8 @@ default void persistFilesFromQueueInBatches(final BlockingQueue comple waitForPersistSendThreads(sendThreads); } - private void dispatchPersistBatch(final List batch, final Semaphore sendPermits, - final List sendThreads) { + private void dispatchPersistBatch( + final List batch, final Semaphore sendPermits, final List sendThreads) { if (batch.isEmpty()) { return; } @@ -92,13 +97,16 @@ private void dispatchPersistBatch(final List batch, final Semaphore se } final List toSend = List.copyOf(batch); batch.clear(); - sendThreads.add(Thread.ofVirtual().start(() -> { - try { - persistFilesBatch(toSend); - } finally { - sendPermits.release(); - } - })); + sendThreads.add( + Thread.ofVirtual() + .start( + () -> { + try { + persistFilesBatch(toSend); + } finally { + sendPermits.release(); + } + })); } private void waitForPersistSendThreads(final List sendThreads) { @@ -116,27 +124,28 @@ private void waitForPersistSendThreads(final List sendThreads) { } /** - * Persists files as they become available on the queue, while analysis is still - * running. Uses {@link Runtime#availableProcessors()} minus one when - * parallelism - * is not positive. + * Persists files as they become available on the queue, while analysis is still running. Uses + * {@link Runtime#availableProcessors()} minus one when parallelism is not positive. */ - default void persistFilesFromQueue(final BlockingQueue completedFiles, - final CountDownLatch analysisFinished) { + default void persistFilesFromQueue( + final BlockingQueue completedFiles, final CountDownLatch analysisFinished) { final int parallelism = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); persistFilesFromQueue(completedFiles, analysisFinished, parallelism); } /** - * Persists files as they become available on the queue, while analysis is still - * running, using up to {@code parallelism} concurrent persist operations. + * Persists files as they become available on the queue, while analysis is still running, using up + * to {@code parallelism} concurrent persist operations. */ - default void persistFilesFromQueue(final BlockingQueue completedFiles, - final CountDownLatch analysisFinished, final int parallelism) { + default void persistFilesFromQueue( + final BlockingQueue completedFiles, + final CountDownLatch analysisFinished, + final int parallelism) { final int effectiveParallelism = Math.max(1, parallelism); final Semaphore inFlightPermits = new Semaphore(effectiveParallelism); try { - while (analysisFinished.getCount() > 0 || !completedFiles.isEmpty() + while (analysisFinished.getCount() > 0 + || !completedFiles.isEmpty() || inFlightPermits.availablePermits() < effectiveParallelism) { inFlightPermits.acquire(); final FileData fileData; @@ -151,32 +160,34 @@ default void persistFilesFromQueue(final BlockingQueue completedFiles, inFlightPermits.release(); continue; } - Thread.ofVirtual().start(() -> { - try { - persistFile(fileData); - } finally { - inFlightPermits.release(); - } - }); + Thread.ofVirtual() + .start( + () -> { + try { + persistFile(fileData); + } finally { + inFlightPermits.release(); + } + }); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } - default void relinkResourceEvents(String token, String repoName){} + default void relinkResourceEvents(String token, String repoName) {} void persistCommit(final CommitData commitData); void persistTrackableResourceEvent(final TrackableResourceEvent trackableResourceEvent); - default void persistTrackableResourceEventBatch(final List trackableResourceEvents) { + default void persistTrackableResourceEventBatch( + final List trackableResourceEvents) { for (final TrackableResourceEvent trackableResourceEvent : trackableResourceEvents) { persistTrackableResourceEvent(trackableResourceEvent); } } - boolean isRemote(); boolean isInvalidCommitHash(final String hash); diff --git a/src/main/java/net/explorviz/code/analysis/export/FileDataExportFilter.java b/src/main/java/net/explorviz/code/analysis/export/FileDataExportFilter.java index 81b1ba6..4375dbf 100644 --- a/src/main/java/net/explorviz/code/analysis/export/FileDataExportFilter.java +++ b/src/main/java/net/explorviz/code/analysis/export/FileDataExportFilter.java @@ -2,25 +2,19 @@ import net.explorviz.code.proto.FileData; -/** - * Filters {@link FileData} before persistence based on export options. - */ +/** Filters {@link FileData} before persistence based on export options. */ public final class FileDataExportFilter { private FileDataExportFilter() {} /** - * Returns file data suitable for export. When data structures are excluded, - * classes and functions (including nested parameters) are stripped while - * file-level metadata and metrics are retained. + * Returns file data suitable for export. When data structures are excluded, classes and functions + * (including nested parameters) are stripped while file-level metadata and metrics are retained. */ public static FileData filter(final FileData fileData, final boolean includeDataStructures) { if (includeDataStructures) { return fileData; } - return fileData.toBuilder() - .clearClasses() - .clearFunctions() - .build(); + return fileData.toBuilder().clearClasses().clearFunctions().build(); } } diff --git a/src/main/java/net/explorviz/code/analysis/export/GrpcExporter.java b/src/main/java/net/explorviz/code/analysis/export/GrpcExporter.java index 4f3159c..083a735 100644 --- a/src/main/java/net/explorviz/code/analysis/export/GrpcExporter.java +++ b/src/main/java/net/explorviz/code/analysis/export/GrpcExporter.java @@ -24,9 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Basic GRPC handler. - */ +/** Basic GRPC handler. */ @ApplicationScoped public final class GrpcExporter implements DataExporter { @@ -34,8 +32,8 @@ public final class GrpcExporter implements DataExporter { private static final String GRPC_CLIENT_NAME = "codeAnalysisGrpcClient"; - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.of("UTC")); + private static final DateTimeFormatter FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("UTC")); @GrpcClient(GRPC_CLIENT_NAME) /* package */ FileDataServiceGrpc.FileDataServiceBlockingStub fileDataGrpcClient; @@ -50,7 +48,8 @@ public final class GrpcExporter implements DataExporter { /* package */ StateDataServiceGrpc.StateDataServiceBlockingStub stateDataGrpcClient; @GrpcClient(GRPC_CLIENT_NAME) - /* package */ TrackableResourceServiceGrpc.TrackableResourceServiceBlockingStub trackableResourceGrpcClient; + /* package */ TrackableResourceServiceGrpc.TrackableResourceServiceBlockingStub + trackableResourceGrpcClient; @ConfigProperty(name = "explorviz.landscape.token") /* default */ String landscapeTokenProperty; @@ -65,9 +64,12 @@ public final class GrpcExporter implements DataExporter { * @return the state of the remote database */ @Override - public StateData getStateData(final String repositoryName, final String branchName, + public StateData getStateData( + final String repositoryName, + final String branchName, final String token, - final Map applicationPaths, final String repositoryUrl, + final Map applicationPaths, + final String repositoryUrl, final boolean skipLatestCommitLookup) { final StateDataRequest.Builder requestBuilder = StateDataRequest.newBuilder(); requestBuilder.setBranchName(branchName); @@ -135,22 +137,26 @@ public void persistCommit(final CommitData commitData) { @Override public void persistTrackableResourceEvent(final TrackableResourceEvent trackableResourceEvent) { - Instant instant = Instant.ofEpochSecond(trackableResourceEvent.getEventTimestamp().getSeconds(), - trackableResourceEvent.getEventTimestamp().getNanos()); + Instant instant = + Instant.ofEpochSecond( + trackableResourceEvent.getEventTimestamp().getSeconds(), + trackableResourceEvent.getEventTimestamp().getNanos()); LOGGER.info( "Sending TrackableResourceEvent {} for {} #{} at timestamp {}", trackableResourceEvent.getAnnotationType(), trackableResourceEvent.getResourceType(), trackableResourceEvent.getResourceId(), - FORMATTER.format(instant) - ); + FORMATTER.format(instant)); try { trackableResourceGrpcClient.persistTrackableResourceEvent(trackableResourceEvent); } catch (final Exception e) { - LOGGER.error("Failed to send trackable resource event {}: {}", trackableResourceEvent.getAnnotationId(), + LOGGER.error( + "Failed to send trackable resource event {}: {}", + trackableResourceEvent.getAnnotationId(), e.getMessage()); - throw new RuntimeException("Failed to send trackable resource event for " - + trackableResourceEvent.getAnnotationId(), e); + throw new RuntimeException( + "Failed to send trackable resource event for " + trackableResourceEvent.getAnnotationId(), + e); } } @@ -162,10 +168,12 @@ public void persistTrackableResourceEventBatch(final List applicationPaths, final String repositoryUrl, + final Map applicationPaths, + final String repositoryUrl, final boolean skipLatestCommitLookup) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(repositoryName) .addArgument(branchName) .addArgument(applicationPaths != null ? applicationPaths.keySet() : "[]") @@ -81,10 +87,11 @@ public StateData getStateData(final String repositoryName, final String branchNa final StateData stateData = StateData.newBuilder().build(); try { - final StateDataRequest.Builder requestBuilder = StateDataRequest.newBuilder() - .setRepositoryName(repositoryName) - .setBranchName(branchName) - .setLandscapeToken(token); + final StateDataRequest.Builder requestBuilder = + StateDataRequest.newBuilder() + .setRepositoryName(repositoryName) + .setBranchName(branchName) + .setLandscapeToken(token); if (repositoryUrl != null && !repositoryUrl.isBlank()) { requestBuilder.setRepositoryUrl(repositoryUrl); } @@ -114,12 +121,9 @@ public StateData getStateData(final String repositoryName, final String branchNa Files.write(Paths.get(storageDirectory, stateFileName), resultJson.getBytes()); Files.write(Paths.get(storageDirectory, requestFileName), requestJson.getBytes()); - LOGGER.atInfo() - .log("✅ Successfully exported state request and result"); + LOGGER.atInfo().log("✅ Successfully exported state request and result"); } catch (IOException e) { - LOGGER.atError() - .addArgument(e.getMessage()) - .log("❌ Failed to export state data: {}"); + LOGGER.atError().addArgument(e.getMessage()).log("❌ Failed to export state data: {}"); } return stateData; } @@ -127,7 +131,8 @@ public StateData getStateData(final String repositoryName, final String branchNa @Override public void persistFile(final FileData fileData) { try { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(fileData.getFilePath()) .addArgument(fileData.getLanguage()) .log("📤 Exporting file data: {} (language: {})"); @@ -156,15 +161,14 @@ public void persistFile(final FileData fileData) { Files.write(outputPath, json.getBytes()); - LOGGER.atInfo() - .addArgument(fileName) - .log("✅ Successfully exported file data to: {}"); - } catch (IOException e) { // NOPMD - LOGGER.atError() + LOGGER.atInfo().addArgument(fileName).log("✅ Successfully exported file data to: {}"); + } catch (IOException e) { + LOGGER + .atError() .addArgument(fileData.getFilePath()) .addArgument(e.getMessage()) .log("❌ Failed to export file data for {}: {}"); - throw new RuntimeException(e); // NOPMD + throw new RuntimeException(e); } } @@ -172,11 +176,11 @@ public void persistFile(final FileData fileData) { public void persistCommit(final CommitData commitData) { try { final String json = unescapeHtml(JsonFormat.printer().print(commitData)); - final String fileName = "CommitReport_" + commitData.getCommitId() + "_" + commitCount - + JSON_FILE_EXTENSION; + final String fileName = + "CommitReport_" + commitData.getCommitId() + "_" + commitCount + JSON_FILE_EXTENSION; Files.write(Paths.get(storageDirectory, fileName), json.getBytes()); - } catch (IOException e) { // NOPMD - throw new RuntimeException(e); // NOPMD + } catch (IOException e) { + throw new RuntimeException(e); } this.commitCount++; } @@ -185,10 +189,11 @@ public void persistCommit(final CommitData commitData) { public void persistTrackableResourceEvent(final TrackableResourceEvent trackableResourceEvent) { try { final String json = unescapeHtml(JsonFormat.printer().print(trackableResourceEvent)); - final String fileName = "TrackableResourceEvent" + trackableResourceEvent.getResourceId() + JSON_FILE_EXTENSION; + final String fileName = + "TrackableResourceEvent" + trackableResourceEvent.getResourceId() + JSON_FILE_EXTENSION; Files.write(Paths.get(storageDirectory, fileName), json.getBytes()); - } catch (IOException e) { // NOPMD - throw new RuntimeException(e); // NOPMD + } catch (IOException e) { + throw new RuntimeException(e); } } @@ -203,8 +208,8 @@ public boolean isInvalidCommitHash(final String hash) { } /** - * Protobuf's JsonFormat uses Gson which HTML-escapes certain characters. - * Since our JSON is written to files and not embedded in HTML, we undo this. + * Protobuf's JsonFormat uses Gson which HTML-escapes certain characters. Since our JSON is + * written to files and not embedded in HTML, we undo this. */ private static String unescapeHtml(final String json) { return json.replace("\\u003c", "<") diff --git a/src/main/java/net/explorviz/code/analysis/export/VoidExporter.java b/src/main/java/net/explorviz/code/analysis/export/VoidExporter.java index 47105eb..ebccc6a 100644 --- a/src/main/java/net/explorviz/code/analysis/export/VoidExporter.java +++ b/src/main/java/net/explorviz/code/analysis/export/VoidExporter.java @@ -2,20 +2,20 @@ import java.util.Map; import net.explorviz.code.proto.CommitData; -import net.explorviz.code.proto.ContributorData; import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.StateData; import net.explorviz.code.proto.TrackableResourceEvent; -/** - * Dummy to dump the data into void. - */ +/** Dummy to dump the data into void. */ public class VoidExporter implements DataExporter { @Override - public StateData getStateData(final String repositoryName, final String branchName, + public StateData getStateData( + final String repositoryName, + final String branchName, final String token, - final Map applicationPaths, final String repositoryUrl, + final Map applicationPaths, + final String repositoryUrl, final boolean skipLatestCommitLookup) { return StateData.newBuilder().build(); } diff --git a/src/main/java/net/explorviz/code/analysis/git/DirectoryFinder.java b/src/main/java/net/explorviz/code/analysis/git/DirectoryFinder.java index 474712f..0d4c133 100644 --- a/src/main/java/net/explorviz/code/analysis/git/DirectoryFinder.java +++ b/src/main/java/net/explorviz/code/analysis/git/DirectoryFinder.java @@ -15,26 +15,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Class to ease the finding of directories inside a local git Repository. - */ +/** Class to ease the finding of directories inside a local git Repository. */ public final class DirectoryFinder { private static final Logger LOGGER = LoggerFactory.getLogger(DirectoryFinder.class); - private DirectoryFinder() { - } + private DirectoryFinder() {} /** * Searches and return the relative path to the directory matching the search string. * * @param paths a list of search strings for the paths. - * @param root the path the return should be relative to + * @param root the path the return should be relative to * @return the directories matching the search strings * @throws NotFoundException thrown if no directory matches the given search string */ - public static List getRelativeDirectory(final List paths, - final String root) + public static List getRelativeDirectory(final List paths, final String root) throws NotFoundException { final List relativePaths = new ArrayList<>(); for (final String path : getDirectories(root, paths)) { @@ -47,36 +43,44 @@ public static List getRelativeDirectory(final List paths, * Searches and return the absolute path to the directory matching the search string. * * @param searchPaths a list of search strings for the paths. - * @param root the root path to start the search from + * @param root the root path to start the search from * @return the directories matching the search strings * @throws MalformedPathException thrown if the search string is malformed and can not be used - * @throws NotFoundException thrown if no directory matches the given search string + * @throws NotFoundException thrown if no directory matches the given search string */ public static List getDirectories(String root, List searchPaths) throws NotFoundException { - Set pathSet = new HashSet<>(); // Use a HashSet to avoid duplicates + Set pathSet = new HashSet<>(); // Use a HashSet to avoid duplicates Path startPath = Paths.get(root); try { - Files.walkFileTree(startPath, new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) - throws IOException { - for (String searchPath : searchPaths) { - String glob = searchPath; - if (!glob.startsWith("glob:") && !glob.startsWith("regex:")) { - glob = "glob:" + glob; - } - java.nio.file.PathMatcher matcher = java.nio.file.FileSystems.getDefault().getPathMatcher(glob); - if (matcher.matches(startPath.relativize(dir))) { - pathSet.add(dir.toAbsolutePath().normalize() - .toString()); // Normalize the path and add to the set - LOGGER.atTrace().addArgument(dir).log("Directory matched glob and was added to set: {}"); + Files.walkFileTree( + startPath, + new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + for (String searchPath : searchPaths) { + String glob = searchPath; + if (!glob.startsWith("glob:") && !glob.startsWith("regex:")) { + glob = "glob:" + glob; + } + java.nio.file.PathMatcher matcher = + java.nio.file.FileSystems.getDefault().getPathMatcher(glob); + if (matcher.matches(startPath.relativize(dir))) { + pathSet.add( + dir.toAbsolutePath() + .normalize() + .toString()); // Normalize the path and add to the set + LOGGER + .atTrace() + .addArgument(dir) + .log("Directory matched glob and was added to set: {}"); + } + } + return FileVisitResult.CONTINUE; } - } - return FileVisitResult.CONTINUE; - } - }); + }); } catch (IOException e) { throw new NotFoundException("Couldn't find path"); } @@ -84,5 +88,3 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) return new ArrayList<>(pathSet); } } - - diff --git a/src/main/java/net/explorviz/code/analysis/git/GitMetricCollector.java b/src/main/java/net/explorviz/code/analysis/git/GitMetricCollector.java index c2a2fa7..3d3a0db 100644 --- a/src/main/java/net/explorviz/code/analysis/git/GitMetricCollector.java +++ b/src/main/java/net/explorviz/code/analysis/git/GitMetricCollector.java @@ -8,24 +8,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * A simple collector for metrics based on git data. - */ +/** A simple collector for metrics based on git data. */ public final class GitMetricCollector { private static final Logger LOGGER = LoggerFactory.getLogger(GitMetricCollector.class); - private GitMetricCollector() { - } + private GitMetricCollector() {} /** * Adds git metrics that are valid for all files within a commit. * * @param fileDataHandler the fileDataHandler to add the metric to - * @param commit the current commit + * @param commit the current commit */ - public static void addCommitGitMetrics(final AbstractFileDataHandler fileDataHandler, - final RevCommit commit) { + public static void addCommitGitMetrics( + final AbstractFileDataHandler fileDataHandler, final RevCommit commit) { addCommitGitMetrics(fileDataHandler, commit.getAuthorIdent().getEmailAddress()); } @@ -33,10 +30,10 @@ public static void addCommitGitMetrics(final AbstractFileDataHandler fileDataHan * Adds git metrics that are valid for all files within a commit. * * @param fileDataHandler the fileDataHandler to add the metric to - * @param authorEmail the author email for the current commit + * @param authorEmail the author email for the current commit */ - public static void addCommitGitMetrics(final AbstractFileDataHandler fileDataHandler, - final String authorEmail) { + public static void addCommitGitMetrics( + final AbstractFileDataHandler fileDataHandler, final String authorEmail) { fileDataHandler.setAuthor(authorEmail); } @@ -44,14 +41,14 @@ public static void addCommitGitMetrics(final AbstractFileDataHandler fileDataHan * Adds git metrics that are valid for a specific file. * * @param fileDataHandler the fileDataHandler to add the metric to - * @param fileDescriptor the fileDescriptor holding the file data + * @param fileDescriptor the fileDescriptor holding the file data */ - public static void addFileGitMetrics(final AbstractFileDataHandler fileDataHandler, - final FileDescriptor fileDescriptor) { + public static void addFileGitMetrics( + final AbstractFileDataHandler fileDataHandler, final FileDescriptor fileDescriptor) { try { - fileDataHandler.setModifications(fileDescriptor.modifiedLines, fileDescriptor.addedLines, - fileDescriptor.removedLines); - } catch (NullPointerException e) { // NOPMD + fileDataHandler.setModifications( + fileDescriptor.modifiedLines, fileDescriptor.addedLines, fileDescriptor.removedLines); + } catch (NullPointerException e) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Failed to add modifications"); } @@ -59,7 +56,8 @@ public static void addFileGitMetrics(final AbstractFileDataHandler fileDataHandl } /** - * Creates a ContributorData object based on the given commit, landscape token and repository name. + * Creates a ContributorData object based on the given commit, landscape token and repository + * name. * * @param commit the commit to extract contributor information from * @param landscapeToken the landscape token to set in the ContributorData @@ -67,19 +65,17 @@ public static void addFileGitMetrics(final AbstractFileDataHandler fileDataHandl * @return a ContributorData object containing the contributor information */ public static ContributorData createContributorData( - final RevCommit commit, - final String landscapeToken, - final String repositoryName + final RevCommit commit, final String landscapeToken, final String repositoryName) { - ) { String name = commit.getAuthorIdent().getName(); String email = commit.getAuthorIdent().getEmailAddress(); - Builder builder = ContributorData.newBuilder() - .setGitUsername(name) - .setEmail(email) - .setLandscapeToken(landscapeToken) - .setRepositoryName(repositoryName); + Builder builder = + ContributorData.newBuilder() + .setGitUsername(name) + .setEmail(email) + .setLandscapeToken(landscapeToken) + .setRepositoryName(repositoryName); return builder.build(); } diff --git a/src/main/java/net/explorviz/code/analysis/git/GitRepositoryHandler.java b/src/main/java/net/explorviz/code/analysis/git/GitRepositoryHandler.java index 912f360..c4b3cf1 100644 --- a/src/main/java/net/explorviz/code/analysis/git/GitRepositoryHandler.java +++ b/src/main/java/net/explorviz/code/analysis/git/GitRepositoryHandler.java @@ -1,4 +1,4 @@ -package net.explorviz.code.analysis.git; // NOPMD +package net.explorviz.code.analysis.git; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -59,21 +59,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Injectable helper class for jGit concerns. - */ +/** Injectable helper class for jGit concerns. */ @ApplicationScoped -public class GitRepositoryHandler { // NOPMD +public class GitRepositoryHandler { private static final Logger LOGGER = LoggerFactory.getLogger(GitRepositoryHandler.class); private static String repositoryPath; @ConfigProperty(name = "explorviz.gitanalysis.remote.storage-path") - /* default */ Optional repoLocalStoragePathProperty; // NOCS + /* default */ Optional repoLocalStoragePathProperty; - @Inject - /* default */ LocalRepositoryService localRepositoryService; // NOCS + @Inject /* default */ LocalRepositoryService localRepositoryService; private Git git; @@ -91,8 +88,8 @@ private static void ensureCommitTreeParsed(final Repository repository, final Re } } - private static AbstractTreeIterator prepareTreeParser(final Repository repository, - final RevCommit commit) throws IOException { + private static AbstractTreeIterator prepareTreeParser( + final Repository repository, final RevCommit commit) throws IOException { ensureCommitTreeParsed(repository, commit); final RevTree tree = commit.getTree(); final CanonicalTreeParser treeParser = new CanonicalTreeParser(); @@ -103,24 +100,24 @@ private static AbstractTreeIterator prepareTreeParser(final Repository repositor } /** - * Converts a git ssh url to a https url and returns it as well as if the - * conversion is usable. If the given url is - * already in https format, it will be returned as-is and the flag is set to - * true. If the given url is in ssh format, - * it will be converted to https and returned and the flag is set to true. If it - * is neither, a warning will be printed - * the url will get returned but the flag is set to false. + * Converts a git ssh url to a https url and returns it as well as if the conversion is usable. If + * the given url is already in https format, it will be returned as-is and the flag is set to + * true. If the given url is in ssh format, it will be converted to https and returned and the + * flag is set to true. If it is neither, a warning will be printed the url will get returned but + * the flag is set to false. * * @param url the original git url - * @return a Tuple containing a flag if the returned url should be used and the - * url itself + * @return a Tuple containing a flag if the returned url should be used and the url itself */ public static Map.Entry convertSshToHttps(final String url) { if (url.startsWith("git@")) { final int colonIndex = url.indexOf(':'); if (colonIndex > "git@".length() && colonIndex < url.length() - 1) { - final String convertedUrl = "https://" + url.substring("git@".length(), colonIndex) + "/" - + url.substring(colonIndex + 1); + final String convertedUrl = + "https://" + + url.substring("git@".length(), colonIndex) + + "/" + + url.substring(colonIndex + 1); if (LOGGER.isInfoEnabled()) { LOGGER.info("SSH URL detected, converting to HTTPS: " + convertedUrl); } @@ -154,11 +151,10 @@ public static String getRemoteOriginUrl(final Repository repository) { } /** - * Returns the string content for a file path that was modified in a commit for - * a given repo. + * Returns the string content for a file path that was modified in a commit for a given repo. * * @param blobId The {@link ObjectId}. - * @param repo The {@link Repository}. + * @param repo The {@link Repository}. * @return The stringified file content. * @throws IOException Thrown if JGit cannot open the Git repo. */ @@ -168,14 +164,13 @@ public static String getContent(final ObjectId blobId, final Repository repo) th final byte[] bytes = objectLoader.getBytes(); return new String(bytes, StandardCharsets.UTF_8); } - } /** * Returns the size in bytes of a git blob without checking out the commit. * * @param blobId The {@link ObjectId}. - * @param repo The {@link Repository}. + * @param repo The {@link Repository}. * @return The blob size in bytes. * @throws IOException Thrown if JGit cannot read the blob. */ @@ -196,25 +191,27 @@ public static String convertRelativeToAbsolutePath(String relativePath) { systemPath = systemPath.replace("\\build\\classes\\java\\main", ""); systemPath = systemPath.replace("/build/classes/java/main", ""); String absolutePath = Paths.get(systemPath, relativePath).toString(); - LOGGER.atInfo().addArgument(relativePath).addArgument(absolutePath) + LOGGER + .atInfo() + .addArgument(relativePath) + .addArgument(absolutePath) .log("Converted relative path {} to absolute path {}"); return absolutePath; } /** - * Tries to download the Git {@link Repository} based on a given Url to the - * given. + * Tries to download the Git {@link Repository} based on a given Url to the given. * - * @param remoteRepositoryObject the {@link RemoteRepositoryObject} object - * containing the path and url + * @param remoteRepositoryObject the {@link RemoteRepositoryObject} object containing the path and + * url * @return returns an opened git repository * @throws GitAPIException gets thrown if the git api encounters an error */ - private Repository downloadGitRepository(// NOCS NOPMD - final RemoteRepositoryObject remoteRepositoryObject) throws GitAPIException, IOException { + private Repository downloadGitRepository(final RemoteRepositoryObject remoteRepositoryObject) + throws GitAPIException, IOException { - final Map.Entry checkedRepositoryUrl = convertSshToHttps( - remoteRepositoryObject.getUrl()); + final Map.Entry checkedRepositoryUrl = + convertSshToHttps(remoteRepositoryObject.getUrl()); final String urlValue = checkedRepositoryUrl.getValue(); String repoName = "repository"; @@ -230,12 +227,10 @@ private Repository downloadGitRepository(// NOCS NOPMD if (remoteRepositoryObject.getStoragePath().isBlank()) { repoPath = Files.createTempDirectory("TemporaryRepository").toAbsolutePath().toString(); repoPath = Paths.get(repoPath, repoName).toString(); - LOGGER.atInfo().addArgument(repoPath) - .log("No path given, repository will be cloned to: {}"); + LOGGER.atInfo().addArgument(repoPath).log("No path given, repository will be cloned to: {}"); } else if (new File(repoPath).isAbsolute()) { repoPath = Paths.get(repoPath, repoName).toString(); - LOGGER.atInfo().addArgument(repoPath) - .log("Repository will be cloned to: {}"); + LOGGER.atInfo().addArgument(repoPath).log("Repository will be cloned to: {}"); } else { LOGGER.atInfo().log("Found local path for remote repository."); repoPath = GitRepositoryHandler.convertRelativeToAbsolutePath(repoPath); @@ -243,25 +238,30 @@ private Repository downloadGitRepository(// NOCS NOPMD } try { - LOGGER.atInfo().addArgument(checkedRepositoryUrl.getValue()) + LOGGER + .atInfo() + .addArgument(checkedRepositoryUrl.getValue()) .log("Cloning repository from: {}"); FileIO.cleanDirectory(repoPath); - final String branchToClone = resolveBranchToClone( - remoteRepositoryObject, checkedRepositoryUrl.getValue()); + final String branchToClone = + resolveBranchToClone(remoteRepositoryObject, checkedRepositoryUrl.getValue()); - final var cloneCommand = Git.cloneRepository() - .setURI(checkedRepositoryUrl.getValue()) - .setCredentialsProvider(remoteRepositoryObject.getCredentialsProvider()) - .setDirectory(new File(repoPath)) - .setCloneAllBranches(false) - .setBranch(branchToClone != null ? branchToClone : Constants.HEAD); + final var cloneCommand = + Git.cloneRepository() + .setURI(checkedRepositoryUrl.getValue()) + .setCredentialsProvider(remoteRepositoryObject.getCredentialsProvider()) + .setDirectory(new File(repoPath)) + .setCloneAllBranches(false) + .setBranch(branchToClone != null ? branchToClone : Constants.HEAD); if (branchToClone != null) { cloneCommand.setBranchesToClone( Collections.singletonList(Constants.R_HEADS + branchToClone)); - LOGGER.atInfo().addArgument(branchToClone) + LOGGER + .atInfo() + .addArgument(branchToClone) .log("Performing single-branch clone for branch: {}"); } @@ -269,7 +269,8 @@ private Repository downloadGitRepository(// NOCS NOPMD if (remoteRepositoryObject.getCloneDepth() != null && remoteRepositoryObject.getCloneDepth() > 0) { cloneCommand.setDepth(remoteRepositoryObject.getCloneDepth()); - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(remoteRepositoryObject.getCloneDepth()) .log("Performing shallow clone with depth: {}"); } @@ -279,8 +280,8 @@ private Repository downloadGitRepository(// NOCS NOPMD return this.git.getRepository(); } catch (TransportException te) { if (!checkedRepositoryUrl.getKey()) { - throw (MalformedURLException) new MalformedURLException( - checkedRepositoryUrl.getValue()).initCause(te); + throw (MalformedURLException) + new MalformedURLException(checkedRepositoryUrl.getValue()).initCause(te); } if (LOGGER.isErrorEnabled()) { @@ -291,7 +292,7 @@ private Repository downloadGitRepository(// NOCS NOPMD } else if (te.getMessage().contains("not authorized")) { LOGGER.error("Transport Exception thrown, credential are wrong"); } else { - LOGGER.error("Transport Exception thrown: " + te.getMessage()); // NOPMD + LOGGER.error("Transport Exception thrown: " + te.getMessage()); } } throw te; @@ -303,26 +304,31 @@ private Repository downloadGitRepository(// NOCS NOPMD } } - private String resolveBranchToClone(final RemoteRepositoryObject remoteRepositoryObject, - final String repositoryUrl) throws GitAPIException { + private String resolveBranchToClone( + final RemoteRepositoryObject remoteRepositoryObject, final String repositoryUrl) + throws GitAPIException { final String configuredBranch = remoteRepositoryObject.getBranchName(); if (!configuredBranch.isBlank()) { return configuredBranch; } - return resolveRemoteDefaultBranch(repositoryUrl, - remoteRepositoryObject.getCredentialsProvider()); + return resolveRemoteDefaultBranch( + repositoryUrl, remoteRepositoryObject.getCredentialsProvider()); } - private String resolveRemoteDefaultBranch(final String repositoryUrl, - final CredentialsProvider credentialsProvider) throws GitAPIException { - final Collection refs = Git.lsRemoteRepository() - .setRemote(repositoryUrl) - .setCredentialsProvider(credentialsProvider) - .call(); + private String resolveRemoteDefaultBranch( + final String repositoryUrl, final CredentialsProvider credentialsProvider) + throws GitAPIException { + final Collection refs = + Git.lsRemoteRepository() + .setRemote(repositoryUrl) + .setCredentialsProvider(credentialsProvider) + .call(); for (final Ref ref : refs) { if (Constants.HEAD.equals(ref.getName()) && ref.isSymbolic()) { final String branchName = Repository.shortenRefName(ref.getTarget().getName()); - LOGGER.atInfo().addArgument(branchName) + LOGGER + .atInfo() + .addArgument(branchName) .log("No branch configured, using remote default branch: {}"); return branchName; } @@ -356,9 +362,13 @@ private Repository openGitRepository(final String repositoryPath, final String b try { if ("true".equals(System.getenv("GITLAB_CI")) || this.git.getRepository().findRef("refs/heads/" + branchName) == null) { - this.git.checkout().setName(branchName).setCreateBranch(true) + this.git + .checkout() + .setName(branchName) + .setCreateBranch(true) .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK) - .setStartPoint("origin/" + branchName).call(); + .setStartPoint("origin/" + branchName) + .call(); } else { this.git.checkout().setName(branchName).call(); } @@ -374,23 +384,21 @@ private Repository openGitRepository(final String repositoryPath, final String b } /** - * Returns a Git {@link Repository} object by opening the repository found at - * {@code localRepositoryPath}.
- * If - * {@code localRepositoryPath} is empty, the repository gets cloned based on - * data defined in + * Returns a Git {@link Repository} object by opening the repository found at {@code + * localRepositoryPath}.
+ * If {@code localRepositoryPath} is empty, the repository gets cloned based on data defined in * {@code remoteRepositoryObject} and the opened repository gets returned. * - * @param localRepositoryPath the system path of the local Repository - * @param remoteRepositoryObject the {@link RemoteRepositoryObject} object - * containing the path and url + * @param localRepositoryPath the system path of the local Repository + * @param remoteRepositoryObject the {@link RemoteRepositoryObject} object containing the path and + * url * @return returns an opened Git {@link Repository} - * @throws IOException gets thrown if the path is not accessible or does not - * point to a folder + * @throws IOException gets thrown if the path is not accessible or does not point to a folder * @throws GitAPIException gets thrown if the git api encounters an error */ - public Repository getGitRepository(final String localRepositoryPath, - final RemoteRepositoryObject remoteRepositoryObject) throws IOException, GitAPIException { + public Repository getGitRepository( + final String localRepositoryPath, final RemoteRepositoryObject remoteRepositoryObject) + throws IOException, GitAPIException { if (localRepositoryPath.isBlank()) { LOGGER.atInfo().log("No local repository given, using remote"); @@ -398,7 +406,8 @@ public Repository getGitRepository(final String localRepositoryPath, } else if (new File(localRepositoryPath).isAbsolute()) { return this.openGitRepository(localRepositoryPath, remoteRepositoryObject.getBranchName()); } else { - String absolutePath = localRepositoryService.resolveRelativeRepositoryPath(localRepositoryPath).toString(); + String absolutePath = + localRepositoryService.resolveRelativeRepositoryPath(localRepositoryPath).toString(); return this.openGitRepository(absolutePath, remoteRepositoryObject.getBranchName()); } } @@ -406,28 +415,20 @@ public Repository getGitRepository(final String localRepositoryPath, /** * Returns a Git {@link Repository} object by using the parameters set in the * application.properties.
- * The local - * repository defined in {@code explorviz.gitanalysis.local.storage-path} will - * be used. + * The local repository defined in {@code explorviz.gitanalysis.local.storage-path} will be used. *
- * If {@code explorviz.gitanalysis.local.storage-path} is empty, the repository - * defined in - * {@code explorviz.gitanalysis.remote.url} will be cloned to the location - * {@code explorviz.gitanalysis.remote.storage-path}.
- * If no storage path is given, a temporary directory will be - * created.
- * The branch given in {@code explorviz.gitanalysis.branch} will be used if - * present, otherwise the + * If {@code explorviz.gitanalysis.local.storage-path} is empty, the repository defined in {@code + * explorviz.gitanalysis.remote.url} will be cloned to the location {@code + * explorviz.gitanalysis.remote.storage-path}.
+ * If no storage path is given, a temporary directory will be created.
+ * The branch given in {@code explorviz.gitanalysis.branch} will be used if present, otherwise the * default (remote) or current (local) will be used. * * @param config the analysis config * @return an opened Git {@link Repository} - * @throws PropertyNotDefinedException gets thrown if a needed property is not - * present - * @throws GitAPIException gets thrown if the git api encounters an - * error - * @throws IOException gets thrown if JGit cannot open the Git - * repository. + * @throws PropertyNotDefinedException gets thrown if a needed property is not present + * @throws GitAPIException gets thrown if the git api encounters an error + * @throws IOException gets thrown if JGit cannot open the Git repository. */ public Repository getGitRepository(AnalysisConfig config) throws PropertyNotDefinedException, GitAPIException, IOException { @@ -439,36 +440,42 @@ public Repository getGitRepository(AnalysisConfig config) if (config.gitUsername().isEmpty() || config.gitPassword().isEmpty()) { credentialsProvider = CredentialsProvider.getDefault(); } else { - credentialsProvider = new UsernamePasswordCredentialsProvider( - config.gitUsername().get(), - config.gitPassword().get()); + credentialsProvider = + new UsernamePasswordCredentialsProvider( + config.gitUsername().get(), config.gitPassword().get()); } - final Integer depth = config.commitAnalysisLimit().isPresent() ? config.commitAnalysisLimit().get() + 1 : null; - - return getGitRepository(config.repoPath().orElse(""), - new RemoteRepositoryObject(config.repoRemoteUrl().orElse(""), - repoLocalStoragePathProperty.orElse(""), credentialsProvider, - config.branch().orElse(""), depth)); + final Integer depth = + config.commitAnalysisLimit().isPresent() ? config.commitAnalysisLimit().get() + 1 : null; + + return getGitRepository( + config.repoPath().orElse(""), + new RemoteRepositoryObject( + config.repoRemoteUrl().orElse(""), + repoLocalStoragePathProperty.orElse(""), + credentialsProvider, + config.branch().orElse(""), + depth)); } /** * Returns the changed filenames between two given commits. * - * @param repository the current repository - * @param oldCommit the old commit, as a baseline for the difference - * calculation - * @param newCommit the new commit, gets checked against the old commit - * @param pathRestrictions comma sep. list of search strings specifying the - * folders to analyze + * @param repository the current repository + * @param oldCommit the old commit, as a baseline for the difference calculation + * @param newCommit the new commit, gets checked against the old commit + * @param pathRestrictions comma sep. list of search strings specifying the folders to analyze * @return triple of FileDescriptor specifying modified, delete and added files - * @throws GitAPIException thrown if git encounters an exception - * @throws IOException thrown if files are not available + * @throws GitAPIException thrown if git encounters an exception + * @throws IOException thrown if files are not available * @throws NotFoundException thrown if the restrictionPath was not found */ public Triple, List, List> listDiff( - final Repository repository, final Optional oldCommit, final RevCommit newCommit, - final String pathRestrictions) throws GitAPIException, IOException, NotFoundException { + final Repository repository, + final Optional oldCommit, + final RevCommit newCommit, + final String pathRestrictions) + throws GitAPIException, IOException, NotFoundException { if (pathRestrictions == null || pathRestrictions.isEmpty()) { return listDiff(repository, oldCommit, newCommit, new ArrayList<>()); } @@ -479,17 +486,18 @@ public Triple, List, List> * Returns the changed filenames between two given commits. * * @param repository the current repository - * @param oldCommit the old commit, as a baseline for the difference - * calculation - * @param newCommit the new commit, gets checked against the old commit + * @param oldCommit the old commit, as a baseline for the difference calculation + * @param newCommit the new commit, gets checked against the old commit * @return triple of FileDescriptor specifying modified, delete and added files * @throws GitAPIException thrown if git encounters an exception - * @throws IOException thrown if files are not available + * @throws IOException thrown if files are not available */ public Triple, List, List> listDiff( - final Repository repository, // NOPMD - final Optional oldCommit, final RevCommit newCommit, - final List pathRestrictions) throws GitAPIException, IOException, NotFoundException { + final Repository repository, + final Optional oldCommit, + final RevCommit newCommit, + final List pathRestrictions) + throws GitAPIException, IOException, NotFoundException { final List modifiedObjectIdList = new ArrayList<>(); final List deletedObjectIdList = new ArrayList<>(); List addedObjectIdList = new ArrayList<>(); @@ -499,10 +507,13 @@ public Triple, List, List> if (oldCommit.isEmpty()) { addedObjectIdList = listFilesInCommit(repository, newCommit, filter); } else { - final List diffs = this.git.diff() - .setOldTree(prepareTreeParser(repository, oldCommit.get())) - .setNewTree(prepareTreeParser(repository, newCommit)).setPathFilter(filter) - .call(); + final List diffs = + this.git + .diff() + .setOldTree(prepareTreeParser(repository, oldCommit.get())) + .setNewTree(prepareTreeParser(repository, newCommit)) + .setPathFilter(filter) + .call(); try (DiffFormatter diffFormatter = new DiffFormatter(DisabledOutputStream.INSTANCE)) { diffFormatter.setRepository(repository); @@ -527,26 +538,30 @@ public Triple, List, List> modifiedObjectIdList, deletedObjectIdList, addedObjectIdList); } - private void putInList(final DiffFormatter diffFormatter, final DiffEntry diff, + private void putInList( + final DiffFormatter diffFormatter, + final DiffEntry diff, final List objectIdList) throws IOException { final FileHeader fileHeader = diffFormatter.toFileHeader(diff); final Triple mods = countModifications(fileHeader.toEditList()); final String[] parts = diff.getNewPath().split("/"); objectIdList.add( - new FileDescriptor(diff.getNewId().toObjectId(), parts[parts.length - 1], diff.getNewPath(), - mods)); + new FileDescriptor( + diff.getNewId().toObjectId(), parts[parts.length - 1], diff.getNewPath(), mods)); } - private void putInList2(final DiffFormatter diffFormatter, final DiffEntry diff, + private void putInList2( + final DiffFormatter diffFormatter, + final DiffEntry diff, final List objectIdList) throws IOException { final FileHeader fileHeader = diffFormatter.toFileHeader(diff); final Triple mods = countModifications(fileHeader.toEditList()); final String[] parts = diff.getOldPath().split("/"); objectIdList.add( - new FileDescriptor(diff.getOldId().toObjectId(), parts[parts.length - 1], diff.getOldPath(), - mods)); + new FileDescriptor( + diff.getOldId().toObjectId(), parts[parts.length - 1], diff.getOldPath(), mods)); } private Triple countModifications(final EditList editList) { @@ -570,70 +585,68 @@ private Triple countModifications(final EditList edit /** * Returns a list of all Java Files in the repository. * - * @param repository the current repository - * @param commit the commit to get the list of files for - * @param pathRestrictions a list of search strings specifying the folders to - * analyze, if omitted, the entire - * repository will be searched - * @return returns a list of FileDescriptors of all java files within the - * specified folders - * @throws IOException thrown if files are not available + * @param repository the current repository + * @param commit the commit to get the list of files for + * @param pathRestrictions a list of search strings specifying the folders to analyze, if omitted, + * the entire repository will be searched + * @return returns a list of FileDescriptors of all java files within the specified folders + * @throws IOException thrown if files are not available * @throws NotFoundException thrown if the restrictionPath was not found */ - public List listFilesInCommit(final Repository repository, // NOPMD - final RevCommit commit, final List pathRestrictions) + public List listFilesInCommit( + final Repository repository, final RevCommit commit, final List pathRestrictions) throws IOException, NotFoundException { return listFilesInCommit(repository, commit, getSourceFileTreeFilter(pathRestrictions)); } /** - * Returns a list of all supported source files in the repository (Java, - * TypeScript, JavaScript). + * Returns a list of all supported source files in the repository (Java, TypeScript, JavaScript). * - * @param repository the current repository - * @param commit the commit to get the list of files for - * @param pathRestrictions a comma separated list of search strings specifying - * the folders to analyze, if omitted, the - * entire repository will be searched - * @return returns a list of FileDescriptors of all supported source files - * within the specified folders - * @throws IOException thrown if files are not available + * @param repository the current repository + * @param commit the commit to get the list of files for + * @param pathRestrictions a comma separated list of search strings specifying the folders to + * analyze, if omitted, the entire repository will be searched + * @return returns a list of FileDescriptors of all supported source files within the specified + * folders + * @throws IOException thrown if files are not available * @throws NotFoundException thrown if the restrictionPath was not found */ - public List listFilesInCommit(final Repository repository, // NOPMD - final RevCommit commit, final String pathRestrictions) throws IOException, NotFoundException { - return listFilesInCommit(repository, commit, - getSourceFileTreeFilter(Arrays.asList(pathRestrictions.split(",")))); + public List listFilesInCommit( + final Repository repository, final RevCommit commit, final String pathRestrictions) + throws IOException, NotFoundException { + return listFilesInCommit( + repository, commit, getSourceFileTreeFilter(Arrays.asList(pathRestrictions.split(",")))); } - private List listFilesInCommit(final Repository repository, // NOPMD - final RevCommit commit, final TreeFilter filter) throws IOException { + private List listFilesInCommit( + final Repository repository, final RevCommit commit, final TreeFilter filter) + throws IOException { ensureCommitTreeParsed(repository, commit); final List objectIdList = new ArrayList<>(); - try (final TreeWalk treeWalk = new TreeWalk(repository)) { // NOPMD + try (final TreeWalk treeWalk = new TreeWalk(repository)) { treeWalk.addTree(commit.getTree()); treeWalk.setRecursive(true); treeWalk.setFilter(filter); while (treeWalk.next()) { - objectIdList.add(new FileDescriptor(treeWalk.getObjectId(0), treeWalk.getNameString(), - treeWalk.getPathString())); + objectIdList.add( + new FileDescriptor( + treeWalk.getObjectId(0), treeWalk.getNameString(), treeWalk.getPathString())); } LOGGER.atDebug().addArgument(objectIdList.size()).log("Discovered {} files in commit tree"); } return objectIdList; - } private TreeFilter getSourceFileTreeFilter(final List pathRestrictions) throws NotFoundException { - if (pathRestrictions.isEmpty() || pathRestrictions.size() == 1 && pathRestrictions.get(0) - .isBlank()) { + if (pathRestrictions.isEmpty() + || pathRestrictions.size() == 1 && pathRestrictions.get(0).isBlank()) { LOGGER.atInfo().log("No path restrictions provided. Analyzing all files."); return TreeFilter.ALL; } else { LOGGER.atInfo().addArgument(pathRestrictions).log("Applying path restrictions: {}"); - final List pathList = DirectoryFinder.getRelativeDirectory(pathRestrictions, - getCurrentRepositoryPath()); + final List pathList = + DirectoryFinder.getRelativeDirectory(pathRestrictions, getCurrentRepositoryPath()); final List newPathList = new ArrayList<>(); for (final String path : pathList) { newPathList.add(path.replaceFirst("^\\\\|/", "").replaceAll("\\\\", "/")); @@ -653,11 +666,10 @@ public boolean isUnreachableCommit(final Optional commitId, final String } /** - * Checks if the given commit is unreachable by the given branch (is not part of - * the branch). + * Checks if the given commit is unreachable by the given branch (is not part of the branch). * * @param commitId the full SHA-1 id of the commit - * @param branch the branch name + * @param branch the branch name * @return if the given commit is unreachable by the given branch */ public boolean isUnreachableCommit(final String commitId, final String branch) { @@ -665,11 +677,10 @@ public boolean isUnreachableCommit(final String commitId, final String branch) { } /** - * Checks if the given commit is reachable by the given branch (is part of the - * branch). + * Checks if the given commit is reachable by the given branch (is part of the branch). * * @param commitId the full SHA-1 id of the commit - * @param branch the branch name + * @param branch the branch name * @return if the commit is reachable by the given branch */ public boolean isReachableCommit(final String commitId, final String branch) { @@ -677,26 +688,28 @@ public boolean isReachableCommit(final String commitId, final String branch) { return true; } try { - final Map map = this.git.nameRev().addPrefix(branch) - .add(ObjectId.fromString(commitId)).call(); + final Map map = + this.git.nameRev().addPrefix(branch).add(ObjectId.fromString(commitId)).call(); if (!map.isEmpty()) { return true; } } catch (GitAPIException | MissingObjectException e) { - throw new RuntimeException(e); // NOPMD + throw new RuntimeException(e); } return false; } /** - * Configures a {@link RevWalk} to traverse commits reachable from the branch tip and emit - * them in topological order (oldest first), matching {@code git rev-list --reverse}. + * Configures a {@link RevWalk} to traverse commits reachable from the branch tip and emit them in + * topological order (oldest first), matching {@code git rev-list --reverse}. * * @param firstParentCommitsOnly when {@code true}, only follow first-parent links so merged - * feature-branch commits are excluded + * feature-branch commits are excluded */ public void configureBranchRevWalk( - final RevWalk revWalk, final Repository repository, final String branchRef, + final RevWalk revWalk, + final Repository repository, + final String branchRef, final boolean firstParentCommitsOnly) throws IOException { revWalk.sort(RevSort.TOPO); @@ -713,5 +726,4 @@ public void configureBranchRevWalk( } revWalk.markStart(revWalk.parseCommit(branchId)); } - } diff --git a/src/main/java/net/explorviz/code/analysis/git/RepositoryFileUrlBuilder.java b/src/main/java/net/explorviz/code/analysis/git/RepositoryFileUrlBuilder.java index fcc4e0e..4d1918b 100644 --- a/src/main/java/net/explorviz/code/analysis/git/RepositoryFileUrlBuilder.java +++ b/src/main/java/net/explorviz/code/analysis/git/RepositoryFileUrlBuilder.java @@ -2,9 +2,7 @@ import java.util.Optional; -/** - * Normalizes repository clone URLs for persistence in the landscape service. - */ +/** Normalizes repository clone URLs for persistence in the landscape service. */ public final class RepositoryFileUrlBuilder { private RepositoryFileUrlBuilder() { diff --git a/src/main/java/net/explorviz/code/analysis/handler/AbstractFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/AbstractFileDataHandler.java index 4f76539..c4a88ed 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/AbstractFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/AbstractFileDataHandler.java @@ -59,8 +59,8 @@ public String getMetricValue(final String metricName) { : null; } - public void setModifications(final int modifiedLines, final int addedLines, - final int deletedLines) { + public void setModifications( + final int modifiedLines, final int addedLines, final int deletedLines) { builder.setModifiedLines(modifiedLines); builder.setAddedLines(addedLines); builder.setDeletedLines(deletedLines); diff --git a/src/main/java/net/explorviz/code/analysis/handler/CFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/CFileDataHandler.java index 33d6670..42026f6 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/CFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/CFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for C source files. - */ +/** FileData handler for C source files. */ public class CFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { diff --git a/src/main/java/net/explorviz/code/analysis/handler/CSharpFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/CSharpFileDataHandler.java index 809dbae..001fc1d 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/CSharpFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/CSharpFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for C# source files. - */ +/** FileData handler for C# source files. */ public class CSharpFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { diff --git a/src/main/java/net/explorviz/code/analysis/handler/ClassDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/ClassDataHandler.java index e858c4e..94a981f 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/ClassDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/ClassDataHandler.java @@ -7,9 +7,7 @@ import net.explorviz.code.proto.ClassType; import net.explorviz.code.proto.FieldData; -/** - * ClassData object holds data from analyzed classes. - */ +/** ClassData object holds data from analyzed classes. */ public class ClassDataHandler implements ProtoBufConvertable { private final ClassData.Builder builder; @@ -17,9 +15,7 @@ public class ClassDataHandler implements ProtoBufConvertable { private final Map methodDataMap; private final Map innerClassDataMap; - /** - * Creates a blank ClassData object. - */ + /** Creates a blank ClassData object. */ public ClassDataHandler() { this.builder = ClassData.newBuilder(); this.methodDataMap = new HashMap<>(); @@ -33,8 +29,8 @@ public void setName(final String name) { /** * Adds a methodData object. * - * @param name the method's simple name - * @param fqn the method's fully qualified name + * @param name the method's simple name + * @param fqn the method's fully qualified name * @param returnType the return type of the method * @return the created methodData object */ @@ -57,7 +53,7 @@ public void setSuperClass(final String superClass) { * Adds a constructor object. * * @param name the constructor's simple name - * @param fqn the constructor's fully qualified name + * @param fqn the constructor's fully qualified name * @return the created methodData object */ public MethodDataHandler addConstructor(final String name, final String fqn) { @@ -65,8 +61,8 @@ public MethodDataHandler addConstructor(final String name, final String fqn) { return methodDataMap.get(fqn); } - public void addField(final String fieldName, final String fieldType, - final List modifiers) { + public void addField( + final String fieldName, final String fieldType, final List modifiers) { this.builder.addFields( FieldData.newBuilder().setName(fieldName).setType(fieldType).addAllModifiers(modifiers)); } @@ -111,9 +107,7 @@ public boolean isAbstractClass() { return this.builder.getType() == ClassType.ABSTRACT_CLASS; } - /** - * Set the current ClassType as class if it wasn't set to anonymous class already. - */ + /** Set the current ClassType as class if it wasn't set to anonymous class already. */ public void setIsClass() { if (this.builder.getType() != ClassType.ANONYMOUS_CLASS) { this.builder.setType(ClassType.CLASS); @@ -149,9 +143,10 @@ public boolean isStruct() { } /** - * Adds a new metric entry to the ClassData, returns the old value of the metric if it existed, null otherwise. + * Adds a new metric entry to the ClassData, returns the old value of the metric if it existed, + * null otherwise. * - * @param metricName the name/identifier of the metric + * @param metricName the name/identifier of the metric * @param metricValue the value of the metric * @return the old value of the metric if it existed, null otherwise. */ @@ -214,14 +209,28 @@ public String toString() { metricDataString.append(entry.getValue()).append('\n'); } return "{ \n" - + "type: " + this.builder.getType().toString() + "\n" - + "modifier: " + this.builder.getModifiersList() + "\n" - + "superClasses: " + this.builder.getSuperclassesList() + "\n" - + "interfaces: " + this.builder.getImplementedInterfacesList() + "\n" - + "fields: " + this.builder.getFieldsList() + "\n" - + "innerClasses: " + this.innerClassDataMap.keySet() + "\n" - + "functions: \n" + methodDataString + "\n" - + metricDataString + "\n}"; + + "type: " + + this.builder.getType().toString() + + "\n" + + "modifier: " + + this.builder.getModifiersList() + + "\n" + + "superClasses: " + + this.builder.getSuperclassesList() + + "\n" + + "interfaces: " + + this.builder.getImplementedInterfacesList() + + "\n" + + "fields: " + + this.builder.getFieldsList() + + "\n" + + "innerClasses: " + + this.innerClassDataMap.keySet() + + "\n" + + "functions: \n" + + methodDataString + + "\n" + + metricDataString + + "\n}"; } - } diff --git a/src/main/java/net/explorviz/code/analysis/handler/CommitReportHandler.java b/src/main/java/net/explorviz/code/analysis/handler/CommitReportHandler.java index ffffa28..87715e0 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/CommitReportHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/CommitReportHandler.java @@ -9,11 +9,9 @@ import net.explorviz.code.proto.ContributorData; import net.explorviz.code.proto.FileIdentifier; -/** - * The CommitReportHandler is used to create commit reports. - */ +/** The CommitReportHandler is used to create commit reports. */ @ApplicationScoped -public class CommitReportHandler { // NOPMD +public class CommitReportHandler { private final List addedFiles = new ArrayList<>(); private final List deletedFiles = new ArrayList<>(); @@ -23,8 +21,8 @@ public class CommitReportHandler { // NOPMD private ContributorData.Builder contributorBuilder; /** - * Creates a blank handler, use - * {@link CommitReportHandler#init(String, java.util.List, String)} to initialize it. + * Creates a blank handler, use {@link CommitReportHandler#init(String, java.util.List, String)} + * to initialize it. */ public CommitReportHandler() { this.builder = CommitData.newBuilder(); @@ -32,8 +30,8 @@ public CommitReportHandler() { } /** - * Clears the commitReportData from old data entries. Gets called in - * {@link CommitReportHandler#init(String, java.util.List, String)} automatically. + * Clears the commitReportData from old data entries. Gets called in {@link + * CommitReportHandler#init(String, java.util.List, String)} automatically. */ public void clear() { this.builder = CommitData.newBuilder(); @@ -47,9 +45,9 @@ public void clear() { /** * Initialize the current report handler. * - * @param commitId the id of the commit + * @param commitId the id of the commit * @param parentCommitIds git parent commit ids (may be empty for root commits) - * @param branchName the name of the branch + * @param branchName the name of the branch */ public void init( final String commitId, final List parentCommitIds, final String branchName) { @@ -119,9 +117,7 @@ public void setAnalysisFileCount(final int analysisFileCount) { builder.setAnalysisFileCount(analysisFileCount); } - /** - * Returns the commit data. * * @return commit data object - */ + /** Returns the commit data. * * @return commit data object */ public CommitData getCommitData() { builder.addAllAddedFiles(addedFiles); builder.addAllModifiedFiles(modifiedFiles); diff --git a/src/main/java/net/explorviz/code/analysis/handler/CppFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/CppFileDataHandler.java index 477d0f1..1ebea07 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/CppFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/CppFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for C++ files. - */ +/** FileData handler for C++ files. */ public class CppFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { @@ -28,8 +26,12 @@ public CppFileDataHandler(final String fileName) { this.classDataMap = new HashMap<>(); this.globalFunctionHandlers = new ArrayList<>(); this.rootClasses = new ArrayList<>(); - this.isCppFile = fileName.endsWith(".cpp") || fileName.endsWith(".cxx") - || fileName.endsWith(".cc") || fileName.endsWith(".hpp") || fileName.endsWith(".hxx"); + this.isCppFile = + fileName.endsWith(".cpp") + || fileName.endsWith(".cxx") + || fileName.endsWith(".cc") + || fileName.endsWith(".hpp") + || fileName.endsWith(".hxx"); } public void enterNamespace(final String name) { @@ -42,9 +44,7 @@ public void leaveNamespace() { } } - /** - * Returns the current namespace as a '::' separated string. - */ + /** Returns the current namespace as a '::' separated string. */ public String getCurrentNamespace() { if (namespaceStack.isEmpty()) { return ""; diff --git a/src/main/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactory.java b/src/main/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactory.java index e4c120b..c4e6cb2 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactory.java +++ b/src/main/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactory.java @@ -5,18 +5,14 @@ import net.explorviz.code.analysis.types.FileDescriptor; import net.explorviz.code.proto.Language; -/** - * Creates file data handlers with basic metrics when full parsing is not possible. - */ +/** Creates file data handlers with basic metrics when full parsing is not possible. */ public final class FallbackFileDataHandlerFactory { private FallbackFileDataHandlerFactory() {} - /** - * Creates a fallback handler with the given language and basic metrics from file content. - */ - public static TextFileDataHandler create(final FileDescriptor file, final String fileContent, - final Language language) { + /** Creates a fallback handler with the given language and basic metrics from file content. */ + public static TextFileDataHandler create( + final FileDescriptor file, final String fileContent, final Language language) { final TextFileDataHandler handler = new TextFileDataHandler(file.reportedPath, language); handler.setFileHash(file.objectId.getName()); if (fileContent != null) { @@ -26,11 +22,11 @@ public static TextFileDataHandler create(final FileDescriptor file, final String return handler; } - /** - * Creates a fallback handler, inferring the language from the file name or path. - */ + /** Creates a fallback handler, inferring the language from the file name or path. */ public static TextFileDataHandler create(final FileDescriptor file, final String fileContent) { - return create(file, fileContent, + return create( + file, + fileContent, FileLanguageResolver.resolveFromFileName(file.fileName, file.reportedPath)); } } diff --git a/src/main/java/net/explorviz/code/analysis/handler/GoFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/GoFileDataHandler.java index e60fef5..f9f35a7 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/GoFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/GoFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for Go source files. - */ +/** FileData handler for Go source files. */ public class GoFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { diff --git a/src/main/java/net/explorviz/code/analysis/handler/JavaFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/JavaFileDataHandler.java index 7a3fd42..29b76e1 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/JavaFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/JavaFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for Java files. - */ +/** FileData handler for Java files. */ public class JavaFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { @@ -164,8 +162,15 @@ public String toString() { mapData.append(entry.getKey()).append(": "); mapData.append(entry.getValue()).append('\n'); } - return "stats: methodCount=" + this.getMethodCount() + "\n" + "package: " - + this.builder.getPackageName() + "\n" + "imports: " + this.builder.getImportNamesList() - + "\n" + mapData; + return "stats: methodCount=" + + this.getMethodCount() + + "\n" + + "package: " + + this.builder.getPackageName() + + "\n" + + "imports: " + + this.builder.getImportNamesList() + + "\n" + + mapData; } } diff --git a/src/main/java/net/explorviz/code/analysis/handler/KotlinFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/KotlinFileDataHandler.java index 2fbe545..498020b 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/KotlinFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/KotlinFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for Kotlin source files. - */ +/** FileData handler for Kotlin source files. */ public class KotlinFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { diff --git a/src/main/java/net/explorviz/code/analysis/handler/MethodDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/MethodDataHandler.java index 681676b..44e57d8 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/MethodDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/MethodDataHandler.java @@ -7,9 +7,7 @@ import net.explorviz.code.proto.FunctionData; import net.explorviz.code.proto.ParameterData; -/** - * FunctionData object holds data from analyzed function/method. - */ +/** FunctionData object holds data from analyzed function/method. */ public class MethodDataHandler implements ProtoBufConvertable { private final FunctionData.Builder builder; @@ -17,7 +15,7 @@ public class MethodDataHandler implements ProtoBufConvertable { /** * Creates a new FunctionData object holding data describing the method. * - * @param name the name of the function + * @param name the name of the function * @param returnType the return type of the method */ public MethodDataHandler(final String name, final String returnType) { @@ -54,12 +52,12 @@ public void addAnnotation(final String annotation) { /** * Add a parameter to the method. * - * @param name name of the parameter - * @param type type of the parameter + * @param name name of the parameter + * @param type type of the parameter * @param modifiers modifiers of the parameter */ - public void addParameter(final String name, final String type, - final NodeList modifiers) { + public void addParameter( + final String name, final String type, final NodeList modifiers) { final ParameterData.Builder parameterBuilder = ParameterData.newBuilder(); parameterBuilder.setName(name); parameterBuilder.setType(type); @@ -72,8 +70,8 @@ public void addParameter(final String name, final String type, /** * Add a parameter to the method. * - * @param name name of the parameter - * @param type type of the parameter + * @param name name of the parameter + * @param type type of the parameter * @param modifiers modifiers of the parameter */ public void addParameter(final String name, final String type, final List modifiers) { @@ -91,7 +89,7 @@ public void addOutgoingMethodCall(final String fqn) { /** * Adds a metric to the Method. * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric * @return the old metric value if it existed, null otherwise */ @@ -139,10 +137,18 @@ public String toString() { metricDataString.append(entry.getKey()).append(": "); metricDataString.append(entry.getValue()).append('\n'); } - return " type: " + this.builder.getReturnType() + "\n" + " modifiers: " - + this.builder.getModifiersList() + "\n" + " parameters: " + return " type: " + + this.builder.getReturnType() + + "\n" + + " modifiers: " + + this.builder.getModifiersList() + + "\n" + + " parameters: " + this.builder.getParametersList() - + "\n" + " outgoing calls: " + this.builder.getOutgoingMethodCallsList() + "\n" + + "\n" + + " outgoing calls: " + + this.builder.getOutgoingMethodCallsList() + + "\n" + metricDataString; } } diff --git a/src/main/java/net/explorviz/code/analysis/handler/MetricAppender.java b/src/main/java/net/explorviz/code/analysis/handler/MetricAppender.java index 99c6e0b..39d510f 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/MetricAppender.java +++ b/src/main/java/net/explorviz/code/analysis/handler/MetricAppender.java @@ -13,11 +13,11 @@ import net.explorviz.code.analysis.types.Verification; /** - * The MetricAppender is a helper Object to facilitate the adding of metric data to the internal FileData object. It - * provides basic functionality to keep track of the class hierarchy and handles the adding of metrics to the right - * objects. + * The MetricAppender is a helper Object to facilitate the adding of metric data to the internal + * FileData object. It provides basic functionality to keep track of the class hierarchy and handles + * the adding of metrics to the right objects. */ -public class MetricAppender { // NOPMD +public class MetricAppender { private static final String OBJECT_NOT_FOUND = "Object to add metric not found."; private static final String UNKNOWN = "UNKNOWN"; @@ -41,7 +41,7 @@ public MetricAppender(final JavaFileDataHandler fileDataHandler) { /** * Sets the metric with metricName to metricValue. The metric is attached to the current file. * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric * @return returns the old value of the metric if the metric exists, otherwise null */ @@ -50,82 +50,84 @@ public String putFileMetric(final String metricName, final String metricValue) { } /** - * Sets the metric with metricName to metricValue. The metric is attached to the current class set by - * {@link #enterClass(ClassOrInterfaceDeclaration)} + * Sets the metric with metricName to metricValue. The metric is attached to the current class set + * by {@link #enterClass(ClassOrInterfaceDeclaration)} * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric * @return returns the old value of the metric if the metric exists, otherwise null - * @throws NotFoundException gets thrown if the class object was not found and therefore the metric could not be - * added + * @throws NotFoundException gets thrown if the class object was not found and therefore the + * metric could not be added */ public String putClassMetric(final String metricName, final String metricValue) throws NotFoundException { try { final ClassDataHandler classDataHandler = fileData.getClassData(classStack.peek()); return classDataHandler.addMetric(metricName, metricValue); - } catch (NullPointerException e) { // NOPMD - final NotFoundException notFoundException = new NotFoundException( - "Not inside class. Object to add metric not found."); + } catch (NullPointerException e) { + final NotFoundException notFoundException = + new NotFoundException("Not inside class. Object to add metric not found."); notFoundException.addSuppressed(e); - throw notFoundException; // NOPMD + throw notFoundException; } } /** * Sets the metric with metricName to metricValue. The metric is attached to the given class. * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric - * @param clazz the class object to add the metric to + * @param clazz the class object to add the metric to * @return returns the old value of the metric if the metric exists, otherwise null - * @throws NotFoundException gets thrown if the method object was not found and therefore the metric could not be - * added + * @throws NotFoundException gets thrown if the method object was not found and therefore the + * metric could not be added */ - public String putClassMetric(final String metricName, final String metricValue, - final ClassOrInterfaceDeclaration clazz) throws NotFoundException { + public String putClassMetric( + final String metricName, final String metricValue, final ClassOrInterfaceDeclaration clazz) + throws NotFoundException { try { - return fileData.getClassData(clazz.getFullyQualifiedName().orElse(UNKNOWN)) + return fileData + .getClassData(clazz.getFullyQualifiedName().orElse(UNKNOWN)) .addMetric(metricName, metricValue); - } catch (NullPointerException e) { // NOPMD + } catch (NullPointerException e) { final NotFoundException notFoundException = new NotFoundException(OBJECT_NOT_FOUND); notFoundException.addSuppressed(e); - throw notFoundException; // NOPMD + throw notFoundException; } } /** - * Sets the metric with metricName to metricValue. The metric is attached to the class defined by classFqn. + * Sets the metric with metricName to metricValue. The metric is attached to the class defined by + * classFqn. * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric - * @param classFqn the fqn of the class + * @param classFqn the fqn of the class * @return returns the old value of the metric if the metric exists, otherwise null - * @throws NotFoundException gets thrown if the method object was not found and therefore the metric could not be - * added + * @throws NotFoundException gets thrown if the method object was not found and therefore the + * metric could not be added */ - public String putClassMetric(final String metricName, final String metricValue, - final String classFqn) - + public String putClassMetric( + final String metricName, final String metricValue, final String classFqn) throws NotFoundException { try { return fileData.getClassData(classFqn).addMetric(metricName, metricValue); - } catch (NullPointerException e) { // NOPMD + } catch (NullPointerException e) { final NotFoundException notFoundException = new NotFoundException(OBJECT_NOT_FOUND); notFoundException.addSuppressed(e); - throw notFoundException; // NOPMD + throw notFoundException; } } /** - * Sets the metric with metricName to metricValue. The metric is attached to the current method set by - * {@link #enterMethod(MethodDeclaration)} + * Sets the metric with metricName to metricValue. The metric is attached to the current method + * set by {@link #enterMethod(MethodDeclaration)} * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric * @return returns the old value of the metric if the metric exists, otherwise null - * @throws NotFoundException gets thrown if the method object was not found and therefore the metric could not be - * added + * @throws NotFoundException gets thrown if the method object was not found and therefore the + * metric could not be added */ public String putMethodMetric(final String metricName, final String metricValue) throws NotFoundException { @@ -133,35 +135,41 @@ public String putMethodMetric(final String metricName, final String metricValue) final ClassDataHandler classDataHandler = fileData.getClassData(classStack.peek()); final MethodDataHandler methodDataHandler = classDataHandler.getMethod(methodStack.peek()); return methodDataHandler.addMetric(metricName, metricValue); - } catch (NullPointerException e) { // NOPMD - final NotFoundException notFoundException = new NotFoundException( - "Not inside class or method. Object to add metric not found."); + } catch (NullPointerException e) { + final NotFoundException notFoundException = + new NotFoundException("Not inside class or method. Object to add metric not found."); notFoundException.addSuppressed(e); - throw notFoundException; // NOPMD + throw notFoundException; } } /** - * Sets the metric with metricName to metricValue. The metric is attached to the given method. If the method's name - * can't be resolved, null gets returned + * Sets the metric with metricName to metricValue. The metric is attached to the given method. If + * the method's name can't be resolved, null gets returned * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric - * @param method the method to add the metric to + * @param method the method to add the metric to * @return returns the old value of the metric if the metric exists, otherwise null - * @throws NotFoundException gets thrown if the method object was not found and therefore the metric could not be - * added + * @throws NotFoundException gets thrown if the method object was not found and therefore the + * metric could not be added */ - public String putMethodMetric(final String metricName, final String metricValue, - final MethodDeclaration method) throws NotFoundException { + public String putMethodMetric( + final String metricName, final String metricValue, final MethodDeclaration method) + throws NotFoundException { if (method.getParentNode().isPresent()) { final Node parent = method.getParentNode().get(); if (parent instanceof ClassOrInterfaceDeclaration) { - final String fqn = ((ClassOrInterfaceDeclaration) parent).getFullyQualifiedName() - .orElse(UNKNOWN); - return fileData.getClassData(fqn).getMethod( - fqn + "." + method.getNameAsString() + "#" + Verification.parameterHash( - method.getParameters())) + final String fqn = + ((ClassOrInterfaceDeclaration) parent).getFullyQualifiedName().orElse(UNKNOWN); + return fileData + .getClassData(fqn) + .getMethod( + fqn + + "." + + method.getNameAsString() + + "#" + + Verification.parameterHash(method.getParameters())) .addMetric(metricName, metricValue); } } @@ -169,36 +177,41 @@ public String putMethodMetric(final String metricName, final String metricValue, } /** - * Sets the metric with metricName to metricValue. The metric is attached to the method defined by classFqn and - * methodFqn. Keep in mind to append the {@link Verification#parameterHash(NodeList)} to the methodFqn to - * differentiate overloaded methods. + * Sets the metric with metricName to metricValue. The metric is attached to the method defined by + * classFqn and methodFqn. Keep in mind to append the {@link Verification#parameterHash(NodeList)} + * to the methodFqn to differentiate overloaded methods. * - * @param metricName the name of the metric + * @param metricName the name of the metric * @param metricValue the value of the metric - * @param classFqn the fqn of the method's class - * @param methodFqn the fqn of the method + * @param classFqn the fqn of the method's class + * @param methodFqn the fqn of the method * @return returns the old value of the metric if the metric exists, otherwise null - * @throws NotFoundException gets thrown if the method object was not found and therefore the metric could not be - * added + * @throws NotFoundException gets thrown if the method object was not found and therefore the + * metric could not be added */ - public String putMethodMetric(final String metricName, final String metricValue, // NOPMD - final String classFqn, final String methodFqn) + public String putMethodMetric( + final String metricName, + final String metricValue, + final String classFqn, + final String methodFqn) throws NotFoundException { try { - return fileData.getClassData(classFqn).getMethod(methodFqn) + return fileData + .getClassData(classFqn) + .getMethod(methodFqn) .addMetric(metricName, metricValue); - } catch (NullPointerException e) { // NOPMD + } catch (NullPointerException e) { final NotFoundException notFoundException = new NotFoundException(OBJECT_NOT_FOUND); notFoundException.addSuppressed(e); - throw notFoundException; // NOPMD + throw notFoundException; } } /** - * Used to handle the tracking of the current class. Call with the current class to use the - * {@link MetricAppender#getClass()} and {@link MetricAppender#getCurrentClassName()} anywhere later. Enables the - * usability of {@link MetricAppender#putMethodMetric(String, String)} and - * {@link MetricAppender#putClassMetric(String, String)}. + * Used to handle the tracking of the current class. Call with the current class to use the {@link + * MetricAppender#getClass()} and {@link MetricAppender#getCurrentClassName()} anywhere later. + * Enables the usability of {@link MetricAppender#putMethodMetric(String, String)} and {@link + * MetricAppender#putClassMetric(String, String)}. * * @param clazz the class to enter */ @@ -210,9 +223,7 @@ public void enterClass(final EnumDeclaration clazz) { this.classStack.push(clazz.getFullyQualifiedName().orElse(UNKNOWN)); } - /** - * Leaves a class. - */ + /** Leaves a class. */ public void leaveClass() { this.classStack.pop(); } @@ -221,7 +232,7 @@ public void leaveClass() { * Used to signal the entering of an anonymous class. * * @param anonymousClassName the name used to identify the anonymous class. - * @param parentFqn the parent fqn, e.g the fqn of the method the anonymous class is in + * @param parentFqn the parent fqn, e.g the fqn of the method the anonymous class is in */ public void enterAnonymousClass(final String anonymousClassName, final String parentFqn) { String fqn = parentFqn + "." + anonymousClassName; @@ -231,9 +242,7 @@ public void enterAnonymousClass(final String anonymousClassName, final String pa this.classStack.push(fqn); } - /** - * Leaves an anonymous class. - */ + /** Leaves an anonymous class. */ public void leaveAnonymousClass() { this.classStack.pop(); } @@ -244,35 +253,40 @@ public String getCurrentClassName() { /** * Used to handle the tracking of the current method. Call with the current method to use the - * {@link MetricAppender#getCurrentMethodName()} anywhere later. Enables the usability of - * {@link MetricAppender#putMethodMetric(String, String)}. + * {@link MetricAppender#getCurrentMethodName()} anywhere later. Enables the usability of {@link + * MetricAppender#putMethodMetric(String, String)}. * * @param method the method to enter */ public void enterMethod(final MethodDeclaration method) { methodStack.push( - getCurrentClassName() + "." + method.getNameAsString() + "#" + Verification.parameterHash( - method.getParameters())); + getCurrentClassName() + + "." + + method.getNameAsString() + + "#" + + Verification.parameterHash(method.getParameters())); } /** * Used to handle the tracking of the current method. Call with the current constructor to use the - * {@link MetricAppender#getCurrentMethodName()} anywhere later. Enables the usability of - * {@link MetricAppender#putMethodMetric(String, String)}. + * {@link MetricAppender#getCurrentMethodName()} anywhere later. Enables the usability of {@link + * MetricAppender#putMethodMetric(String, String)}. * * @param constructor the constructor to enter */ public void enterMethod(final ConstructorDeclaration constructor) { methodStack.push( - getCurrentClassName() + "." + constructor.getNameAsString() + "#" - + Verification.parameterHash( - constructor.getParameters())); + getCurrentClassName() + + "." + + constructor.getNameAsString() + + "#" + + Verification.parameterHash(constructor.getParameters())); } /** - * Used to handle the tracking of the current method. Call with the methodFqn to use the - * {@link MetricAppender#getCurrentMethodName()} anywhere later. Enables the usability of - * {@link MetricAppender#putMethodMetric(String, String)}. + * Used to handle the tracking of the current method. Call with the methodFqn to use the {@link + * MetricAppender#getCurrentMethodName()} anywhere later. Enables the usability of {@link + * MetricAppender#putMethodMetric(String, String)}. * * @param methodFqn the methodFqn, the user needs to make sure the fqn is correct */ @@ -280,9 +294,7 @@ public void enterMethod(final String methodFqn) { methodStack.push(methodFqn); } - /** - * Leaves a method. - */ + /** Leaves a method. */ public void leaveMethod() { methodStack.pop(); } @@ -292,8 +304,8 @@ public String getCurrentMethodName() { } /** - * Gets the wrapped {@link JavaFileDataHandler}. Keep in mind that some functionality of the fileDataHandler is only - * avaible during the initial collection of data, use with caution. + * Gets the wrapped {@link JavaFileDataHandler}. Keep in mind that some functionality of the + * fileDataHandler is only avaible during the initial collection of data, use with caution. * * @return the wrapped FileDataHanlder */ diff --git a/src/main/java/net/explorviz/code/analysis/handler/ParameterDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/ParameterDataHandler.java index 7df6382..2bcd854 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/ParameterDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/ParameterDataHandler.java @@ -3,9 +3,7 @@ import java.util.List; import net.explorviz.code.proto.ParameterData; -/** - * A handler to easily access and maintain the ParameterData object. - */ +/** A handler to easily access and maintain the ParameterData object. */ public class ParameterDataHandler implements ProtoBufConvertable { private final ParameterData.Builder builder; diff --git a/src/main/java/net/explorviz/code/analysis/handler/PhpFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/PhpFileDataHandler.java index 977a93b..864c8af 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/PhpFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/PhpFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for PHP source files. - */ +/** FileData handler for PHP source files. */ public class PhpFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { diff --git a/src/main/java/net/explorviz/code/analysis/handler/ProtoBufConvertable.java b/src/main/java/net/explorviz/code/analysis/handler/ProtoBufConvertable.java index 2f60614..8244174 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/ProtoBufConvertable.java +++ b/src/main/java/net/explorviz/code/analysis/handler/ProtoBufConvertable.java @@ -10,5 +10,4 @@ public interface ProtoBufConvertable { T getProtoBufObject(); - } diff --git a/src/main/java/net/explorviz/code/analysis/handler/PythonFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/PythonFileDataHandler.java index 19bfe0a..24a6b8b 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/PythonFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/PythonFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * File data handler specifically for Python files. - */ +/** File data handler specifically for Python files. */ public class PythonFileDataHandler extends AbstractFileDataHandler { private final Stack classStack = new Stack<>(); diff --git a/src/main/java/net/explorviz/code/analysis/handler/RustFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/RustFileDataHandler.java index 30f0185..3fa00ab 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/RustFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/RustFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for Rust source files. - */ +/** FileData handler for Rust source files. */ public class RustFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { diff --git a/src/main/java/net/explorviz/code/analysis/handler/SwiftFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/SwiftFileDataHandler.java index 88fd349..461effe 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/SwiftFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/SwiftFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for Swift source files. - */ +/** FileData handler for Swift source files. */ public class SwiftFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { diff --git a/src/main/java/net/explorviz/code/analysis/handler/TypeScriptFileDataHandler.java b/src/main/java/net/explorviz/code/analysis/handler/TypeScriptFileDataHandler.java index f518cfc..98ac218 100644 --- a/src/main/java/net/explorviz/code/analysis/handler/TypeScriptFileDataHandler.java +++ b/src/main/java/net/explorviz/code/analysis/handler/TypeScriptFileDataHandler.java @@ -8,9 +8,7 @@ import net.explorviz.code.proto.FileData; import net.explorviz.code.proto.Language; -/** - * FileData handler for TypeScript and JavaScript files. - */ +/** FileData handler for TypeScript and JavaScript files. */ public class TypeScriptFileDataHandler extends AbstractFileDataHandler implements ProtoBufConvertable { @@ -84,9 +82,7 @@ public String getCurrentModulePath() { return modulePath.toString(); } - /** - * Builds a fully qualified name for a top-level or nested type, method, or function. - */ + /** Builds a fully qualified name for a top-level or nested type, method, or function. */ public String buildFqn(final String name) { final String parentClassFqn = getCurrentClassFqnOrNull(); if (parentClassFqn != null) { @@ -100,8 +96,9 @@ public String buildFqn(final String name) { } public String buildMethodFqn(final String methodName, final List parameterTypes) { - return buildFqn(methodName) + "#" + net.explorviz.code.analysis.types.Verification.parameterHash( - parameterTypes); + return buildFqn(methodName) + + "#" + + net.explorviz.code.analysis.types.Verification.parameterHash(parameterTypes); } public void enterClass(final String name, final String fqn) { @@ -184,9 +181,10 @@ public FileData getProtoBufObject() { builder.addFunctions(handler.getProtoBufObject()); } - final Language language = fileName.endsWith(".ts") || fileName.endsWith(".tsx") - ? Language.TYPESCRIPT - : Language.JAVASCRIPT; + final Language language = + fileName.endsWith(".ts") || fileName.endsWith(".tsx") + ? Language.TYPESCRIPT + : Language.JAVASCRIPT; builder.setLanguage(language); return builder.build(); @@ -197,7 +195,8 @@ public String toString() { final int INITIAL_STRING_CAPACITY = 500; final StringBuilder result = new StringBuilder(INITIAL_STRING_CAPACITY); - result.append("Language: ") + result + .append("Language: ") .append(fileName.endsWith(".ts") ? "TypeScript" : "JavaScript") .append("\n"); result.append("Package/Module: ").append(builder.getPackageName()).append("\n"); diff --git a/src/main/java/net/explorviz/code/analysis/listener/CFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/CFileDataListener.java index 4fe9453..6de9847 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/CFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/CFileDataListener.java @@ -10,9 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR listener for extracting file data from C source code. - */ +/** ANTLR listener for extracting file data from C source code. */ public class CFileDataListener extends CParserBaseListener implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(CFileDataListener.class); @@ -22,8 +20,7 @@ public class CFileDataListener extends CParserBaseListener implements CommonFile private int functionCount = 0; private int variableCount = 0; - public CFileDataListener(final CFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public CFileDataListener(final CFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -34,9 +31,7 @@ public void enterTranslationUnit(final CParser.TranslationUnitContext ctx) { // populateLexerMetrics() before the walk (also covers parse-tree failures). } - /** - * Populates file-level metrics from lexer tokens when a full parse tree is unavailable. - */ + /** Populates file-level metrics from lexer tokens when a full parse tree is unavailable. */ public void populateLexerMetrics() { final int sloc = getSlocIncludingPreprocessor(); final int cloc = getCloc(); @@ -45,7 +40,8 @@ public void populateLexerMetrics() { fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); extractIncludes(); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -77,7 +73,8 @@ private int getSlocIncludingPreprocessor() { } final String trimmed = text.trim(); - if (trimmed.startsWith("#include") || trimmed.startsWith("#define") + if (trimmed.startsWith("#include") + || trimmed.startsWith("#define") || trimmed.startsWith("#if")) { codeLines.add(token.getLine()); } @@ -203,11 +200,16 @@ public void enterFunctionDefinition(final CParser.FunctionDefinitionContext ctx) final String currentClassName = getSimpleName(fileDataHandler.getCurrentClassFqnOrNull()); final MethodDataHandler methodData; if (functionName.equals(currentClassName)) { - methodData = classData.addConstructor(functionName, - functionName + "#" + extractParameterTypes(ctx.declarator()).hashCode()); + methodData = + classData.addConstructor( + functionName, + functionName + "#" + extractParameterTypes(ctx.declarator()).hashCode()); } else { - methodData = classData.addMethod(functionName, - functionName + "#" + extractParameterTypes(ctx.declarator()).hashCode(), returnType); + methodData = + classData.addMethod( + functionName, + functionName + "#" + extractParameterTypes(ctx.declarator()).hashCode(), + returnType); } addFunctionParameters(methodData, ctx.declarator()); if (ctx.start != null && ctx.stop != null) { @@ -239,8 +241,8 @@ public void enterMemberDeclaration(final CParser.MemberDeclarationContext ctx) { } final String fieldType = extractTypeFromSpecifierQualifierList(ctx.specifierQualifierList()); - for (final CParser.MemberDeclaratorContext memberDecl : ctx.memberDeclaratorList() - .memberDeclarator()) { + for (final CParser.MemberDeclaratorContext memberDecl : + ctx.memberDeclaratorList().memberDeclarator()) { if (memberDecl.declarator() != null) { final String fieldName = extractDeclaratorName(memberDecl.declarator()); if (fieldName != null && !fieldName.contains("(")) { @@ -344,8 +346,12 @@ private void appendTypeText(final StringBuilder typeBuilder, final String text) } private void appendStorageOrQualifier(final StringBuilder typeBuilder, final String text) { - if ("static".equals(text) || "extern".equals(text) || "inline".equals(text) - || "typedef".equals(text) || "const".equals(text) || "volatile".equals(text)) { + if ("static".equals(text) + || "extern".equals(text) + || "inline".equals(text) + || "typedef".equals(text) + || "const".equals(text) + || "volatile".equals(text)) { return; } appendTypeText(typeBuilder, text); @@ -355,8 +361,8 @@ private List extractParameterTypes(final CParser.DeclaratorContext ctx) final List paramTypes = new ArrayList<>(); final CParser.ParameterTypeListContext params = findParameterTypeList(ctx); if (params != null && params.parameterList() != null) { - for (final CParser.ParameterDeclarationContext param : params.parameterList() - .parameterDeclaration()) { + for (final CParser.ParameterDeclarationContext param : + params.parameterList().parameterDeclaration()) { String type = "unknown"; if (param.declarationSpecifiers() != null) { type = extractTypeFromDeclarationSpecifiers(param.declarationSpecifiers()); @@ -367,7 +373,8 @@ private List extractParameterTypes(final CParser.DeclaratorContext ctx) return paramTypes; } - private CParser.ParameterTypeListContext findParameterTypeList(final CParser.DeclaratorContext ctx) { + private CParser.ParameterTypeListContext findParameterTypeList( + final CParser.DeclaratorContext ctx) { if (ctx.directDeclarator() != null) { return findParameterTypeListInDirect(ctx.directDeclarator()); } @@ -385,18 +392,19 @@ private CParser.ParameterTypeListContext findParameterTypeListInDirect( return null; } - private void addFunctionParameters(final MethodDataHandler methodData, - final CParser.DeclaratorContext ctx) { + private void addFunctionParameters( + final MethodDataHandler methodData, final CParser.DeclaratorContext ctx) { final CParser.ParameterTypeListContext params = findParameterTypeList(ctx); if (params == null || params.parameterList() == null) { return; } - for (final CParser.ParameterDeclarationContext param : params.parameterList() - .parameterDeclaration()) { - String paramType = param.declarationSpecifiers() != null - ? extractTypeFromDeclarationSpecifiers(param.declarationSpecifiers()) - : "unknown"; + for (final CParser.ParameterDeclarationContext param : + params.parameterList().parameterDeclaration()) { + String paramType = + param.declarationSpecifiers() != null + ? extractTypeFromDeclarationSpecifiers(param.declarationSpecifiers()) + : "unknown"; String paramName = ""; if (param.declarator() != null) { paramName = extractDeclaratorName(param.declarator()); diff --git a/src/main/java/net/explorviz/code/analysis/listener/CSharpFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/CSharpFileDataListener.java index 0fb4afa..3ed6416 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/CSharpFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/CSharpFileDataListener.java @@ -12,10 +12,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR listener for extracting file data from C# source code. - */ -public class CSharpFileDataListener extends CSharpParserBaseListener implements CommonFileDataListener { +/** ANTLR listener for extracting file data from C# source code. */ +public class CSharpFileDataListener extends CSharpParserBaseListener + implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(CSharpFileDataListener.class); @@ -24,8 +23,8 @@ public class CSharpFileDataListener extends CSharpParserBaseListener implements private int functionCount = 0; private int variableCount = 0; - public CSharpFileDataListener(final CSharpFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public CSharpFileDataListener( + final CSharpFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -38,7 +37,8 @@ public void enterCompilation_unit(final CSharpParser.Compilation_unitContext ctx fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -105,8 +105,8 @@ public void enterClass_declaration(final CSharpParser.Class_declarationContext c } } - private void addClassBaseRelations(final ClassDataHandler classData, - final CSharpParser.Class_baseContext classBase) { + private void addClassBaseRelations( + final ClassDataHandler classData, final CSharpParser.Class_baseContext classBase) { if (classBase == null) { return; } @@ -116,16 +116,23 @@ private void addClassBaseRelations(final ClassDataHandler classData, } if (classBase.interface_type_list() != null) { - for (final CSharpParser.Interface_typeContext interfaceType : classBase.interface_type_list() - .interface_type()) { + for (final CSharpParser.Interface_typeContext interfaceType : + classBase.interface_type_list().interface_type()) { classData.addImplementedInterface(interfaceType.getText()); } } } - private void addFormattedSuperClass(final ClassDataHandler classData, final String superClassFqn) { - classData.setSuperClass(getClassPathFromFqn(superClassFqn, ".cs", fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(superClassFqn)); + private void addFormattedSuperClass( + final ClassDataHandler classData, final String superClassFqn) { + classData.setSuperClass( + getClassPathFromFqn( + superClassFqn, + ".cs", + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(superClassFqn)); } @Override @@ -151,9 +158,10 @@ public void enterStruct_declaration(final CSharpParser.Struct_declarationContext classData.addMetric(SLOC, String.valueOf(getSloc(ctx, tokens))); classData.addMetric(LINE_COUNT, String.valueOf(calculateLoc(ctx))); - if (ctx.struct_interfaces() != null && ctx.struct_interfaces().interface_type_list() != null) { - for (final CSharpParser.Interface_typeContext interfaceType : ctx.struct_interfaces() - .interface_type_list().interface_type()) { + if (ctx.struct_interfaces() != null + && ctx.struct_interfaces().interface_type_list() != null) { + for (final CSharpParser.Interface_typeContext interfaceType : + ctx.struct_interfaces().interface_type_list().interface_type()) { classData.addImplementedInterface(interfaceType.getText()); } } @@ -235,8 +243,8 @@ public void enterField_declaration(final CSharpParser.Field_declarationContext c } final String fieldType = ctx.type_().getText(); - for (final CSharpParser.Variable_declaratorContext variableDecl : ctx.variable_declarators() - .variable_declarator()) { + for (final CSharpParser.Variable_declaratorContext variableDecl : + ctx.variable_declarators().variable_declarator()) { if (variableDecl.identifier() != null) { classData.addField(variableDecl.identifier().getText(), fieldType, new ArrayList<>()); } @@ -256,7 +264,8 @@ public void enterMethod_declaration(final CSharpParser.Method_declarationContext } final String returnType = extractReturnType(ctx); - final MethodDataHandler methodData = createMethodHandler(methodName, returnType, ctx.method_header()); + final MethodDataHandler methodData = + createMethodHandler(methodName, returnType, ctx.method_header()); if (methodData == null) { return; } @@ -281,8 +290,10 @@ public void enterConstructor_declaration(final CSharpParser.Constructor_declarat return; } - final MethodDataHandler methodData = classData.addConstructor(constructorName, - constructorName + "#" + extractParameterTypes(ctx.constructor_declarator()).hashCode()); + final MethodDataHandler methodData = + classData.addConstructor( + constructorName, + constructorName + "#" + extractParameterTypes(ctx.constructor_declarator()).hashCode()); addParameters(methodData, ctx.constructor_declarator().parameter_list()); if (ctx.start != null && ctx.stop != null) { @@ -340,15 +351,18 @@ public void enterVariable_declarator(final CSharpParser.Variable_declaratorConte } } - private MethodDataHandler createMethodHandler(final String methodName, final String returnType, + private MethodDataHandler createMethodHandler( + final String methodName, + final String returnType, final CSharpParser.Method_headerContext header) { if (fileDataHandler.isInClassContext()) { final ClassDataHandler classData = fileDataHandler.getCurrentClassData(); if (classData == null) { return null; } - final MethodDataHandler methodData = classData.addMethod(methodName, - methodName + "#" + extractParameterTypes(header).hashCode(), returnType); + final MethodDataHandler methodData = + classData.addMethod( + methodName, methodName + "#" + extractParameterTypes(header).hashCode(), returnType); addParameters(methodData, header.parameter_list()); return methodData; } @@ -382,18 +396,20 @@ private List extractParameterTypes(final CSharpParser.Method_headerConte return extractParameterTypes(header.parameter_list()); } - private List extractParameterTypes(final CSharpParser.Constructor_declaratorContext declarator) { + private List extractParameterTypes( + final CSharpParser.Constructor_declaratorContext declarator) { return extractParameterTypes(declarator.parameter_list()); } - private List extractParameterTypes(final CSharpParser.Parameter_listContext parameterList) { + private List extractParameterTypes( + final CSharpParser.Parameter_listContext parameterList) { final List paramTypes = new ArrayList<>(); if (parameterList == null || parameterList.fixed_parameters() == null) { return paramTypes; } - for (final CSharpParser.Fixed_parameterContext param : parameterList.fixed_parameters() - .fixed_parameter()) { + for (final CSharpParser.Fixed_parameterContext param : + parameterList.fixed_parameters().fixed_parameter()) { if (param.type_() != null) { paramTypes.add(param.type_().getText()); } @@ -401,14 +417,14 @@ private List extractParameterTypes(final CSharpParser.Parameter_listCont return paramTypes; } - private void addParameters(final MethodDataHandler methodData, - final CSharpParser.Parameter_listContext parameterList) { + private void addParameters( + final MethodDataHandler methodData, final CSharpParser.Parameter_listContext parameterList) { if (parameterList == null || parameterList.fixed_parameters() == null) { return; } - for (final CSharpParser.Fixed_parameterContext param : parameterList.fixed_parameters() - .fixed_parameter()) { + for (final CSharpParser.Fixed_parameterContext param : + parameterList.fixed_parameters().fixed_parameter()) { final String paramType = param.type_() != null ? param.type_().getText() : "unknown"; String paramName = ""; if (param.identifier() != null) { diff --git a/src/main/java/net/explorviz/code/analysis/listener/CommonFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/CommonFileDataListener.java index 436b9a4..7b8d593 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/CommonFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/CommonFileDataListener.java @@ -4,9 +4,7 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; -/** - * Common methods for file data listeners. - */ +/** Common methods for file data listeners. */ public interface CommonFileDataListener { String FILE_SIZE = "size"; String SLOC = "sloc"; @@ -69,11 +67,12 @@ default int getSloc(final org.antlr.v4.runtime.CommonTokenStream tokens) { * Calculates the Source Lines of Code (SLOC) for a specific context by counting unique lines that * contain tokens on the default channel (channel 0) within the range of the context. * - * @param ctx The parser rule context + * @param ctx The parser rule context * @param tokens The token stream * @return The number of lines containing code tokens within the context's range */ - default int getSloc(final ParserRuleContext ctx, final org.antlr.v4.runtime.CommonTokenStream tokens) { + default int getSloc( + final ParserRuleContext ctx, final org.antlr.v4.runtime.CommonTokenStream tokens) { if (ctx == null || ctx.start == null || ctx.stop == null || tokens == null) { return 0; } @@ -102,8 +101,11 @@ default int getSloc(final ParserRuleContext ctx, final org.antlr.v4.runtime.Comm return codeLines.size(); } - default String getClassPathFromFqn(final String fqn, final String fileExtension, - final String currentFilePath, final String currentPackage) { + default String getClassPathFromFqn( + final String fqn, + final String fileExtension, + final String currentFilePath, + final String currentPackage) { if (fqn == null || fqn.isEmpty()) { return "unknown/file"; } diff --git a/src/main/java/net/explorviz/code/analysis/listener/CppFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/CppFileDataListener.java index 9073c7e..10c4aee 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/CppFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/CppFileDataListener.java @@ -11,10 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR Listener for extracting file data from C/C++ source code using the - * CPP14 grammar. - */ +/** ANTLR Listener for extracting file data from C/C++ source code using the CPP14 grammar. */ public class CppFileDataListener extends CPP14ParserBaseListener implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(CppFileDataListener.class); @@ -24,8 +21,8 @@ public class CppFileDataListener extends CPP14ParserBaseListener implements Comm private int functionCount = 0; private int variableCount = 0; - public CppFileDataListener(final CppFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public CppFileDataListener( + final CppFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -42,7 +39,8 @@ public void enterTranslationUnit(final CPP14Parser.TranslationUnitContext ctx) { // Extract #include directives extractIncludes(); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -55,10 +53,7 @@ public void exitTranslationUnit(final CPP14Parser.TranslationUnitContext ctx) { addImportAndClassCountMetrics(fileDataHandler); } - /** - * Scans (hidden-channel) tokens for #include directives and adds them as - * imports. - */ + /** Scans (hidden-channel) tokens for #include directives and adds them as imports. */ private void extractIncludes() { if (tokens == null) { return; @@ -80,9 +75,7 @@ private void extractIncludes() { if (!includeName.isEmpty()) { fileDataHandler.addImport(includeName); - LOGGER.atTrace() - .addArgument(includeName) - .log("Include: {}"); + LOGGER.atTrace().addArgument(includeName).log("Include: {}"); } } } @@ -109,9 +102,7 @@ public void enterNamespaceDefinition(final CPP14Parser.NamespaceDefinitionContex // Use the namespace as the package name fileDataHandler.setPackageName(fileDataHandler.getCurrentNamespace()); - LOGGER.atTrace() - .addArgument(namespaceName) - .log("Namespace: {}"); + LOGGER.atTrace().addArgument(namespaceName).log("Namespace: {}"); } } @@ -178,7 +169,8 @@ public void enterClassSpecifier(final CPP14Parser.ClassSpecifierContext ctx) { extractBaseClasses(classHead.baseClause()); } - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(isStruct ? "Struct" : "Class") .addArgument(className) .addArgument(fqn) @@ -188,7 +180,8 @@ public void enterClassSpecifier(final CPP14Parser.ClassSpecifierContext ctx) { @Override public void exitClassSpecifier(final CPP14Parser.ClassSpecifierContext ctx) { - if (ctx.classHead() != null && ctx.classHead().classHeadName() != null + if (ctx.classHead() != null + && ctx.classHead().classHeadName() != null && ctx.classHead().classHeadName().className() != null) { fileDataHandler.leaveClass(); } @@ -216,9 +209,7 @@ public void enterEnumSpecifier(final CPP14Parser.EnumSpecifierContext ctx) { classData.addMetric(LINE_COUNT, String.valueOf(calculateLoc(ctx))); } - LOGGER.atTrace() - .addArgument(enumName) - .log("Enum: {}"); + LOGGER.atTrace().addArgument(enumName).log("Enum: {}"); } } @@ -276,9 +267,8 @@ public void enterFunctionDefinition(final CPP14Parser.FunctionDefinitionContext if (classData != null) { // Check if it is a constructor (name matches class name) final String currentClassFqn = fileDataHandler.getCurrentClassFqnOrNull(); - final String currentClassName = currentClassFqn != null - ? getSimpleName(currentClassFqn) - : null; + final String currentClassName = + currentClassFqn != null ? getSimpleName(currentClassFqn) : null; final MethodDataHandler methodData; if (functionName.equals(currentClassName)) { @@ -306,9 +296,7 @@ public void enterFunctionDefinition(final CPP14Parser.FunctionDefinitionContext methodData.addMetric(SLOC, String.valueOf(getSloc(ctx, tokens))); methodData.addMetric(LINE_COUNT, String.valueOf(functionLoc)); - LOGGER.atTrace() - .addArgument(functionName) - .log("Class method: {}"); + LOGGER.atTrace().addArgument(functionName).log("Class method: {}"); } } else { // Global function (or namespace-level function) @@ -328,9 +316,7 @@ public void enterFunctionDefinition(final CPP14Parser.FunctionDefinitionContext addFunctionParameters(methodHandler, ctx.declarator()); - LOGGER.atTrace() - .addArgument(qualifiedName) - .log("Out-of-class method definition: {}"); + LOGGER.atTrace().addArgument(qualifiedName).log("Out-of-class method definition: {}"); } else { final var methodHandler = fileDataHandler.addGlobalFunction(functionName, returnType); @@ -342,9 +328,7 @@ public void enterFunctionDefinition(final CPP14Parser.FunctionDefinitionContext addFunctionParameters(methodHandler, ctx.declarator()); - LOGGER.atTrace() - .addArgument(functionName) - .log("Global function: {}"); + LOGGER.atTrace().addArgument(functionName).log("Global function: {}"); } } } @@ -371,8 +355,8 @@ public void enterMemberDeclaration(final CPP14Parser.MemberDeclarationContext ct final String declaredType = extractTypeFromDeclSpecifierSeq(ctx.declSpecifierSeq()); - for (final CPP14Parser.MemberDeclaratorContext memberDecl : ctx.memberDeclaratorList() - .memberDeclarator()) { + for (final CPP14Parser.MemberDeclaratorContext memberDecl : + ctx.memberDeclaratorList().memberDeclarator()) { if (memberDecl.declarator() == null) { continue; } @@ -391,8 +375,8 @@ public void enterMemberDeclaration(final CPP14Parser.MemberDeclarationContext ct if (ctx.declSpecifierSeq().getText().contains("virtual")) { modifiers.add("virtual"); } - if (memberDecl.pureSpecifier() != null || (memberDecl.getText() != null - && memberDecl.getText().contains("=0"))) { + if (memberDecl.pureSpecifier() != null + || (memberDecl.getText() != null && memberDecl.getText().contains("=0"))) { modifiers.add("pure"); } for (final String modifier : modifiers) { @@ -419,9 +403,7 @@ public void enterAccessSpecifier(final CPP14Parser.AccessSpecifierContext ctx) { final var classData = fileDataHandler.getCurrentClassData(); if (classData != null) { final String accessModifier = ctx.getText(); - LOGGER.atTrace() - .addArgument(accessModifier) - .log("Access specifier: {}"); + LOGGER.atTrace().addArgument(accessModifier).log("Access specifier: {}"); } } @@ -461,8 +443,8 @@ private void extractBaseClasses(final CPP14Parser.BaseClauseContext baseClause) return; } - for (final CPP14Parser.BaseSpecifierContext baseSpec : baseClause.baseSpecifierList() - .baseSpecifier()) { + for (final CPP14Parser.BaseSpecifierContext baseSpec : + baseClause.baseSpecifierList().baseSpecifier()) { if (baseSpec.baseTypeSpecifier() != null && baseSpec.baseTypeSpecifier().classOrDeclType() != null) { final String baseClassName = baseSpec.baseTypeSpecifier().classOrDeclType().getText(); @@ -471,9 +453,7 @@ private void extractBaseClasses(final CPP14Parser.BaseClauseContext baseClause) } } - /** - * Extract the function name from a declarator, handling nested declarators. - */ + /** Extract the function name from a declarator, handling nested declarators. */ private String extractFunctionName(final CPP14Parser.DeclaratorContext ctx) { if (ctx.pointerDeclarator() != null) { return extractFunctionNameFromPointerDeclarator(ctx.pointerDeclarator()); @@ -534,12 +514,13 @@ private String extractIdExpressionName(final CPP14Parser.IdExpressionContext ctx } /** - * Extract the fully qualified function name (including class prefix for - * out-of-class definitions). + * Extract the fully qualified function name (including class prefix for out-of-class + * definitions). */ private String extractQualifiedFunctionName(final CPP14Parser.DeclaratorContext ctx) { if (ctx.pointerDeclarator() != null && ctx.pointerDeclarator().noPointerDeclarator() != null) { - final CPP14Parser.NoPointerDeclaratorContext noPtr = ctx.pointerDeclarator().noPointerDeclarator(); + final CPP14Parser.NoPointerDeclaratorContext noPtr = + ctx.pointerDeclarator().noPointerDeclarator(); if (noPtr.declaratorId() != null && noPtr.declaratorId().idExpression() != null) { return noPtr.declaratorId().idExpression().getText(); } @@ -550,8 +531,7 @@ private String extractQualifiedFunctionName(final CPP14Parser.DeclaratorContext return null; } - private String extractQualifiedFromNoPointer( - final CPP14Parser.NoPointerDeclaratorContext ctx) { + private String extractQualifiedFromNoPointer(final CPP14Parser.NoPointerDeclaratorContext ctx) { if (ctx.declaratorId() != null && ctx.declaratorId().idExpression() != null) { return ctx.declaratorId().idExpression().getText(); } @@ -561,9 +541,7 @@ private String extractQualifiedFromNoPointer( return null; } - /** - * Extract a simple declarator name for fields. - */ + /** Extract a simple declarator name for fields. */ private String extractDeclaratorName(final CPP14Parser.DeclaratorContext ctx) { if (ctx.pointerDeclarator() != null) { return extractDeclaratorNameFromPointer(ctx.pointerDeclarator()); @@ -574,8 +552,7 @@ private String extractDeclaratorName(final CPP14Parser.DeclaratorContext ctx) { return null; } - private String extractDeclaratorNameFromPointer( - final CPP14Parser.PointerDeclaratorContext ctx) { + private String extractDeclaratorNameFromPointer(final CPP14Parser.PointerDeclaratorContext ctx) { if (ctx.noPointerDeclarator() != null) { return extractNameFromNoPointerDeclarator(ctx.noPointerDeclarator()); } @@ -583,11 +560,10 @@ private String extractDeclaratorNameFromPointer( } /** - * Extract the type from a declSpecifierSeq context, skipping cv-qualifiers - * (const, volatile) and non-type specifiers. + * Extract the type from a declSpecifierSeq context, skipping cv-qualifiers (const, volatile) and + * non-type specifiers. */ - private String extractTypeFromDeclSpecifierSeq( - final CPP14Parser.DeclSpecifierSeqContext ctx) { + private String extractTypeFromDeclSpecifierSeq(final CPP14Parser.DeclSpecifierSeqContext ctx) { if (ctx == null) { return "void"; } @@ -607,11 +583,17 @@ private String extractTypeFromDeclSpecifierSeq( } else { final String text = declSpec.getText(); // Skip storage class specifiers and function specifiers for type name - if (!"static".equals(text) && !"extern".equals(text) && !"inline".equals(text) - && !"virtual".equals(text) && !"explicit".equals(text) - && !"friend".equals(text) && !"typedef".equals(text) - && !"constexpr".equals(text) && !"mutable".equals(text) - && !"register".equals(text) && !"thread_local".equals(text)) { + if (!"static".equals(text) + && !"extern".equals(text) + && !"inline".equals(text) + && !"virtual".equals(text) + && !"explicit".equals(text) + && !"friend".equals(text) + && !"typedef".equals(text) + && !"constexpr".equals(text) + && !"mutable".equals(text) + && !"register".equals(text) + && !"thread_local".equals(text)) { if (typeBuilder.length() > 0) { typeBuilder.append(" "); } @@ -625,8 +607,8 @@ private String extractTypeFromDeclSpecifierSeq( } /** - * Extract the actual data type from a typeSpecifier, filtering out - * cv-qualifiers (const, volatile). + * Extract the actual data type from a typeSpecifier, filtering out cv-qualifiers (const, + * volatile). */ private String extractActualType(final CPP14Parser.TypeSpecifierContext typeSpec) { // typeSpecifier -> trailingTypeSpecifier | classSpecifier | enumSpecifier @@ -647,18 +629,16 @@ private String extractActualType(final CPP14Parser.TypeSpecifierContext typeSpec return typeSpec.getText(); } - /** - * Extract parameter types from a declarator's parameter list. - */ + /** Extract parameter types from a declarator's parameter list. */ private List extractParameterTypes(final CPP14Parser.DeclaratorContext ctx) { final List paramTypes = new ArrayList<>(); final CPP14Parser.ParametersAndQualifiersContext params = findParametersAndQualifiers(ctx); - if (params != null && params.parameterDeclarationClause() != null + if (params != null + && params.parameterDeclarationClause() != null && params.parameterDeclarationClause().parameterDeclarationList() != null) { - for (final CPP14Parser.ParameterDeclarationContext param : params.parameterDeclarationClause() - .parameterDeclarationList() - .parameterDeclaration()) { + for (final CPP14Parser.ParameterDeclarationContext param : + params.parameterDeclarationClause().parameterDeclarationList().parameterDeclaration()) { if (param.declSpecifierSeq() != null) { String type = extractTypeFromDeclSpecifierSeq(param.declSpecifierSeq()); type += extractPointerSuffix(param); @@ -670,9 +650,7 @@ private List extractParameterTypes(final CPP14Parser.DeclaratorContext c return paramTypes; } - /** - * Find the parametersAndQualifiers context within a declarator. - */ + /** Find the parametersAndQualifiers context within a declarator. */ private CPP14Parser.ParametersAndQualifiersContext findParametersAndQualifiers( final CPP14Parser.DeclaratorContext ctx) { if (ctx.pointerDeclarator() != null) { @@ -698,14 +676,12 @@ private CPP14Parser.ParametersAndQualifiersContext findParamsInNoPointerDeclarat return null; } - /** - * Add function modifiers (virtual, static, inline, etc.) to the method handler. - */ - private void addFunctionModifiers(final MethodDataHandler methodData, - final CPP14Parser.FunctionDefinitionContext ctx) { + /** Add function modifiers (virtual, static, inline, etc.) to the method handler. */ + private void addFunctionModifiers( + final MethodDataHandler methodData, final CPP14Parser.FunctionDefinitionContext ctx) { if (ctx.declSpecifierSeq() != null) { - for (final CPP14Parser.DeclSpecifierContext declSpec : ctx.declSpecifierSeq() - .declSpecifier()) { + for (final CPP14Parser.DeclSpecifierContext declSpec : + ctx.declSpecifierSeq().declSpecifier()) { if (declSpec.functionSpecifier() != null) { methodData.addModifier(declSpec.functionSpecifier().getText()); } else if (declSpec.storageClassSpecifier() != null) { @@ -716,28 +692,27 @@ private void addFunctionModifiers(final MethodDataHandler methodData, // Check for virtual specifiers (override, final) if (ctx.virtualSpecifierSeq() != null) { - for (final CPP14Parser.VirtualSpecifierContext virtSpec : ctx.virtualSpecifierSeq() - .virtualSpecifier()) { + for (final CPP14Parser.VirtualSpecifierContext virtSpec : + ctx.virtualSpecifierSeq().virtualSpecifier()) { methodData.addModifier(virtSpec.getText()); } } } - /** - * Add function parameters to the method handler. - */ - private void addFunctionParameters(final MethodDataHandler methodData, - final CPP14Parser.DeclaratorContext ctx) { + /** Add function parameters to the method handler. */ + private void addFunctionParameters( + final MethodDataHandler methodData, final CPP14Parser.DeclaratorContext ctx) { final CPP14Parser.ParametersAndQualifiersContext params = findParametersAndQualifiers(ctx); - if (params != null && params.parameterDeclarationClause() != null + if (params != null + && params.parameterDeclarationClause() != null && params.parameterDeclarationClause().parameterDeclarationList() != null) { - for (final CPP14Parser.ParameterDeclarationContext param : params.parameterDeclarationClause() - .parameterDeclarationList() - .parameterDeclaration()) { - String paramType = param.declSpecifierSeq() != null - ? extractTypeFromDeclSpecifierSeq(param.declSpecifierSeq()) - : "unknown"; + for (final CPP14Parser.ParameterDeclarationContext param : + params.parameterDeclarationClause().parameterDeclarationList().parameterDeclaration()) { + String paramType = + param.declSpecifierSeq() != null + ? extractTypeFromDeclSpecifierSeq(param.declSpecifierSeq()) + : "unknown"; paramType += extractPointerSuffix(param); paramType += extractArraySuffix(param); String paramName = ""; @@ -753,15 +728,15 @@ private void addFunctionParameters(final MethodDataHandler methodData, } } - /** - * Extract const and volatile modifiers from a parameter declaration. - */ - private List extractParameterModifiers(final CPP14Parser.ParameterDeclarationContext param) { + /** Extract const and volatile modifiers from a parameter declaration. */ + private List extractParameterModifiers( + final CPP14Parser.ParameterDeclarationContext param) { final List modifiers = new ArrayList<>(); // Check declSpecifierSeq for const/volatile if (param.declSpecifierSeq() != null) { - for (final CPP14Parser.DeclSpecifierContext declSpec : param.declSpecifierSeq().declSpecifier()) { + for (final CPP14Parser.DeclSpecifierContext declSpec : + param.declSpecifierSeq().declSpecifier()) { if (declSpec.typeSpecifier() != null) { if (declSpec.typeSpecifier().trailingTypeSpecifier() != null) { final var trailing = declSpec.typeSpecifier().trailingTypeSpecifier(); @@ -779,7 +754,8 @@ private List extractParameterModifiers(final CPP14Parser.ParameterDeclar if (ptrDecl.pointerOperator() != null) { for (final CPP14Parser.PointerOperatorContext ptrOp : ptrDecl.pointerOperator()) { if (ptrOp.cvQualifierSeq() != null && ptrOp.cvQualifierSeq().cvQualifier() != null) { - for (final CPP14Parser.CvQualifierContext cvQual : ptrOp.cvQualifierSeq().cvQualifier()) { + for (final CPP14Parser.CvQualifierContext cvQual : + ptrOp.cvQualifierSeq().cvQualifier()) { modifiers.add(cvQual.getText()); } } @@ -791,8 +767,8 @@ private List extractParameterModifiers(final CPP14Parser.ParameterDeclar } /** - * Check if a parameter declaration contains array brackets (e.g. int arr[]) - * and return "[]" if so, otherwise empty string. + * Check if a parameter declaration contains array brackets (e.g. int arr[]) and return "[]" if + * so, otherwise empty string. */ private String extractArraySuffix(final CPP14Parser.ParameterDeclarationContext param) { // Check the declarator for array brackets: noPointerDeclarator [ ] @@ -810,8 +786,8 @@ private String extractArraySuffix(final CPP14Parser.ParameterDeclarationContext } /** - * Check if a parameter declaration contains pointer operators (e.g. int *p) - * and return the pointer suffix ("*", "&", "&&") if so. + * Check if a parameter declaration contains pointer operators (e.g. int *p) and return the + * pointer suffix ("*", "&", "&&") if so. */ private String extractPointerSuffix(final CPP14Parser.ParameterDeclarationContext param) { if (param.declarator() != null && param.declarator().pointerDeclarator() != null) { @@ -833,9 +809,7 @@ private String extractPointerSuffix(final CPP14Parser.ParameterDeclarationContex return ""; } - /** - * Check if a declarator contains array brackets (LeftBracket / RightBracket). - */ + /** Check if a declarator contains array brackets (LeftBracket / RightBracket). */ private boolean hasArrayBrackets(final CPP14Parser.DeclaratorContext ctx) { if (ctx.pointerDeclarator() != null && ctx.pointerDeclarator().noPointerDeclarator() != null) { return hasArrayBracketsInNoPointer(ctx.pointerDeclarator().noPointerDeclarator()); @@ -846,8 +820,7 @@ private boolean hasArrayBrackets(final CPP14Parser.DeclaratorContext ctx) { return false; } - private boolean hasArrayBracketsInNoPointer( - final CPP14Parser.NoPointerDeclaratorContext ctx) { + private boolean hasArrayBracketsInNoPointer(final CPP14Parser.NoPointerDeclaratorContext ctx) { // noPointerDeclarator: ... | noPointerDeclarator LeftBracket ... RightBracket if (ctx.LeftBracket() != null) { return true; @@ -858,9 +831,7 @@ private boolean hasArrayBracketsInNoPointer( return false; } - /** - * Get comment lines of code by counting tokens on the hidden channel. - */ + /** Get comment lines of code by counting tokens on the hidden channel. */ @Override public void enterSimpleDeclaration(final CPP14Parser.SimpleDeclarationContext ctx) { if (ctx.initDeclaratorList() != null) { diff --git a/src/main/java/net/explorviz/code/analysis/listener/GoFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/GoFileDataListener.java index 1553ddf..91c9f3e 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/GoFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/GoFileDataListener.java @@ -11,9 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR listener for extracting file data from Go source code. - */ +/** ANTLR listener for extracting file data from Go source code. */ public class GoFileDataListener extends GoParserBaseListener implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(GoFileDataListener.class); @@ -23,8 +21,8 @@ public class GoFileDataListener extends GoParserBaseListener implements CommonFi private int functionCount = 0; private int variableCount = 0; - public GoFileDataListener(final GoFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public GoFileDataListener( + final GoFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -37,7 +35,8 @@ public void enterSourceFile(final GoParser.SourceFileContext ctx) { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -140,7 +139,8 @@ public void enterFunctionDecl(final GoParser.FunctionDeclContext ctx) { methodHandler.addMetric(SLOC, String.valueOf(getSloc(ctx, tokens))); methodHandler.addMetric(LINE_COUNT, String.valueOf(calculateLoc(ctx))); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(functionName) .addArgument(paramTypes.size()) .log("Function: {} ({} params)"); @@ -159,7 +159,8 @@ public void enterMethodDecl(final GoParser.MethodDeclContext ctx) { final ClassDataHandler classData = fileDataHandler.getClassData(fqn); if (classData == null) { - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(methodName) .addArgument(receiverType) .log("Method {} on unknown type {}"); @@ -216,8 +217,8 @@ private List extractParameterTypes(final GoParser.SignatureContext signa return paramTypes; } - private void addSignatureParameters(final MethodDataHandler methodData, - final GoParser.SignatureContext signature) { + private void addSignatureParameters( + final MethodDataHandler methodData, final GoParser.SignatureContext signature) { if (signature.parameters() == null) { return; } diff --git a/src/main/java/net/explorviz/code/analysis/listener/JavaFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/JavaFileDataListener.java index 24990ea..ac9b150 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/JavaFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/JavaFileDataListener.java @@ -14,9 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR Listener-based implementation for extracting file data from Java source code. - */ +/** ANTLR Listener-based implementation for extracting file data from Java source code. */ public class JavaFileDataListener extends JavaParserBaseListener implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(JavaFileDataListener.class); @@ -30,7 +28,8 @@ public class JavaFileDataListener extends JavaParserBaseListener implements Comm private int functionCount = 0; private int variableCount = 0; - public JavaFileDataListener(final JavaFileDataHandler fileDataHandler, + public JavaFileDataListener( + final JavaFileDataHandler fileDataHandler, final boolean wildcardImportProperty, final org.antlr.v4.runtime.CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; @@ -48,7 +47,8 @@ public void enterCompilationUnit(final JavaParser.CompilationUnitContext ctx) { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -108,14 +108,23 @@ public void enterClassDeclaration(final JavaParser.ClassDeclarationContext ctx) if (ctx.typeType() != null) { final String superClassFqn = resolveTypeName(ctx.typeType().getText()); - fileDataHandler.getCurrentClassData() - .setSuperClass(getClassPathFromFqn(superClassFqn, ".java", fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(superClassFqn)); + fileDataHandler + .getCurrentClassData() + .setSuperClass( + getClassPathFromFqn( + superClassFqn, + ".java", + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(superClassFqn)); } if (ctx.IMPLEMENTS() != null && !ctx.typeList().isEmpty()) { for (final JavaParser.TypeTypeContext typeCtx : ctx.typeList(0).typeType()) { - fileDataHandler.getCurrentClassData().addImplementedInterface(resolveTypeName(typeCtx.getText())); + fileDataHandler + .getCurrentClassData() + .addImplementedInterface(resolveTypeName(typeCtx.getText())); } } } @@ -139,7 +148,9 @@ public void enterInterfaceDeclaration(final JavaParser.InterfaceDeclarationConte if (ctx.EXTENDS() != null && !ctx.typeList().isEmpty()) { for (final JavaParser.TypeTypeContext typeCtx : ctx.typeList(0).typeType()) { - fileDataHandler.getCurrentClassData().addImplementedInterface(resolveTypeName(typeCtx.getText())); + fileDataHandler + .getCurrentClassData() + .addImplementedInterface(resolveTypeName(typeCtx.getText())); } } } @@ -186,20 +197,22 @@ public void enterRecordDeclaration(final JavaParser.RecordDeclarationContext ctx if (ctx.IMPLEMENTS() != null && ctx.typeList() != null) { for (final JavaParser.TypeTypeContext typeCtx : ctx.typeList().typeType()) { - fileDataHandler.getCurrentClassData() + fileDataHandler + .getCurrentClassData() .addImplementedInterface(resolveTypeName(typeCtx.getText())); } } if (ctx.recordHeader() != null && ctx.recordHeader().recordComponentList() != null) { - for (final JavaParser.RecordComponentContext component : ctx.recordHeader() - .recordComponentList().recordComponent()) { + for (final JavaParser.RecordComponentContext component : + ctx.recordHeader().recordComponentList().recordComponent()) { if (component.identifier() == null || component.typeType() == null) { continue; } final String fieldName = component.identifier().getText(); final String fieldType = resolveTypeName(component.typeType().getText()); - fileDataHandler.getCurrentClassData() + fileDataHandler + .getCurrentClassData() .addField(fieldName, fieldType, List.of("private", "final")); variableCount++; } @@ -219,12 +232,13 @@ public void enterCompactConstructorDeclaration( } final String constructorName = ctx.identifier().getText(); - final String constructorFqn = fileDataHandler.getCurrentClassFqn() + "." + constructorName + "#"; + final String constructorFqn = + fileDataHandler.getCurrentClassFqn() + "." + constructorName + "#"; fileDataHandler.enterMethod(constructorFqn); functionCount++; - final MethodDataHandler constructor = fileDataHandler.getCurrentClassData() - .addConstructor(constructorName, constructorFqn); + final MethodDataHandler constructor = + fileDataHandler.getCurrentClassData().addConstructor(constructorName, constructorFqn); if (ctx.modifier() != null) { for (final JavaParser.ModifierContext modCtx : ctx.modifier()) { final String modText = modCtx.getText(); @@ -268,8 +282,7 @@ public void enterAnnotationTypeDeclaration( } @Override - public void exitAnnotationTypeDeclaration( - final JavaParser.AnnotationTypeDeclarationContext ctx) { + public void exitAnnotationTypeDeclaration(final JavaParser.AnnotationTypeDeclarationContext ctx) { fileDataHandler.leaveClass(); } @@ -291,8 +304,8 @@ public void enterAnnotationMethodRest(final JavaParser.AnnotationMethodRestConte returnType = resolveTypeName(elementRest.typeType().getText()); } - final MethodDataHandler methodData = fileDataHandler.getCurrentClassData() - .addMethod(methodName, methodFqn, returnType); + final MethodDataHandler methodData = + fileDataHandler.getCurrentClassData().addMethod(methodName, methodFqn, returnType); if (ctx.start != null && ctx.stop != null) { methodData.setLines(ctx.start.getLine(), ctx.stop.getLine()); } @@ -334,7 +347,8 @@ public void enterEnumConstant(final JavaParser.EnumConstantContext ctx) { @Override public void enterFieldDeclaration(final JavaParser.FieldDeclarationContext ctx) { - if (ctx.variableDeclarators() == null || ctx.typeType() == null + if (ctx.variableDeclarators() == null + || ctx.typeType() == null || !fileDataHandler.isInClassContext()) { return; } @@ -342,7 +356,8 @@ public void enterFieldDeclaration(final JavaParser.FieldDeclarationContext ctx) final String fieldType = resolveTypeName(ctx.typeType().getText()); final List modifiers = extractMemberModifiers(ctx); - for (final JavaParser.VariableDeclaratorContext varCtx : ctx.variableDeclarators().variableDeclarator()) { + for (final JavaParser.VariableDeclaratorContext varCtx : + ctx.variableDeclarators().variableDeclarator()) { final String fieldName = varCtx.variableDeclaratorId().identifier().getText(); final String fieldFqn = fileDataHandler.getCurrentClassFqn() + "." + fieldName; fileDataHandler.enterMethod(fieldFqn); @@ -354,7 +369,8 @@ public void enterFieldDeclaration(final JavaParser.FieldDeclarationContext ctx) @Override public void enterMethodDeclaration(final JavaParser.MethodDeclarationContext ctx) { - if (ctx.identifier() == null || ctx.formalParameters() == null + if (ctx.identifier() == null + || ctx.formalParameters() == null || !fileDataHandler.isInClassContext()) { return; } @@ -362,8 +378,8 @@ public void enterMethodDeclaration(final JavaParser.MethodDeclarationContext ctx final String methodName = ctx.identifier().getText(); final List parameterTypes = extractParameterTypes(ctx.formalParameters()); final String parameterHash = Verification.parameterHash(parameterTypes); - final String methodFqn = fileDataHandler.getCurrentClassFqn() + "." + methodName - + "#" + parameterHash; + final String methodFqn = + fileDataHandler.getCurrentClassFqn() + "." + methodName + "#" + parameterHash; fileDataHandler.enterMethod(methodFqn); functionCount++; @@ -373,8 +389,8 @@ public void enterMethodDeclaration(final JavaParser.MethodDeclarationContext ctx returnType = resolveTypeName(ctx.typeTypeOrVoid().getText()); } - final MethodDataHandler methodData = fileDataHandler.getCurrentClassData() - .addMethod(methodName, methodFqn, returnType); + final MethodDataHandler methodData = + fileDataHandler.getCurrentClassData().addMethod(methodName, methodFqn, returnType); for (final String modifier : extractMemberModifiers(ctx)) { methodData.addModifier(modifier); @@ -402,7 +418,8 @@ public void enterInterfaceMethodDeclaration( return; } - final JavaParser.InterfaceCommonBodyDeclarationContext body = ctx.interfaceCommonBodyDeclaration(); + final JavaParser.InterfaceCommonBodyDeclarationContext body = + ctx.interfaceCommonBodyDeclaration(); if (body.identifier() == null || body.formalParameters() == null) { return; } @@ -410,8 +427,8 @@ public void enterInterfaceMethodDeclaration( final String methodName = body.identifier().getText(); final List parameterTypes = extractParameterTypes(body.formalParameters()); final String parameterHash = Verification.parameterHash(parameterTypes); - final String methodFqn = fileDataHandler.getCurrentClassFqn() + "." + methodName - + "#" + parameterHash; + final String methodFqn = + fileDataHandler.getCurrentClassFqn() + "." + methodName + "#" + parameterHash; fileDataHandler.enterMethod(methodFqn); functionCount++; @@ -421,8 +438,8 @@ public void enterInterfaceMethodDeclaration( returnType = resolveTypeName(body.typeTypeOrVoid().getText()); } - final MethodDataHandler methodData = fileDataHandler.getCurrentClassData() - .addMethod(methodName, methodFqn, returnType); + final MethodDataHandler methodData = + fileDataHandler.getCurrentClassData().addMethod(methodName, methodFqn, returnType); for (final String modifier : extractInterfaceMethodModifiers(ctx)) { methodData.addModifier(modifier); @@ -446,7 +463,8 @@ public void exitInterfaceMethodDeclaration( @Override public void enterConstructorDeclaration(final JavaParser.ConstructorDeclarationContext ctx) { - if (ctx.identifier() == null || ctx.formalParameters() == null + if (ctx.identifier() == null + || ctx.formalParameters() == null || !fileDataHandler.isInClassContext()) { return; } @@ -454,14 +472,14 @@ public void enterConstructorDeclaration(final JavaParser.ConstructorDeclarationC final String constructorName = ctx.identifier().getText(); final List parameterTypes = extractParameterTypes(ctx.formalParameters()); final String parameterHash = Verification.parameterHash(parameterTypes); - final String constructorFqn = fileDataHandler.getCurrentClassFqn() + "." + constructorName - + "#" + parameterHash; + final String constructorFqn = + fileDataHandler.getCurrentClassFqn() + "." + constructorName + "#" + parameterHash; fileDataHandler.enterMethod(constructorFqn); functionCount++; - final MethodDataHandler constructor = fileDataHandler.getCurrentClassData() - .addConstructor(constructorName, constructorFqn); + final MethodDataHandler constructor = + fileDataHandler.getCurrentClassData().addConstructor(constructorName, constructorFqn); for (final String modifier : extractMemberModifiers(ctx)) { constructor.addModifier(modifier); @@ -511,7 +529,8 @@ private List getTypeModifiers(final ParserRuleConte } if (parent instanceof JavaParser.MemberDeclarationContext) { final ParseTree grandParent = parent.getParent(); - if (grandParent instanceof JavaParser.ClassBodyDeclarationContext classBodyDeclarationContext) { + if (grandParent + instanceof JavaParser.ClassBodyDeclarationContext classBodyDeclarationContext) { return classBodyDeclarationContext.modifier(); } } @@ -537,14 +556,15 @@ private List getMemberModifiers(final ParserRuleCon if (parent instanceof JavaParser.ClassBodyDeclarationContext classBodyDeclarationContext) { return classBodyDeclarationContext.modifier(); } - if (parent instanceof JavaParser.InterfaceBodyDeclarationContext interfaceBodyDeclarationContext) { + if (parent + instanceof JavaParser.InterfaceBodyDeclarationContext interfaceBodyDeclarationContext) { return interfaceBodyDeclarationContext.modifier(); } return List.of(); } - private boolean hasModifier(final List modifiers, - final String modifier) { + private boolean hasModifier( + final List modifiers, final String modifier) { if (modifiers == null) { return false; } @@ -595,8 +615,8 @@ private List extractParameterTypes(final JavaParser.FormalParametersCont return parameterTypes; } - private void addParameters(final MethodDataHandler methodData, - final JavaParser.FormalParametersContext ctx) { + private void addParameters( + final MethodDataHandler methodData, final JavaParser.FormalParametersContext ctx) { if (ctx == null) { return; } @@ -672,10 +692,26 @@ private String resolveTypeName(final String typeName) { return wildcardImport + "." + baseType + genericsPart + arraySuffix; } - final List javaLangTypes = Arrays.asList( - "String", "Integer", "Long", "Double", "Float", "Boolean", "Character", - "Byte", "Short", "Object", "Class", "System", "Math", "Thread", - "Runnable", "Exception", "RuntimeException", "Error"); + final List javaLangTypes = + Arrays.asList( + "String", + "Integer", + "Long", + "Double", + "Float", + "Boolean", + "Character", + "Byte", + "Short", + "Object", + "Class", + "System", + "Math", + "Thread", + "Runnable", + "Exception", + "RuntimeException", + "Error"); if (javaLangTypes.contains(baseType)) { return "java.lang." + baseType + genericsPart + arraySuffix; } @@ -698,8 +734,9 @@ public void enterLocalVariableDeclaration(final JavaParser.LocalVariableDeclarat } private boolean isPrimitiveType(final String type) { - return Arrays.asList("byte", "short", "int", "long", "float", "double", - "boolean", "char", "void").contains(type); + return Arrays.asList( + "byte", "short", "int", "long", "float", "double", "boolean", "char", "void") + .contains(type); } private int getCloc(final ParserRuleContext ctx) { diff --git a/src/main/java/net/explorviz/code/analysis/listener/KotlinFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/KotlinFileDataListener.java index d844bfc..20953b4 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/KotlinFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/KotlinFileDataListener.java @@ -11,10 +11,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR listener for extracting file data from Kotlin source code. - */ -public class KotlinFileDataListener extends KotlinParserBaseListener implements CommonFileDataListener { +/** ANTLR listener for extracting file data from Kotlin source code. */ +public class KotlinFileDataListener extends KotlinParserBaseListener + implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(KotlinFileDataListener.class); @@ -24,8 +23,8 @@ public class KotlinFileDataListener extends KotlinParserBaseListener implements private int variableCount = 0; private int functionDepth = 0; - public KotlinFileDataListener(final KotlinFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public KotlinFileDataListener( + final KotlinFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -57,7 +56,8 @@ private void addFileMetrics() { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -118,8 +118,8 @@ public void enterClassDeclaration(final KotlinParser.ClassDeclarationContext ctx addDelegationRelations(classData, ctx); } - private void addDelegationRelations(final ClassDataHandler classData, - final KotlinParser.ClassDeclarationContext ctx) { + private void addDelegationRelations( + final ClassDataHandler classData, final KotlinParser.ClassDeclarationContext ctx) { if (ctx.delegationSpecifiers() == null) { return; } @@ -153,9 +153,16 @@ private String extractDelegationType(final KotlinParser.DelegationSpecifierConte return null; } - private void addFormattedSuperClass(final ClassDataHandler classData, final String superClassFqn) { - classData.setSuperClass(getClassPathFromFqn(superClassFqn, ".kt", fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(superClassFqn)); + private void addFormattedSuperClass( + final ClassDataHandler classData, final String superClassFqn) { + classData.setSuperClass( + getClassPathFromFqn( + superClassFqn, + ".kt", + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(superClassFqn)); } @Override @@ -184,8 +191,8 @@ public void enterObjectDeclaration(final KotlinParser.ObjectDeclarationContext c } } - private void addObjectDelegationRelations(final ClassDataHandler classData, - final KotlinParser.ObjectDeclarationContext ctx) { + private void addObjectDelegationRelations( + final ClassDataHandler classData, final KotlinParser.ObjectDeclarationContext ctx) { if (ctx.delegationSpecifiers() == null) { return; } @@ -216,9 +223,8 @@ public void exitObjectDeclaration(final KotlinParser.ObjectDeclarationContext ct @Override public void enterCompanionObject(final KotlinParser.CompanionObjectContext ctx) { - final String companionName = ctx.simpleIdentifier() != null - ? ctx.simpleIdentifier().getText() - : "Companion"; + final String companionName = + ctx.simpleIdentifier() != null ? ctx.simpleIdentifier().getText() : "Companion"; final String fqn = fileDataHandler.buildFqn(companionName); fileDataHandler.enterClass(companionName, fqn); @@ -255,14 +261,15 @@ public void enterPropertyDeclaration(final KotlinParser.PropertyDeclarationConte } if (ctx.multiVariableDeclaration() != null) { - for (final KotlinParser.VariableDeclarationContext variableDecl : ctx.multiVariableDeclaration() - .variableDeclaration()) { + for (final KotlinParser.VariableDeclarationContext variableDecl : + ctx.multiVariableDeclaration().variableDeclaration()) { countVariableDeclaration(variableDecl); } } } - private void countVariableDeclaration(final KotlinParser.VariableDeclarationContext variableDecl) { + private void countVariableDeclaration( + final KotlinParser.VariableDeclarationContext variableDecl) { if (variableDecl.simpleIdentifier() != null) { variableCount++; } @@ -279,13 +286,14 @@ private void addPropertyField(final KotlinParser.PropertyDeclarationContext ctx) return; } - final String fieldType = ctx.type() != null - ? ctx.type().getText() - : ctx.variableDeclaration().type() != null - ? ctx.variableDeclaration().type().getText() - : "unknown"; - classData.addField(ctx.variableDeclaration().simpleIdentifier().getText(), fieldType, - new ArrayList<>()); + final String fieldType = + ctx.type() != null + ? ctx.type().getText() + : ctx.variableDeclaration().type() != null + ? ctx.variableDeclaration().type().getText() + : "unknown"; + classData.addField( + ctx.variableDeclaration().simpleIdentifier().getText(), fieldType, new ArrayList<>()); } @Override @@ -345,8 +353,9 @@ public void enterSecondaryConstructor(final KotlinParser.SecondaryConstructorCon } final String className = classData.getProtoBufObject().getName(); - final MethodDataHandler methodData = classData.addConstructor(className, - className + "#" + extractParameterTypes(ctx).hashCode()); + final MethodDataHandler methodData = + classData.addConstructor( + className, className + "#" + extractParameterTypes(ctx).hashCode()); addParameters(methodData, ctx.functionValueParameters()); if (ctx.start != null && ctx.stop != null) { @@ -370,15 +379,18 @@ private String extractFunctionName(final KotlinParser.FunctionDeclarationContext return null; } - private MethodDataHandler createMethodHandler(final String methodName, final String returnType, + private MethodDataHandler createMethodHandler( + final String methodName, + final String returnType, final KotlinParser.FunctionDeclarationContext ctx) { if (fileDataHandler.isInClassContext()) { final ClassDataHandler classData = fileDataHandler.getCurrentClassData(); if (classData == null) { return null; } - final MethodDataHandler methodData = classData.addMethod(methodName, - methodName + "#" + extractParameterTypes(ctx).hashCode(), returnType); + final MethodDataHandler methodData = + classData.addMethod( + methodName, methodName + "#" + extractParameterTypes(ctx).hashCode(), returnType); addParameters(methodData, ctx.functionValueParameters()); return methodData; } @@ -403,7 +415,8 @@ private List extractParameterTypes( return paramTypes; } - for (final KotlinParser.FunctionValueParameterContext param : parameters.functionValueParameter()) { + for (final KotlinParser.FunctionValueParameterContext param : + parameters.functionValueParameter()) { if (param.parameter() != null && param.parameter().type() != null) { paramTypes.add(param.parameter().type().getText()); } @@ -411,23 +424,25 @@ private List extractParameterTypes( return paramTypes; } - private void addParameters(final MethodDataHandler methodData, + private void addParameters( + final MethodDataHandler methodData, final KotlinParser.FunctionValueParametersContext parameters) { if (parameters == null) { return; } - for (final KotlinParser.FunctionValueParameterContext param : parameters.functionValueParameter()) { + for (final KotlinParser.FunctionValueParameterContext param : + parameters.functionValueParameter()) { if (param.parameter() == null) { continue; } - final String paramType = param.parameter().type() != null - ? param.parameter().type().getText() - : "unknown"; - final String paramName = param.parameter().simpleIdentifier() != null - ? param.parameter().simpleIdentifier().getText() - : ""; + final String paramType = + param.parameter().type() != null ? param.parameter().type().getText() : "unknown"; + final String paramName = + param.parameter().simpleIdentifier() != null + ? param.parameter().simpleIdentifier().getText() + : ""; methodData.addParameter(paramName, paramType, new ArrayList<>()); } } diff --git a/src/main/java/net/explorviz/code/analysis/listener/PhpFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/PhpFileDataListener.java index c68a335..034cff4 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/PhpFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/PhpFileDataListener.java @@ -12,9 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR listener for extracting file data from PHP source code. - */ +/** ANTLR listener for extracting file data from PHP source code. */ public class PhpFileDataListener extends PhpParserBaseListener implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(PhpFileDataListener.class); @@ -24,8 +22,8 @@ public class PhpFileDataListener extends PhpParserBaseListener implements Common private int functionCount = 0; private int variableCount = 0; - public PhpFileDataListener(final PhpFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public PhpFileDataListener( + final PhpFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -38,7 +36,8 @@ public void enterHtmlDocument(final PhpParser.HtmlDocumentContext ctx) { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -78,8 +77,8 @@ public void enterUseDeclaration(final PhpParser.UseDeclarationContext ctx) { return; } - for (final PhpParser.UseDeclarationContentContext useContent : ctx.useDeclarationContentList() - .useDeclarationContent()) { + for (final PhpParser.UseDeclarationContentContext useContent : + ctx.useDeclarationContentList().useDeclarationContent()) { if (useContent.namespaceNameList() != null) { fileDataHandler.addImport(normalizeNamespace(useContent.namespaceNameList().getText())); } @@ -118,27 +117,34 @@ public void enterClassDeclaration(final PhpParser.ClassDeclarationContext ctx) { } } - private void addInterfaceExtends(final ClassDataHandler classData, - final PhpParser.InterfaceListContext interfaceList) { + private void addInterfaceExtends( + final ClassDataHandler classData, final PhpParser.InterfaceListContext interfaceList) { if (interfaceList == null) { return; } - for (final PhpParser.QualifiedStaticTypeRefContext typeRef : interfaceList - .qualifiedStaticTypeRef()) { + for (final PhpParser.QualifiedStaticTypeRefContext typeRef : + interfaceList.qualifiedStaticTypeRef()) { classData.addImplementedInterface(normalizeNamespace(typeRef.getText())); } } - private void addInterfaceImplements(final ClassDataHandler classData, - final PhpParser.InterfaceListContext interfaceList) { + private void addInterfaceImplements( + final ClassDataHandler classData, final PhpParser.InterfaceListContext interfaceList) { addInterfaceExtends(classData, interfaceList); } - private void addFormattedSuperClass(final ClassDataHandler classData, final String superClassFqn) { + private void addFormattedSuperClass( + final ClassDataHandler classData, final String superClassFqn) { final String normalizedFqn = normalizeNamespace(superClassFqn); - classData.setSuperClass(getClassPathFromFqn(normalizedFqn, ".php", fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(normalizedFqn)); + classData.setSuperClass( + getClassPathFromFqn( + normalizedFqn, + ".php", + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(normalizedFqn)); } @Override @@ -263,8 +269,8 @@ private void addPropertyFields(final PhpParser.ClassStatementContext ctx) { final String fieldType = ctx.typeHint() != null ? ctx.typeHint().getText() : "mixed"; for (final PhpParser.VariableInitializerContext variable : ctx.variableInitializer()) { if (variable.VarName() != null) { - classData.addField(stripVariableName(variable.VarName().getText()), fieldType, - new ArrayList<>()); + classData.addField( + stripVariableName(variable.VarName().getText()), fieldType, new ArrayList<>()); } } } @@ -283,7 +289,9 @@ public void enterGlobalConstantDeclaration(final PhpParser.GlobalConstantDeclara } } - private MethodDataHandler createMethodHandler(final String methodName, final String returnType, + private MethodDataHandler createMethodHandler( + final String methodName, + final String returnType, final PhpParser.FunctionDeclarationContext ctx) { if (fileDataHandler.isInClassContext()) { return createClassMethodFromParameters(methodName, returnType, ctx.formalParameterList()); @@ -294,20 +302,25 @@ private MethodDataHandler createMethodHandler(final String methodName, final Str return methodData; } - private MethodDataHandler createClassMethodHandler(final String methodName, - final String returnType, final PhpParser.ClassStatementContext ctx) { + private MethodDataHandler createClassMethodHandler( + final String methodName, final String returnType, final PhpParser.ClassStatementContext ctx) { return createClassMethodFromParameters(methodName, returnType, ctx.formalParameterList()); } - private MethodDataHandler createClassMethodFromParameters(final String methodName, - final String returnType, final PhpParser.FormalParameterListContext parameterList) { + private MethodDataHandler createClassMethodFromParameters( + final String methodName, + final String returnType, + final PhpParser.FormalParameterListContext parameterList) { final ClassDataHandler classData = fileDataHandler.getCurrentClassData(); if (classData == null) { return null; } - final MethodDataHandler methodData = classData.addMethod(methodName, - methodName + "#" + extractParameterTypes(parameterList).hashCode(), returnType); + final MethodDataHandler methodData = + classData.addMethod( + methodName, + methodName + "#" + extractParameterTypes(parameterList).hashCode(), + returnType); addParameters(methodData, parameterList); return methodData; } @@ -326,7 +339,8 @@ private String extractReturnType(final PhpParser.ReturnTypeDeclContext returnTyp return returnTypeDecl.typeHint().getText(); } - private List extractParameterTypes(final PhpParser.FormalParameterListContext parameterList) { + private List extractParameterTypes( + final PhpParser.FormalParameterListContext parameterList) { final List paramTypes = new ArrayList<>(); if (parameterList == null) { return paramTypes; @@ -340,7 +354,8 @@ private List extractParameterTypes(final PhpParser.FormalParameterListCo return paramTypes; } - private void addParameters(final MethodDataHandler methodData, + private void addParameters( + final MethodDataHandler methodData, final PhpParser.FormalParameterListContext parameterList) { if (parameterList == null) { return; diff --git a/src/main/java/net/explorviz/code/analysis/listener/PythonFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/PythonFileDataListener.java index af3a344..3f88e8a 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/PythonFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/PythonFileDataListener.java @@ -9,10 +9,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR Listener for extracting file data from Python source code. - */ -public class PythonFileDataListener extends PythonParserBaseListener implements CommonFileDataListener { +/** ANTLR Listener for extracting file data from Python source code. */ +public class PythonFileDataListener extends PythonParserBaseListener + implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(PythonFileDataListener.class); @@ -21,8 +20,8 @@ public class PythonFileDataListener extends PythonParserBaseListener implements private int functionCount = 0; private int variableCount = 0; - public PythonFileDataListener(final PythonFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public PythonFileDataListener( + final PythonFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -36,7 +35,8 @@ public void enterFile_input(final PythonParser.File_inputContext ctx) { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -55,9 +55,7 @@ public void enterImport_stmt(final PythonParser.Import_stmtContext ctx) { if (ctx.getText() != null) { final String importText = ctx.getText(); fileDataHandler.addImport(importText); - LOGGER.atTrace() - .addArgument(importText) - .log("Import: {}"); + LOGGER.atTrace().addArgument(importText).log("Import: {}"); } } @@ -70,9 +68,7 @@ public void enterClassdef(final PythonParser.ClassdefContext ctx) { fileDataHandler.enterClass(className, classFqn); - LOGGER.atTrace() - .addArgument(className) - .log("Class: {}"); + LOGGER.atTrace().addArgument(className).log("Class: {}"); // Calculate class SLOC and LOC final int classLoc = calculateLoc(ctx); @@ -84,11 +80,19 @@ public void enterClassdef(final PythonParser.ClassdefContext ctx) { // Extract superclasses if (ctx.arglist() != null) { for (final PythonParser.ArgumentContext argCtx : ctx.arglist().argument()) { - if (argCtx.ASSIGN() == null && argCtx.comp_for() == null && argCtx.POWER() == null + if (argCtx.ASSIGN() == null + && argCtx.comp_for() == null + && argCtx.POWER() == null && argCtx.STAR() == null) { final String superClassFqn = argCtx.getText(); - classData.setSuperClass(getClassPathFromFqn(superClassFqn, ".py", fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(superClassFqn)); + classData.setSuperClass( + getClassPathFromFqn( + superClassFqn, + ".py", + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(superClassFqn)); } } } @@ -117,12 +121,12 @@ public void enterFuncdef(final PythonParser.FuncdefContext ctx) { // Function inside a class - treat as a method final String functionFqn = functionName + "#1"; // TODO: Add proper parameter hashing - final var methodData = fileDataHandler.getCurrentClassData() - .addMethod(functionName, functionFqn, "None"); // Python default return is None + final var methodData = + fileDataHandler + .getCurrentClassData() + .addMethod(functionName, functionFqn, "None"); // Python default return is None - LOGGER.atTrace() - .addArgument(functionName) - .log("Method: {}"); + LOGGER.atTrace().addArgument(functionName).log("Method: {}"); // Calculate function SLOC and LOC final int functionLoc = calculateLoc(ctx); @@ -138,10 +142,10 @@ public void enterFuncdef(final PythonParser.FuncdefContext ctx) { } } else { // Global function - final var funcBuilder = fileDataHandler.addGlobalFunction( - functionName, - "None" // TODO: Extract actual return type from type hints - ); + final var funcBuilder = + fileDataHandler.addGlobalFunction( + functionName, "None" // TODO: Extract actual return type from type hints + ); if (ctx.ASYNC() != null) { funcBuilder.addModifier("async"); @@ -191,9 +195,7 @@ public void enterFuncdef(final PythonParser.FuncdefContext ctx) { funcBuilder.addMetric(SLOC, String.valueOf(getSloc(ctx, tokens))); funcBuilder.addMetric(LINE_COUNT, String.valueOf(functionLoc)); - LOGGER.atTrace() - .addArgument(functionName) - .log("Global function: {}"); + LOGGER.atTrace().addArgument(functionName).log("Global function: {}"); } } diff --git a/src/main/java/net/explorviz/code/analysis/listener/RustFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/RustFileDataListener.java index d19c802..f6d8a04 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/RustFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/RustFileDataListener.java @@ -11,9 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR listener for extracting file data from Rust source code. - */ +/** ANTLR listener for extracting file data from Rust source code. */ public class RustFileDataListener extends RustParserBaseListener implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(RustFileDataListener.class); @@ -24,8 +22,8 @@ public class RustFileDataListener extends RustParserBaseListener implements Comm private int variableCount = 0; private String currentImplType; - public RustFileDataListener(final RustFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public RustFileDataListener( + final RustFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -38,7 +36,8 @@ public void enterCrate(final RustParser.CrateContext ctx) { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -289,14 +288,17 @@ public void enterStaticItem(final RustParser.StaticItemContext ctx) { } } - private MethodDataHandler createFunctionHandler(final String functionName, final String returnType, - final RustParser.Function_Context ctx) { + private MethodDataHandler createFunctionHandler( + final String functionName, final String returnType, final RustParser.Function_Context ctx) { if (currentImplType != null) { final ClassDataHandler classData = fileDataHandler.getClassData(fileDataHandler.buildFqn(currentImplType)); if (classData != null) { - final MethodDataHandler methodData = classData.addMethod(functionName, - functionName + "#" + extractParameterTypes(ctx).hashCode(), returnType); + final MethodDataHandler methodData = + classData.addMethod( + functionName, + functionName + "#" + extractParameterTypes(ctx).hashCode(), + returnType); addFunctionParameters(methodData, ctx); return methodData; } @@ -305,14 +307,18 @@ private MethodDataHandler createFunctionHandler(final String functionName, final if (fileDataHandler.isInClassContext()) { final ClassDataHandler classData = fileDataHandler.getCurrentClassData(); if (classData != null) { - final MethodDataHandler methodData = classData.addMethod(functionName, - functionName + "#" + extractParameterTypes(ctx).hashCode(), returnType); + final MethodDataHandler methodData = + classData.addMethod( + functionName, + functionName + "#" + extractParameterTypes(ctx).hashCode(), + returnType); addFunctionParameters(methodData, ctx); return methodData; } } - final MethodDataHandler methodData = fileDataHandler.addGlobalFunction(functionName, returnType); + final MethodDataHandler methodData = + fileDataHandler.addGlobalFunction(functionName, returnType); addFunctionParameters(methodData, ctx); return methodData; } @@ -341,8 +347,8 @@ private List extractParameterTypes(final RustParser.Function_Context ctx return paramTypes; } - private void addFunctionParameters(final MethodDataHandler methodData, - final RustParser.Function_Context ctx) { + private void addFunctionParameters( + final MethodDataHandler methodData, final RustParser.Function_Context ctx) { if (ctx.functionParameters() == null) { return; } diff --git a/src/main/java/net/explorviz/code/analysis/listener/SwiftFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/SwiftFileDataListener.java index af607f7..9a00ebc 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/SwiftFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/SwiftFileDataListener.java @@ -11,10 +11,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR listener for extracting file data from Swift source code. - */ -public class SwiftFileDataListener extends Swift5ParserBaseListener implements CommonFileDataListener { +/** ANTLR listener for extracting file data from Swift source code. */ +public class SwiftFileDataListener extends Swift5ParserBaseListener + implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(SwiftFileDataListener.class); @@ -23,8 +22,8 @@ public class SwiftFileDataListener extends Swift5ParserBaseListener implements C private int functionCount = 0; private int variableCount = 0; - public SwiftFileDataListener(final SwiftFileDataHandler fileDataHandler, - final CommonTokenStream tokens) { + public SwiftFileDataListener( + final SwiftFileDataHandler fileDataHandler, final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.tokens = tokens; } @@ -37,7 +36,8 @@ public void enterTop_level(final Swift5Parser.Top_levelContext ctx) { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -64,8 +64,8 @@ public void enterClass_declaration(final Swift5Parser.Class_declarationContext c } enterType(ctx.class_name().identifier().getText(), ctx, false, false); - addInheritanceRelations(fileDataHandler.getCurrentClassData(), ctx.type_inheritance_clause(), - false); + addInheritanceRelations( + fileDataHandler.getCurrentClassData(), ctx.type_inheritance_clause(), false); } @Override @@ -82,8 +82,8 @@ public void enterStruct_declaration(final Swift5Parser.Struct_declarationContext } enterType(ctx.struct_name().identifier().getText(), ctx, true, false); - addInheritanceRelations(fileDataHandler.getCurrentClassData(), ctx.type_inheritance_clause(), - true); + addInheritanceRelations( + fileDataHandler.getCurrentClassData(), ctx.type_inheritance_clause(), true); } @Override @@ -100,8 +100,8 @@ public void enterProtocol_declaration(final Swift5Parser.Protocol_declarationCon } enterType(ctx.protocol_name().identifier().getText(), ctx, false, true); - addInheritanceRelations(fileDataHandler.getCurrentClassData(), ctx.type_inheritance_clause(), - true); + addInheritanceRelations( + fileDataHandler.getCurrentClassData(), ctx.type_inheritance_clause(), true); } @Override @@ -153,8 +153,11 @@ public void exitRaw_value_style_enum(final Swift5Parser.Raw_value_style_enumCont } } - private void enterType(final String typeName, final org.antlr.v4.runtime.ParserRuleContext ctx, - final boolean isStruct, final boolean isInterface) { + private void enterType( + final String typeName, + final org.antlr.v4.runtime.ParserRuleContext ctx, + final boolean isStruct, + final boolean isInterface) { final String fqn = fileDataHandler.buildFqn(typeName); fileDataHandler.enterClass(typeName, fqn); @@ -175,7 +178,8 @@ private void enterType(final String typeName, final org.antlr.v4.runtime.ParserR classData.addMetric(LINE_COUNT, String.valueOf(calculateLoc(ctx))); } - private void addInheritanceRelations(final ClassDataHandler classData, + private void addInheritanceRelations( + final ClassDataHandler classData, final Swift5Parser.Type_inheritance_clauseContext inheritance, final boolean treatAllAsInterfaces) { if (classData == null || inheritance == null || inheritance.type_inheritance_list() == null) { @@ -195,9 +199,16 @@ private void addInheritanceRelations(final ClassDataHandler classData, } } - private void addFormattedSuperClass(final ClassDataHandler classData, final String superClassFqn) { - classData.setSuperClass(getClassPathFromFqn(superClassFqn, ".swift", fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(superClassFqn)); + private void addFormattedSuperClass( + final ClassDataHandler classData, final String superClassFqn) { + classData.setSuperClass( + getClassPathFromFqn( + superClassFqn, + ".swift", + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(superClassFqn)); } @Override @@ -206,7 +217,8 @@ public void enterUnion_style_enum_case(final Swift5Parser.Union_style_enum_caseC } @Override - public void enterRaw_value_style_enum_case(final Swift5Parser.Raw_value_style_enum_caseContext ctx) { + public void enterRaw_value_style_enum_case( + final Swift5Parser.Raw_value_style_enum_caseContext ctx) { addEnumCase(ctx.enum_case_name()); } @@ -229,8 +241,8 @@ public void enterVariable_declaration(final Swift5Parser.Variable_declarationCon addPropertyField(ctx.variable_name().identifier().getText(), ctx.type_annotation()); } } else if (ctx.pattern_initializer_list() != null) { - for (final Swift5Parser.Pattern_initializerContext patternInit : ctx.pattern_initializer_list() - .pattern_initializer()) { + for (final Swift5Parser.Pattern_initializerContext patternInit : + ctx.pattern_initializer_list().pattern_initializer()) { countPattern(patternInit.pattern()); if (fileDataHandler.isInClassContext()) { addFieldFromPattern(patternInit.pattern(), null); @@ -245,8 +257,8 @@ public void enterConstant_declaration(final Swift5Parser.Constant_declarationCon return; } - for (final Swift5Parser.Pattern_initializerContext patternInit : ctx.pattern_initializer_list() - .pattern_initializer()) { + for (final Swift5Parser.Pattern_initializerContext patternInit : + ctx.pattern_initializer_list().pattern_initializer()) { countPattern(patternInit.pattern()); if (fileDataHandler.isInClassContext()) { addFieldFromPattern(patternInit.pattern(), null); @@ -264,30 +276,34 @@ private void countPattern(final Swift5Parser.PatternContext pattern) { return; } - if (pattern.tuple_pattern() != null && pattern.tuple_pattern().tuple_pattern_element_list() != null) { - for (final Swift5Parser.Tuple_pattern_elementContext element : pattern.tuple_pattern() - .tuple_pattern_element_list().tuple_pattern_element()) { + if (pattern.tuple_pattern() != null + && pattern.tuple_pattern().tuple_pattern_element_list() != null) { + for (final Swift5Parser.Tuple_pattern_elementContext element : + pattern.tuple_pattern().tuple_pattern_element_list().tuple_pattern_element()) { countPattern(element.pattern()); } } } - private void addPropertyField(final String fieldName, - final Swift5Parser.Type_annotationContext typeAnnotation) { + private void addPropertyField( + final String fieldName, final Swift5Parser.Type_annotationContext typeAnnotation) { final ClassDataHandler classData = fileDataHandler.getCurrentClassData(); if (classData == null) { return; } - final String fieldType = typeAnnotation != null && typeAnnotation.type() != null - ? typeAnnotation.type().getText() - : "unknown"; + final String fieldType = + typeAnnotation != null && typeAnnotation.type() != null + ? typeAnnotation.type().getText() + : "unknown"; classData.addField(fieldName, fieldType, new ArrayList<>()); } - private void addFieldFromPattern(final Swift5Parser.PatternContext pattern, + private void addFieldFromPattern( + final Swift5Parser.PatternContext pattern, final Swift5Parser.Type_annotationContext typeAnnotation) { - if (pattern == null || pattern.identifier_pattern() == null + if (pattern == null + || pattern.identifier_pattern() == null || pattern.identifier_pattern().identifier() == null) { return; } @@ -327,8 +343,9 @@ public void enterInitializer_declaration(final Swift5Parser.Initializer_declarat } final String className = classData.getProtoBufObject().getName(); - final MethodDataHandler methodData = classData.addConstructor(className, - className + "#" + extractParameterTypes(ctx.parameter_clause()).hashCode()); + final MethodDataHandler methodData = + classData.addConstructor( + className, className + "#" + extractParameterTypes(ctx.parameter_clause()).hashCode()); addParameters(methodData, ctx.parameter_clause()); if (ctx.start != null && ctx.stop != null) { @@ -356,24 +373,30 @@ private String extractFunctionName(final Swift5Parser.Function_declarationContex } private String extractReturnType(final Swift5Parser.Function_declarationContext ctx) { - if (ctx.function_signature() != null && ctx.function_signature().function_result() != null + if (ctx.function_signature() != null + && ctx.function_signature().function_result() != null && ctx.function_signature().function_result().type() != null) { return ctx.function_signature().function_result().type().getText(); } return "Void"; } - private MethodDataHandler createMethodHandler(final String methodName, final String returnType, + private MethodDataHandler createMethodHandler( + final String methodName, + final String returnType, final Swift5Parser.Function_declarationContext ctx) { if (fileDataHandler.isInClassContext()) { final ClassDataHandler classData = fileDataHandler.getCurrentClassData(); if (classData == null) { return null; } - final MethodDataHandler methodData = classData.addMethod(methodName, - methodName + "#" + extractParameterTypes(ctx.function_signature().parameter_clause()) - .hashCode(), - returnType); + final MethodDataHandler methodData = + classData.addMethod( + methodName, + methodName + + "#" + + extractParameterTypes(ctx.function_signature().parameter_clause()).hashCode(), + returnType); addParameters(methodData, ctx.function_signature().parameter_clause()); return methodData; } @@ -385,7 +408,8 @@ private MethodDataHandler createMethodHandler(final String methodName, final Str return methodData; } - private List extractParameterTypes(final Swift5Parser.Parameter_clauseContext parameters) { + private List extractParameterTypes( + final Swift5Parser.Parameter_clauseContext parameters) { final List paramTypes = new ArrayList<>(); if (parameters == null || parameters.parameter_list() == null) { return paramTypes; @@ -399,18 +423,20 @@ private List extractParameterTypes(final Swift5Parser.Parameter_clauseCo return paramTypes; } - private void addParameters(final MethodDataHandler methodData, - final Swift5Parser.Parameter_clauseContext parameters) { + private void addParameters( + final MethodDataHandler methodData, final Swift5Parser.Parameter_clauseContext parameters) { if (parameters == null || parameters.parameter_list() == null) { return; } for (final Swift5Parser.ParameterContext param : parameters.parameter_list().parameter()) { - final String paramType = param.type_annotation() != null && param.type_annotation().type() != null - ? param.type_annotation().type().getText() - : "unknown"; + final String paramType = + param.type_annotation() != null && param.type_annotation().type() != null + ? param.type_annotation().type().getText() + : "unknown"; String paramName = ""; - if (param.local_parameter_name() != null && param.local_parameter_name().identifier() != null) { + if (param.local_parameter_name() != null + && param.local_parameter_name().identifier() != null) { paramName = param.local_parameter_name().identifier().getText(); } methodData.addParameter(paramName, paramType, new ArrayList<>()); diff --git a/src/main/java/net/explorviz/code/analysis/listener/TypeScriptFileDataListener.java b/src/main/java/net/explorviz/code/analysis/listener/TypeScriptFileDataListener.java index d8ecfc0..97e668e 100644 --- a/src/main/java/net/explorviz/code/analysis/listener/TypeScriptFileDataListener.java +++ b/src/main/java/net/explorviz/code/analysis/listener/TypeScriptFileDataListener.java @@ -11,11 +11,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR Listener for extracting file data from TypeScript/JavaScript source - * code. - */ -public class TypeScriptFileDataListener extends TypeScriptParserBaseListener implements CommonFileDataListener { +/** ANTLR Listener for extracting file data from TypeScript/JavaScript source code. */ +public class TypeScriptFileDataListener extends TypeScriptParserBaseListener + implements CommonFileDataListener { private static final Logger LOGGER = LoggerFactory.getLogger(TypeScriptFileDataListener.class); private static final String DEFAULT_RETURN_TYPE = "void"; @@ -27,8 +25,10 @@ public class TypeScriptFileDataListener extends TypeScriptParserBaseListener imp private int functionCount = 0; private int variableCount = 0; - public TypeScriptFileDataListener(final TypeScriptFileDataHandler fileDataHandler, - final String fileExtension, final CommonTokenStream tokens) { + public TypeScriptFileDataListener( + final TypeScriptFileDataHandler fileDataHandler, + final String fileExtension, + final CommonTokenStream tokens) { this.fileDataHandler = fileDataHandler; this.fileExtension = fileExtension; this.tokens = tokens; @@ -42,7 +42,8 @@ public void enterProgram(final TypeScriptParser.ProgramContext ctx) { fileDataHandler.addMetric(SLOC, String.valueOf(sloc)); fileDataHandler.addMetric(CLOC, String.valueOf(cloc)); - LOGGER.atTrace() + LOGGER + .atTrace() .addArgument(fileDataHandler.getFileName()) .addArgument(sloc) .log("{} - SLOC: {}"); @@ -60,9 +61,7 @@ public void enterImportStatement(final TypeScriptParser.ImportStatementContext c if (ctx.getText() != null) { final String importText = ctx.getText(); fileDataHandler.addImport(importText); - LOGGER.atTrace() - .addArgument(importText) - .log("Import: {}"); + LOGGER.atTrace().addArgument(importText).log("Import: {}"); } } @@ -71,9 +70,7 @@ public void enterNamespaceDeclaration(final TypeScriptParser.NamespaceDeclaratio if (ctx.namespaceName() != null) { final String namespaceName = ctx.namespaceName().getText(); fileDataHandler.enterNamespace(namespaceName); - LOGGER.atTrace() - .addArgument(namespaceName) - .log("Namespace: {}"); + LOGGER.atTrace().addArgument(namespaceName).log("Namespace: {}"); } } @@ -90,9 +87,7 @@ public void enterClassDeclaration(final TypeScriptParser.ClassDeclarationContext fileDataHandler.enterClass(className, fqn); - LOGGER.atTrace() - .addArgument(className) - .log("Class: {}"); + LOGGER.atTrace().addArgument(className).log("Class: {}"); final int classLoc = calculateLoc(ctx); final var classData = fileDataHandler.getCurrentClassData(); @@ -107,15 +102,23 @@ public void enterClassDeclaration(final TypeScriptParser.ClassDeclarationContext classData.addMetric(LINE_COUNT, String.valueOf(classLoc)); if (ctx.classHeritage() != null && ctx.classHeritage().classExtendsClause() != null) { - final String superClassFqn = ctx.classHeritage().classExtendsClause().typeReference().getText(); - classData.setSuperClass(getClassPathFromFqn(superClassFqn, fileExtension, fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(superClassFqn)); + final String superClassFqn = + ctx.classHeritage().classExtendsClause().typeReference().getText(); + classData.setSuperClass( + getClassPathFromFqn( + superClassFqn, + fileExtension, + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(superClassFqn)); } - if (ctx.classHeritage() != null && ctx.classHeritage().implementsClause() != null + if (ctx.classHeritage() != null + && ctx.classHeritage().implementsClause() != null && ctx.classHeritage().implementsClause().classOrInterfaceTypeList() != null) { - for (final TypeScriptParser.TypeReferenceContext typeRef : ctx.classHeritage() - .implementsClause().classOrInterfaceTypeList().typeReference()) { + for (final TypeScriptParser.TypeReferenceContext typeRef : + ctx.classHeritage().implementsClause().classOrInterfaceTypeList().typeReference()) { classData.addImplementedInterface(typeRef.getText()); } } @@ -144,19 +147,22 @@ public void enterInterfaceDeclaration(final TypeScriptParser.InterfaceDeclaratio classData.addMetric(LINE_COUNT, String.valueOf(interfaceLoc)); if (ctx.interfaceExtendsClause() != null) { - for (final TypeScriptParser.TypeReferenceContext typeRef : ctx.interfaceExtendsClause() - .classOrInterfaceTypeList().typeReference()) { + for (final TypeScriptParser.TypeReferenceContext typeRef : + ctx.interfaceExtendsClause().classOrInterfaceTypeList().typeReference()) { final String superClassFqn = typeRef.getText(); classData.addImplementedInterface( - getClassPathFromFqn(superClassFqn, fileExtension, fileDataHandler.getFileName(), - fileDataHandler.getPackageName()) + "::" + getClassNameFromFqn(superClassFqn)); + getClassPathFromFqn( + superClassFqn, + fileExtension, + fileDataHandler.getFileName(), + fileDataHandler.getPackageName()) + + "::" + + getClassNameFromFqn(superClassFqn)); } } } - LOGGER.atTrace() - .addArgument(interfaceName) - .log("Interface: {}"); + LOGGER.atTrace().addArgument(interfaceName).log("Interface: {}"); } } @@ -179,9 +185,10 @@ public void enterPropertyDeclarationExpression( } final String fieldName = ctx.classElementName().getText(); - final String fieldType = ctx.typeAnnotation() != null && ctx.typeAnnotation().type_() != null - ? ctx.typeAnnotation().type_().getText() - : UNTYPED_PARAMETER; + final String fieldType = + ctx.typeAnnotation() != null && ctx.typeAnnotation().type_() != null + ? ctx.typeAnnotation().type_().getText() + : UNTYPED_PARAMETER; final List modifiers = new ArrayList<>(); if (ctx.propertyMemberBase() != null) { @@ -199,9 +206,7 @@ public void enterPropertyDeclarationExpression( classData.addField(fieldName, fieldType, modifiers); variableCount++; - LOGGER.atTrace() - .addArgument(fieldName) - .log("Class field: {}"); + LOGGER.atTrace().addArgument(fieldName).log("Class field: {}"); } @Override @@ -224,9 +229,7 @@ public void enterEnumDeclaration(final TypeScriptParser.EnumDeclarationContext c classData.addMetric(LINE_COUNT, String.valueOf(calculateLoc(ctx))); } - LOGGER.atTrace() - .addArgument(enumName) - .log("Enum: {}"); + LOGGER.atTrace().addArgument(enumName).log("Enum: {}"); } @Override @@ -280,7 +283,8 @@ public void enterMethodDeclarationExpression( functionCount++; final String methodName = ctx.classElementName().getText(); final List parameters = extractParameters(ctx.callSignature()); - final String methodFqn = fileDataHandler.buildMethodFqn(methodName, extractParameterTypes(parameters)); + final String methodFqn = + fileDataHandler.buildMethodFqn(methodName, extractParameterTypes(parameters)); final String returnType = extractReturnType(ctx.callSignature()); final boolean async = isAsync(ctx.propertyMemberBase()); @@ -289,9 +293,7 @@ public void enterMethodDeclarationExpression( final MethodDataHandler methodData = classData.addMethod(methodName, methodFqn, returnType); populateMethod(methodData, ctx, parameters, async); - LOGGER.atTrace() - .addArgument(methodName) - .log("Class method: {}"); + LOGGER.atTrace().addArgument(methodName).log("Class method: {}"); } } @@ -306,12 +308,12 @@ public void enterConstructorDeclaration( final var classData = fileDataHandler.getCurrentClassData(); if (classData != null) { final List parameters = extractParameters(ctx.formalParameterList()); - final String constructorFqn = fileDataHandler.buildMethodFqn("constructor", extractParameterTypes(parameters)); + final String constructorFqn = + fileDataHandler.buildMethodFqn("constructor", extractParameterTypes(parameters)); final MethodDataHandler methodData = classData.addConstructor("constructor", constructorFqn); populateMethod(methodData, ctx, parameters, false); - LOGGER.atTrace() - .log("Constructor detected"); + LOGGER.atTrace().log("Constructor detected"); } } @@ -328,21 +330,19 @@ public void enterFunctionDeclaration(final TypeScriptParser.FunctionDeclarationC final boolean async = ctx.Async() != null; if (fileDataHandler.isInClassContext()) { - final String functionFqn = fileDataHandler.buildMethodFqn(functionName, extractParameterTypes(parameters)); - final var methodData = fileDataHandler.getCurrentClassData() - .addMethod(functionName, functionFqn, returnType); + final String functionFqn = + fileDataHandler.buildMethodFqn(functionName, extractParameterTypes(parameters)); + final var methodData = + fileDataHandler.getCurrentClassData().addMethod(functionName, functionFqn, returnType); populateMethod(methodData, ctx, parameters, async); - LOGGER.atTrace() - .addArgument(functionName) - .log("Function inside class: {}"); + LOGGER.atTrace().addArgument(functionName).log("Function inside class: {}"); } else { - final MethodDataHandler methodHandler = fileDataHandler.addGlobalFunction(functionName, returnType); + final MethodDataHandler methodHandler = + fileDataHandler.addGlobalFunction(functionName, returnType); populateMethod(methodHandler, ctx, parameters, async); - LOGGER.atTrace() - .addArgument(functionName) - .log("Global function: {}"); + LOGGER.atTrace().addArgument(functionName).log("Global function: {}"); } } @@ -366,21 +366,19 @@ public void enterArrowFunctionDeclaration( final boolean async = ctx.Async() != null; if (fileDataHandler.isInClassContext()) { - final String functionFqn = fileDataHandler.buildMethodFqn(functionName, extractParameterTypes(parameters)); - final var methodData = fileDataHandler.getCurrentClassData() - .addMethod(functionName, functionFqn, returnType); + final String functionFqn = + fileDataHandler.buildMethodFqn(functionName, extractParameterTypes(parameters)); + final var methodData = + fileDataHandler.getCurrentClassData().addMethod(functionName, functionFqn, returnType); populateMethod(methodData, ctx, parameters, async); - LOGGER.atTrace() - .addArgument(functionName) - .log("Arrow function inside class: {}"); + LOGGER.atTrace().addArgument(functionName).log("Arrow function inside class: {}"); } else { - final MethodDataHandler methodHandler = fileDataHandler.addGlobalFunction(functionName, returnType); + final MethodDataHandler methodHandler = + fileDataHandler.addGlobalFunction(functionName, returnType); populateMethod(methodHandler, ctx, parameters, async); - LOGGER.atTrace() - .addArgument(functionName) - .log("Global arrow function: {}"); + LOGGER.atTrace().addArgument(functionName).log("Global arrow function: {}"); } } @@ -389,8 +387,11 @@ public void enterVariableDeclaration(final TypeScriptParser.VariableDeclarationC variableCount++; } - private void populateMethod(final MethodDataHandler methodData, final ParserRuleContext ctx, - final List parameters, final boolean async) { + private void populateMethod( + final MethodDataHandler methodData, + final ParserRuleContext ctx, + final List parameters, + final boolean async) { for (final ParameterInfo parameter : parameters) { methodData.addParameter(parameter.name(), parameter.type(), List.of()); } @@ -405,7 +406,8 @@ private void populateMethod(final MethodDataHandler methodData, final ParserRule methodData.addMetric(LINE_COUNT, String.valueOf(methodLoc)); } - private List extractParameters(final TypeScriptParser.CallSignatureContext callSignature) { + private List extractParameters( + final TypeScriptParser.CallSignatureContext callSignature) { if (callSignature == null || callSignature.parameterList() == null) { return List.of(); } @@ -421,7 +423,8 @@ private List extractParameters( return extractParameters(arrowParameters.formalParameterList()); } if (arrowParameters.propertyName() != null) { - return List.of(new ParameterInfo(arrowParameters.propertyName().getText(), UNTYPED_PARAMETER)); + return List.of( + new ParameterInfo(arrowParameters.propertyName().getText(), UNTYPED_PARAMETER)); } return List.of(); } @@ -433,21 +436,28 @@ private List extractParameters( return parameters; } - for (final TypeScriptParser.FormalParameterArgContext arg : formalParameterList.formalParameterArg()) { - parameters.add(new ParameterInfo(extractAssignableName(arg.assignable()), - extractTypeFromAnnotation(arg.typeAnnotation()))); + for (final TypeScriptParser.FormalParameterArgContext arg : + formalParameterList.formalParameterArg()) { + parameters.add( + new ParameterInfo( + extractAssignableName(arg.assignable()), + extractTypeFromAnnotation(arg.typeAnnotation()))); } if (formalParameterList.lastFormalParameterArg() != null) { - final TypeScriptParser.LastFormalParameterArgContext restArg = formalParameterList.lastFormalParameterArg(); - final String restName = restArg.identifier() != null ? restArg.identifier().getText() : "rest"; - parameters.add(new ParameterInfo(restName, extractTypeFromAnnotation(restArg.typeAnnotation(), "..."))); + final TypeScriptParser.LastFormalParameterArgContext restArg = + formalParameterList.lastFormalParameterArg(); + final String restName = + restArg.identifier() != null ? restArg.identifier().getText() : "rest"; + parameters.add( + new ParameterInfo(restName, extractTypeFromAnnotation(restArg.typeAnnotation(), "..."))); } return parameters; } - private List extractParameters(final TypeScriptParser.ParameterListContext parameterList) { + private List extractParameters( + final TypeScriptParser.ParameterListContext parameterList) { final List parameters = new ArrayList<>(); if (parameterList == null) { return parameters; @@ -455,7 +465,8 @@ private List extractParameters(final TypeScriptParser.ParameterLi if (parameterList.restParameter() != null) { final TypeScriptParser.RestParameterContext rest = parameterList.restParameter(); - parameters.add(new ParameterInfo("rest", extractTypeFromAnnotation(rest.typeAnnotation(), "..."))); + parameters.add( + new ParameterInfo("rest", extractTypeFromAnnotation(rest.typeAnnotation(), "..."))); return parameters; } @@ -472,14 +483,17 @@ private List extractParameters(final TypeScriptParser.ParameterLi private ParameterInfo extractRequiredParameter( final TypeScriptParser.RequiredParameterContext requiredParameter) { - return new ParameterInfo(extractIdentifierOrPatternName(requiredParameter.identifierOrPattern()), + return new ParameterInfo( + extractIdentifierOrPatternName(requiredParameter.identifierOrPattern()), extractTypeFromAnnotation(requiredParameter.typeAnnotation())); } private ParameterInfo extractOptionalParameter( final TypeScriptParser.OptionalParameterContext optionalParameter) { - final TypeScriptParser.IdentifierOrPatternContext pattern = optionalParameter.identifierOrPattern(); - return new ParameterInfo(extractIdentifierOrPatternName(pattern), + final TypeScriptParser.IdentifierOrPatternContext pattern = + optionalParameter.identifierOrPattern(); + return new ParameterInfo( + extractIdentifierOrPatternName(pattern), extractTypeFromAnnotation(optionalParameter.typeAnnotation())); } @@ -501,12 +515,13 @@ private String extractReturnType(final TypeScriptParser.TypeAnnotationContext ty return normalizeTypeName(typeAnnotation.type_().getText()); } - private String extractTypeFromAnnotation(final TypeScriptParser.TypeAnnotationContext typeAnnotation) { + private String extractTypeFromAnnotation( + final TypeScriptParser.TypeAnnotationContext typeAnnotation) { return extractTypeFromAnnotation(typeAnnotation, UNTYPED_PARAMETER); } - private String extractTypeFromAnnotation(final TypeScriptParser.TypeAnnotationContext typeAnnotation, - final String defaultType) { + private String extractTypeFromAnnotation( + final TypeScriptParser.TypeAnnotationContext typeAnnotation, final String defaultType) { if (typeAnnotation == null || typeAnnotation.type_() == null) { return defaultType; } @@ -555,9 +570,7 @@ private boolean isAsync(final TypeScriptParser.PropertyMemberBaseContext propert return propertyMemberBase != null && propertyMemberBase.Async() != null; } - /** - * Extract the name of an arrow function from its parent context. - */ + /** Extract the name of an arrow function from its parent context. */ private String extractArrowFunctionName( final TypeScriptParser.ArrowFunctionDeclarationContext ctx) { ParserRuleContext parent = ctx.getParent(); @@ -569,7 +582,8 @@ private String extractArrowFunctionName( return variableDeclaration.identifierOrKeyWord().getText(); } } - if (parent instanceof TypeScriptParser.PropertyDeclarationExpressionContext propertyDeclaration) { + if (parent + instanceof TypeScriptParser.PropertyDeclarationExpressionContext propertyDeclaration) { if (propertyDeclaration.classElementName() != null) { return propertyDeclaration.classElementName().getText(); } @@ -592,8 +606,8 @@ private String extractArrowFunctionName( } /** - * Get comment lines of code by counting tokens on the hidden channel. ANTLR - * places comments on a hidden channel, so we need to extract them from there. + * Get comment lines of code by counting tokens on the hidden channel. ANTLR places comments on a + * hidden channel, so we need to extract them from there. */ private int getCloc(final ParserRuleContext ctx) { if (ctx == null || tokens == null) { diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrCParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrCParserService.java index 4e9f181..d2a2eac 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrCParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrCParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing C source code. - */ +/** ANTLR-based parser service for analyzing C source code. */ @ApplicationScoped public class AntlrCParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrCParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public CFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public CFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing C file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent, fileName); @@ -55,8 +52,8 @@ public CFileDataHandler parseFile(final String pathToFile, final String fileHash } } - private CFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private CFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final CLexer lexer = new CLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); @@ -71,8 +68,14 @@ private CFileDataHandler parse(final CharStream charStream, final String fileNam ParseTree compilationUnit = null; try { - compilationUnit = AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName, - parseSettings.parseTimeoutMs(), parser::compilationUnit); + compilationUnit = + AntlrParserUtils.parseTwoStage( + parser, + tokens, + LOGGER, + fileName, + parseSettings.parseTimeoutMs(), + parser::compilationUnit); } catch (Exception e) { LOGGER.warn("Could not build parse tree for {}: {}", fileName, e.getMessage()); } diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrCSharpParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrCSharpParserService.java index 8c0093e..756f100 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrCSharpParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrCSharpParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing C# source code. - */ +/** ANTLR-based parser service for analyzing C# source code. */ @ApplicationScoped public class AntlrCSharpParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrCSharpParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public CSharpFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public CSharpFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing C# file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent, fileName); @@ -55,15 +52,16 @@ public CSharpFileDataHandler parseFile(final String pathToFile, final String fil } } - private CSharpFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private CSharpFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final CSharpLexer lexer = new CSharpLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final CSharpParser parser = new CSharpParser(tokens); - final ParseTree prog = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::prog); + final ParseTree prog = + AntlrParserUtils.parseTwoStage( + parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::prog); final CSharpFileDataHandler fileDataHandler = new CSharpFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrCppParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrCppParserService.java index 9125ff3..98d7cd6 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrCppParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrCppParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing C/C++ source code. - */ +/** ANTLR-based parser service for analyzing C/C++ source code. */ @ApplicationScoped public class AntlrCppParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrCppParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public CppFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public CppFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing C/C++ file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent); @@ -55,16 +52,21 @@ public CppFileDataHandler parseFile(final String pathToFile, final String fileHa } } - private CppFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private CppFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final CPP14Lexer lexer = new CPP14Lexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final CPP14Parser parser = new CPP14Parser(tokens); - final ParseTree translationUnit = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), - parser::translationUnit); + final ParseTree translationUnit = + AntlrParserUtils.parseTwoStage( + parser, + tokens, + LOGGER, + fileName, + parseSettings.parseTimeoutMs(), + parser::translationUnit); final CppFileDataHandler fileDataHandler = new CppFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrGoParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrGoParserService.java index d8c93b5..ff12b38 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrGoParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrGoParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing Go source code. - */ +/** ANTLR-based parser service for analyzing Go source code. */ @ApplicationScoped public class AntlrGoParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrGoParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public GoFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public GoFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing Go file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent, fileName); @@ -55,15 +52,16 @@ public GoFileDataHandler parseFile(final String pathToFile, final String fileHas } } - private GoFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private GoFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final GoLexer lexer = new GoLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final GoParser parser = new GoParser(tokens); - final ParseTree sourceFile = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::sourceFile); + final ParseTree sourceFile = + AntlrParserUtils.parseTwoStage( + parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::sourceFile); final GoFileDataHandler fileDataHandler = new GoFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrKotlinParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrKotlinParserService.java index 6d2bde5..b0bbe4f 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrKotlinParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrKotlinParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing Kotlin source code. - */ +/** ANTLR-based parser service for analyzing Kotlin source code. */ @ApplicationScoped public class AntlrKotlinParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrKotlinParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public KotlinFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public KotlinFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing Kotlin file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent, fileName); @@ -55,17 +52,22 @@ public KotlinFileDataHandler parseFile(final String pathToFile, final String fil } } - private KotlinFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private KotlinFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final KotlinLexer lexer = new KotlinLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final KotlinParser parser = new KotlinParser(tokens); final boolean isScript = fileName.toLowerCase().endsWith(".kts"); - final ParseTree tree = AntlrParserUtils.parseTwoStage(parser, tokens, LOGGER, fileName, - parseSettings.parseTimeoutMs(), - () -> isScript ? parser.script() : parser.kotlinFile()); + final ParseTree tree = + AntlrParserUtils.parseTwoStage( + parser, + tokens, + LOGGER, + fileName, + parseSettings.parseTimeoutMs(), + () -> isScript ? parser.script() : parser.kotlinFile()); final KotlinFileDataHandler fileDataHandler = new KotlinFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrParseSettings.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrParseSettings.java index 0016dcc..c17c3bc 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrParseSettings.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrParseSettings.java @@ -3,13 +3,12 @@ import jakarta.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.config.inject.ConfigProperty; -/** - * Shared ANTLR parse wall-clock budget for all language parser services. - */ +/** Shared ANTLR parse wall-clock budget for all language parser services. */ @ApplicationScoped public class AntlrParseSettings { - @ConfigProperty(name = "explorviz.gitanalysis.parse-timeout-ms", + @ConfigProperty( + name = "explorviz.gitanalysis.parse-timeout-ms", defaultValue = "" + AntlrParserUtils.DEFAULT_PARSE_TIMEOUT_MS) /* default */ long parseTimeoutMs = AntlrParserUtils.DEFAULT_PARSE_TIMEOUT_MS; diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrParserService.java index bb2b410..befcab4 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrParserService.java @@ -17,22 +17,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing Java source code. - */ +/** ANTLR-based parser service for analyzing Java source code. */ @ApplicationScoped public class AntlrParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); @ConfigProperty(name = "explorviz.gitanalysis.assume-unresolved-types-from-wildcard-imports") - /* default */ boolean wildcardImportProperty; // NOCS + /* default */ boolean wildcardImportProperty; - public JavaFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public JavaFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent); @@ -59,22 +56,27 @@ public JavaFileDataHandler parseFile(final String pathToFile, final String fileH } } - private JavaFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private JavaFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final JavaLexer lexer = new JavaLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final JavaParser parser = new JavaParser(tokens); - final ParseTree compilationUnit = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), - parser::compilationUnit); + final ParseTree compilationUnit = + AntlrParserUtils.parseTwoStage( + parser, + tokens, + LOGGER, + fileName, + parseSettings.parseTimeoutMs(), + parser::compilationUnit); final JavaFileDataHandler fileDataHandler = new JavaFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); - final JavaFileDataListener listener = new JavaFileDataListener(fileDataHandler, - wildcardImportProperty, tokens); + final JavaFileDataListener listener = + new JavaFileDataListener(fileDataHandler, wildcardImportProperty, tokens); final ParseTreeWalker walker = new ParseTreeWalker(); walker.walk(listener, compilationUnit); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrParserUtils.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrParserUtils.java index 382269d..219e3f0 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrParserUtils.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrParserUtils.java @@ -35,8 +35,8 @@ * *

Optional parse timeout: callers may pass {@code parseTimeoutMs} so the entire SLL → LL * → lenient sequence cannot hang indefinitely. Large or ambiguous JS files (vendored libraries, - * bundles) can stall in SLL alone; the timeout covers that stage as well. On timeout a - * {@link ParseCancellationException} is thrown so callers can fall back to metrics-only analysis. + * bundles) can stall in SLL alone; the timeout covers that stage as well. On timeout a {@link + * ParseCancellationException} is thrown so callers can fall back to metrics-only analysis. * *

The timeout is enforced by a {@link DeadlineTokenStream} that checks a wall-clock deadline on * every lookahead/{@code consume}. ANTLR prediction does not respond to {@link Thread#interrupt()}, @@ -60,14 +60,13 @@ public final class AntlrParserUtils { * @deprecated use {@link #DEFAULT_PARSE_TIMEOUT_MS}; kept for binary compatibility with callers * that timed only the LL stage. */ - @Deprecated - public static final long DEFAULT_LL_TIMEOUT_MS = DEFAULT_PARSE_TIMEOUT_MS; + @Deprecated public static final long DEFAULT_LL_TIMEOUT_MS = DEFAULT_PARSE_TIMEOUT_MS; private AntlrParserUtils() {} /** - * Removes the default {@code ConsoleErrorListener} from the lexer so that lexer errors - * are not printed to {@code stderr}. + * Removes the default {@code ConsoleErrorListener} from the lexer so that lexer errors are not + * printed to {@code stderr}. */ public static void configureLexer(final Lexer lexer) { lexer.removeErrorListeners(); @@ -75,21 +74,25 @@ public static void configureLexer(final Lexer lexer) { /** * Parses using SLL prediction first; retries with full LL only if SLL cannot resolve an - * ambiguity. The LL stage uses a {@link ThresholdBailErrorStrategy} and routes syntax errors - * to SLF4J instead of the console. + * ambiguity. The LL stage uses a {@link ThresholdBailErrorStrategy} and routes syntax errors to + * SLF4J instead of the console. * - * @param the parse-tree context type returned by the entry-point rule - * @param parser the configured parser (tokens already consumed) - * @param tokens the token stream, rewound when falling back to LL - * @param logger the caller's SLF4J logger - * @param fileName source file name, included in warning messages - * @param parseCall supplier that invokes the grammar's entry-point rule (e.g. - * {@code parser::compilationUnit}) + * @param the parse-tree context type returned by the entry-point rule + * @param parser the configured parser (tokens already consumed) + * @param tokens the token stream, rewound when falling back to LL + * @param logger the caller's SLF4J logger + * @param fileName source file name, included in warning messages + * @param parseCall supplier that invokes the grammar's entry-point rule (e.g. {@code + * parser::compilationUnit}) * @return the root parse-tree node * @throws ParseCancellationException if the error threshold is exceeded during LL recovery */ - public static T parseTwoStage(final Parser parser, final CommonTokenStream tokens, - final Logger logger, final String fileName, final Supplier parseCall) { + public static T parseTwoStage( + final Parser parser, + final CommonTokenStream tokens, + final Logger logger, + final String fileName, + final Supplier parseCall) { return parseTwoStage(parser, tokens, logger, fileName, null, 0L, parseCall); } @@ -99,8 +102,12 @@ public static T parseTwoStage(final Parser parser, final CommonTokenStream t * value {@code <= 0} means no timeout. On timeout a {@link ParseCancellationException} is thrown * so callers can fall back to metrics-only analysis. */ - public static T parseTwoStage(final Parser parser, final CommonTokenStream tokens, - final Logger logger, final String fileName, final long parseTimeoutMs, + public static T parseTwoStage( + final Parser parser, + final CommonTokenStream tokens, + final Logger logger, + final String fileName, + final long parseTimeoutMs, final Supplier parseCall) { return parseTwoStage(parser, tokens, logger, fileName, null, parseTimeoutMs, parseCall); } @@ -109,21 +116,29 @@ public static T parseTwoStage(final Parser parser, final CommonTokenStream t * Same as {@link #parseTwoStage(Parser, CommonTokenStream, Logger, String, Supplier)} but also * records syntax-error messages in {@code syntaxErrors} (for tests and diagnostics). */ - static T parseTwoStage(final Parser parser, final CommonTokenStream tokens, - final Logger logger, final String fileName, final List syntaxErrors, + static T parseTwoStage( + final Parser parser, + final CommonTokenStream tokens, + final Logger logger, + final String fileName, + final List syntaxErrors, final Supplier parseCall) { return parseTwoStage(parser, tokens, logger, fileName, syntaxErrors, 0L, parseCall); } - static T parseTwoStage(final Parser parser, final CommonTokenStream tokens, - final Logger logger, final String fileName, final List syntaxErrors, - final long parseTimeoutMs, final Supplier parseCall) { + static T parseTwoStage( + final Parser parser, + final CommonTokenStream tokens, + final Logger logger, + final String fileName, + final List syntaxErrors, + final long parseTimeoutMs, + final Supplier parseCall) { if (parseTimeoutMs <= 0L) { return parseTwoStageUnbounded(parser, tokens, logger, fileName, syntaxErrors, parseCall); } - final long deadlineNanos = - System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(parseTimeoutMs); + final long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(parseTimeoutMs); final TokenStream previous = parser.getInputStream(); final DeadlineTokenStream deadlineStream = new DeadlineTokenStream(tokens, deadlineNanos, parseTimeoutMs, fileName); @@ -140,8 +155,12 @@ static T parseTwoStage(final Parser parser, final CommonTokenStream tokens, } } - private static T parseTwoStageUnbounded(final Parser parser, final CommonTokenStream tokens, - final Logger logger, final String fileName, final List syntaxErrors, + private static T parseTwoStageUnbounded( + final Parser parser, + final CommonTokenStream tokens, + final Logger logger, + final String fileName, + final List syntaxErrors, final Supplier parseCall) { // Stage 1: SLL — fast, works for the vast majority of valid source files. @@ -181,16 +200,24 @@ private static boolean isDeadlineTimeout(final ParseCancellationException e) { } /** - * Parses with full LL prediction and default error recovery, returning the best-effort parse - * tree even when the source contains unexpanded macros or other non-standard constructs. + * Parses with full LL prediction and default error recovery, returning the best-effort parse tree + * even when the source contains unexpanded macros or other non-standard constructs. */ - public static T parseLenient(final Parser parser, final CommonTokenStream tokens, - final Logger logger, final String fileName, final Supplier parseCall) { + public static T parseLenient( + final Parser parser, + final CommonTokenStream tokens, + final Logger logger, + final String fileName, + final Supplier parseCall) { return parseLenient(parser, tokens, logger, fileName, null, parseCall); } - static T parseLenient(final Parser parser, final CommonTokenStream tokens, - final Logger logger, final String fileName, final List syntaxErrors, + static T parseLenient( + final Parser parser, + final CommonTokenStream tokens, + final Logger logger, + final String fileName, + final List syntaxErrors, final Supplier parseCall) { tokens.seek(0); parser.reset(); @@ -205,8 +232,8 @@ static T parseLenient(final Parser parser, final CommonTokenStream tokens, /** * Behaves like {@link DefaultErrorStrategy} (attempts single-token insertion/deletion recovery) - * but throws {@link ParseCancellationException} after {@code maxErrors} recovery attempts. - * This prevents the parser from spending minutes on a file the grammar cannot handle. + * but throws {@link ParseCancellationException} after {@code maxErrors} recovery attempts. This + * prevents the parser from spending minutes on a file the grammar cannot handle. */ static final class ThresholdBailErrorStrategy extends DefaultErrorStrategy { @@ -248,16 +275,20 @@ static final class LoggingErrorListener extends BaseErrorListener { this(logger, fileName, null); } - LoggingErrorListener(final Logger logger, final String fileName, - final List syntaxErrors) { + LoggingErrorListener( + final Logger logger, final String fileName, final List syntaxErrors) { this.logger = logger; this.fileName = fileName; this.syntaxErrors = syntaxErrors; } @Override - public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, - final int line, final int charPositionInLine, final String msg, + public void syntaxError( + final Recognizer recognizer, + final Object offendingSymbol, + final int line, + final int charPositionInLine, + final String msg, final RecognitionException e) { final String formatted = "Parse error in %s at %d:%d — %s".formatted(fileName, line, charPositionInLine, msg); @@ -271,9 +302,9 @@ public void syntaxError(final Recognizer recognizer, final Object offendin /** * Token stream decorator that aborts the parse once a wall-clock deadline is exceeded. * - *

Extends {@link CommonTokenStream} (rather than only implementing {@link TokenStream}) because - * several language-specific parser bases cast {@code _input} to {@code CommonTokenStream} / - * {@code BufferedTokenStream} for semantic predicates. + *

Extends {@link CommonTokenStream} (rather than only implementing {@link TokenStream}) + * because several language-specific parser bases cast {@code _input} to {@code CommonTokenStream} + * / {@code BufferedTokenStream} for semantic predicates. * *

ANTLR adaptive prediction calls {@link #LT(int)} / {@link #LA(int)} millions of times during * catastrophic backtracking, so checking the deadline there stops the parse on the same thread @@ -287,8 +318,11 @@ static final class DeadlineTokenStream extends CommonTokenStream { private final String fileName; private volatile boolean timedOut; - DeadlineTokenStream(final CommonTokenStream input, final long deadlineNanos, - final long timeoutMs, final String fileName) { + DeadlineTokenStream( + final CommonTokenStream input, + final long deadlineNanos, + final long timeoutMs, + final String fileName) { super(input.getTokenSource()); this.input = input; this.deadlineNanos = deadlineNanos; diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrPhpParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrPhpParserService.java index df06f4a..0fc9205 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrPhpParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrPhpParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing PHP source code. - */ +/** ANTLR-based parser service for analyzing PHP source code. */ @ApplicationScoped public class AntlrPhpParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrPhpParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public PhpFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public PhpFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing PHP file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent, fileName); @@ -55,15 +52,16 @@ public PhpFileDataHandler parseFile(final String pathToFile, final String fileHa } } - private PhpFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private PhpFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final PhpLexer lexer = new PhpLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final PhpParser parser = new PhpParser(tokens); - final ParseTree document = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::htmlDocument); + final ParseTree document = + AntlrParserUtils.parseTwoStage( + parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::htmlDocument); final PhpFileDataHandler fileDataHandler = new PhpFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrPythonParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrPythonParserService.java index 96cf228..6a76907 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrPythonParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrPythonParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing Python source code. - */ +/** ANTLR-based parser service for analyzing Python source code. */ @ApplicationScoped public class AntlrPythonParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrPythonParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public PythonFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public PythonFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing Python file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent); @@ -55,15 +52,16 @@ public PythonFileDataHandler parseFile(final String pathToFile, final String fil } } - private PythonFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private PythonFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final PythonLexer lexer = new PythonLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final PythonParser parser = new PythonParser(tokens); - final ParseTree fileInput = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::file_input); + final ParseTree fileInput = + AntlrParserUtils.parseTwoStage( + parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::file_input); final PythonFileDataHandler fileDataHandler = new PythonFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrRustParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrRustParserService.java index 3fea4fd..1b46a50 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrRustParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrRustParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing Rust source code. - */ +/** ANTLR-based parser service for analyzing Rust source code. */ @ApplicationScoped public class AntlrRustParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrRustParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public RustFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public RustFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing Rust file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent, fileName); @@ -55,15 +52,16 @@ public RustFileDataHandler parseFile(final String pathToFile, final String fileH } } - private RustFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private RustFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final RustLexer lexer = new RustLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final RustParser parser = new RustParser(tokens); - final ParseTree crate = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::crate); + final ParseTree crate = + AntlrParserUtils.parseTwoStage( + parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::crate); final RustFileDataHandler fileDataHandler = new RustFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrSwiftParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrSwiftParserService.java index 97818e1..f7d1cf4 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrSwiftParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrSwiftParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing Swift source code. - */ +/** ANTLR-based parser service for analyzing Swift source code. */ @ApplicationScoped public class AntlrSwiftParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrSwiftParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public SwiftFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public SwiftFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing Swift file content for {}", fileName); final CharStream charStream = CharStreams.fromString(fileContent, fileName); @@ -55,15 +52,16 @@ public SwiftFileDataHandler parseFile(final String pathToFile, final String file } } - private SwiftFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash) { + private SwiftFileDataHandler parse( + final CharStream charStream, final String fileName, final String fileHash) { final Swift5Lexer lexer = new Swift5Lexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); final Swift5Parser parser = new Swift5Parser(tokens); - final ParseTree topLevel = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::top_level); + final ParseTree topLevel = + AntlrParserUtils.parseTwoStage( + parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::top_level); final SwiftFileDataHandler fileDataHandler = new SwiftFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); diff --git a/src/main/java/net/explorviz/code/analysis/parser/AntlrTypeScriptParserService.java b/src/main/java/net/explorviz/code/analysis/parser/AntlrTypeScriptParserService.java index a5ebacb..47a00b8 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/AntlrTypeScriptParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/AntlrTypeScriptParserService.java @@ -16,19 +16,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * ANTLR-based parser service for analyzing TypeScript/JavaScript source code. - */ +/** ANTLR-based parser service for analyzing TypeScript/JavaScript source code. */ @ApplicationScoped public class AntlrTypeScriptParserService { public static final Logger LOGGER = LoggerFactory.getLogger(AntlrTypeScriptParserService.class); - @Inject - AntlrParseSettings parseSettings = new AntlrParseSettings(); + @Inject AntlrParseSettings parseSettings = new AntlrParseSettings(); - public TypeScriptFileDataHandler parseFileContent(final String fileContent, final String fileName, - final String fileHash) { + public TypeScriptFileDataHandler parseFileContent( + final String fileContent, final String fileName, final String fileHash) { try { LOGGER.trace("Parsing TS/JS file content for {}", fileName); final String normalizedContent = normalizeTsx(fileContent, fileName); @@ -57,8 +54,11 @@ public TypeScriptFileDataHandler parseFile(final String pathToFile, final String } } - private TypeScriptFileDataHandler parse(final CharStream charStream, final String fileName, - final String fileHash, final String extension) { + private TypeScriptFileDataHandler parse( + final CharStream charStream, + final String fileName, + final String fileHash, + final String extension) { final TypeScriptLexer lexer = new TypeScriptLexer(charStream); AntlrParserUtils.configureLexer(lexer); final CommonTokenStream tokens = new CommonTokenStream(lexer); @@ -68,16 +68,15 @@ private TypeScriptFileDataHandler parse(final CharStream charStream, final Strin final boolean javaScript = ".js".equals(extension) || ".jsx".equals(extension); parser.setJavaScriptMode(javaScript); - final ParseTree program = AntlrParserUtils.parseTwoStage( - parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::program); + final ParseTree program = + AntlrParserUtils.parseTwoStage( + parser, tokens, LOGGER, fileName, parseSettings.parseTimeoutMs(), parser::program); final TypeScriptFileDataHandler fileDataHandler = new TypeScriptFileDataHandler(fileName); fileDataHandler.setFileHash(fileHash); - final TypeScriptFileDataListener listener = new TypeScriptFileDataListener( - fileDataHandler, - extension, - tokens); + final TypeScriptFileDataListener listener = + new TypeScriptFileDataListener(fileDataHandler, extension, tokens); final ParseTreeWalker walker = new ParseTreeWalker(); walker.walk(listener, program); diff --git a/src/main/java/net/explorviz/code/analysis/parser/JavaParserService.java b/src/main/java/net/explorviz/code/analysis/parser/JavaParserService.java index f18aaf3..0d8b83c 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/JavaParserService.java +++ b/src/main/java/net/explorviz/code/analysis/parser/JavaParserService.java @@ -27,9 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Parser Object loads and parses .java files. - */ +/** Parser Object loads and parses .java files. */ @ApplicationScoped public class JavaParserService { @@ -37,10 +35,10 @@ public class JavaParserService { private static final String CRASHED_FILES_PATH = "/logs/crashedfiles/"; @ConfigProperty(name = "explorviz.gitanalysis.save-crashed_files") - /* default */ boolean saveCrashedFilesProperty; // NOCS + /* default */ boolean saveCrashedFilesProperty; @ConfigProperty(name = "explorviz.gitanalysis.assume-unresolved-types-from-wildcard-imports") - /* default */ boolean wildcardImportProperty; // NOCS + /* default */ boolean wildcardImportProperty; private List sourcePaths; private JavaSymbolSolver javaSymbolSolver; @@ -48,9 +46,8 @@ public class JavaParserService { private ReflectionTypeSolver reflectionTypeSolver; /** - * Creates a new JavaParserService with only the reflectionTypeSolver, call - * {@link JavaParserService#reset(List)} to - * add JavaParserTypeSolvers and check in the given paths. + * Creates a new JavaParserService with only the reflectionTypeSolver, call {@link + * JavaParserService#reset(List)} to add JavaParserTypeSolvers and check in the given paths. */ public JavaParserService() { combinedTypeSolver = new CombinedTypeSolver(); @@ -70,7 +67,7 @@ public JavaParserService(final List sourcePaths) { reflectionTypeSolver = new ReflectionTypeSolver(false); combinedTypeSolver.add(reflectionTypeSolver); for (final String path : sourcePaths) { - combinedTypeSolver.add(new JavaParserTypeSolver(Path.of(path))); // NOPMD + combinedTypeSolver.add(new JavaParserTypeSolver(Path.of(path))); } javaSymbolSolver = new JavaSymbolSolver(combinedTypeSolver); } @@ -84,7 +81,9 @@ public JavaParserService(final String sourcePath) { this(Collections.singletonList(sourcePath)); } - private JavaFileDataHandler parse(final CompilationUnit compilationUnit, final String fileName, + private JavaFileDataHandler parse( + final CompilationUnit compilationUnit, + final String fileName, final boolean calculateMetrics) { final JavaFileDataHandler data = new JavaFileDataHandler(fileName); final FileDataVisitor fileDataVisitor; @@ -97,16 +96,15 @@ private JavaFileDataHandler parse(final CompilationUnit compilationUnit, final S } /** - * Resets the state of the JavaParserService, all cached values are cleared and - * the parser can be reused for another - * task. + * Resets the state of the JavaParserService, all cached values are cleared and the parser can be + * reused for another task. */ public void reset() { combinedTypeSolver = new CombinedTypeSolver(); reflectionTypeSolver = new ReflectionTypeSolver(false); combinedTypeSolver.add(reflectionTypeSolver); for (final String path : this.sourcePaths) { - combinedTypeSolver.add(new JavaParserTypeSolver(path)); // NOPMD + combinedTypeSolver.add(new JavaParserTypeSolver(path)); } javaSymbolSolver = new JavaSymbolSolver(combinedTypeSolver); } @@ -121,9 +119,12 @@ public void reset(final List sourcePaths) { reset(); } - private JavaFileDataHandler parseAny(final String fileContent, final String fileName, + private JavaFileDataHandler parseAny( + final String fileContent, + final String fileName, final Path path, - final boolean calculateMetrics, final String fileHash) + final boolean calculateMetrics, + final String fileHash) throws IOException { // ToDo: Make this configurable StaticJavaParser.getParserConfiguration().setLanguageLevel(LanguageLevel.JAVA_21); @@ -162,7 +163,7 @@ private JavaFileDataHandler parseAny(final String fileContent, final String file if (LOGGER.isErrorEnabled()) { LOGGER.error("NoSuchMethodError in " + fileName + System.lineSeparator() + e.getMessage()); } - } catch (Exception | Error e) { // NOPMD + } catch (Exception | Error e) { if (LOGGER.isErrorEnabled()) { LOGGER.error("Catched unknown exception in file " + fileName + System.lineSeparator() + e); @@ -176,8 +177,11 @@ private JavaFileDataHandler parseAny(final String fileContent, final String file * * @param fileContent stringified java file */ - public JavaFileDataHandler parseFileContent(final String fileContent, final String fileName, - final boolean calculateMetrics, final String fileHash) { + public JavaFileDataHandler parseFileContent( + final String fileContent, + final String fileName, + final boolean calculateMetrics, + final String fileHash) { try { return parseAny(fileContent, fileName, null, calculateMetrics, fileHash); } catch (IOException e) { @@ -192,18 +196,21 @@ public JavaFileDataHandler parseFileContent(final String fileContent, final Stri * * @throws IOException Gets thrown if the file is not reachable */ - public JavaFileDataHandler parseFile(final String pathToFile, final boolean calculateMetrics, - final String fileHash) throws IOException { + public JavaFileDataHandler parseFile( + final String pathToFile, final boolean calculateMetrics, final String fileHash) + throws IOException { final Path path = Path.of(pathToFile); return parseAny("", path.getFileName().toString(), path, calculateMetrics, fileHash); } - private void calculateMetrics(final JavaFileDataHandler data, // NOPMD - final CompilationUnit compilationUnit, final String fileName) { + private void calculateMetrics( + final JavaFileDataHandler data, + final CompilationUnit compilationUnit, + final String fileName) { try { final Pair pair = new Pair<>(new MetricAppender(data), new Object()); new CyclomaticComplexityVisitor().visit(compilationUnit, pair); - } catch (Exception e) { // NOPMD + } catch (Exception e) { // Catch everything and proceed, as these are only the metrics, the analysis has // to continue if (LOGGER.isErrorEnabled()) { @@ -215,9 +222,9 @@ private void calculateMetrics(final JavaFileDataHandler data, // NOPMD } } try { - new NestedBlockDepthVisitor().visit(compilationUnit, - new Pair<>(new MetricAppender(data), null)); - } catch (Exception e) { // NOPMD + new NestedBlockDepthVisitor() + .visit(compilationUnit, new Pair<>(new MetricAppender(data), null)); + } catch (Exception e) { // Catch everything and proceed, as these are only the metrics, the analysis has // to continue if (LOGGER.isErrorEnabled()) { @@ -229,9 +236,9 @@ private void calculateMetrics(final JavaFileDataHandler data, // NOPMD } } try { - new LackOfCohesionMethodsVisitor().visit(compilationUnit, - new Pair<>(new MetricAppender(data), null)); - } catch (Exception e) { // NOPMD + new LackOfCohesionMethodsVisitor() + .visit(compilationUnit, new Pair<>(new MetricAppender(data), null)); + } catch (Exception e) { // Catch everything and proceed, as these are only the metrics, the analysis has // to continue if (LOGGER.isErrorEnabled()) { @@ -243,5 +250,4 @@ private void calculateMetrics(final JavaFileDataHandler data, // NOPMD } } } - } diff --git a/src/main/java/net/explorviz/code/analysis/parser/TsxJsxNormalizer.java b/src/main/java/net/explorviz/code/analysis/parser/TsxJsxNormalizer.java index 3745a88..4672c68 100644 --- a/src/main/java/net/explorviz/code/analysis/parser/TsxJsxNormalizer.java +++ b/src/main/java/net/explorviz/code/analysis/parser/TsxJsxNormalizer.java @@ -1,15 +1,12 @@ package net.explorviz.code.analysis.parser; /** - * Replaces JSX elements in TSX source with {@code null} so the TypeScript - * grammar can parse - * files without expensive JSX disambiguation. Suffices for structural code - * analysis. + * Replaces JSX elements in TSX source with {@code null} so the TypeScript grammar can parse files + * without expensive JSX disambiguation. Suffices for structural code analysis. */ public final class TsxJsxNormalizer { - private TsxJsxNormalizer() { - } + private TsxJsxNormalizer() {} public static String replaceJsxWithNull(final String source) { final StringBuilder out = new StringBuilder(source.length()); @@ -71,7 +68,9 @@ private static boolean hasJsxPredecessor(final String source, final int ltIndex) } if (index > 0) { final String twoChars = source.substring(index - 1, index + 1); - if ("=>".equals(twoChars) || "&&".equals(twoChars) || "||".equals(twoChars) + if ("=>".equals(twoChars) + || "&&".equals(twoChars) + || "||".equals(twoChars) || "??".equals(twoChars)) { return true; } @@ -90,7 +89,9 @@ private static int skipJsxElement(final String source, final int start) { index = skipTagName(source, index); index = skipTypeArguments(source, index); index = skipAttributes(source, index); - if (index + 1 < source.length() && source.charAt(index) == '/' && source.charAt(index + 1) == '>') { + if (index + 1 < source.length() + && source.charAt(index) == '/' + && source.charAt(index + 1) == '>') { return index + 2; } if (index < source.length() && source.charAt(index) == '>') { @@ -259,7 +260,8 @@ private static int skipQuoted(final String source, final int start) { return index; } - private static int skipBalanced(final String source, final int start, final char open, final char close) { + private static int skipBalanced( + final String source, final int start, final char open, final char close) { int depth = 0; int index = start; while (index < source.length()) { diff --git a/src/main/java/net/explorviz/code/analysis/service/AnalysisCancellationResult.java b/src/main/java/net/explorviz/code/analysis/service/AnalysisCancellationResult.java index ba13ae7..b360830 100644 --- a/src/main/java/net/explorviz/code/analysis/service/AnalysisCancellationResult.java +++ b/src/main/java/net/explorviz/code/analysis/service/AnalysisCancellationResult.java @@ -1,8 +1,6 @@ package net.explorviz.code.analysis.service; -/** - * Result of requesting cancellation for an analysis job. - */ +/** Result of requesting cancellation for an analysis job. */ public enum AnalysisCancellationResult { /** Cancellation was accepted. */ CANCELLED, diff --git a/src/main/java/net/explorviz/code/analysis/service/AnalysisConfig.java b/src/main/java/net/explorviz/code/analysis/service/AnalysisConfig.java index 98530b3..25427f4 100644 --- a/src/main/java/net/explorviz/code/analysis/service/AnalysisConfig.java +++ b/src/main/java/net/explorviz/code/analysis/service/AnalysisConfig.java @@ -6,16 +6,19 @@ import java.util.Optional; import java.util.stream.Collectors; -/** - * Configuration object for Git analysis operations. - */ -public record AnalysisConfig(Optional repoPath, Optional repoRemoteUrl, Optional gitUsername, - Optional gitPassword, Optional branch, +/** Configuration object for Git analysis operations. */ +public record AnalysisConfig( + Optional repoPath, + Optional repoRemoteUrl, + Optional gitUsername, + Optional gitPassword, + Optional branch, Optional includeInAnalysisExpressions, Optional excludeFromAnalysisExpressions, List applicationPaths, boolean includeDataStructures, - Optional startCommit, Optional endCommit, + Optional startCommit, + Optional endCommit, Optional commitAnalysisLimit, Optional commitSamplingInterval, Optional commitSamplingPeriod, @@ -24,18 +27,22 @@ public record AnalysisConfig(Optional repoPath, Optional repoRem boolean skipCommitsWithoutRelevantFileChanges, String landscapeToken, boolean fetchSocialData, - boolean syncSocialWindow, Optional fetchEndDate, Optional socialDataTimeFrameDays) { + boolean syncSocialWindow, + Optional fetchEndDate, + Optional socialDataTimeFrameDays) { /** - * Path filter passed to Git diffs: union of all application roots, or global filters when appropriate. + * Path filter passed to Git diffs: union of all application roots, or global filters when + * appropriate. */ public String pathRestrictionForDiff() { if (applicationPaths == null || applicationPaths.isEmpty()) { return includeInAnalysisExpressions().orElse(""); } - final boolean anyBlankRoot = applicationPaths.stream() - .map(ApplicationPath::root) - .anyMatch(r -> r == null || r.isBlank()); + final boolean anyBlankRoot = + applicationPaths.stream() + .map(ApplicationPath::root) + .anyMatch(r -> r == null || r.isBlank()); if (anyBlankRoot) { return includeInAnalysisExpressions().orElse(""); } @@ -46,9 +53,7 @@ public String pathRestrictionForDiff() { .collect(Collectors.joining(",")); } - /** - * Map for {@link net.explorviz.code.proto.StateDataRequest} {@code application_paths}. - */ + /** Map for {@link net.explorviz.code.proto.StateDataRequest} {@code application_paths}. */ public Map applicationPathsMap() { final Map map = new LinkedHashMap<>(); if (applicationPaths == null) { @@ -77,9 +82,7 @@ public String primaryApplicationNameForExport() { .trim(); } - /** - * Builder for AnalysisConfig. - */ + /** Builder for AnalysisConfig. */ public static class Builder { private Optional repoPath = Optional.empty(); private Optional repoRemoteUrl = Optional.empty(); @@ -134,12 +137,14 @@ public Builder branch(final Optional branch) { return this; } - public Builder includeInAnalysisExpressions(final Optional includeInAnalysisExpressions) { + public Builder includeInAnalysisExpressions( + final Optional includeInAnalysisExpressions) { this.includeInAnalysisExpressions = includeInAnalysisExpressions; return this; } - public Builder excludeFromAnalysisExpressions(final Optional excludeFromAnalysisExpressions) { + public Builder excludeFromAnalysisExpressions( + final Optional excludeFromAnalysisExpressions) { this.excludeFromAnalysisExpressions = excludeFromAnalysisExpressions; return this; } @@ -224,7 +229,7 @@ public Builder socialDataTimeFrameDays(final Optional socialDataTimeFra this.socialDataTimeFrameDays = socialDataTimeFrameDays; return this; } - + public Builder applicationPaths(final List paths) { this.explicitApplicationPaths = paths; return this; @@ -235,9 +240,10 @@ public AnalysisConfig build() { if (explicitApplicationPaths != null && !explicitApplicationPaths.isEmpty()) { paths = List.copyOf(explicitApplicationPaths); } else { - paths = List.of(new ApplicationPath( - applicationName != null ? applicationName : "", - applicationRoot.orElse(""))); + paths = + List.of( + new ApplicationPath( + applicationName != null ? applicationName : "", applicationRoot.orElse(""))); } return new AnalysisConfig( repoPath, @@ -272,8 +278,8 @@ public AnalysisConfig build() { * @param repoRemoteUrl optional clone URL (takes precedence over {@code repoPath}) * @return The repository name, or an empty string if neither source yields one */ - public static String deriveRepositoryName(final Optional repoPath, - final Optional repoRemoteUrl) { + public static String deriveRepositoryName( + final Optional repoPath, final Optional repoRemoteUrl) { if (repoRemoteUrl != null && repoRemoteUrl.isPresent()) { String upstream = repoRemoteUrl.get(); // remove trailing slash if present @@ -309,8 +315,7 @@ public static String deriveRepositoryName(final Optional repoPath, } /** - * Returns the name of the repository extracted from the remote URL or the local - * path. + * Returns the name of the repository extracted from the remote URL or the local path. * * @return The repository name, or an empty string if not found. */ diff --git a/src/main/java/net/explorviz/code/analysis/service/AnalysisProgressState.java b/src/main/java/net/explorviz/code/analysis/service/AnalysisProgressState.java index 90f5206..a544af2 100644 --- a/src/main/java/net/explorviz/code/analysis/service/AnalysisProgressState.java +++ b/src/main/java/net/explorviz/code/analysis/service/AnalysisProgressState.java @@ -2,14 +2,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; -/** - * In-memory representation of analysis state. - */ +/** In-memory representation of analysis state. */ public record AnalysisProgressState( String status, int totalCommits, int analyzedCommits, int totalFiles, int analyzedFiles, - @JsonProperty("currentAnalyzingFile") String currentAnalysingFile) { -} + @JsonProperty("currentAnalyzingFile") String currentAnalysingFile) {} diff --git a/src/main/java/net/explorviz/code/analysis/service/AnalysisService.java b/src/main/java/net/explorviz/code/analysis/service/AnalysisService.java index 7c81097..6606aeb 100644 --- a/src/main/java/net/explorviz/code/analysis/service/AnalysisService.java +++ b/src/main/java/net/explorviz/code/analysis/service/AnalysisService.java @@ -67,103 +67,120 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Service for analyzing Git repositories and extracting code metrics. - */ +/** Service for analyzing Git repositories and extracting code metrics. */ @ApplicationScoped public class AnalysisService { private static final Logger LOGGER = LoggerFactory.getLogger(AnalysisService.class); private static final Long SOCIAL_FETCH_TIMEOUT_SECONDS = 900L; // should probably be in config - private static final Set TEXT_FILE_EXTENSIONS = Set.of( - // Plain text & docs - "txt", "md", "rst", "adoc", "log", "license", "notice", "readme", - - // Configuration formats - "conf", "cfg", "ini", "properties", "prefs", - "env", "dotenv", - "toml", - "yaml", "yml", - "json", - "xml", - - // Infrastructure / tooling configs - "gradle", - "editorconfig", - "gitignore", "gitattributes", "gitmodules", - "dockerignore", - "npmrc", "yarnrc", "pnpmrc", - "eslintrc", "prettierrc", "stylelintrc", - "babelrc", - "htaccess", - - // CI / automation - "workflow", - - // Data & text-based assets - "csv", "tsv", "sql", - - // System / service configs - "service", "socket", "timer"); - @Inject - /* package */ GitRepositoryHandler gitRepositoryHandler; - @Inject - /* package */ AntlrParserService antlrParserService; - @Inject - /* package */ AntlrTypeScriptParserService tsParserService; - @Inject - /* package */ AntlrPythonParserService pythonParserService; - @Inject - /* package */ AntlrCppParserService cppParserService; - @Inject - /* package */ AntlrCParserService antlrCParserService; - @Inject - /* package */ AntlrGoParserService goParserService; - @Inject - /* package */ AntlrCSharpParserService csharpParserService; - @Inject - /* package */ AntlrRustParserService rustParserService; - @Inject - /* package */ AntlrKotlinParserService kotlinParserService; - @Inject - /* package */ AntlrPhpParserService phpParserService; - @Inject - /* package */ AntlrSwiftParserService swiftParserService; - @Inject - /* package */ AnalysisStatusService analysisStatusService; - @Inject - /* package */ GithubFetcherService socialFetcherService; - @Inject - /* package */ ManagedExecutor managedExecutor; + private static final Set TEXT_FILE_EXTENSIONS = + Set.of( + // Plain text & docs + "txt", + "md", + "rst", + "adoc", + "log", + "license", + "notice", + "readme", + + // Configuration formats + "conf", + "cfg", + "ini", + "properties", + "prefs", + "env", + "dotenv", + "toml", + "yaml", + "yml", + "json", + "xml", + + // Infrastructure / tooling configs + "gradle", + "editorconfig", + "gitignore", + "gitattributes", + "gitmodules", + "dockerignore", + "npmrc", + "yarnrc", + "pnpmrc", + "eslintrc", + "prettierrc", + "stylelintrc", + "babelrc", + "htaccess", + + // CI / automation + "workflow", + + // Data & text-based assets + "csv", + "tsv", + "sql", + + // System / service configs + "service", + "socket", + "timer"); + @Inject /* package */ GitRepositoryHandler gitRepositoryHandler; + @Inject /* package */ AntlrParserService antlrParserService; + @Inject /* package */ AntlrTypeScriptParserService tsParserService; + @Inject /* package */ AntlrPythonParserService pythonParserService; + @Inject /* package */ AntlrCppParserService cppParserService; + @Inject /* package */ AntlrCParserService antlrCParserService; + @Inject /* package */ AntlrGoParserService goParserService; + @Inject /* package */ AntlrCSharpParserService csharpParserService; + @Inject /* package */ AntlrRustParserService rustParserService; + @Inject /* package */ AntlrKotlinParserService kotlinParserService; + @Inject /* package */ AntlrPhpParserService phpParserService; + @Inject /* package */ AntlrSwiftParserService swiftParserService; + @Inject /* package */ AnalysisStatusService analysisStatusService; + @Inject /* package */ GithubFetcherService socialFetcherService; + @Inject /* package */ ManagedExecutor managedExecutor; + @ConfigProperty(name = "explorviz.gitanalysis.save-crashed_files") /* default */ boolean saveCrashedFilesProperty; + @ConfigProperty(name = "explorviz.gitanalysis.file-analysis-parallelism", defaultValue = "0") /* default */ int fileAnalysisParallelismProperty; + @ConfigProperty(name = "explorviz.gitanalysis.file-persist-concurrency", defaultValue = "8") /* default */ int filePersistConcurrencyProperty; + @ConfigProperty(name = "explorviz.gitanalysis.file-persist-batch-size", defaultValue = "50") /* default */ int filePersistBatchSizeProperty; + @ConfigProperty(name = "explorviz.gitanalysis.run-mode", defaultValue = "api") /* default */ String runModeProperty; - private static String toErrorText(final String position, final String commitId, - final String branchName) { - return "The given " + position + " commit <" + commitId - + "> was not found in the current branch <" + branchName + ">"; + private static String toErrorText( + final String position, final String commitId, final String branchName) { + return "The given " + + position + + " commit <" + + commitId + + "> was not found in the current branch <" + + branchName + + ">"; } /** * Analyzes a Git repository and sends the results using the provided exporter. * - * @param config The analysis configuration + * @param config The analysis configuration * @param exporter The data exporter to use for sending results - * @throws IOException If an I/O error occurs - * @throws GitAPIException If a Git operation fails - * @throws NotFoundException If a required resource is not found + * @throws IOException If an I/O error occurs + * @throws GitAPIException If a Git operation fails + * @throws NotFoundException If a required resource is not found * @throws PropertyNotDefinedException If a required property is not defined */ - public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter exporter) // NOCS - throws IOException, GitAPIException, NotFoundException, PropertyNotDefinedException { // NOPMD + public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter exporter) + throws IOException, GitAPIException, NotFoundException, PropertyNotDefinedException { ensureNotCancelled(config.landscapeToken()); @@ -184,19 +201,27 @@ public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter e initializeRemoteStateAndResolveStart(config, exporter, branch, repositoryUrl); final Optional startCommit = analysisStartContext.startCommit(); - final Optional endCommit = exporter.isRemote() ? Optional.empty() : config.endCommit(); + final Optional endCommit = + exporter.isRemote() ? Optional.empty() : config.endCommit(); checkIfCommitsAreReachable(startCommit, endCommit, fullBranch); - final List commitsInRange = collectCommitWalkEntries(repository, fullBranch, startCommit, - endCommit, exporter.isRemote(), config.firstParentCommitsOnly()); + final List commitsInRange = + collectCommitWalkEntries( + repository, + fullBranch, + startCommit, + endCommit, + exporter.isRemote(), + config.firstParentCommitsOnly()); final int totalCommitsInRange = commitsInRange.size(); final boolean commitSamplingEnabled = CommitSampler.isEnabled(config); CommitRangeSelection commitRangeSelection = resolveCommitRangeSelection(totalCommitsInRange, config); - commitRangeSelection = adjustSkipForShallowCloneBoundary( - repository, commitsInRange, commitRangeSelection, config); + commitRangeSelection = + adjustSkipForShallowCloneBoundary( + repository, commitsInRange, commitRangeSelection, config); final int commitsToAnalyze = commitRangeSelection.commitsToAnalyze(); final int commitsToSkipBeforeAnalyzing = commitRangeSelection.commitsToSkipBeforeAnalyzing(); @@ -220,9 +245,10 @@ public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter e analysisStatusService.markRunning(config.landscapeToken(), commitsForProgressTracking, 0); // find start and end dates for social analysis - final List analyzedCommits = commitSamplingEnabled - ? commitsToProcess - : commitsInRange.subList(commitsToSkipBeforeAnalyzing, totalCommitsInRange); + final List analyzedCommits = + commitSamplingEnabled + ? commitsToProcess + : commitsInRange.subList(commitsToSkipBeforeAnalyzing, totalCommitsInRange); if (config.syncSocialWindow() && !analyzedCommits.isEmpty()) { long min = Long.MAX_VALUE; long max = Long.MIN_VALUE; @@ -233,16 +259,18 @@ public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter e } final Date socialStart = new Date(min * 1000L); final Date socialEnd = new Date(max * 1000L); - socialFuture = socialFetcherService.fetchSocialData(config, exporter, managedExecutor, socialStart, socialEnd); + socialFuture = + socialFetcherService.fetchSocialData( + config, exporter, managedExecutor, socialStart, socialEnd); } final Map> tagsByCommitId = buildTagsByCommitId(repository); // Pre-compile patterns - final List restrictMatchers = compileMatchers( - config.includeInAnalysisExpressions()); - final List excludeMatchers = compileMatchers( - config.excludeFromAnalysisExpressions()); + final List restrictMatchers = + compileMatchers(config.includeInAnalysisExpressions()); + final List excludeMatchers = + compileMatchers(config.excludeFromAnalysisExpressions()); String lastCheckedCommitHash = startCommit.filter(hash -> !hash.isBlank()).orElse(null); String lastFullyAnalyzedCommitHash = lastCheckedCommitHash; @@ -258,7 +286,9 @@ public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter e || !commitSamplingEnabled && processedCommitCount < commitsToAnalyze; if (!fullyAnalyzeCommit && commitSamplingEnabled) { - LOGGER.atDebug().addArgument(walkEntry.hash()) + LOGGER + .atDebug() + .addArgument(walkEntry.hash()) .log("Skipping commit not selected for sampling: {}"); recordSkippedCommitInWalk(config); lastCheckedCommitHash = walkEntry.hash(); @@ -299,10 +329,14 @@ public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter e final var reportTriple = gitRepositoryHandler.listDiff( - repository, Optional.ofNullable(baseCommit), commit, config.pathRestrictionForDiff()); - - final List descriptorAddedList = new ArrayList<>(reportTriple.right()); // NOPMD - final List descriptorModifiedList = new ArrayList<>(reportTriple.left()); + repository, + Optional.ofNullable(baseCommit), + commit, + config.pathRestrictionForDiff()); + + final List descriptorAddedList = new ArrayList<>(reportTriple.right()); + final List descriptorModifiedList = + new ArrayList<>(reportTriple.left()); final List descriptorDeletedList = reportTriple.middle(); final GlobFilterStats globFilterStats = new GlobFilterStats(); @@ -317,27 +351,30 @@ public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter e if (config.skipCommitsWithoutRelevantFileChanges() && !hasRelevantFilteredFileChanges( descriptorAddedList, descriptorModifiedList, descriptorDeletedList)) { - LOGGER.atDebug().addArgument(commit.getName()) + LOGGER + .atDebug() + .addArgument(commit.getName()) .log("Skipping commit without file changes in analysis scope: {}"); recordSkippedCommitInWalk(config); lastCheckedCommitHash = walkEntry.hash(); continue; } - LOGGER.atDebug().addArgument(descriptorAddedList.size()) + LOGGER + .atDebug() + .addArgument(descriptorAddedList.size()) .addArgument(descriptorModifiedList.size()) .log("Files added: {}, files modified: {}"); final List unchangedFiles = List.of(); - final List filesToAnalyze = new ArrayList<>(descriptorAddedList.size() - + descriptorModifiedList.size()); + final List filesToAnalyze = + new ArrayList<>(descriptorAddedList.size() + descriptorModifiedList.size()); filesToAnalyze.addAll(descriptorAddedList); filesToAnalyze.addAll(descriptorModifiedList); analysisStatusService.setCurrentCommitFiles( - config.landscapeToken(), - filesToAnalyze.size()); + config.landscapeToken(), filesToAnalyze.size()); if (filesToAnalyze.isEmpty()) { createCommitReport( @@ -395,24 +432,25 @@ public void analyzeAndSendRepo(final AnalysisConfig config, final DataExporter e LOGGER.atTrace().addArgument(fullAnalysisCount).log("Analyzed {} commits"); - socialFuture.ifPresent(f -> { - try { - f.get(SOCIAL_FETCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); - exporter.relinkResourceEvents(config.landscapeToken(), config.getRepositoryName()); - } catch (TimeoutException | ExecutionException e) { - LOGGER.warn("Social fetch did not complete", e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }); + socialFuture.ifPresent( + f -> { + try { + f.get(SOCIAL_FETCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); + exporter.relinkResourceEvents(config.landscapeToken(), config.getRepositoryName()); + } catch (TimeoutException | ExecutionException e) { + LOGGER.warn("Social fetch did not complete", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); // checkout the branch, so not a single commit is checked out after the run Git.wrap(repository).checkout().setName(fullBranch).call(); } } - private String resolveRepositoryUrlForStateRequest(final AnalysisConfig config, - final String repositoryUrl) { + private String resolveRepositoryUrlForStateRequest( + final AnalysisConfig config, final String repositoryUrl) { return RepositoryFileUrlBuilder.resolveRepositoryUrl( repositoryUrl.isBlank() ? config.repoRemoteUrl() : Optional.of(repositoryUrl), "") .orElse(""); @@ -422,21 +460,25 @@ private String resolveRepositoryUrlForStateRequest(final AnalysisConfig config, * Sends a single state request that both registers landscape metadata and, in CI mode, resolves * the latest fully persisted commit to resume from. */ - private AnalysisStartContext initializeRemoteStateAndResolveStart(final AnalysisConfig config, - final DataExporter exporter, final String branch, final String repositoryUrl) { + private AnalysisStartContext initializeRemoteStateAndResolveStart( + final AnalysisConfig config, + final DataExporter exporter, + final String branch, + final String repositoryUrl) { if (!exporter.isRemote()) { return new AnalysisStartContext(config.startCommit(), false); } try { final boolean ciMode = isCiMode(); - final StateData remoteState = exporter.getStateData( - config.getRepositoryName(), - branch, - config.landscapeToken(), - config.applicationPathsMap(), - resolveRepositoryUrlForStateRequest(config, repositoryUrl), - !ciMode); + final StateData remoteState = + exporter.getStateData( + config.getRepositoryName(), + branch, + config.landscapeToken(), + config.applicationPathsMap(), + resolveRepositoryUrlForStateRequest(config, repositoryUrl), + !ciMode); return resolveAnalysisStartContext(config, exporter, remoteState, ciMode, branch); } catch (final Exception e) { LOGGER.warn("Could not initialize remote state: {}", e.getMessage()); @@ -444,8 +486,8 @@ private AnalysisStartContext initializeRemoteStateAndResolveStart(final Analysis } } - private void checkIfCommitsAreReachable(final Optional startCommit, - final Optional endCommit, final String branch) + private void checkIfCommitsAreReachable( + final Optional startCommit, final Optional endCommit, final String branch) throws NotFoundException { if (this.gitRepositoryHandler.isUnreachableCommit(startCommit, branch)) { throw new NotFoundException(toErrorText("start", startCommit.orElse(""), branch)); @@ -455,32 +497,35 @@ private void checkIfCommitsAreReachable(final Optional startCommit, } private record AnalysisStartContext( - Optional startCommit, boolean landscapeHasPersistedCommits) { - } + Optional startCommit, boolean landscapeHasPersistedCommits) {} /** * Resolves the git commit to start from and whether landscape already contains a fully persisted * commit for this repository branch. */ - private AnalysisStartContext resolveAnalysisStartContext(final AnalysisConfig config, - final DataExporter exporter, final StateData remoteState, final boolean ciMode, + private AnalysisStartContext resolveAnalysisStartContext( + final AnalysisConfig config, + final DataExporter exporter, + final StateData remoteState, + final boolean ciMode, final String branch) { - final boolean landscapeHasPersistedCommits = remoteState.getCommitId() != null - && !remoteState.getCommitId().isBlank(); + final boolean landscapeHasPersistedCommits = + remoteState.getCommitId() != null && !remoteState.getCommitId().isBlank(); if (ciMode) { if (!landscapeHasPersistedCommits) { - LOGGER.info("No remote state found for branch {}. Starting analysis from the beginning.", - branch); + LOGGER.info( + "No remote state found for branch {}. Starting analysis from the beginning.", branch); return new AnalysisStartContext(Optional.empty(), false); } - LOGGER.info("Remote state found. Starting analysis after already analyzed commit: {}", + LOGGER.info( + "Remote state found. Starting analysis after already analyzed commit: {}", remoteState.getCommitId()); return new AnalysisStartContext(Optional.of(remoteState.getCommitId()), true); } - if (config.startCommit().isPresent() && exporter.isInvalidCommitHash( - config.startCommit().get())) { + if (config.startCommit().isPresent() + && exporter.isInvalidCommitHash(config.startCommit().get())) { return new AnalysisStartContext(Optional.empty(), landscapeHasPersistedCommits); } return new AnalysisStartContext(config.startCommit(), landscapeHasPersistedCommits); @@ -501,8 +546,7 @@ private AnalysisStartContext resolveAnalysisStartContext(final AnalysisConfig co final String lastFullyAnalyzedCommitHash, final Set analyzedCommitHashes) throws IOException { - if (hasGapSinceLastFullAnalysis( - lastFullyAnalyzedCommitHash, commit, analyzedCommitHashes)) { + if (hasGapSinceLastFullAnalysis(lastFullyAnalyzedCommitHash, commit, analyzedCommitHashes)) { return parseCommitByHash(repository, lastFullyAnalyzedCommitHash); } final boolean isFirstAnalyzedCommit = commitCount == 0; @@ -539,8 +583,9 @@ private AnalysisStartContext resolveAnalysisStartContext(final AnalysisConfig co } /** - * Parent commit ids for {@link CommitData}. When {@code connectToLastAnalyzedWhenCommitsWereSkipped} - * is enabled, commits after ignored ones link to the last persisted commit instead of git parents. + * Parent commit ids for {@link CommitData}. When {@code + * connectToLastAnalyzedWhenCommitsWereSkipped} is enabled, commits after ignored ones link to the + * last persisted commit instead of git parents. */ /* package */ List resolveLandscapeParentCommitIds( final RevCommit commit, @@ -548,8 +593,7 @@ private AnalysisStartContext resolveAnalysisStartContext(final AnalysisConfig co final boolean connectToLastAnalyzedWhenCommitsWereSkipped, final Set analyzedCommitHashes) { if (connectToLastAnalyzedWhenCommitsWereSkipped - && hasGapSinceLastFullAnalysis( - lastFullyAnalyzedCommitHash, commit, analyzedCommitHashes)) { + && hasGapSinceLastFullAnalysis(lastFullyAnalyzedCommitHash, commit, analyzedCommitHashes)) { return List.of(lastFullyAnalyzedCommitHash); } return resolveStoredParentCommitIds(commit); @@ -563,9 +607,9 @@ && hasGapSinceLastFullAnalysis( } /** - * Determines how many oldest commits to skip and how many to analyze when a - * {@link AnalysisConfig#commitAnalysisLimit()} is set. Shallow clones fetch {@code limit + 1} - * commits; the extra oldest commit is excluded via {@code total - limit} when {@code total > limit}. + * Determines how many oldest commits to skip and how many to analyze when a {@link + * AnalysisConfig#commitAnalysisLimit()} is set. Shallow clones fetch {@code limit + 1} commits; + * the extra oldest commit is excluded via {@code total - limit} when {@code total > limit}. */ /* package */ CommitRangeSelection resolveCommitRangeSelection( final int totalCommitsInRange, final AnalysisConfig config) { @@ -588,7 +632,8 @@ && hasGapSinceLastFullAnalysis( final Repository repository, final List commitsInRange, final CommitRangeSelection selection, - final AnalysisConfig config) throws IOException { + final AnalysisConfig config) + throws IOException { if (config.commitAnalysisLimit().isEmpty() || commitsInRange.isEmpty()) { return selection; } @@ -600,14 +645,14 @@ && hasGapSinceLastFullAnalysis( } final CommitWalkEntry firstEntryToProcess = commitsInRange.get(commitsToSkipBeforeAnalyzing); - final RevCommit firstCommitToProcess = parseCommitByHash(repository, firstEntryToProcess.hash()); + final RevCommit firstCommitToProcess = + parseCommitByHash(repository, firstEntryToProcess.hash()); try { if (shouldExcludeAsShallowCloneBoundary(repository, firstCommitToProcess)) { commitsToSkipBeforeAnalyzing++; commitsToAnalyze = Math.max(0, commitsToAnalyze - 1); LOGGER.info( - "Excluding shallow-clone boundary commit {} from analysis", - firstEntryToProcess.hash()); + "Excluding shallow-clone boundary commit {} from analysis", firstEntryToProcess.hash()); } } finally { firstCommitToProcess.disposeBody(); @@ -645,16 +690,17 @@ && hasGapSinceLastFullAnalysis( return !isCommitAvailableInRepository(repository, commit.getParent(0).getName()); } - private boolean isCommitAvailableInRepository(final Repository repository, final String commitHash) - throws IOException { + private boolean isCommitAvailableInRepository( + final Repository repository, final String commitHash) throws IOException { return repository.resolve(commitHash) != null; } - /* package */ record CommitRangeSelection(int commitsToAnalyze, int commitsToSkipBeforeAnalyzing) {} + /* package */ record CommitRangeSelection( + int commitsToAnalyze, int commitsToSkipBeforeAnalyzing) {} /** - * Returns {@code true} when skipped commits sit between the last fully analyzed commit and - * {@code commit}, so landscape-service cannot inherit unchanged files from its git parent. + * Returns {@code true} when skipped commits sit between the last fully analyzed commit and {@code + * commit}, so landscape-service cannot inherit unchanged files from its git parent. * *

When the walk analyzes commits from merged branches, the last fully analyzed commit may * belong to a parallel branch while {@code commit}'s first git parent was already analyzed. That @@ -706,9 +752,14 @@ private RevCommit parseCommitWalkEntry(final Repository repository, final Commit return parseCommitByHash(repository, entry.hash()); } - private List collectCommitWalkEntries(final Repository repository, final String fullBranch, - final Optional startCommit, final Optional endCommit, - final boolean remoteExport, final boolean firstParentCommitsOnly) throws IOException { + private List collectCommitWalkEntries( + final Repository repository, + final String fullBranch, + final Optional startCommit, + final Optional endCommit, + final boolean remoteExport, + final boolean firstParentCommitsOnly) + throws IOException { try (RevWalk revWalk = new RevWalk(repository)) { prepareRevWalk(repository, revWalk, fullBranch, firstParentCommitsOnly); @@ -746,24 +797,36 @@ private Map> buildTagsByCommitId(final Repository reposit final Map> tagsByCommitId = new HashMap<>(); final List tags = Git.wrap(repository).tagList().call(); for (final Ref tag : tags) { - tagsByCommitId.computeIfAbsent(tag.getObjectId(), ignored -> new ArrayList<>()) + tagsByCommitId + .computeIfAbsent(tag.getObjectId(), ignored -> new ArrayList<>()) .add(tag.getName()); } return tagsByCommitId; } - private void prepareRevWalk(final Repository repository, final RevWalk revWalk, - final String branch, final boolean firstParentCommitsOnly) throws IOException { - gitRepositoryHandler.configureBranchRevWalk(revWalk, repository, branch, - firstParentCommitsOnly); + private void prepareRevWalk( + final Repository repository, + final RevWalk revWalk, + final String branch, + final boolean firstParentCommitsOnly) + throws IOException { + gitRepositoryHandler.configureBranchRevWalk( + revWalk, repository, branch, firstParentCommitsOnly); } - private void commitAnalysis(final AnalysisConfig config, final Repository repository, - final RevCommit commit, final List filesToAnalyze, - final DataExporter exporter, final String branchName, - final List addedFiles, final List modifiedFiles, - final List deletedFiles, final List unchangedFiles, - final Map> tagsByCommitId, final String lastFullyAnalyzedCommitHash, + private void commitAnalysis( + final AnalysisConfig config, + final Repository repository, + final RevCommit commit, + final List filesToAnalyze, + final DataExporter exporter, + final String branchName, + final List addedFiles, + final List modifiedFiles, + final List deletedFiles, + final List unchangedFiles, + final Map> tagsByCommitId, + final String lastFullyAnalyzedCommitHash, final Set analyzedCommitHashes) throws GitAPIException, NotFoundException, IOException { @@ -788,20 +851,25 @@ private void commitAnalysis(final AnalysisConfig config, final Repository reposi LOGGER.atTrace().addArgument(filesToAnalyze.toString()).log("Files: {}"); final long analysisStartedAt = System.nanoTime(); - final List> analysisTasks = submitFileAnalysisTasks(config, - repository, commit, filesToAnalyze); + final List> analysisTasks = + submitFileAnalysisTasks(config, repository, commit, filesToAnalyze); CompletableFuture.allOf(analysisTasks.toArray(new CompletableFuture[0])) - .whenComplete((ignored, error) -> LOGGER.atDebug() - .addArgument(commit.getName()) - .addArgument(analysisTasks.size()) - .addArgument((System.nanoTime() - analysisStartedAt) / 1_000_000L) - .log("File analysis for commit {} ({} files) took {} ms")); + .whenComplete( + (ignored, error) -> + LOGGER + .atDebug() + .addArgument(commit.getName()) + .addArgument(analysisTasks.size()) + .addArgument((System.nanoTime() - analysisStartedAt) / 1_000_000L) + .log("File analysis for commit {} ({} files) took {} ms")); pipelinePersistAnalyzedFiles(exporter, analysisTasks, commit.getName()); } - private List> submitFileAnalysisTasks(final AnalysisConfig config, - final Repository repository, final RevCommit commit, + private List> submitFileAnalysisTasks( + final AnalysisConfig config, + final Repository repository, + final RevCommit commit, final List descriptorList) { final String commitAuthor = commit.getAuthorIdent().getEmailAddress(); final int parallelism = resolveFileAnalysisParallelism(); @@ -809,60 +877,69 @@ private List> submitFileAnalysisTasks(final Analysis final List> analysisTasks = new ArrayList<>(descriptorList.size()); for (final FileDescriptor fileDescriptor : descriptorList) { - analysisTasks.add(managedExecutor.supplyAsync(() -> { - try { - ensureNotCancelled(config.landscapeToken()); - inFlightTasks.acquire(); - analysisStatusService.setCurrentAnalyzingFile(config.landscapeToken(), - fileDescriptor.reportedPath); - - LOGGER.atDebug() - .addArgument(fileDescriptor.reportedPath) - .log("Analyzing file: {}"); - - AbstractFileDataHandler fileDataHandler = analyzeFileForCommit(config, repository, - fileDescriptor, commit.getName(), commitAuthor); - if (fileDataHandler == null) { - LOGGER.atWarn() - .addArgument(fileDescriptor.reportedPath) - .log("Analysis of file {} failed - sending minimal file data with updated hash"); - fileDataHandler = createMinimalFileDataHandler(fileDescriptor, commit); - GitMetricCollector.addCommitGitMetrics(fileDataHandler, commitAuthor); - fileDataHandler.setLandscapeToken(config.landscapeToken()); - fileDataHandler.setRepositoryName(config.getRepositoryName()); - } - - return toExportFileData(fileDataHandler, config); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - if (analysisStatusService.isCancellationRequested(config.landscapeToken())) { - throw new AnalysisCancelledException(config.landscapeToken()); - } - LOGGER.warn("File analysis interrupted for {}", fileDescriptor.reportedPath); - final AbstractFileDataHandler minimalHandler = createMinimalFileDataHandler(fileDescriptor, commit); - GitMetricCollector.addCommitGitMetrics(minimalHandler, commitAuthor); - minimalHandler.setLandscapeToken(config.landscapeToken()); - minimalHandler.setRepositoryName(config.getRepositoryName()); - return toExportFileData(minimalHandler, config); - } finally { - inFlightTasks.release(); - analysisStatusService.clearCurrentAnalyzingFile(config.landscapeToken(), - fileDescriptor.reportedPath); - analysisStatusService.incrementAnalyzedFile(config.landscapeToken()); - } - })); + analysisTasks.add( + managedExecutor.supplyAsync( + () -> { + try { + ensureNotCancelled(config.landscapeToken()); + inFlightTasks.acquire(); + analysisStatusService.setCurrentAnalyzingFile( + config.landscapeToken(), fileDescriptor.reportedPath); + + LOGGER + .atDebug() + .addArgument(fileDescriptor.reportedPath) + .log("Analyzing file: {}"); + + AbstractFileDataHandler fileDataHandler = + analyzeFileForCommit( + config, repository, fileDescriptor, commit.getName(), commitAuthor); + if (fileDataHandler == null) { + LOGGER + .atWarn() + .addArgument(fileDescriptor.reportedPath) + .log( + "Analysis of file {} failed - sending minimal file data with updated hash"); + fileDataHandler = createMinimalFileDataHandler(fileDescriptor, commit); + GitMetricCollector.addCommitGitMetrics(fileDataHandler, commitAuthor); + fileDataHandler.setLandscapeToken(config.landscapeToken()); + fileDataHandler.setRepositoryName(config.getRepositoryName()); + } + + return toExportFileData(fileDataHandler, config); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + if (analysisStatusService.isCancellationRequested(config.landscapeToken())) { + throw new AnalysisCancelledException(config.landscapeToken()); + } + LOGGER.warn("File analysis interrupted for {}", fileDescriptor.reportedPath); + final AbstractFileDataHandler minimalHandler = + createMinimalFileDataHandler(fileDescriptor, commit); + GitMetricCollector.addCommitGitMetrics(minimalHandler, commitAuthor); + minimalHandler.setLandscapeToken(config.landscapeToken()); + minimalHandler.setRepositoryName(config.getRepositoryName()); + return toExportFileData(minimalHandler, config); + } finally { + inFlightTasks.release(); + analysisStatusService.clearCurrentAnalyzingFile( + config.landscapeToken(), fileDescriptor.reportedPath); + analysisStatusService.incrementAnalyzedFile(config.landscapeToken()); + } + })); } return analysisTasks; } - private FileData toExportFileData(final AbstractFileDataHandler fileDataHandler, - final AnalysisConfig config) { - return FileDataExportFilter.filter(fileDataHandler.getProtoBufObject(), - config.includeDataStructures()); + private FileData toExportFileData( + final AbstractFileDataHandler fileDataHandler, final AnalysisConfig config) { + return FileDataExportFilter.filter( + fileDataHandler.getProtoBufObject(), config.includeDataStructures()); } - private void pipelinePersistAnalyzedFiles(final DataExporter exporter, - final List> analysisTasks, final String commitId) { + private void pipelinePersistAnalyzedFiles( + final DataExporter exporter, + final List> analysisTasks, + final String commitId) { if (analysisTasks.isEmpty()) { return; } @@ -875,20 +952,30 @@ private void pipelinePersistAnalyzedFiles(final DataExporter exporter, final BlockingQueue completedFiles = new LinkedBlockingQueue<>(); final CountDownLatch analysisFinished = new CountDownLatch(1); - final Thread persistThread = Thread.ofVirtual().name("file-persist-" + commitId) - .start(() -> exporter.persistFilesFromQueueInBatches(completedFiles, analysisFinished, - filePersistBatchSizeProperty, filePersistConcurrencyProperty)); + final Thread persistThread = + Thread.ofVirtual() + .name("file-persist-" + commitId) + .start( + () -> + exporter.persistFilesFromQueueInBatches( + completedFiles, + analysisFinished, + filePersistBatchSizeProperty, + filePersistConcurrencyProperty)); for (final CompletableFuture analysisTask : analysisTasks) { - analysisTask.whenComplete((fileData, error) -> { - if (error != null) { - LOGGER.error("Unexpected analysis failure during pipelined persist for commit {}: {}", - commitId, error.getMessage()); - } - if (fileData != null) { - completedFiles.offer(fileData); - } - }); + analysisTask.whenComplete( + (fileData, error) -> { + if (error != null) { + LOGGER.error( + "Unexpected analysis failure during pipelined persist for commit {}: {}", + commitId, + error.getMessage()); + } + if (fileData != null) { + completedFiles.offer(fileData); + } + }); } CompletableFuture.allOf(analysisTasks.toArray(new CompletableFuture[0])).join(); @@ -907,16 +994,17 @@ private void ensureNotCancelled(final String landscapeToken) { } } - private AbstractFileDataHandler analyzeFileForCommit(final AnalysisConfig config, - final Repository repository, final FileDescriptor fileDescriptor, final String commitSha, + private AbstractFileDataHandler analyzeFileForCommit( + final AnalysisConfig config, + final Repository repository, + final FileDescriptor fileDescriptor, + final String commitSha, final String commitAuthor) { try { - LOGGER.atDebug() - .addArgument(fileDescriptor.reportedPath) - .log("Analyzing file: {}"); + LOGGER.atDebug().addArgument(fileDescriptor.reportedPath).log("Analyzing file: {}"); - AbstractFileDataHandler fileDataHandler = fileAnalysis(config, repository, fileDescriptor, - commitSha); + AbstractFileDataHandler fileDataHandler = + fileAnalysis(config, repository, fileDescriptor, commitSha); if (fileDataHandler == null) { return null; } @@ -925,7 +1013,9 @@ private AbstractFileDataHandler analyzeFileForCommit(final AnalysisConfig config final long fileSize = GitRepositoryHandler.getBlobSize(fileDescriptor.objectId, repository); fileDataHandler.addMetric(CommonFileDataListener.FILE_SIZE, String.valueOf(fileSize)); } catch (IOException e) { - LOGGER.error("File size of file {} could not be analyzed: {}", fileDescriptor.relativePath, + LOGGER.error( + "File size of file {} could not be analyzed: {}", + fileDescriptor.relativePath, e.getMessage()); } @@ -959,36 +1049,43 @@ private void recordSkippedCommitInWalk(final AnalysisConfig config) { } private boolean shouldReconnectParentAcrossSkippedCommits(final AnalysisConfig config) { - return config.skipCommitsWithoutRelevantFileChanges() - || CommitSampler.isEnabled(config); + return config.skipCommitsWithoutRelevantFileChanges() || CommitSampler.isEnabled(config); } - private void createCommitReport(final AnalysisConfig config, final RevCommit commit, - final DataExporter exporter, final String branchName, - final List addedFiles, final List modifiedFiles, - final List deletedFiles, final List unchangedFiles, - final Map> tagsByCommitId, final String lastFullyAnalyzedCommitHash, + private void createCommitReport( + final AnalysisConfig config, + final RevCommit commit, + final DataExporter exporter, + final String branchName, + final List addedFiles, + final List modifiedFiles, + final List deletedFiles, + final List unchangedFiles, + final Map> tagsByCommitId, + final String lastFullyAnalyzedCommitHash, final Set analyzedCommitHashes) throws NotFoundException, IOException, GitAPIException { final CommitReportHandler commitReportHandler = new CommitReportHandler(); - final List parentCommitIds = shouldReconnectParentAcrossSkippedCommits(config) - ? resolveLandscapeParentCommitIds( - commit, lastFullyAnalyzedCommitHash, true, analyzedCommitHashes) - : resolveLandscapeParentCommitIds(commit); + final List parentCommitIds = + shouldReconnectParentAcrossSkippedCommits(config) + ? resolveLandscapeParentCommitIds( + commit, lastFullyAnalyzedCommitHash, true, analyzedCommitHashes) + : resolveLandscapeParentCommitIds(commit); - commitReportHandler.init( - commit.getId().getName(), - parentCommitIds, - branchName); + commitReportHandler.init(commit.getId().getName(), parentCommitIds, branchName); commitReportHandler.setAnalysisFileCount( addedFiles.size() + modifiedFiles.size() + unchangedFiles.size()); - commitReportHandler.setAuthorDate(Timestamp.newBuilder() - .setSeconds(commit.getAuthorIdent().getWhen().getTime() / 1000).build()); - commitReportHandler.setCommitDate(Timestamp.newBuilder() - .setSeconds(commit.getCommitterIdent().getWhen().getTime() / 1000).build()); + commitReportHandler.setAuthorDate( + Timestamp.newBuilder() + .setSeconds(commit.getAuthorIdent().getWhen().getTime() / 1000) + .build()); + commitReportHandler.setCommitDate( + Timestamp.newBuilder() + .setSeconds(commit.getCommitterIdent().getWhen().getTime() / 1000) + .build()); for (final FileDescriptor addedFile : addedFiles) { commitReportHandler.addAdded(addedFile); @@ -1011,10 +1108,9 @@ private void createCommitReport(final AnalysisConfig config, final RevCommit com commitReportHandler.addToken(config.landscapeToken()); commitReportHandler.setRepositoryName(config.getRepositoryName()); - ContributorData contributorData = GitMetricCollector.createContributorData( - commit, - config.landscapeToken(), - config.getRepositoryName()); + ContributorData contributorData = + GitMetricCollector.createContributorData( + commit, config.landscapeToken(), config.getRepositoryName()); commitReportHandler.setAuthor(contributorData); @@ -1022,9 +1118,8 @@ private void createCommitReport(final AnalysisConfig config, final RevCommit com } /** - * Checks if a file is a text file by checking its MIME type. Detects text/*, - * application/json, and application/yaml - * files. + * Checks if a file is a text file by checking its MIME type. Detects text/*, application/json, + * and application/yaml files. * * @param file the file descriptor * @return true if it's a readable text file @@ -1062,19 +1157,21 @@ private boolean isLikelyTextContent(final String fileContent) { } /** - * Analyzes a file and returns the appropriate handler based on file extension. - * Routes code files to parsers and text - * files to basic metric collection. + * Analyzes a file and returns the appropriate handler based on file extension. Routes code files + * to parsers and text files to basic metric collection. * - * @param config the analysis configuration + * @param config the analysis configuration * @param repository the git repository - * @param file the file descriptor - * @param commitSha the commit SHA + * @param file the file descriptor + * @param commitSha the commit SHA * @return the file data handler * @throws IOException if file content cannot be read */ - private AbstractFileDataHandler fileAnalysis(final AnalysisConfig config, - final Repository repository, final FileDescriptor file, final String commitSha) + private AbstractFileDataHandler fileAnalysis( + final AnalysisConfig config, + final Repository repository, + final FileDescriptor file, + final String commitSha) throws IOException { final String fileContent; try { @@ -1090,13 +1187,15 @@ private AbstractFileDataHandler fileAnalysis(final AnalysisConfig config, try { AbstractFileDataHandler fileDataHandler = null; - LOGGER.atDebug() + LOGGER + .atDebug() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Analyzing file {} with size {} bytes"); if (shouldUseMinimalSourceAnalysis(config, fileName, loc)) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(loc) .addArgument(config.maxLocForFullAnalysis().get()) @@ -1105,130 +1204,191 @@ private AbstractFileDataHandler fileAnalysis(final AnalysisConfig config, } // Route to appropriate parser based on file extension - if (fileName.endsWith(".ts") || fileName.endsWith(".tsx") - || fileName.endsWith(".js") || fileName.endsWith(".jsx")) { - final Language tsJsLanguage = fileName.endsWith(".ts") || fileName.endsWith(".tsx") - ? Language.TYPESCRIPT - : Language.JAVASCRIPT; - LOGGER.atInfo() + if (fileName.endsWith(".ts") + || fileName.endsWith(".tsx") + || fileName.endsWith(".js") + || fileName.endsWith(".jsx")) { + final Language tsJsLanguage = + fileName.endsWith(".ts") || fileName.endsWith(".tsx") + ? Language.TYPESCRIPT + : Language.JAVASCRIPT; + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing TypeScript/JavaScript file: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> tsParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, tsJsLanguage); + fileDataHandler = + parseOrFallback( + () -> + tsParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + tsJsLanguage); } else if (fileName.endsWith(".java")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing Java file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> antlrParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.JAVA); + fileDataHandler = + parseOrFallback( + () -> + antlrParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.JAVA); } else if (fileName.endsWith(".py")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing Python file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> pythonParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.PYTHON); + fileDataHandler = + parseOrFallback( + () -> + pythonParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.PYTHON); } else if (fileName.endsWith(".go")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing Go file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> goParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.GO); + fileDataHandler = + parseOrFallback( + () -> + goParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.GO); } else if (fileName.endsWith(".cs")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing C# file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> csharpParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.CSHARP); + fileDataHandler = + parseOrFallback( + () -> + csharpParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.CSHARP); } else if (fileName.endsWith(".rs")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing Rust file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> rustParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.RUST); + fileDataHandler = + parseOrFallback( + () -> + rustParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.RUST); } else if (fileName.endsWith(".kt") || fileName.endsWith(".kts")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing Kotlin file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> kotlinParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.KOTLIN); + fileDataHandler = + parseOrFallback( + () -> + kotlinParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.KOTLIN); } else if (fileName.endsWith(".php")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing PHP file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> phpParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.PHP); + fileDataHandler = + parseOrFallback( + () -> + phpParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.PHP); } else if (fileName.endsWith(".swift")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing Swift file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> swiftParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.SWIFT); + fileDataHandler = + parseOrFallback( + () -> + swiftParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.SWIFT); } else if (fileName.endsWith(".c") || fileName.endsWith(".h")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing C file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> antlrCParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.C); - } else if (fileName.endsWith(".cpp") || fileName.endsWith(".cxx") - || fileName.endsWith(".cc") || fileName.endsWith(".hpp") + fileDataHandler = + parseOrFallback( + () -> + antlrCParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.C); + } else if (fileName.endsWith(".cpp") + || fileName.endsWith(".cxx") + || fileName.endsWith(".cc") + || fileName.endsWith(".hpp") || fileName.endsWith(".hxx")) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("Parsing C++ file with ANTLR: {} (size: {} bytes)"); - fileDataHandler = parseOrFallback( - () -> cppParserService.parseFileContent(fileContent, file.reportedPath, - file.objectId.getName()), - file, fileContent, Language.CPP); + fileDataHandler = + parseOrFallback( + () -> + cppParserService.parseFileContent( + fileContent, file.reportedPath, file.objectId.getName()), + file, + fileContent, + Language.CPP); } else if (isTextFile(file, fileContent)) { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .addArgument(fileContent.length()) .log("📄 Processing detected text file: {} (size: {} bytes)"); - final TextFileDataHandler textHandler = new TextFileDataHandler(file.reportedPath, - Language.PLAINTEXT); + final TextFileDataHandler textHandler = + new TextFileDataHandler(file.reportedPath, Language.PLAINTEXT); textHandler.setFileHash(file.objectId.getName()); textHandler.calculateMetrics(fileContent); @@ -1236,16 +1396,18 @@ private AbstractFileDataHandler fileAnalysis(final AnalysisConfig config, GitMetricCollector.addFileGitMetrics(textHandler, file); fileDataHandler = textHandler; - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .log("✅ Successfully processed text file: {}"); } else { - LOGGER.atInfo() + LOGGER + .atInfo() .addArgument(file.reportedPath) .log("📄 Processing other file (size only): {}"); - final TextFileDataHandler genericHandler = new TextFileDataHandler(file.reportedPath, - Language.LANGUAGE_UNSPECIFIED); + final TextFileDataHandler genericHandler = + new TextFileDataHandler(file.reportedPath, Language.LANGUAGE_UNSPECIFIED); genericHandler.setFileHash(file.objectId.getName()); // Add git metrics @@ -1289,8 +1451,8 @@ private AbstractFileDataHandler createMinimalFileDataHandler( return FallbackFileDataHandlerFactory.create(file, fileContent); } - /* package */ boolean shouldUseMinimalSourceAnalysis(final AnalysisConfig config, - final String fileName, final long loc) { + /* package */ boolean shouldUseMinimalSourceAnalysis( + final AnalysisConfig config, final String fileName, final long loc) { if (config.maxLocForFullAnalysis().isEmpty()) { return false; } @@ -1314,7 +1476,8 @@ private AbstractFileDataHandler parseOrFallback( if (saveCrashedFilesProperty) { DebugFileWriter.saveDebugFile("/logs/crashedfiles/", fileContent, file.fileName); } - LOGGER.atWarn() + LOGGER + .atWarn() .addArgument(file.reportedPath) .addArgument(language) .log("Parser failed for {}, using fallback metrics (language={}, loc, size)"); @@ -1339,7 +1502,8 @@ private void logGlobFilterSummary(final String commitHash, final GlobFilterStats if (!stats.hasExclusions()) { return; } - LOGGER.atDebug() + LOGGER + .atDebug() .addArgument(commitHash) .addArgument(stats.excludedByInclusion) .addArgument(stats.excludedByExclusion) @@ -1348,7 +1512,8 @@ private void logGlobFilterSummary(final String commitHash, final GlobFilterStats + " exclusion patterns"); } - private GlobFilterStats applyGlobFiltering(final List descriptors, + private GlobFilterStats applyGlobFiltering( + final List descriptors, final List restrictMatchers, final List excludeMatchers) { final GlobFilterStats stats = new GlobFilterStats(); @@ -1356,36 +1521,37 @@ private GlobFilterStats applyGlobFiltering(final List descriptor return stats; } - descriptors.removeIf(desc -> { - final java.nio.file.Path path = java.nio.file.Paths.get(desc.relativePath); - - // Restriction (Inclusion) - if specified, it must match one of them - if (restrictMatchers != null && !restrictMatchers.isEmpty()) { - boolean matchesRestrict = false; - for (final java.nio.file.PathMatcher matcher : restrictMatchers) { - if (matcher.matches(path)) { - matchesRestrict = true; - break; + descriptors.removeIf( + desc -> { + final java.nio.file.Path path = java.nio.file.Paths.get(desc.relativePath); + + // Restriction (Inclusion) - if specified, it must match one of them + if (restrictMatchers != null && !restrictMatchers.isEmpty()) { + boolean matchesRestrict = false; + for (final java.nio.file.PathMatcher matcher : restrictMatchers) { + if (matcher.matches(path)) { + matchesRestrict = true; + break; + } + } + if (!matchesRestrict) { + stats.excludedByInclusion++; + return true; // remove because it doesn't match restriction + } } - } - if (!matchesRestrict) { - stats.excludedByInclusion++; - return true; // remove because it doesn't match restriction - } - } - // Exclusion - if it matches any, remove it - if (excludeMatchers != null && !excludeMatchers.isEmpty()) { - for (final java.nio.file.PathMatcher matcher : excludeMatchers) { - if (matcher.matches(path)) { - stats.excludedByExclusion++; - return true; // remove because it matches exclusion + // Exclusion - if it matches any, remove it + if (excludeMatchers != null && !excludeMatchers.isEmpty()) { + for (final java.nio.file.PathMatcher matcher : excludeMatchers) { + if (matcher.matches(path)) { + stats.excludedByExclusion++; + return true; // remove because it matches exclusion + } + } } - } - } - return false; // keep it - }); + return false; // keep it + }); return stats; } @@ -1410,5 +1576,4 @@ private List compileMatchers(final Optional p } return matchers; } - } diff --git a/src/main/java/net/explorviz/code/analysis/service/AnalysisStatusService.java b/src/main/java/net/explorviz/code/analysis/service/AnalysisStatusService.java index 4fb9326..dce604f 100644 --- a/src/main/java/net/explorviz/code/analysis/service/AnalysisStatusService.java +++ b/src/main/java/net/explorviz/code/analysis/service/AnalysisStatusService.java @@ -10,11 +10,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import org.jboss.logging.Logger; -/** - * In-memory status tracking for analysis jobs. - */ +/** In-memory status tracking for analysis jobs. */ @ApplicationScoped public class AnalysisStatusService { @@ -25,51 +22,79 @@ public class AnalysisStatusService { public static final String STATUS_CANCELLED = "cancelled"; private static final String UNKNOWN_TOKEN = "unknown"; - private final Map stateByLandscapeToken = new ConcurrentHashMap<>(); - private final Map> subscribersByLandscapeToken = new ConcurrentHashMap<>(); + private final Map stateByLandscapeToken = + new ConcurrentHashMap<>(); + private final Map> subscribersByLandscapeToken = + new ConcurrentHashMap<>(); private final Set cancellationRequestedTokens = ConcurrentHashMap.newKeySet(); - @Inject - TerminalProgressReporter terminalProgressReporter; + @Inject TerminalProgressReporter terminalProgressReporter; - private record SseSubscriber(SseEventSink sink, Sse sse) { - } + private record SseSubscriber(SseEventSink sink, Sse sse) {} public void markPending(final String landscapeToken) { cancellationRequestedTokens.remove(normalizeToken(landscapeToken)); upsertStateAndNotify(landscapeToken, current -> emptyState(STATUS_PENDING)); } - public void markRunning(final String landscapeToken, final int totalCommits, - final int totalFiles) { + public void markRunning( + final String landscapeToken, final int totalCommits, final int totalFiles) { if (isCancellationRequested(landscapeToken)) { return; } - upsertStateAndNotify(landscapeToken, current -> { - final AnalysisProgressState previous = current == null ? emptyState(STATUS_PENDING) : current; - return new AnalysisProgressState(STATUS_RUNNING, totalCommits, previous.analyzedCommits(), - totalFiles, previous.analyzedFiles(), previous.currentAnalysingFile()); - }); + upsertStateAndNotify( + landscapeToken, + current -> { + final AnalysisProgressState previous = + current == null ? emptyState(STATUS_PENDING) : current; + return new AnalysisProgressState( + STATUS_RUNNING, + totalCommits, + previous.analyzedCommits(), + totalFiles, + previous.analyzedFiles(), + previous.currentAnalysingFile()); + }); } public void incrementAnalyzedCommit(final String landscapeToken) { - updateExistingStateAndNotify(landscapeToken, - state -> new AnalysisProgressState(state.status(), state.totalCommits(), - state.analyzedCommits() + 1, state.totalFiles(), state.analyzedFiles(), - state.currentAnalysingFile())); + updateExistingStateAndNotify( + landscapeToken, + state -> + new AnalysisProgressState( + state.status(), + state.totalCommits(), + state.analyzedCommits() + 1, + state.totalFiles(), + state.analyzedFiles(), + state.currentAnalysingFile())); } public void setCurrentCommitFiles(final String landscapeToken, final int totalFiles) { - updateExistingStateAndNotify(landscapeToken, - state -> new AnalysisProgressState(state.status(), state.totalCommits(), - state.analyzedCommits(), Math.max(0, totalFiles), 0, null)); + updateExistingStateAndNotify( + landscapeToken, + state -> + new AnalysisProgressState( + state.status(), + state.totalCommits(), + state.analyzedCommits(), + Math.max(0, totalFiles), + 0, + null)); } - public void setCurrentAnalyzingFile(final String landscapeToken, final String currentAnalysingFile) { - updateExistingStateAndNotify(landscapeToken, - state -> new AnalysisProgressState(state.status(), state.totalCommits(), - state.analyzedCommits(), state.totalFiles(), state.analyzedFiles(), - currentAnalysingFile)); + public void setCurrentAnalyzingFile( + final String landscapeToken, final String currentAnalysingFile) { + updateExistingStateAndNotify( + landscapeToken, + state -> + new AnalysisProgressState( + state.status(), + state.totalCommits(), + state.analyzedCommits(), + state.totalFiles(), + state.analyzedFiles(), + currentAnalysingFile)); } /** @@ -80,57 +105,90 @@ public void clearCurrentAnalyzingFile(final String landscapeToken, final String if (filePath == null) { return; } - updateExistingStateAndNotify(landscapeToken, state -> { - if (!filePath.equals(state.currentAnalysingFile())) { - return state; - } - return new AnalysisProgressState(state.status(), state.totalCommits(), - state.analyzedCommits(), state.totalFiles(), state.analyzedFiles(), null); - }); + updateExistingStateAndNotify( + landscapeToken, + state -> { + if (!filePath.equals(state.currentAnalysingFile())) { + return state; + } + return new AnalysisProgressState( + state.status(), + state.totalCommits(), + state.analyzedCommits(), + state.totalFiles(), + state.analyzedFiles(), + null); + }); } public void incrementAnalyzedFile(final String landscapeToken) { - updateExistingStateAndNotify(landscapeToken, - state -> new AnalysisProgressState(state.status(), state.totalCommits(), - state.analyzedCommits(), state.totalFiles(), state.analyzedFiles() + 1, - state.currentAnalysingFile())); + updateExistingStateAndNotify( + landscapeToken, + state -> + new AnalysisProgressState( + state.status(), + state.totalCommits(), + state.analyzedCommits(), + state.totalFiles(), + state.analyzedFiles() + 1, + state.currentAnalysingFile())); } public void markFinished(final String landscapeToken) { if (isTerminalStatus(getStatus(landscapeToken).orElse(null))) { return; } - upsertStateAndNotify(landscapeToken, current -> { - if (current == null) { - return emptyState(STATUS_FINISHED); - } - return new AnalysisProgressState(STATUS_FINISHED, current.totalCommits(), - current.totalCommits(), current.totalFiles(), current.totalFiles(), null); - }); + upsertStateAndNotify( + landscapeToken, + current -> { + if (current == null) { + return emptyState(STATUS_FINISHED); + } + return new AnalysisProgressState( + STATUS_FINISHED, + current.totalCommits(), + current.totalCommits(), + current.totalFiles(), + current.totalFiles(), + null); + }); } public void markFailed(final String landscapeToken) { if (isTerminalStatus(getStatus(landscapeToken).orElse(null))) { return; } - upsertStateAndNotify(landscapeToken, current -> { - if (current == null) { - return emptyState(STATUS_FAILED); - } - return new AnalysisProgressState(STATUS_FAILED, current.totalCommits(), - current.analyzedCommits(), current.totalFiles(), current.analyzedFiles(), - current.currentAnalysingFile()); - }); + upsertStateAndNotify( + landscapeToken, + current -> { + if (current == null) { + return emptyState(STATUS_FAILED); + } + return new AnalysisProgressState( + STATUS_FAILED, + current.totalCommits(), + current.analyzedCommits(), + current.totalFiles(), + current.analyzedFiles(), + current.currentAnalysingFile()); + }); } public void markCancelled(final String landscapeToken) { - upsertStateAndNotify(landscapeToken, current -> { - if (current == null) { - return emptyState(STATUS_CANCELLED); - } - return new AnalysisProgressState(STATUS_CANCELLED, current.totalCommits(), - current.analyzedCommits(), current.totalFiles(), current.analyzedFiles(), null); - }); + upsertStateAndNotify( + landscapeToken, + current -> { + if (current == null) { + return emptyState(STATUS_CANCELLED); + } + return new AnalysisProgressState( + STATUS_CANCELLED, + current.totalCommits(), + current.analyzedCommits(), + current.totalFiles(), + current.analyzedFiles(), + null); + }); } public AnalysisCancellationResult requestCancellation(final String landscapeToken) { @@ -166,8 +224,8 @@ public Optional getState(final String landscapeToken) { return Optional.ofNullable(stateByLandscapeToken.get(normalizeToken(landscapeToken))); } - public void subscribeToStateUpdates(final String landscapeToken, - final SseEventSink sink, final Sse sse) { + public void subscribeToStateUpdates( + final String landscapeToken, final SseEventSink sink, final Sse sse) { final String token = normalizeToken(landscapeToken); if (sink == null || sse == null || sink.isClosed()) { return; @@ -178,31 +236,36 @@ public void subscribeToStateUpdates(final String landscapeToken, .computeIfAbsent(token, ignored -> ConcurrentHashMap.newKeySet()) .add(subscriber); - final AnalysisProgressState currentState = stateByLandscapeToken.getOrDefault(token, emptyState(STATUS_PENDING)); + final AnalysisProgressState currentState = + stateByLandscapeToken.getOrDefault(token, emptyState(STATUS_PENDING)); sendState(token, subscriber, currentState); } - private void updateExistingStateAndNotify(final String landscapeToken, + private void updateExistingStateAndNotify( + final String landscapeToken, final Function update) { final String token = normalizeToken(landscapeToken); - final AnalysisProgressState updatedState = stateByLandscapeToken.computeIfPresent(token, - (ignored, current) -> { - if (isTerminalStatus(current.status())) { - return current; - } - return update.apply(current); - }); + final AnalysisProgressState updatedState = + stateByLandscapeToken.computeIfPresent( + token, + (ignored, current) -> { + if (isTerminalStatus(current.status())) { + return current; + } + return update.apply(current); + }); if (updatedState != null) { notifySubscribers(token, updatedState); } } - private void upsertStateAndNotify(final String landscapeToken, + private void upsertStateAndNotify( + final String landscapeToken, final Function update) { final String token = normalizeToken(landscapeToken); - final AnalysisProgressState updatedState = stateByLandscapeToken.compute(token, - (ignored, current) -> update.apply(current)); + final AnalysisProgressState updatedState = + stateByLandscapeToken.compute(token, (ignored, current) -> update.apply(current)); if (updatedState != null) { notifySubscribers(token, updatedState); @@ -231,7 +294,9 @@ private void notifySubscribers(final String landscapeToken, final AnalysisProgre subscribers.forEach(subscriber -> sendState(landscapeToken, subscriber, state)); } - private void sendState(final String landscapeToken, final SseSubscriber subscriber, + private void sendState( + final String landscapeToken, + final SseSubscriber subscriber, final AnalysisProgressState state) { final SseEventSink sink = subscriber.sink(); if (sink.isClosed()) { @@ -239,15 +304,19 @@ private void sendState(final String landscapeToken, final SseSubscriber subscrib return; } - sink.send(subscriber.sse().newEventBuilder() - .mediaType(MediaType.APPLICATION_JSON_TYPE) - .data(AnalysisProgressState.class, state) - .build()) - .whenComplete((ignored, throwable) -> { - if (throwable != null || sink.isClosed()) { - removeSubscriber(landscapeToken, subscriber); - } - }); + sink.send( + subscriber + .sse() + .newEventBuilder() + .mediaType(MediaType.APPLICATION_JSON_TYPE) + .data(AnalysisProgressState.class, state) + .build()) + .whenComplete( + (ignored, throwable) -> { + if (throwable != null || sink.isClosed()) { + removeSubscriber(landscapeToken, subscriber); + } + }); if (isTerminalStatus(state.status())) { sink.close(); diff --git a/src/main/java/net/explorviz/code/analysis/service/ApplicationPath.java b/src/main/java/net/explorviz/code/analysis/service/ApplicationPath.java index 6c7e5bb..65683e1 100644 --- a/src/main/java/net/explorviz/code/analysis/service/ApplicationPath.java +++ b/src/main/java/net/explorviz/code/analysis/service/ApplicationPath.java @@ -6,5 +6,4 @@ * @param name display name used in landscape / export * @param root path relative to repository root; may be empty for repository root */ -public record ApplicationPath(String name, String root) { -} +public record ApplicationPath(String name, String root) {} diff --git a/src/main/java/net/explorviz/code/analysis/service/CommitSampler.java b/src/main/java/net/explorviz/code/analysis/service/CommitSampler.java index a27a0d8..51c9c46 100644 --- a/src/main/java/net/explorviz/code/analysis/service/CommitSampler.java +++ b/src/main/java/net/explorviz/code/analysis/service/CommitSampler.java @@ -11,16 +11,14 @@ import org.eclipse.jgit.revwalk.RevCommit; /** - * Selects which commits in a branch walk receive full file analysis. Commits that are not - * selected are skipped entirely during the analysis walk. + * Selects which commits in a branch walk receive full file analysis. Commits that are not selected + * are skipped entirely during the analysis walk. */ public final class CommitSampler { private CommitSampler() {} - /** - * Returns {@code true} when commit sampling is configured. - */ + /** Returns {@code true} when commit sampling is configured. */ public static boolean isEnabled(final AnalysisConfig config) { return config.commitSamplingInterval().filter(interval -> interval > 1).isPresent() || config.commitSamplingPeriod().isPresent(); @@ -39,8 +37,7 @@ public static Set selectFullyAnalyzedIndices( return Set.of(); } - final List commitTimes = - commits.stream().map(RevCommit::getCommitTime).toList(); + final List commitTimes = commits.stream().map(RevCommit::getCommitTime).toList(); return selectFullyAnalyzedIndicesFromCommitTimes(commitTimes, config); } diff --git a/src/main/java/net/explorviz/code/analysis/service/ConcurrentAnalysisService.java b/src/main/java/net/explorviz/code/analysis/service/ConcurrentAnalysisService.java index 5b62cdf..1281f88 100644 --- a/src/main/java/net/explorviz/code/analysis/service/ConcurrentAnalysisService.java +++ b/src/main/java/net/explorviz/code/analysis/service/ConcurrentAnalysisService.java @@ -19,19 +19,17 @@ import org.slf4j.LoggerFactory; /** - * Wrapper service that handles concurrent analysis requests safely. Uses an executor service to queue and process - * analysis requests sequentially + * Wrapper service that handles concurrent analysis requests safely. Uses an executor service to + * queue and process analysis requests sequentially */ @ApplicationScoped public class ConcurrentAnalysisService { private static final Logger LOGGER = LoggerFactory.getLogger(ConcurrentAnalysisService.class); - @Inject - /* package */ AnalysisService analysisService; // NOCS + @Inject /* package */ AnalysisService analysisService; - @Inject - /* package */ AnalysisStatusService analysisStatusService; // NOCS + @Inject /* package */ AnalysisStatusService analysisStatusService; // Single-threaded executor to process analysis requests sequentially private ExecutorService executorService; @@ -40,11 +38,13 @@ public class ConcurrentAnalysisService { @PostConstruct public void init() { - executorService = Executors.newSingleThreadExecutor(r -> { - final Thread thread = new Thread(r, "code-analysis-worker"); - thread.setDaemon(false); // Keep thread alive for pending requests - return thread; - }); + executorService = + Executors.newSingleThreadExecutor( + r -> { + final Thread thread = new Thread(r, "code-analysis-worker"); + thread.setDaemon(false); // Keep thread alive for pending requests + return thread; + }); LOGGER.info("ConcurrentAnalysisService initialized with sequential request processing"); } @@ -66,36 +66,43 @@ public void shutdown() { } } - public CompletableFuture analyzeAndSendRepoAsync(final AnalysisConfig config, - final DataExporter exporter) { + public CompletableFuture analyzeAndSendRepoAsync( + final AnalysisConfig config, final DataExporter exporter) { final String landscapeToken = config.landscapeToken(); final String repoUrl = config.repoRemoteUrl().orElse("unknown"); LOGGER.info("📥 Queuing analysis request for repository: {}", repoUrl); - final CompletableFuture jobFuture = CompletableFuture.runAsync(() -> { - try { - LOGGER.info("⚙️ Processing analysis request for repository: {}", repoUrl); - analysisService.analyzeAndSendRepo(config, exporter); - LOGGER.info("✅ Completed analysis for repository: {}", repoUrl); - } catch (AnalysisCancelledException e) { - LOGGER.info("🛑 Analysis cancelled for repository: {}", repoUrl); - throw e; - } catch (IOException | GitAPIException | NotFoundException - | PropertyNotDefinedException e) { - LOGGER.error("❌ Analysis failed for repository: {}", repoUrl, e); - throw new RuntimeException("Analysis failed: " + e.getMessage(), e); - } - }, executorService); + final CompletableFuture jobFuture = + CompletableFuture.runAsync( + () -> { + try { + LOGGER.info("⚙️ Processing analysis request for repository: {}", repoUrl); + analysisService.analyzeAndSendRepo(config, exporter); + LOGGER.info("✅ Completed analysis for repository: {}", repoUrl); + } catch (AnalysisCancelledException e) { + LOGGER.info("🛑 Analysis cancelled for repository: {}", repoUrl); + throw e; + } catch (IOException + | GitAPIException + | NotFoundException + | PropertyNotDefinedException e) { + LOGGER.error("❌ Analysis failed for repository: {}", repoUrl, e); + throw new RuntimeException("Analysis failed: " + e.getMessage(), e); + } + }, + executorService); jobsByLandscapeToken.put(landscapeToken, jobFuture); - jobFuture.whenComplete((ignored, error) -> jobsByLandscapeToken.remove(landscapeToken, jobFuture)); + jobFuture.whenComplete( + (ignored, error) -> jobsByLandscapeToken.remove(landscapeToken, jobFuture)); return jobFuture; } public AnalysisCancellationResult cancelAnalysis(final String landscapeToken) { - final AnalysisCancellationResult result = analysisStatusService.requestCancellation(landscapeToken); + final AnalysisCancellationResult result = + analysisStatusService.requestCancellation(landscapeToken); if (result != AnalysisCancellationResult.CANCELLED) { return result; } @@ -105,7 +112,8 @@ public AnalysisCancellationResult cancelAnalysis(final String landscapeToken) { jobFuture.cancel(true); LOGGER.info("🛑 Cancelled analysis job for landscapeToken={}", landscapeToken); } else { - LOGGER.info("🛑 Marked analysis as cancelled for landscapeToken={} (no active worker future)", + LOGGER.info( + "🛑 Marked analysis as cancelled for landscapeToken={} (no active worker future)", landscapeToken); } diff --git a/src/main/java/net/explorviz/code/analysis/service/GithubFetcherService.java b/src/main/java/net/explorviz/code/analysis/service/GithubFetcherService.java index 144ad9f..823db87 100644 --- a/src/main/java/net/explorviz/code/analysis/service/GithubFetcherService.java +++ b/src/main/java/net/explorviz/code/analysis/service/GithubFetcherService.java @@ -51,9 +51,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Service to fetch GitHub social data for a given repository. - */ +/** Service to fetch GitHub social data for a given repository. */ @ApplicationScoped public class GithubFetcherService { private static final Logger LOGGER = LoggerFactory.getLogger(GithubFetcherService.class); @@ -70,9 +68,10 @@ public class GithubFetcherService { private long tqueryTotal = 0; private long numUpdates = 0; - Optional> fetchSocialData( - final AnalysisConfig config, final DataExporter exporter, final ManagedExecutor managedExecutor) { + final AnalysisConfig config, + final DataExporter exporter, + final ManagedExecutor managedExecutor) { final int days = config.socialDataTimeFrameDays().orElse(365); final Date endDate = determineEndDate(config); final Date startDate = Date.from(endDate.toInstant().minus(days, ChronoUnit.DAYS)); @@ -83,7 +82,8 @@ Optional> fetchSocialData( final AnalysisConfig config, final DataExporter exporter, ManagedExecutor managedExecutor, - Date startDate, Date endDate) { + Date startDate, + Date endDate) { if (!config.fetchSocialData()) { LOGGER.info("Skipping GitHub social data fetch, not enabled in config."); return Optional.empty(); @@ -102,10 +102,13 @@ Optional> fetchSocialData( preInitializeRemoteState(config, exporter, config.branch().orElse("main"), ""); return Optional.of( - managedExecutor.runAsync(() -> { + managedExecutor.runAsync( + () -> { try { - LOGGER.info("Starting independent background fetch for GitHub Social Data from {} to {}", - startDate, endDate); + LOGGER.info( + "Starting independent background fetch for GitHub Social Data from {} to {}", + startDate, + endDate); fetchSocialDataInRange( repoSubString.get(), startDate, @@ -116,9 +119,7 @@ Optional> fetchSocialData( } catch (final Exception e) { LOGGER.error("Background social fetch aborted: {}", e.getMessage()); } - } - ) - ); + })); } /** @@ -138,22 +139,30 @@ public void fetchSocialDataInRange( final Date endDate, final DataExporter exporter, final String landscapeToken, - final String githubToken - ) { - try (DynamicGraphQLClient githubClient = DynamicGraphQLClientBuilder.newBuilder() - .url(GITHUB_URL) - .header("Authorization", "Bearer " + githubToken) - .build()) { + final String githubToken) { + try (DynamicGraphQLClient githubClient = + DynamicGraphQLClientBuilder.newBuilder() + .url(GITHUB_URL) + .header("Authorization", "Bearer " + githubToken) + .build()) { final long tStart = System.nanoTime(); validateToken(githubClient); - fetchData(exporter, githubClient, landscapeToken, repoOwnerAndName, "issues", startDate, endDate); - fetchData(exporter, githubClient, landscapeToken, repoOwnerAndName, "pullRequests", startDate, endDate); + fetchData( + exporter, githubClient, landscapeToken, repoOwnerAndName, "issues", startDate, endDate); + fetchData( + exporter, + githubClient, + landscapeToken, + repoOwnerAndName, + "pullRequests", + startDate, + endDate); LOGGER.info("✅ Completed all social fetch queries."); long tqueryAvg = tqueryTotal / numUpdates; - long tmapAvg = tmapTotal / numUpdates; + long tmapAvg = tmapTotal / numUpdates; long tpersistAvg = tpersistTotal / numUpdates; LOGGER.info( @@ -181,7 +190,8 @@ private Date determineEndDate(AnalysisConfig config) { endDate = Date.from(Instant.parse(dateStr)); } catch (final DateTimeParseException e) { // Fallback to simple date parsing "YYYY-MM-DD" - endDate = Date.from(LocalDate.parse(dateStr).atStartOfDay(ZoneId.systemDefault()).toInstant()); + endDate = + Date.from(LocalDate.parse(dateStr).atStartOfDay(ZoneId.systemDefault()).toInstant()); } } return endDate; @@ -189,7 +199,8 @@ private Date determineEndDate(AnalysisConfig config) { private Optional extractGithubRepoSubString(String remoteUrl) { if (!remoteUrl.contains("github.com")) { - LOGGER.info("Skipping GitHub collaboration data fetch, not a GitHub repository: {}", remoteUrl); + LOGGER.info( + "Skipping GitHub collaboration data fetch, not a GitHub repository: {}", remoteUrl); return Optional.empty(); } final String[] parts = remoteUrl.split("github.com[:/]"); @@ -200,15 +211,16 @@ private Optional extractGithubRepoSubString(String remoteUrl) { return Optional.of(parts[1].replace(".git", "")); } - void preInitializeRemoteState(final AnalysisConfig config, final DataExporter exporter, - final String branch, final String repositoryUrl) { + void preInitializeRemoteState( + final AnalysisConfig config, + final DataExporter exporter, + final String branch, + final String repositoryUrl) { if (exporter.isRemote()) { try { final String resolvedRepositoryUrl = RepositoryFileUrlBuilder.resolveRepositoryUrl( - repositoryUrl.isBlank() - ? config.repoRemoteUrl() - : Optional.of(repositoryUrl), + repositoryUrl.isBlank() ? config.repoRemoteUrl() : Optional.of(repositoryUrl), "") .orElse(""); exporter.getStateData( @@ -226,27 +238,32 @@ void preInitializeRemoteState(final AnalysisConfig config, final DataExporter ex @SuppressWarnings("try") private void validateToken(DynamicGraphQLClient client) { - Document viewerQuery = document( - operation( - field("viewer", - field("login") - ) - ) - ); + Document viewerQuery = document(operation(field("viewer", field("login")))); try { client.executeSync(viewerQuery); } catch (Exception e) { - throw new IllegalArgumentException("Invalid GitHub token, aborting GitHub Data fetching: " + e.getMessage(), e); + throw new IllegalArgumentException( + "Invalid GitHub token, aborting GitHub Data fetching: " + e.getMessage(), e); } } - private void fetchData(final DataExporter exporter, final DynamicGraphQLClient client, final String landscapeToken, - final String repoOwnerName, final String resourceType, final Date startDate, final Date endDate) { + private void fetchData( + final DataExporter exporter, + final DynamicGraphQLClient client, + final String landscapeToken, + final String repoOwnerName, + final String resourceType, + final Date startDate, + final Date endDate) { final String[] splits = repoOwnerName.split("/"); - final GithubPager pager = new GithubPager( - client, buildQuery(resourceType), Map.of("owner", splits[0], "name", splits[1]), resourceType); - - final Instant start = startDate.toInstant(); + final GithubPager pager = + new GithubPager( + client, + buildQuery(resourceType), + Map.of("owner", splits[0], "name", splits[1]), + resourceType); + + final Instant start = startDate.toInstant(); final Instant end = endDate.toInstant(); Instant currentDate = end; @@ -263,7 +280,8 @@ private void fetchData(final DataExporter exporter, final DynamicGraphQLClient c if (date.isEmpty()) { LOGGER.warn("Could not parse date for node {}.", node.getInt("number", -1)); } else { - final Instant nodeDate = Instant.ofEpochSecond(date.get().getSeconds(), date.get().getNanos()); + final Instant nodeDate = + Instant.ofEpochSecond(date.get().getSeconds(), date.get().getNanos()); if (nodeDate.isAfter(end)) { continue; } @@ -282,20 +300,34 @@ private void fetchData(final DataExporter exporter, final DynamicGraphQLClient c logProgress(resourceType, pager, currentDate, start, end, events.size(), tmap, tpersist); } if (pager.isFailed()) { - LOGGER.error("{}: Fetch failed after {}/{} nodes", resourceType, pager.getSeen(), pager.getTotalCount()); + LOGGER.error( + "{}: Fetch failed after {}/{} nodes", + resourceType, + pager.getSeen(), + pager.getTotalCount()); } } private void logProgress( - final String resourceType, final GithubPager pager, final Instant currentTime, - final Instant startTime, final Instant endTime, final int batchSize, long tmap, long tpersist) { + final String resourceType, + final GithubPager pager, + final Instant currentTime, + final Instant startTime, + final Instant endTime, + final int batchSize, + long tmap, + long tpersist) { final double windowMs = Math.max(1, Duration.between(startTime, endTime).toMillis()); final double estimatePercent = Math.clamp(100 * Duration.between(currentTime, endTime).toMillis() / windowMs, 0, 100); LOGGER.info( "{}: {}% of time window (at {}), {} nodes processed, sending {} events. Rate limit: {}", - resourceType, String.format("%.1f", estimatePercent), currentTime, - pager.getSeen(), batchSize, pager.getLastRateLimit()); + resourceType, + String.format("%.1f", estimatePercent), + currentTime, + pager.getSeen(), + batchSize, + pager.getLastRateLimit()); tmapTotal += tmap; tpersistTotal += tpersist; tqueryTotal += pager.getLastQueryNanos(); @@ -313,131 +345,182 @@ private Document buildQuery(final String resourceType) { final Variable name = var("name", nonNull("String")); final Variable cursor = var("cursor", "String"); - return document(operation( - vars(owner, name, cursor), - field("repository", - args(arg("owner", owner), arg("name", name)), - field(resourceType, - args( - arg("first", PAGE_SIZE), - arg("after", cursor), - arg("orderBy", inputObject( - prop("field", gqlEnum("UPDATED_AT")), - prop("direction", gqlEnum("DESC"))))), - field("totalCount"), - field("pageInfo", - field("hasNextPage"), - field("endCursor")), - field("nodes", - field("__typename"), - field("updatedAt"), - "issues".equals(resourceType) - ? on("Issue", - field("id"), - field("number"), - field("title"), - field("body"), - field("url"), - field("state"), - field("createdAt"), - field("closedAt"), - field("author", - field("login"), - field("avatarUrl"), - on("User", - field("name"), - field("email") - ) - ), - field("labels", args(arg("first", NUM_LABELS)), - field("nodes", field("name")) - ), - field("timelineItems", args(arg("first", NUM_TIMELINE_ITEMS)), - field("nodes", - field("__typename"), - on("ClosedEvent", field("createdAt"), - field("actor", field("login"), field("avatarUrl"), - on("User", field("email")))), - on("ReopenedEvent", field("createdAt"), - field("actor", field("login"), field("avatarUrl"), - on("User", field("email")))), - on("IssueComment", field("createdAt"), - field("author", field("login"), field("avatarUrl"), - on("User", field("email")))) - ) - ) - ) - : on("PullRequest", - field("commits", args(arg("first", NUM_COMMITS)), - field("nodes", field("commit", field("oid"))) - ), - field("closingIssuesReferences", args(arg("first", NUM_ISSUE_REFERENCES)), - field("nodes", field("number"))), - field("mergeCommit", field("oid")), - field("id"), - field("number"), - field("title"), - field("body"), - field("url"), - field("state"), - field("createdAt"), - field("closedAt"), - field("mergedAt"), - field("author", - field("login"), - field("avatarUrl"), - on("User", - field("name"), - field("email") - ) - ), - field("labels", args(arg("first", NUM_LABELS)), - field("nodes", field("name")) - ), - field("timelineItems", args(arg("first", NUM_TIMELINE_ITEMS)), - field("nodes", - field("__typename"), - on("ClosedEvent", field("createdAt"), - field("actor", field("login"), field("avatarUrl"), - on("User", field("email")))), - on("MergedEvent", field("createdAt"), - field("actor", field("login"), field("avatarUrl"), - on("User", field("email")))), - on("ReopenedEvent", field("createdAt"), - field("actor", field("login"), field("avatarUrl"), - on("User", field("email")))), - on("IssueComment", field("createdAt"), - field("author", field("login"), field("avatarUrl"), - on("User", field("email")))), - on("HeadRefForcePushedEvent", field("createdAt"), - field("actor", field("login"), field("avatarUrl"), - on("User", field("email")))), - on("PullRequestCommit", - field("commit", - field("authoredDate"), - field("author", - field("email"), - field("user", field("login"), field("avatarUrl")) - ) - ) - ), - on("PullRequestReview", field("createdAt"), - field("author", field("login"), field("avatarUrl"), - on("User", field("email")))) - ) - ) - ) - ) - ) - ), - field("rateLimit", field("cost"), field("remaining"), field("resetAt")))); + return document( + operation( + vars(owner, name, cursor), + field( + "repository", + args(arg("owner", owner), arg("name", name)), + field( + resourceType, + args( + arg("first", PAGE_SIZE), + arg("after", cursor), + arg( + "orderBy", + inputObject( + prop("field", gqlEnum("UPDATED_AT")), + prop("direction", gqlEnum("DESC"))))), + field("totalCount"), + field("pageInfo", field("hasNextPage"), field("endCursor")), + field( + "nodes", + field("__typename"), + field("updatedAt"), + "issues".equals(resourceType) + ? on( + "Issue", + field("id"), + field("number"), + field("title"), + field("body"), + field("url"), + field("state"), + field("createdAt"), + field("closedAt"), + field( + "author", + field("login"), + field("avatarUrl"), + on("User", field("name"), field("email"))), + field( + "labels", + args(arg("first", NUM_LABELS)), + field("nodes", field("name"))), + field( + "timelineItems", + args(arg("first", NUM_TIMELINE_ITEMS)), + field( + "nodes", + field("__typename"), + on( + "ClosedEvent", + field("createdAt"), + field( + "actor", + field("login"), + field("avatarUrl"), + on("User", field("email")))), + on( + "ReopenedEvent", + field("createdAt"), + field( + "actor", + field("login"), + field("avatarUrl"), + on("User", field("email")))), + on( + "IssueComment", + field("createdAt"), + field( + "author", + field("login"), + field("avatarUrl"), + on("User", field("email"))))))) + : on( + "PullRequest", + field( + "commits", + args(arg("first", NUM_COMMITS)), + field("nodes", field("commit", field("oid")))), + field( + "closingIssuesReferences", + args(arg("first", NUM_ISSUE_REFERENCES)), + field("nodes", field("number"))), + field("mergeCommit", field("oid")), + field("id"), + field("number"), + field("title"), + field("body"), + field("url"), + field("state"), + field("createdAt"), + field("closedAt"), + field("mergedAt"), + field( + "author", + field("login"), + field("avatarUrl"), + on("User", field("name"), field("email"))), + field( + "labels", + args(arg("first", NUM_LABELS)), + field("nodes", field("name"))), + field( + "timelineItems", + args(arg("first", NUM_TIMELINE_ITEMS)), + field( + "nodes", + field("__typename"), + on( + "ClosedEvent", + field("createdAt"), + field( + "actor", + field("login"), + field("avatarUrl"), + on("User", field("email")))), + on( + "MergedEvent", + field("createdAt"), + field( + "actor", + field("login"), + field("avatarUrl"), + on("User", field("email")))), + on( + "ReopenedEvent", + field("createdAt"), + field( + "actor", + field("login"), + field("avatarUrl"), + on("User", field("email")))), + on( + "IssueComment", + field("createdAt"), + field( + "author", + field("login"), + field("avatarUrl"), + on("User", field("email")))), + on( + "HeadRefForcePushedEvent", + field("createdAt"), + field( + "actor", + field("login"), + field("avatarUrl"), + on("User", field("email")))), + on( + "PullRequestCommit", + field( + "commit", + field("authoredDate"), + field( + "author", + field("email"), + field( + "user", + field("login"), + field("avatarUrl"))))), + on( + "PullRequestReview", + field("createdAt"), + field( + "author", + field("login"), + field("avatarUrl"), + on("User", field("email")))))))))), + field("rateLimit", field("cost"), field("remaining"), field("resetAt")))); } List mapToEvents( JsonObject node, String landscapeToken, String repositoryName) { List events = new ArrayList<>(); - TrackableResourceEvent.Builder baseBuilder = parseBaseResource(node, landscapeToken, repositoryName); + TrackableResourceEvent.Builder baseBuilder = + parseBaseResource(node, landscapeToken, repositoryName); if (baseBuilder == null) { return events; } @@ -469,9 +552,8 @@ TrackableResourceEvent.Builder parseBaseResource( JsonObject node, String landscapeToken, String repositoryName) { String typeName = getJsonString(node, "__typename", "Unknown"); - final TrackableResourceType resourceType = typeName.equals("Issue") - ? TrackableResourceType.ISSUE - : TrackableResourceType.PULL_REQUEST; + final TrackableResourceType resourceType = + typeName.equals("Issue") ? TrackableResourceType.ISSUE : TrackableResourceType.PULL_REQUEST; // String rawState = getJsonString(node, "state", "OPEN"); @@ -486,7 +568,6 @@ TrackableResourceEvent.Builder parseBaseResource( avatarUrl = getJsonString(authorObj, "avatarUrl", ""); } - // Parse Labels List labelNames = new ArrayList<>(); if (node.containsKey("labels") && !node.isNull("labels")) { @@ -534,13 +615,14 @@ TrackableResourceEvent.Builder parseBaseResource( } } - ContributorData actor = ContributorData.newBuilder() - .setLandscapeToken(landscapeToken) - .setRepositoryName(repoName) - .setEmail(authorEmail) - .setGithubLogin(authorLogin) - .setAvatarUrl(avatarUrl) - .build(); + ContributorData actor = + ContributorData.newBuilder() + .setLandscapeToken(landscapeToken) + .setRepositoryName(repoName) + .setEmail(authorEmail) + .setGithubLogin(authorLogin) + .setAvatarUrl(avatarUrl) + .build(); return TrackableResourceEvent.newBuilder() .setLandscapeToken(landscapeToken) @@ -566,12 +648,14 @@ private List generateLifecycleEvents( if (timestamp.isEmpty()) { LOGGER.warn("Skipping CREATE event for id={}: missing or invalid createdAt", id); } else { - events.add(baseBuilder.clone() - .setAnnotationType(AnnotationType.CREATE) - .setAnnotationId(id + "-" + AnnotationType.CREATE.name()) - .setNewState(ResourceState.OPEN) - .setEventTimestamp(timestamp.get()) - .build()); + events.add( + baseBuilder + .clone() + .setAnnotationType(AnnotationType.CREATE) + .setAnnotationId(id + "-" + AnnotationType.CREATE.name()) + .setNewState(ResourceState.OPEN) + .setEventTimestamp(timestamp.get()) + .build()); } } @@ -580,24 +664,28 @@ private List generateLifecycleEvents( if (timestamp.isEmpty()) { LOGGER.warn("Skipping MERGE event for id={}: missing or invalid mergedAt", id); } else { - events.add(baseBuilder.clone() - .setAnnotationType(AnnotationType.MERGE) - .setAnnotationId(id + "-" + AnnotationType.MERGE.name()) - .setNewState(ResourceState.MERGED) - .setEventTimestamp(timestamp.get()) - .build()); + events.add( + baseBuilder + .clone() + .setAnnotationType(AnnotationType.MERGE) + .setAnnotationId(id + "-" + AnnotationType.MERGE.name()) + .setNewState(ResourceState.MERGED) + .setEventTimestamp(timestamp.get()) + .build()); } } else if (node.containsKey("closedAt") && !node.isNull("closedAt")) { Optional timestamp = parseTimestamp(node.getString("closedAt")); if (timestamp.isEmpty()) { LOGGER.warn("Skipping CLOSE event for id={}: missing or invalid closedAt", id); } else { - events.add(baseBuilder.clone() - .setAnnotationType(AnnotationType.CLOSE) - .setAnnotationId(id + "-" + AnnotationType.CLOSE.name()) - .setNewState(ResourceState.CLOSED) - .setEventTimestamp(timestamp.get()) - .build()); + events.add( + baseBuilder + .clone() + .setAnnotationType(AnnotationType.CLOSE) + .setAnnotationId(id + "-" + AnnotationType.CLOSE.name()) + .setNewState(ResourceState.CLOSED) + .setEventTimestamp(timestamp.get()) + .build()); } } return events; @@ -612,13 +700,16 @@ private List parseTimelineEvents( // Process Timeline Items if (node.containsKey("timelineItems") && !node.isNull("timelineItems")) { - jakarta.json.JsonArray timelineNodes = node.getJsonObject("timelineItems").getJsonArray("nodes"); + jakarta.json.JsonArray timelineNodes = + node.getJsonObject("timelineItems").getJsonArray("nodes"); for (int i = 0; i < timelineNodes.size(); i++) { JsonObject eventNode = timelineNodes.getJsonObject(i); String type = eventNode.getString("__typename", ""); // skip closedEvent if merged like generateLifecycleEvents - if ("ClosedEvent".equals(type) && node.containsKey("mergedAt") && !node.isNull("mergedAt")) { + if ("ClosedEvent".equals(type) + && node.containsKey("mergedAt") + && !node.isNull("mergedAt")) { continue; } @@ -627,7 +718,8 @@ private List parseTimelineEvents( continue; } - String timestamp = eventNode.containsKey("createdAt") ? eventNode.getString("createdAt") : ""; + String timestamp = + eventNode.containsKey("createdAt") ? eventNode.getString("createdAt") : ""; // Determine Actor String eventActorLogin = ""; @@ -645,7 +737,8 @@ private List parseTimelineEvents( eventActorAvatarUrl = getJsonString(authorObj, "avatarUrl", ""); } - ResourceState newState = ResourceState.UNCHANGED; // Default to unchanged and update on transition only + ResourceState newState = + ResourceState.UNCHANGED; // Default to unchanged and update on transition only // Special handling for PullRequestCommit if ("PullRequestCommit".equals(type)) { @@ -671,25 +764,29 @@ private List parseTimelineEvents( newState = ResourceState.MERGED; } - ContributorData eventActor = ContributorData.newBuilder() - .setLandscapeToken(baseActor.getLandscapeToken()) - .setRepositoryName(baseActor.getRepositoryName()) - .setGithubLogin(eventActorLogin) - .setAvatarUrl(eventActorAvatarUrl) - .setEmail(eventActorEmail) - .build(); + ContributorData eventActor = + ContributorData.newBuilder() + .setLandscapeToken(baseActor.getLandscapeToken()) + .setRepositoryName(baseActor.getRepositoryName()) + .setGithubLogin(eventActorLogin) + .setAvatarUrl(eventActorAvatarUrl) + .setEmail(eventActorEmail) + .build(); Optional parsedTimestamp = parseTimestamp(timestamp); if (parsedTimestamp.isEmpty()) { - LOGGER.warn("Skipping {} event for id={}: missing or invalid timestamp", annotationType, id); + LOGGER.warn( + "Skipping {} event for id={}: missing or invalid timestamp", annotationType, id); } else { - events.add(baseBuilder.clone() - .setAnnotationType(annotationType) - .setAnnotationId(id + "-" + type + "-" + i) - .setEventTimestamp(parsedTimestamp.get()) - .setActor(eventActor) - .setNewState(newState) - .build()); + events.add( + baseBuilder + .clone() + .setAnnotationType(annotationType) + .setAnnotationId(id + "-" + type + "-" + i) + .setEventTimestamp(parsedTimestamp.get()) + .setActor(eventActor) + .setNewState(newState) + .build()); } } } @@ -722,10 +819,11 @@ public Optional parseTimestamp(String isoTimestamp) { try { Instant instant = Instant.parse(isoTimestamp); - return Optional.of(Timestamp.newBuilder() - .setSeconds(instant.getEpochSecond()) - .setNanos(instant.getNano()) - .build()); + return Optional.of( + Timestamp.newBuilder() + .setSeconds(instant.getEpochSecond()) + .setNanos(instant.getNano()) + .build()); } catch (DateTimeParseException e) { return Optional.empty(); } diff --git a/src/main/java/net/explorviz/code/analysis/service/GithubPager.java b/src/main/java/net/explorviz/code/analysis/service/GithubPager.java index fad2d7d..4ae9e30 100644 --- a/src/main/java/net/explorviz/code/analysis/service/GithubPager.java +++ b/src/main/java/net/explorviz/code/analysis/service/GithubPager.java @@ -33,7 +33,10 @@ final class GithubPager { private int lastRateLimit; private long lastQueryNanos; - GithubPager(final DynamicGraphQLClient client, final Document query, final Map variables, + GithubPager( + final DynamicGraphQLClient client, + final Document query, + final Map variables, final String resourceType) { this.client = client; this.query = query; @@ -57,7 +60,8 @@ public Optional nextPage() { final Response response = client.executeSync(query, vars); lastQueryNanos = System.nanoTime() - t0; if (!response.hasError()) { - final JsonObject data = response.getData().getJsonObject("repository").getJsonObject(resourceType); + final JsonObject data = + response.getData().getJsonObject("repository").getJsonObject(resourceType); if (totalCount < 0) { totalCount = data.getInt("totalCount", -1); } @@ -65,18 +69,25 @@ public Optional nextPage() { checkRateLimit(response.getData()); final JsonObject pageInfo = data.getJsonObject("pageInfo"); hasNextPage = pageInfo.getBoolean("hasNextPage", false); - cursor = hasNextPage && !pageInfo.isNull("endCursor") ? pageInfo.getString("endCursor") : null; + cursor = + hasNextPage && !pageInfo.isNull("endCursor") ? pageInfo.getString("endCursor") : null; final JsonArray nodes = data.getJsonArray("nodes"); seen += nodes.size(); return Optional.of(nodes); } - LOGGER.warn("attempt {} of {} returned errors: {}", attempt, RETRY_ATTEMPTS, response.getErrors()); + LOGGER.warn( + "attempt {} of {} returned errors: {}", attempt, RETRY_ATTEMPTS, response.getErrors()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } catch (Exception e) { - LOGGER.error("attempt {}/{} failed at cursor {}: {}", attempt, RETRY_ATTEMPTS, cursor, e.getMessage()); + LOGGER.error( + "attempt {}/{} failed at cursor {}: {}", + attempt, + RETRY_ATTEMPTS, + cursor, + e.getMessage()); } if (attempt < RETRY_ATTEMPTS) { try { @@ -133,5 +144,4 @@ public int getLastRateLimit() { public long getLastQueryNanos() { return lastQueryNanos; } - } diff --git a/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryInfo.java b/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryInfo.java index 82a804a..ea424a0 100644 --- a/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryInfo.java +++ b/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryInfo.java @@ -5,8 +5,7 @@ /** * Local Git repository metadata exposed to the web UI. * - * @param path repository path relative to the local clone root + * @param path repository path relative to the local clone root * @param branches branch names available in the repository */ -public record LocalRepositoryInfo(String path, List branches) { -} +public record LocalRepositoryInfo(String path, List branches) {} diff --git a/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryService.java b/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryService.java index 8140ea8..fff7cf2 100644 --- a/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryService.java +++ b/src/main/java/net/explorviz/code/analysis/service/LocalRepositoryService.java @@ -19,16 +19,16 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.microprofile.config.inject.ConfigProperty; -/** - * Resolves and lists repositories stored in the local clone folder. - */ +/** Resolves and lists repositories stored in the local clone folder. */ @ApplicationScoped public class LocalRepositoryService { private static final String DEFAULT_CLONE_ROOT = "cloned-repositories"; - @ConfigProperty(name = "explorviz.gitanalysis.remote.storage-path", defaultValue = DEFAULT_CLONE_ROOT) - /* default */ String cloneRootProperty; // NOCS + @ConfigProperty( + name = "explorviz.gitanalysis.remote.storage-path", + defaultValue = DEFAULT_CLONE_ROOT) + /* default */ String cloneRootProperty; /** * Lists all Git repositories below the configured clone root. @@ -61,7 +61,8 @@ public List listRepositories() throws IOException { * @return absolute repository path * @throws IOException if the path escapes the clone root */ - public Path resolveRelativeRepositoryPath(final String relativeRepositoryPath) throws IOException { + public Path resolveRelativeRepositoryPath(final String relativeRepositoryPath) + throws IOException { final Path cloneRoot = getCloneRoot(); final Path repositoryPath = cloneRoot.resolve(relativeRepositoryPath.trim()).normalize(); @@ -73,9 +74,10 @@ public Path resolveRelativeRepositoryPath(final String relativeRepositoryPath) t } private Path getCloneRoot() { - final String cloneRoot = cloneRootProperty == null || cloneRootProperty.isBlank() - ? DEFAULT_CLONE_ROOT - : cloneRootProperty; + final String cloneRoot = + cloneRootProperty == null || cloneRootProperty.isBlank() + ? DEFAULT_CLONE_ROOT + : cloneRootProperty; final Path configuredPath = Paths.get(cloneRoot); if (configuredPath.isAbsolute()) { @@ -93,8 +95,8 @@ private boolean isGitRepository(final Path path) { } private LocalRepositoryInfo getRepositoryInfo(final Path walkRoot, final Path repositoryPath) { - final String relativePath = walkRoot.relativize(repositoryPath).toString() - .replace(File.separatorChar, '/'); + final String relativePath = + walkRoot.relativize(repositoryPath).toString().replace(File.separatorChar, '/'); return new LocalRepositoryInfo(relativePath, listBranches(repositoryPath)); } @@ -117,9 +119,11 @@ private List listBranches(final Path repositoryPath) { final Set branchNames = new LinkedHashSet<>(); try (Git repository = Git.open(repositoryPath.toFile())) { for (final Ref branch : repository.branchList().setListMode(ListMode.ALL).call()) { - final String branchName = branch.getName() - .replaceFirst("^refs/heads/", "") - .replaceFirst("^refs/remotes/origin/", ""); + final String branchName = + branch + .getName() + .replaceFirst("^refs/heads/", "") + .replaceFirst("^refs/remotes/origin/", ""); if (!branchName.equals("HEAD")) { branchNames.add(branchName); } diff --git a/src/main/java/net/explorviz/code/analysis/service/TerminalProgressReporter.java b/src/main/java/net/explorviz/code/analysis/service/TerminalProgressReporter.java index dc57cd6..7f2039a 100644 --- a/src/main/java/net/explorviz/code/analysis/service/TerminalProgressReporter.java +++ b/src/main/java/net/explorviz/code/analysis/service/TerminalProgressReporter.java @@ -62,8 +62,15 @@ static String formatFileLine(final AnalysisProgressState state) { final int totalFiles = Math.max(0, state.totalFiles()); final int analyzedFiles = Math.min(Math.max(0, state.analyzedFiles()), totalFiles); final int percent = totalFiles == 0 ? 100 : (analyzedFiles * 100) / totalFiles; - return "Files " + renderBar(analyzedFiles, totalFiles) + " " - + analyzedFiles + "/" + totalFiles + " (" + percent + "%)"; + return "Files " + + renderBar(analyzedFiles, totalFiles) + + " " + + analyzedFiles + + "/" + + totalFiles + + " (" + + percent + + "%)"; } static String formatAnalysisStartedMessage(final AnalysisProgressState state) { @@ -76,15 +83,31 @@ static String formatCommitStartedMessage(final AnalysisProgressState state) { if (state.totalFiles() == 0) { return "Commit " + currentCommit + "/" + totalCommits + ": no files to analyze"; } - return "Commit " + currentCommit + "/" + totalCommits + ": analyzing " + state.totalFiles() + " files"; + return "Commit " + + currentCommit + + "/" + + totalCommits + + ": analyzing " + + state.totalFiles() + + " files"; } - static String formatFileMilestoneMessage(final AnalysisProgressState state, final int milestonePercent) { + static String formatFileMilestoneMessage( + final AnalysisProgressState state, final int milestonePercent) { final int totalCommits = Math.max(0, state.totalCommits()); final int totalFiles = Math.max(0, state.totalFiles()); final int analyzedFiles = Math.min(Math.max(0, state.analyzedFiles()), totalFiles); - return "Commit " + resolveDisplayedCommit(state) + "/" + totalCommits + ": files " - + analyzedFiles + "/" + totalFiles + " (" + milestonePercent + "%)"; + return "Commit " + + resolveDisplayedCommit(state) + + "/" + + totalCommits + + ": files " + + analyzedFiles + + "/" + + totalFiles + + " (" + + milestonePercent + + "%)"; } static String formatCommitCompletedMessage(final AnalysisProgressState state) { @@ -148,9 +171,11 @@ private static int resolveDisplayedCommit(final AnalysisProgressState state) { } private static String renderBar(final int analyzedFiles, final int totalFiles) { - final int filled = totalFiles == 0 - ? BAR_WIDTH - : Math.min(BAR_WIDTH, (int) Math.round((double) analyzedFiles * BAR_WIDTH / totalFiles)); + final int filled = + totalFiles == 0 + ? BAR_WIDTH + : Math.min( + BAR_WIDTH, (int) Math.round((double) analyzedFiles * BAR_WIDTH / totalFiles)); return "[" + "#".repeat(filled) + "-".repeat(BAR_WIDTH - filled) + "]"; } @@ -181,11 +206,15 @@ private static boolean isCiEnvironment() { private static boolean isTruthyEnv(final String name) { final String value = System.getenv(name); - return value != null && !value.isBlank() && !"false".equalsIgnoreCase(value) && !"0".equals(value); + return value != null + && !value.isBlank() + && !"false".equalsIgnoreCase(value) + && !"0".equals(value); } private void updateInteractive(final AnalysisProgressState state) { - if (STATUS_FINISHED.equals(state.status()) || STATUS_FAILED.equals(state.status()) + if (STATUS_FINISHED.equals(state.status()) + || STATUS_FAILED.equals(state.status()) || STATUS_CANCELLED.equals(state.status())) { renderFinalState(state); finish(); @@ -237,7 +266,8 @@ private void updateMilestones(final AnalysisProgressState state) { } if (state.totalFiles() > 0) { - final int milestone = nextFileMilestonePercent(resolveFilePercent(state), lastLoggedFilePercent); + final int milestone = + nextFileMilestonePercent(resolveFilePercent(state), lastLoggedFilePercent); if (milestone > 0) { LOGGER.info(formatFileMilestoneMessage(state, milestone)); lastLoggedFilePercent = milestone; @@ -262,8 +292,9 @@ private void resetMilestoneTracking() { private boolean shouldRender(final AnalysisProgressState state) { final long now = System.nanoTime(); - final boolean commitBoundaryChanged = state.analyzedCommits() != lastRenderedCommit - || state.totalFiles() != lastRenderedTotalFiles; + final boolean commitBoundaryChanged = + state.analyzedCommits() != lastRenderedCommit + || state.totalFiles() != lastRenderedTotalFiles; if (!active || commitBoundaryChanged) { return true; } @@ -277,13 +308,14 @@ private void renderRunningState(final AnalysisProgressState state) { private void renderFinalState(final AnalysisProgressState state) { if (STATUS_FINISHED.equals(state.status())) { - final AnalysisProgressState completed = new AnalysisProgressState( - STATUS_FINISHED, - state.totalCommits(), - state.totalCommits(), - state.totalFiles(), - state.totalFiles(), - null); + final AnalysisProgressState completed = + new AnalysisProgressState( + STATUS_FINISHED, + state.totalCommits(), + state.totalCommits(), + state.totalFiles(), + state.totalFiles(), + null); writeProgressLines(formatCommitLine(completed), formatFileLine(completed)); rememberRender(completed); return; diff --git a/src/main/java/net/explorviz/code/analysis/types/FileDescriptor.java b/src/main/java/net/explorviz/code/analysis/types/FileDescriptor.java index 91f9bcc..4405239 100644 --- a/src/main/java/net/explorviz/code/analysis/types/FileDescriptor.java +++ b/src/main/java/net/explorviz/code/analysis/types/FileDescriptor.java @@ -4,24 +4,24 @@ import org.eclipse.jgit.lib.ObjectId; /** - * Basic data object to link the objectId of git files to the associated file names. Heavily based on - * {@link com.github.javaparser.utils.Pair} + * Basic data object to link the objectId of git files to the associated file names. Heavily based + * on {@link com.github.javaparser.utils.Pair} */ public class FileDescriptor { - public final ObjectId objectId; // NOCS - public final String fileName; // NOCS - public final String relativePath; // NOCS - public String reportedPath; // NOCS - public int modifiedLines; // NOCS - public int addedLines; // NOCS - public int removedLines; // NOCS + public final ObjectId objectId; + public final String fileName; + public final String relativePath; + public String reportedPath; + public int modifiedLines; + public int addedLines; + public int removedLines; /** * Create a new FileDescriptor. * - * @param objectId the ObjectId of the File - * @param fileName the name of the File + * @param objectId the ObjectId of the File + * @param fileName the name of the File * @param relativePath the relative path of the file starting from the repository's directory. */ public FileDescriptor(final ObjectId objectId, final String fileName, final String relativePath) { @@ -34,13 +34,16 @@ public FileDescriptor(final ObjectId objectId, final String fileName, final Stri /** * Create a new FileDescriptor with modification data added. * - * @param objectId the ObjectId of the File - * @param fileName the name of the File - * @param relativePath the relative path of the file starting from the repository's directory. - * @param modificationData a {@link Triple} containing {modification data of the file. left -> amount of modified - * lines, middle -> amount of added lines, right -> amount of removed lines + * @param objectId the ObjectId of the File + * @param fileName the name of the File + * @param relativePath the relative path of the file starting from the repository's directory. + * @param modificationData a {@link Triple} containing {modification data of the file. left -> + * amount of modified lines, middle -> amount of added lines, right -> amount of removed lines */ - public FileDescriptor(final ObjectId objectId, final String fileName, final String relativePath, + public FileDescriptor( + final ObjectId objectId, + final String fileName, + final String relativePath, final Triple modificationData) { this(objectId, fileName, relativePath); if (modificationData != null) { diff --git a/src/main/java/net/explorviz/code/analysis/types/Graph.java b/src/main/java/net/explorviz/code/analysis/types/Graph.java index 5b5ee19..c81ef7b 100644 --- a/src/main/java/net/explorviz/code/analysis/types/Graph.java +++ b/src/main/java/net/explorviz/code/analysis/types/Graph.java @@ -7,9 +7,7 @@ import java.util.Map; import java.util.Set; -/** - * Class to represent a simple Graph for easy cohesion checks. - */ +/** Class to represent a simple Graph for easy cohesion checks. */ public class Graph { private final Map> adjVertices; @@ -36,7 +34,7 @@ public void addVertex(final String label) { /** * Add a vertex to the graph, allows to set the isField flag conveniently. * - * @param label the label for the new vertex + * @param label the label for the new vertex * @param isField if the current vertex is a field */ public void addVertex(final String label, final boolean isField) { diff --git a/src/main/java/net/explorviz/code/analysis/types/JavaTypes.java b/src/main/java/net/explorviz/code/analysis/types/JavaTypes.java index de7b2d2..9921bd5 100644 --- a/src/main/java/net/explorviz/code/analysis/types/JavaTypes.java +++ b/src/main/java/net/explorviz/code/analysis/types/JavaTypes.java @@ -1,39 +1,114 @@ package net.explorviz.code.analysis.types; -/** - * Class for holding lists of type names. - */ +/** Class for holding lists of type names. */ public final class JavaTypes { - public static final String[] BUILT_INS = {"Appendable", "AutoCloseable", "CharSequence", - "Cloneable", "Comparable", "Iterable", "Readable", "Runnable", - "Thread.UncaughtExceptionHandler", "Boolean", "Byte", "Character", "Class", "ClassLoader", - "ClassValue", "Compiler", "Double", "Enum", "Float", "InheritableThreadLocal", "Integer", - "Long", "Math", "Number", "Object", "Package", "Process", "ProcessBuilder", "Runtime", - "RuntimePermission", "SecurityManager", "Short", "StackTraceElement", "StrictMath", "String", - "StringBuffer", "StringBuilder", "System", "Thread", "ThreadGroup", "ThreadLocal", - "Throwable", "Void", "Character.UnicodeScript", "ProcessBuilder.Redirect.Type", - "Thread.State", "ArithmeticException", "ArrayIndexOutOfBoundsException", - "ArrayStoreException", "ClassCastException", "ClassNotFoundException", - "CloneNotSupportedException", "EnumConstantNotPresentException", "Exception", - "IllegalAccessException", "IllegalArgumentException", "IllegalMonitorStateException", - "IllegalStateException", "IllegalThreadStateException", "IndexOutOfBoundsException", - "InstantiationException", "InterruptedException", "NegativeArraySizeException", - "NoSuchFieldException", "NoSuchMethodException", "NullPointerException", - "NumberFormatException", "ReflectiveOperationException", "RuntimeException", - "SecurityException", "StringIndexOutOfBoundsException", "TypeNotPresentException", - "UnsupportedOperationException", "AbstractMethodError", "AssertionError", - "BootstrapMethodError", "ClassCircularityError", "ClassFormatError", "Error", - "ExceptionInInitializerError", "IllegalAccessError", "IncompatibleClassChangeError", - "InstantiationError", "InternalError", "LinkageError", "NoClassDefFoundError", - "NoSuchFieldError", "NoSuchMethodError", "OutOfMemoryError", "StackOverflowError", - "ThreadDeath", "UnknownError", "UnsatisfiedLinkError", "UnsupportedClassVersionError", - "VerifyError", "VirtualMachineError", "Deprecated", "Override", "SafeVarargs", - "SuppressWarnings"}; + public static final String[] BUILT_INS = { + "Appendable", + "AutoCloseable", + "CharSequence", + "Cloneable", + "Comparable", + "Iterable", + "Readable", + "Runnable", + "Thread.UncaughtExceptionHandler", + "Boolean", + "Byte", + "Character", + "Class", + "ClassLoader", + "ClassValue", + "Compiler", + "Double", + "Enum", + "Float", + "InheritableThreadLocal", + "Integer", + "Long", + "Math", + "Number", + "Object", + "Package", + "Process", + "ProcessBuilder", + "Runtime", + "RuntimePermission", + "SecurityManager", + "Short", + "StackTraceElement", + "StrictMath", + "String", + "StringBuffer", + "StringBuilder", + "System", + "Thread", + "ThreadGroup", + "ThreadLocal", + "Throwable", + "Void", + "Character.UnicodeScript", + "ProcessBuilder.Redirect.Type", + "Thread.State", + "ArithmeticException", + "ArrayIndexOutOfBoundsException", + "ArrayStoreException", + "ClassCastException", + "ClassNotFoundException", + "CloneNotSupportedException", + "EnumConstantNotPresentException", + "Exception", + "IllegalAccessException", + "IllegalArgumentException", + "IllegalMonitorStateException", + "IllegalStateException", + "IllegalThreadStateException", + "IndexOutOfBoundsException", + "InstantiationException", + "InterruptedException", + "NegativeArraySizeException", + "NoSuchFieldException", + "NoSuchMethodException", + "NullPointerException", + "NumberFormatException", + "ReflectiveOperationException", + "RuntimeException", + "SecurityException", + "StringIndexOutOfBoundsException", + "TypeNotPresentException", + "UnsupportedOperationException", + "AbstractMethodError", + "AssertionError", + "BootstrapMethodError", + "ClassCircularityError", + "ClassFormatError", + "Error", + "ExceptionInInitializerError", + "IllegalAccessError", + "IncompatibleClassChangeError", + "InstantiationError", + "InternalError", + "LinkageError", + "NoClassDefFoundError", + "NoSuchFieldError", + "NoSuchMethodError", + "OutOfMemoryError", + "StackOverflowError", + "ThreadDeath", + "UnknownError", + "UnsatisfiedLinkError", + "UnsupportedClassVersionError", + "VerifyError", + "VirtualMachineError", + "Deprecated", + "Override", + "SafeVarargs", + "SuppressWarnings" + }; - public static final String[] PRIMITIVES = {"void", "byte", "short", "int", "long", "float", - "double", "boolean", "char"}; + public static final String[] PRIMITIVES = { + "void", "byte", "short", "int", "long", "float", "double", "boolean", "char" + }; - private JavaTypes() { - } + private JavaTypes() {} } diff --git a/src/main/java/net/explorviz/code/analysis/types/RemoteRepositoryObject.java b/src/main/java/net/explorviz/code/analysis/types/RemoteRepositoryObject.java index abef1ba..e7c981f 100644 --- a/src/main/java/net/explorviz/code/analysis/types/RemoteRepositoryObject.java +++ b/src/main/java/net/explorviz/code/analysis/types/RemoteRepositoryObject.java @@ -4,9 +4,7 @@ import java.util.List; import org.eclipse.jgit.transport.CredentialsProvider; -/** - * Storage Object to abstract the remote repository's url and storage path. - */ +/** Storage Object to abstract the remote repository's url and storage path. */ public class RemoteRepositoryObject { private String url; @@ -18,15 +16,18 @@ public class RemoteRepositoryObject { /** * Create new RemoteRepositoryObject holding data for cloning remote repository. * - * @param url the repository's url - * @param storagePath where to clone the repository to + * @param url the repository's url + * @param storagePath where to clone the repository to * @param credentialsProvider the credential provider for private repositories - * @param branchName the name of the branch to analyze - * @param cloneDepth optional clone depth for shallow cloning (null for full clone) + * @param branchName the name of the branch to analyze + * @param cloneDepth optional clone depth for shallow cloning (null for full clone) */ - public RemoteRepositoryObject(final String url, final String storagePath, + public RemoteRepositoryObject( + final String url, + final String storagePath, final CredentialsProvider credentialsProvider, - final String branchName, final Integer cloneDepth) { + final String branchName, + final Integer cloneDepth) { this.url = url; this.storagePath = storagePath; this.credentialsProvider = credentialsProvider; @@ -34,14 +35,16 @@ public RemoteRepositoryObject(final String url, final String storagePath, this.cloneDepth = cloneDepth; } - public RemoteRepositoryObject(final String url, final String storagePath, + public RemoteRepositoryObject( + final String url, + final String storagePath, final CredentialsProvider credentialsProvider, final String branchName) { this(url, storagePath, credentialsProvider, branchName, null); } - public RemoteRepositoryObject(final String url, final String storagePath, - final String branchName) { + public RemoteRepositoryObject( + final String url, final String storagePath, final String branchName) { this(url, storagePath, CredentialsProvider.getDefault(), branchName); } diff --git a/src/main/java/net/explorviz/code/analysis/types/Triple.java b/src/main/java/net/explorviz/code/analysis/types/Triple.java index a0ff018..2766795 100644 --- a/src/main/java/net/explorviz/code/analysis/types/Triple.java +++ b/src/main/java/net/explorviz/code/analysis/types/Triple.java @@ -12,10 +12,9 @@ public record Triple(L left, M middle, R right) { /** * Creates a Triple of data entries. * - * @param left the left data entry + * @param left the left data entry * @param middle the middle data entry - * @param right the right data entry + * @param right the right data entry */ - public Triple { - } + public Triple {} } diff --git a/src/main/java/net/explorviz/code/analysis/types/Verification.java b/src/main/java/net/explorviz/code/analysis/types/Verification.java index 0fdae08..89b3b5f 100644 --- a/src/main/java/net/explorviz/code/analysis/types/Verification.java +++ b/src/main/java/net/explorviz/code/analysis/types/Verification.java @@ -5,13 +5,10 @@ import java.util.ArrayList; import java.util.List; -/** - * Class to hold some hashing methods. - */ +/** Class to hold some hashing methods. */ public final class Verification { - private Verification() { - } + private Verification() {} /** * Calculates the hash for a parameter list provided as String List. diff --git a/src/main/java/net/explorviz/code/analysis/types/Vertex.java b/src/main/java/net/explorviz/code/analysis/types/Vertex.java index 212dafc..3c8acf0 100644 --- a/src/main/java/net/explorviz/code/analysis/types/Vertex.java +++ b/src/main/java/net/explorviz/code/analysis/types/Vertex.java @@ -2,13 +2,11 @@ import java.util.Objects; -/** - * Vertex to be used in the Graph. Holds a flag if it is a field vertex and holds a group label. - */ +/** Vertex to be used in the Graph. Holds a flag if it is a field vertex and holds a group label. */ public class Vertex { private final String label; - private boolean isField; // NOPMD + private boolean isField; private int group; public Vertex(final String label, final int group) { diff --git a/src/main/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitor.java b/src/main/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitor.java index 50617b6..55c2dd5 100644 --- a/src/main/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitor.java +++ b/src/main/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitor.java @@ -26,11 +26,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Visitor to calculate the cyclomatic complexity for methods and classes. - */ -public class CyclomaticComplexityVisitor // NOPMD - extends VoidVisitorAdapter> { // NOPMD +/** Visitor to calculate the cyclomatic complexity for methods and classes. */ +public class CyclomaticComplexityVisitor extends VoidVisitorAdapter> { public static final String CYCLOMATIC_COMPLEXITY = "cyclomatic_complexity"; private static final Logger LOGGER = LoggerFactory.getLogger(CyclomaticComplexityVisitor.class); @@ -86,8 +83,8 @@ public void visit(final ClassOrInterfaceDeclaration n, final Pair da } // Update the file metric - final Integer oldValue = Integer.getInteger( - data.a.getFileData().getMetricValue(CYCLOMATIC_COMPLEXITY)); + final Integer oldValue = + Integer.getInteger(data.a.getFileData().getMetricValue(CYCLOMATIC_COMPLEXITY)); if (oldValue == null) { data.a.putFileMetric(CYCLOMATIC_COMPLEXITY, String.valueOf(metricValue)); } else { @@ -185,7 +182,6 @@ public void visit(final ObjectCreationExpr n, final Pair } } - @Override public void visit(final ForEachStmt statement, final Pair data) { addOccurrence(data.a.getCurrentMethodName()); @@ -193,7 +189,6 @@ public void visit(final ForEachStmt statement, final Pair data) { addOccurrence(data.a.getCurrentMethodName()); @@ -205,7 +200,6 @@ public void visit(final ForStmt statement, final Pair da super.visit(statement, data); } - @Override public void visit(final IfStmt statement, final Pair data) { addOccurrence(data.a.getCurrentMethodName()); @@ -224,7 +218,6 @@ public void visit(final SwitchEntry statement, final Pair super.visit(statement, data); } - @Override public void visit(final TryStmt statement, final Pair data) { @@ -255,7 +247,6 @@ public void visit(final TryStmt statement, final Pair da super.visit(statement, data); } - @Override public void visit(final WhileStmt statement, final Pair data) { @@ -273,9 +264,8 @@ private void conditionCheck(final String condition, final Pair data) { + private void regexCheck( + final String haystack, final Pattern pattern, final Pair data) { final Matcher matcher = pattern.matcher(haystack); while (matcher.find()) { @@ -295,5 +285,4 @@ private void addOccurrence(final String methodName) { methodCounter.put(methodName, 1); } } - } diff --git a/src/main/java/net/explorviz/code/analysis/visitor/EmptyGenericVisitor.java b/src/main/java/net/explorviz/code/analysis/visitor/EmptyGenericVisitor.java index e1fb928..57802e2 100644 --- a/src/main/java/net/explorviz/code/analysis/visitor/EmptyGenericVisitor.java +++ b/src/main/java/net/explorviz/code/analysis/visitor/EmptyGenericVisitor.java @@ -3,9 +3,5 @@ import com.github.javaparser.ast.visitor.GenericVisitorAdapter; import net.explorviz.code.analysis.handler.JavaFileDataHandler; -/** - * Dummy Visitor that does nothing. - */ -public class EmptyGenericVisitor extends GenericVisitorAdapter { - -} +/** Dummy Visitor that does nothing. */ +public class EmptyGenericVisitor extends GenericVisitorAdapter {} diff --git a/src/main/java/net/explorviz/code/analysis/visitor/FileDataVisitor.java b/src/main/java/net/explorviz/code/analysis/visitor/FileDataVisitor.java index 92ed4c5..b4bd520 100644 --- a/src/main/java/net/explorviz/code/analysis/visitor/FileDataVisitor.java +++ b/src/main/java/net/explorviz/code/analysis/visitor/FileDataVisitor.java @@ -39,11 +39,10 @@ import org.slf4j.LoggerFactory; /** - * Visitor filling a FileData object with typical information about java files. - * Includes the LOC (lines of code) - * metric. + * Visitor filling a FileData object with typical information about java files. Includes the LOC + * (lines of code) metric. */ -public class FileDataVisitor extends VoidVisitorAdapter { // NOPMD +public class FileDataVisitor extends VoidVisitorAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(FileDataVisitor.class); private static final String UNKNOWN = "UNKNOWN"; @@ -58,11 +57,11 @@ public class FileDataVisitor extends VoidVisitorAdapter { / /** * Create a FileDataVisitor. * - * @param fallbackTypeSolver a fallback type solver + * @param fallbackTypeSolver a fallback type solver * @param wildcardImportProperty set true if wildcard imports should be handled */ - public FileDataVisitor(final Optional fallbackTypeSolver, - final boolean wildcardImportProperty) { + public FileDataVisitor( + final Optional fallbackTypeSolver, final boolean wildcardImportProperty) { super(); this.fallbackTypeSolver = fallbackTypeSolver; this.wildcardImportProperty = wildcardImportProperty; @@ -91,8 +90,10 @@ public void visit(final EnumDeclaration n, final JavaFileDataHandler data) { final String name = n.getNameAsString(); final String fqn = n.getFullyQualifiedName().orElse(UNKNOWN); data.enterClass(name, fqn); - data.getCurrentClassData().addMetric(CommonFileDataListener.SLOC, String.valueOf(getLoc(n) - getCloc(n))); - data.getCurrentClassData().addMetric(CommonFileDataListener.LINE_COUNT, String.valueOf(getLoc(n))); + data.getCurrentClassData() + .addMetric(CommonFileDataListener.SLOC, String.valueOf(getLoc(n) - getCloc(n))); + data.getCurrentClassData() + .addMetric(CommonFileDataListener.LINE_COUNT, String.valueOf(getLoc(n))); data.getCurrentClassData().setIsEnum(); for (final Modifier modifier : n.getModifiers()) { data.getCurrentClassData().addModifier(modifier.getKeyword().asString()); @@ -110,8 +111,8 @@ public void visit(final FieldDeclaration n, final JavaFileDataHandler data) { } for (final VariableDeclarator declarator : n.getVariables()) { data.getCurrentClassData() - .addField(declarator.getNameAsString(), resolveFqn(declarator.getType(), data), - modifierList); + .addField( + declarator.getNameAsString(), resolveFqn(declarator.getType(), data), modifierList); } super.visit(n, data); data.leaveMethod(); @@ -123,14 +124,15 @@ public void visit(final VariableDeclarator n, final JavaFileDataHandler data) { super.visit(n, data); } - @Override // NOCS - public void visit(final ClassOrInterfaceDeclaration n, - final JavaFileDataHandler data) { // NOCS NOPMD + @Override + public void visit(final ClassOrInterfaceDeclaration n, final JavaFileDataHandler data) { final String name = n.getNameAsString(); final String classFqn = n.getFullyQualifiedName().orElse(UNKNOWN); data.enterClass(name, classFqn); - data.getCurrentClassData().addMetric(CommonFileDataListener.SLOC, String.valueOf(getLoc(n) - getCloc(n))); - data.getCurrentClassData().addMetric(CommonFileDataListener.LINE_COUNT, String.valueOf(getLoc(n))); + data.getCurrentClassData() + .addMetric(CommonFileDataListener.SLOC, String.valueOf(getLoc(n) - getCloc(n))); + data.getCurrentClassData() + .addMetric(CommonFileDataListener.LINE_COUNT, String.valueOf(getLoc(n))); if (n.isInterface()) { data.getCurrentClassData().setIsInterface(); @@ -180,19 +182,25 @@ public void visit(final ClassOrInterfaceDeclaration n, @Override public void visit(final MethodDeclaration n, final JavaFileDataHandler data) { - final String methodsFullyQualifiedName = data.getCurrentClassFqn() + "." + n.getNameAsString() + "#" - + Verification.parameterHash( - n.getParameters()); + final String methodsFullyQualifiedName = + data.getCurrentClassFqn() + + "." + + n.getNameAsString() + + "#" + + Verification.parameterHash(n.getParameters()); data.enterMethod(methodsFullyQualifiedName); final String returnType = resolveFqn(n.getType(), data); // data.setLastAddedMethodFqn(methodsFullyQualifiedName); - final MethodDataHandler method = data.getCurrentClassData() - .addMethod(n.getNameAsString(), methodsFullyQualifiedName, returnType); + final MethodDataHandler method = + data.getCurrentClassData() + .addMethod(n.getNameAsString(), methodsFullyQualifiedName, returnType); for (final Modifier modifier : n.getModifiers()) { method.addModifier(modifier.getKeyword().asString()); } for (final Parameter parameter : n.getParameters()) { - method.addParameter(parameter.getNameAsString(), resolveFqn(parameter.getType(), data), + method.addParameter( + parameter.getNameAsString(), + resolveFqn(parameter.getType(), data), parameter.getModifiers()); } for (final AnnotationExpr annotation : n.getAnnotations()) { @@ -216,17 +224,23 @@ public void visit(final MethodDeclaration n, final JavaFileDataHandler data) { @Override public void visit(final ConstructorDeclaration n, final JavaFileDataHandler data) { - final String constructorsFullyQualifiedName = data.getCurrentClassFqn() + "." + n.getNameAsString() + "#" - + Verification.parameterHash( - n.getParameters()); + final String constructorsFullyQualifiedName = + data.getCurrentClassFqn() + + "." + + n.getNameAsString() + + "#" + + Verification.parameterHash(n.getParameters()); data.enterMethod(constructorsFullyQualifiedName); - final MethodDataHandler constructor = data.getCurrentClassData() - .addConstructor(n.getNameAsString(), constructorsFullyQualifiedName); + final MethodDataHandler constructor = + data.getCurrentClassData() + .addConstructor(n.getNameAsString(), constructorsFullyQualifiedName); for (final Modifier modifier : n.getModifiers()) { constructor.addModifier(modifier.getKeyword().asString()); } for (final Parameter parameter : n.getParameters()) { - constructor.addParameter(parameter.getNameAsString(), resolveFqn(parameter.getType(), data), + constructor.addParameter( + parameter.getNameAsString(), + resolveFqn(parameter.getType(), data), parameter.getModifiers()); } constructor.addMetric(CommonFileDataListener.SLOC, String.valueOf(getLoc(n) - getCloc(n))); @@ -317,14 +331,13 @@ private String buildResolvedTypeFullDepthType(final ResolvedReferenceType resolv } /** - * Returns the FQN for the type by simply comparing it with potential imports. - * If no import matches the type, the type - * itself will be returned + * Returns the FQN for the type by simply comparing it with potential imports. If no import + * matches the type, the type itself will be returned * * @param type the type of the Object * @return the fqn or the original type */ - private String findFqnInImports(final Type type, final JavaFileDataHandler data) { // NOPMD NOCS + private String findFqnInImports(final Type type, final JavaFileDataHandler data) { final List imports = data.getImportNames(); String attachedGenerics = ""; if (type instanceof ClassOrInterfaceType) { @@ -347,8 +360,8 @@ private String findFqnInImports(final Type type, final JavaFileDataHandler data) if (type.asString().contains(".")) { final String[] a = type.asString().split("\\."); if (importEntry.endsWith(a[0])) { - final String result = Arrays.stream(a).filter(str -> !str.equals(a[0])) - .collect(Collectors.joining(".")); + final String result = + Arrays.stream(a).filter(str -> !str.equals(a[0])).collect(Collectors.joining(".")); return importEntry + "." + result + attachedGenerics; } } @@ -359,8 +372,12 @@ private String findFqnInImports(final Type type, final JavaFileDataHandler data) if (wildcardImportProperty && wildcardImportCount == 1) { if (LOGGER.isWarnEnabled()) { - LOGGER.warn("assumed type from wildcard import: " + wildcardImport + "." + type.asString() - + attachedGenerics); + LOGGER.warn( + "assumed type from wildcard import: " + + wildcardImport + + "." + + type.asString() + + attachedGenerics); } return wildcardImport + "." + type.asString() + attachedGenerics; } @@ -369,14 +386,18 @@ private String findFqnInImports(final Type type, final JavaFileDataHandler data) // if wildcard in imports, note here that it might be possible the type is // defined there if (wildcardImportCount > 1 && wildcardImportProperty) { - LOGGER.warn("File contains multiple wildcard imports, type <" + type.asString() // NOPMD - + "> is ambiguous."); + LOGGER.warn( + "File contains multiple wildcard imports, type <" + + type.asString() + + "> is ambiguous."); } else { if (wildcardImportCount > 0 && !wildcardImportProperty) { - LOGGER.warn("File contains wildcard import(s), type <" + type.asString() // NOPMD - + "> might be defined there. Type assumption by wildcards is turned off."); + LOGGER.warn( + "File contains wildcard import(s), type <" + + type.asString() + + "> might be defined there. Type assumption by wildcards is turned off."); } else { - LOGGER.warn("Unable to get FQN for <" + type.asString() + ">"); // NOPMD + LOGGER.warn("Unable to get FQN for <" + type.asString() + ">"); } } } @@ -424,8 +445,8 @@ private String solveTypeInCurrentContext(final String name) { if (fallbackTypeSolver.isPresent()) { // Don't know why, but symbol solver seems to have problems with for (final String builtInPackage : Arrays.asList("", "java.lang.")) { - final SymbolReference ref = fallbackTypeSolver.get() - .tryToSolveType(builtInPackage + name); + final SymbolReference ref = + fallbackTypeSolver.get().tryToSolveType(builtInPackage + name); if (ref.isSolved()) { return ref.getCorrespondingDeclaration().getQualifiedName(); } diff --git a/src/main/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitor.java b/src/main/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitor.java index bb11398..a170da7 100644 --- a/src/main/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitor.java +++ b/src/main/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitor.java @@ -22,11 +22,8 @@ import net.explorviz.code.analysis.handler.MetricAppender; import net.explorviz.code.analysis.types.Graph; -/** - * Basic LCOM4 implementation. - */ -public class LackOfCohesionMethodsVisitor // NOPMD - extends VoidVisitorAdapter> { +/** Basic LCOM4 implementation. */ +public class LackOfCohesionMethodsVisitor extends VoidVisitorAdapter> { private static final String METRIC_NAME = "LCOM4"; private final Stack graphStack = new Stack<>(); @@ -45,14 +42,17 @@ public void visit(final ClassOrInterfaceDeclaration n, final Pair field.getVariables().getFirst().get().getName().asString()) - .collect(Collectors.toList()); + currentFieldNames = + n.getFields().stream() + .map(field -> field.getVariables().getFirst().get().getName().asString()) + .collect(Collectors.toList()); for (final String fieldName : currentFieldNames) { currentGraph.addVertex(fieldName, true); } - currentMethodNames = n.getMethods().stream().map(NodeWithSimpleName::getNameAsString) - .collect(Collectors.toList()); + currentMethodNames = + n.getMethods().stream() + .map(NodeWithSimpleName::getNameAsString) + .collect(Collectors.toList()); for (final String methodName : currentMethodNames) { currentGraph.addVertex(methodName); } @@ -62,7 +62,7 @@ public void visit(final ClassOrInterfaceDeclaration n, final Pair da data.a.enterClass(n); currentFields = n.getFields(); - currentFieldNames = n.getFields().stream() - .map(field -> field.getVariables().getFirst().get().getName().asString()) - .collect(Collectors.toList()); + currentFieldNames = + n.getFields().stream() + .map(field -> field.getVariables().getFirst().get().getName().asString()) + .collect(Collectors.toList()); for (final String fieldName : currentFieldNames) { currentGraph.addVertex(fieldName, true); } - currentMethodNames = n.getMethods().stream().map(NodeWithSimpleName::getNameAsString) - .collect(Collectors.toList()); + currentMethodNames = + n.getMethods().stream() + .map(NodeWithSimpleName::getNameAsString) + .collect(Collectors.toList()); for (final String methodName : currentMethodNames) { currentGraph.addVertex(methodName); } @@ -92,7 +95,7 @@ public void visit(final EnumDeclaration n, final Pair da try { data.a.putClassMetric(METRIC_NAME, String.valueOf(currentGraph.getGroups().size())); } catch (NotFoundException e) { - throw new RuntimeException(e); // NOPMD + throw new RuntimeException(e); } data.a.leaveClass(); leaveClass(); @@ -105,9 +108,8 @@ public void visit(final FieldDeclaration n, final Pair d data.a.leaveMethod(); } - @Override // NOCS - public void visit(final MethodDeclaration n, // NOCS NOPMD - final Pair data) { // NOCS NOPMD + @Override + public void visit(final MethodDeclaration n, final Pair data) { data.a.enterMethod(n); // Skip this method if it is inherited, remove the graph entry if (n.isAnnotationPresent("Override")) { @@ -115,8 +117,9 @@ public void visit(final MethodDeclaration n, // NOCS NOPMD return; } // it the method is empty, remove the graph entry - if (n.getBody().isEmpty() || n.getBody().get().getStatements().isEmpty() && n.getBody().get() - .getChildNodes().isEmpty()) { + if (n.getBody().isEmpty() + || n.getBody().get().getStatements().isEmpty() + && n.getBody().get().getChildNodes().isEmpty()) { currentGraph.removeVertex(n.getNameAsString()); return; } @@ -136,8 +139,8 @@ public void visit(final MethodDeclaration n, // NOCS NOPMD } for (final NameExpr nameExpr : n.findAll(NameExpr.class)) { - if (currentFieldNames.contains(nameExpr.getNameAsString()) && isNotShadowedByLocalVariable( - localVariables, nameExpr)) { + if (currentFieldNames.contains(nameExpr.getNameAsString()) + && isNotShadowedByLocalVariable(localVariables, nameExpr)) { currentGraph.addEdge(n.getNameAsString(), nameExpr.getNameAsString()); } } @@ -147,7 +150,6 @@ public void visit(final MethodDeclaration n, // NOCS NOPMD // add something to method name so fields and methods can have the same name currentGraph.addEdge(n.getNameAsString(), method.getNameAsString()); } - } super.visit(n, data); data.a.leaveMethod(); @@ -163,10 +165,10 @@ public void visit(final ObjectCreationExpr n, final Pair private boolean isNotShadowedByLocalVariable( final List>> localVariables, final NameExpr nameExpr) { for (final Pair> entry : localVariables) { - if (nameExpr.getNameAsString().equals(entry.a) && entry.b.isPresent() && nameExpr.getRange() - .isPresent()) { + if (nameExpr.getNameAsString().equals(entry.a) + && entry.b.isPresent() + && nameExpr.getRange().isPresent()) { return entry.b.get().isAfter(nameExpr.getRange().get().end); - } } return true; @@ -174,8 +176,8 @@ private boolean isNotShadowedByLocalVariable( private boolean findInClassFields(final String field) { for (final FieldDeclaration f : currentFields) { - if (f.getVariables().getFirst().isPresent() && field.contains( - f.getVariables().getFirst().get().getName().toString())) { + if (f.getVariables().getFirst().isPresent() + && field.contains(f.getVariables().getFirst().get().getName().toString())) { return true; } } @@ -203,12 +205,12 @@ private void initNewClass() { private void leaveClass() { graphStack.pop(); - currentGraph = graphStack.isEmpty() ? null : graphStack.peek(); // NOPMD + currentGraph = graphStack.isEmpty() ? null : graphStack.peek(); fieldsStack.pop(); - currentFields = fieldsStack.isEmpty() ? null : fieldsStack.peek(); // NOPMD + currentFields = fieldsStack.isEmpty() ? null : fieldsStack.peek(); fieldNamesStack.pop(); - currentFieldNames = fieldNamesStack.isEmpty() ? null : fieldNamesStack.peek(); // NOPMD + currentFieldNames = fieldNamesStack.isEmpty() ? null : fieldNamesStack.peek(); methodNamesStack.pop(); - currentMethodNames = methodNamesStack.isEmpty() ? null : methodNamesStack.peek(); // NOPMD + currentMethodNames = methodNamesStack.isEmpty() ? null : methodNamesStack.peek(); } } diff --git a/src/main/java/net/explorviz/code/analysis/visitor/NPathVisitor.java b/src/main/java/net/explorviz/code/analysis/visitor/NPathVisitor.java index 227d4fa..73a13de 100644 --- a/src/main/java/net/explorviz/code/analysis/visitor/NPathVisitor.java +++ b/src/main/java/net/explorviz/code/analysis/visitor/NPathVisitor.java @@ -24,12 +24,10 @@ import net.explorviz.code.analysis.handler.JavaFileDataHandler; /** - * Visitor filling a FileData object with NPath data. Only handles constructor and methods bodies. This implementation - * is a reimplementation of the {@code NPathBaseVisitor} from PMD. + * Visitor filling a FileData object with NPath data. Only handles constructor and methods bodies. + * This implementation is a reimplementation of the {@code NPathBaseVisitor} from PMD. */ -public class NPathVisitor - extends GenericVisitorAdapter { // NOCS NOPMD - +public class NPathVisitor extends GenericVisitorAdapter { @Override public Integer visit(final ConstructorDeclaration n, final JavaFileDataHandler data) { @@ -59,7 +57,6 @@ public Integer visit(final BlockStmt n, final JavaFileDataHandler data) { if (childComplexity != null) { product *= childComplexity; } - } return product; @@ -104,7 +101,7 @@ public Integer visit(final WhileStmt n, final JavaFileDataHandler data) { // (npath of while + bool_comp of while + 1) * npath of next final int boolCompWhile = booleanExpressionComplexity(n.getCondition()); - final int nPathWhile = n.getBody().accept(this, data); // NOCS + final int nPathWhile = n.getBody().accept(this, data); return boolCompWhile + nPathWhile + 1; } @@ -114,7 +111,7 @@ public Integer visit(final DoStmt n, final JavaFileDataHandler data) { // (npath of do + bool_comp of do + 1) * npath of next final int boolCompDo = booleanExpressionComplexity(n.getCondition()); - final int nPathDo = n.getBody().accept(this, data); // NOCS + final int nPathDo = n.getBody().accept(this, data); return boolCompDo + nPathDo + 1; } @@ -134,7 +131,7 @@ public Integer visit(final ForStmt n, final JavaFileDataHandler data) { for (final Expression e : n.getUpdate()) { boolCompForUpdate += booleanExpressionComplexity(e); } - final int nPathFor = n.getBody().accept(this, data); // NOCS + final int nPathFor = n.getBody().accept(this, data); return boolCompForInit + boolCompForCompare + boolCompForUpdate + nPathFor + 1; } @@ -147,9 +144,9 @@ public Integer visit(final ReturnStmt n, final JavaFileDataHandler data) { } int boolCompReturn = booleanExpressionComplexity(n.getExpression().get()); // int conditionalExpressionComplexity = multiply(n.getExpression().get(), data); - final int conditionalExpressionComplexity = 0; // NOPMD + final int conditionalExpressionComplexity = 0; - if (conditionalExpressionComplexity > 1) { // NOPMD + if (conditionalExpressionComplexity > 1) { boolCompReturn += conditionalExpressionComplexity; } return boolCompReturn > 0 ? conditionalExpressionComplexity : 1; @@ -177,12 +174,12 @@ public Integer visit(final SwitchStmt n, final JavaFileDataHandler data) { // int caseRange = 0; for (final SwitchEntry entry : n.getEntries()) { - if (entry.getType().equals(SwitchEntry.Type.STATEMENT_GROUP) || entry.getType() - .equals(SwitchEntry.Type.THROWS_STATEMENT)) { + if (entry.getType().equals(SwitchEntry.Type.STATEMENT_GROUP) + || entry.getType().equals(SwitchEntry.Type.THROWS_STATEMENT)) { npath += caseRange; caseRange = 1; - } else if (entry.getType().equals(SwitchEntry.Type.EXPRESSION) || entry.getType().equals( - SwitchEntry.Type.BLOCK)) { + } else if (entry.getType().equals(SwitchEntry.Type.EXPRESSION) + || entry.getType().equals(SwitchEntry.Type.BLOCK)) { npath += caseRange; final int complexity = entry.accept(this, data); caseRange = complexity; @@ -246,10 +243,9 @@ private int booleanExpressionComplexity(final Expression expr) { } if (expr.isBinaryExpr()) { - final boolean isAndOperator = expr.asBinaryExpr().getOperator() - .equals(BinaryExpr.Operator.AND); - final boolean isOrOperator = expr.asBinaryExpr().getOperator() - .equals(BinaryExpr.Operator.OR); + final boolean isAndOperator = + expr.asBinaryExpr().getOperator().equals(BinaryExpr.Operator.AND); + final boolean isOrOperator = expr.asBinaryExpr().getOperator().equals(BinaryExpr.Operator.OR); if (isAndOperator || isOrOperator) { int complexity = 1; complexity += booleanExpressionComplexity(expr.asBinaryExpr().getLeft()); diff --git a/src/main/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitor.java b/src/main/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitor.java index 89385fc..a2d7b56 100644 --- a/src/main/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitor.java +++ b/src/main/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitor.java @@ -23,11 +23,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * This Visitor calculates the nestedBlockDepth for methods. - */ -public class NestedBlockDepthVisitor - extends VoidVisitorAdapter> { // NOPMD +/** This Visitor calculates the nestedBlockDepth for methods. */ +public class NestedBlockDepthVisitor extends VoidVisitorAdapter> { private static final Logger LOGGER = LoggerFactory.getLogger(NestedBlockDepthVisitor.class); @@ -57,7 +54,6 @@ public void visit(final FieldDeclaration n, final Pair d data.a.leaveMethod(); } - @Override public void visit(final MethodDeclaration n, final Pair data) { data.a.enterMethod(n); diff --git a/src/main/java/net/explorviz/code/analysis/visitor/VisitorStub.java b/src/main/java/net/explorviz/code/analysis/visitor/VisitorStub.java index 385ad0c..9a70cd8 100644 --- a/src/main/java/net/explorviz/code/analysis/visitor/VisitorStub.java +++ b/src/main/java/net/explorviz/code/analysis/visitor/VisitorStub.java @@ -12,13 +12,14 @@ import org.slf4j.LoggerFactory; /** - * This is a starting point for the implementation of further metrics. Use this stub as basis for your metrics - * collector. The type of the VoidVisitorAdapter is set to a Pair where Pair.a is reserved for the MetricAppender. - * Part.b is free to use and can be changed to whatever type is desired. The MetricAppender is a convenient way to add - * metrics to the internal File representation without the need to construct the fqn and keep track of the current - * Class-Method hierarchy. If more direct access is required, use the - * {@link net.explorviz.code.analysis.handler.JavaFileDataHandler} directly but you have to handle all the fqn tracking - * yourself. + * This is a starting point for the implementation of further metrics. Use this stub as basis for + * your metrics collector. The type of the VoidVisitorAdapter is set to a Pair where Pair.a is + * reserved for the MetricAppender. Part.b is free to use and can be changed to whatever type is + * desired. The MetricAppender is a convenient way to add metrics to the internal File + * representation without the need to construct the fqn and keep track of the current Class-Method + * hierarchy. If more direct access is required, use the {@link + * net.explorviz.code.analysis.handler.JavaFileDataHandler} directly but you have to handle all the + * fqn tracking yourself. */ public class VisitorStub extends VoidVisitorAdapter> { @@ -54,7 +55,6 @@ public void visit(final ClassOrInterfaceDeclaration n, final Pair data) { data.a.enterMethod(n); diff --git a/src/test/java/net/explorviz/code/analysis/FileLanguageResolverTest.java b/src/test/java/net/explorviz/code/analysis/FileLanguageResolverTest.java index 7d81d23..9c03896 100644 --- a/src/test/java/net/explorviz/code/analysis/FileLanguageResolverTest.java +++ b/src/test/java/net/explorviz/code/analysis/FileLanguageResolverTest.java @@ -42,21 +42,21 @@ void resolvesSupportedLanguagesFromExtension(final String filePath, final Langua @Test void prefersFileNameOverReportedPathWhenBothProvided() { - Assertions.assertEquals(Language.JAVA, - FileLanguageResolver.resolveFromFileName("Main.java", "src/unknown.bin")); + Assertions.assertEquals( + Language.JAVA, FileLanguageResolver.resolveFromFileName("Main.java", "src/unknown.bin")); } @Test void fallsBackToReportedPathWhenFileNameHasNoExtension() { - Assertions.assertEquals(Language.PYTHON, - FileLanguageResolver.resolveFromFileName("script", "src/script.py")); + Assertions.assertEquals( + Language.PYTHON, FileLanguageResolver.resolveFromFileName("script", "src/script.py")); } @Test void returnsUnspecifiedForUnknownExtension() { - Assertions.assertEquals(Language.LANGUAGE_UNSPECIFIED, - FileLanguageResolver.resolveFromFileName("archive.zip")); - Assertions.assertEquals(Language.LANGUAGE_UNSPECIFIED, - FileLanguageResolver.resolveFromFileName((String[]) null)); + Assertions.assertEquals( + Language.LANGUAGE_UNSPECIFIED, FileLanguageResolver.resolveFromFileName("archive.zip")); + Assertions.assertEquals( + Language.LANGUAGE_UNSPECIFIED, FileLanguageResolver.resolveFromFileName((String[]) null)); } } diff --git a/src/test/java/net/explorviz/code/analysis/GitAnalysisTest.java b/src/test/java/net/explorviz/code/analysis/GitAnalysisTest.java index 6b09a07..ab5d3b7 100644 --- a/src/test/java/net/explorviz/code/analysis/GitAnalysisTest.java +++ b/src/test/java/net/explorviz/code/analysis/GitAnalysisTest.java @@ -8,10 +8,8 @@ class GitAnalysisTest { @BeforeEach - void setUp() { - } + void setUp() {} @AfterEach - void tearDown() { - } -} \ No newline at end of file + void tearDown() {} +} diff --git a/src/test/java/net/explorviz/code/analysis/export/DataExporterBatchingTest.java b/src/test/java/net/explorviz/code/analysis/export/DataExporterBatchingTest.java index b45b03a..a7a5937 100644 --- a/src/test/java/net/explorviz/code/analysis/export/DataExporterBatchingTest.java +++ b/src/test/java/net/explorviz/code/analysis/export/DataExporterBatchingTest.java @@ -22,8 +22,12 @@ void smallCommitProducesSingleBatchDespiteHighSendConcurrency() throws Interrupt final BlockingQueue completedFiles = new LinkedBlockingQueue<>(); final CountDownLatch analysisFinished = new CountDownLatch(1); - final Thread batcher = Thread.ofVirtual().start(() -> exporter.persistFilesFromQueueInBatches( - completedFiles, analysisFinished, 500, 8)); + final Thread batcher = + Thread.ofVirtual() + .start( + () -> + exporter.persistFilesFromQueueInBatches( + completedFiles, analysisFinished, 500, 8)); for (int i = 0; i < 5; i++) { completedFiles.offer(FileData.getDefaultInstance()); @@ -42,8 +46,12 @@ void drainsQueuedFilesAsSoonAsPersistThreadStarts() throws InterruptedException completedFiles.offer(FileData.getDefaultInstance()); - final Thread batcher = Thread.ofVirtual().start(() -> exporter.persistFilesFromQueueInBatches( - completedFiles, analysisFinished, 500, 8)); + final Thread batcher = + Thread.ofVirtual() + .start( + () -> + exporter.persistFilesFromQueueInBatches( + completedFiles, analysisFinished, 500, 8)); analysisFinished.countDown(); batcher.join(); @@ -57,8 +65,12 @@ void largeCommitUsesFullBatchesAndMinimalRemainder() throws InterruptedException final BlockingQueue completedFiles = new LinkedBlockingQueue<>(); final CountDownLatch analysisFinished = new CountDownLatch(1); - final Thread batcher = Thread.ofVirtual().start(() -> exporter.persistFilesFromQueueInBatches( - completedFiles, analysisFinished, 500, 8)); + final Thread batcher = + Thread.ofVirtual() + .start( + () -> + exporter.persistFilesFromQueueInBatches( + completedFiles, analysisFinished, 500, 8)); for (int i = 0; i < 1200; i++) { completedFiles.offer(FileData.getDefaultInstance()); @@ -77,9 +89,13 @@ private static final class RecordingExporter implements DataExporter { private final List batchSizes = Collections.synchronizedList(new ArrayList<>()); @Override - public StateData getStateData(final String repositoryName, final String branchName, - final String token, final Map applicationPaths, - final String repositoryUrl, final boolean skipLatestCommitLookup) { + public StateData getStateData( + final String repositoryName, + final String branchName, + final String token, + final Map applicationPaths, + final String repositoryUrl, + final boolean skipLatestCommitLookup) { throw new UnsupportedOperationException(); } diff --git a/src/test/java/net/explorviz/code/analysis/export/FileDataExportFilterTest.java b/src/test/java/net/explorviz/code/analysis/export/FileDataExportFilterTest.java index baef9db..bae8692 100644 --- a/src/test/java/net/explorviz/code/analysis/export/FileDataExportFilterTest.java +++ b/src/test/java/net/explorviz/code/analysis/export/FileDataExportFilterTest.java @@ -35,10 +35,12 @@ private static FileData sampleFileData() { .setFilePath("src/Main.java") .putMetrics("loc", 42.0) .addClasses(ClassData.newBuilder().setName("Main").build()) - .addFunctions(FunctionData.newBuilder() - .setName("run") - .addParameters(ParameterData.newBuilder().setName("args").setType("String[]").build()) - .build()) + .addFunctions( + FunctionData.newBuilder() + .setName("run") + .addParameters( + ParameterData.newBuilder().setName("args").setType("String[]").build()) + .build()) .build(); } } diff --git a/src/test/java/net/explorviz/code/analysis/git/DirectoryFinderTest.java b/src/test/java/net/explorviz/code/analysis/git/DirectoryFinderTest.java index 3d8270f..aeb2654 100644 --- a/src/test/java/net/explorviz/code/analysis/git/DirectoryFinderTest.java +++ b/src/test/java/net/explorviz/code/analysis/git/DirectoryFinderTest.java @@ -17,9 +17,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Testing the repository loader. - */ +/** Testing the repository loader. */ @QuarkusTest public class DirectoryFinderTest { @@ -40,28 +38,25 @@ void setup() throws IOException { Files.createDirectories(Paths.get(tempLocation.getAbsolutePath(), "src", "test", "java")); } - @AfterEach void tearDown() throws IOException { try (Stream walk = Files.walk(tempLocation.toPath())) { - walk.sorted(Comparator.reverseOrder()).map(Path::toFile) - .forEach(File::delete); + walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } } - @Test() void testRelativePathWithWildcard() { List searchPaths = new ArrayList<>(); searchPaths.add("**/main/java"); try { - List absolutePaths = DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), - searchPaths); + List absolutePaths = + DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), searchPaths); Assertions.assertEquals(1, absolutePaths.size()); // clean string from mutiple slashes and bring to system seperator - final String expected = (tempLocation.getAbsolutePath() + MAIN_SOURCE_PATH).replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); + final String expected = + (tempLocation.getAbsolutePath() + MAIN_SOURCE_PATH) + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); Assertions.assertEquals(expected, absolutePaths.get(0)); } catch (NotFoundException e) { Assertions.fail(NOT_FOUND); @@ -73,12 +68,12 @@ void testLeadingWildcardPath() { List searchPaths = new ArrayList<>(); searchPaths.add("src/main/java"); try { - List absolutePaths = DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), - searchPaths); + List absolutePaths = + DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), searchPaths); Assertions.assertEquals(1, absolutePaths.size()); - String s = (tempLocation.getAbsolutePath() + "/src/main/java").replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); + String s = + (tempLocation.getAbsolutePath() + "/src/main/java") + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); Assertions.assertEquals(absolutePaths.get(0), s); } catch (NotFoundException e) { Assertions.fail(NOT_FOUND); @@ -92,12 +87,12 @@ void testForNoDuplicates() { searchPaths.add("**/main/java"); searchPaths.add("src/main/java"); try { - List absolutePaths = DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), - searchPaths); + List absolutePaths = + DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), searchPaths); Assertions.assertEquals(1, absolutePaths.size()); - String s = (tempLocation.getAbsolutePath() + "/src/main/java").replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); + String s = + (tempLocation.getAbsolutePath() + "/src/main/java") + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); Assertions.assertEquals(absolutePaths.get(0), s); } catch (NotFoundException e) { Assertions.fail(NOT_FOUND); @@ -109,15 +104,15 @@ void testInfixWildcardPath() { List searchPaths = new ArrayList<>(); searchPaths.add("src/*/java"); try { - List absolutePaths = DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), - searchPaths); + List absolutePaths = + DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), searchPaths); Assertions.assertEquals(2, absolutePaths.size()); - String expected1 = (tempLocation.getAbsolutePath() + TEST_SOURCE_PATH).replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); - String expected2 = (tempLocation.getAbsolutePath() + MAIN_SOURCE_PATH).replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); + String expected1 = + (tempLocation.getAbsolutePath() + TEST_SOURCE_PATH) + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); + String expected2 = + (tempLocation.getAbsolutePath() + MAIN_SOURCE_PATH) + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); Assertions.assertTrue(absolutePaths.contains(expected1)); Assertions.assertTrue(absolutePaths.contains(expected2)); } catch (NotFoundException e) { @@ -134,18 +129,18 @@ void testZeroOrMoreDirectoriesWildcardPath() throws IOException { Paths.get(tempLocation.getAbsolutePath(), SRC, "test", "integration", JAVA)); try { - List absolutePaths = DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), - searchPaths); + List absolutePaths = + DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), searchPaths); Assertions.assertEquals(3, absolutePaths.size()); - String expected1 = (tempLocation.getAbsolutePath() + "/src/test/integration/java").replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); - String expected2 = (tempLocation.getAbsolutePath() + "/src/main/java").replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); - String expected3 = (tempLocation.getAbsolutePath() + "/src/test/java").replaceAll( - REGEX_CONTAINS_SLASH, - Matcher.quoteReplacement(File.separator)); + String expected1 = + (tempLocation.getAbsolutePath() + "/src/test/integration/java") + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); + String expected2 = + (tempLocation.getAbsolutePath() + "/src/main/java") + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); + String expected3 = + (tempLocation.getAbsolutePath() + "/src/test/java") + .replaceAll(REGEX_CONTAINS_SLASH, Matcher.quoteReplacement(File.separator)); Assertions.assertTrue(absolutePaths.contains(expected1)); Assertions.assertTrue(absolutePaths.contains(expected2)); Assertions.assertTrue(absolutePaths.contains(expected3)); @@ -158,8 +153,8 @@ void testZeroOrMoreDirectoriesWildcardPath() throws IOException { void testConsecutiveInfixWildcardUnresolvablePath() throws NotFoundException { List searchPaths = new ArrayList<>(); searchPaths.add("src/*/*/main/java"); - List result = DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), - searchPaths); + List result = + DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), searchPaths); Assertions.assertEquals(0, result.size()); } @@ -167,8 +162,8 @@ void testConsecutiveInfixWildcardUnresolvablePath() throws NotFoundException { void testRegexMatching() throws NotFoundException { List searchPaths = new ArrayList<>(); searchPaths.add("src/main/**"); - List absolutePaths = DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), - searchPaths); + List absolutePaths = + DirectoryFinder.getDirectories(tempLocation.getAbsolutePath(), searchPaths); Assertions.assertEquals(1, absolutePaths.size()); } } diff --git a/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java b/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java index 930debf..eb076c2 100644 --- a/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java +++ b/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java @@ -25,12 +25,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -/** - * Testing the repository loader. - */ +/** Testing the repository loader. */ @QuarkusTest public class GitRepositoryHandlerTest { @@ -38,8 +35,7 @@ public class GitRepositoryHandlerTest { private final String sshUrl = "git@gitlab.com:0xhexdec/busydoingnothing.git"; private final String httpsUrl = "https://gitlab.com/0xhexdec/busydoingnothing.git"; - @Inject - GitRepositoryHandler gitRepositoryHandler; // NOCS + @Inject GitRepositoryHandler gitRepositoryHandler; private File tempGitLocation; @BeforeEach @@ -50,8 +46,7 @@ void setup() throws IOException { @AfterEach void tearDown() { try (Stream walk = Files.walk(tempGitLocation.toPath())) { - walk.sorted(Comparator.reverseOrder()).map(Path::toFile) - .forEach(File::delete); + walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } catch (IOException exception) { System.err.println("Folder not deletable"); } @@ -61,61 +56,72 @@ void tearDown() { void testInvalidRemote() { String url = "%%%%"; - Assertions.assertThrows(InvalidRemoteException.class, () -> { - this.gitRepositoryHandler.getGitRepository("", - new RemoteRepositoryObject(url, tempGitLocation.getAbsolutePath(), MASTER)); - }); + Assertions.assertThrows( + InvalidRemoteException.class, + () -> { + this.gitRepositoryHandler.getGitRepository( + "", new RemoteRepositoryObject(url, tempGitLocation.getAbsolutePath(), MASTER)); + }); } @Test void testInvalidParameters() { - Assertions.assertThrows(InvalidRemoteException.class, () -> { - this.gitRepositoryHandler.getGitRepository("", new RemoteRepositoryObject("", "", MASTER)); - }); + Assertions.assertThrows( + InvalidRemoteException.class, + () -> { + this.gitRepositoryHandler.getGitRepository( + "", new RemoteRepositoryObject("", "", MASTER)); + }); } @Test() void testMalformedRemote() { String url = "https://gitlab.com/0xhexdec/"; - Assertions.assertThrows(MalformedURLException.class, () -> { - this.gitRepositoryHandler.getGitRepository("", - new RemoteRepositoryObject(url, tempGitLocation.getAbsolutePath(), MASTER)); - }); + Assertions.assertThrows( + MalformedURLException.class, + () -> { + this.gitRepositoryHandler.getGitRepository( + "", new RemoteRepositoryObject(url, tempGitLocation.getAbsolutePath(), MASTER)); + }); } @Test void testFileInsteadDirectory() throws IOException { File file = new File(tempGitLocation.getAbsolutePath() + "/file"); Assertions.assertTrue(file.createNewFile()); - Assertions.assertThrows(NotDirectoryException.class, () -> { - this.gitRepositoryHandler.getGitRepository(file.getAbsolutePath(), - new RemoteRepositoryObject("", "", MASTER)); - }); + Assertions.assertThrows( + NotDirectoryException.class, + () -> { + this.gitRepositoryHandler.getGitRepository( + file.getAbsolutePath(), new RemoteRepositoryObject("", "", MASTER)); + }); } @Test void openRepository() throws GitAPIException, IOException { // downloading the repository first - try (Repository repository = this.gitRepositoryHandler.getGitRepository("", - new RemoteRepositoryObject(httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { + try (Repository repository = + this.gitRepositoryHandler.getGitRepository( + "", new RemoteRepositoryObject(httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { // call is here to satisfy checkstyle by not having empty try block System.out.println(GitRepositoryHandler.getRemoteOriginUrl(repository)); } catch (Exception e) { Assertions.fail(); } // checking the same folder and reopen the repository - try (Repository repository = this.gitRepositoryHandler.getGitRepository( - tempGitLocation.getAbsolutePath() + "/busydoingnothing", new RemoteRepositoryObject())) { + try (Repository repository = + this.gitRepositoryHandler.getGitRepository( + tempGitLocation.getAbsolutePath() + "/busydoingnothing", + new RemoteRepositoryObject())) { Assertions.assertEquals(GitRepositoryHandler.getRemoteOriginUrl(repository), httpsUrl); } - } @Test() void testSsh() { - try (Repository repository = this.gitRepositoryHandler.getGitRepository("", - new RemoteRepositoryObject(sshUrl, - tempGitLocation.getAbsolutePath(), MASTER))) { + try (Repository repository = + this.gitRepositoryHandler.getGitRepository( + "", new RemoteRepositoryObject(sshUrl, tempGitLocation.getAbsolutePath(), MASTER))) { // call is here to satisfy checkstyle by not having empty try block repository.getBranch(); } catch (Exception e) { @@ -125,9 +131,9 @@ void testSsh() { @Test() void testHttps() { - try (Repository repository = this.gitRepositoryHandler.getGitRepository("", - new RemoteRepositoryObject( - httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { + try (Repository repository = + this.gitRepositoryHandler.getGitRepository( + "", new RemoteRepositoryObject(httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { // call is here to satisfy checkstyle by not having empty try block repository.getBranch(); } catch (Exception e) { @@ -137,11 +143,11 @@ void testHttps() { @Test() void testSshConversion() { - Assertions.assertEquals(Map.entry(true, httpsUrl), - GitRepositoryHandler.convertSshToHttps(httpsUrl)); + Assertions.assertEquals( + Map.entry(true, httpsUrl), GitRepositoryHandler.convertSshToHttps(httpsUrl)); - Assertions.assertEquals(Map.entry(true, httpsUrl), - GitRepositoryHandler.convertSshToHttps(sshUrl)); + Assertions.assertEquals( + Map.entry(true, httpsUrl), GitRepositoryHandler.convertSshToHttps(sshUrl)); // GitHub SSH URL with .git suffix Assertions.assertEquals( @@ -166,28 +172,29 @@ void testSshConversion() { // if the url looks off, assume the user wants it that way final String urlUnderTest2 = "abc.xyz"; - Assertions.assertEquals(Map.entry(false, urlUnderTest2), - GitRepositoryHandler.convertSshToHttps(urlUnderTest2)); - + Assertions.assertEquals( + Map.entry(false, urlUnderTest2), GitRepositoryHandler.convertSshToHttps(urlUnderTest2)); } @Test void testRemoteLookup() throws GitAPIException, IOException { - try (Repository repository = this.gitRepositoryHandler.getGitRepository("", - new RemoteRepositoryObject(httpsUrl, - tempGitLocation.getAbsolutePath(), MASTER))) { + try (Repository repository = + this.gitRepositoryHandler.getGitRepository( + "", new RemoteRepositoryObject(httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { Assertions.assertEquals(GitRepositoryHandler.getRemoteOriginUrl(repository), httpsUrl); } } @Test() - void testGetStringifiedFileInCommit() - throws GitAPIException, IOException { + void testGetStringifiedFileInCommit() throws GitAPIException, IOException { - try (final Repository repository = this.gitRepositoryHandler.getGitRepository("", - new RemoteRepositoryObject( - "https://github.com/Alexander-Krause-Glau/Test-JGit-Code.git", - tempGitLocation.getAbsolutePath(), MASTER))) { + try (final Repository repository = + this.gitRepositoryHandler.getGitRepository( + "", + new RemoteRepositoryObject( + "https://github.com/Alexander-Krause-Glau/Test-JGit-Code.git", + tempGitLocation.getAbsolutePath(), + MASTER))) { try (RevWalk walk = new RevWalk(repository)) { final ObjectId id = repository.resolve("8ee1f25"); @@ -200,31 +207,38 @@ void testGetStringifiedFileInCommit() treeWalk.addTree(tree); treeWalk.setRecursive(true); while (treeWalk.next()) { - final String actual = GitRepositoryHandler.getContent(treeWalk.getObjectId(0), - repository); - final String expected = "package testgit.my.test.pckg;\n" + "\n" + "public class TestGitClass {\n" + "\n" - + " private final String testVariable;\n" + "\n" - + " public TestGitClass(final String testVariable) {\n" - + " this.testVariable = testVariable;\n" + " }\n" + "\n" + "}"; - - Assertions.assertEquals(expected.replace(" ", "").replace("\n", "").replace("\r", ""), + final String actual = + GitRepositoryHandler.getContent(treeWalk.getObjectId(0), repository); + final String expected = + "package testgit.my.test.pckg;\n" + + "\n" + + "public class TestGitClass {\n" + + "\n" + + " private final String testVariable;\n" + + "\n" + + " public TestGitClass(final String testVariable) {\n" + + " this.testVariable = testVariable;\n" + + " }\n" + + "\n" + + "}"; + + Assertions.assertEquals( + expected.replace(" ", "").replace("\n", "").replace("\r", ""), actual.replace(" ", "").replace("\n", "").replace("\r", "")); walk.dispose(); - } } } } - } @Test - void configureBranchRevWalk_firstParentOnly_excludesMergedBranchCommits() - throws IOException { - final Path traceServiceRepo = Path.of(System.getProperty("user.home"), "Documents", - "ExplorViz", "trace-service"); - Assumptions.assumeTrue(Files.isDirectory(traceServiceRepo.resolve(".git")), + void configureBranchRevWalk_firstParentOnly_excludesMergedBranchCommits() throws IOException { + final Path traceServiceRepo = + Path.of(System.getProperty("user.home"), "Documents", "ExplorViz", "trace-service"); + Assumptions.assumeTrue( + Files.isDirectory(traceServiceRepo.resolve(".git")), "Local trace-service clone required for this test"); try (Git git = Git.open(traceServiceRepo.toFile()); @@ -238,7 +252,8 @@ void configureBranchRevWalk_firstParentOnly_excludesMergedBranchCommits() gitRepositoryHandler.configureBranchRevWalk(firstParentWalk, repository, mainRef, true); final long firstParentCount = countCommits(firstParentWalk); - Assertions.assertTrue(firstParentCount < allReachableCount, + Assertions.assertTrue( + firstParentCount < allReachableCount, "First-parent walk must exclude merged feature-branch commits"); } } @@ -246,9 +261,10 @@ void configureBranchRevWalk_firstParentOnly_excludesMergedBranchCommits() @Test void configureBranchRevWalk_includesAllReachableCommits_whenFirstParentDisabled() throws IOException { - final Path traceServiceRepo = Path.of(System.getProperty("user.home"), "Documents", - "ExplorViz", "trace-service"); - Assumptions.assumeTrue(Files.isDirectory(traceServiceRepo.resolve(".git")), + final Path traceServiceRepo = + Path.of(System.getProperty("user.home"), "Documents", "ExplorViz", "trace-service"); + Assumptions.assumeTrue( + Files.isDirectory(traceServiceRepo.resolve(".git")), "Local trace-service clone required for this test"); try (Git git = Git.open(traceServiceRepo.toFile()); @@ -263,7 +279,8 @@ void configureBranchRevWalk_includesAllReachableCommits_whenFirstParentDisabled( final long branchWalkCount = countCommits(branchWalk); Assertions.assertEquals(allReachableCount, branchWalkCount); - Assertions.assertTrue(branchWalkCount > 72, + Assertions.assertTrue( + branchWalkCount > 72, "Branch walk must include merged commits when first-parent filtering is disabled"); } } @@ -275,5 +292,4 @@ private static long countCommits(final RevWalk revWalk) { } return count; } - } diff --git a/src/test/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactoryTest.java b/src/test/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactoryTest.java index b93a0e8..3948587 100644 --- a/src/test/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactoryTest.java +++ b/src/test/java/net/explorviz/code/analysis/handler/FallbackFileDataHandlerFactoryTest.java @@ -40,27 +40,27 @@ static Stream supportedProgrammingLanguages() { @ParameterizedTest @MethodSource("supportedProgrammingLanguages") void createsFallbackWithLanguageLocAndSize(final String filePath, final Language language) { - final FileDescriptor file = new FileDescriptor(ObjectId.zeroId(), filePath, - filePath); + final FileDescriptor file = new FileDescriptor(ObjectId.zeroId(), filePath, filePath); - final TextFileDataHandler handler = FallbackFileDataHandlerFactory.create(file, CONTENT, - language); + final TextFileDataHandler handler = + FallbackFileDataHandlerFactory.create(file, CONTENT, language); Assertions.assertEquals(language, handler.getProtoBufObject().getLanguage()); Assertions.assertEquals("2.0", handler.getMetricValue("lineCount")); - Assertions.assertEquals(String.valueOf((double) CONTENT.length()), handler.getMetricValue("size")); + Assertions.assertEquals( + String.valueOf((double) CONTENT.length()), handler.getMetricValue("size")); } @ParameterizedTest @MethodSource("supportedProgrammingLanguages") void infersLanguageFromFilePath(final String filePath, final Language language) { - final FileDescriptor file = new FileDescriptor(ObjectId.zeroId(), filePath, - filePath); + final FileDescriptor file = new FileDescriptor(ObjectId.zeroId(), filePath, filePath); final TextFileDataHandler handler = FallbackFileDataHandlerFactory.create(file, CONTENT); Assertions.assertEquals(language, handler.getProtoBufObject().getLanguage()); Assertions.assertEquals("2.0", handler.getMetricValue("lineCount")); - Assertions.assertEquals(String.valueOf((double) CONTENT.length()), handler.getMetricValue("size")); + Assertions.assertEquals( + String.valueOf((double) CONTENT.length()), handler.getMetricValue("size")); } } diff --git a/src/test/java/net/explorviz/code/analysis/listener/PythonFileDataListenerTest.java b/src/test/java/net/explorviz/code/analysis/listener/PythonFileDataListenerTest.java index e77117c..fc3b48a 100644 --- a/src/test/java/net/explorviz/code/analysis/listener/PythonFileDataListenerTest.java +++ b/src/test/java/net/explorviz/code/analysis/listener/PythonFileDataListenerTest.java @@ -14,52 +14,51 @@ public class PythonFileDataListenerTest { @Test public void testMultipleFunctionsLineNumbers() { // Python file with 3 functions and comments between them - final String pythonCode = """ + final String pythonCode = + """ # This is a comment def first_function(): print("hello") return 1 - + # Comment before next function def second_function(): data = get_data() return data - + # Comment before third function def third_function(): pass """; final AntlrPythonParserService parser = new AntlrPythonParserService(); - final PythonFileDataHandler handler = parser.parseFileContent( - pythonCode, - "test.py", - "test123" - ); + final PythonFileDataHandler handler = parser.parseFileContent(pythonCode, "test.py", "test123"); assertNotNull(handler, "Parser should return a handler"); final FileData fileData = handler.getProtoBufObject(); - assertEquals(3, fileData.getFunctionsCount(), - "Should have 3 global functions"); + assertEquals(3, fileData.getFunctionsCount(), "Should have 3 global functions"); FunctionData firstFunc = fileData.getFunctions(0); assertEquals("first_function", firstFunc.getName()); assertEquals(2, firstFunc.getStartLine(), "first_function should start at line 2"); - assertEquals(4, firstFunc.getEndLine(), + assertEquals( + 4, + firstFunc.getEndLine(), "first_function should end at line 4 (skipping blank line 5 and comment on line 6)"); FunctionData secondFunc = fileData.getFunctions(1); assertEquals("second_function", secondFunc.getName()); assertEquals(7, secondFunc.getStartLine(), "second_function should start at line 7"); - assertEquals(9, secondFunc.getEndLine(), + assertEquals( + 9, + secondFunc.getEndLine(), "second_function should end at line 9 (skipping blank line 10 and comment on line 11)"); FunctionData thirdFunc = fileData.getFunctions(2); assertEquals("third_function", thirdFunc.getName()); assertEquals(12, thirdFunc.getStartLine(), "third_function should start at line 12"); - assertEquals(13, thirdFunc.getEndLine(), - "third_function should end at line 13"); + assertEquals(13, thirdFunc.getEndLine(), "third_function should end at line 13"); } } diff --git a/src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java b/src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java index 2634638..e34d472 100644 --- a/src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java +++ b/src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java @@ -11,7 +11,8 @@ @QuarkusTest class JsConfigParsePerfTest { - private static final String WEBPACK_DEVALL_JS = """ + private static final String WEBPACK_DEVALL_JS = + """ // Specific configuration for developers // Runs a HTTPS server in port 8080, accesible // from all hosts that can connect to it. @@ -44,13 +45,12 @@ class JsConfigParsePerfTest { }); """; - private static final Path BABIA_EASYRTC = Path.of( - "cloned-repositories/aframe-babia-components/examples/multiuser/easyrtc.js"); - private static final Path BABIA_BOATS = Path.of( - "cloned-repositories/aframe-babia-components/components/visualizers/babia-boats.js"); + private static final Path BABIA_EASYRTC = + Path.of("cloned-repositories/aframe-babia-components/examples/multiuser/easyrtc.js"); + private static final Path BABIA_BOATS = + Path.of("cloned-repositories/aframe-babia-components/components/visualizers/babia-boats.js"); - @Inject - AntlrTypeScriptParserService parser; + @Inject AntlrTypeScriptParserService parser; @Test void webpackDevallParsesQuickly() { @@ -73,7 +73,8 @@ void easyrtcTimesOutInsteadOfHanging() throws Exception { System.out.println("easyrtc.js parse took " + ms + " ms, handler=" + (handler != null)); // Pathological vendored JS: full parse should time out and fall back (null). Assertions.assertNull(handler); - Assertions.assertTrue(ms < budget, "Expected timeout under " + budget + "ms, was " + ms + " ms"); + Assertions.assertTrue( + ms < budget, "Expected timeout under " + budget + "ms, was " + ms + " ms"); } @Test @@ -86,7 +87,8 @@ void babiaBoatsDoesNotHang() throws Exception { parser.parseFileContent(content, "components/visualizers/babia-boats.js", "hash"); final long ms = (System.nanoTime() - start) / 1_000_000L; System.out.println("babia-boats.js parse took " + ms + " ms"); - Assertions.assertTrue(ms < budget, "Expected parse/timeout under " + budget + "ms, was " + ms + " ms"); + Assertions.assertTrue( + ms < budget, "Expected parse/timeout under " + budget + "ms, was " + ms + " ms"); } static boolean babiaRepoPresent() { diff --git a/src/test/java/net/explorviz/code/analysis/parser/ParseTimeoutTest.java b/src/test/java/net/explorviz/code/analysis/parser/ParseTimeoutTest.java index 4f4a5b3..e4c6c0c 100644 --- a/src/test/java/net/explorviz/code/analysis/parser/ParseTimeoutTest.java +++ b/src/test/java/net/explorviz/code/analysis/parser/ParseTimeoutTest.java @@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory; /** - * Verifies that {@code parse-timeout-ms} aborts ANTLR prediction on the parsing thread itself - * (via {@link AntlrParserUtils.DeadlineTokenStream}), rather than leaving a zombie worker. + * Verifies that {@code parse-timeout-ms} aborts ANTLR prediction on the parsing thread itself (via + * {@link AntlrParserUtils.DeadlineTokenStream}), rather than leaving a zombie worker. */ class ParseTimeoutTest { @@ -41,19 +41,24 @@ void deadlineAbortsPathologicalObjectLiteralWithinBudget() { final long timeoutMs = 200L; final long budgetMs = timeoutMs + 2_000L; final long start = System.nanoTime(); - final ParseCancellationException thrown = Assertions.assertThrows( - ParseCancellationException.class, - () -> AntlrParserUtils.parseTwoStage( - parser, tokens, LoggerFactory.getLogger(ParseTimeoutTest.class), - "pathological.js", timeoutMs, parser::program)); + final ParseCancellationException thrown = + Assertions.assertThrows( + ParseCancellationException.class, + () -> + AntlrParserUtils.parseTwoStage( + parser, + tokens, + LoggerFactory.getLogger(ParseTimeoutTest.class), + "pathological.js", + timeoutMs, + parser::program)); final long elapsedMs = (System.nanoTime() - start) / 1_000_000L; Assertions.assertTrue( thrown.getMessage() != null && thrown.getMessage().startsWith("Parse timeout after "), "Expected timeout cancellation, got: " + thrown.getMessage()); Assertions.assertTrue( - elapsedMs < budgetMs, - "Expected abort under " + budgetMs + " ms, was " + elapsedMs + " ms"); + elapsedMs < budgetMs, "Expected abort under " + budgetMs + " ms, was " + elapsedMs + " ms"); } @Test @@ -64,8 +69,14 @@ void zeroTimeoutDisablesDeadline() { final TypeScriptParser parser = new TypeScriptParser(tokens); parser.setJavaScriptMode(true); - Assertions.assertDoesNotThrow(() -> AntlrParserUtils.parseTwoStage( - parser, tokens, LoggerFactory.getLogger(ParseTimeoutTest.class), - "ok.js", 0L, parser::program)); + Assertions.assertDoesNotThrow( + () -> + AntlrParserUtils.parseTwoStage( + parser, + tokens, + LoggerFactory.getLogger(ParseTimeoutTest.class), + "ok.js", + 0L, + parser::program)); } } diff --git a/src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java b/src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java index 6c007f5..9dd9e97 100644 --- a/src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java +++ b/src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java @@ -8,8 +8,8 @@ class TsxJsxNormalizerTest { - private static final Path SUBJECTS_PAGE = Path.of( - "cloned-repositories/wsp-3-1/frontend/src/pages/masterdata/SubjectsPage.tsx"); + private static final Path SUBJECTS_PAGE = + Path.of("cloned-repositories/wsp-3-1/frontend/src/pages/masterdata/SubjectsPage.tsx"); @Test @EnabledIf("wspRepoPresent") diff --git a/src/test/java/net/explorviz/code/analysis/parser/Wsp31ParsePerfTest.java b/src/test/java/net/explorviz/code/analysis/parser/Wsp31ParsePerfTest.java index ced0609..d0cb1d0 100644 --- a/src/test/java/net/explorviz/code/analysis/parser/Wsp31ParsePerfTest.java +++ b/src/test/java/net/explorviz/code/analysis/parser/Wsp31ParsePerfTest.java @@ -13,14 +13,11 @@ class Wsp31ParsePerfTest { private static final Path REPO = Path.of("cloned-repositories/wsp-3-1"); - @Inject - AntlrTypeScriptParserService tsParser; + @Inject AntlrTypeScriptParserService tsParser; - @Inject - AntlrPythonParserService pythonParser; + @Inject AntlrPythonParserService pythonParser; - @Inject - AntlrParserService javaParser; + @Inject AntlrParserService javaParser; @Test @EnabledIf("repoPresent") @@ -56,8 +53,8 @@ private void assertParsesWithinBudget(final String relativePath, final long budg final var handler = tsParser.parseFileContent(content, relativePath, "hash"); final long ms = (System.nanoTime() - start) / 1_000_000L; System.out.println(relativePath + " TS parse took " + ms + " ms, handler=" + (handler != null)); - Assertions.assertTrue(ms < budgetMs, - relativePath + " expected under " + budgetMs + " ms, was " + ms + " ms"); + Assertions.assertTrue( + ms < budgetMs, relativePath + " expected under " + budgetMs + " ms, was " + ms + " ms"); } private void assertPythonWithinBudget(final String relativePath, final long budgetMs) @@ -67,9 +64,10 @@ private void assertPythonWithinBudget(final String relativePath, final long budg final long start = System.nanoTime(); final var handler = pythonParser.parseFileContent(content, relativePath, "hash"); final long ms = (System.nanoTime() - start) / 1_000_000L; - System.out.println(relativePath + " Python parse took " + ms + " ms, handler=" + (handler != null)); - Assertions.assertTrue(ms < budgetMs, - relativePath + " expected under " + budgetMs + " ms, was " + ms + " ms"); + System.out.println( + relativePath + " Python parse took " + ms + " ms, handler=" + (handler != null)); + Assertions.assertTrue( + ms < budgetMs, relativePath + " expected under " + budgetMs + " ms, was " + ms + " ms"); } private void assertJavaWithinBudget(final String relativePath, final long budgetMs) @@ -79,9 +77,10 @@ private void assertJavaWithinBudget(final String relativePath, final long budget final long start = System.nanoTime(); final var handler = javaParser.parseFileContent(content, relativePath, "hash"); final long ms = (System.nanoTime() - start) / 1_000_000L; - System.out.println(relativePath + " Java parse took " + ms + " ms, handler=" + (handler != null)); - Assertions.assertTrue(ms < budgetMs, - relativePath + " expected under " + budgetMs + " ms, was " + ms + " ms"); + System.out.println( + relativePath + " Java parse took " + ms + " ms, handler=" + (handler != null)); + Assertions.assertTrue( + ms < budgetMs, relativePath + " expected under " + budgetMs + " ms, was " + ms + " ms"); } static boolean repoPresent() { diff --git a/src/test/java/net/explorviz/code/analysis/regression/GoldenFileRegressionSupport.java b/src/test/java/net/explorviz/code/analysis/regression/GoldenFileRegressionSupport.java index c329ed3..4bdba92 100644 --- a/src/test/java/net/explorviz/code/analysis/regression/GoldenFileRegressionSupport.java +++ b/src/test/java/net/explorviz/code/analysis/regression/GoldenFileRegressionSupport.java @@ -10,15 +10,12 @@ import net.explorviz.code.proto.FileData; import org.junit.jupiter.api.Assertions; -/** - * Shared helpers for golden-file parser regression tests. - */ +/** Shared helpers for golden-file parser regression tests. */ final class GoldenFileRegressionSupport { static final String FILE_HASH = "regression-hash"; - private GoldenFileRegressionSupport() { - } + private GoldenFileRegressionSupport() {} static String fixturePath(final String languageDir, final String fileName) { return "src/test/resources/regression/" + languageDir + "/" + fileName; @@ -57,8 +54,8 @@ static FileData parseFileDataJson(final String json) throws IOException { return builder.build(); } - static String readExpectedJson(final Class owner, final String languageDir, - final String fileName) throws IOException { + static String readExpectedJson( + final Class owner, final String languageDir, final String fileName) throws IOException { final String resource = expectedResourcePath(languageDir, fileName); try (InputStream in = owner.getResourceAsStream(resource)) { Assertions.assertNotNull(in, "Missing expected JSON resource: " + resource); @@ -66,25 +63,37 @@ static String readExpectedJson(final Class owner, final String languageDir, } } - static void assertMatchesGolden(final Class owner, final String languageDir, - final String fileName, final FileData actual) throws IOException { + static void assertMatchesGolden( + final Class owner, final String languageDir, final String fileName, final FileData actual) + throws IOException { final String actualJson = toJson(actual); if (Boolean.getBoolean("updateGoldenFiles")) { - Files.writeString(Path.of(expectedFilesystemPath(languageDir, fileName)), - actualJson + "\n", StandardCharsets.UTF_8); + Files.writeString( + Path.of(expectedFilesystemPath(languageDir, fileName)), + actualJson + "\n", + StandardCharsets.UTF_8); return; } final String expectedJson = readExpectedJson(owner, languageDir, fileName); final FileData expected = parseFileDataJson(expectedJson); - Assertions.assertEquals(expected, actual, - () -> "Analyzer output diverged from golden file for " - + languageDir + "/" + fileName + ".\n" - + "If the change is intentional, regenerate with -DupdateGoldenFiles=true\n" - + "--- expected ---\n" + expectedJson + "\n" - + "--- actual ---\n" + actualJson); + Assertions.assertEquals( + expected, + actual, + () -> + "Analyzer output diverged from golden file for " + + languageDir + + "/" + + fileName + + ".\n" + + "If the change is intentional, regenerate with -DupdateGoldenFiles=true\n" + + "--- expected ---\n" + + expectedJson + + "\n" + + "--- actual ---\n" + + actualJson); } private static String unescapeHtml(final String json) { diff --git a/src/test/java/net/explorviz/code/analysis/regression/ParserTestInputRegressionTest.java b/src/test/java/net/explorviz/code/analysis/regression/ParserTestInputRegressionTest.java index c1c1882..1c99261 100644 --- a/src/test/java/net/explorviz/code/analysis/regression/ParserTestInputRegressionTest.java +++ b/src/test/java/net/explorviz/code/analysis/regression/ParserTestInputRegressionTest.java @@ -25,44 +25,33 @@ /** * Golden-file regression tests across all supported languages. * - *

To regenerate expected files after intentional analyzer changes, run with - * {@code -DupdateGoldenFiles=true}. + *

To regenerate expected files after intentional analyzer changes, run with {@code + * -DupdateGoldenFiles=true}. */ @QuarkusTest public class ParserTestInputRegressionTest { - @Inject - AntlrParserService javaParserService; + @Inject AntlrParserService javaParserService; - @Inject - AntlrTypeScriptParserService typeScriptParserService; + @Inject AntlrTypeScriptParserService typeScriptParserService; - @Inject - AntlrPythonParserService pythonParserService; + @Inject AntlrPythonParserService pythonParserService; - @Inject - AntlrGoParserService goParserService; + @Inject AntlrGoParserService goParserService; - @Inject - AntlrCSharpParserService csharpParserService; + @Inject AntlrCSharpParserService csharpParserService; - @Inject - AntlrRustParserService rustParserService; + @Inject AntlrRustParserService rustParserService; - @Inject - AntlrKotlinParserService kotlinParserService; + @Inject AntlrKotlinParserService kotlinParserService; - @Inject - AntlrPhpParserService phpParserService; + @Inject AntlrPhpParserService phpParserService; - @Inject - AntlrSwiftParserService swiftParserService; + @Inject AntlrSwiftParserService swiftParserService; - @Inject - AntlrCParserService cParserService; + @Inject AntlrCParserService cParserService; - @Inject - AntlrCppParserService cppParserService; + @Inject AntlrCppParserService cppParserService; static Stream fixtures() { return Stream.of( @@ -79,8 +68,7 @@ static Stream fixtures() { Arguments.of("php", "ParserTestInput.php"), Arguments.of("swift", "ParserTestInput.swift"), Arguments.of("c", "ParserTestInput.c"), - Arguments.of("cpp", "ParserTestInput.cpp") - ); + Arguments.of("cpp", "ParserTestInput.cpp")); } @ParameterizedTest(name = "{0}/{1}") @@ -90,15 +78,16 @@ void parserTestInputMatchesExpectedJson(final String languageDir, final String f final String source = GoldenFileRegressionSupport.readFixture(languageDir, fileName); final AbstractFileDataHandler handler = parse(languageDir, fileName, source); - Assertions.assertNotNull(handler, "Parser should return a handler for " + languageDir + "/" + fileName); + Assertions.assertNotNull( + handler, "Parser should return a handler for " + languageDir + "/" + fileName); final FileData actual = handler.getProtoBufObject(); GoldenFileRegressionSupport.assertMatchesGolden( ParserTestInputRegressionTest.class, languageDir, fileName, actual); } - private AbstractFileDataHandler parse(final String languageDir, final String fileName, - final String source) { + private AbstractFileDataHandler parse( + final String languageDir, final String fileName, final String source) { final String hash = GoldenFileRegressionSupport.FILE_HASH; return switch (languageDir) { case "java" -> javaParserService.parseFileContent(source, fileName, hash); diff --git a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceCommitRangeTest.java b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceCommitRangeTest.java index b9b773a..94be71e 100644 --- a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceCommitRangeTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceCommitRangeTest.java @@ -16,8 +16,7 @@ @QuarkusTest class AnalysisServiceCommitRangeTest { - @Inject - AnalysisService analysisService; + @Inject AnalysisService analysisService; @Test void excludesOldestCommitWhenShallowCloneHasOneExtraCommit() { @@ -47,7 +46,8 @@ void analyzesNewestCommitsWhenFullCloneExceedsLimit() { } @Test - void excludesShallowBoundaryWhenParentIsMissingLocally(@TempDir final File tempDir) throws Exception { + void excludesShallowBoundaryWhenParentIsMissingLocally(@TempDir final File tempDir) + throws Exception { final File sourceDir = new File(tempDir, "source"); final File shallowDir = new File(tempDir, "shallow"); @@ -56,11 +56,12 @@ void excludesShallowBoundaryWhenParentIsMissingLocally(@TempDir final File tempD writeAndCommit(sourceGit, "v2"); } - try (Git ignored = Git.cloneRepository() - .setURI(sourceDir.toURI().toString()) - .setDirectory(shallowDir) - .setDepth(1) - .call()) { + try (Git ignored = + Git.cloneRepository() + .setURI(sourceDir.toURI().toString()) + .setDirectory(shallowDir) + .setDepth(1) + .call()) { try (Git shallowGit = Git.open(shallowDir); Repository shallowRepository = shallowGit.getRepository()) { final RevCommit shallowTip = shallowGit.log().setMaxCount(1).call().iterator().next(); @@ -70,8 +71,8 @@ void excludesShallowBoundaryWhenParentIsMissingLocally(@TempDir final File tempD Assertions.assertTrue( analysisService.shouldExcludeAsShallowCloneBoundary(shallowRepository, shallowTip)); - final List commitsInRange = List.of( - new CommitWalkEntry(shallowTip.getName(), shallowTip.getCommitTime())); + final List commitsInRange = + List.of(new CommitWalkEntry(shallowTip.getName(), shallowTip.getCommitTime())); final AnalysisService.CommitRangeSelection adjustedSelection = analysisService.adjustSkipForShallowCloneBoundary( @@ -87,9 +88,7 @@ void excludesShallowBoundaryWhenParentIsMissingLocally(@TempDir final File tempD } private static AnalysisConfig configWithLimit(final int limit) { - return new AnalysisConfig.Builder() - .commitAnalysisLimit(Optional.of(limit)) - .build(); + return new AnalysisConfig.Builder().commitAnalysisLimit(Optional.of(limit)).build(); } private static void writeAndCommit(final Git git, final String content) throws Exception { diff --git a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceDiffBaseTest.java b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceDiffBaseTest.java index 833bfb1..508aa75 100644 --- a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceDiffBaseTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceDiffBaseTest.java @@ -23,8 +23,7 @@ @QuarkusTest class AnalysisServiceDiffBaseTest { - @Inject - AnalysisService analysisService; + @Inject AnalysisService analysisService; @Test void usesNullDiffBaseForFirstLocalCommitWithoutStartCommit() throws IOException { @@ -80,14 +79,14 @@ void resolvesUnchangedFilesForBootstrapCommit() { final ObjectId addedHash = ObjectId.fromString("abcdef0123456789abcdef0123456789abcdef01"); final ObjectId modifiedHash = ObjectId.fromString("fedcba9876543210fedcba9876543210fedcba98"); - final List allFiles = List.of( - new FileDescriptor(unchangedHash, "Unchanged.java", "src/Unchanged.java"), - new FileDescriptor(addedHash, "Added.java", "src/Added.java"), - new FileDescriptor(modifiedHash, "Modified.java", "src/Modified.java")); - final var reportTriple = new Triple, List, List>( - List.of(allFiles.get(2)), - List.of(), - List.of(allFiles.get(1))); + final List allFiles = + List.of( + new FileDescriptor(unchangedHash, "Unchanged.java", "src/Unchanged.java"), + new FileDescriptor(addedHash, "Added.java", "src/Added.java"), + new FileDescriptor(modifiedHash, "Modified.java", "src/Modified.java")); + final var reportTriple = + new Triple, List, List>( + List.of(allFiles.get(2)), List.of(), List.of(allFiles.get(1))); final List unchangedFiles = analysisService.resolveUnchangedFilesForBootstrapCommit(allFiles, reportTriple); diff --git a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLandscapeParentTest.java b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLandscapeParentTest.java index 723c7e8..0442bfe 100644 --- a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLandscapeParentTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLandscapeParentTest.java @@ -31,8 +31,7 @@ void usesGitParentsForLandscapeTopology() { Assertions.assertEquals( List.of( - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "cccccccccccccccccccccccccccccccccccccccc"), + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccc"), parentIds); } @@ -94,15 +93,13 @@ void linksToLastAnalyzedCommitWhenIrrelevantCommitsWereSkipped() { true, Set.of("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); - Assertions.assertEquals( - List.of("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), parentIds); + Assertions.assertEquals(List.of("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), parentIds); } @Test void hasRelevantChangesWhenAnyFileListIsNonEmpty() { final ObjectId hash = ObjectId.fromString("0123456789abcdef0123456789abcdef01234567"); - final FileDescriptor file = - new FileDescriptor(hash, "File.java", "src/File.java"); + final FileDescriptor file = new FileDescriptor(hash, "File.java", "src/File.java"); Assertions.assertTrue( analysisService.hasRelevantFilteredFileChanges(List.of(file), List.of(), List.of())); diff --git a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLocLimitTest.java b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLocLimitTest.java index 52b6709..dc1c193 100644 --- a/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLocLimitTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/AnalysisServiceLocLimitTest.java @@ -9,21 +9,17 @@ @QuarkusTest class AnalysisServiceLocLimitTest { - @Inject - AnalysisService analysisService; + @Inject AnalysisService analysisService; @Test void skipsFullAnalysisWhenLocExceedsLimitForSourceFiles() { - final AnalysisConfig config = new AnalysisConfig.Builder() - .maxLocForFullAnalysis(Optional.of(100)) - .build(); + final AnalysisConfig config = + new AnalysisConfig.Builder().maxLocForFullAnalysis(Optional.of(100)).build(); - Assertions.assertTrue( - analysisService.shouldUseMinimalSourceAnalysis(config, "Main.java", 101)); + Assertions.assertTrue(analysisService.shouldUseMinimalSourceAnalysis(config, "Main.java", 101)); Assertions.assertFalse( analysisService.shouldUseMinimalSourceAnalysis(config, "Main.java", 100)); - Assertions.assertFalse( - analysisService.shouldUseMinimalSourceAnalysis(config, "Main.java", 50)); + Assertions.assertFalse(analysisService.shouldUseMinimalSourceAnalysis(config, "Main.java", 50)); } @Test @@ -36,9 +32,8 @@ void analyzesAllFilesWhenLimitIsUnset() { @Test void doesNotApplyLimitToNonSourceFiles() { - final AnalysisConfig config = new AnalysisConfig.Builder() - .maxLocForFullAnalysis(Optional.of(100)) - .build(); + final AnalysisConfig config = + new AnalysisConfig.Builder().maxLocForFullAnalysis(Optional.of(100)).build(); Assertions.assertFalse( analysisService.shouldUseMinimalSourceAnalysis(config, "README.md", 500)); diff --git a/src/test/java/net/explorviz/code/analysis/service/AnalysisStatusServiceTest.java b/src/test/java/net/explorviz/code/analysis/service/AnalysisStatusServiceTest.java index 18ca932..a20395e 100644 --- a/src/test/java/net/explorviz/code/analysis/service/AnalysisStatusServiceTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/AnalysisStatusServiceTest.java @@ -8,8 +8,7 @@ @QuarkusTest class AnalysisStatusServiceTest { - @Inject - AnalysisStatusService analysisStatusService; + @Inject AnalysisStatusService analysisStatusService; @Test void requestCancellationMarksJobAsCancelled() { diff --git a/src/test/java/net/explorviz/code/analysis/service/CommitSamplerTest.java b/src/test/java/net/explorviz/code/analysis/service/CommitSamplerTest.java index c58907c..2827d9f 100644 --- a/src/test/java/net/explorviz/code/analysis/service/CommitSamplerTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/CommitSamplerTest.java @@ -93,10 +93,11 @@ void selectsFirstCommitPerYear() { @Test void intervalSamplingTakesPrecedenceOverPeriod() { - final AnalysisConfig config = new AnalysisConfig.Builder() - .commitSamplingInterval(Optional.of(4)) - .commitSamplingPeriod(Optional.of(CommitSamplingPeriod.DAY)) - .build(); + final AnalysisConfig config = + new AnalysisConfig.Builder() + .commitSamplingInterval(Optional.of(4)) + .commitSamplingPeriod(Optional.of(CommitSamplingPeriod.DAY)) + .build(); final Set indices = CommitSampler.selectFullyAnalyzedIndicesFromCommitTimes( @@ -106,15 +107,11 @@ void intervalSamplingTakesPrecedenceOverPeriod() { } private static AnalysisConfig configWithInterval(final int interval) { - return new AnalysisConfig.Builder() - .commitSamplingInterval(Optional.of(interval)) - .build(); + return new AnalysisConfig.Builder().commitSamplingInterval(Optional.of(interval)).build(); } private static AnalysisConfig configWithPeriod(final CommitSamplingPeriod period) { - return new AnalysisConfig.Builder() - .commitSamplingPeriod(Optional.of(period)) - .build(); + return new AnalysisConfig.Builder().commitSamplingPeriod(Optional.of(period)).build(); } private static int epochSeconds(final String isoInstant) { diff --git a/src/test/java/net/explorviz/code/analysis/service/GithubFetcherParseTest.java b/src/test/java/net/explorviz/code/analysis/service/GithubFetcherParseTest.java index 5624e3f..eaa4e65 100644 --- a/src/test/java/net/explorviz/code/analysis/service/GithubFetcherParseTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/GithubFetcherParseTest.java @@ -23,17 +23,15 @@ class GithubFetcherParseTest { private static final String REPO = "kieker-monitoring/kieker"; private static final String ISSUE_BASIC = "issue-basic.json"; - private final GithubFetcherService service = - new GithubFetcherService(); + private final GithubFetcherService service = new GithubFetcherService(); // --- fixture helpers ----------------------------------------------------- /** Loads {@code search.nodes} from a fixture on the test classpath. */ private static JsonArray loadNodes(final String fixture) { final String resource = "/github/" + fixture; - try (InputStream in = - GithubFetcherParseTest.class.getResourceAsStream(resource); - JsonReader reader = Json.createReader(in)) { + try (InputStream in = GithubFetcherParseTest.class.getResourceAsStream(resource); + JsonReader reader = Json.createReader(in)) { return reader.readObject().getJsonObject("search").getJsonArray("nodes"); } catch (IOException e) { throw new RuntimeException(e); @@ -49,8 +47,7 @@ private static JsonObject nodeByNumber(final JsonArray nodes, final int number) throw new IllegalArgumentException("No node with number " + number); } - private static List annotationTypes( - final List events) { + private static List annotationTypes(final List events) { return events.stream() .map(TrackableResourceEvent::getAnnotationType) .collect(Collectors.toList()); @@ -62,8 +59,7 @@ private static List annotationTypes( void parseBaseResource_closedIssue_extractsCoreFields() { final JsonObject issue = nodeByNumber(loadNodes(ISSUE_BASIC), 3008); - final TrackableResourceEvent base = - service.parseBaseResource(issue, TOKEN, REPO).build(); + final TrackableResourceEvent base = service.parseBaseResource(issue, TOKEN, REPO).build(); assertEquals(TrackableResourceType.ISSUE, base.getResourceType()); assertEquals("3008", base.getResourceId()); @@ -81,7 +77,7 @@ void mapToEvents_mergedPullRequest_producesExpectedEventSet() { final List events = service.mapToEvents(pr, TOKEN, REPO); final List types = annotationTypes(events); - //System.out.println(types); + // System.out.println(types); // Use `types` (List) to check what mapToEvents produced. assertNotNull(types); diff --git a/src/test/java/net/explorviz/code/analysis/service/GithubFetcherServiceTest.java b/src/test/java/net/explorviz/code/analysis/service/GithubFetcherServiceTest.java index 2e407b6..e5286dd 100644 --- a/src/test/java/net/explorviz/code/analysis/service/GithubFetcherServiceTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/GithubFetcherServiceTest.java @@ -13,8 +13,7 @@ @QuarkusTest public class GithubFetcherServiceTest { - @Inject - GithubFetcherService githubSocialFetcherService; + @Inject GithubFetcherService githubSocialFetcherService; @Disabled @Test @@ -37,6 +36,5 @@ void testFetchSocialDataInRange() { repositoryName, startDate, endDate, mockExporter, landscapeToken, "dummy-token"); System.out.println("Finished testFetchSocialDataInRange"); System.out.println("#######################################################"); - } } diff --git a/src/test/java/net/explorviz/code/analysis/service/LocalRepositoryServiceTest.java b/src/test/java/net/explorviz/code/analysis/service/LocalRepositoryServiceTest.java index d8b9752..ef0affb 100644 --- a/src/test/java/net/explorviz/code/analysis/service/LocalRepositoryServiceTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/LocalRepositoryServiceTest.java @@ -16,8 +16,7 @@ class LocalRepositoryServiceTest { private static final String CLONE_ROOT_NAME = "cloned-repositories"; - @TempDir - Path tempDir; + @TempDir Path tempDir; private String originalUserDir; diff --git a/src/test/java/net/explorviz/code/analysis/service/TerminalProgressReporterTest.java b/src/test/java/net/explorviz/code/analysis/service/TerminalProgressReporterTest.java index f039783..164aa0d 100644 --- a/src/test/java/net/explorviz/code/analysis/service/TerminalProgressReporterTest.java +++ b/src/test/java/net/explorviz/code/analysis/service/TerminalProgressReporterTest.java @@ -8,21 +8,24 @@ class TerminalProgressReporterTest { @Test void formatCommitLineShowsCurrentCommitWhileRunning() { - final AnalysisProgressState state = new AnalysisProgressState("running", 220, 1, 100, 5, "src/Foo.java"); + final AnalysisProgressState state = + new AnalysisProgressState("running", 220, 1, 100, 5, "src/Foo.java"); assertEquals("Commit 2/220", TerminalProgressReporter.formatCommitLine(state)); } @Test void formatCommitLineShowsTotalWhenFinished() { - final AnalysisProgressState state = new AnalysisProgressState("finished", 220, 220, 100, 100, null); + final AnalysisProgressState state = + new AnalysisProgressState("finished", 220, 220, 100, 100, null); assertEquals("Commit 220/220", TerminalProgressReporter.formatCommitLine(state)); } @Test void formatFileLineShowsProgressBarAndPercentage() { - final AnalysisProgressState state = new AnalysisProgressState("running", 220, 1, 100, 5, "src/Foo.java"); + final AnalysisProgressState state = + new AnalysisProgressState("running", 220, 1, 100, 5, "src/Foo.java"); assertEquals( "Files [##----------------------------] 5/100 (5%)", @@ -77,6 +80,8 @@ void nextFileMilestonePercentReturnsFirstUnloggedMilestone() { void formatAnalysisFinishedMessage() { final AnalysisProgressState state = new AnalysisProgressState("finished", 220, 220, 0, 0, null); - assertEquals("Analysis finished: 220/220 commits", TerminalProgressReporter.formatAnalysisFinishedMessage(state)); + assertEquals( + "Analysis finished: 220/220 commits", + TerminalProgressReporter.formatAnalysisFinishedMessage(state)); } } diff --git a/src/test/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitorTest.java b/src/test/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitorTest.java index d66d90e..a9f0ff0 100644 --- a/src/test/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitorTest.java +++ b/src/test/java/net/explorviz/code/analysis/visitor/CyclomaticComplexityVisitorTest.java @@ -12,14 +12,12 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Testing the CyclomaticComplexityVisitor. - */ +/** Testing the CyclomaticComplexityVisitor. */ @QuarkusTest -public class CyclomaticComplexityVisitorTest { // NOCS +public class CyclomaticComplexityVisitorTest { @Test() - void cyclomaticComplexityTest() throws FileNotFoundException { // NOCS + void cyclomaticComplexityTest() throws FileNotFoundException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("Nested.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); String path = "src/test/resources/files/Nested.java"; @@ -29,14 +27,22 @@ void cyclomaticComplexityTest() throws FileNotFoundException { // NOCS MetricAppender appender = new MetricAppender(fileDataHandler); cyclomaticComplexityVisitor.visit(compilationUnit, new Pair<>(appender, null)); - Assertions.assertEquals("6.0", fileDataHandler.getClassData("com.easy.life.Nested") // NOCS - .getMethod("com.easy.life.Nested.heavyNested#1") - .getMetricValue("cyclomatic_complexity")); // NOCS - Assertions.assertEquals("2.0", fileDataHandler.getClassData("com.easy.life.Nested") // NOCS - .getMethod("com.easy.life.Nested.heavyNested2#1980e") - .getMetricValue("cyclomatic_complexity")); // NOCS - Assertions.assertEquals("4.0", fileDataHandler.getClassData("com.easy.life.Nested") // NOCS - .getMetricValue("cyclomatic_complexity_weighted")); - + Assertions.assertEquals( + "6.0", + fileDataHandler + .getClassData("com.easy.life.Nested") + .getMethod("com.easy.life.Nested.heavyNested#1") + .getMetricValue("cyclomatic_complexity")); + Assertions.assertEquals( + "2.0", + fileDataHandler + .getClassData("com.easy.life.Nested") + .getMethod("com.easy.life.Nested.heavyNested2#1980e") + .getMetricValue("cyclomatic_complexity")); + Assertions.assertEquals( + "4.0", + fileDataHandler + .getClassData("com.easy.life.Nested") + .getMetricValue("cyclomatic_complexity_weighted")); } } diff --git a/src/test/java/net/explorviz/code/analysis/visitor/FileDataVisitorTest.java b/src/test/java/net/explorviz/code/analysis/visitor/FileDataVisitorTest.java index 6283d35..171425c 100644 --- a/src/test/java/net/explorviz/code/analysis/visitor/FileDataVisitorTest.java +++ b/src/test/java/net/explorviz/code/analysis/visitor/FileDataVisitorTest.java @@ -18,34 +18,34 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Tests for the FileDataVisitor. - */ +/** Tests for the FileDataVisitor. */ @QuarkusTest public class FileDataVisitorTest { @Test() - void fileDataTest1() throws FileNotFoundException { // NOCS + void fileDataTest1() throws FileNotFoundException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("Nested.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); String path = "src/test/resources/files/Nested.java"; final CompilationUnit compilationUnit = StaticJavaParser.parse(new File(path)); visitor.visit(compilationUnit, fileDataHandler); FileData data = fileDataHandler.getProtoBufObject(); - Assertions.assertEquals("com.easy.life", data.getPackageName()); // NOCS + Assertions.assertEquals("com.easy.life", data.getPackageName()); Assertions.assertEquals(0, data.getImportNamesCount()); Assertions.assertEquals(1, data.getClassesCount()); ClassData clazz = findClass(data.getClassesList(), "Nested"); Assertions.assertTrue(containsFunction(clazz.getFunctionsList(), "heavyNested")); - Assertions.assertTrue( - containsFunction(clazz.getFunctionsList(), "heavyNested2")); // NOCS - Assertions.assertEquals("int", - findFunction(clazz.getFunctionsList(), "heavyNested2").getParametersList() - .get(0).getType()); + Assertions.assertTrue(containsFunction(clazz.getFunctionsList(), "heavyNested2")); + Assertions.assertEquals( + "int", + findFunction(clazz.getFunctionsList(), "heavyNested2") + .getParametersList() + .get(0) + .getType()); } @Test() - void fileDataTest2() throws FileNotFoundException { // NOCS + void fileDataTest2() throws FileNotFoundException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("Happy.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); String path = "src/test/resources/files/Happy.java"; @@ -53,17 +53,17 @@ void fileDataTest2() throws FileNotFoundException { // NOCS visitor.visit(compilationUnit, fileDataHandler); FileData data = fileDataHandler.getProtoBufObject(); Assertions.assertEquals("com.easy.life", data.getPackageName()); - Assertions.assertEquals(4, data.getImportNamesCount()); // NOCS + Assertions.assertEquals(4, data.getImportNamesCount()); Assertions.assertEquals(1, data.getClassesCount()); ClassData clazz = findClass(data.getClassesList(), "Happy"); - Assertions.assertTrue( - containsFunction(clazz.getFunctionsList(), "fromArrayToList")); // NOCS - Assertions.assertEquals("java.util.ArrayList", + Assertions.assertTrue(containsFunction(clazz.getFunctionsList(), "fromArrayToList")); + Assertions.assertEquals( + "java.util.ArrayList", findFunction(clazz.getFunctionsList(), "fromArrayToList").getReturnType()); } @Test() - void fileDataEnumTest() throws FileNotFoundException { // NOCS + void fileDataEnumTest() throws FileNotFoundException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("ColorParam.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); String path = "src/test/resources/files/ColorParam.java"; @@ -79,7 +79,7 @@ void fileDataEnumTest() throws FileNotFoundException { // NOCS } @Test() - void fileDataAnnotationTest() throws FileNotFoundException { // NOCS + void fileDataAnnotationTest() throws FileNotFoundException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("SimpleJdbcClinic.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); String path = "src/test/resources/files/SimpleJdbcClinic.java"; @@ -88,11 +88,9 @@ void fileDataAnnotationTest() throws FileNotFoundException { // NOCS final CompilationUnit compilationUnit = StaticJavaParser.parse(new File(path)); visitor.visit(compilationUnit, fileDataHandler); FileData data = fileDataHandler.getProtoBufObject(); - ClassData clazz = findClass(data.getClassesList(), - "SimpleJdbcClinic"); + ClassData clazz = findClass(data.getClassesList(), "SimpleJdbcClinic"); Assertions.assertTrue(containsConstant(clazz.getAnnotationsList(), "Service")); - FunctionData method = findFunction(clazz.getFunctionsList(), - "init"); + FunctionData method = findFunction(clazz.getFunctionsList(), "init"); Assertions.assertTrue(containsConstant(method.getAnnotationsList(), "Autowired")); } diff --git a/src/test/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitorTest.java b/src/test/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitorTest.java index 28b59c2..d03825f 100644 --- a/src/test/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitorTest.java +++ b/src/test/java/net/explorviz/code/analysis/visitor/LackOfCohesionMethodsVisitorTest.java @@ -15,16 +15,14 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Tests for the LackOfCohesionMethodsVisitor calculating the LCOM4 metric. - */ +/** Tests for the LackOfCohesionMethodsVisitor calculating the LCOM4 metric. */ @QuarkusTest public class LackOfCohesionMethodsVisitorTest { private static final String LCOM4 = "LCOM4"; @Test() - void fileDataTest1() throws FileNotFoundException { // NOCS + void fileDataTest1() throws FileNotFoundException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("LCOM4.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); String path = "src/test/resources/files/LCOM4.java"; diff --git a/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java b/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java index 434db6d..55adc39 100644 --- a/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java +++ b/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java @@ -12,13 +12,11 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Tests for the NestedBlockDepthVisitor. - */ +/** Tests for the NestedBlockDepthVisitor. */ @QuarkusTest public class NestedBlockDepthVisitorTest { @Test() - void nestedBlockDepth() throws FileNotFoundException { // NOCS + void nestedBlockDepth() throws FileNotFoundException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("Nested.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); String path = "src/test/resources/files/Nested.java"; @@ -28,12 +26,17 @@ void nestedBlockDepth() throws FileNotFoundException { // NOCS MetricAppender appender = new MetricAppender(fileDataHandler); nestedBlockDepth.visit(compilationUnit, new Pair<>(appender, null)); - Assertions.assertEquals("6.0", fileDataHandler.getClassData("com.easy.life.Nested") // NOCS - .getMethod("com.easy.life.Nested.heavyNested#1") - .getMetricValue("nestedBlockDepth")); // NOCS - Assertions.assertEquals("4.0", fileDataHandler.getClassData("com.easy.life.Nested") // NOCS - .getMethod("com.easy.life.Nested.heavyNested2#1980e") - .getMetricValue("nestedBlockDepth")); // NOCS - + Assertions.assertEquals( + "6.0", + fileDataHandler + .getClassData("com.easy.life.Nested") + .getMethod("com.easy.life.Nested.heavyNested#1") + .getMetricValue("nestedBlockDepth")); + Assertions.assertEquals( + "4.0", + fileDataHandler + .getClassData("com.easy.life.Nested") + .getMethod("com.easy.life.Nested.heavyNested2#1980e") + .getMetricValue("nestedBlockDepth")); } } From 27cffdbbbdeab3f7b0b6878070da17825fe1af2e Mon Sep 17 00:00:00 2001 From: Malte Hansen Date: Tue, 29 Sep 2026 11:09:06 +0200 Subject: [PATCH 2/6] Add reports to GitLab and GitHub CI --- .github/workflows/ci.yaml | 77 +++++++++++++++--- .gitlab-ci.yml | 32 ++++++-- code-analysis/ci/gitlab-code-quality.py | 89 +++++++++++++++++++++ code-analysis/ci/report-summary.py | 101 ++++++++++++++++++++++++ 4 files changed, 280 insertions(+), 19 deletions(-) create mode 100755 code-analysis/ci/gitlab-code-quality.py create mode 100755 code-analysis/ci/report-summary.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c0f0bd6..909b2bd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: @@ -23,7 +23,9 @@ jobs: - name: Cache Gradle Packages uses: actions/cache@v4 with: - path: ~/.gradle/caches + path: | + ~/.gradle/caches + ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Build Project run: ./gradlew assemble @@ -36,7 +38,7 @@ jobs: needs: build steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: @@ -45,7 +47,9 @@ jobs: - name: Cache Gradle Packages uses: actions/cache@v4 with: - path: ~/.gradle/caches + path: | + ~/.gradle/caches + ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Run Spotless run: ./gradlew spotlessCheck @@ -58,7 +62,7 @@ jobs: needs: build steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: @@ -67,10 +71,34 @@ jobs: - name: Cache Gradle Packages uses: actions/cache@v4 with: - path: ~/.gradle/caches + path: | + ~/.gradle/caches + ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Run Checkstyle - run: ./gradlew checkstyleMain + id: checkstyle + run: | + set +e + ./gradlew checkstyleMain --continue + python3 code-analysis/ci/gitlab-code-quality.py build/reports/checkstyle/main.xml build/reports/ci/checkstyle-code-quality.json + echo "exit_code=$?" >> "$GITHUB_OUTPUT" + env: + GRADLE_OPTS: "-Dorg.gradle.daemon=false" + - name: Write Checkstyle Summary + if: always() + run: | + python3 code-analysis/ci/report-summary.py build/reports/checkstyle/main.xml >> "$GITHUB_STEP_SUMMARY" + - name: Publish Checkstyle Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: checkstyle-report + path: | + build/reports/checkstyle/ + build/reports/ci/checkstyle-code-quality.json + - name: Fail on Checkstyle Violations + if: steps.checkstyle.outputs.exit_code != '0' + run: exit 1 test: name: Unit Tests @@ -78,7 +106,7 @@ jobs: needs: build steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: @@ -87,10 +115,37 @@ jobs: - name: Cache Gradle Packages uses: actions/cache@v4 with: - path: ~/.gradle/caches + path: | + ~/.gradle/caches + ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Run Unit Tests - run: ./gradlew test + id: unit_tests + run: | + set +e + ./gradlew test jacocoTestReport --continue + echo "exit_code=$?" >> "$GITHUB_OUTPUT" + env: + GRADLE_OPTS: "-Dorg.gradle.daemon=false" + - name: Publish Unit Test Results + if: always() + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: build/test-results/test/**/*.xml + check_name: Unit Tests + comment_mode: off + - name: Publish Test and Coverage Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: unit-test-report + path: | + build/reports/tests/test/ + build/reports/jacoco/test/html/ + build/reports/jacoco/test/jacocoTestReport.xml + - name: Fail on Test Failures + if: steps.unit_tests.outputs.exit_code != '0' + run: exit 1 docker-image-jvm: name: Docker image JVM @@ -99,7 +154,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build and Push Image uses: explorviz/deployment/.github/actions/build-and-deploy-quarkus-jvm@main with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6debb9b..5d16e99 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,30 +29,46 @@ build: stage: build script: ./gradlew assemble artifacts: + expire_in: 1 week paths: - - build + - build/libs/ spotless: stage: code-analysis script: ./gradlew spotlessCheck - artifacts: - paths: - - build checkstyle: stage: code-analysis - script: ./gradlew checkstyleMain + script: + - | + set +e + ./gradlew checkstyleMain --continue + CHECKSTYLE_EXIT=$? + python3 code-analysis/ci/gitlab-code-quality.py build/reports/checkstyle/main.xml build/reports/ci/checkstyle-code-quality.json + python3 code-analysis/ci/report-summary.py build/reports/checkstyle/main.xml + exit $CHECKSTYLE_EXIT artifacts: + when: always + expire_in: 1 week + reports: + codequality: build/reports/ci/checkstyle-code-quality.json paths: - - build + - build/reports/checkstyle/ + - build/reports/ci/checkstyle-code-quality.json unit-test: stage: test script: - - ./gradlew test + - ./gradlew test jacocoTestReport --continue artifacts: + when: always + expire_in: 1 week + reports: + junit: build/test-results/test/TEST-*.xml paths: - - build + - build/reports/tests/test/ + - build/reports/jacoco/test/html/ + - build/reports/jacoco/test/jacocoTestReport.xml .push-dockerhub-base-job: stage: publish diff --git a/code-analysis/ci/gitlab-code-quality.py b/code-analysis/ci/gitlab-code-quality.py new file mode 100755 index 0000000..8a4d72b --- /dev/null +++ b/code-analysis/ci/gitlab-code-quality.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Convert Checkstyle XML reports into GitLab Code Quality JSON.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import sys +import xml.etree.ElementTree as ET +from pathlib import Path + + +def _relativize(path: str, root: Path) -> str: + try: + file_path = Path(path) + if file_path.is_absolute(): + return file_path.relative_to(root).as_posix() + except ValueError: + pass + return path.replace("\\", "/") + + +def _fingerprint(*parts: str) -> str: + payload = "|".join(parts).encode("utf-8") + return hashlib.sha256(payload).hexdigest() + + +def _checkstyle_severity(raw: str | None) -> str: + match (raw or "").lower(): + case "error": + return "major" + case "warning": + return "minor" + case _: + return "info" + + +def parse_checkstyle(xml_path: Path, root: Path) -> list[dict]: + tree = ET.parse(xml_path) + issues: list[dict] = [] + for file_node in tree.getroot().findall("file"): + path = _relativize(file_node.get("name", ""), root) + for error in file_node.findall("error"): + line = int(error.get("line", "1")) + message = error.get("message", "") + check_name = error.get("source", "checkstyle") + severity = _checkstyle_severity(error.get("severity")) + issues.append( + { + "description": f"[Checkstyle] {message}", + "check_name": check_name, + "fingerprint": _fingerprint("checkstyle", check_name, path, str(line), message), + "severity": severity, + "location": {"path": path, "lines": {"begin": line}}, + } + ) + return issues + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("input", type=Path, help="Checkstyle XML report path.") + parser.add_argument("output", type=Path, help="GitLab Code Quality JSON output path.") + parser.add_argument( + "--root", + type=Path, + default=Path.cwd(), + help="Repository root used to shorten absolute file paths.", + ) + args = parser.parse_args() + + if not args.input.is_file(): + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text("[]\n", encoding="utf-8") + print(f"No input report at {args.input}; wrote empty Code Quality JSON.", file=sys.stderr) + return 0 + + root = args.root.resolve() + issues = parse_checkstyle(args.input, root) + + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(issues, indent=2) + "\n", encoding="utf-8") + print(f"Wrote {len(issues)} issue(s) to {args.output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/code-analysis/ci/report-summary.py b/code-analysis/ci/report-summary.py new file mode 100755 index 0000000..29ffb4d --- /dev/null +++ b/code-analysis/ci/report-summary.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Render markdown summaries for Checkstyle CI job logs (GitHub Step Summary / GitLab).""" + +from __future__ import annotations + +import argparse +import sys +import xml.etree.ElementTree as ET +from pathlib import Path + + +def _relativize(path: str, root: Path) -> str: + try: + file_path = Path(path) + if file_path.is_absolute(): + return file_path.relative_to(root).as_posix() + except ValueError: + pass + return path.replace("\\", "/") + + +def summarize_checkstyle(xml_path: Path, root: Path, limit: int) -> str: + if not xml_path.is_file(): + return f"## Checkstyle\n\nNo report found at `{xml_path.as_posix()}`.\n" + + tree = ET.parse(xml_path) + issues: list[tuple[str, str, str, str]] = [] + for file_node in tree.getroot().findall("file"): + file_name = _relativize(file_node.get("name", ""), root) + for error in file_node.findall("error"): + line = error.get("line", "?") + severity = error.get("severity", "unknown") + message = error.get("message", "") + rule = error.get("source", "") + issues.append((severity, file_name, line, f"{rule}: {message}" if rule else message)) + + lines = [ + "## Checkstyle", + "", + f"**Total findings:** {len(issues)}", + "", + ] + if not issues: + lines.append("_No violations reported._") + lines.append("") + return "\n".join(lines) + + lines.extend( + [ + "| Severity | Location | Message |", + "| --- | --- | --- |", + ] + ) + for severity, file_name, line, message in issues[:limit]: + location = f"`{file_name}:{line}`" + safe_message = message.replace("|", "\\|") + lines.append(f"| {severity} | {location} | {safe_message} |") + + if len(issues) > limit: + lines.append("") + lines.append(f"_Showing {limit} of {len(issues)} findings._") + + lines.append("") + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("xml", type=Path, help="Path to the Checkstyle XML report.") + parser.add_argument( + "--root", + type=Path, + default=Path.cwd(), + help="Repository root used to shorten absolute file paths.", + ) + parser.add_argument( + "--limit", + type=int, + default=25, + help="Maximum number of findings to include in the table.", + ) + parser.add_argument( + "--output", + type=Path, + help="Optional file to write markdown to (defaults to stdout).", + ) + args = parser.parse_args() + + markdown = summarize_checkstyle(args.xml, args.root.resolve(), args.limit) + + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(markdown, encoding="utf-8") + else: + sys.stdout.write(markdown) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 028e0d22c2665598378ac319dac04f7685bea4cd Mon Sep 17 00:00:00 2001 From: Malte Hansen Date: Tue, 29 Sep 2026 12:05:30 +0200 Subject: [PATCH 3/6] Remove test casese that rely on external input --- .github/workflows/ci.yaml | 4 +- .gitlab-ci.yml | 2 +- .../code/analysis/TestResources.java | 26 +++ .../git/GitRepositoryHandlerTest.java | 166 ++++++------------ .../parser/JsConfigParsePerfTest.java | 97 ---------- .../analysis/parser/TsxJsxNormalizerTest.java | 29 --- .../analysis/parser/Wsp31ParsePerfTest.java | 89 ---------- .../GoldenFileRegressionSupport.java | 6 +- .../service/GithubFetcherServiceTest.java | 40 ----- .../CyclomaticComplexityVisitorTest.java | 10 +- .../analysis/visitor/FileDataVisitorTest.java | 26 ++- .../LackOfCohesionMethodsVisitorTest.java | 10 +- .../visitor/NestedBlockDepthVisitorTest.java | 10 +- 13 files changed, 114 insertions(+), 401 deletions(-) create mode 100644 src/test/java/net/explorviz/code/analysis/TestResources.java delete mode 100644 src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java delete mode 100644 src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java delete mode 100644 src/test/java/net/explorviz/code/analysis/parser/Wsp31ParsePerfTest.java delete mode 100644 src/test/java/net/explorviz/code/analysis/service/GithubFetcherServiceTest.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 909b2bd..86f4ebc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ jobs: GRADLE_OPTS: "-Dorg.gradle.daemon=false" spotless: - name: Spotless Format Check + name: Spotless runs-on: ubuntu-latest needs: build steps: @@ -57,7 +57,7 @@ jobs: GRADLE_OPTS: "-Dorg.gradle.daemon=false" checkstyle: - name: Checkstyle Analysis + name: Checkstyle runs-on: ubuntu-latest needs: build steps: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5d16e99..b008db6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ default: tags: - exec-docker image: ghcr.io/graalvm/graalvm-community:21 - cache: # Cache modules in between jobs + cache: key: $CI_COMMIT_REF_SLUG paths: - .gradle diff --git a/src/test/java/net/explorviz/code/analysis/TestResources.java b/src/test/java/net/explorviz/code/analysis/TestResources.java new file mode 100644 index 0000000..8a3cee8 --- /dev/null +++ b/src/test/java/net/explorviz/code/analysis/TestResources.java @@ -0,0 +1,26 @@ +package net.explorviz.code.analysis; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Assertions; + +/** Loads version-controlled files from {@code src/test/resources} via the test classpath. */ +public final class TestResources { + + private TestResources() {} + + public static String readUtf8(final String classpathResource) throws IOException { + try (InputStream in = TestResources.class.getResourceAsStream(classpathResource)) { + Assertions.assertNotNull(in, "Missing classpath resource: " + classpathResource); + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + } + + public static CompilationUnit parseJavaFixture(final String classpathResource) + throws IOException { + return StaticJavaParser.parse(readUtf8(classpathResource)); + } +} diff --git a/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java b/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java index eb076c2..a03cfb4 100644 --- a/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java +++ b/src/test/java/net/explorviz/code/analysis/git/GitRepositoryHandlerTest.java @@ -13,17 +13,16 @@ import java.util.stream.Stream; import net.explorviz.code.analysis.types.RemoteRepositoryObject; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -98,46 +97,40 @@ void testFileInsteadDirectory() throws IOException { } @Test - void openRepository() throws GitAPIException, IOException { - // downloading the repository first + void openRepository() throws Exception { + final File repoDir = initLocalRepositoryWithOrigin(httpsUrl, "busydoingnothing"); try (Repository repository = this.gitRepositoryHandler.getGitRepository( - "", new RemoteRepositoryObject(httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { - // call is here to satisfy checkstyle by not having empty try block - System.out.println(GitRepositoryHandler.getRemoteOriginUrl(repository)); - } catch (Exception e) { - Assertions.fail(); + repoDir.getAbsolutePath(), new RemoteRepositoryObject())) { + Assertions.assertEquals(httpsUrl, GitRepositoryHandler.getRemoteOriginUrl(repository)); } - // checking the same folder and reopen the repository try (Repository repository = this.gitRepositoryHandler.getGitRepository( - tempGitLocation.getAbsolutePath() + "/busydoingnothing", - new RemoteRepositoryObject())) { - Assertions.assertEquals(GitRepositoryHandler.getRemoteOriginUrl(repository), httpsUrl); + repoDir.getAbsolutePath(), new RemoteRepositoryObject())) { + Assertions.assertEquals(httpsUrl, GitRepositoryHandler.getRemoteOriginUrl(repository)); + Assertions.assertNotNull(repository.getBranch()); } } @Test() - void testSsh() { + void testSshOriginUrlOnLocalRepository() throws Exception { + final File repoDir = initLocalRepositoryWithOrigin(sshUrl, "ssh-origin"); try (Repository repository = this.gitRepositoryHandler.getGitRepository( - "", new RemoteRepositoryObject(sshUrl, tempGitLocation.getAbsolutePath(), MASTER))) { - // call is here to satisfy checkstyle by not having empty try block - repository.getBranch(); - } catch (Exception e) { - Assertions.fail(); + repoDir.getAbsolutePath(), new RemoteRepositoryObject())) { + Assertions.assertEquals(sshUrl, GitRepositoryHandler.getRemoteOriginUrl(repository)); + Assertions.assertNotNull(repository.getBranch()); } } @Test() - void testHttps() { + void testHttpsOriginUrlOnLocalRepository() throws Exception { + final File repoDir = initLocalRepositoryWithOrigin(httpsUrl, "https-origin"); try (Repository repository = this.gitRepositoryHandler.getGitRepository( - "", new RemoteRepositoryObject(httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { - // call is here to satisfy checkstyle by not having empty try block - repository.getBranch(); - } catch (Exception e) { - Assertions.fail(); + repoDir.getAbsolutePath(), new RemoteRepositoryObject())) { + Assertions.assertEquals(httpsUrl, GitRepositoryHandler.getRemoteOriginUrl(repository)); + Assertions.assertNotNull(repository.getBranch()); } } @@ -177,30 +170,44 @@ void testSshConversion() { } @Test - void testRemoteLookup() throws GitAPIException, IOException { + void testRemoteLookup() throws Exception { + final File repoDir = initLocalRepositoryWithOrigin(httpsUrl, "remote-lookup"); try (Repository repository = this.gitRepositoryHandler.getGitRepository( - "", new RemoteRepositoryObject(httpsUrl, tempGitLocation.getAbsolutePath(), MASTER))) { - Assertions.assertEquals(GitRepositoryHandler.getRemoteOriginUrl(repository), httpsUrl); + repoDir.getAbsolutePath(), new RemoteRepositoryObject())) { + Assertions.assertEquals(httpsUrl, GitRepositoryHandler.getRemoteOriginUrl(repository)); } } @Test() - void testGetStringifiedFileInCommit() throws GitAPIException, IOException { + void testGetStringifiedFileInCommit() throws Exception { + final String expected = + "package testgit.my.test.pckg;\n" + + "\n" + + "public class TestGitClass {\n" + + "\n" + + " private final String testVariable;\n" + + "\n" + + " public TestGitClass(final String testVariable) {\n" + + " this.testVariable = testVariable;\n" + + " }\n" + + "\n" + + "}"; + + final File repoDir = new File(tempGitLocation, "content-test"); + final ObjectId commitId; + try (Git git = Git.init().setDirectory(repoDir).call()) { + final File sourceFile = new File(repoDir, "TestGitClass.java"); + Files.writeString(sourceFile.toPath(), expected); + git.add().addFilepattern("TestGitClass.java").call(); + commitId = git.commit().setMessage("add test class").call().getId(); + } try (final Repository repository = this.gitRepositoryHandler.getGitRepository( - "", - new RemoteRepositoryObject( - "https://github.com/Alexander-Krause-Glau/Test-JGit-Code.git", - tempGitLocation.getAbsolutePath(), - MASTER))) { - + repoDir.getAbsolutePath(), new RemoteRepositoryObject())) { try (RevWalk walk = new RevWalk(repository)) { - final ObjectId id = repository.resolve("8ee1f25"); - - final RevCommit commit = walk.parseCommit(id); - + final RevCommit commit = walk.parseCommit(commitId); final RevTree tree = commit.getTree(); try (TreeWalk treeWalk = new TreeWalk(repository)) { @@ -209,23 +216,9 @@ void testGetStringifiedFileInCommit() throws GitAPIException, IOException { while (treeWalk.next()) { final String actual = GitRepositoryHandler.getContent(treeWalk.getObjectId(0), repository); - final String expected = - "package testgit.my.test.pckg;\n" - + "\n" - + "public class TestGitClass {\n" - + "\n" - + " private final String testVariable;\n" - + "\n" - + " public TestGitClass(final String testVariable) {\n" - + " this.testVariable = testVariable;\n" - + " }\n" - + "\n" - + "}"; - Assertions.assertEquals( expected.replace(" ", "").replace("\n", "").replace("\r", ""), actual.replace(" ", "").replace("\n", "").replace("\r", "")); - walk.dispose(); } } @@ -233,63 +226,16 @@ void testGetStringifiedFileInCommit() throws GitAPIException, IOException { } } - @Test - void configureBranchRevWalk_firstParentOnly_excludesMergedBranchCommits() throws IOException { - final Path traceServiceRepo = - Path.of(System.getProperty("user.home"), "Documents", "ExplorViz", "trace-service"); - Assumptions.assumeTrue( - Files.isDirectory(traceServiceRepo.resolve(".git")), - "Local trace-service clone required for this test"); - - try (Git git = Git.open(traceServiceRepo.toFile()); - Repository repository = git.getRepository(); - RevWalk allReachableWalk = new RevWalk(repository); - RevWalk firstParentWalk = new RevWalk(repository)) { - final String mainRef = "refs/heads/main"; - allReachableWalk.markStart(allReachableWalk.parseCommit(repository.resolve(mainRef))); - final long allReachableCount = countCommits(allReachableWalk); - - gitRepositoryHandler.configureBranchRevWalk(firstParentWalk, repository, mainRef, true); - final long firstParentCount = countCommits(firstParentWalk); - - Assertions.assertTrue( - firstParentCount < allReachableCount, - "First-parent walk must exclude merged feature-branch commits"); - } - } - - @Test - void configureBranchRevWalk_includesAllReachableCommits_whenFirstParentDisabled() - throws IOException { - final Path traceServiceRepo = - Path.of(System.getProperty("user.home"), "Documents", "ExplorViz", "trace-service"); - Assumptions.assumeTrue( - Files.isDirectory(traceServiceRepo.resolve(".git")), - "Local trace-service clone required for this test"); - - try (Git git = Git.open(traceServiceRepo.toFile()); - Repository repository = git.getRepository(); - RevWalk allReachableWalk = new RevWalk(repository); - RevWalk branchWalk = new RevWalk(repository)) { - final String mainRef = "refs/heads/main"; - allReachableWalk.markStart(allReachableWalk.parseCommit(repository.resolve(mainRef))); - final long allReachableCount = countCommits(allReachableWalk); - - gitRepositoryHandler.configureBranchRevWalk(branchWalk, repository, mainRef, false); - final long branchWalkCount = countCommits(branchWalk); - - Assertions.assertEquals(allReachableCount, branchWalkCount); - Assertions.assertTrue( - branchWalkCount > 72, - "Branch walk must include merged commits when first-parent filtering is disabled"); - } - } - - private static long countCommits(final RevWalk revWalk) { - long count = 0; - for (RevCommit ignored : revWalk) { - count++; + private File initLocalRepositoryWithOrigin(final String remoteUrl, final String directoryName) + throws Exception { + final File repoDir = new File(tempGitLocation, directoryName); + Files.createDirectories(repoDir.toPath()); + try (Git git = Git.init().setDirectory(repoDir).call()) { + final StoredConfig config = git.getRepository().getConfig(); + config.setString("remote", "origin", "url", remoteUrl); + config.save(); + git.commit().setMessage("init").setAllowEmpty(true).call(); } - return count; + return repoDir; } } diff --git a/src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java b/src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java deleted file mode 100644 index e34d472..0000000 --- a/src/test/java/net/explorviz/code/analysis/parser/JsConfigParsePerfTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.explorviz.code.analysis.parser; - -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; - -@QuarkusTest -class JsConfigParsePerfTest { - - private static final String WEBPACK_DEVALL_JS = - """ - // Specific configuration for developers - // Runs a HTTPS server in port 8080, accesible - // from all hosts that can connect to it. - // Convenient for connecting external devices (eg, a VR device) - // USE AT YOUR OWN RISK - - const path = require("path"); - const { merge } = require("webpack-merge"); - const common = require("./webpack.common"); - - module.exports = merge(common, { - mode: "development", - devtool: "inline-source-map", - devServer: { - static: { - directory: path.join(__dirname, '.'), - }, - host: "0.0.0.0", - server: { - type: 'https', - options: { - cert: './babia_cert.pem', - key: './babia_key.pem' - } - }, - devMiddleware: { - writeToDisk: true, - } - } - }); - """; - - private static final Path BABIA_EASYRTC = - Path.of("cloned-repositories/aframe-babia-components/examples/multiuser/easyrtc.js"); - private static final Path BABIA_BOATS = - Path.of("cloned-repositories/aframe-babia-components/components/visualizers/babia-boats.js"); - - @Inject AntlrTypeScriptParserService parser; - - @Test - void webpackDevallParsesQuickly() { - final long start = System.nanoTime(); - final var handler = parser.parseFileContent(WEBPACK_DEVALL_JS, "webpack.devall.js", "hash"); - final long ms = (System.nanoTime() - start) / 1_000_000L; - System.out.println("webpack.devall.js parse took " + ms + " ms, handler=" + (handler != null)); - Assertions.assertNotNull(handler); - Assertions.assertTrue(ms < 1_000, "Expected parse under 1s, was " + ms + " ms"); - } - - @Test - @EnabledIf("babiaRepoPresent") - void easyrtcTimesOutInsteadOfHanging() throws Exception { - final String content = Files.readString(BABIA_EASYRTC); - final long budget = AntlrParserUtils.DEFAULT_PARSE_TIMEOUT_MS + 3_000L; - final long start = System.nanoTime(); - final var handler = parser.parseFileContent(content, "examples/multiuser/easyrtc.js", "hash"); - final long ms = (System.nanoTime() - start) / 1_000_000L; - System.out.println("easyrtc.js parse took " + ms + " ms, handler=" + (handler != null)); - // Pathological vendored JS: full parse should time out and fall back (null). - Assertions.assertNull(handler); - Assertions.assertTrue( - ms < budget, "Expected timeout under " + budget + "ms, was " + ms + " ms"); - } - - @Test - @EnabledIf("babiaRepoPresent") - void babiaBoatsDoesNotHang() throws Exception { - final String content = Files.readString(BABIA_BOATS); - final long budget = AntlrParserUtils.DEFAULT_PARSE_TIMEOUT_MS + 3_000L; - final long start = System.nanoTime(); - // May succeed or fall back (null) after timeout — must not hang indefinitely. - parser.parseFileContent(content, "components/visualizers/babia-boats.js", "hash"); - final long ms = (System.nanoTime() - start) / 1_000_000L; - System.out.println("babia-boats.js parse took " + ms + " ms"); - Assertions.assertTrue( - ms < budget, "Expected parse/timeout under " + budget + "ms, was " + ms + " ms"); - } - - static boolean babiaRepoPresent() { - return Files.isRegularFile(BABIA_EASYRTC) && Files.isRegularFile(BABIA_BOATS); - } -} diff --git a/src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java b/src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java deleted file mode 100644 index 9dd9e97..0000000 --- a/src/test/java/net/explorviz/code/analysis/parser/TsxJsxNormalizerTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.explorviz.code.analysis.parser; - -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; - -class TsxJsxNormalizerTest { - - private static final Path SUBJECTS_PAGE = - Path.of("cloned-repositories/wsp-3-1/frontend/src/pages/masterdata/SubjectsPage.tsx"); - - @Test - @EnabledIf("wspRepoPresent") - void subjectsPageNormalizesQuickly() throws Exception { - final String source = Files.readString(SUBJECTS_PAGE); - final long start = System.nanoTime(); - final String normalized = TsxJsxNormalizer.replaceJsxWithNull(source); - final long ms = (System.nanoTime() - start) / 1_000_000L; - System.out.println("SubjectsPage normalize took " + ms + " ms, len " + normalized.length()); - Assertions.assertTrue(ms < 5_000, "Expected normalize under 5s, was " + ms + " ms"); - Assertions.assertFalse(normalized.contains("(new MetricAppender(fileDataHandler), null)); diff --git a/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java b/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java index 55adc39..6db12c8 100644 --- a/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java +++ b/src/test/java/net/explorviz/code/analysis/visitor/NestedBlockDepthVisitorTest.java @@ -1,12 +1,11 @@ package net.explorviz.code.analysis.visitor; -import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.utils.Pair; import io.quarkus.test.junit.QuarkusTest; -import java.io.File; -import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Optional; +import net.explorviz.code.analysis.TestResources; import net.explorviz.code.analysis.handler.JavaFileDataHandler; import net.explorviz.code.analysis.handler.MetricAppender; import org.junit.jupiter.api.Assertions; @@ -16,11 +15,10 @@ @QuarkusTest public class NestedBlockDepthVisitorTest { @Test() - void nestedBlockDepth() throws FileNotFoundException { + void nestedBlockDepth() throws IOException { JavaFileDataHandler fileDataHandler = new JavaFileDataHandler("Nested.java"); FileDataVisitor visitor = new FileDataVisitor(Optional.empty(), false); - String path = "src/test/resources/files/Nested.java"; - final CompilationUnit compilationUnit = StaticJavaParser.parse(new File(path)); + final CompilationUnit compilationUnit = TestResources.parseJavaFixture("/files/Nested.java"); visitor.visit(compilationUnit, fileDataHandler); NestedBlockDepthVisitor nestedBlockDepth = new NestedBlockDepthVisitor(); MetricAppender appender = new MetricAppender(fileDataHandler); From 8e659fe63ed944565459cbdf60285441682830d1 Mon Sep 17 00:00:00 2001 From: Malte Hansen Date: Tue, 29 Sep 2026 12:11:59 +0200 Subject: [PATCH 4/6] Update CI pipelines --- .github/workflows/ci.yaml | 31 +++--- .gitlab-ci.yml | 7 +- code-analysis/ci/report-summary.py | 153 +++++++++++++++++++++++++++-- 3 files changed, 171 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 86f4ebc..d48c34b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,14 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: distribution: "graalvm" java-version: "21" - name: Cache Gradle Packages - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | ~/.gradle/caches @@ -38,14 +38,14 @@ jobs: needs: build steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: distribution: "graalvm" java-version: "21" - name: Cache Gradle Packages - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | ~/.gradle/caches @@ -62,14 +62,14 @@ jobs: needs: build steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: distribution: "graalvm" java-version: "21" - name: Cache Gradle Packages - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | ~/.gradle/caches @@ -106,14 +106,14 @@ jobs: needs: build steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v6 with: distribution: "graalvm" java-version: "21" - name: Cache Gradle Packages - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | ~/.gradle/caches @@ -127,6 +127,10 @@ jobs: echo "exit_code=$?" >> "$GITHUB_OUTPUT" env: GRADLE_OPTS: "-Dorg.gradle.daemon=false" + - name: Write Unit Test Summary + if: always() + run: | + python3 code-analysis/ci/report-summary.py junit build/test-results/test >> "$GITHUB_STEP_SUMMARY" - name: Publish Unit Test Results if: always() uses: EnricoMi/publish-unit-test-result-action@v2 @@ -134,6 +138,7 @@ jobs: files: build/test-results/test/**/*.xml check_name: Unit Tests comment_mode: off + job_summary: false - name: Publish Test and Coverage Reports if: always() uses: actions/upload-artifact@v4 @@ -154,7 +159,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Build and Push Image uses: explorviz/deployment/.github/actions/build-and-deploy-quarkus-jvm@main with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b008db6..b5ae6e4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,7 +59,12 @@ checkstyle: unit-test: stage: test script: - - ./gradlew test jacocoTestReport --continue + - | + set +e + ./gradlew test jacocoTestReport --continue + TEST_EXIT=$? + python3 code-analysis/ci/report-summary.py junit build/test-results/test + exit $TEST_EXIT artifacts: when: always expire_in: 1 week diff --git a/code-analysis/ci/report-summary.py b/code-analysis/ci/report-summary.py index 29ffb4d..987b1a6 100755 --- a/code-analysis/ci/report-summary.py +++ b/code-analysis/ci/report-summary.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Render markdown summaries for Checkstyle CI job logs (GitHub Step Summary / GitLab).""" +"""Render markdown summaries for CI job logs (GitHub Step Summary / GitLab).""" from __future__ import annotations @@ -64,29 +64,170 @@ def summarize_checkstyle(xml_path: Path, root: Path, limit: int) -> str: return "\n".join(lines) +def _iter_testsuites(root: ET.Element) -> list[ET.Element]: + if root.tag == "testsuites": + return root.findall("testsuite") + if root.tag == "testsuite": + return [root] + return [] + + +def _format_duration(seconds: float) -> str: + if seconds < 60: + return f"{seconds:.1f} s" + minutes, remainder = divmod(seconds, 60) + return f"{int(minutes)} min {remainder:.1f} s" + + +def _collect_test_failures(testsuite: ET.Element) -> list[tuple[str, str, str, str]]: + failures: list[tuple[str, str, str, str]] = [] + for testcase in testsuite.findall("testcase"): + failure = testcase.find("failure") + error = testcase.find("error") + if failure is None and error is None: + continue + node = failure if failure is not None else error + kind = "Failed" if failure is not None else "Error" + name = testcase.get("name", "?") + classname = testcase.get("classname", "?") + message = (node.get("message") or "").strip() + if not message and node.text: + message = node.text.strip().splitlines()[0] + failures.append((kind, classname, name, message)) + return failures + + +def summarize_junit(results_path: Path, limit: int) -> str: + if results_path.is_file(): + xml_files = [results_path] + elif results_path.is_dir(): + xml_files = sorted(results_path.rglob("TEST-*.xml")) + else: + return f"## Unit Tests\n\nNo test results found at `{results_path.as_posix()}`.\n" + + if not xml_files: + return f"## Unit Tests\n\nNo JUnit XML reports found under `{results_path.as_posix()}`.\n" + + suites = 0 + tests = 0 + failures = 0 + errors = 0 + skipped = 0 + duration = 0.0 + failed_cases: list[tuple[str, str, str, str]] = [] + + for xml_file in xml_files: + try: + tree = ET.parse(xml_file) + except ET.ParseError: + continue + file_suites = _iter_testsuites(tree.getroot()) + if not file_suites: + continue + suites += len(file_suites) + for suite in file_suites: + tests += int(suite.get("tests", 0)) + failures += int(suite.get("failures", 0)) + errors += int(suite.get("errors", 0)) + skipped += int(suite.get("skipped", 0)) + duration += float(suite.get("time", 0) or 0) + failed_cases.extend(_collect_test_failures(suite)) + + passed = max(tests - failures - errors - skipped, 0) + lines = [ + "## Unit Tests", + "", + "| Metric | Count |", + "| --- | ---: |", + f"| Test suites | {suites} |", + f"| Tests (total) | {tests} |", + f"| Passed | {passed} |", + f"| Failed | {failures} |", + f"| Errors | {errors} |", + f"| Skipped | {skipped} |", + f"| Duration | {_format_duration(duration)} |", + "", + ] + + if failed_cases: + lines.extend( + [ + "### Failures and errors", + "", + "| Result | Test class | Test | Message |", + "| --- | --- | --- | --- |", + ] + ) + for kind, classname, name, message in failed_cases[:limit]: + safe_message = message.replace("|", "\\|") + lines.append(f"| {kind} | `{classname}` | `{name}` | {safe_message} |") + if len(failed_cases) > limit: + lines.append("") + lines.append(f"_Showing {limit} of {len(failed_cases)} failures and errors._") + lines.append("") + elif tests == 0: + lines.append("_No tests were executed._") + lines.append("") + else: + lines.append("_All tests passed._") + lines.append("") + + return "\n".join(lines) + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("xml", type=Path, help="Path to the Checkstyle XML report.") - parser.add_argument( + subparsers = parser.add_subparsers(dest="command") + + checkstyle_parser = subparsers.add_parser("checkstyle", help="Summarize a Checkstyle XML report.") + checkstyle_parser.add_argument("xml", type=Path, help="Path to the Checkstyle XML report.") + checkstyle_parser.add_argument( "--root", type=Path, default=Path.cwd(), help="Repository root used to shorten absolute file paths.", ) - parser.add_argument( + checkstyle_parser.add_argument( "--limit", type=int, default=25, help="Maximum number of findings to include in the table.", ) + + junit_parser = subparsers.add_parser( + "junit", help="Summarize Gradle/JUnit XML test results." + ) + junit_parser.add_argument( + "results", + type=Path, + help="Path to a JUnit XML file or a directory containing TEST-*.xml files.", + ) + junit_parser.add_argument( + "--limit", + type=int, + default=25, + help="Maximum number of failures and errors to include in the table.", + ) + parser.add_argument( "--output", type=Path, help="Optional file to write markdown to (defaults to stdout).", ) - args = parser.parse_args() - markdown = summarize_checkstyle(args.xml, args.root.resolve(), args.limit) + # Legacy invocation: report-summary.py path/to/checkstyle.xml + argv = sys.argv[1:] + if argv and argv[0] not in {"checkstyle", "junit"} and not argv[0].startswith("-"): + argv = ["checkstyle", *argv] + + args = parser.parse_args(argv) + if args.command is None: + parser.error("the following arguments are required: command") + + if args.command == "checkstyle": + markdown = summarize_checkstyle(args.xml, args.root.resolve(), args.limit) + else: + markdown = summarize_junit(args.results, args.limit) if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) From c4a2f75e934c4463822514c9737644a89daf6b63 Mon Sep 17 00:00:00 2001 From: Malte Hansen Date: Tue, 29 Sep 2026 12:23:00 +0200 Subject: [PATCH 5/6] Update GitHub CI test report --- .github/workflows/ci.yaml | 22 +- .gitignore | 4 + .gitlab-ci.yml | 4 +- code-analysis/ci/report-summary.py | 427 ++++++++++++++++++++++++----- 4 files changed, 375 insertions(+), 82 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d48c34b..daf7e23 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -129,16 +129,20 @@ jobs: GRADLE_OPTS: "-Dorg.gradle.daemon=false" - name: Write Unit Test Summary if: always() + env: + REPORT_COMMIT_SHA: ${{ github.sha }} + REPORT_BRANCH: ${{ github.head_ref || github.ref_name }} + REPORT_WORKFLOW: ${{ github.workflow }} + REPORT_RUN_NUMBER: ${{ github.run_number }} + REPORT_OS: ${{ runner.os }} + REPORT_RUNTIME: GraalVM + REPORT_RUNTIME_VERSION: "21" + REPORT_TEST_FRAMEWORK: JUnit Jupiter (Quarkus) + REPORT_TEST_EXIT_CODE: ${{ steps.unit_tests.outputs.exit_code }} run: | - python3 code-analysis/ci/report-summary.py junit build/test-results/test >> "$GITHUB_STEP_SUMMARY" - - name: Publish Unit Test Results - if: always() - uses: EnricoMi/publish-unit-test-result-action@v2 - with: - files: build/test-results/test/**/*.xml - check_name: Unit Tests - comment_mode: off - job_summary: false + python3 code-analysis/ci/report-summary.py junit build/test-results/test \ + --jacoco build/reports/jacoco/test/jacocoTestReport.xml \ + >> "$GITHUB_STEP_SUMMARY" - name: Publish Test and Coverage Reports if: always() uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 0b6a60b..8290949 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,10 @@ nb-configuration.xml *.orig *.rej +# Python +__pycache__/ +*.py[cod] + # Local environment .env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b5ae6e4..aab8609 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,7 +63,9 @@ unit-test: set +e ./gradlew test jacocoTestReport --continue TEST_EXIT=$? - python3 code-analysis/ci/report-summary.py junit build/test-results/test + REPORT_TEST_EXIT_CODE=$TEST_EXIT \ + python3 code-analysis/ci/report-summary.py junit build/test-results/test \ + --jacoco build/reports/jacoco/test/jacocoTestReport.xml exit $TEST_EXIT artifacts: when: always diff --git a/code-analysis/ci/report-summary.py b/code-analysis/ci/report-summary.py index 987b1a6..9baa6d6 100755 --- a/code-analysis/ci/report-summary.py +++ b/code-analysis/ci/report-summary.py @@ -4,8 +4,10 @@ from __future__ import annotations import argparse +import os import sys import xml.etree.ElementTree as ET +from dataclasses import dataclass from pathlib import Path @@ -74,102 +76,385 @@ def _iter_testsuites(root: ET.Element) -> list[ET.Element]: def _format_duration(seconds: float) -> str: if seconds < 60: - return f"{seconds:.1f} s" + return f"{seconds:.2f} s" minutes, remainder = divmod(seconds, 60) - return f"{int(minutes)} min {remainder:.1f} s" + return f"{int(minutes)} min {remainder:.2f} s" -def _collect_test_failures(testsuite: ET.Element) -> list[tuple[str, str, str, str]]: - failures: list[tuple[str, str, str, str]] = [] - for testcase in testsuite.findall("testcase"): - failure = testcase.find("failure") - error = testcase.find("error") - if failure is None and error is None: - continue - node = failure if failure is not None else error - kind = "Failed" if failure is not None else "Error" - name = testcase.get("name", "?") - classname = testcase.get("classname", "?") - message = (node.get("message") or "").strip() - if not message and node.text: - message = node.text.strip().splitlines()[0] - failures.append((kind, classname, name, message)) - return failures - - -def summarize_junit(results_path: Path, limit: int) -> str: +def _format_test_duration(seconds: float) -> str: + if seconds < 1: + return f"{seconds * 1000:.0f} ms" + return f"{seconds:.2f} s" + + +def _escape_table_cell(value: str) -> str: + return value.replace("|", "\\|").replace("\n", " ").strip() + + +@dataclass +class TestCaseResult: + classname: str + name: str + time: float + outcome: str + message: str = "" + details: str = "" + + +@dataclass +class JunitSummary: + test_cases: list[TestCaseResult] + total_duration: float + + @property + def total(self) -> int: + return len(self.test_cases) + + @property + def passed(self) -> int: + return sum(1 for test in self.test_cases if test.outcome == "passed") + + @property + def failed(self) -> int: + return sum(1 for test in self.test_cases if test.outcome == "failed") + + @property + def errors(self) -> int: + return sum(1 for test in self.test_cases if test.outcome == "error") + + @property + def skipped(self) -> int: + return sum(1 for test in self.test_cases if test.outcome == "skipped") + + +@dataclass +class ReportMetadata: + commit_sha: str | None = None + branch: str | None = None + workflow_name: str | None = None + run_number: str | None = None + os_name: str | None = None + runtime: str | None = None + runtime_version: str | None = None + test_framework: str | None = None + test_framework_version: str | None = None + test_exit_code: int | None = None + + +def _node_text(node: ET.Element | None) -> str: + if node is None: + return "" + message = (node.get("message") or "").strip() + body = (node.text or "").strip() + if message and body: + return f"{message}\n{body}" + return message or body + + +def _parse_testcase(testcase: ET.Element) -> TestCaseResult: + name = testcase.get("name", "?") + classname = testcase.get("classname", "?") + time = float(testcase.get("time", 0) or 0) + + skipped = testcase.find("skipped") + if skipped is not None: + message = _node_text(skipped) or "Skipped" + return TestCaseResult(classname, name, time, "skipped", message) + + failure = testcase.find("failure") + if failure is not None: + message = (failure.get("message") or "").strip() + details = _node_text(failure) + if not message and details: + message = details.splitlines()[0] + return TestCaseResult(classname, name, time, "failed", message, details) + + error = testcase.find("error") + if error is not None: + message = (error.get("message") or "").strip() + details = _node_text(error) + if not message and details: + message = details.splitlines()[0] + return TestCaseResult(classname, name, time, "error", message, details) + + return TestCaseResult(classname, name, time, "passed") + + +def load_junit_summary(results_path: Path) -> JunitSummary | None: if results_path.is_file(): xml_files = [results_path] elif results_path.is_dir(): xml_files = sorted(results_path.rglob("TEST-*.xml")) else: - return f"## Unit Tests\n\nNo test results found at `{results_path.as_posix()}`.\n" + return None if not xml_files: - return f"## Unit Tests\n\nNo JUnit XML reports found under `{results_path.as_posix()}`.\n" + return None - suites = 0 - tests = 0 - failures = 0 - errors = 0 - skipped = 0 - duration = 0.0 - failed_cases: list[tuple[str, str, str, str]] = [] + test_cases: list[TestCaseResult] = [] + total_duration = 0.0 for xml_file in xml_files: try: tree = ET.parse(xml_file) except ET.ParseError: continue - file_suites = _iter_testsuites(tree.getroot()) - if not file_suites: + for suite in _iter_testsuites(tree.getroot()): + total_duration += float(suite.get("time", 0) or 0) + for testcase in suite.findall("testcase"): + test_cases.append(_parse_testcase(testcase)) + + test_cases.sort(key=lambda test: (test.classname, test.name)) + return JunitSummary(test_cases=test_cases, total_duration=total_duration) + + +def _coverage_percent(missed: int, covered: int) -> float: + total = missed + covered + if total == 0: + return 0.0 + return covered * 100.0 / total + + +def load_jacoco_coverage(jacoco_path: Path) -> dict[str, float] | None: + if not jacoco_path.is_file(): + return None + + tree = ET.parse(jacoco_path) + root = tree.getroot() + if root.tag != "report": + return None + + mapping = { + "LINE": "lines", + "BRANCH": "branches", + "METHOD": "functions", + "INSTRUCTION": "statements", + } + coverage: dict[str, float] = {} + for counter in root.findall("counter"): + counter_type = counter.get("type") + key = mapping.get(counter_type or "") + if not key: continue - suites += len(file_suites) - for suite in file_suites: - tests += int(suite.get("tests", 0)) - failures += int(suite.get("failures", 0)) - errors += int(suite.get("errors", 0)) - skipped += int(suite.get("skipped", 0)) - duration += float(suite.get("time", 0) or 0) - failed_cases.extend(_collect_test_failures(suite)) - - passed = max(tests - failures - errors - skipped, 0) + missed = int(counter.get("missed", 0)) + covered = int(counter.get("covered", 0)) + coverage[key] = _coverage_percent(missed, covered) + + return coverage or None + + +def _metadata_from_env() -> ReportMetadata: + commit_sha = os.environ.get("REPORT_COMMIT_SHA") or os.environ.get("GITHUB_SHA") or os.environ.get( + "CI_COMMIT_SHA" + ) + branch = ( + os.environ.get("REPORT_BRANCH") + or os.environ.get("GITHUB_HEAD_REF") + or os.environ.get("GITHUB_REF_NAME") + or os.environ.get("CI_COMMIT_REF_NAME") + ) + workflow_name = os.environ.get("REPORT_WORKFLOW") or os.environ.get("GITHUB_WORKFLOW") + run_number = os.environ.get("REPORT_RUN_NUMBER") or os.environ.get("GITHUB_RUN_NUMBER") + os_name = os.environ.get("REPORT_OS") or os.environ.get("RUNNER_OS") or os.environ.get("CI_RUNNER_DESCRIPTION") + runtime = os.environ.get("REPORT_RUNTIME") or "Java" + runtime_version = os.environ.get("REPORT_RUNTIME_VERSION") or os.environ.get("JAVA_VERSION") + test_framework = os.environ.get("REPORT_TEST_FRAMEWORK") or "JUnit Jupiter" + test_framework_version = os.environ.get("REPORT_TEST_FRAMEWORK_VERSION") + + exit_code_raw = os.environ.get("REPORT_TEST_EXIT_CODE") + test_exit_code = int(exit_code_raw) if exit_code_raw is not None and exit_code_raw != "" else None + + return ReportMetadata( + commit_sha=commit_sha, + branch=branch, + workflow_name=workflow_name, + run_number=run_number, + os_name=os_name, + runtime=runtime, + runtime_version=runtime_version, + test_framework=test_framework, + test_framework_version=test_framework_version, + test_exit_code=test_exit_code, + ) + + +def _test_label(test: TestCaseResult) -> str: + return f"`{test.classname}.{test.name}`" + + +def _resolve_status(summary: JunitSummary | None, metadata: ReportMetadata) -> str: + if summary is not None and summary.failed + summary.errors > 0: + return "Failed ❌" + if metadata.test_exit_code not in (None, 0): + return "Failed ❌" + if summary is None or summary.total == 0: + return "No tests executed" + return "Passed ✅" + + +def _pass_rate(summary: JunitSummary) -> float: + executed = summary.passed + summary.failed + summary.errors + if executed == 0: + return 0.0 + return summary.passed * 100.0 / executed + + +def _append_report_context(lines: list[str], metadata: ReportMetadata) -> None: + context_lines: list[str] = [] + if metadata.commit_sha: + context_lines.append(f"**Commit:** `{metadata.commit_sha}`") + if metadata.branch: + context_lines.append(f"**Branch:** `{metadata.branch}`") + if metadata.workflow_name: + context_lines.append(f"**Workflow:** {metadata.workflow_name}") + if metadata.run_number: + context_lines.append(f"**Run:** {metadata.run_number}") + if context_lines: + lines.append(" \n".join(context_lines)) + lines.append("") + + +def _render_missing_junit_report(results_path: Path, metadata: ReportMetadata) -> str: + status = _resolve_status(None, metadata) + lines = [ + "# Unit Test Report", + "", + "## Summary", + "", + "| Metric | Result |", + "| --- | --- |", + f"| Status | {status} |", + "", + f"No test results found at `{results_path.as_posix()}`.", + "", + ] + _append_report_context(lines, metadata) + return "\n".join(lines) + + +def summarize_junit( + results_path: Path, + jacoco_path: Path | None = None, + metadata: ReportMetadata | None = None, +) -> str: + metadata = metadata or _metadata_from_env() + summary = load_junit_summary(results_path) + if summary is None: + return _render_missing_junit_report(results_path, metadata) + + coverage = load_jacoco_coverage(jacoco_path) if jacoco_path else None + + failed_total = summary.failed + summary.errors + pass_rate = _pass_rate(summary) + status = _resolve_status(summary, metadata) + lines = [ - "## Unit Tests", + "# Unit Test Report", "", - "| Metric | Count |", - "| --- | ---: |", - f"| Test suites | {suites} |", - f"| Tests (total) | {tests} |", - f"| Passed | {passed} |", - f"| Failed | {failures} |", - f"| Errors | {errors} |", - f"| Skipped | {skipped} |", - f"| Duration | {_format_duration(duration)} |", + "## Summary", + "", + "| Metric | Result |", + "| --- | --- |", + f"| Total tests | {summary.total} |", + f"| Passed | {summary.passed} ✅ |", + f"| Failed | {failed_total} ❌ |", + f"| Skipped | {summary.skipped} ⏭️ |", + f"| Pass rate | {pass_rate:.1f}% |", + f"| Duration | {_format_duration(summary.total_duration)} |", + f"| Status | {status} |", "", ] - if failed_cases: + _append_report_context(lines, metadata) + + environment_items: list[str] = [] + if metadata.os_name: + environment_items.append(f"- **OS:** {metadata.os_name}") + if metadata.runtime: + environment_items.append(f"- **Runtime:** {metadata.runtime}") + if metadata.runtime_version: + environment_items.append(f"- **Runtime version:** {metadata.runtime_version}") + if metadata.test_framework: + environment_items.append(f"- **Test framework:** {metadata.test_framework}") + if metadata.test_framework_version: + environment_items.append(f"- **Test framework version:** {metadata.test_framework_version}") + + if environment_items: + lines.extend(["## Environment", ""]) + lines.extend(environment_items) + lines.append("") + + if coverage: lines.extend( [ - "### Failures and errors", + "## Coverage", "", - "| Result | Test class | Test | Message |", - "| --- | --- | --- | --- |", + "| Metric | Coverage |", + "| --- | ---: |", ] ) - for kind, classname, name, message in failed_cases[:limit]: - safe_message = message.replace("|", "\\|") - lines.append(f"| {kind} | `{classname}` | `{name}` | {safe_message} |") - if len(failed_cases) > limit: - lines.append("") - lines.append(f"_Showing {limit} of {len(failed_cases)} failures and errors._") + labels = { + "lines": "Lines", + "branches": "Branches", + "functions": "Functions", + "statements": "Statements", + } + for key, label in labels.items(): + if key in coverage: + lines.append(f"| {label} | {coverage[key]:.1f}% |") + lines.append("") + + lines.extend(["## Detailed Test Results", ""]) + + executed = [test for test in summary.test_cases if test.outcome != "skipped"] + if executed: + lines.extend( + [ + "| Test | Duration |", + "| --- | ---: |", + ] + ) + for test in executed: + lines.append(f"| {_test_label(test)} | {_format_test_duration(test.time)} |") lines.append("") - elif tests == 0: - lines.append("_No tests were executed._") + + error_cases = [test for test in summary.test_cases if test.outcome == "error"] + if error_cases: + lines.extend( + [ + "| Test | Error |", + "| --- | --- |", + ] + ) + for test in error_cases: + lines.append(f"| {_test_label(test)} | {_escape_table_cell(test.message)} |") lines.append("") - else: - lines.append("_All tests passed._") + + detail_cases = [ + test for test in summary.test_cases if test.outcome in {"failed", "error"} + ] + if detail_cases: + lines.append("### Failure Details") + lines.append("") + for test in detail_cases: + lines.append(f"**{_test_label(test)}**") + lines.append("") + trace = test.details or test.message or "_No failure details recorded._" + lines.append("```") + lines.append(trace) + lines.append("```") + lines.append("") + + skipped_cases = [test for test in summary.test_cases if test.outcome == "skipped"] + if skipped_cases: + lines.extend( + [ + "| Test | Reason |", + "| --- | --- |", + ] + ) + for test in skipped_cases: + lines.append(f"| {_test_label(test)} | {_escape_table_cell(test.message)} |") lines.append("") return "\n".join(lines) @@ -203,10 +488,9 @@ def main() -> int: help="Path to a JUnit XML file or a directory containing TEST-*.xml files.", ) junit_parser.add_argument( - "--limit", - type=int, - default=25, - help="Maximum number of failures and errors to include in the table.", + "--jacoco", + type=Path, + help="Optional JaCoCo XML report for coverage metrics.", ) parser.add_argument( @@ -215,7 +499,6 @@ def main() -> int: help="Optional file to write markdown to (defaults to stdout).", ) - # Legacy invocation: report-summary.py path/to/checkstyle.xml argv = sys.argv[1:] if argv and argv[0] not in {"checkstyle", "junit"} and not argv[0].startswith("-"): argv = ["checkstyle", *argv] @@ -227,7 +510,7 @@ def main() -> int: if args.command == "checkstyle": markdown = summarize_checkstyle(args.xml, args.root.resolve(), args.limit) else: - markdown = summarize_junit(args.results, args.limit) + markdown = summarize_junit(args.results, args.jacoco) if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) From 23a7ada3a67905fa9e87a230c2d1bd0cd1810ab2 Mon Sep 17 00:00:00 2001 From: Malte Hansen Date: Tue, 29 Sep 2026 13:08:06 +0200 Subject: [PATCH 6/6] Update calculation of unit test times --- code-analysis/ci/report-summary.py | 78 +++++++++++++++++-- .../antlr/generated/c/CLexerBase.java | 5 -- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/code-analysis/ci/report-summary.py b/code-analysis/ci/report-summary.py index 9baa6d6..bab9be7 100755 --- a/code-analysis/ci/report-summary.py +++ b/code-analysis/ci/report-summary.py @@ -8,6 +8,7 @@ import sys import xml.etree.ElementTree as ET from dataclasses import dataclass +from datetime import datetime, timedelta from pathlib import Path @@ -74,6 +75,71 @@ def _iter_testsuites(root: ET.Element) -> list[ET.Element]: return [] +def _parse_junit_seconds(raw: str | None) -> float: + if not raw: + return 0.0 + normalized = raw.strip().replace(",", "") + try: + return float(normalized) + except ValueError: + return 0.0 + + +def _iter_testcases(suite: ET.Element): + for testcase in suite.findall("testcase"): + yield testcase + for nested in suite.findall("testsuite"): + yield from _iter_testcases(nested) + + +def _effective_suite_duration(suite: ET.Element) -> float: + suite_time = _parse_junit_seconds(suite.get("time")) + if suite_time > 0: + return suite_time + return sum(_parse_junit_seconds(testcase.get("time")) for testcase in _iter_testcases(suite)) + + +def _suite_interval(suite: ET.Element) -> tuple[datetime, datetime] | None: + timestamp = suite.get("timestamp") + if not timestamp: + return None + try: + start = datetime.fromisoformat(timestamp) + except ValueError: + return None + duration = _effective_suite_duration(suite) + return start, start + timedelta(seconds=duration) + + +def _total_execution_seconds(roots: list[ET.Element]) -> float: + intervals: list[tuple[datetime, datetime]] = [] + fallback_sum = 0.0 + + for root in roots: + if root.tag == "testsuites": + root_time = _parse_junit_seconds(root.get("time")) + child_suites = root.findall("testsuite") + if root_time > 0 and child_suites: + child_suite_time = sum(_parse_junit_seconds(s.get("time")) for s in child_suites) + if child_suite_time <= 0: + fallback_sum += root_time + for suite in child_suites: + interval = _suite_interval(suite) + if interval: + intervals.append(interval) + continue + + for suite in _iter_testsuites(root): + fallback_sum += _effective_suite_duration(suite) + interval = _suite_interval(suite) + if interval: + intervals.append(interval) + + if intervals: + return (max(end for _, end in intervals) - min(start for start, _ in intervals)).total_seconds() + return fallback_sum + + def _format_duration(seconds: float) -> str: if seconds < 60: return f"{seconds:.2f} s" @@ -154,7 +220,7 @@ def _node_text(node: ET.Element | None) -> str: def _parse_testcase(testcase: ET.Element) -> TestCaseResult: name = testcase.get("name", "?") classname = testcase.get("classname", "?") - time = float(testcase.get("time", 0) or 0) + time = _parse_junit_seconds(testcase.get("time")) skipped = testcase.find("skipped") if skipped is not None: @@ -192,19 +258,21 @@ def load_junit_summary(results_path: Path) -> JunitSummary | None: return None test_cases: list[TestCaseResult] = [] - total_duration = 0.0 + roots: list[ET.Element] = [] for xml_file in xml_files: try: tree = ET.parse(xml_file) except ET.ParseError: continue - for suite in _iter_testsuites(tree.getroot()): - total_duration += float(suite.get("time", 0) or 0) - for testcase in suite.findall("testcase"): + root = tree.getroot() + roots.append(root) + for suite in _iter_testsuites(root): + for testcase in _iter_testcases(suite): test_cases.append(_parse_testcase(testcase)) test_cases.sort(key=lambda test: (test.classname, test.name)) + total_duration = _total_execution_seconds(roots) return JunitSummary(test_cases=test_cases, total_duration=total_duration) diff --git a/src/main/java/net/explorviz/code/analysis/antlr/generated/c/CLexerBase.java b/src/main/java/net/explorviz/code/analysis/antlr/generated/c/CLexerBase.java index 40ed5bf..3514db0 100644 --- a/src/main/java/net/explorviz/code/analysis/antlr/generated/c/CLexerBase.java +++ b/src/main/java/net/explorviz/code/analysis/antlr/generated/c/CLexerBase.java @@ -43,11 +43,6 @@ public static CharStream runGccAndMakeStream(CharStream input) { String inputText = input.getText(new Interval(0, input.size() - 1)); if (nopp) { - try { - Files.writeString(Path.of(outputName), inputText); - } catch (IOException e) { - // Ignore - } return CharStreams.fromString(inputText); }