All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joern.jssrc2cpg.utils.PackageJsonParser.scala Maven / Gradle / Ivy

The newest version!
package io.joern.jssrc2cpg.utils

import io.shiftleft.utils.IOUtils
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory
import upickle.default.read

import java.nio.file.{Path, Paths}
import scala.collection.concurrent.TrieMap
import scala.jdk.CollectionConverters.*
import scala.util.{Failure, Success, Try}

object PackageJsonParser {
  private val logger = LoggerFactory.getLogger(PackageJsonParser.getClass)

  val PackageJsonFilename     = "package.json"
  val PackageJsonLockFilename = "package-lock.json"

  private val ProjectDependencies =
    Seq("dependencies", "devDependencies", "peerDependencies", "optionalDependencies")

  private val cachedDependencies: TrieMap[Path, Map[String, String]] = TrieMap.empty

  def isValidProjectPackageJson(packageJsonPath: Path): Boolean = {
    if (packageJsonPath.toString.endsWith(PackageJsonParser.PackageJsonFilename)) {
      val isNotEmpty = Try(IOUtils.readLinesInFile(packageJsonPath)) match {
        case Success(content) =>
          content.forall(l => StringUtils.isNotBlank(StringUtils.normalizeSpace(l)))
        case Failure(_) => false
      }
      isNotEmpty && dependencies(packageJsonPath).nonEmpty
    } else {
      false
    }
  }

  def dependencies(packageJsonPath: Path): Map[String, String] =
    cachedDependencies.getOrElseUpdate(
      packageJsonPath, {
        val depsPath     = packageJsonPath
        val lockDepsPath = packageJsonPath.resolveSibling(Paths.get(PackageJsonLockFilename))

        val lockDeps = Try {
          val content     = IOUtils.readEntireFile(lockDepsPath)
          val packageJson = read[ujson.Obj](content)

          var depToVersion = Map.empty[String, String]
          val dependencyIt = packageJson.value.get("dependencies").map(_.obj).getOrElse(Map.empty[String, ujson.Value])
          dependencyIt.foreach {
            case (depName, value @ ujson.Str(version)) =>
              depToVersion = depToVersion.updated(depName, version)
            case (depName, value @ ujson.Obj(obj)) =>
              obj.get("version").foreach { version =>
                depToVersion = depToVersion.updated(depName, version.str)
              }
            case (depName, value) =>
              logger.warn(s"Unexpected version structure for dependency $depName: ${value.getClass}")
          }
          depToVersion
        }.toOption

        // lazy val because we only evaluate this in case no package lock file is available.
        lazy val deps = Try {
          val content     = IOUtils.readEntireFile(depsPath)
          val packageJson = read[ujson.Obj](content)

          var depToVersion = Map.empty[String, String]
          ProjectDependencies
            .foreach { dependency =>
              val dependencyIt = packageJson.value.get(dependency).map(_.obj).getOrElse(Map.empty[String, ujson.Value])
              dependencyIt.foreach { case (key, value) =>
                depToVersion = depToVersion.updated(key, value.str)
              }
            }
          depToVersion
        }.toOption

        if (lockDeps.isDefined && lockDeps.get.nonEmpty) {
          logger.debug(s"Loaded dependencies from '$lockDepsPath'.")
          lockDeps.get
        } else {
          if (deps.isDefined && deps.get.nonEmpty) {
            logger.debug(s"Loaded dependencies from '$depsPath'.")
            deps.get
          } else {
            logger.debug(
              s"No project dependencies found in $PackageJsonFilename or $PackageJsonLockFilename at '${depsPath.getParent}'."
            )
            Map.empty
          }
        }
      }
    )

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy