Skip to content

Commit 8af69e1

Browse files
committed
feat: add support for yarn package manager
refactor: use CAUtil for various CAAnnotator
1 parent f45d3b0 commit 8af69e1

File tree

19 files changed

+333
-114
lines changed

19 files changed

+333
-114
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ vulnerability report.
4242
**Prerequisites**
4343

4444
- For Maven projects, analyzing a `pom.xml` file, you must have the `mvn` binary in your IDE's `PATH` environment.
45-
- 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`
45+
- 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`
4646
environment.
4747
- For Golang projects, analyzing a `go.mod` file, you must have the `go` binary in your IDE's `PATH` environment.
4848
- For Python projects, analyzing a `requirements.txt` file, you must have the `python3` and `pip3` binaries in your
@@ -86,9 +86,9 @@ according to your preferences.
8686
executables.
8787

8888
- **Node** :
89-
<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
89+
<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
9090
dependencies for Node projects.
91-
<br >Path of the directory containing the `node` executable is required by one of the corresponding `npm` or `pnpm` executable.
91+
<br >Path of the directory containing the `node` executable is required by one of the corresponding `npm`, `pnpm` or `yarn` executable.
9292
<br >If the paths are not provided, your IDE's `PATH` environment will be used to locate the executables.
9393

9494
- **Golang** :
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
12+
package org.jboss.tools.intellij.componentanalysis;
13+
14+
15+
import com.intellij.json.psi.JsonArray;
16+
import com.intellij.json.psi.JsonObject;
17+
import com.intellij.json.psi.JsonProperty;
18+
import com.intellij.json.psi.JsonStringLiteral;
19+
import com.intellij.json.psi.JsonValue;
20+
import com.intellij.psi.PsiElement;
21+
import com.intellij.psi.PsiFile;
22+
23+
import java.util.Arrays;
24+
import java.util.Collections;
25+
import java.util.HashMap;
26+
import java.util.LinkedList;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.Set;
30+
import java.util.stream.Collectors;
31+
32+
public class CAUtil {
33+
34+
public static String PACKAGE_JSON = "package.json";
35+
public static String EXHORT_IGNORE = "exhortignore";
36+
public static String DEPENDENCIES = "dependencies";
37+
38+
public static Map<Dependency, List<PsiElement>> getDependencyListMap(PsiFile file) {
39+
if (PACKAGE_JSON.equals(file.getName())) {
40+
Set<String> ignored = Arrays.stream(file.getChildren())
41+
.filter(e -> e instanceof JsonObject)
42+
.flatMap(e -> Arrays.stream(e.getChildren()))
43+
.filter(e -> e instanceof JsonProperty && EXHORT_IGNORE.equals(((JsonProperty) e).getName()))
44+
.flatMap(e -> Arrays.stream(e.getChildren()))
45+
.filter(e -> e instanceof JsonArray)
46+
.flatMap(e -> Arrays.stream(e.getChildren()))
47+
.filter(c -> c instanceof JsonStringLiteral)
48+
.map(c -> ((JsonStringLiteral) c).getValue())
49+
.collect(Collectors.toSet());
50+
51+
Map<Dependency, List<PsiElement>> resultMap = new HashMap<>();
52+
Arrays.stream(file.getChildren())
53+
.filter(e -> e instanceof JsonObject)
54+
.flatMap(e -> Arrays.stream(e.getChildren()))
55+
.filter(e -> e instanceof JsonProperty && DEPENDENCIES.equals(((JsonProperty) e).getName()))
56+
.flatMap(e -> Arrays.stream(e.getChildren()))
57+
.filter(e -> e instanceof JsonObject)
58+
.flatMap(e -> Arrays.stream(e.getChildren()))
59+
.filter(c -> c instanceof JsonProperty && !ignored.contains(((JsonProperty) c).getName()))
60+
.forEach(c -> {
61+
String name = ((JsonProperty) c).getName();
62+
int index = name.lastIndexOf("/");
63+
String namespace = null;
64+
if (index > 0) {
65+
namespace = name.substring(0, index);
66+
name = name.substring(index + 1);
67+
} else if (index == 0) {
68+
name = name.substring(index + 1);
69+
}
70+
JsonValue value = ((JsonProperty) c).getValue();
71+
String version = value instanceof JsonStringLiteral
72+
? ((JsonStringLiteral) value).getValue()
73+
: null;
74+
Dependency dp = new Dependency("npm", namespace, name, version);
75+
resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(c);
76+
});
77+
return resultMap;
78+
}
79+
return Collections.emptyMap();
80+
}
81+
}

src/main/java/org/jboss/tools/intellij/componentanalysis/golang/GoCAAnnotator.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import java.util.*;
2525

26+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;
27+
2628
public class GoCAAnnotator extends CAAnnotator {
2729
@Override
2830
protected String getInspectionShortName() {
@@ -42,7 +44,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
4244
PsiComment[] comments = PsiTreeUtil.getChildrenOfType(m, PsiComment.class);
4345
if (comments != null) {
4446
return Arrays.stream(comments)
45-
.noneMatch(c -> c.getText().contains("exhortignore"));
47+
.noneMatch(c -> c.getText().contains(EXHORT_IGNORE));
4648
}
4749
return true;
4850
})
@@ -57,7 +59,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
5759
PsiComment[] comments = PsiTreeUtil.getChildrenOfType(r, PsiComment.class);
5860
if (comments != null) {
5961
return Arrays.stream(comments)
60-
.noneMatch(c -> c.getText().contains("exhortignore"));
62+
.noneMatch(c -> c.getText().contains(EXHORT_IGNORE));
6163
}
6264
return true;
6365
})

src/main/java/org/jboss/tools/intellij/componentanalysis/gradle/GradleCAAnnotator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.util.*;
2727
import java.util.stream.Collectors;
2828

29+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;
30+
2931
public class GradleCAAnnotator extends CAAnnotator {
3032

3133
@Override
@@ -40,7 +42,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
4042
List<Artifact> elements;
4143
Arrays.stream(file.getChildren())
4244
.filter(e -> e instanceof Artifact)
43-
.filter(artifact -> ((Artifact)artifact).getComment() == null || Objects.nonNull(((Artifact)artifact).getComment()) && !((Artifact)artifact).getComment().getText().contains("exhortignore"))
45+
.filter(artifact -> ((Artifact)artifact).getComment() == null || Objects.nonNull(((Artifact)artifact).getComment()) && !((Artifact)artifact).getComment().getText().contains(EXHORT_IGNORE))
4446
.map(dep -> (Artifact)dep)
4547
.forEach( dep -> {
4648
Dependency dependency = new Dependency("maven", dep.getGroup().getText().replace("\"","") , dep.getArtifactId().getText(),dep.getVersion().getText());

src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import java.util.*;
2424
import java.util.stream.Collectors;
2525

26+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.DEPENDENCIES;
27+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;
28+
2629
public class MavenCAAnnotator extends CAAnnotator {
2730

2831
@Override
@@ -40,12 +43,12 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
4043
.flatMap(e -> Arrays.stream(e.getChildren()))
4144
.filter(e -> e instanceof XmlTag && "project".equals(((XmlTag) e).getName()))
4245
.flatMap(e -> Arrays.stream(e.getChildren()))
43-
.filter(e -> e instanceof XmlTag && "dependencies".equals(((XmlTag) e).getName()))
46+
.filter(e -> e instanceof XmlTag && DEPENDENCIES.equals(((XmlTag) e).getName()))
4447
.flatMap(e -> Arrays.stream(e.getChildren()))
4548
.filter(e -> e instanceof XmlTag && "dependency".equals(((XmlTag) e).getName()))
4649
.filter(e -> Arrays.stream(e.getChildren())
4750
.noneMatch(c -> c instanceof XmlComment
48-
&& "exhortignore".equals(((XmlComment) c).getCommentText().trim())))
51+
&& EXHORT_IGNORE.equals(((XmlComment) c).getCommentText().trim())))
4952
.map(e -> (XmlTag) e)
5053
.forEach(d -> {
5154
List<XmlTag> elements = Arrays.stream(d.getChildren())

src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@
1111

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

14-
import com.intellij.json.psi.*;
14+
import com.intellij.json.psi.JsonProperty;
15+
import com.intellij.json.psi.JsonStringLiteral;
1516
import com.intellij.psi.PsiElement;
1617
import com.intellij.psi.PsiFile;
1718
import com.redhat.exhort.api.v4.DependencyReport;
18-
import org.jboss.tools.intellij.componentanalysis.*;
19+
import org.jboss.tools.intellij.componentanalysis.CAAnnotator;
20+
import org.jboss.tools.intellij.componentanalysis.CAIntentionAction;
21+
import org.jboss.tools.intellij.componentanalysis.CAUpdateManifestIntentionAction;
22+
import org.jboss.tools.intellij.componentanalysis.Dependency;
23+
import org.jboss.tools.intellij.componentanalysis.VulnerabilitySource;
1924

20-
import java.util.*;
21-
import java.util.stream.Collectors;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.getDependencyListMap;
2229

2330
public class NpmCAAnnotator extends CAAnnotator {
2431

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

3037
@Override
3138
protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
32-
if ("package.json".equals(file.getName())) {
33-
Set<String> ignored = Arrays.stream(file.getChildren())
34-
.filter(e -> e instanceof JsonObject)
35-
.flatMap(e -> Arrays.stream(e.getChildren()))
36-
.filter(e -> e instanceof JsonProperty && "exhortignore".equals(((JsonProperty) e).getName()))
37-
.flatMap(e -> Arrays.stream(e.getChildren()))
38-
.filter(e -> e instanceof JsonArray)
39-
.flatMap(e -> Arrays.stream(e.getChildren()))
40-
.filter(c -> c instanceof JsonStringLiteral)
41-
.map(c -> ((JsonStringLiteral) c).getValue())
42-
.collect(Collectors.toSet());
43-
44-
Map<Dependency, List<PsiElement>> resultMap = new HashMap<>();
45-
Arrays.stream(file.getChildren())
46-
.filter(e -> e instanceof JsonObject)
47-
.flatMap(e -> Arrays.stream(e.getChildren()))
48-
.filter(e -> e instanceof JsonProperty && "dependencies".equals(((JsonProperty) e).getName()))
49-
.flatMap(e -> Arrays.stream(e.getChildren()))
50-
.filter(e -> e instanceof JsonObject)
51-
.flatMap(e -> Arrays.stream(e.getChildren()))
52-
.filter(c -> c instanceof JsonProperty && !ignored.contains(((JsonProperty) c).getName()))
53-
.forEach(c -> {
54-
String name = ((JsonProperty) c).getName();
55-
int index = name.lastIndexOf("/");
56-
String namespace = null;
57-
if (index > 0) {
58-
namespace = name.substring(0, index);
59-
name = name.substring(index + 1);
60-
} else if (index == 0) {
61-
name = name.substring(index + 1);
62-
}
63-
JsonValue value = ((JsonProperty) c).getValue();
64-
String version = value instanceof JsonStringLiteral
65-
? ((JsonStringLiteral) value).getValue()
66-
: null;
67-
Dependency dp = new Dependency("npm", namespace, name, version);
68-
resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(c);
69-
});
70-
return resultMap;
71-
}
72-
return Collections.emptyMap();
39+
return getDependencyListMap(file);
7340
}
7441

7542
@Override

src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAIntentionAction.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.jetbrains.annotations.NotNull;
2626
import org.jetbrains.annotations.Nullable;
2727

28+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.PACKAGE_JSON;
29+
2830
public final class NpmCAIntentionAction extends CAIntentionAction {
2931
NpmCAIntentionAction(PsiElement element, VulnerabilitySource source, DependencyReport report) {
3032
super(element, source, report);
@@ -46,6 +48,6 @@ protected void updateVersion(@NotNull Project project, Editor editor, PsiFile fi
4648

4749
@Override
4850
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
49-
return file != null && "package.json".equals(file.getName());
51+
return file != null && PACKAGE_JSON.equals(file.getName());
5052
}
5153
}

src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAAnnotator.java

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@
1111

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

14-
import com.intellij.json.psi.JsonArray;
15-
import com.intellij.json.psi.JsonObject;
1614
import com.intellij.json.psi.JsonProperty;
1715
import com.intellij.json.psi.JsonStringLiteral;
18-
import com.intellij.json.psi.JsonValue;
1916
import com.intellij.psi.PsiElement;
2017
import com.intellij.psi.PsiFile;
2118
import com.redhat.exhort.api.v4.DependencyReport;
@@ -25,14 +22,10 @@
2522
import org.jboss.tools.intellij.componentanalysis.Dependency;
2623
import org.jboss.tools.intellij.componentanalysis.VulnerabilitySource;
2724

28-
import java.util.Arrays;
29-
import java.util.Collections;
30-
import java.util.HashMap;
31-
import java.util.LinkedList;
3225
import java.util.List;
3326
import java.util.Map;
34-
import java.util.Set;
35-
import java.util.stream.Collectors;
27+
28+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.getDependencyListMap;
3629

3730
public class PnpmCAAnnotator extends CAAnnotator {
3831

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

4437
@Override
4538
protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
46-
if ("package.json".equals(file.getName())) {
47-
Set<String> ignored = Arrays.stream(file.getChildren())
48-
.filter(e -> e instanceof JsonObject)
49-
.flatMap(e -> Arrays.stream(e.getChildren()))
50-
.filter(e -> e instanceof JsonProperty && "exhortignore".equals(((JsonProperty) e).getName()))
51-
.flatMap(e -> Arrays.stream(e.getChildren()))
52-
.filter(e -> e instanceof JsonArray)
53-
.flatMap(e -> Arrays.stream(e.getChildren()))
54-
.filter(c -> c instanceof JsonStringLiteral)
55-
.map(c -> ((JsonStringLiteral) c).getValue())
56-
.collect(Collectors.toSet());
57-
58-
Map<Dependency, List<PsiElement>> resultMap = new HashMap<>();
59-
Arrays.stream(file.getChildren())
60-
.filter(e -> e instanceof JsonObject)
61-
.flatMap(e -> Arrays.stream(e.getChildren()))
62-
.filter(e -> e instanceof JsonProperty && "dependencies".equals(((JsonProperty) e).getName()))
63-
.flatMap(e -> Arrays.stream(e.getChildren()))
64-
.filter(e -> e instanceof JsonObject)
65-
.flatMap(e -> Arrays.stream(e.getChildren()))
66-
.filter(c -> c instanceof JsonProperty && !ignored.contains(((JsonProperty) c).getName()))
67-
.forEach(c -> {
68-
String name = ((JsonProperty) c).getName();
69-
int index = name.lastIndexOf("/");
70-
String namespace = null;
71-
if (index > 0) {
72-
namespace = name.substring(0, index);
73-
name = name.substring(index + 1);
74-
} else if (index == 0) {
75-
name = name.substring(index + 1);
76-
}
77-
JsonValue value = ((JsonProperty) c).getValue();
78-
String version = value instanceof JsonStringLiteral
79-
? ((JsonStringLiteral) value).getValue()
80-
: null;
81-
Dependency dp = new Dependency("npm", namespace, name, version);
82-
resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(c);
83-
});
84-
return resultMap;
85-
}
86-
return Collections.emptyMap();
39+
return getDependencyListMap(file);
8740
}
8841

8942
@Override

src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAIntentionAction.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.jetbrains.annotations.NotNull;
2626
import org.jetbrains.annotations.Nullable;
2727

28+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.PACKAGE_JSON;
29+
2830
public final class PnpmCAIntentionAction extends CAIntentionAction {
2931
PnpmCAIntentionAction(PsiElement element, VulnerabilitySource source, DependencyReport report) {
3032
super(element, source, report);
@@ -46,6 +48,6 @@ protected void updateVersion(@NotNull Project project, Editor editor, PsiFile fi
4648

4749
@Override
4850
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
49-
return file != null && "package.json".equals(file.getName());
51+
return file != null && PACKAGE_JSON.equals(file.getName());
5052
}
5153
}

src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PipCAAnnotator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import java.util.*;
2222

23+
import static org.jboss.tools.intellij.componentanalysis.CAUtil.EXHORT_IGNORE;
24+
2325
public class PipCAAnnotator extends CAAnnotator {
2426
@Override
2527
protected String getInspectionShortName() {
@@ -39,7 +41,7 @@ protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
3941
.noneMatch(c -> {
4042
String comment = c.getText().trim();
4143
if (!comment.isEmpty() && '#' == comment.charAt(0)) {
42-
return "exhortignore".equals(comment.substring(1).trim());
44+
return EXHORT_IGNORE.equals(comment.substring(1).trim());
4345
}
4446
return false;
4547
}))

0 commit comments

Comments
 (0)