2019-07-25 06:17:19 +00:00
|
|
|
import { DEFAULT_MAVEN_REPO } from '../maven/extract';
|
|
|
|
import { PackageFile, PackageDependency } from '../common';
|
2020-01-15 06:24:57 +00:00
|
|
|
import { get } from '../../versioning';
|
2020-02-18 07:34:10 +00:00
|
|
|
import * as mavenVersioning from '../../versioning/maven';
|
2020-03-01 07:01:12 +00:00
|
|
|
import * as datasourceSbtPackage from '../../datasource/sbt-package';
|
|
|
|
import * as datasourceSbtPlugin from '../../datasource/sbt-plugin';
|
2019-05-01 06:40:35 +00:00
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isComment = (str: string): boolean => /^\s*\/\//.test(str);
|
2019-05-01 06:40:35 +00:00
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isSingleLineDep = (str: string): boolean =>
|
2019-05-01 06:40:35 +00:00
|
|
|
/^\s*(libraryDependencies|dependencyOverrides)\s*\+=\s*/.test(str);
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isDepsBegin = (str: string): boolean =>
|
2019-05-01 06:40:35 +00:00
|
|
|
/^\s*(libraryDependencies|dependencyOverrides)\s*\+\+=\s*/.test(str);
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isPluginDep = (str: string): boolean =>
|
|
|
|
/^\s*addSbtPlugin\s*\(.*\)\s*$/.test(str);
|
2019-05-01 06:40:35 +00:00
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isStringLiteral = (str: string): boolean => /^"[^"]*"$/.test(str);
|
2019-05-01 06:40:35 +00:00
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isScalaVersion = (str: string): boolean =>
|
2019-07-25 06:17:19 +00:00
|
|
|
/^\s*scalaVersion\s*:=\s*"[^"]*"\s*$/.test(str);
|
2019-11-23 20:44:55 +00:00
|
|
|
|
|
|
|
const getScalaVersion = (str: string): string =>
|
2019-05-01 06:40:35 +00:00
|
|
|
str.replace(/^\s*scalaVersion\s*:=\s*"/, '').replace(/"\s*$/, '');
|
|
|
|
|
2020-01-15 06:24:57 +00:00
|
|
|
/*
|
|
|
|
https://www.scala-sbt.org/release/docs/Cross-Build.html#Publishing+conventions
|
|
|
|
*/
|
|
|
|
const normalizeScalaVersion = (str: string): string => {
|
|
|
|
// istanbul ignore if
|
|
|
|
if (!str) return str;
|
2020-02-18 07:34:10 +00:00
|
|
|
const versioning = get(mavenVersioning.id);
|
2020-01-15 06:24:57 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isScalaVersionVariable = (str: string): boolean =>
|
2019-07-27 15:19:38 +00:00
|
|
|
/^\s*scalaVersion\s*:=\s*[_a-zA-Z][_a-zA-Z0-9]*\s*$/.test(str);
|
2019-11-23 20:44:55 +00:00
|
|
|
|
|
|
|
const getScalaVersionVariable = (str: string): string =>
|
2019-07-27 15:19:38 +00:00
|
|
|
str.replace(/^\s*scalaVersion\s*:=\s*/, '').replace(/\s*$/, '');
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isResolver = (str: string): boolean =>
|
2019-05-01 06:40:35 +00:00
|
|
|
/^\s*(resolvers\s*\+\+?=\s*(Seq\()?)?"[^"]*"\s*at\s*"[^"]*"[\s,)]*$/.test(
|
|
|
|
str
|
|
|
|
);
|
2019-11-23 20:44:55 +00:00
|
|
|
const getResolverUrl = (str: string): string =>
|
2019-05-01 06:40:35 +00:00
|
|
|
str
|
|
|
|
.replace(/^\s*(resolvers\s*\+\+?=\s*(Seq\()?)?"[^"]*"\s*at\s*"/, '')
|
|
|
|
.replace(/"[\s,)]*$/, '');
|
|
|
|
|
2020-01-14 11:45:17 +00:00
|
|
|
const isVarDependency = (str: string): boolean =>
|
|
|
|
/^\s*(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=.*(%%?).*%.*/.test(str);
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isVarDef = (str: string): boolean =>
|
2020-01-14 11:45:17 +00:00
|
|
|
/^\s*(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"[^"]*"\s*$/.test(str);
|
2019-11-23 20:44:55 +00:00
|
|
|
|
|
|
|
const getVarName = (str: string): string =>
|
2020-01-14 11:45:17 +00:00
|
|
|
str.replace(/^\s*(lazy\s*)?val\s+/, '').replace(/\s*=\s*"[^"]*"\s*$/, '');
|
2019-11-23 20:44:55 +00:00
|
|
|
|
|
|
|
const isVarName = (str: string): boolean =>
|
|
|
|
/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(str);
|
|
|
|
|
|
|
|
const getVarInfo = (
|
|
|
|
str: string,
|
|
|
|
ctx: ParseContext
|
|
|
|
): { val: string; fileReplacePosition: number } => {
|
2019-05-01 06:40:35 +00:00
|
|
|
const { fileOffset } = ctx;
|
2020-01-14 11:45:17 +00:00
|
|
|
const rightPart = str.replace(
|
|
|
|
/^\s*(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"/,
|
|
|
|
''
|
|
|
|
);
|
2020-01-01 17:09:43 +00:00
|
|
|
const fileReplacePosition = str.indexOf(rightPart) + fileOffset;
|
2019-05-01 06:40:35 +00:00
|
|
|
const val = rightPart.replace(/"\s*$/, '');
|
|
|
|
return { val, fileReplacePosition };
|
|
|
|
};
|
|
|
|
|
2019-07-25 06:17:19 +00:00
|
|
|
interface ParseContext {
|
|
|
|
fileOffset: number;
|
|
|
|
scalaVersion: string;
|
|
|
|
variables: any;
|
|
|
|
depType?: string;
|
|
|
|
}
|
|
|
|
|
2019-08-22 15:42:35 +00:00
|
|
|
function parseDepExpr(
|
|
|
|
expr: string,
|
|
|
|
ctx: ParseContext
|
|
|
|
): PackageDependency | null {
|
2019-05-01 06:40:35 +00:00
|
|
|
const { scalaVersion, fileOffset, variables } = ctx;
|
|
|
|
let { depType } = ctx;
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const isValidToken = (str: string): boolean =>
|
2019-05-01 06:40:35 +00:00
|
|
|
isStringLiteral(str) || (isVarName(str) && !!variables[str]);
|
|
|
|
|
2019-11-23 20:44:55 +00:00
|
|
|
const resolveToken = (str: string): string =>
|
2019-05-01 06:40:35 +00:00
|
|
|
isStringLiteral(str)
|
|
|
|
? str.replace(/^"/, '').replace(/"$/, '')
|
|
|
|
: variables[str].val;
|
|
|
|
|
|
|
|
const tokens = expr.trim().split(/\s*(%%?)\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);
|
2019-05-30 23:39:07 +00:00
|
|
|
const artifactId =
|
|
|
|
groupOp === '%%' && scalaVersion
|
|
|
|
? `${resolveToken(rawArtifactId)}_${scalaVersion}`
|
|
|
|
: resolveToken(rawArtifactId);
|
2019-05-01 06:40:35 +00:00
|
|
|
const depName = `${groupId}:${artifactId}`;
|
|
|
|
const currentValue = resolveToken(rawVersion);
|
|
|
|
|
|
|
|
if (!depType && rawScope) {
|
|
|
|
depType = rawScope.replace(/^"/, '').replace(/"$/, '');
|
|
|
|
}
|
|
|
|
|
2019-07-25 06:17:19 +00:00
|
|
|
let fileReplacePosition: number;
|
2019-05-01 06:40:35 +00:00
|
|
|
if (isStringLiteral(rawVersion)) {
|
|
|
|
// Calculate fileReplacePosition incrementally
|
|
|
|
// help us to avoid errors in updating phase.
|
|
|
|
fileReplacePosition = 0;
|
|
|
|
fileReplacePosition +=
|
2020-01-01 17:09:43 +00:00
|
|
|
expr.slice(fileReplacePosition).indexOf(rawGroupId) + rawGroupId.length;
|
2019-05-01 06:40:35 +00:00
|
|
|
fileReplacePosition +=
|
2020-01-01 17:09:43 +00:00
|
|
|
expr.slice(fileReplacePosition).indexOf(rawArtifactId) +
|
2019-05-01 06:40:35 +00:00
|
|
|
rawArtifactId.length;
|
2020-01-01 17:09:43 +00:00
|
|
|
fileReplacePosition += expr
|
|
|
|
.slice(fileReplacePosition)
|
|
|
|
.indexOf(currentValue);
|
2019-05-01 06:40:35 +00:00
|
|
|
fileReplacePosition += fileOffset;
|
|
|
|
} else {
|
|
|
|
fileReplacePosition = variables[rawVersion].fileReplacePosition;
|
|
|
|
}
|
|
|
|
|
2019-07-25 06:17:19 +00:00
|
|
|
const result: PackageDependency = {
|
2019-05-01 06:40:35 +00:00
|
|
|
depName,
|
|
|
|
currentValue,
|
|
|
|
fileReplacePosition,
|
|
|
|
};
|
|
|
|
|
2019-05-30 23:39:07 +00:00
|
|
|
if (depType) {
|
|
|
|
result.depType = depType;
|
|
|
|
}
|
2019-05-01 06:40:35 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2019-07-25 06:17:19 +00:00
|
|
|
interface ParseOptions {
|
|
|
|
fileOffset?: number;
|
|
|
|
isMultiDeps?: boolean;
|
|
|
|
scalaVersion?: string;
|
|
|
|
variables?: Record<string, any>;
|
|
|
|
}
|
2019-05-01 06:40:35 +00:00
|
|
|
|
2019-07-25 06:17:19 +00:00
|
|
|
function parseSbtLine(
|
|
|
|
acc: PackageFile & ParseOptions,
|
|
|
|
line: string,
|
|
|
|
lineIndex: number,
|
|
|
|
lines: string[]
|
2019-08-22 15:42:35 +00:00
|
|
|
): (PackageFile & ParseOptions) | null {
|
2019-05-01 06:40:35 +00:00
|
|
|
const { deps, registryUrls, fileOffset, variables } = acc;
|
|
|
|
|
|
|
|
let { isMultiDeps, scalaVersion } = acc;
|
|
|
|
|
2019-07-25 06:17:19 +00:00
|
|
|
const ctx: ParseContext = {
|
2019-05-01 06:40:35 +00:00
|
|
|
scalaVersion,
|
|
|
|
fileOffset,
|
|
|
|
variables,
|
|
|
|
};
|
|
|
|
|
2019-07-25 06:17:19 +00:00
|
|
|
let dep: PackageDependency = null;
|
2019-07-27 15:19:38 +00:00
|
|
|
let scalaVersionVariable: string = null;
|
2019-05-01 06:40:35 +00:00
|
|
|
if (!isComment(line)) {
|
|
|
|
if (isScalaVersion(line)) {
|
|
|
|
isMultiDeps = false;
|
2020-01-15 06:24:57 +00:00
|
|
|
scalaVersion = normalizeScalaVersion(getScalaVersion(line));
|
2019-07-27 15:19:38 +00:00
|
|
|
} else if (isScalaVersionVariable(line)) {
|
|
|
|
isMultiDeps = false;
|
|
|
|
scalaVersionVariable = getScalaVersionVariable(line);
|
2019-05-01 06:40:35 +00:00
|
|
|
} else if (isResolver(line)) {
|
|
|
|
isMultiDeps = false;
|
|
|
|
const url = getResolverUrl(line);
|
|
|
|
registryUrls.push(url);
|
|
|
|
} else if (isVarDef(line)) {
|
|
|
|
variables[getVarName(line)] = getVarInfo(line, ctx);
|
2020-01-14 11:45:17 +00:00
|
|
|
} else if (isVarDependency(line)) {
|
|
|
|
isMultiDeps = false;
|
|
|
|
const depExpr = line.replace(
|
|
|
|
/^\s*(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*/,
|
|
|
|
''
|
|
|
|
);
|
|
|
|
const expOffset = line.length - depExpr.length;
|
|
|
|
dep = parseDepExpr(depExpr, {
|
|
|
|
...ctx,
|
|
|
|
fileOffset: fileOffset + expOffset,
|
|
|
|
});
|
2019-05-01 06:40:35 +00:00
|
|
|
} else if (isSingleLineDep(line)) {
|
|
|
|
isMultiDeps = false;
|
|
|
|
const depExpr = line.replace(/^.*\+=\s*/, '');
|
|
|
|
const expOffset = line.length - depExpr.length;
|
|
|
|
dep = parseDepExpr(depExpr, {
|
|
|
|
...ctx,
|
|
|
|
fileOffset: fileOffset + expOffset,
|
|
|
|
});
|
|
|
|
} else if (isPluginDep(line)) {
|
|
|
|
isMultiDeps = false;
|
|
|
|
const rightPart = line.replace(/^\s*addSbtPlugin\s*\(/, '');
|
|
|
|
const expOffset = line.length - rightPart.length;
|
|
|
|
const depExpr = rightPart.replace(/\)\s*$/, '');
|
|
|
|
dep = parseDepExpr(depExpr, {
|
|
|
|
...ctx,
|
2019-05-30 23:39:07 +00:00
|
|
|
depType: 'plugin',
|
2019-05-01 06:40:35 +00:00
|
|
|
fileOffset: fileOffset + expOffset,
|
|
|
|
});
|
|
|
|
} else if (isDepsBegin(line)) {
|
|
|
|
isMultiDeps = true;
|
|
|
|
} else if (isMultiDeps) {
|
|
|
|
const rightPart = line.replace(/^[\s,]*/, '');
|
|
|
|
const expOffset = line.length - rightPart.length;
|
|
|
|
const depExpr = rightPart.replace(/[\s,]*$/, '');
|
|
|
|
dep = parseDepExpr(depExpr, {
|
|
|
|
...ctx,
|
|
|
|
fileOffset: fileOffset + expOffset,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-28 07:49:51 +00:00
|
|
|
if (dep) {
|
|
|
|
if (dep.depType === 'plugin') {
|
2020-03-01 07:01:12 +00:00
|
|
|
dep.datasource = datasourceSbtPlugin.id;
|
2020-02-28 07:49:51 +00:00
|
|
|
} else {
|
2020-03-01 07:01:12 +00:00
|
|
|
dep.datasource = datasourceSbtPackage.id;
|
2020-02-28 07:49:51 +00:00
|
|
|
}
|
2019-05-01 06:40:35 +00:00
|
|
|
deps.push({
|
2020-02-05 18:17:20 +00:00
|
|
|
registryUrls,
|
2019-05-01 06:40:35 +00:00
|
|
|
...dep,
|
|
|
|
});
|
2020-02-28 07:49:51 +00:00
|
|
|
}
|
2019-05-01 06:40:35 +00:00
|
|
|
|
|
|
|
if (lineIndex + 1 < lines.length)
|
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
fileOffset: fileOffset + line.length + 1, // inc. newline
|
|
|
|
isMultiDeps,
|
2019-07-27 15:19:38 +00:00
|
|
|
scalaVersion:
|
|
|
|
scalaVersion ||
|
2019-07-29 05:51:48 +00:00
|
|
|
(scalaVersionVariable &&
|
|
|
|
variables[scalaVersionVariable] &&
|
2020-01-15 06:24:57 +00:00
|
|
|
normalizeScalaVersion(variables[scalaVersionVariable].val)),
|
2019-05-01 06:40:35 +00:00
|
|
|
};
|
|
|
|
if (deps.length) return { deps };
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-07-25 06:17:19 +00:00
|
|
|
export function extractPackageFile(content: string): PackageFile {
|
2019-05-01 06:40:35 +00:00
|
|
|
if (!content) return null;
|
|
|
|
const lines = content.split(/\n/);
|
|
|
|
return lines.reduce(parseSbtLine, {
|
|
|
|
fileOffset: 0,
|
|
|
|
registryUrls: [DEFAULT_MAVEN_REPO],
|
|
|
|
deps: [],
|
|
|
|
isMultiDeps: false,
|
|
|
|
scalaVersion: null,
|
|
|
|
variables: {},
|
|
|
|
});
|
|
|
|
}
|