mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +00:00
feat(gradle): gradle versioning scheme (#5789)
This commit is contained in:
parent
cb56b54351
commit
bb6ab0bed3
5 changed files with 784 additions and 3 deletions
|
@ -4,7 +4,7 @@ import { Stats } from 'fs';
|
|||
import upath from 'upath';
|
||||
import { exec, ExecOptions } from '../../util/exec';
|
||||
import { logger } from '../../logger';
|
||||
import * as mavenVersioning from '../../versioning/maven';
|
||||
import * as gradleVersioning from '../../versioning/gradle';
|
||||
import {
|
||||
ExtractConfig,
|
||||
PackageFile,
|
||||
|
@ -188,5 +188,5 @@ export const language = LANGUAGE_JAVA;
|
|||
export const defaultConfig = {
|
||||
fileMatch: ['\\.gradle(\\.kts)?$', '(^|/)gradle.properties$'],
|
||||
timeout: 600,
|
||||
versioning: mavenVersioning.id,
|
||||
versioning: gradleVersioning.id,
|
||||
};
|
||||
|
|
301
lib/versioning/gradle/compare.ts
Normal file
301
lib/versioning/gradle/compare.ts
Normal file
|
@ -0,0 +1,301 @@
|
|||
export enum TokenType {
|
||||
Number = 1,
|
||||
String,
|
||||
}
|
||||
|
||||
type Token = {
|
||||
type: TokenType;
|
||||
val: string | number;
|
||||
};
|
||||
|
||||
function iterateChars(str: string, cb: (p: string, n: string) => void): void {
|
||||
let prev = null;
|
||||
let next = null;
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
next = str.charAt(i);
|
||||
cb(prev, next);
|
||||
prev = next;
|
||||
}
|
||||
cb(prev, null);
|
||||
}
|
||||
|
||||
function isSeparator(char: string): boolean {
|
||||
return /^[-._+]$/i.test(char);
|
||||
}
|
||||
|
||||
function isDigit(char: string): boolean {
|
||||
return /^\d$/.test(char);
|
||||
}
|
||||
|
||||
function isLetter(char: string): boolean {
|
||||
return !isSeparator(char) && !isDigit(char);
|
||||
}
|
||||
|
||||
function isTransition(prevChar: string, nextChar: string): boolean {
|
||||
return (
|
||||
(isDigit(prevChar) && isLetter(nextChar)) ||
|
||||
(isLetter(prevChar) && isDigit(nextChar))
|
||||
);
|
||||
}
|
||||
|
||||
export function tokenize(versionStr: string): Token[] | null {
|
||||
let result = [];
|
||||
let currentVal = '';
|
||||
|
||||
function yieldToken(): void {
|
||||
if (currentVal === '') {
|
||||
result = null;
|
||||
}
|
||||
if (result) {
|
||||
const val = currentVal;
|
||||
if (/^\d+$/.test(val)) {
|
||||
result.push({
|
||||
type: TokenType.Number,
|
||||
val: parseInt(val, 10),
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
type: TokenType.String,
|
||||
val,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iterateChars(versionStr, (prevChar, nextChar) => {
|
||||
if (nextChar === null) {
|
||||
yieldToken();
|
||||
} else if (isSeparator(nextChar)) {
|
||||
yieldToken();
|
||||
currentVal = '';
|
||||
} else if (prevChar !== null && isTransition(prevChar, nextChar)) {
|
||||
yieldToken();
|
||||
currentVal = nextChar;
|
||||
} else {
|
||||
currentVal = currentVal.concat(nextChar);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isSpecial(str: string, special: string): boolean {
|
||||
return str.toLowerCase() === special;
|
||||
}
|
||||
|
||||
function checkSpecial(left: string, right: string, tag: string): number | null {
|
||||
if (isSpecial(left, tag) && isSpecial(right, tag)) {
|
||||
return 0;
|
||||
}
|
||||
if (isSpecial(left, tag)) {
|
||||
return 1;
|
||||
}
|
||||
if (isSpecial(right, tag)) {
|
||||
return -1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function stringTokenCmp(left: string, right: string): number {
|
||||
const dev = checkSpecial(left, right, 'dev');
|
||||
if (dev !== null) {
|
||||
return dev ? -dev : 0;
|
||||
}
|
||||
|
||||
const final = checkSpecial(left, right, 'final');
|
||||
if (final !== null) {
|
||||
return final;
|
||||
}
|
||||
|
||||
const release = checkSpecial(left, right, 'release');
|
||||
if (release !== null) {
|
||||
return release;
|
||||
}
|
||||
|
||||
const rc = checkSpecial(left, right, 'rc');
|
||||
if (rc !== null) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (left === 'SNAPSHOT' || right === 'SNAPSHOT') {
|
||||
if (left.toLowerCase() < right.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (left.toLowerCase() > right.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (left < right) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (left > right) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function tokenCmp(left: Token | null, right: Token | null): number {
|
||||
if (left === null) {
|
||||
if (right.type === TokenType.String) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (right === null) {
|
||||
if (left.type === TokenType.String) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (left.type === TokenType.Number && right.type === TokenType.Number) {
|
||||
if (left.val < right.val) {
|
||||
return -1;
|
||||
}
|
||||
if (left.val > right.val) {
|
||||
return 1;
|
||||
}
|
||||
} else if (typeof left.val === 'string' && typeof right.val === 'string') {
|
||||
return stringTokenCmp(left.val, right.val);
|
||||
} else if (right.type === TokenType.Number) {
|
||||
return -1;
|
||||
} else if (left.type === TokenType.Number) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function compare(left: string, right: string): number {
|
||||
const leftTokens = tokenize(left);
|
||||
const rightTokens = tokenize(right);
|
||||
const length = Math.max(leftTokens.length, rightTokens.length);
|
||||
for (let idx = 0; idx < length; idx += 1) {
|
||||
const leftToken = leftTokens[idx] || null;
|
||||
const rightToken = rightTokens[idx] || null;
|
||||
const cmpResult = tokenCmp(leftToken, rightToken);
|
||||
if (cmpResult !== 0) {
|
||||
return cmpResult;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function isVersion(input: string): boolean {
|
||||
if (!input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!/^[-._+a-zA-Z0-9]+$/i.test(input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/^latest\.?/i.test(input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tokens = tokenize(input);
|
||||
return !!tokens && !!tokens.length;
|
||||
}
|
||||
|
||||
interface PrefixRange {
|
||||
tokens: Token[];
|
||||
}
|
||||
|
||||
export enum RangeBound {
|
||||
Inclusive = 1,
|
||||
Exclusive,
|
||||
}
|
||||
|
||||
interface MavenBasedRange {
|
||||
leftBound: RangeBound;
|
||||
leftBoundStr: string;
|
||||
leftVal: string | null;
|
||||
separator: string;
|
||||
rightBound: RangeBound;
|
||||
rightBoundStr: string;
|
||||
rightVal: string | null;
|
||||
}
|
||||
|
||||
export function parsePrefixRange(input: string): PrefixRange | null {
|
||||
if (!input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (input.trim() === '+') {
|
||||
return { tokens: [] };
|
||||
}
|
||||
|
||||
const postfixRegex = /[-._]\+$/;
|
||||
if (postfixRegex.test(input)) {
|
||||
const prefixValue = input.replace(/[-._]\+$/, '');
|
||||
const tokens = tokenize(prefixValue);
|
||||
return tokens ? { tokens } : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const mavenBasedRangeRegex = /^(?<leftBoundStr>[[\](]\s*)(?<leftVal>[-._+a-zA-Z0-9]*?)(?<separator>\s*,\s*)(?<rightVal>[-._+a-zA-Z0-9]*?)(?<rightBoundStr>\s*[[\])])$/;
|
||||
|
||||
export function parseMavenBasedRange(input: string): MavenBasedRange | null {
|
||||
if (!input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = mavenBasedRangeRegex.exec(input);
|
||||
if (match) {
|
||||
const { leftBoundStr, separator, rightBoundStr } = match.groups;
|
||||
let { leftVal, rightVal } = match.groups;
|
||||
if (!leftVal) {
|
||||
leftVal = null;
|
||||
}
|
||||
if (!rightVal) {
|
||||
rightVal = null;
|
||||
}
|
||||
const isVersionLeft = isVersion(leftVal);
|
||||
const isVersionRight = isVersion(rightVal);
|
||||
if (
|
||||
(leftVal === null || isVersionLeft) &&
|
||||
(rightVal === null || isVersionRight)
|
||||
) {
|
||||
if (isVersionLeft && isVersionRight && compare(leftVal, rightVal) === 1) {
|
||||
return null;
|
||||
}
|
||||
const leftBound =
|
||||
leftBoundStr.trim() === '['
|
||||
? RangeBound.Inclusive
|
||||
: RangeBound.Exclusive;
|
||||
const rightBound =
|
||||
rightBoundStr.trim() === ']'
|
||||
? RangeBound.Inclusive
|
||||
: RangeBound.Exclusive;
|
||||
return {
|
||||
leftBound,
|
||||
leftBoundStr,
|
||||
leftVal,
|
||||
separator,
|
||||
rightBound,
|
||||
rightBoundStr,
|
||||
rightVal,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isValid(str: string): boolean {
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
isVersion(str) || !!parsePrefixRange(str) || !!parseMavenBasedRange(str)
|
||||
);
|
||||
}
|
270
lib/versioning/gradle/index.spec.ts
Normal file
270
lib/versioning/gradle/index.spec.ts
Normal file
|
@ -0,0 +1,270 @@
|
|||
import { api } from '.';
|
||||
import { compare, parsePrefixRange, parseMavenBasedRange } from './compare';
|
||||
|
||||
describe('versioning/gradle/compare', () => {
|
||||
it('returns equality', () => {
|
||||
expect(compare('1', '1')).toEqual(0);
|
||||
expect(compare('a', 'a')).toEqual(0);
|
||||
|
||||
expect(compare('1a1', '1.a.1')).toEqual(0);
|
||||
expect(compare('1a1', '1-a-1')).toEqual(0);
|
||||
expect(compare('1a1', '1_a_1')).toEqual(0);
|
||||
expect(compare('1a1', '1+a+1')).toEqual(0);
|
||||
expect(compare('1.a.1', '1a1')).toEqual(0);
|
||||
expect(compare('1-a-1', '1a1')).toEqual(0);
|
||||
expect(compare('1_a_1', '1a1')).toEqual(0);
|
||||
expect(compare('1+a+1', '1a1')).toEqual(0);
|
||||
|
||||
expect(compare('1.a.1', '1-a+1')).toEqual(0);
|
||||
expect(compare('1-a+1', '1.a-1')).toEqual(0);
|
||||
expect(compare('1.a-1', '1a1')).toEqual(0);
|
||||
|
||||
expect(compare('dev', 'dev')).toEqual(0);
|
||||
expect(compare('rc', 'rc')).toEqual(0);
|
||||
expect(compare('release', 'release')).toEqual(0);
|
||||
expect(compare('final', 'final')).toEqual(0);
|
||||
expect(compare('snapshot', 'SNAPSHOT')).toEqual(0);
|
||||
expect(compare('SNAPSHOT', 'snapshot')).toEqual(0);
|
||||
});
|
||||
it('returns less than', () => {
|
||||
expect(compare('1.1', '1.2')).toEqual(-1);
|
||||
expect(compare('1.a', '1.1')).toEqual(-1);
|
||||
expect(compare('1.A', '1.B')).toEqual(-1);
|
||||
expect(compare('1.B', '1.a')).toEqual(-1);
|
||||
expect(compare('1.a', '1.b')).toEqual(-1);
|
||||
expect(compare('1.1', '1.1.0')).toEqual(-1);
|
||||
expect(compare('1.1.a', '1.1')).toEqual(-1);
|
||||
expect(compare('1.0-dev', '1.0-alpha')).toEqual(-1);
|
||||
expect(compare('1.0-alpha', '1.0-rc')).toEqual(-1);
|
||||
expect(compare('1.0-zeta', '1.0-rc')).toEqual(-1);
|
||||
expect(compare('1.0-rc', '1.0-release')).toEqual(-1);
|
||||
expect(compare('1.0-release', '1.0-final')).toEqual(-1);
|
||||
expect(compare('1.0-final', '1.0')).toEqual(-1);
|
||||
expect(compare('1.0-alpha', '1.0-SNAPSHOT')).toEqual(-1);
|
||||
expect(compare('1.0-SNAPSHOT', '1.0-zeta')).toEqual(-1);
|
||||
expect(compare('1.0-zeta', '1.0-rc')).toEqual(-1);
|
||||
expect(compare('1.0-rc', '1.0')).toEqual(-1);
|
||||
expect(compare('1.0', '1.0-20150201.121010-123')).toEqual(-1);
|
||||
expect(compare('1.0-20150201.121010-123', '1.1')).toEqual(-1);
|
||||
expect(compare('sNaPsHoT', 'snapshot')).toEqual(-1);
|
||||
});
|
||||
it('returns greater than', () => {
|
||||
expect(compare('1.2', '1.1')).toEqual(1);
|
||||
expect(compare('1.1', '1.1.a')).toEqual(1);
|
||||
expect(compare('1.B', '1.A')).toEqual(1);
|
||||
expect(compare('1.a', '1.B')).toEqual(1);
|
||||
expect(compare('1.b', '1.a')).toEqual(1);
|
||||
expect(compare('1.1.0', '1.1')).toEqual(1);
|
||||
expect(compare('1.1', '1.a')).toEqual(1);
|
||||
expect(compare('1.0-alpha', '1.0-dev')).toEqual(1);
|
||||
expect(compare('1.0-rc', '1.0-alpha')).toEqual(1);
|
||||
expect(compare('1.0-rc', '1.0-zeta')).toEqual(1);
|
||||
expect(compare('1.0-release', '1.0-rc')).toEqual(1);
|
||||
expect(compare('1.0-final', '1.0-release')).toEqual(1);
|
||||
expect(compare('1.0', '1.0-final')).toEqual(1);
|
||||
expect(compare('1.0-SNAPSHOT', '1.0-alpha')).toEqual(1);
|
||||
expect(compare('1.0-zeta', '1.0-SNAPSHOT')).toEqual(1);
|
||||
expect(compare('1.0-rc', '1.0-zeta')).toEqual(1);
|
||||
expect(compare('1.0', '1.0-rc')).toEqual(1);
|
||||
expect(compare('1.0-20150201.121010-123', '1.0')).toEqual(1);
|
||||
expect(compare('1.1', '1.0-20150201.121010-123')).toEqual(1);
|
||||
expect(compare('snapshot', 'sNaPsHoT')).toEqual(1);
|
||||
});
|
||||
|
||||
const invalidPrefixRanges = [
|
||||
'',
|
||||
'1.2.3-SNAPSHOT', // versions should be handled separately
|
||||
'1.2..+',
|
||||
'1.2.++',
|
||||
];
|
||||
it('filters out incorrect prefix ranges', () => {
|
||||
invalidPrefixRanges.forEach(rangeStr => {
|
||||
const range = parsePrefixRange(rangeStr);
|
||||
expect(range).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
const invalidMavenBasedRanges = [
|
||||
'',
|
||||
'1.2.3-SNAPSHOT', // versions should be handled separately
|
||||
'[]',
|
||||
'(',
|
||||
'[',
|
||||
',',
|
||||
'[1.0',
|
||||
'1.0]',
|
||||
'[1.0],',
|
||||
',[1.0]',
|
||||
'[2.0,1.0)',
|
||||
'[1.2,1.3],1.4',
|
||||
'[1.2,,1.3]',
|
||||
'[1,[2,3],4]',
|
||||
'[1.3,1.2]',
|
||||
];
|
||||
it('filters out incorrect maven-based ranges', () => {
|
||||
invalidMavenBasedRanges.forEach(rangeStr => {
|
||||
const range = parseMavenBasedRange(rangeStr);
|
||||
expect(range).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('versioning/gradle', () => {
|
||||
it('isValid', () => {
|
||||
expect(api.isValid('1.0.0')).toBe(true);
|
||||
expect(api.isValid('[1.12.6,1.18.6]')).toBe(true);
|
||||
expect(api.isValid(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('isVersion', () => {
|
||||
expect(api.isVersion('')).toBe(false);
|
||||
|
||||
expect(api.isVersion('latest.integration')).toBe(false);
|
||||
expect(api.isVersion('latest.release')).toBe(false);
|
||||
expect(api.isVersion('latest')).toBe(false);
|
||||
|
||||
expect(api.isVersion('1')).toBe(true);
|
||||
expect(api.isVersion('a')).toBe(true);
|
||||
expect(api.isVersion('A')).toBe(true);
|
||||
expect(api.isVersion('1a1')).toBe(true);
|
||||
expect(api.isVersion('1.a.1')).toBe(true);
|
||||
expect(api.isVersion('1-a-1')).toBe(true);
|
||||
expect(api.isVersion('1_a_1')).toBe(true);
|
||||
expect(api.isVersion('1+a+1')).toBe(true);
|
||||
expect(api.isVersion('1!a!1')).toBe(false);
|
||||
|
||||
expect(api.isVersion('1.0-20150201.121010-123')).toBe(true);
|
||||
expect(api.isVersion('dev')).toBe(true);
|
||||
expect(api.isVersion('rc')).toBe(true);
|
||||
expect(api.isVersion('release')).toBe(true);
|
||||
expect(api.isVersion('final')).toBe(true);
|
||||
expect(api.isVersion('SNAPSHOT')).toBe(true);
|
||||
|
||||
expect(api.isVersion('1.2')).toBe(true);
|
||||
expect(api.isVersion('1..2')).toBe(false);
|
||||
expect(api.isVersion('1++2')).toBe(false);
|
||||
expect(api.isVersion('1--2')).toBe(false);
|
||||
expect(api.isVersion('1__2')).toBe(false);
|
||||
});
|
||||
it('checks if version is stable', () => {
|
||||
expect(api.isStable('')).toBeNull();
|
||||
expect(api.isStable('foobar')).toBe(true);
|
||||
expect(api.isStable('final')).toBe(true);
|
||||
expect(api.isStable('1')).toBe(true);
|
||||
expect(api.isStable('1.2')).toBe(true);
|
||||
expect(api.isStable('1.2.3')).toBe(true);
|
||||
expect(api.isStable('1.2.3.4')).toBe(true);
|
||||
expect(api.isStable('v1.2.3.4')).toBe(true);
|
||||
expect(api.isStable('1-alpha-1')).toBe(false);
|
||||
expect(api.isStable('1-b1')).toBe(false);
|
||||
expect(api.isStable('1-foo')).toBe(true);
|
||||
expect(api.isStable('1-final-1.0.0')).toBe(true);
|
||||
expect(api.isStable('1-release')).toBe(true);
|
||||
expect(api.isStable('1.final')).toBe(true);
|
||||
expect(api.isStable('1.0milestone1')).toBe(false);
|
||||
expect(api.isStable('1-sp')).toBe(true);
|
||||
expect(api.isStable('1-ga-1')).toBe(true);
|
||||
expect(api.isStable('1.3-groovy-2.5')).toBe(true);
|
||||
expect(api.isStable('1.3-RC1-groovy-2.5')).toBe(false);
|
||||
expect(api.isStable('Hoxton.RELEASE')).toBe(true);
|
||||
expect(api.isStable('Hoxton.SR')).toBe(true);
|
||||
expect(api.isStable('Hoxton.SR1')).toBe(true);
|
||||
|
||||
// https://github.com/renovatebot/renovate/pull/5789
|
||||
expect(api.isStable('1.3.5-native-mt-1.3.71-release-429')).toBe(false);
|
||||
});
|
||||
it('returns major version', () => {
|
||||
expect(api.getMajor('')).toBeNull();
|
||||
expect(api.getMajor('1')).toEqual(1);
|
||||
expect(api.getMajor('1.2')).toEqual(1);
|
||||
expect(api.getMajor('1.2.3')).toEqual(1);
|
||||
expect(api.getMajor('v1.2.3')).toEqual(1);
|
||||
expect(api.getMajor('1rc42')).toEqual(1);
|
||||
});
|
||||
it('returns minor version', () => {
|
||||
expect(api.getMinor('')).toBeNull();
|
||||
expect(api.getMinor('1')).toEqual(0);
|
||||
expect(api.getMinor('1.2')).toEqual(2);
|
||||
expect(api.getMinor('1.2.3')).toEqual(2);
|
||||
expect(api.getMinor('v1.2.3')).toEqual(2);
|
||||
expect(api.getMinor('1.2.3.4')).toEqual(2);
|
||||
expect(api.getMinor('1-rc42')).toEqual(0);
|
||||
});
|
||||
it('returns patch version', () => {
|
||||
expect(api.getPatch('')).toBeNull();
|
||||
expect(api.getPatch('1')).toEqual(0);
|
||||
expect(api.getPatch('1.2')).toEqual(0);
|
||||
expect(api.getPatch('1.2.3')).toEqual(3);
|
||||
expect(api.getPatch('v1.2.3')).toEqual(3);
|
||||
expect(api.getPatch('1.2.3.4')).toEqual(3);
|
||||
expect(api.getPatch('1-rc10')).toEqual(0);
|
||||
expect(api.getPatch('1-rc42-1')).toEqual(0);
|
||||
});
|
||||
it('matches against maven ranges', () => {
|
||||
expect(api.matches('0', '[0,1]')).toBe(true);
|
||||
expect(api.matches('1', '[0,1]')).toBe(true);
|
||||
expect(api.matches('0', '(0,1)')).toBe(false);
|
||||
expect(api.matches('1', '(0,1)')).toBe(false);
|
||||
expect(api.matches('1', '(0,2)')).toBe(true);
|
||||
expect(api.matches('1', '[0,2]')).toBe(true);
|
||||
expect(api.matches('1', '(,1]')).toBe(true);
|
||||
expect(api.matches('1', '(,1)')).toBe(false);
|
||||
expect(api.matches('1', '[1,)')).toBe(true);
|
||||
expect(api.matches('1', '(1,)')).toBe(false);
|
||||
expect(api.matches('1', '[[]]')).toBe(null);
|
||||
expect(api.matches('0', '')).toBe(false);
|
||||
expect(api.matches('1', '1')).toBe(true);
|
||||
expect(api.matches('1.2.3', '1.2.+')).toBe(true);
|
||||
expect(api.matches('1.2.3.4', '1.2.+')).toBe(true);
|
||||
expect(api.matches('1.3.0', '1.2.+')).toBe(false);
|
||||
expect(api.matches('foo', '+')).toBe(true);
|
||||
expect(api.matches('1', '+')).toBe(true);
|
||||
expect(api.matches('99999999999', '+')).toBe(true);
|
||||
});
|
||||
it('api', () => {
|
||||
expect(api.isGreaterThan('1.1', '1')).toBe(true);
|
||||
expect(api.minSatisfyingVersion(['0', '1.5', '1', '2'], '1.+')).toBe('1');
|
||||
expect(api.maxSatisfyingVersion(['0', '1', '1.5', '2'], '1.+')).toBe('1.5');
|
||||
expect(
|
||||
api.getNewValue({
|
||||
currentValue: '1',
|
||||
rangeStrategy: null,
|
||||
fromVersion: null,
|
||||
toVersion: '1.1',
|
||||
})
|
||||
).toBe('1.1');
|
||||
expect(
|
||||
api.getNewValue({
|
||||
currentValue: '[1.2.3,]',
|
||||
rangeStrategy: null,
|
||||
fromVersion: null,
|
||||
toVersion: '1.2.4',
|
||||
})
|
||||
).toBe(null);
|
||||
});
|
||||
it('pins maven ranges', () => {
|
||||
const sample = [
|
||||
['[1.2.3]', '1.2.3', '1.2.4'],
|
||||
['[1.0.0,1.2.3]', '1.0.0', '1.2.4'],
|
||||
['[1.0.0,1.2.23]', '1.0.0', '1.2.23'],
|
||||
['(,1.0]', '0.0.1', '2.0'],
|
||||
['],1.0]', '0.0.1', '2.0'],
|
||||
['(,1.0)', '0.1', '2.0'],
|
||||
['],1.0[', '2.0', '],2.0['],
|
||||
['[1.0,1.2],[1.3,1.5)', '1.0', '1.2.4'],
|
||||
['[1.0,1.2],[1.3,1.5[', '1.0', '1.2.4'],
|
||||
['[1.2.3,)', '1.2.3', '1.2.4'],
|
||||
['[1.2.3,[', '1.2.3', '1.2.4'],
|
||||
];
|
||||
sample.forEach(([currentValue, fromVersion, toVersion]) => {
|
||||
expect(
|
||||
api.getNewValue({
|
||||
currentValue,
|
||||
rangeStrategy: 'pin',
|
||||
fromVersion,
|
||||
toVersion,
|
||||
})
|
||||
).toEqual(toVersion);
|
||||
});
|
||||
});
|
||||
});
|
210
lib/versioning/gradle/index.ts
Normal file
210
lib/versioning/gradle/index.ts
Normal file
|
@ -0,0 +1,210 @@
|
|||
import { NewValueConfig, VersioningApi } from '../common';
|
||||
import {
|
||||
compare,
|
||||
isValid,
|
||||
isVersion,
|
||||
parseMavenBasedRange,
|
||||
parsePrefixRange,
|
||||
RangeBound,
|
||||
tokenize,
|
||||
TokenType,
|
||||
} from './compare';
|
||||
|
||||
export const id = 'gradle';
|
||||
export const displayName = 'Gradle';
|
||||
export const urls = [
|
||||
'https://docs.gradle.org/current/userguide/single_versions.html#version_ordering',
|
||||
];
|
||||
export const supportsRanges = true;
|
||||
export const supportedRangeStrategies = ['pin'];
|
||||
|
||||
const equals = (a: string, b: string): boolean => compare(a, b) === 0;
|
||||
|
||||
const getMajor = (version: string): number | null => {
|
||||
if (isVersion(version)) {
|
||||
const tokens = tokenize(version.replace(/^v/i, ''));
|
||||
const majorToken = tokens[0];
|
||||
if (majorToken && majorToken.type === TokenType.Number) {
|
||||
return +majorToken.val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getMinor = (version: string): number | null => {
|
||||
if (isVersion(version)) {
|
||||
const tokens = tokenize(version.replace(/^v/i, ''));
|
||||
const majorToken = tokens[0];
|
||||
const minorToken = tokens[1];
|
||||
if (
|
||||
majorToken &&
|
||||
majorToken.type === TokenType.Number &&
|
||||
minorToken &&
|
||||
minorToken.type === TokenType.Number
|
||||
) {
|
||||
return +minorToken.val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getPatch = (version: string): number | null => {
|
||||
if (isVersion(version)) {
|
||||
const tokens = tokenize(version.replace(/^v/i, ''));
|
||||
const majorToken = tokens[0];
|
||||
const minorToken = tokens[1];
|
||||
const patchToken = tokens[2];
|
||||
if (
|
||||
majorToken &&
|
||||
majorToken.type === TokenType.Number &&
|
||||
minorToken &&
|
||||
minorToken.type === TokenType.Number &&
|
||||
patchToken &&
|
||||
patchToken.type === TokenType.Number
|
||||
) {
|
||||
return +patchToken.val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const isGreaterThan = (a: string, b: string): boolean => compare(a, b) === 1;
|
||||
|
||||
const unstable = new Set([
|
||||
'a',
|
||||
'alpha',
|
||||
'b',
|
||||
'beta',
|
||||
'm',
|
||||
'mt',
|
||||
'milestone',
|
||||
'rc',
|
||||
'cr',
|
||||
'snapshot',
|
||||
]);
|
||||
|
||||
const isStable = (version: string): boolean | null => {
|
||||
if (isVersion(version)) {
|
||||
const tokens = tokenize(version);
|
||||
for (const token of tokens) {
|
||||
if (token.type === TokenType.String) {
|
||||
const val = token.val.toString().toLowerCase();
|
||||
if (unstable.has(val)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const matches = (a: string, b: string): boolean => {
|
||||
if (!a || !isVersion(a) || !b) {
|
||||
return false;
|
||||
}
|
||||
if (isVersion(b)) {
|
||||
return equals(a, b);
|
||||
}
|
||||
|
||||
const prefixRange = parsePrefixRange(b);
|
||||
if (prefixRange) {
|
||||
const tokens = prefixRange.tokens;
|
||||
if (tokens.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const versionTokens = tokenize(a);
|
||||
const x = versionTokens
|
||||
.slice(0, tokens.length)
|
||||
.map(({ val }) => val)
|
||||
.join('.');
|
||||
const y = tokens.map(({ val }) => val).join('.');
|
||||
return equals(x, y);
|
||||
}
|
||||
|
||||
const mavenBasedRange = parseMavenBasedRange(b);
|
||||
if (!mavenBasedRange) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { leftBound, leftVal, rightBound, rightVal } = mavenBasedRange;
|
||||
let leftResult = true;
|
||||
let rightResult = true;
|
||||
|
||||
if (leftVal) {
|
||||
leftResult =
|
||||
leftBound === RangeBound.Exclusive
|
||||
? compare(leftVal, a) === -1
|
||||
: compare(leftVal, a) !== 1;
|
||||
}
|
||||
|
||||
if (rightVal) {
|
||||
rightResult =
|
||||
rightBound === RangeBound.Exclusive
|
||||
? compare(a, rightVal) === -1
|
||||
: compare(a, rightVal) !== 1;
|
||||
}
|
||||
|
||||
return leftResult && rightResult;
|
||||
};
|
||||
|
||||
const maxSatisfyingVersion = (versions: string[], range: string): string => {
|
||||
return versions.reduce((result, version) => {
|
||||
if (matches(version, range)) {
|
||||
if (!result) {
|
||||
return version;
|
||||
}
|
||||
if (isGreaterThan(version, result)) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, null);
|
||||
};
|
||||
|
||||
const minSatisfyingVersion = (versions: string[], range: string): string => {
|
||||
return versions.reduce((result, version) => {
|
||||
if (matches(version, range)) {
|
||||
if (!result) {
|
||||
return version;
|
||||
}
|
||||
if (compare(version, result) === -1) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, null);
|
||||
};
|
||||
|
||||
function getNewValue({
|
||||
currentValue,
|
||||
rangeStrategy,
|
||||
toVersion,
|
||||
}: NewValueConfig): string | null {
|
||||
if (isVersion(currentValue) || rangeStrategy === 'pin') {
|
||||
return toVersion;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const api: VersioningApi = {
|
||||
equals,
|
||||
getMajor,
|
||||
getMinor,
|
||||
getPatch,
|
||||
isCompatible: isVersion,
|
||||
isGreaterThan,
|
||||
isSingleVersion: isVersion,
|
||||
isStable,
|
||||
isValid,
|
||||
isVersion,
|
||||
matches,
|
||||
maxSatisfyingVersion,
|
||||
minSatisfyingVersion,
|
||||
getNewValue,
|
||||
sortVersions: compare,
|
||||
};
|
||||
|
||||
export default api;
|
|
@ -129,7 +129,7 @@ describe('versioning/maven/compare', () => {
|
|||
'[,1.0]',
|
||||
];
|
||||
it('filters out incorrect ranges', () => {
|
||||
Object.keys(invalidRanges).forEach(rangeStr => {
|
||||
invalidRanges.forEach(rangeStr => {
|
||||
const range = parseRange(rangeStr);
|
||||
expect(range).toBeNull();
|
||||
expect(rangeToStr(range)).toBeNull();
|
||||
|
|
Loading…
Reference in a new issue