Repository: redhat-developer/intellij-dependency-analytics
Pull request #181: feat: add support for yarn package manager
Author: login: soul2zimate
Created: 2025-05-21T08:20:48Z | Merged: 2025-05-22T09:36:49Z

feat: add support for yarn package manager

Fixes: https://github.com/redhat-developer/intellij-dependency-analytics/issues/175


Diff:
diff --git a/README.md b/README.md
index 7d2e7f13..5806a464 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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** :
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAUtil.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAUtil.java
new file mode 100644
index 00000000..904ce3a0
--- /dev/null
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAUtil.java
@@ -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;
+    }
+}
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/golang/GoCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/golang/GoCAAnnotator.java
index 54552f33..f4f4c8a0 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/golang/GoCAAnnotator.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/golang/GoCAAnnotator.java
@@ -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() {
@@ -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;
                         })
@@ -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;
                         })
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/gradle/GradleCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/gradle/GradleCAAnnotator.java
index 6236a36c..6bad5d8f 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/gradle/GradleCAAnnotator.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/gradle/GradleCAAnnotator.java
@@ -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
@@ -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());
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java
index b30f43bf..8a928f7d 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java
@@ -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
@@ -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())
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java
index 5cc5b2e6..94516545 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java
@@ -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 {
 
@@ -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
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAIntentionAction.java
index 3eb09098..4f0bd06e 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAIntentionAction.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAIntentionAction.java
@@ -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);
@@ -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());
     }
 }
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAAnnotator.java
index 4b2a281d..4158fbe1 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAAnnotator.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAAnnotator.java
@@ -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;
@@ -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 {
 
@@ -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
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAIntentionAction.java
index ee63e964..36c0ae64 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAIntentionAction.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/pnpm/PnpmCAIntentionAction.java
@@ -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);
@@ -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());
     }
 }
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PipCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PipCAAnnotator.java
index 45c4e59f..98459c0e 100644
--- a/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PipCAAnnotator.java
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/pypi/PipCAAnnotator.java
@@ -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() {
@@ -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;
                             }))
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAAnnotator.java
new file mode 100644
index 00000000..d0e30106
--- /dev/null
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAAnnotator.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.yarn;
+
+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.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.List;
+import java.util.Map;
+
+import static org.jboss.tools.intellij.componentanalysis.CAUtil.getDependencyListMap;
+
+public class YarnCAAnnotator extends CAAnnotator {
+
+    @Override
+    protected String getInspectionShortName() {
+        return YarnCAInspection.SHORT_NAME;
+    }
+
+    @Override
+    protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
+        return getDependencyListMap(file);
+    }
+
+    @Override
+    protected CAIntentionAction createQuickFix(PsiElement element, VulnerabilitySource source, DependencyReport report) {
+        return new YarnCAIntentionAction(element, source, report);
+    }
+
+    @Override
+    protected CAUpdateManifestIntentionAction patchManifest(PsiElement element, DependencyReport report) {
+        return null;
+    }
+
+    @Override
+    protected boolean isQuickFixApplicable(PsiElement element) {
+        return element instanceof JsonProperty && ((JsonProperty) element).getValue() instanceof JsonStringLiteral;
+    }
+}
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAInspection.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAInspection.java
new file mode 100644
index 00000000..553c1b03
--- /dev/null
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAInspection.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.yarn;
+
+import com.intellij.codeHighlighting.HighlightDisplayLevel;
+import com.intellij.codeInspection.LocalInspectionTool;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+public class YarnCAInspection extends LocalInspectionTool {
+
+    @NonNls
+    public static final String SHORT_NAME = "YarnCAInspection";
+
+    @Override
+    @NotNull
+    public String getGroupDisplayName() {
+        return "Imports and dependencies";
+    }
+
+    @Override
+    public @Nls(capitalization = Nls.Capitalization.Sentence) String @NotNull [] getGroupPath() {
+        return new String[]{"JavaScript and TypeScript"};
+    }
+
+    @Override
+    @NotNull
+    public String getShortName() {
+        return SHORT_NAME;
+    }
+
+    @Override
+    @NotNull
+    public HighlightDisplayLevel getDefaultLevel() {
+        return HighlightDisplayLevel.ERROR;
+    }
+}
diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAIntentionAction.java
new file mode 100644
index 00000000..b9c5ce23
--- /dev/null
+++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAIntentionAction.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * 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.yarn;
+
+import com.intellij.codeInsight.intention.FileModifier;
+import com.intellij.json.psi.JsonElementGenerator;
+import com.intellij.json.psi.JsonProperty;
+import com.intellij.json.psi.JsonStringLiteral;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.redhat.exhort.api.v4.DependencyReport;
+import org.jboss.tools.intellij.componentanalysis.CAIntentionAction;
+import org.jboss.tools.intellij.componentanalysis.VulnerabilitySource;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static org.jboss.tools.intellij.componentanalysis.CAUtil.PACKAGE_JSON;
+
+public final class YarnCAIntentionAction extends CAIntentionAction {
+    YarnCAIntentionAction(PsiElement element, VulnerabilitySource source, DependencyReport report) {
+        super(element, source, report);
+    }
+
+    @Override
+    protected void updateVersion(@NotNull Project project, Editor editor, PsiFile file, String version) {
+        JsonStringLiteral value = (JsonStringLiteral) ((JsonProperty) element).getValue();
+        if (value != null) {
+            JsonStringLiteral newValue = new JsonElementGenerator(project).createStringLiteral(version);
+            value.replace(newValue);
+        }
+    }
+
+    @Override
+    protected @Nullable FileModifier createCAIntentionActionInCopy(PsiElement element) {
+        return new YarnCAIntentionAction(element, this.source, this.report);
+    }
+
+    @Override
+    public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
+        return file != null && PACKAGE_JSON.equals(file.getName());
+    }
+}
diff --git a/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java b/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java
index a816261e..501fd6ec 100644
--- a/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java
+++ b/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java
@@ -149,6 +149,11 @@ private void setRequestProperties(final String manifestName) {
         } else {
             System.clearProperty("EXHORT_NPM_PATH");
         }
+        if (settings.yarnPath != null && !settings.yarnPath.isBlank()) {
+            System.setProperty("EXHORT_YARN_PATH", settings.yarnPath);
+        } else {
+            System.clearProperty("EXHORT_YARN_PATH");
+        }
         if (settings.nodePath != null && !settings.nodePath.isBlank()) {
             System.setProperty("NODE_HOME", settings.nodePath);
         } else {
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 4feb0495..0f597b17 100644
--- a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java
+++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java
@@ -16,13 +16,12 @@
 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
 import com.intellij.ui.components.JBCheckBox;
 import com.intellij.ui.components.JBLabel;
-import com.intellij.ui.components.JBRadioButton;
 import com.intellij.ui.components.JBTextField;
 import com.intellij.util.ui.FormBuilder;
 import org.jetbrains.annotations.NotNull;
 
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
 
 public class ApiSettingsComponent {
 
@@ -34,6 +33,8 @@ public class ApiSettingsComponent {
             + "<br>Specifies absolute path of <b>npm</b> executable.</html>";
     private final static String pnpmPathLabel = "<html>Pnpm > Executable: <b>Path</b>"
             + "<br>Specifies absolute path of <b>pnpm</b> executable.</html>";
+    private final static String yarnPathLabel = "<html>Yarn > Executable: <b>Path</b>"
+            + "<br>Specifies absolute path of <b>yarn</b> executable.</html>";
     private final static String nodePathLabel = "<html>Node > Directory: <b>Path</b>"
             + "<br>Specifies absolute path of the <i>directory</i> containing <b>node</b> executable.</html>";
     private final static String goPathLabel = "<html>Go > Executable: <b>Path</b>"
@@ -75,6 +76,7 @@ public class ApiSettingsComponent {
     private final TextFieldWithBrowseButton javaPathText;
     private final TextFieldWithBrowseButton npmPathText;
     private final TextFieldWithBrowseButton pnpmPathText;
+    private final TextFieldWithBrowseButton yarnPathText;
     private final TextFieldWithBrowseButton nodePathText;
     private final TextFieldWithBrowseButton goPathText;
     private final JBCheckBox goMatchManifestVersionsCheck;
@@ -133,6 +135,15 @@ public ApiSettingsComponent() {
                 TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT
         );
 
+        yarnPathText = new TextFieldWithBrowseButton();
+        yarnPathText.addBrowseFolderListener(
+                null,
+                null,
+                null,
+                FileChooserDescriptorFactory.createSingleFileDescriptor(),
+                TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT
+        );
+
         nodePathText = new TextFieldWithBrowseButton();
         nodePathText.addBrowseFolderListener(
                 null,
@@ -253,6 +264,8 @@ public ApiSettingsComponent() {
                 .addVerticalGap(10)
                 .addLabeledComponent(new JBLabel(pnpmPathLabel), pnpmPathText, 1, true)
                 .addVerticalGap(10)
+                .addLabeledComponent(new JBLabel(yarnPathLabel), yarnPathText, 1, true)
+                .addVerticalGap(10)
                 .addLabeledComponent(new JBLabel(nodePathLabel), nodePathText, 1, true)
                 .addSeparator(10)
                 .addVerticalGap(10)
@@ -339,6 +352,14 @@ public void setPnpmPathText(@NotNull String text) {
         pnpmPathText.setText(text);
     }
 
+    public String getYarnPathText() {
+        return yarnPathText.getText();
+    }
+
+    public void setYarnPathText(@NotNull String text) {
+        yarnPathText.setText(text);
+    }
+
     @NotNull
     public String getNodePathText() {
         return nodePathText.getText();
diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java
index 9cda0e29..684e92cd 100644
--- a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java
+++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java
@@ -14,7 +14,7 @@
 import com.intellij.openapi.util.NlsContexts;
 import org.jetbrains.annotations.Nullable;
 
-import javax.swing.*;
+import javax.swing.JComponent;
 
 public class ApiSettingsConfigurable implements com.intellij.openapi.options.Configurable {
 
@@ -43,6 +43,7 @@ public boolean isModified() {
         modified |= !settingsComponent.getJavaPathText().equals(settings.javaPath);
         modified |= !settingsComponent.getNpmPathText().equals(settings.npmPath);
         modified |= !settingsComponent.getPnpmPathText().equals(settings.pnpmPath);
+        modified |= !settingsComponent.getYarnPathText().equals(settings.yarnPath);
         modified |= !settingsComponent.getNodePathText().equals(settings.nodePath);
         modified |= !settingsComponent.getGoPathText().equals(settings.goPath);
         modified |= settingsComponent.getGoMatchManifestVersionsCheck() != settings.goMatchManifestVersions;
@@ -70,6 +71,7 @@ public void apply() {
         settings.javaPath = settingsComponent.getJavaPathText();
         settings.npmPath = settingsComponent.getNpmPathText();
         settings.pnpmPath = settingsComponent.getPnpmPathText();
+        settings.yarnPath = settingsComponent.getYarnPathText();
         settings.nodePath = settingsComponent.getNodePathText();
         settings.goPath = settingsComponent.getGoPathText();
         settings.goMatchManifestVersions = settingsComponent.getGoMatchManifestVersionsCheck();
@@ -96,6 +98,7 @@ public void reset() {
         settingsComponent.setJavaPathText(settings.javaPath != null ? settings.javaPath : "");
         settingsComponent.setNpmPathText(settings.npmPath != null ? settings.npmPath : "");
         settingsComponent.setPnpmPathText(settings.pnpmPath != null ? settings.pnpmPath : "");
+        settingsComponent.setYarnPathText(settings.yarnPath != null ? settings.yarnPath : "");
         settingsComponent.setNodePathText(settings.nodePath != null ? settings.nodePath : "");
         settingsComponent.setGoPathText(settings.goPath != null ? settings.goPath : "");
         settingsComponent.setGoMatchManifestVersionsCheck(settings.goMatchManifestVersions);
diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java
index 616ac86e..1dec2889 100644
--- a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java
+++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java
@@ -40,6 +40,7 @@ public final class ApiSettingsState implements PersistentStateComponent<ApiSetti
 
     public String npmPath;
     public String pnpmPath;
+    public String yarnPath;
     public String nodePath;
 
     public String goPath;
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 76e98707..e96f3649 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -21,7 +21,7 @@
 <p>
     <b>IMPORTANT:</b>
     <br>Currently, Dependency Analytics only supports projects that use Maven (<code>mvn</code>), and Node
-    (<code>npm</code> or <code>pnpm</code>), Golang (<code>go mod</code>) and Python (<code>pip</code>) ecosystems, and base images in
+    (<code>npm</code>, <code>pnpm</code> or <code>yarn</code>), Golang (<code>go mod</code>) and Python (<code>pip</code>) ecosystems, and base images in
     <code>Dockerfile</code>.
     <br>In future releases, Red Hat plans to support other programming languages.
 <p>
@@ -33,7 +33,7 @@
     <li>For Maven projects, analyzing a <code>pom.xml</code> file, you must have the <code>mvn</code> binary in your
         IDE's <code>PATH</code> environment.
     </li>
-    <li>For Node projects, analyzing a <code>package.json</code> file, you must have one of the corresponding package manager <code>npm</code> or <code>pnpm</code> and
+    <li>For Node projects, analyzing a <code>package.json</code> file, you must have one of the corresponding package manager <code>npm</code>, <code>pnpm</code> or <code>yarn</code> and
         <code>node</code> binaries in your IDE's <code>PATH</code> environment.
     </li>
     <li>For Golang projects, analyzing a <code>go.mod</code> file, you must have the <code>go</code> binary in your
@@ -99,9 +99,9 @@
     </li>
     <li>
         <b>Node</b>:
-        <br>Set the full path of the Node executable, which allows Exhort to locate and execute one of the corresponding <code>npm</code> or <code>pnpm</code> command
+        <br>Set the full path of the Node executable, which allows Exhort to locate and execute one of the corresponding <code>npm</code>, <code>pnpm</code> or <code>yarn</code> command
         to resolve dependencies for Node projects.
-        <br>Path of the directory containing the <code>node</code> executable is required by one of the corresponding package manager <code>npm</code> or <code>pnpm</code>
+        <br>Path of the directory containing the <code>node</code> executable is required by one of the corresponding package manager <code>npm</code>, <code>pnpm</code> or <code>yarn</code>
         executable.
         <br>If the paths are not provided, your IDE's <code>PATH</code> environment will be used to locate the
         executables.
@@ -459,6 +459,13 @@
         <externalAnnotator language="JSON"
                            implementationClass="org.jboss.tools.intellij.componentanalysis.pnpm.PnpmCAAnnotator"/>
 
+        <localInspection language="JSON" shortName="YarnCAInspection"
+                         displayName="Red Hat Dependency Analytics component analysis"
+                         groupPath="JavaScript and TypeScript" groupName="Imports and dependencies"
+                         enabledByDefault="true" level="ERROR"
+                         implementationClass="org.jboss.tools.intellij.componentanalysis.yarn.YarnCAInspection"/>
+        <externalAnnotator language="JSON"
+                           implementationClass="org.jboss.tools.intellij.componentanalysis.yarn.YarnCAAnnotator"/>
         <fileType name="rhda-requirements"
                   language="rhda-requirements"
                   fileNames="requirements.txt"
diff --git a/src/main/resources/inspectionDescriptions/YarnCAInspection.html b/src/main/resources/inspectionDescriptions/YarnCAInspection.html
new file mode 100644
index 00000000..d9ffb4c2
--- /dev/null
+++ b/src/main/resources/inspectionDescriptions/YarnCAInspection.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en" xml:lang="en">
+<head>
+    <meta charset="UTF-8" />
+    <title>Yarn Dependency Vulnerability Inspection</title>
+</head>
+<body>
+<p>
+    Reports dependency vulnerabilities found in <code>package.json</code> files managed by <strong>pnpm</strong>.
+</p>
+</body>
+</html>
\ No newline at end of file


Reviews:
user: login: ruromero
body: The PR is correctly implemented. I added 2 small suggestions that don't affect the functionality.
One thing that would be beneficial is also the addition of some unit testing for the new logic added to the Annotaror.

Finally, I see that there is too much repeated code when handling PsiFiles. We can consider creating a follow up task to create a utilities class and do a small refactor.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-21T10:40:52Z
user: login: soul2zimate
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T03:09:03Z
user: login: soul2zimate
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T03:15:53Z
user: login: ruromero
body: Looks great!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-22T07:00:58Z

Review comments:
diff_hunk: @@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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.yarn;
+
+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;
+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.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 YarnCAAnnotator extends CAAnnotator {
+
+    @Override
+    protected String getInspectionShortName() {
+        return YarnCAInspection.SHORT_NAME;
+    }
+
+    @Override
+    protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
path: src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAAnnotator.java
user: login: ruromero
body: This method seems a bit hard to follow. What about creating helper methods to make it more readable and maintainable?
Some suggestions:
```
private Set<String> getIgnoredDependencies(PsiFile file) {
 ...
}

private Map<Dependency, List<PsiElement> collectDependencies(PsiFile file, Set<String> ignored) {...}
```
created_at: 2025-05-21T10:38:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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.yarn;
+
+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;
+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.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 YarnCAAnnotator extends CAAnnotator {
+
+    @Override
+    protected String getInspectionShortName() {
+        return YarnCAInspection.SHORT_NAME;
+    }
+
+    @Override
+    protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
+        if ("package.json".equals(file.getName())) {
path: src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAAnnotator.java
user: login: ruromero
body: Maybe we could create a constant for `package.json` as it is repeated in too many places and can introduce bugs while refactoring.
created_at: 2025-05-21T10:38:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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.yarn;
+
+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;
+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.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 YarnCAAnnotator extends CAAnnotator {
+
+    @Override
+    protected String getInspectionShortName() {
+        return YarnCAInspection.SHORT_NAME;
+    }
+
+    @Override
+    protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
+        if ("package.json".equals(file.getName())) {
path: src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAAnnotator.java
user: login: soul2zimate
body: updated, use some constant instead. Thanks.
created_at: 2025-05-22T03:09:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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.yarn;
+
+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;
+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.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 YarnCAAnnotator extends CAAnnotator {
+
+    @Override
+    protected String getInspectionShortName() {
+        return YarnCAInspection.SHORT_NAME;
+    }
+
+    @Override
+    protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file) {
path: src/main/java/org/jboss/tools/intellij/componentanalysis/yarn/YarnCAAnnotator.java
user: login: soul2zimate
body: Fixed. Moved common codes to `CAUtil` and refactor methods by functionality. 
created_at: 2025-05-22T03:15:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: soul2zimate
created_at: 2025-05-21T08:26:34Z
body: @ruromero similar stuff for yarn, if you can give a quick review 😄  Thanks.
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-22T03:17:21Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=redhat-developer_intellij-dependency-analytics&pullRequest=181) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=redhat-developer_intellij-dependency-analytics&pullRequest=181&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=redhat-developer_intellij-dependency-analytics&pullRequest=181&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=redhat-developer_intellij-dependency-analytics&pullRequest=181&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=redhat-developer_intellij-dependency-analytics&pullRequest=181&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=redhat-developer_intellij-dependency-analytics&pullRequest=181&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=redhat-developer_intellij-dependency-analytics&pullRequest=181)


author_association: NONE
user: login: soul2zimate
created_at: 2025-05-22T03:29:36Z
body: rebased as per last review comment. thanks.
author_association: CONTRIBUTOR