From b9afdadf71d76512183c7470159f34c49e545702 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 22 Aug 2018 11:22:09 -0700 Subject: [PATCH] Remove jsTypings/semver.ts, add unit tests and logging --- src/compiler/diagnosticMessages.json | 8 ++ src/compiler/moduleNameResolver.ts | 27 +++- src/compiler/semver.ts | 125 +++++++++--------- src/jsTyping/jsTyping.ts | 6 +- src/jsTyping/semver.ts | 61 --------- src/jsTyping/tsconfig.json | 3 +- src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/semver.ts | 94 +++++++++++++ src/testRunner/unittests/typingsInstaller.ts | 14 +- src/typingsInstallerCore/typingsInstaller.ts | 9 +- .../typesVersions.ambientModules.trace.json | 6 +- .../typesVersions.multiFile.trace.json | 4 +- 12 files changed, 207 insertions(+), 151 deletions(-) delete mode 100644 src/jsTyping/semver.ts create mode 100644 src/testRunner/unittests/semver.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index ed528e5b87e..ebe5c3efbcd 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3671,6 +3671,14 @@ "category": "Error", "code": 6205 }, + "'package.json' has invalid version '{0}' in 'typesVersions' field.": { + "category": "Message", + "code": 6206 + }, + "'package.json' does not have a 'typesVersions' entry that matches version '{0}'.": { + "category": "Message", + "code": 6207 + }, "Projects to reference": { "category": "Message", diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 21ff919872b..3b61c2cd83b 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -127,7 +127,14 @@ namespace ts { /* @internal */ export function getPackageJsonTypesVersionsOverride(typesVersions: MapLike) { - const typeScriptVersion = Version.parse(versionMajorMinor); + return getPackageJsonTypesVersionsOverrideWithTrace(typesVersions, /*state*/ undefined); + } + + let typeScriptVersion: Version | undefined; + + function getPackageJsonTypesVersionsOverrideWithTrace(typesVersions: MapLike, state: ModuleResolutionState | undefined) { + if (!typeScriptVersion) typeScriptVersion = new Version(versionMajorMinor); + let bestVersion: Version | undefined; let bestVersionKey: string | undefined; for (const key in typesVersions) { @@ -135,12 +142,15 @@ namespace ts { const keyVersion = Version.tryParse(key); if (keyVersion === undefined) { + if (state && state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_invalid_version_0_in_typesVersions_field, key); + } continue; } // match the greatest version less than the current TypeScript version - if (keyVersion.compareTo(typeScriptVersion) <= 0 - && (bestVersion === undefined || keyVersion.compareTo(bestVersion) > 0)) { + if (keyVersion.compareTo(bestVersion) > 0 && + keyVersion.compareTo(typeScriptVersion) <= 0) { bestVersion = keyVersion; bestVersionKey = key; } @@ -169,22 +179,25 @@ namespace ts { return; } - const result = getPackageJsonTypesVersionsOverride(typesVersions); + const result = getPackageJsonTypesVersionsOverrideWithTrace(typesVersions, state); if (!result) { - return undefined; + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); + } + return; } const { version: bestVersionKey, directory: bestVersionPath } = result; if (!isString(bestVersionPath)) { if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersion['${bestVersionKey}']`, "string", typeof bestVersionPath); + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "string", typeof bestVersionPath); } return; } if (state.traceEnabled) { const path = normalizePath(combinePaths(baseDirectory, bestVersionPath)); - trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, `typesVersion['${bestVersionKey}']`, bestVersionPath, path); + trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, `typesVersions['${bestVersionKey}']`, bestVersionPath, path); } return bestVersionPath; diff --git a/src/compiler/semver.ts b/src/compiler/semver.ts index a3c4e90d37a..203739f7c95 100644 --- a/src/compiler/semver.ts +++ b/src/compiler/semver.ts @@ -1,48 +1,49 @@ /* @internal */ namespace ts { - // Per https://semver.org/#spec-item-2: - // + // https://semver.org/#spec-item-2 // > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative // > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor // > version, and Z is the patch version. Each element MUST increase numerically. // // NOTE: We differ here in that we allow X and X.Y, with missing parts having the default // value of `0`. - const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:(\+[a-z0-9-.]+))?)?)?$/i; + const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; - // Per https://semver.org/#spec-item-9: - // + // https://semver.org/#spec-item-9 // > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated // > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII // > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers // > MUST NOT include leading zeroes. const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i; - // Per https://semver.org/#spec-item-10: - // + // https://semver.org/#spec-item-10 // > Build metadata MAY be denoted by appending a plus sign and a series of dot separated // > identifiers immediately following the patch or pre-release version. Identifiers MUST // > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i; - // Per https://semver.org/#spec-item-9: - // + // https://semver.org/#spec-item-9 // > Numeric identifiers MUST NOT include leading zeroes. const numericIdentifierRegExp = /^(0|[1-9]\d*)$/; /** - * Describes a precise semantic version number, per https://semver.org + * Describes a precise semantic version number, https://semver.org */ export class Version { - static readonly zero = new Version(0); - readonly major: number; readonly minor: number; readonly patch: number; readonly prerelease: ReadonlyArray; readonly build: ReadonlyArray; - constructor(major: number, minor = 0, patch = 0, prerelease = "", build = "") { + constructor(text: string); + constructor(major: number, minor?: number, patch?: number, prerelease?: string, build?: string); + constructor(major: number | string, minor = 0, patch = 0, prerelease = "", build = "") { + if (typeof major === "string") { + const result = Debug.assertDefined(tryParseComponents(major), "Invalid version"); + ({ major, minor, patch, prerelease, build } = result); + } + Debug.assert(major >= 0, "Invalid argument: major"); Debug.assert(minor >= 0, "Invalid argument: minor"); Debug.assert(patch >= 0, "Invalid argument: patch"); @@ -51,104 +52,102 @@ namespace ts { this.major = major; this.minor = minor; this.patch = patch; - this.prerelease = prerelease === "" ? emptyArray : prerelease.split("."); - this.build = build === "" ? emptyArray : build.split("."); - } - - static parse(text: string) { - return Debug.assertDefined(this.tryParse(text)); + this.prerelease = prerelease ? prerelease.split(".") : emptyArray; + this.build = build ? build.split(".") : emptyArray; } static tryParse(text: string) { - const match = versionRegExp.exec(text); - if (!match) return undefined; + const result = tryParseComponents(text); + if (!result) return undefined; - const [, major, minor = 0, patch = 0, prerelease, build] = match; - if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined; - if (build && !buildRegExp.test(build)) return undefined; - return new Version(+major, +minor, +patch, prerelease, build); + const { major, minor, patch, prerelease, build } = result; + return new Version(major, minor, patch, prerelease, build); } - static compare(left: Version | undefined, right: Version | undefined, compareBuildMetadata?: boolean) { - // Per https://semver.org/#spec-item-11: - // + compareTo(other: Version | undefined) { + // https://semver.org/#spec-item-11 // > Precedence is determined by the first difference when comparing each of these // > identifiers from left to right as follows: Major, minor, and patch versions are // > always compared numerically. // - // > When major, minor, and patch are equal, a pre-release version has lower - // > precedence than a normal version. + // https://semver.org/#spec-item-11 + // > Precedence for two pre-release versions with the same major, minor, and patch version + // > MUST be determined by comparing each dot separated identifier from left to right until + // > a difference is found [...] // - // Per https://semver.org/#spec-item-10: - // - // > Build metadata SHOULD be ignored when determining version precedence. - if (left === right) return Comparison.EqualTo; - if (left === undefined) return Comparison.LessThan; - if (right === undefined) return Comparison.GreaterThan; - return compareValues(left.major, right.major) - || compareValues(left.minor, right.minor) - || compareValues(left.patch, right.patch) - || compareVersionFragments(left.prerelease, right.prerelease, /*compareNumericIdentifiers*/ true) - || (compareBuildMetadata ? compareVersionFragments(left.build, right.build, /*compareNumericIdentifiers*/ false) : Comparison.EqualTo); - } - - compareTo(other: Version, compareBuildMetadata?: boolean) { - return Version.compare(this, other, compareBuildMetadata); + // https://semver.org/#spec-item-11 + // > Build metadata does not figure into precedence + if (this === other) return Comparison.EqualTo; + if (other === undefined) return Comparison.GreaterThan; + return compareValues(this.major, other.major) + || compareValues(this.minor, other.minor) + || compareValues(this.patch, other.patch) + || comparePrerelaseIdentifiers(this.prerelease, other.prerelease); } toString() { let result = `${this.major}.${this.minor}.${this.patch}`; - if (this.prerelease) result += `-${this.prerelease.join(".")}`; - if (this.build) result += `+${this.build.join(".")}`; + if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`; + if (some(this.build)) result += `+${this.build.join(".")}`; return result; } } - function compareVersionFragments(left: ReadonlyArray, right: ReadonlyArray, compareNumericIdentifiers: boolean) { - // Per https://semver.org/#spec-item-11: - // + function tryParseComponents(text: string) { + const match = versionRegExp.exec(text); + if (!match) return undefined; + + const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match; + if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined; + if (build && !buildRegExp.test(build)) return undefined; + return { + major: parseInt(major, 10), + minor: parseInt(minor, 10), + patch: parseInt(patch, 10), + prerelease, + build + }; + } + + function comparePrerelaseIdentifiers(left: ReadonlyArray, right: ReadonlyArray) { + // https://semver.org/#spec-item-11 // > When major, minor, and patch are equal, a pre-release version has lower precedence // > than a normal version. if (left === right) return Comparison.EqualTo; if (left.length === 0) return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan; if (right.length === 0) return Comparison.LessThan; - // Per https://semver.org/#spec-item-11: - // + // https://semver.org/#spec-item-11 // > Precedence for two pre-release versions with the same major, minor, and patch version // > MUST be determined by comparing each dot separated identifier from left to right until - // > a difference is found + // > a difference is found [...] const length = Math.min(left.length, right.length); for (let i = 0; i < length; i++) { const leftIdentifier = left[i]; const rightIdentifier = right[i]; if (leftIdentifier === rightIdentifier) continue; - const leftIsNumeric = compareNumericIdentifiers && numericIdentifierRegExp.test(leftIdentifier); - const rightIsNumeric = compareNumericIdentifiers && numericIdentifierRegExp.test(rightIdentifier); + const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier); + const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier); if (leftIsNumeric || rightIsNumeric) { - // Per https://semver.org/#spec-item-11: - // + // https://semver.org/#spec-item-11 // > Numeric identifiers always have lower precedence than non-numeric identifiers. if (leftIsNumeric !== rightIsNumeric) return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan; - // Per https://semver.org/#spec-item-11: - // + // https://semver.org/#spec-item-11 // > identifiers consisting of only digits are compared numerically const result = compareValues(+leftIdentifier, +rightIdentifier); if (result) return result; } else { - // Per https://semver.org/#spec-item-11: - // + // https://semver.org/#spec-item-11 // > identifiers with letters or hyphens are compared lexically in ASCII sort order. const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier); if (result) return result; } } - // Per https://semver.org/#spec-item-11: - // + // https://semver.org/#spec-item-11 // > A larger set of pre-release fields has a higher precedence than a smaller set, if all // > of the preceding identifiers are equal. return compareValues(left.length, right.length); diff --git a/src/jsTyping/jsTyping.ts b/src/jsTyping/jsTyping.ts index e9c96ba2bf6..db55ce4993b 100644 --- a/src/jsTyping/jsTyping.ts +++ b/src/jsTyping/jsTyping.ts @@ -21,13 +21,13 @@ namespace ts.JsTyping { export interface CachedTyping { typingLocation: string; - version: Semver; + version: Version; } /* @internal */ export function isTypingUpToDate(cachedTyping: CachedTyping, availableTypingVersions: MapLike) { - const availableVersion = Semver.parse(getProperty(availableTypingVersions, `ts${versionMajorMinor}`) || getProperty(availableTypingVersions, "latest")!); - return !availableVersion.greaterThan(cachedTyping.version); + const availableVersion = new Version(getProperty(availableTypingVersions, `ts${versionMajorMinor}`) || getProperty(availableTypingVersions, "latest")!); + return availableVersion.compareTo(cachedTyping.version) <= 0; } /* @internal */ diff --git a/src/jsTyping/semver.ts b/src/jsTyping/semver.ts deleted file mode 100644 index 1c58da8c8f7..00000000000 --- a/src/jsTyping/semver.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* @internal */ -namespace ts { - function stringToInt(str: string): number { - const n = parseInt(str, 10); - if (isNaN(n)) { - throw new Error(`Error in parseInt(${JSON.stringify(str)})`); - } - return n; - } - - const isPrereleaseRegex = /^(.*)-next.\d+/; - const prereleaseSemverRegex = /^(\d+)\.(\d+)\.0-next.(\d+)$/; - const semverRegex = /^(\d+)\.(\d+)\.(\d+)$/; - - export class Semver { - static parse(semver: string): Semver { - const isPrerelease = isPrereleaseRegex.test(semver); - const result = Semver.tryParse(semver, isPrerelease); - if (!result) { - throw new Error(`Unexpected semver: ${semver} (isPrerelease: ${isPrerelease})`); - } - return result; - } - - static fromRaw({ major, minor, patch, isPrerelease }: Semver): Semver { - return new Semver(major, minor, patch, isPrerelease); - } - - // This must parse the output of `versionString`. - private static tryParse(semver: string, isPrerelease: boolean): Semver | undefined { - // Per the semver spec : - // "A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes." - const rgx = isPrerelease ? prereleaseSemverRegex : semverRegex; - const match = rgx.exec(semver); - return match ? new Semver(stringToInt(match[1]), stringToInt(match[2]), stringToInt(match[3]), isPrerelease) : undefined; - } - - private constructor( - readonly major: number, readonly minor: number, readonly patch: number, - /** - * If true, this is `major.minor.0-next.patch`. - * If false, this is `major.minor.patch`. - */ - readonly isPrerelease: boolean) { } - - get versionString(): string { - return this.isPrerelease ? `${this.major}.${this.minor}.0-next.${this.patch}` : `${this.major}.${this.minor}.${this.patch}`; - } - - equals(sem: Semver): boolean { - return this.major === sem.major && this.minor === sem.minor && this.patch === sem.patch && this.isPrerelease === sem.isPrerelease; - } - - greaterThan(sem: Semver): boolean { - return this.major > sem.major || this.major === sem.major - && (this.minor > sem.minor || this.minor === sem.minor - && (!this.isPrerelease && sem.isPrerelease || this.isPrerelease === sem.isPrerelease - && this.patch > sem.patch)); - } - } -} \ No newline at end of file diff --git a/src/jsTyping/tsconfig.json b/src/jsTyping/tsconfig.json index 4886463e163..ac5b8b19c29 100644 --- a/src/jsTyping/tsconfig.json +++ b/src/jsTyping/tsconfig.json @@ -16,7 +16,6 @@ "files": [ "shared.ts", "types.ts", - "jsTyping.ts", - "semver.ts" + "jsTyping.ts" ] } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 12c671f9cf0..744a62811e5 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -73,6 +73,7 @@ "unittests/publicApi.ts", "unittests/reuseProgramStructure.ts", "unittests/session.ts", + "unittests/semver.ts", "unittests/symbolWalker.ts", "unittests/telemetry.ts", "unittests/textChanges.ts", diff --git a/src/testRunner/unittests/semver.ts b/src/testRunner/unittests/semver.ts new file mode 100644 index 00000000000..edc888e1075 --- /dev/null +++ b/src/testRunner/unittests/semver.ts @@ -0,0 +1,94 @@ +namespace ts { + describe("semver", () => { + describe("Version", () => { + function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) { + assert.strictEqual(version.major, major); + assert.strictEqual(version.minor, minor); + assert.strictEqual(version.patch, patch); + assert.deepEqual(version.prerelease, prerelease || emptyArray); + assert.deepEqual(version.build, build || emptyArray); + } + describe("new", () => { + it("text", () => { + assertVersion(new Version("1.2.3-pre.4+build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); + }); + it("parts", () => { + assertVersion(new Version(1, 2, 3, "pre.4", "build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); + assertVersion(new Version(1, 2, 3), [1, 2, 3]); + assertVersion(new Version(1, 2), [1, 2, 0]); + assertVersion(new Version(1), [1, 0, 0]); + }); + }); + it("toString", () => { + assert.strictEqual(new Version(1, 2, 3, "pre.4", "build.5").toString(), "1.2.3-pre.4+build.5"); + assert.strictEqual(new Version(1, 2, 3, "pre.4").toString(), "1.2.3-pre.4"); + assert.strictEqual(new Version(1, 2, 3, /*prerelease*/ undefined, "build.5").toString(), "1.2.3+build.5"); + assert.strictEqual(new Version(1, 2, 3).toString(), "1.2.3"); + assert.strictEqual(new Version(1, 2).toString(), "1.2.0"); + assert.strictEqual(new Version(1).toString(), "1.0.0"); + }); + it("compareTo", () => { + // https://semver.org/#spec-item-11 + // > Precedence is determined by the first difference when comparing each of these + // > identifiers from left to right as follows: Major, minor, and patch versions are + // > always compared numerically. + assert.strictEqual(new Version("1.0.0").compareTo(new Version("2.0.0")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.1.0")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.1")), Comparison.LessThan); + assert.strictEqual(new Version("2.0.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.1.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.1").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0")), Comparison.EqualTo); + + // https://semver.org/#spec-item-11 + // > When major, minor, and patch are equal, a pre-release version has lower + // > precedence than a normal version. + assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0-pre")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.1-pre").compareTo(new Version("1.0.0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-pre").compareTo(new Version("1.0.0")), Comparison.LessThan); + + // https://semver.org/#spec-item-11 + // > identifiers consisting of only digits are compared numerically + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-1")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-1").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-2").compareTo(new Version("1.0.0-10")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-10").compareTo(new Version("1.0.0-2")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); + + // https://semver.org/#spec-item-11 + // > identifiers with letters or hyphens are compared lexically in ASCII sort order. + assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-b")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-a-2").compareTo(new Version("1.0.0-a-10")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-b").compareTo(new Version("1.0.0-a")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-a")), Comparison.EqualTo); + assert.strictEqual(new Version("1.0.0-A").compareTo(new Version("1.0.0-a")), Comparison.LessThan); + + // https://semver.org/#spec-item-11 + // > Numeric identifiers always have lower precedence than non-numeric identifiers. + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-alpha")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); + assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha")), Comparison.EqualTo); + + // https://semver.org/#spec-item-11 + // > A larger set of pre-release fields has a higher precedence than a smaller set, if all + // > of the preceding identifiers are equal. + assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha.0")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-alpha.0").compareTo(new Version("1.0.0-alpha")), Comparison.GreaterThan); + + // https://semver.org/#spec-item-11 + // > Precedence for two pre-release versions with the same major, minor, and patch version + // > MUST be determined by comparing each dot separated identifier from left to right until + // > a difference is found [...] + assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-a.0.b.2")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-b.0.a.1")), Comparison.LessThan); + assert.strictEqual(new Version("1.0.0-a.0.b.2").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); + assert.strictEqual(new Version("1.0.0-b.0.a.1").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); + + // https://semver.org/#spec-item-11 + // > Build metadata does not figure into precedence + assert.strictEqual(new Version("1.0.0+build").compareTo(new Version("1.0.0")), Comparison.EqualTo); + }); + }); + }); +} \ No newline at end of file diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/typingsInstaller.ts index 6a7e76e15de..a718a12f548 100644 --- a/src/testRunner/unittests/typingsInstaller.ts +++ b/src/testRunner/unittests/typingsInstaller.ts @@ -1322,7 +1322,7 @@ namespace ts.projectSystem { content: "" }; const host = createServerHost([f, node]); - const cache = createMapFromTemplate({ node: { typingLocation: node.path, version: Semver.parse("1.3.0") } }); + const cache = createMapFromTemplate({ node: { typingLocation: node.path, version: new Version("1.3.0") } }); const registry = createTypesRegistry("node"); const logger = trackingLogger(); const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"], registry); @@ -1344,7 +1344,7 @@ namespace ts.projectSystem { content: "" }; const host = createServerHost([f, node]); - const cache = createMapFromTemplate({ node: { typingLocation: node.path, version: Semver.parse("1.3.0") } }); + const cache = createMapFromTemplate({ node: { typingLocation: node.path, version: new Version("1.3.0") } }); const logger = trackingLogger(); const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"], emptyMap); assert.deepEqual(logger.finish(), [ @@ -1401,8 +1401,8 @@ namespace ts.projectSystem { }; const host = createServerHost([app]); const cache = createMapFromTemplate({ - node: { typingLocation: node.path, version: Semver.parse("1.3.0") }, - commander: { typingLocation: commander.path, version: Semver.parse("1.0.0") } + node: { typingLocation: node.path, version: new Version("1.3.0") }, + commander: { typingLocation: commander.path, version: new Version("1.0.0") } }); const registry = createTypesRegistry("node", "commander"); const logger = trackingLogger(); @@ -1427,7 +1427,7 @@ namespace ts.projectSystem { }; const host = createServerHost([app]); const cache = createMapFromTemplate({ - node: { typingLocation: node.path, version: Semver.parse("1.0.0") } + node: { typingLocation: node.path, version: new Version("1.0.0") } }); const registry = createTypesRegistry("node"); registry.delete(`ts${versionMajorMinor}`); @@ -1458,8 +1458,8 @@ namespace ts.projectSystem { }; const host = createServerHost([app]); const cache = createMapFromTemplate({ - node: { typingLocation: node.path, version: Semver.parse("1.3.0-next.0") }, - commander: { typingLocation: commander.path, version: Semver.parse("1.3.0-next.0") } + node: { typingLocation: node.path, version: new Version("1.3.0-next.0") }, + commander: { typingLocation: commander.path, version: new Version("1.3.0-next.0") } }); const registry = createTypesRegistry("node", "commander"); registry.get("node")![`ts${versionMajorMinor}`] = "1.3.0-next.1"; diff --git a/src/typingsInstallerCore/typingsInstaller.ts b/src/typingsInstallerCore/typingsInstaller.ts index 44a51a01779..df83f1a677c 100644 --- a/src/typingsInstallerCore/typingsInstaller.ts +++ b/src/typingsInstallerCore/typingsInstaller.ts @@ -252,8 +252,11 @@ namespace ts.server.typingsInstaller { } const info = getProperty(npmLock.dependencies, key); const version = info && info.version; - const semver = Semver.parse(version!); // TODO: GH#18217 - const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: semver }; + if (!version) { + continue; + } + + const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: new Version(version) }; this.packageNameToTypingLocation.set(packageName, newTyping); } } @@ -356,7 +359,7 @@ namespace ts.server.typingsInstaller { // packageName is guaranteed to exist in typesRegistry by filterTypings const distTags = this.typesRegistry.get(packageName)!; - const newVersion = Semver.parse(distTags[`ts${versionMajorMinor}`] || distTags[this.latestDistTag]); + const newVersion = new Version(distTags[`ts${versionMajorMinor}`] || distTags[this.latestDistTag]); const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: newVersion }; this.packageNameToTypingLocation.set(packageName, newTyping); installedTypingFiles.push(typingFile); diff --git a/tests/baselines/reference/typesVersions.ambientModules.trace.json b/tests/baselines/reference/typesVersions.ambientModules.trace.json index 990834accc2..0bf8bc16c5d 100644 --- a/tests/baselines/reference/typesVersions.ambientModules.trace.json +++ b/tests/baselines/reference/typesVersions.ambientModules.trace.json @@ -2,7 +2,7 @@ "======== Resolving module 'ext' from 'tests/cases/conformance/moduleResolution/main.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'ext' from 'node_modules' folder, target file type 'TypeScript'.", - "'package.json' has 'typesVersion['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", + "'package.json' has 'typesVersions['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", "'package.json' does not have a 'typings' field.", "'package.json' has 'types' field 'index' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/index'.", "Found 'package.json' at 'tests/cases/conformance/moduleResolution/node_modules/ext/package.json'. Package ID is 'ext/ts3.0/index.d.ts@1.0.0'.", @@ -21,7 +21,7 @@ "======== Resolving module 'ext/other' from 'tests/cases/conformance/moduleResolution/main.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'ext/other' from 'node_modules' folder, target file type 'TypeScript'.", - "'package.json' has 'typesVersion['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", + "'package.json' has 'typesVersions['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", "Found 'package.json' at 'tests/cases/conformance/moduleResolution/node_modules/ext/package.json'. Package ID is 'ext/ts3.0/other/index.d.ts@1.0.0'.", "File 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/other.ts' does not exist.", "File 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/other.tsx' does not exist.", @@ -33,7 +33,7 @@ "Directory 'node_modules' does not exist, skipping all lookups in it.", "Directory '/node_modules' does not exist, skipping all lookups in it.", "Loading module 'ext/other' from 'node_modules' folder, target file type 'JavaScript'.", - "'package.json' has 'typesVersion['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", + "'package.json' has 'typesVersions['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", "Found 'package.json' at 'tests/cases/conformance/moduleResolution/node_modules/ext/package.json'. Package ID is 'ext/ts3.0/other/index.d.ts@1.0.0'.", "File 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/other.js' does not exist.", "File 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/other.jsx' does not exist.", diff --git a/tests/baselines/reference/typesVersions.multiFile.trace.json b/tests/baselines/reference/typesVersions.multiFile.trace.json index 0ef02bb8a02..2508681421b 100644 --- a/tests/baselines/reference/typesVersions.multiFile.trace.json +++ b/tests/baselines/reference/typesVersions.multiFile.trace.json @@ -2,7 +2,7 @@ "======== Resolving module 'ext' from 'tests/cases/conformance/moduleResolution/main.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'ext' from 'node_modules' folder, target file type 'TypeScript'.", - "'package.json' has 'typesVersion['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", + "'package.json' has 'typesVersions['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", "'package.json' does not have a 'typings' field.", "'package.json' has 'types' field 'index' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/index'.", "Found 'package.json' at 'tests/cases/conformance/moduleResolution/node_modules/ext/package.json'. Package ID is 'ext/ts3.0/index.d.ts@1.0.0'.", @@ -21,7 +21,7 @@ "======== Resolving module 'ext/other' from 'tests/cases/conformance/moduleResolution/main.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'ext/other' from 'node_modules' folder, target file type 'TypeScript'.", - "'package.json' has 'typesVersion['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", + "'package.json' has 'typesVersions['3.0']' field 'ts3.0' that references 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0'.", "Found 'package.json' at 'tests/cases/conformance/moduleResolution/node_modules/ext/package.json'. Package ID is 'ext/ts3.0/other/index.d.ts@1.0.0'.", "File 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/other.ts' does not exist.", "File 'tests/cases/conformance/moduleResolution/node_modules/ext/ts3.0/other.tsx' does not exist.",