renovate/lib/manager/sbt/extract.ts

361 lines
9.8 KiB
TypeScript
Raw Normal View History

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 { regEx } from '../../util/regex';
2020-05-01 16:03:48 +00:00
import { get } from '../../versioning';
import * as mavenVersioning from '../../versioning/maven';
2021-03-02 20:44:55 +00:00
import type { PackageDependency, PackageFile } from '../types';
2021-05-11 17:08:02 +00:00
import type { ParseContext, ParseOptions } from './types';
2019-05-01 06:40:35 +00:00
const stripComment = (str: string): string =>
str.replace(regEx(/(^|\s+)\/\/.*$/), '');
2019-05-01 06:40:35 +00:00
const isSingleLineDep = (str: string): boolean =>
regEx(/^\s*(libraryDependencies|dependencyOverrides)\s*\+=\s*/).test(str);
2019-05-01 06:40:35 +00:00
const isDepsBegin = (str: string): boolean =>
regEx(/^\s*(libraryDependencies|dependencyOverrides)\s*\+\+=\s*/).test(str);
2019-05-01 06:40:35 +00:00
const isPluginDep = (str: string): boolean =>
regEx(/^\s*addSbtPlugin\s*\(.*\)\s*$/).test(str);
2019-05-01 06:40:35 +00:00
const isStringLiteral = (str: string): boolean => regEx(/^"[^"]*"$/).test(str);
2019-05-01 06:40:35 +00:00
const isScalaVersion = (str: string): boolean =>
regEx(/^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*"[^"]*"[\s,]*$/).test(
str
);
const getScalaVersion = (str: string): string =>
str
.replace(regEx(/^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*"/), '')
.replace(regEx(/"[\s,]*$/), '');
2019-05-01 06:40:35 +00:00
const isPackageFileVersion = (str: string): boolean =>
regEx(/^(version\s*:=\s*).*$/).test(str);
const getPackageFileVersion = (str: string): string =>
str
.replace(regEx(/^\s*version\s*:=\s*/), '')
.replace(regEx(/[\s,]*$/), '')
.replace(regEx(/"/g), '');
/*
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 (regEx(/^\d+\.\d+\.\d+$/).test(str)) {
return str.replace(regEx(/^(\d+)\.(\d+)\.\d+$/), '$1.$2');
}
// istanbul ignore next
return str;
};
const isScalaVersionVariable = (str: string): boolean =>
regEx(
/^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*[_a-zA-Z][_a-zA-Z0-9]*[\s,]*$/
).test(str);
const getScalaVersionVariable = (str: string): string =>
str
.replace(regEx(/^\s*(?:ThisBuild\s*\/\s*)?scalaVersion\s*:=\s*/), '')
.replace(regEx(/[\s,]*$/), '');
const isResolver = (str: string): boolean =>
regEx(
/^\s*(resolvers\s*\+\+?=\s*((Seq|List|Stream)\()?)?"[^"]*"\s*at\s*"[^"]*"[\s,)]*$/
).test(str);
const getResolverUrl = (str: string): string =>
2019-05-01 06:40:35 +00:00
str
.replace(
regEx(
/^\s*(resolvers\s*\+\+?=\s*((Seq|List|Stream)\()?)?"[^"]*"\s*at\s*"/
),
''
)
.replace(regEx(/"[\s,)]*$/), '');
2019-05-01 06:40:35 +00:00
const isVarDependency = (str: string): boolean =>
regEx(
/^\s*(private\s*)?(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=.*(%%?).*%.*/
).test(str);
const isVarDef = (str: string): boolean =>
regEx(
/^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"[^"]*"\s*$/
).test(str);
const isVarSeqSingleLine = (str: string): boolean =>
regEx(
/^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*(Seq|List|Stream)\(.*\).*\s*$/
).test(str);
const isVarSeqMultipleLine = (str: string): boolean =>
regEx(
/^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*(Seq|List|Stream)\(.*[^)]*.*$/
).test(str);
const getVarName = (str: string): string =>
str
.replace(regEx(/^\s*(private\s*)?(lazy\s*)?val\s+/), '')
.replace(regEx(/\s*=\s*"[^"]*"\s*$/), '');
const isVarName = (str: string): boolean =>
regEx(/^[_a-zA-Z][_a-zA-Z0-9]*$/).test(str);
2020-04-08 08:56:37 +00:00
const getVarInfo = (str: string, ctx: ParseContext): { val: string } => {
const rightPart = str.replace(
regEx(/^\s*(private\s*)?(lazy\s*)?val\s+[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*"/),
''
);
const val = rightPart.replace(regEx(/"\s*$/), '');
2020-04-08 08:56:37 +00:00
return { val };
2019-05-01 06:40:35 +00:00
};
function parseDepExpr(
expr: string,
ctx: ParseContext
): PackageDependency | null {
2020-04-08 08:56:37 +00:00
const { scalaVersion, variables } = ctx;
2019-05-01 06:40:35 +00:00
let { depType } = ctx;
const isValidToken = (str: string): boolean =>
2019-05-01 06:40:35 +00:00
isStringLiteral(str) || (isVarName(str) && !!variables[str]);
const resolveToken = (str: string): string =>
2019-05-01 06:40:35 +00:00
isStringLiteral(str)
? str.replace(regEx(/^"/), '').replace(regEx(/"$/), '')
2019-05-01 06:40:35 +00:00
: variables[str].val;
const tokens = expr
.trim()
.split(regEx(/("[^"]*")/g))
.map((x) => (regEx(/"[^"]*"/).test(x) ? x : x.replace(regEx(/[()]+/g), '')))
.join('')
.split(regEx(/\s*(%%?)\s*|\s*classifier\s*/));
2019-05-01 06:40:35 +00:00
const [
rawGroupId,
groupOp,
rawArtifactId,
artifactOp,
rawVersion,
scopeOp,
rawScope,
] = tokens;
if (!rawGroupId) {
return null;
}
if (!isValidToken(rawGroupId)) {
return null;
}
2019-05-01 06:40:35 +00:00
if (!rawArtifactId) {
return null;
}
if (!isValidToken(rawArtifactId)) {
return null;
}
if (artifactOp !== '%') {
return null;
}
2019-05-01 06:40:35 +00:00
if (!rawVersion) {
return null;
}
if (!isValidToken(rawVersion)) {
return null;
}
2019-05-01 06:40:35 +00:00
if (scopeOp && scopeOp !== '%') {
return null;
}
2019-05-01 06:40:35 +00:00
const groupId = resolveToken(rawGroupId);
const depName = `${groupId}:${resolveToken(rawArtifactId)}`;
const artifactId =
groupOp === '%%' && scalaVersion
? `${resolveToken(rawArtifactId)}_${scalaVersion}`
: resolveToken(rawArtifactId);
const lookupName = `${groupId}:${artifactId}`;
2019-05-01 06:40:35 +00:00
const currentValue = resolveToken(rawVersion);
if (!depType && rawScope) {
depType = rawScope.replace(regEx(/^"/), '').replace(regEx(/"$/), '');
2019-05-01 06:40:35 +00:00
}
const result: PackageDependency = {
2019-05-01 06:40:35 +00:00
depName,
lookupName,
2019-05-01 06:40:35 +00:00
currentValue,
};
if (variables[rawVersion]) {
result.groupName = `${rawVersion} for ${groupId}`;
}
if (depType) {
result.depType = depType;
}
2019-05-01 06:40:35 +00:00
return result;
}
function parseSbtLine(
acc: PackageFile & ParseOptions,
line: string,
lineIndex: number,
lines: string[]
): (PackageFile & ParseOptions) | null {
2020-04-08 08:56:37 +00:00
const { deps, registryUrls, variables } = acc;
2019-05-01 06:40:35 +00:00
let { isMultiDeps, scalaVersion, packageFileVersion } = acc;
2019-05-01 06:40:35 +00:00
const ctx: ParseContext = {
2019-05-01 06:40:35 +00:00
scalaVersion,
variables,
};
let dep: PackageDependency = null;
let scalaVersionVariable: string = null;
if (line !== '') {
2019-05-01 06:40:35 +00:00
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 (isPackageFileVersion(line)) {
packageFileVersion = getPackageFileVersion(line);
2019-05-01 06:40:35 +00:00
} else if (isResolver(line)) {
isMultiDeps = false;
const url = getResolverUrl(line);
registryUrls.push(url);
} else if (isVarSeqSingleLine(line)) {
isMultiDeps = false;
const depExpr = line
.replace(regEx(/^.*(Seq|List|Stream)\(\s*/), '')
.replace(regEx(/\).*$/), '');
dep = parseDepExpr(depExpr, {
...ctx,
});
} else if (isVarSeqMultipleLine(line)) {
isMultiDeps = true;
const depExpr = line.replace(regEx(/^.*(Seq|List|Stream)\(\s*/), '');
dep = parseDepExpr(depExpr, {
...ctx,
});
2019-05-01 06:40:35 +00:00
} else if (isVarDef(line)) {
variables[getVarName(line)] = getVarInfo(line, ctx);
} else if (isVarDependency(line)) {
isMultiDeps = false;
const depExpr = line.replace(
regEx(/^\s*(private\s*)?(lazy\s*)?val\s[_a-zA-Z][_a-zA-Z0-9]*\s*=\s*/),
''
);
dep = parseDepExpr(depExpr, {
...ctx,
});
2019-05-01 06:40:35 +00:00
} else if (isSingleLineDep(line)) {
isMultiDeps = false;
const depExpr = line.replace(regEx(/^.*\+=\s*/), '');
2019-05-01 06:40:35 +00:00
dep = parseDepExpr(depExpr, {
...ctx,
});
} else if (isPluginDep(line)) {
isMultiDeps = false;
const rightPart = line.replace(regEx(/^\s*addSbtPlugin\s*\(/), '');
const depExpr = rightPart.replace(regEx(/\)\s*$/), '');
2019-05-01 06:40:35 +00:00
dep = parseDepExpr(depExpr, {
...ctx,
depType: 'plugin',
2019-05-01 06:40:35 +00:00
});
} else if (isDepsBegin(line)) {
isMultiDeps = true;
} else if (isMultiDeps) {
const rightPart = line.replace(regEx(/^[\s,]*/), '');
const depExpr = rightPart.replace(regEx(/[\s,]*$/), '');
2019-05-01 06:40:35 +00:00
dep = parseDepExpr(depExpr, {
...ctx,
});
}
}
2020-02-28 07:49:51 +00:00
if (dep) {
if (!dep.datasource) {
if (dep.depType === 'plugin') {
dep.datasource = datasourceSbtPlugin.id;
dep.registryUrls = [
...registryUrls,
...datasourceSbtPlugin.defaultRegistryUrls,
];
} else {
dep.datasource = datasourceSbtPackage.id;
}
2020-02-28 07:49:51 +00:00
}
2019-05-01 06:40:35 +00:00
deps.push({
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) {
2019-05-01 06:40:35 +00:00
return {
...acc,
isMultiDeps,
scalaVersion:
scalaVersion ||
2019-07-29 05:51:48 +00:00
(scalaVersionVariable &&
variables[scalaVersionVariable] &&
normalizeScalaVersion(variables[scalaVersionVariable].val)),
packageFileVersion,
2019-05-01 06:40:35 +00:00
};
}
if (deps.length) {
return {
deps,
packageFileVersion,
};
}
2019-05-01 06:40:35 +00:00
return null;
}
export function extractPackageFile(content: string): PackageFile {
if (!content) {
return null;
}
const lines = content.split(regEx(/\n/)).map(stripComment);
2019-05-01 06:40:35 +00:00
return lines.reduce(parseSbtLine, {
registryUrls: [MAVEN_REPO],
2019-05-01 06:40:35 +00:00
deps: [],
isMultiDeps: false,
scalaVersion: null,
variables: {},
});
}