diff --git a/MAVEN_LINTER_USAGE.md b/MAVEN_LINTER_USAGE.md new file mode 100644 index 000000000..dc0b6b43a --- /dev/null +++ b/MAVEN_LINTER_USAGE.md @@ -0,0 +1,96 @@ +# Maven Linter Quick Start + +## Enable (pom.xml) +```xml + + dev.dsf + dsf-maven-plugin + + + lint-plugin + + lint + + + + +``` + +## Run +- `mvn verify` +- `mvn deploy` +- `mvn install` +- `mvn dsf-maven-plugin:lint` + +## Parameters +- Skip: `-Ddsf.lint.skip=true` +- Do not fail build: `-Ddsf.lint.failOnErrors=false` +- HTML Report: `-Ddsf.lint.html=true` +- JSON Report: `-Ddsf.lint.json=true` +- Verbose Logs: `-Ddsf.lint.verbose=true` +- Report path: `-Ddsf.lint.reportPath=target/dsf-linter-report` +- Exclusion rules: `-Ddsf.lint.exclusionsFile=path/to/dsf-linter-exclusions.json` + +## Excluding Issues + +Users often encounter known, intentional, or external findings that clutter reports and make triage harder. +The exclusion system lets you suppress specific lint items from HTML and JSON reports without touching the plugin source. + +### Option A — Auto-discovery (recommended) + +Place a file named `dsf-linter-exclusions.json` in your project root. The plugin picks it up automatically — no extra configuration needed. + +### Option B — Explicit file + +Pass the path via the Maven property: + +```bash +mvn verify -Ddsf.lint.exclusionsFile=path/to/my-exclusions.json +``` + +Or configure it permanently in `pom.xml`: + +```xml + + dev.dsf + dsf-maven-plugin + + ${project.basedir}/dsf-linter-exclusions.json + + + + lint-plugin + lint + + + +``` + +### Exclusion file format + +```json +{ + "affectsExitStatus": false, + "rules": [ + { "type": "BPMN_PROCESS_HISTORY_TIME_TO_LIVE_MISSING" }, + { "severity": "WARN", "file": "update-allow-list.bpmn" }, + { "messageContains": "optional field" } + ] +} +``` + +**Rule fields** (AND-combined within a rule; multiple rules are OR-combined): + +| Field | Match | Example | +|---|---|---| +| `type` | Exact, case-insensitive `LintingType` name | `"BPMN_PROCESS_HISTORY_TIME_TO_LIVE_MISSING"` | +| `severity` | Exact, case-insensitive severity | `"WARN"`, `"ERROR"`, `"INFO"` | +| `file` | Case-insensitive substring of the file name | `"update-allow-list"` | +| `messageContains` | Case-insensitive substring of the description | `"optional field"` | + +**`affectsExitStatus`:** + +| Value | Behaviour | +|---|---| +| `false` *(default)* | Excluded items are fully suppressed — hidden from reports **and** not counted towards build failure | +| `true` | Excluded items are hidden from reports, but their error count **still** causes a build failure | diff --git a/dsf-bpe/dsf-bpe-test-plugin-v1/pom.xml b/dsf-bpe/dsf-bpe-test-plugin-v1/pom.xml index 59a322ca5..eba0be72c 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v1/pom.xml +++ b/dsf-bpe/dsf-bpe-test-plugin-v1/pom.xml @@ -49,21 +49,28 @@ - - - dev.dsf - dsf-maven-plugin - - - - generate-config-doc - - - - - dev.dsf.bpe.test.spring.config - - - + + + dev.dsf + dsf-maven-plugin + + + generate-config-doc + + generate-config-doc + + + + lint-plugin + + lint + + + + + dev.dsf.bpe.test.spring.config + + + \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/pom.xml b/dsf-bpe/dsf-bpe-test-plugin-v2/pom.xml index 328ac2027..2132e7674 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/pom.xml +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/pom.xml @@ -45,20 +45,27 @@ - - dev.dsf - dsf-maven-plugin - - - - generate-config-doc - - - - - dev.dsf.bpe.test.spring.config - - + + dev.dsf + dsf-maven-plugin + + + generate-config-doc + + generate-config-doc + + + + lint-plugin + + lint + + + + + dev.dsf.bpe.test.spring.config + + \ No newline at end of file diff --git a/dsf-maven/dsf-maven-plugin/pom.xml b/dsf-maven/dsf-maven-plugin/pom.xml index 396038382..823d3c678 100644 --- a/dsf-maven/dsf-maven-plugin/pom.xml +++ b/dsf-maven/dsf-maven-plugin/pom.xml @@ -52,7 +52,12 @@ provided - + + dev.dsf.linter + linter-core + + + dev.dsf dsf-bpe-process-api-v1 diff --git a/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/linter/LintPluginMojo.java b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/linter/LintPluginMojo.java new file mode 100644 index 000000000..9487dc124 --- /dev/null +++ b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/linter/LintPluginMojo.java @@ -0,0 +1,194 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.maven.linter; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; + +import dev.dsf.linter.DsfLinter; +import dev.dsf.linter.DsfLinter.OverallLinterResult; +import dev.dsf.linter.exclusion.ExclusionConfig; +import dev.dsf.linter.exclusion.ExclusionConfigLoader; +import dev.dsf.linter.input.InputResolver; +import dev.dsf.linter.input.InputResolver.ResolutionResult; +import dev.dsf.linter.logger.Logger; + +/** + * Lints DSF process plugin JAR files by validating BPMN processes, FHIR resources and plugin configurations. + *

+ * Runs after the JAR has been built (default phase: verify) and delegates to the + * DSF Linter core library. + */ +@Mojo(name = "lint", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.NONE, threadSafe = true) +public class LintPluginMojo extends AbstractMojo +{ + @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true) + private File projectBuildDirectory; + + @Parameter(defaultValue = "${project.build.finalName}", readonly = true, required = true) + private String finalName; + + @Parameter(property = "dsf.lint.reportPath") + private File reportPath; + + @Parameter(property = "dsf.lint.html", defaultValue = "false") + private boolean generateHtmlReport; + + @Parameter(property = "dsf.lint.json", defaultValue = "false") + private boolean generateJsonReport; + + @Parameter(property = "dsf.lint.failOnErrors", defaultValue = "true") + private boolean failOnErrors; + + @Parameter(property = "dsf.lint.skip", defaultValue = "false") + private boolean skip; + + @Parameter(property = "dsf.lint.verbose", defaultValue = "false") + private boolean verbose; + + /** + * Path to a JSON file containing exclusion rules. Excluded items are hidden from HTML and JSON reports. + * When omitted, the plugin also looks for {@code dsf-linter-exclusions.json} in the project build + * directory automatically. + * + *

Example: {@code -Ddsf.lint.exclusionsFile=path/to/dsf-linter-exclusions.json}

+ */ + @Parameter(property = "dsf.lint.exclusionsFile") + private File exclusionsFile; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException + { + if (skip) + { + getLog().info("DSF Linter: skipped"); + return; + } + + Path jarPath = projectBuildDirectory.toPath().resolve(finalName + ".jar"); + + if (!Files.exists(jarPath)) + { + getLog().warn("DSF Linter: JAR not found at " + jarPath + ", skipping"); + return; + } + + Logger logger = new MavenLinterLogger(getLog(), verbose); + + InputResolver resolver = new InputResolver(logger); + Optional resolutionResult = resolver.resolve(jarPath.toString()); + + if (resolutionResult.isEmpty()) + { + throw new MojoExecutionException("DSF Linter: failed to resolve JAR file: " + jarPath); + } + + ResolutionResult resolution = resolutionResult.get(); + + try + { + Path lintReportPath = reportPath != null ? reportPath.toPath() + : projectBuildDirectory.toPath().resolve("dsf-linter-report"); + + Files.createDirectories(lintReportPath); + + ExclusionConfig exclusionConfig = loadExclusionConfig(resolution.resolvedPath().toAbsolutePath()); + + DsfLinter.Config config = new DsfLinter.Config(resolution.resolvedPath().toAbsolutePath(), + lintReportPath.toAbsolutePath(), generateHtmlReport, generateJsonReport, failOnErrors, + exclusionConfig, logger); + + OverallLinterResult result = new DsfLinter(config).lint(); + + if (!result.success()) + { + String msg = "DSF Linter: " + result.getTotalErrors() + " error(s), " + result.getPluginWarnings() + + " warning(s)"; + + if (failOnErrors) + { + throw new MojoFailureException(msg); + } + + getLog().warn(msg); + } + else + { + getLog().info("DSF Linter: passed - no errors found"); + } + } + catch (IOException e) + { + throw new MojoExecutionException("DSF Linter failed", e); + } + finally + { + if (resolution.requiresCleanup()) + { + resolver.cleanup(resolution); + } + } + } + + /** + * Loads the exclusion config from an explicit file ({@code dsf.lint.exclusionsFile}) or + * auto-discovers {@code dsf-linter-exclusions.json} in the extracted project directory. + * Returns {@code null} when no exclusion file is found or configured. + */ + private ExclusionConfig loadExclusionConfig(Path projectRoot) throws MojoExecutionException + { + ExclusionConfigLoader loader = new ExclusionConfigLoader(); + try + { + if (exclusionsFile != null) + { + ExclusionConfig config = loader.load(exclusionsFile.toPath()); + getLog().info("DSF Linter: loaded exclusion rules from " + exclusionsFile + + " (" + config.getRules().size() + " rule(s), affectsExitStatus=" + + config.isAffectsExitStatus() + ")"); + return config; + } + + Optional auto = loader.loadFromProjectRoot(projectRoot); + if (auto.isPresent()) + { + ExclusionConfig config = auto.get(); + getLog().info("DSF Linter: auto-discovered exclusion rules from " + + projectRoot.resolve(ExclusionConfigLoader.DEFAULT_FILENAME) + + " (" + config.getRules().size() + " rule(s), affectsExitStatus=" + + config.isAffectsExitStatus() + ")"); + return config; + } + + return null; + } + catch (IOException e) + { + throw new MojoExecutionException("DSF Linter: failed to load exclusion config", e); + } + } +} diff --git a/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/linter/MavenLinterLogger.java b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/linter/MavenLinterLogger.java new file mode 100644 index 000000000..7e9b82e35 --- /dev/null +++ b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/linter/MavenLinterLogger.java @@ -0,0 +1,74 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.maven.linter; + +import org.apache.maven.plugin.logging.Log; + +import dev.dsf.linter.logger.Logger; + +public class MavenLinterLogger implements Logger +{ + private final Log mavenLog; + private final boolean verboseMode; + + public MavenLinterLogger(Log mavenLog, boolean verboseMode) + { + this.mavenLog = mavenLog; + this.verboseMode = verboseMode; + } + + @Override + public void info(String message) + { + mavenLog.info(message); + } + + @Override + public void warn(String message) + { + mavenLog.warn(message); + } + + @Override + public void error(String message) + { + mavenLog.error(message); + } + + @Override + public void error(String message, Throwable throwable) + { + mavenLog.error(message, throwable); + } + + @Override + public void debug(String message) + { + mavenLog.debug(message); + } + + @Override + public boolean verbose() + { + return verboseMode; + } + + @Override + public boolean isVerbose() + { + return verboseMode; + } +} diff --git a/pom.xml b/pom.xml index cbf3de556..94e865cf5 100755 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 3.8.0 5.2.1 5.2.1 - + DSF Parent POM Data Sharing Framework (DSF) @@ -507,6 +507,13 @@ 3.2.3
+ + + dev.dsf.linter + linter-core + 0.1.3 + + org.apache.maven