Skip to content
Snippets Groups Projects
extract.ts 3.91 KiB
import * as datasourceGitTags from '../../datasource/git-tags';
import * as datasourceGithubTags from '../../datasource/github-tags';
import * as datasourceGitlabTags from '../../datasource/gitlab-tags';
import * as datasourcePod from '../../datasource/pod';
import { logger } from '../../logger';
import { SkipReason } from '../../types';
import { getSiblingFileName, localPathExists } from '../../util/fs';
import type { PackageDependency, PackageFile } from '../types';
import type { ParsedLine } from './types';

const regexMappings = [
  /^\s*pod\s+(['"])(?<spec>[^'"/]+)(\/(?<subspec>[^'"]+))?\1/,
  /^\s*pod\s+(['"])[^'"]+\1\s*,\s*(['"])(?<currentValue>[^'"]+)\2\s*$/,
  /,\s*:git\s*=>\s*(['"])(?<git>[^'"]+)\1/,
  /,\s*:tag\s*=>\s*(['"])(?<tag>[^'"]+)\1/,
  /,\s*:path\s*=>\s*(['"])(?<path>[^'"]+)\1/,
  /^\s*source\s*(['"])(?<source>[^'"]+)\1/,
];

export function parseLine(line: string): ParsedLine {
  let result: ParsedLine = {};
  if (!line) {
    return result;
  }
  for (const regex of Object.values(regexMappings)) {
    const match = regex.exec(line.replace(/#.*$/, ''));
    if (match?.groups) {
      result = { ...result, ...match.groups };
    }
  }

  if (result.spec) {
    const depName = result.subspec
      ? `${result.spec}/${result.subspec}`
      : result.spec;
    const groupName = result.spec;
    if (depName) {
      result.depName = depName;
    }
    if (groupName) {
      result.groupName = groupName;
    }
    delete result.spec;
    delete result.subspec;
  }

  return result;
}

export function gitDep(parsedLine: ParsedLine): PackageDependency | null {
  const { depName, git, tag } = parsedLine;

  const platformMatch =
    /[@/](?<platform>github|gitlab)\.com[:/](?<account>[^/]+)\/(?<repo>[^/]+)/.exec(
      git
    );

  if (platformMatch) {
    const { account, repo, platform } = platformMatch?.groups || {};
    if (account && repo) {
      const datasource =
        platform === 'github'
          ? datasourceGithubTags.id
          : datasourceGitlabTags.id;
      return {
        datasource,
        depName,
        lookupName: `${account}/${repo.replace(/\.git$/, '')}`,
        currentValue: tag,
      };
    }
  }

  return {
    datasource: datasourceGitTags.id,
    depName,
    lookupName: git,
    currentValue: tag,
  };
}

export async function extractPackageFile(
  content: string,
  fileName: string
): Promise<PackageFile | null> {
  logger.trace('cocoapods.extractPackageFile()');
  const deps: PackageDependency[] = [];
  const lines: string[] = content.split('\n');

  const registryUrls: string[] = [];

  for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
    const line = lines[lineNumber];
    const parsedLine = parseLine(line);
    const {
      depName,
      groupName,
      currentValue,
      git,
      tag,
      path,
      source,
    }: ParsedLine = parsedLine;

    if (source) {
      registryUrls.push(source.replace(/\/*$/, ''));
    }

    if (depName) {
      const managerData = { lineNumber };
      let dep: PackageDependency = {
        depName,
        groupName,
        skipReason: SkipReason.UnknownVersion,
      };

      if (currentValue) {
        dep = {
          depName,
          groupName,
          datasource: datasourcePod.id,
          currentValue,
          managerData,
          registryUrls,
        };
      } else if (git) {
        if (tag) {
          dep = { ...gitDep(parsedLine), managerData };
        } else {
          dep = {
            depName,
            groupName,
            skipReason: SkipReason.GitDependency,
          };
        }
      } else if (path) {
        dep = {
          depName,
          groupName,
          skipReason: SkipReason.PathDependency,
        };
      }

      deps.push(dep);
    }
  }
  const res: PackageFile = { deps };
  const lockFile = getSiblingFileName(fileName, 'Podfile.lock');
  // istanbul ignore if
  if (await localPathExists(lockFile)) {
    res.lockFiles = [lockFile];
  }
  return res;
}