Skip to content
Snippets Groups Projects
Select Git revision
  • a8fdb4e38cdcd7f4b24a3f9b722d3fc5bf3424d2
  • main default protected
  • next
  • renovate/main-redis-5.x
  • fix/36615b-branch-reuse-no-cache
  • chore/punycode
  • fix/36615-branch-reuse-bug
  • refactor/pin-new-value
  • feat/36219--git-x509-signing
  • feat/structured-logger
  • hotfix/39.264.1
  • feat/skip-dangling
  • gh-readonly-queue/next/pr-36034-7a061c4ca1024a19e2c295d773d9642625d1c2be
  • hotfix/39.238.3
  • refactor/gitlab-auto-approve
  • feat/template-strings
  • gh-readonly-queue/next/pr-35654-137d934242c784e0c45d4b957362214f0eade1d7
  • fix/32307-global-extends-merging
  • fix/32307-global-extends-repositories
  • gh-readonly-queue/next/pr-35009-046ebf7cb84ab859f7fefceb5fa53a54ce9736f8
  • gh-readonly-queue/next/pr-35009-9d5e583b7d7251148ab0d11ee8dd38149618d162
  • 41.17.2
  • 41.17.1
  • 41.17.0
  • 41.16.3
  • 41.16.2
  • 41.16.1
  • 41.16.0
  • 41.15.0
  • 41.14.0
  • 41.13.1
  • 41.13.0
  • 41.12.1
  • 41.12.0
  • 41.11.1
  • 41.11.0
  • 41.10.1
  • 41.10.0
  • 41.9.0
  • 41.8.0
  • 41.7.2
41 results

extract.ts

Blame
  • extract.ts 7.90 KiB
    import * as datasourceMaven from '../../datasource/maven';
    import { MAVEN_REPO } from '../../datasource/maven/common';
    import * as datasourceSbtPackage from '../../datasource/sbt-package';
    import * as datasourceSbtPlugin from '../../datasource/sbt-plugin';
    import { get } from '../../versioning';
    import * as mavenVersioning from '../../versioning/maven';
    import { PackageDependency, PackageFile } from '../common';
    
    const stripComment = (str: string): string => str.replace(/(^|\s+)\/\/.*$/, '');
    
    const isSingleLineDep = (str: string): boolean =>
      /^\s*(libraryDependencies|dependencyOverrides)\s*\+=\s*/.test(str);
    
    const isDepsBegin = (str: string): boolean =>
      /^\s*(libraryDependencies|dependencyOverrides)\s*\+\+=\s*/.test(str);
    
    const isPluginDep = (str: string): boolean =>
      /^\s*addSbtPlugin\s*\(.*\)\s*$/.test(str);
    
    const isStringLiteral = (str: string): boolean => /^"[^"]*"$/.test(str);
    
    const isScalaVersion = (str: string): boolean =>
      /^\s*scalaVersion\s*:=\s*"[^"]*"[\s,]*$/.test(str);
    
    const getScalaVersion = (str: string): string =>
      str.replace(/^\s*scalaVersion\s*:=\s*"/, '').replace(/"[\s,]*$/, '');
    
    /*
      https://www.scala-sbt.org/release/docs/Cross-Build.html#Publishing+conventions
     */
    const normalizeScalaVersion = (str: string): string => {
      // istanbul ignore if
      if (!str) {
        return str;
      }
      const versioning = get(mavenVersioning.id);
      if (versioning.isVersion(str)) {
        // Do not normalize unstable versions
        if (!versioning.isStable(str)) {
          return str;
        }
        // Do not normalize versions prior to 2.10
        if (!versioning.isGreaterThan(str, '2.10.0')) {
          return str;
        }
      }
      if (/^\d+\.\d+\.\d+$/.test(str)) {
        return str.replace(/^(\d+)\.(\d+)\.\d+$/, '$1.$2');
      }
      // istanbul ignore next
      return str;
    };
    
    const isScalaVersionVariable = (str: string): boolean =>
      /^\s*scalaVersion\s*:=\s*[_a-zA-Z][_a-zA-Z0-9]*[\s,]*$/.test(str);
    
    const getScalaVersionVariable = (str: string): string =>
      str.replace(/^\s*scalaVersion\s*:=\s*/, '').replace(/[\s,]*$/, '');
    
    const isResolver = (str: string): boolean =>
      /^\s*(resolvers\s*\+\+?=\s*(Seq\()?)?"[^"]*"\s*at\s*"[^"]*"[\s,)]*$/.test(
        str
      );
    const getResolverUrl = (str: string): string =>
      str
        .replace(/^\s*(resolvers\s*\+\+?=\s*(Seq\()?)?"[^"]*"\s*at\s*"/, '')
        .replace(/"[\s,)]*$/, '');
    
    const isVarDependency = (str: string): boolean =>
      /^\s*(private\s*)?(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=.*(%%?).*%.*/.test(
        str
      );
    
    const isVarDef = (str: string): boolean =>
      /^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"[^"]*"\s*$/.test(
        str
      );
    
    const getVarName = (str: string): string =>
      str
        .replace(/^\s*(private\s*)?(lazy\s*)?val\s+/, '')
        .replace(/\s*=\s*"[^"]*"\s*$/, '');
    
    const isVarName = (str: string): boolean =>
      /^[_a-zA-Z][_a-zA-Z0-9]*$/.test(str);
    
    const getVarInfo = (str: string, ctx: ParseContext): { val: string } => {
      const rightPart = str.replace(
        /^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"/,
        ''
      );
      const val = rightPart.replace(/"\s*$/, '');
      return { val };
    };
    
    interface ParseContext {
      scalaVersion: string;
      variables: any;
      depType?: string;
    }
    
    function parseDepExpr(
      expr: string,
      ctx: ParseContext
    ): PackageDependency | null {
      const { scalaVersion, variables } = ctx;
      let { depType } = ctx;
    
      const isValidToken = (str: string): boolean =>
        isStringLiteral(str) || (isVarName(str) && !!variables[str]);
    
      const resolveToken = (str: string): string =>
        isStringLiteral(str)
          ? str.replace(/^"/, '').replace(/"$/, '')
          : variables[str].val;
    
      const tokens = expr
        .trim()
        .replace(/[()]/g, '')
        .split(/\s*(%%?)\s*|\s*classifier\s*/);
    
      const [
        rawGroupId,
        groupOp,
        rawArtifactId,
        artifactOp,
        rawVersion,
        scopeOp,
        rawScope,
      ] = tokens;
    
      if (!rawGroupId) {
        return null;
      }
      if (!isValidToken(rawGroupId)) {
        return null;
      }
    
      if (!rawArtifactId) {
        return null;
      }
      if (!isValidToken(rawArtifactId)) {
        return null;
      }
      if (artifactOp !== '%') {
        return null;
      }
    
      if (!rawVersion) {
        return null;
      }
      if (!isValidToken(rawVersion)) {
        return null;
      }
    
      if (scopeOp && scopeOp !== '%') {
        return null;
      }
    
      const groupId = resolveToken(rawGroupId);
      const depName = `${groupId}:${resolveToken(rawArtifactId)}`;
      const artifactId =
        groupOp === '%%' && scalaVersion
          ? `${resolveToken(rawArtifactId)}_${scalaVersion}`
          : resolveToken(rawArtifactId);
      const lookupName = `${groupId}:${artifactId}`;
      const currentValue = resolveToken(rawVersion);
    
      if (!depType && rawScope) {
        depType = rawScope.replace(/^"/, '').replace(/"$/, '');
      }
    
      const result: PackageDependency = {
        depName,
        lookupName,
        currentValue,
      };
    
      if (depType) {
        result.depType = depType;
      }
    
      return result;
    }
    
    interface ParseOptions {
      isMultiDeps?: boolean;
      scalaVersion?: string;
      variables?: Record<string, any>;
    }
    
    function parseSbtLine(
      acc: PackageFile & ParseOptions,
      line: string,
      lineIndex: number,
      lines: string[]
    ): (PackageFile & ParseOptions) | null {
      const { deps, registryUrls, variables } = acc;
    
      let { isMultiDeps, scalaVersion } = acc;
    
      const ctx: ParseContext = {
        scalaVersion,
        variables,
      };
    
      let dep: PackageDependency = null;
      let scalaVersionVariable: string = null;
      if (line !== '') {
        if (isScalaVersion(line)) {
          isMultiDeps = false;
          const rawScalaVersion = getScalaVersion(line);
          scalaVersion = normalizeScalaVersion(rawScalaVersion);
          dep = {
            datasource: datasourceMaven.id,
            depName: 'scala',
            lookupName: 'org.scala-lang:scala-library',
            currentValue: rawScalaVersion,
            separateMinorPatch: true,
          };
        } else if (isScalaVersionVariable(line)) {
          isMultiDeps = false;
          scalaVersionVariable = getScalaVersionVariable(line);
        } else if (isResolver(line)) {
          isMultiDeps = false;
          const url = getResolverUrl(line);
          registryUrls.push(url);
        } else if (isVarDef(line)) {
          variables[getVarName(line)] = getVarInfo(line, ctx);
        } else if (isVarDependency(line)) {
          isMultiDeps = false;
          const depExpr = line.replace(
            /^\s*(private\s*)?(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*/,
            ''
          );
          dep = parseDepExpr(depExpr, {
            ...ctx,
          });
        } else if (isSingleLineDep(line)) {
          isMultiDeps = false;
          const depExpr = line.replace(/^.*\+=\s*/, '');
          dep = parseDepExpr(depExpr, {
            ...ctx,
          });
        } else if (isPluginDep(line)) {
          isMultiDeps = false;
          const rightPart = line.replace(/^\s*addSbtPlugin\s*\(/, '');
          const depExpr = rightPart.replace(/\)\s*$/, '');
          dep = parseDepExpr(depExpr, {
            ...ctx,
            depType: 'plugin',
          });
        } else if (isDepsBegin(line)) {
          isMultiDeps = true;
        } else if (isMultiDeps) {
          const rightPart = line.replace(/^[\s,]*/, '');
          const depExpr = rightPart.replace(/[\s,]*$/, '');
          dep = parseDepExpr(depExpr, {
            ...ctx,
          });
        }
      }
    
      if (dep) {
        if (!dep.datasource) {
          if (dep.depType === 'plugin') {
            dep.datasource = datasourceSbtPlugin.id;
          } else {
            dep.datasource = datasourceSbtPackage.id;
          }
        }
        deps.push({
          registryUrls,
          ...dep,
        });
      }
    
      if (lineIndex + 1 < lines.length) {
        return {
          ...acc,
          isMultiDeps,
          scalaVersion:
            scalaVersion ||
            (scalaVersionVariable &&
              variables[scalaVersionVariable] &&
              normalizeScalaVersion(variables[scalaVersionVariable].val)),
        };
      }
      if (deps.length) {
        return { deps };
      }
      return null;
    }
    
    export function extractPackageFile(content: string): PackageFile {
      if (!content) {
        return null;
      }
      const lines = content.split(/\n/).map(stripComment);
      return lines.reduce(parseSbtLine, {
        registryUrls: [MAVEN_REPO],
        deps: [],
        isMultiDeps: false,
        scalaVersion: null,
        variables: {},
      });
    }