mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-18 07:29:16 -05:00
Initial support for 'typesVersions'
This commit is contained in:
@@ -2255,7 +2255,7 @@ namespace ts {
|
||||
? Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1
|
||||
: Diagnostics.Try_npm_install_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
|
||||
packageId.name,
|
||||
getMangledNameForScopedPackage(packageId.name))
|
||||
mangleScopedPackageName(packageId.name))
|
||||
: undefined;
|
||||
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
|
||||
errorInfo,
|
||||
|
||||
@@ -3273,7 +3273,7 @@
|
||||
"category": "Message",
|
||||
"code": 6104
|
||||
},
|
||||
"Expected type of '{0}' field in 'package.json' to be 'string', got '{1}'.": {
|
||||
"Expected type of '{0}' field in 'package.json' to be '{1}', got '{2}'.": {
|
||||
"category": "Message",
|
||||
"code": 6105
|
||||
},
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace ts {
|
||||
interface PackageJsonPathFields {
|
||||
typings?: string;
|
||||
types?: string;
|
||||
typesVersions?: MapLike<string>;
|
||||
main?: string;
|
||||
}
|
||||
|
||||
@@ -111,7 +112,7 @@ namespace ts {
|
||||
const fileName = jsonContent[fieldName];
|
||||
if (!isString(fileName)) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof fileName);
|
||||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, "string", typeof fileName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -124,21 +125,65 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function readJson(path: string, host: { readFile(fileName: string): string | undefined }): object {
|
||||
try {
|
||||
const jsonText = host.readFile(path);
|
||||
if (!jsonText) return {};
|
||||
const result = parseConfigFileTextToJson(path, jsonText);
|
||||
if (result.error) {
|
||||
return {};
|
||||
function tryReadPackageJsonTypesVersion(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState): string | undefined {
|
||||
if (!hasProperty(jsonContent, "typesVersions")) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, "typesVersions");
|
||||
}
|
||||
return result.config;
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
// gracefully handle if readFile fails or returns not JSON
|
||||
return {};
|
||||
|
||||
const typesVersions = jsonContent.typesVersions;
|
||||
if (typeof typesVersions !== "object") {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, "typesVersions", "object", typeof typesVersions);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const typeScriptVersion = Version.parse(version);
|
||||
let bestVersion: Version | undefined;
|
||||
let bestVersionKey: string | undefined;
|
||||
for (const key in typesVersions) {
|
||||
if (!hasProperty(typesVersions, key)) continue;
|
||||
|
||||
const keyVersion = Version.tryParse(key);
|
||||
if (keyVersion === undefined) {
|
||||
if (state.traceEnabled) {
|
||||
// TODO(rbuckton): log
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// match the greatest version less than the current TypeScript version
|
||||
if (keyVersion.compareTo(typeScriptVersion) <= 0
|
||||
&& (bestVersion === undefined || keyVersion.compareTo(bestVersion) > 0)) {
|
||||
bestVersion = keyVersion;
|
||||
bestVersionKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bestVersionKey) {
|
||||
if (state.traceEnabled) {
|
||||
// TODO(rbuckton): log
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const bestVersionPath = typesVersions[bestVersionKey];
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return bestVersionPath;
|
||||
}
|
||||
|
||||
export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined {
|
||||
@@ -720,7 +765,6 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, moduleName));
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, state.compilerOptions.baseUrl, candidate);
|
||||
}
|
||||
@@ -892,12 +936,6 @@ namespace ts {
|
||||
return path + "/index.d.ts";
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
|
||||
// if host does not support 'directoryExists' assume that directory will exist
|
||||
return !host.directoryExists || host.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
|
||||
return noPackageId(loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state));
|
||||
}
|
||||
@@ -977,9 +1015,12 @@ namespace ts {
|
||||
}
|
||||
|
||||
function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) {
|
||||
const { packageJsonContent, packageId } = considerPackageJson
|
||||
const { packageJsonContent, packageId, versionPath } = considerPackageJson
|
||||
? getPackageJsonInfo(candidate, "", failedLookupLocations, onlyRecordFailures, state)
|
||||
: { packageJsonContent: undefined, packageId: undefined };
|
||||
: { packageJsonContent: undefined, packageId: undefined, versionPath: undefined };
|
||||
if (versionPath) {
|
||||
candidate = normalizePath(combinePaths(candidate, versionPath));
|
||||
}
|
||||
return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, packageJsonContent));
|
||||
}
|
||||
|
||||
@@ -998,16 +1039,18 @@ namespace ts {
|
||||
failedLookupLocations: Push<string>,
|
||||
onlyRecordFailures: boolean,
|
||||
state: ModuleResolutionState,
|
||||
): { found: boolean, packageJsonContent: PackageJsonPathFields | undefined, packageId: PackageId | undefined } {
|
||||
): { found: boolean, packageJsonContent: PackageJsonPathFields | undefined, packageId: PackageId | undefined, versionPath: string | undefined } {
|
||||
const { host, traceEnabled } = state;
|
||||
const directoryExists = !onlyRecordFailures && directoryProbablyExists(nodeModuleDirectory, host);
|
||||
const packageJsonPath = pathToPackageJson(nodeModuleDirectory);
|
||||
if (directoryExists && host.fileExists(packageJsonPath)) {
|
||||
const packageJsonContent = readJson(packageJsonPath, host) as PackageJson;
|
||||
const versionPath = tryReadPackageJsonTypesVersion(packageJsonContent, nodeModuleDirectory, state);
|
||||
if (subModuleName === "") { // looking up the root - need to handle types/typings/main redirects for subModuleName
|
||||
const path = tryReadPackageJsonFields(/*readTypes*/ true, packageJsonContent, nodeModuleDirectory, state);
|
||||
const versionDirectory = versionPath ? normalizePath(combinePaths(nodeModuleDirectory, versionPath)) : nodeModuleDirectory;
|
||||
const path = tryReadPackageJsonFields(/*readTypes*/ true, packageJsonContent, versionDirectory, state);
|
||||
if (typeof path === "string") {
|
||||
subModuleName = addExtensionAndIndex(path.substring(nodeModuleDirectory.length + 1));
|
||||
subModuleName = addExtensionAndIndex(path.substring(versionDirectory.length + 1));
|
||||
}
|
||||
else {
|
||||
const jsPath = tryReadPackageJsonFields(/*readTypes*/ false, packageJsonContent, nodeModuleDirectory, state);
|
||||
@@ -1021,6 +1064,11 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (versionPath) {
|
||||
// subModuleName = combinePaths(versionPath, subModuleName);
|
||||
// }
|
||||
|
||||
if (!endsWith(subModuleName, Extension.Dts)) {
|
||||
subModuleName = addExtensionAndIndex(subModuleName);
|
||||
}
|
||||
@@ -1035,15 +1083,16 @@ namespace ts {
|
||||
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath);
|
||||
}
|
||||
}
|
||||
return { found: true, packageJsonContent, packageId };
|
||||
return { found: true, packageJsonContent, packageId, versionPath };
|
||||
}
|
||||
else {
|
||||
if (directoryExists && traceEnabled) {
|
||||
trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath);
|
||||
}
|
||||
|
||||
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
|
||||
failedLookupLocations.push(packageJsonPath);
|
||||
return { found: false, packageJsonContent: undefined, packageId: undefined };
|
||||
return { found: false, packageJsonContent: undefined, packageId: undefined, versionPath: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1110,20 +1159,30 @@ namespace ts {
|
||||
}
|
||||
|
||||
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
|
||||
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
|
||||
let candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
|
||||
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
|
||||
let packageJsonContent: PackageJsonPathFields | undefined;
|
||||
let versionPath: string | undefined;
|
||||
let packageId: PackageId | undefined;
|
||||
const packageInfo = getPackageJsonInfo(candidate, "", failedLookupLocations, /*onlyRecordFailures*/ !nodeModulesFolderExists, state);
|
||||
if (packageInfo.found) {
|
||||
({ packageJsonContent, packageId } = packageInfo);
|
||||
({ packageJsonContent, packageId, versionPath } = packageInfo);
|
||||
|
||||
// If package.json supplied a typescript-version prefix path, apply it to the candidate.
|
||||
if (versionPath) {
|
||||
candidate = normalizePath(combinePaths(candidate, versionPath));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const { packageName, rest } = getPackageName(moduleName);
|
||||
const { packageName, rest } = parsePackageName(moduleName);
|
||||
if (rest !== "") { // If "rest" is empty, we just did this search above.
|
||||
const packageRootPath = combinePaths(nodeModulesFolder, packageName);
|
||||
// Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId.
|
||||
packageId = getPackageJsonInfo(packageRootPath, rest, failedLookupLocations, !nodeModulesFolderExists, state).packageId;
|
||||
({ packageId, versionPath } = getPackageJsonInfo(packageRootPath, rest, failedLookupLocations, !nodeModulesFolderExists, state));
|
||||
// If package.json supplied a typescript-version prefix path, apply it to the candidate.
|
||||
if (versionPath) {
|
||||
candidate = normalizePath(combinePaths(packageRootPath, versionPath, rest));
|
||||
}
|
||||
}
|
||||
}
|
||||
const pathAndExtension = loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
|
||||
@@ -1132,7 +1191,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getPackageName(moduleName: string): { packageName: string, rest: string } {
|
||||
export function parsePackageName(moduleName: string): { packageName: string, rest: string } {
|
||||
let idx = moduleName.indexOf(directorySeparator);
|
||||
if (moduleName[0] === "@") {
|
||||
idx = moduleName.indexOf(directorySeparator, idx + 1);
|
||||
@@ -1141,14 +1200,14 @@ namespace ts {
|
||||
}
|
||||
|
||||
function loadModuleFromNodeModules(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
|
||||
return loadModuleFromNodeModulesWorker(extensions, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ false, cache);
|
||||
return loadModuleFromNearestNodeModules(extensions, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ false, cache);
|
||||
}
|
||||
function loadModuleFromNodeModulesAtTypes(moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): SearchResult<Resolved> {
|
||||
// Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly.
|
||||
return loadModuleFromNodeModulesWorker(Extensions.DtsOnly, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ true, /*cache*/ undefined);
|
||||
return loadModuleFromNearestNodeModules(Extensions.DtsOnly, moduleName, directory, failedLookupLocations, state, /*typesOnly*/ true, /*cache*/ undefined);
|
||||
}
|
||||
|
||||
function loadModuleFromNodeModulesWorker(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
|
||||
function loadModuleFromNearestNodeModules(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
|
||||
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
|
||||
return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => {
|
||||
if (getBaseFileName(ancestorDirectory) !== "node_modules") {
|
||||
@@ -1156,13 +1215,12 @@ namespace ts {
|
||||
if (resolutionFromCache) {
|
||||
return resolutionFromCache;
|
||||
}
|
||||
return toSearchResult(loadModuleFromNodeModulesOneLevel(extensions, moduleName, ancestorDirectory, failedLookupLocations, state, typesOnly));
|
||||
return toSearchResult(loadModuleFromImmediateNodeModules(extensions, moduleName, ancestorDirectory, failedLookupLocations, state, typesOnly));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Load a module from a single node_modules directory, but not from any ancestors' node_modules directories. */
|
||||
function loadModuleFromNodeModulesOneLevel(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly = false): Resolved | undefined {
|
||||
function loadModuleFromImmediateNodeModules(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly: boolean): Resolved | undefined {
|
||||
const nodeModulesFolder = combinePaths(directory, "node_modules");
|
||||
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
|
||||
if (!nodeModulesFolderExists && state.traceEnabled) {
|
||||
@@ -1182,7 +1240,7 @@ namespace ts {
|
||||
}
|
||||
nodeModulesAtTypesExists = false;
|
||||
}
|
||||
return loadModuleFromNodeModulesFolder(Extensions.DtsOnly, mangleScopedPackage(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, failedLookupLocations, state);
|
||||
return loadModuleFromNodeModulesFolder(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, failedLookupLocations, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,8 +1248,8 @@ namespace ts {
|
||||
const mangledScopedPackageSeparator = "__";
|
||||
|
||||
/** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */
|
||||
function mangleScopedPackage(packageName: string, state: ModuleResolutionState): string {
|
||||
const mangled = getMangledNameForScopedPackage(packageName);
|
||||
function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string {
|
||||
const mangled = mangleScopedPackageName(packageName);
|
||||
if (state.traceEnabled && mangled !== packageName) {
|
||||
trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled);
|
||||
}
|
||||
@@ -1200,11 +1258,11 @@ namespace ts {
|
||||
|
||||
/* @internal */
|
||||
export function getTypesPackageName(packageName: string): string {
|
||||
return `@types/${getMangledNameForScopedPackage(packageName)}`;
|
||||
return `@types/${mangleScopedPackageName(packageName)}`;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getMangledNameForScopedPackage(packageName: string): string {
|
||||
export function mangleScopedPackageName(packageName: string): string {
|
||||
if (startsWith(packageName, "@")) {
|
||||
const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator);
|
||||
if (replaceSlash !== packageName) {
|
||||
@@ -1215,16 +1273,16 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getPackageNameFromAtTypesDirectory(mangledName: string): string {
|
||||
export function getPackageNameFromTypesPackageName(mangledName: string): string {
|
||||
const withoutAtTypePrefix = removePrefix(mangledName, "@types/");
|
||||
if (withoutAtTypePrefix !== mangledName) {
|
||||
return getUnmangledNameForScopedPackage(withoutAtTypePrefix);
|
||||
return unmangleScopedPackageName(withoutAtTypePrefix);
|
||||
}
|
||||
return mangledName;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function getUnmangledNameForScopedPackage(typesPackageName: string): string {
|
||||
export function unmangleScopedPackageName(typesPackageName: string): string {
|
||||
return stringContains(typesPackageName, mangledScopedPackageSeparator) ?
|
||||
"@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) :
|
||||
typesPackageName;
|
||||
@@ -1295,7 +1353,7 @@ namespace ts {
|
||||
}
|
||||
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
|
||||
const failedLookupLocations: string[] = [];
|
||||
const resolved = loadModuleFromNodeModulesOneLevel(Extensions.DtsOnly, moduleName, globalCache, failedLookupLocations, state);
|
||||
const resolved = loadModuleFromImmediateNodeModules(Extensions.DtsOnly, moduleName, globalCache, failedLookupLocations, state, /*typesOnly*/ false);
|
||||
return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations);
|
||||
}
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace ts.moduleSpecifiers {
|
||||
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
|
||||
if (!startsWith(sourceDirectory, getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)))) return undefined;
|
||||
// If the module was found in @types, get the actual Node package name
|
||||
return getPackageNameFromAtTypesDirectory(moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1));
|
||||
return getPackageNameFromTypesPackageName(moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1));
|
||||
|
||||
function getDirectoryOrExtensionlessFileName(path: string): string {
|
||||
// If the file is the main module, it can be imported by the package name
|
||||
|
||||
156
src/compiler/semver.ts
Normal file
156
src/compiler/semver.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
// Per 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;
|
||||
|
||||
// Per 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:
|
||||
//
|
||||
// > 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:
|
||||
//
|
||||
// > Numeric identifiers MUST NOT include leading zeroes.
|
||||
const numericIdentifierRegExp = /^(0|[1-9]\d*)$/;
|
||||
|
||||
/**
|
||||
* Describes a precise semantic version number, per https://semver.org
|
||||
*/
|
||||
export class Version {
|
||||
static readonly zero = new Version(0);
|
||||
|
||||
readonly major: number;
|
||||
readonly minor: number;
|
||||
readonly patch: number;
|
||||
readonly prerelease: ReadonlyArray<string>;
|
||||
readonly build: ReadonlyArray<string>;
|
||||
|
||||
constructor(major: number, minor = 0, patch = 0, prerelease = "", build = "") {
|
||||
Debug.assert(major >= 0, "Invalid argument: major");
|
||||
Debug.assert(minor >= 0, "Invalid argument: minor");
|
||||
Debug.assert(patch >= 0, "Invalid argument: patch");
|
||||
Debug.assert(!prerelease || prereleaseRegExp.test(prerelease), "Invalid argument: prerelease");
|
||||
Debug.assert(!build || buildRegExp.test(build), "Invalid argument: build");
|
||||
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));
|
||||
}
|
||||
|
||||
static tryParse(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 new Version(+major, +minor, +patch, prerelease, build);
|
||||
}
|
||||
|
||||
static compare(left: Version | undefined, right: Version | undefined, compareBuildMetadata?: boolean) {
|
||||
// Per 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.
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
|
||||
toString() {
|
||||
let result = `${this.major}.${this.minor}.${this.patch}`;
|
||||
if (this.prerelease) result += `-${this.prerelease.join(".")}`;
|
||||
if (this.build) result += `+${this.build.join(".")}`;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function compareVersionFragments(left: ReadonlyArray<string>, right: ReadonlyArray<string>, compareNumericIdentifiers: boolean) {
|
||||
// Per 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:
|
||||
//
|
||||
// > 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
|
||||
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);
|
||||
if (leftIsNumeric || rightIsNumeric) {
|
||||
// Per 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:
|
||||
//
|
||||
// > 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:
|
||||
//
|
||||
// > 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:
|
||||
//
|
||||
// > 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);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
"files": [
|
||||
"core.ts",
|
||||
"performance.ts",
|
||||
"semver.ts",
|
||||
|
||||
"types.ts",
|
||||
"sys.ts",
|
||||
|
||||
@@ -3940,6 +3940,27 @@ namespace ts {
|
||||
return getStringFromExpandedCharCodes(expandedCharCodes);
|
||||
}
|
||||
|
||||
export function readJson(path: string, host: { readFile(fileName: string): string | undefined }): object {
|
||||
try {
|
||||
const jsonText = host.readFile(path);
|
||||
if (!jsonText) return {};
|
||||
const result = parseConfigFileTextToJson(path, jsonText);
|
||||
if (result.error) {
|
||||
return {};
|
||||
}
|
||||
return result.config;
|
||||
}
|
||||
catch (e) {
|
||||
// gracefully handle if readFile fails or returns not JSON
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
|
||||
// if host does not support 'directoryExists' assume that directory will exist
|
||||
return !host.directoryExists || host.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
const carriageReturnLineFeed = "\r\n";
|
||||
const lineFeed = "\n";
|
||||
export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, getNewLine?: () => string): string {
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace ts.codefix {
|
||||
|
||||
function getTypesPackageNameToInstall(host: LanguageServiceHost, sourceFile: SourceFile, pos: number, diagCode: number): string | undefined {
|
||||
const moduleName = cast(getTokenAtPosition(sourceFile, pos), isStringLiteral).text;
|
||||
const { packageName } = getPackageName(moduleName);
|
||||
const { packageName } = parsePackageName(moduleName);
|
||||
return diagCode === errorCodeCannotFindModule
|
||||
? (JsTyping.nodeCoreModules.has(packageName) ? "@types/node" : undefined)
|
||||
: (host.isKnownTypesPackageName!(packageName) ? getTypesPackageName(packageName) : undefined); // TODO: GH#18217
|
||||
|
||||
@@ -329,7 +329,7 @@ namespace ts.Completions.PathCompletions {
|
||||
const seen = createMap<true>();
|
||||
if (options.types) {
|
||||
for (const typesName of options.types) {
|
||||
const moduleName = getUnmangledNameForScopedPackage(typesName);
|
||||
const moduleName = unmangleScopedPackageName(typesName);
|
||||
pushResult(moduleName);
|
||||
}
|
||||
}
|
||||
@@ -363,7 +363,7 @@ namespace ts.Completions.PathCompletions {
|
||||
for (let typeDirectory of directories) {
|
||||
typeDirectory = normalizePath(typeDirectory);
|
||||
const directoryName = getBaseFileName(typeDirectory);
|
||||
const moduleName = getUnmangledNameForScopedPackage(directoryName);
|
||||
const moduleName = unmangleScopedPackageName(directoryName);
|
||||
pushResult(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user