Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ vulnerability report.
**Prerequisites**

- For Maven projects, analyzing a `pom.xml` file, you must have the `mvn` binary in your IDE's `PATH` environment.
- For Node projects, analyzing a `package.json` file, you must have one of the corresponding package manager `npm` or `pnpm`, `node` binaries in your IDE's `PATH`
- For Node projects, analyzing a `package.json` file, you must have one of the corresponding package manager `npm`, `pnpm` or `yarn`, `node` binaries in your IDE's `PATH`
environment.
- For Golang projects, analyzing a `go.mod` file, you must have the `go` binary in your IDE's `PATH` environment.
- For Python projects, analyzing a `requirements.txt` file, you must have the `python3` and `pip3` binaries in your
Expand Down Expand Up @@ -86,9 +86,9 @@ according to your preferences.
executables.

- **Node** :
<br >Set the full path of the Node executable, which allows Exhort to locate and run one of the corresponding `npm` or `pnpm` command to resolve
<br >Set the full path of the Node executable, which allows Exhort to locate and run one of the corresponding `npm`, `pnpm` or `yarn` command to resolve
dependencies for Node projects.
<br >Path of the directory containing the `node` executable is required by one of the corresponding `npm` or `pnpm` executable.
<br >Path of the directory containing the `node` executable is required by one of the corresponding `npm`, `pnpm` or `yarn` executable.
<br >If the paths are not provided, your IDE's `PATH` environment will be used to locate the executables.

- **Golang** :
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*******************************************************************************
* 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.json.psi.JsonArray;
import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonProperty;
import com.intellij.json.psi.JsonStringLiteral;
import com.intellij.json.psi.JsonValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class CAUtil {

public static String PACKAGE_JSON = "package.json";
public static String EXHORT_IGNORE = "exhortignore";
public static String DEPENDENCIES = "dependencies";

public static Map<Dependency, List<PsiElement>> getDependencyListMap(PsiFile file) {
if (PACKAGE_JSON.equals(file.getName())) {
Set<String> ignored = getIgnoredDependencies(file);
return getDependencyListMap(file, ignored);
}
return Collections.emptyMap();
}

private static Map<Dependency, List<PsiElement>> getDependencyListMap(PsiFile file, Set<String> ignored) {
Map<Dependency, List<PsiElement>> resultMap = new HashMap<>();
Arrays.stream(file.getChildren())
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonProperty && DEPENDENCIES.equals(((JsonProperty) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(c -> c instanceof JsonProperty && !ignored.contains(((JsonProperty) c).getName()))
.forEach(c -> {
String name = ((JsonProperty) c).getName();
int index = name.lastIndexOf("/");
String namespace = null;
if (index > 0) {
namespace = name.substring(0, index);
name = name.substring(index + 1);
} else if (index == 0) {
name = name.substring(index + 1);
}
JsonValue value = ((JsonProperty) c).getValue();
String version = value instanceof JsonStringLiteral
? ((JsonStringLiteral) value).getValue()
: null;
Dependency dp = new Dependency("npm", namespace, name, version);
resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(c);
});
return resultMap;
}

private static Set<String> getIgnoredDependencies(PsiFile file) {
Set<String> ignored = Arrays.stream(file.getChildren())
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonProperty && EXHORT_IGNORE.equals(((JsonProperty) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonArray)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(c -> c instanceof JsonStringLiteral)
.map(c -> ((JsonStringLiteral) c).getValue())
.collect(Collectors.toSet());
return ignored;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import java.util.*;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;

public class GoCAAnnotator extends CAAnnotator {
@Override
protected String getInspectionShortName() {
Expand All @@ -42,7 +44,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
PsiComment[] comments = PsiTreeUtil.getChildrenOfType(m, PsiComment.class);
if (comments != null) {
return Arrays.stream(comments)
.noneMatch(c -> c.getText().contains("exhortignore"));
.noneMatch(c -> c.getText().contains(EXHORT_IGNORE));
}
return true;
})
Expand All @@ -57,7 +59,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
PsiComment[] comments = PsiTreeUtil.getChildrenOfType(r, PsiComment.class);
if (comments != null) {
return Arrays.stream(comments)
.noneMatch(c -> c.getText().contains("exhortignore"));
.noneMatch(c -> c.getText().contains(EXHORT_IGNORE));
}
return true;
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.util.*;
import java.util.stream.Collectors;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;

public class GradleCAAnnotator extends CAAnnotator {

@Override
Expand All @@ -40,7 +42,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
List<Artifact> elements;
Arrays.stream(file.getChildren())
.filter(e -> e instanceof Artifact)
.filter(artifact -> ((Artifact)artifact).getComment() == null || Objects.nonNull(((Artifact)artifact).getComment()) && !((Artifact)artifact).getComment().getText().contains("exhortignore"))
.filter(artifact -> ((Artifact)artifact).getComment() == null || Objects.nonNull(((Artifact)artifact).getComment()) && !((Artifact)artifact).getComment().getText().contains(EXHORT_IGNORE))
.map(dep -> (Artifact)dep)
.forEach( dep -> {
Dependency dependency = new Dependency("maven", dep.getGroup().getText().replace("\"","") , dep.getArtifactId().getText(),dep.getVersion().getText());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import java.util.*;
import java.util.stream.Collectors;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.DEPENDENCIES;
import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;

public class MavenCAAnnotator extends CAAnnotator {

@Override
Expand All @@ -40,12 +43,12 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof XmlTag && "project".equals(((XmlTag) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof XmlTag && "dependencies".equals(((XmlTag) e).getName()))
.filter(e -> e instanceof XmlTag && DEPENDENCIES.equals(((XmlTag) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof XmlTag && "dependency".equals(((XmlTag) e).getName()))
.filter(e -> Arrays.stream(e.getChildren())
.noneMatch(c -> c instanceof XmlComment
&& "exhortignore".equals(((XmlComment) c).getCommentText().trim())))
&& EXHORT_IGNORE.equals(((XmlComment) c).getCommentText().trim())))
.map(e -> (XmlTag) e)
.forEach(d -> {
List<XmlTag> elements = Arrays.stream(d.getChildren())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@

package org.jboss.tools.intellij.componentanalysis.npm;

import com.intellij.json.psi.*;
import com.intellij.json.psi.JsonProperty;
import com.intellij.json.psi.JsonStringLiteral;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.redhat.exhort.api.v4.DependencyReport;
import org.jboss.tools.intellij.componentanalysis.*;
import org.jboss.tools.intellij.componentanalysis.CAAnnotator;
import org.jboss.tools.intellij.componentanalysis.CAIntentionAction;
import org.jboss.tools.intellij.componentanalysis.CAUpdateManifestIntentionAction;
import org.jboss.tools.intellij.componentanalysis.Dependency;
import org.jboss.tools.intellij.componentanalysis.VulnerabilitySource;

import java.util.*;
import java.util.stream.Collectors;
import java.util.List;
import java.util.Map;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.getDependencyListMap;

public class NpmCAAnnotator extends CAAnnotator {

Expand All @@ -29,47 +36,7 @@ protected String getInspectionShortName() {

@Override
protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
if ("package.json".equals(file.getName())) {
Set<String> ignored = Arrays.stream(file.getChildren())
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonProperty && "exhortignore".equals(((JsonProperty) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonArray)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(c -> c instanceof JsonStringLiteral)
.map(c -> ((JsonStringLiteral) c).getValue())
.collect(Collectors.toSet());

Map<Dependency, List<PsiElement>> resultMap = new HashMap<>();
Arrays.stream(file.getChildren())
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonProperty && "dependencies".equals(((JsonProperty) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(c -> c instanceof JsonProperty && !ignored.contains(((JsonProperty) c).getName()))
.forEach(c -> {
String name = ((JsonProperty) c).getName();
int index = name.lastIndexOf("/");
String namespace = null;
if (index > 0) {
namespace = name.substring(0, index);
name = name.substring(index + 1);
} else if (index == 0) {
name = name.substring(index + 1);
}
JsonValue value = ((JsonProperty) c).getValue();
String version = value instanceof JsonStringLiteral
? ((JsonStringLiteral) value).getValue()
: null;
Dependency dp = new Dependency("npm", namespace, name, version);
resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(c);
});
return resultMap;
}
return Collections.emptyMap();
return getDependencyListMap(file);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.PACKAGE_JSON;

public final class NpmCAIntentionAction extends CAIntentionAction {
NpmCAIntentionAction(PsiElement element, VulnerabilitySource source, DependencyReport report) {
super(element, source, report);
Expand All @@ -46,6 +48,6 @@ protected void updateVersion(@NotNull Project project, Editor editor, PsiFile fi

@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return file != null && "package.json".equals(file.getName());
return file != null && PACKAGE_JSON.equals(file.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@

package org.jboss.tools.intellij.componentanalysis.pnpm;

import com.intellij.json.psi.JsonArray;
import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonProperty;
import com.intellij.json.psi.JsonStringLiteral;
import com.intellij.json.psi.JsonValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.redhat.exhort.api.v4.DependencyReport;
Expand All @@ -25,14 +22,10 @@
import org.jboss.tools.intellij.componentanalysis.Dependency;
import org.jboss.tools.intellij.componentanalysis.VulnerabilitySource;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.getDependencyListMap;

public class PnpmCAAnnotator extends CAAnnotator {

Expand All @@ -43,47 +36,7 @@ protected String getInspectionShortName() {

@Override
protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
if ("package.json".equals(file.getName())) {
Set<String> ignored = Arrays.stream(file.getChildren())
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonProperty && "exhortignore".equals(((JsonProperty) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonArray)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(c -> c instanceof JsonStringLiteral)
.map(c -> ((JsonStringLiteral) c).getValue())
.collect(Collectors.toSet());

Map<Dependency, List<PsiElement>> resultMap = new HashMap<>();
Arrays.stream(file.getChildren())
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonProperty && "dependencies".equals(((JsonProperty) e).getName()))
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(e -> e instanceof JsonObject)
.flatMap(e -> Arrays.stream(e.getChildren()))
.filter(c -> c instanceof JsonProperty && !ignored.contains(((JsonProperty) c).getName()))
.forEach(c -> {
String name = ((JsonProperty) c).getName();
int index = name.lastIndexOf("/");
String namespace = null;
if (index > 0) {
namespace = name.substring(0, index);
name = name.substring(index + 1);
} else if (index == 0) {
name = name.substring(index + 1);
}
JsonValue value = ((JsonProperty) c).getValue();
String version = value instanceof JsonStringLiteral
? ((JsonStringLiteral) value).getValue()
: null;
Dependency dp = new Dependency("npm", namespace, name, version);
resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(c);
});
return resultMap;
}
return Collections.emptyMap();
return getDependencyListMap(file);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.PACKAGE_JSON;

public final class PnpmCAIntentionAction extends CAIntentionAction {
PnpmCAIntentionAction(PsiElement element, VulnerabilitySource source, DependencyReport report) {
super(element, source, report);
Expand All @@ -46,6 +48,6 @@ protected void updateVersion(@NotNull Project project, Editor editor, PsiFile fi

@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return file != null && "package.json".equals(file.getName());
return file != null && PACKAGE_JSON.equals(file.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import java.util.*;

import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;

public class PipCAAnnotator extends CAAnnotator {
@Override
protected String getInspectionShortName() {
Expand All @@ -39,7 +41,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
.noneMatch(c -> {
String comment = c.getText().trim();
if (!comment.isEmpty() && '#' == comment.charAt(0)) {
return "exhortignore".equals(comment.substring(1).trim());
return EXHORT_IGNORE.equals(comment.substring(1).trim());
}
return false;
}))
Expand Down
Loading
Loading