diff --git a/README.md b/README.md
index 2112a94..e9a755e 100644
--- a/README.md
+++ b/README.md
@@ -150,6 +150,10 @@ according to your preferences.
- **Proxy Configuration** :
From IntelliJ IDEA Appearance & Behavior > System Settings > HTTP Proxy, you can configure a static proxy for all HTTP requests made by the plugin. This is useful when your environment requires going through a proxy to access external services. For example:`http://proxy.example.com:8080`
+- **Manifest Exclusion Patterns** :
+
You can exclude manifest files from component analysis using glob patterns. This is useful for excluding third-party dependencies, test files, or other manifests that should not be analyzed.
+
Enter one pattern per line. Examples: `**/node_modules/**/package.json` to exclude all package.json files in node_modules directories, or `test/**/pom.xml` to exclude all Maven files in test directories.
+
## Features
- **Component analysis**
@@ -294,6 +298,18 @@ according to your preferences.
You can create an alternative file to `requirements.txt`, for example, a `requirements-dev.txt` or
a `requirements-test.txt` file where you can add the development or test dependencies there.
+
+- **Excluding manifest files with patterns**
+
You can exclude specific manifest files from component analysis using configurable glob patterns. This feature allows you to avoid analyzing third-party dependencies, test files, or other manifests that are not relevant to your security analysis.
+
Patterns are configured in the plugin settings under **Tools > Red Hat Dependency Analytics > Manifest Exclusion Patterns**.
+
Examples of exclusion patterns:
+ - `**/node_modules/**/package.json` - Excludes all package.json files in node_modules directories
+ - `test/**/pom.xml` - Excludes all Maven pom.xml files in test directories
+ - `vendor/**/*.go.mod` - Excludes all go.mod files in vendor directories
+ - `**/build.gradle` - Excludes all Gradle build files
+
Right-click on any manifest file and select **Exclude from Component Analysis** to quickly add an exclusion pattern for that specific file.
+
+
- **Red Hat Dependency Analytics report**
The Red Hat Dependency Analytics report is a temporary HTML file that exist if the **Red Hat Dependency Analytics
Report** tab remains open.
diff --git a/build.gradle.kts b/build.gradle.kts
index e574f69..c4d8332 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -88,6 +88,7 @@ dependencies {
// for tests
testImplementation(libs.junit)
+ testImplementation(libs.mockito)
}
tasks {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 72c5694..d8a4e72 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,6 +7,7 @@ exhort-api-spec = "1.0.18"
exhort-java-api = "0.0.8"
github-api = "1.314"
junit = "4.13.2"
+mockito = "4.11.0"
packageurl-java = "1.4.1"
# plugins
@@ -20,6 +21,7 @@ exhort-api-spec = { group = "com.redhat.ecosystemappeng", name = "exhort-api-spe
exhort-java-api = { group = "com.redhat.exhort", name = "exhort-java-api", version.ref = "exhort-java-api" }
github-api = { group = "org.kohsuke", name = "github-api", version.ref = "github-api" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
+mockito = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
packageurl-java = { group = "com.github.package-url", name = "packageurl-java", version.ref = "packageurl-java" }
[plugins]
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java
index 091d55e..2eb844d 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java
@@ -52,6 +52,12 @@ public abstract class CAAnnotator extends ExternalAnnotator annotationResul
}
}
builder.withFix(new SAIntentionAction());
+ builder.withFix(new ExcludeManifestIntentionAction());
builder.create();
}
);
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAService.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAService.java
index b666430..5b5d3c5 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAService.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAService.java
@@ -27,12 +27,8 @@
import com.redhat.exhort.api.v4.DependencyReport;
import com.redhat.exhort.api.v4.ProviderReport;
import com.redhat.exhort.api.v4.Source;
-import org.apache.commons.io.FileUtils;
import org.jboss.tools.intellij.exhort.ApiService;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
@@ -171,14 +167,4 @@ public static boolean performAnalysis(String packageManager,
}
return false;
}
-
- private static void deleteTempDir(Path tempDirectory) {
- try {
- FileUtils.deleteDirectory(tempDirectory.toFile());
- } catch (IOException e) {
- LOG.warn("Failed to delete temp directory: " + tempDirectory, e);
- }
- }
-
-
}
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/ExcludeManifestIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/ExcludeManifestIntentionAction.java
new file mode 100644
index 0000000..bb2ff05
--- /dev/null
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/ExcludeManifestIntentionAction.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Red Hat, Inc.
+ * Distributed under license by Red Hat, Inc. All rights reserved.
+ * This program is made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+
+package org.jboss.tools.intellij.componentanalysis;
+
+import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.codeInsight.intention.FileModifier;
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInspection.util.IntentionFamilyName;
+import com.intellij.codeInspection.util.IntentionName;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class ExcludeManifestIntentionAction implements IntentionAction {
+
+ @Override
+ public @IntentionName @NotNull String getText() {
+ return "Exclude this manifest from component analysis";
+ }
+
+ @Override
+ public @NotNull @IntentionFamilyName String getFamilyName() {
+ return "RHDA";
+ }
+
+ @Override
+ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
+ if (file == null || file.getVirtualFile() == null) {
+ return false;
+ }
+
+ String fileName = file.getName();
+ return "pom.xml".equals(fileName) ||
+ "package.json".equals(fileName) ||
+ "go.mod".equals(fileName) ||
+ "requirements.txt".equals(fileName) ||
+ "build.gradle".equals(fileName);
+ }
+
+ @Override
+ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
+ VirtualFile virtualFile = file.getVirtualFile();
+ if (virtualFile == null) {
+ return;
+ }
+
+ VirtualFile projectRoot = project.getBaseDir();
+ if (projectRoot == null) {
+ return;
+ }
+
+ String filePath = virtualFile.getPath();
+ String projectPath = projectRoot.getPath();
+
+ if (filePath.startsWith(projectPath)) {
+ String relativePath = filePath.substring(projectPath.length());
+ if (relativePath.startsWith("/") || relativePath.startsWith("\\")) {
+ relativePath = relativePath.substring(1);
+ }
+
+ ManifestExclusionManager.addExclusionPattern(relativePath, project);
+
+ ApplicationManager.getApplication().runReadAction(() -> {
+ DaemonCodeAnalyzer.getInstance(project).restart(file);
+ });
+ }
+ }
+
+ @Override
+ public boolean startInWriteAction() {
+ return false;
+ }
+
+ @Override
+ public @Nullable FileModifier getFileModifierForPreview(@NotNull PsiFile target) {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/ManifestExclusionManager.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/ManifestExclusionManager.java
new file mode 100644
index 0000000..c4a0dcf
--- /dev/null
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/ManifestExclusionManager.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Red Hat, Inc.
+ * Distributed under license by Red Hat, Inc. All rights reserved.
+ * This program is made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+
+package org.jboss.tools.intellij.componentanalysis;
+
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jboss.tools.intellij.settings.ApiSettingsState;
+
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ManifestExclusionManager {
+
+ private static final Logger LOG = Logger.getInstance(ManifestExclusionManager.class);
+
+ public static boolean isManifestExcluded(VirtualFile file, Project project) {
+ if (file == null) {
+ return false;
+ }
+
+ ApiSettingsState settings = ApiSettingsState.getInstance();
+ String patterns = settings.manifestExclusionPatterns;
+
+ if (patterns == null || patterns.trim().isEmpty()) {
+ return false;
+ }
+
+ VirtualFile projectRoot = project.getBaseDir();
+ if (projectRoot == null) {
+ return false;
+ }
+
+ String relativePath = getRelativePath(file, projectRoot);
+ if (relativePath == null) {
+ return false;
+ }
+
+ List exclusionPatterns = parsePatterns(patterns);
+ return matchesAnyPattern(relativePath, exclusionPatterns);
+ }
+
+ public static void addExclusionPattern(String manifestPath, Project project) {
+ if (manifestPath == null || manifestPath.trim().isEmpty()) {
+ return;
+ }
+
+ VirtualFile projectRoot = project.getBaseDir();
+ if (projectRoot == null) {
+ return;
+ }
+
+ VirtualFile manifestFile = project.getBaseDir().findFileByRelativePath(manifestPath);
+ if (manifestFile == null) {
+ return;
+ }
+
+ String relativePath = getRelativePath(manifestFile, projectRoot);
+ if (relativePath == null) {
+ return;
+ }
+
+ ApiSettingsState settings = ApiSettingsState.getInstance();
+ List currentPatterns = parsePatterns(settings.manifestExclusionPatterns);
+
+ if (!currentPatterns.contains(relativePath)) {
+ currentPatterns.add(relativePath);
+ settings.manifestExclusionPatterns = String.join("\n", currentPatterns);
+ }
+ }
+
+ private static String getRelativePath(VirtualFile file, VirtualFile projectRoot) {
+ try {
+ String filePath = file.getPath();
+ String projectPath = projectRoot.getPath();
+
+ if (filePath.startsWith(projectPath)) {
+ String relativePath = filePath.substring(projectPath.length());
+ if (relativePath.startsWith("/") || relativePath.startsWith("\\")) {
+ relativePath = relativePath.substring(1);
+ }
+ return relativePath;
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to get relative path for file: " + file.getPath(), e);
+ }
+ return null;
+ }
+
+ private static List parsePatterns(String patterns) {
+ if (patterns == null || patterns.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return Arrays.stream(patterns.split("[\n\r]+"))
+ .map(String::trim)
+ .filter(pattern -> !pattern.isEmpty() && !pattern.startsWith("#"))
+ .collect(Collectors.toList());
+ }
+
+ private static boolean matchesAnyPattern(String path, List patterns) {
+ Path filePath = Paths.get(path);
+
+ for (String pattern : patterns) {
+ try {
+ // Test the pattern as-is first
+ PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
+ if (matcher.matches(filePath)) {
+ LOG.debug("File " + path + " matches exclusion pattern: " + pattern);
+ return true;
+ }
+
+ // For patterns starting with "**/" that don't match, also test against
+ // the path with a virtual directory prefix to handle the Java PathMatcher
+ // limitation where "**/file.txt" doesn't match "file.txt" at root
+ if (pattern.startsWith("**/")) {
+ // Try matching with a virtual directory prefix
+ Path prefixedPath = Paths.get("dummy/" + path);
+ if (matcher.matches(prefixedPath)) {
+ LOG.debug("File " + path + " matches exclusion pattern: " + pattern + " (with directory prefix)");
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Invalid glob pattern: " + pattern, e);
+ }
+ }
+
+ return false;
+ }
+
+ public static List getExclusionPatterns() {
+ ApiSettingsState settings = ApiSettingsState.getInstance();
+ return parsePatterns(settings.manifestExclusionPatterns);
+ }
+
+ public static void setExclusionPatterns(List patterns) {
+ ApiSettingsState settings = ApiSettingsState.getInstance();
+ settings.manifestExclusionPatterns = String.join("\n", patterns);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java
index 2fbc7b4..f4f4122 100644
--- a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java
+++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java
@@ -18,6 +18,8 @@
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTextField;
+import com.intellij.ui.components.JBTextArea;
+import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ui.FormBuilder;
import org.jetbrains.annotations.NotNull;
@@ -74,6 +76,9 @@ public class ApiSettingsComponent {
+ "
Specifies absolute path of podman executable.