Merge branch 'master' into typedBindCallApply

# Conflicts:
#	src/compiler/diagnosticMessages.json
This commit is contained in:
Anders Hejlsberg
2018-09-11 10:48:18 -07:00
318 changed files with 3574 additions and 868 deletions

View File

@@ -2295,12 +2295,12 @@ namespace ts {
? chainDiagnosticMessages(
/*details*/ undefined,
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,
packageId.name, getMangledNameForScopedPackage(packageId.name))
packageId.name, mangleScopedPackageName(packageId.name))
: chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Try_npm_install_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
moduleReference,
getMangledNameForScopedPackage(packageId.name))
mangleScopedPackageName(packageId.name))
: undefined;
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
errorInfo,
@@ -5910,9 +5910,9 @@ namespace ts {
for (const declaration of symbol.declarations) {
if (declaration.kind === SyntaxKind.EnumDeclaration) {
for (const member of (<EnumDeclaration>declaration).members) {
const memberType = getLiteralType(getEnumMemberValue(member)!, enumCount, getSymbolOfNode(member)); // TODO: GH#18217
const memberType = getFreshTypeOfLiteralType(getLiteralType(getEnumMemberValue(member)!, enumCount, getSymbolOfNode(member))); // TODO: GH#18217
getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType;
memberTypeList.push(memberType);
memberTypeList.push(getRegularTypeOfLiteralType(memberType));
}
}
}
@@ -8249,7 +8249,7 @@ namespace ts {
const res = tryGetDeclaredTypeOfSymbol(symbol);
if (res) {
return checkNoTypeArguments(node, symbol) ?
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : res :
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : getRegularTypeOfLiteralType(res) :
errorType;
}
@@ -12743,7 +12743,7 @@ namespace ts {
}
function getWidenedLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
return type.flags & TypeFlags.EnumLiteral && type.flags & TypeFlags.FreshLiteral ? getBaseTypeOfEnumLiteralType(<LiteralType>type) :
type.flags & TypeFlags.StringLiteral && type.flags & TypeFlags.FreshLiteral ? stringType :
type.flags & TypeFlags.NumberLiteral && type.flags & TypeFlags.FreshLiteral ? numberType :
type.flags & TypeFlags.BooleanLiteral ? booleanType :
@@ -19217,12 +19217,16 @@ namespace ts {
let aboveArgCount = Number.POSITIVE_INFINITY;
let argCount = args.length;
let closestSignature: Signature | undefined;
for (const sig of signatures) {
const minCount = getMinArgumentCount(sig);
const maxCount = getParameterCount(sig);
if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount;
if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount;
min = Math.min(min, minCount);
if (minCount < min) {
min = minCount;
closestSignature = sig;
}
max = Math.max(max, maxCount);
}
@@ -19235,16 +19239,29 @@ namespace ts {
argCount--;
}
let related: DiagnosticWithLocation | undefined;
if (closestSignature && getMinArgumentCount(closestSignature) > argCount && closestSignature.declaration) {
const paramDecl = closestSignature.declaration.parameters[closestSignature.thisParameter ? argCount + 1 : argCount];
if (paramDecl) {
related = createDiagnosticForNode(
paramDecl,
isBindingPattern(paramDecl.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided : Diagnostics.An_argument_for_0_was_not_provided,
!paramDecl.name ? argCount : !isBindingPattern(paramDecl.name) ? idText(getFirstIdentifier(paramDecl.name)) : undefined
);
}
}
if (hasRestParameter || hasSpreadArgument) {
const error = hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 :
Diagnostics.Expected_0_arguments_but_got_1_or_more;
return createDiagnosticForNode(node, error, paramRange, argCount);
const diagnostic = createDiagnosticForNode(node, error, paramRange, argCount);
return related ? addRelatedInfo(diagnostic, related) : diagnostic;
}
if (min < argCount && argCount < max) {
return createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount);
}
return createDiagnosticForNode(node, Diagnostics.Expected_0_arguments_but_got_1, paramRange, argCount);
const diagnostic = createDiagnosticForNode(node, Diagnostics.Expected_0_arguments_but_got_1, paramRange, argCount);
return related ? addRelatedInfo(diagnostic, related) : diagnostic;
}
function getTypeArgumentArityError(node: Node, signatures: ReadonlyArray<Signature>, typeArguments: NodeArray<TypeNode>) {
@@ -28286,13 +28303,14 @@ namespace ts {
return false;
}
function literalTypeToNode(type: LiteralType): Expression {
return createLiteral(type.value);
function literalTypeToNode(type: LiteralType, enclosing: Node): Expression {
const enumResult = type.flags & TypeFlags.EnumLiteral && nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing);
return enumResult || createLiteral(type.value);
}
function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration) {
const type = getTypeOfSymbol(getSymbolOfNode(node));
return literalTypeToNode(<LiteralType>type);
return literalTypeToNode(<LiteralType>type, node);
}
function createResolver(): EmitResolver {
@@ -29657,13 +29675,20 @@ namespace ts {
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.NumericLiteral;
}
function isSimpleLiteralEnumReference(expr: Expression) {
if (
(isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) &&
isEntityNameExpression(expr.expression)
) return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral);
}
function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) {
if (node.initializer) {
const isInvalidInitializer = !isStringOrNumberLiteralExpression(node.initializer);
const isInvalidInitializer = !(isStringOrNumberLiteralExpression(node.initializer) || isSimpleLiteralEnumReference(node.initializer));
const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node);
if (isConstOrReadonly && !node.type) {
if (isInvalidInitializer) {
return grammarErrorOnNode(node.initializer!, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal);
return grammarErrorOnNode(node.initializer!, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference);
}
}
else {

View File

@@ -65,6 +65,7 @@ namespace ts {
/* @internal */
namespace ts {
export const emptyArray: never[] = [] as never[];
/** Create a MapLike with good performance. */
function createDictionaryObject<T>(): MapLike<T> {

View File

@@ -835,7 +835,7 @@
"category": "Error",
"code": 1253
},
"A 'const' initializer in an ambient context must be a string or numeric literal.": {
"A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference.": {
"category": "Error",
"code": 1254
},
@@ -3294,7 +3294,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
},
@@ -3692,10 +3692,34 @@
"category": "Error",
"code": 6205
},
"Enable strict 'bind', 'call', and 'apply' methods on functions.": {
"'package.json' has a 'typesVersions' field with version-specific path mappings.": {
"category": "Message",
"code": 6206
},
"'package.json' does not have a 'typesVersions' entry that matches version '{0}'.": {
"category": "Message",
"code": 6207
},
"'package.json' has a 'typesVersions' entry '{0}' that matches compiler version '{1}', looking for a pattern to match module name '{2}'.": {
"category": "Message",
"code": 6208
},
"'package.json' has a 'typesVersions' entry '{0}' that is not a valid semver range.": {
"category": "Message",
"code": 6209
},
"An argument for '{0}' was not provided.": {
"category": "Message",
"code": 6210
},
"An argument matching this binding pattern was not provided.": {
"category": "Message",
"code": 6211
},
"Enable strict 'bind', 'call', and 'apply' methods on functions.": {
"category": "Message",
"code": 6212
},
"Projects to reference": {
"category": "Message",

View File

@@ -2818,7 +2818,8 @@ namespace ts {
const parameter = singleOrUndefined(parameters);
return parameter
&& parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter
&& !(isArrowFunction(parentNode) && parentNode.type) // arrow function may not have return type annotation
&& isArrowFunction(parentNode) // only arrow functions may have simple arrow head
&& !parentNode.type // arrow function may not have return type annotation
&& !some(parentNode.decorators) // parent may not have decorators
&& !some(parentNode.modifiers) // parent may not have modifiers
&& !some(parentNode.typeParameters) // parent may not have type parameters

View File

@@ -24,6 +24,13 @@ namespace ts {
return withPackageId(/*packageId*/ undefined, r);
}
function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined {
if (r) {
Debug.assert(r.packageId === undefined);
return { path: r.path, ext: r.extension };
}
}
/** Result of trying to resolve a module. */
interface Resolved {
path: string;
@@ -82,12 +89,14 @@ namespace ts {
host: ModuleResolutionHost;
compilerOptions: CompilerOptions;
traceEnabled: boolean;
failedLookupLocations: Push<string>;
}
/** Just the fields that we use for module resolution. */
interface PackageJsonPathFields {
typings?: string;
types?: string;
typesVersions?: MapLike<MapLike<string[]>>;
main?: string;
}
@@ -96,48 +105,111 @@ namespace ts {
version?: string;
}
/** Reads from "main" or "types"/"typings" depending on `extensions`. */
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJsonPathFields, baseDirectory: string, state: ModuleResolutionState): string | undefined {
return readTypes ? tryReadFromField("typings") || tryReadFromField("types") : tryReadFromField("main");
type MatchingKeys<TRecord, TMatch, K extends keyof TRecord = keyof TRecord> = K extends (TRecord[K] extends TMatch ? K : never) ? K : never;
function tryReadFromField(fieldName: "typings" | "types" | "main"): string | undefined {
if (!hasProperty(jsonContent, fieldName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName);
}
return;
}
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);
}
return;
}
const path = normalizePath(combinePaths(baseDirectory, fileName));
function readPackageJsonField<TMatch, K extends MatchingKeys<PackageJson, string | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined;
function readPackageJsonField<K extends MatchingKeys<PackageJson, object | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined;
function readPackageJsonField<K extends keyof PackageJson>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined {
if (!hasProperty(jsonContent, fieldName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path);
trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName);
}
return path;
return;
}
const value = jsonContent[fieldName];
if (typeof value !== typeOfTag || value === null) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value);
}
return;
}
return value;
}
/* @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 {};
}
return result.config;
function readPackageJsonPathField<K extends "typings" | "types" | "main">(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined {
const fileName = readPackageJsonField(jsonContent, fieldName, "string", state);
if (fileName === undefined) return;
const path = normalizePath(combinePaths(baseDirectory, fileName));
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path);
}
catch (e) {
// gracefully handle if readFile fails or returns not JSON
return {};
return path;
}
function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) {
return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state)
|| readPackageJsonPathField(jsonContent, "types", baseDirectory, state);
}
function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) {
return readPackageJsonPathField(jsonContent, "main", baseDirectory, state);
}
function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) {
const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state);
if (typesVersions === undefined) return;
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings);
}
return typesVersions;
}
interface VersionPaths {
version: string;
paths: MapLike<string[]>;
}
function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined {
const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state);
if (typesVersions === undefined) return;
if (state.traceEnabled) {
for (const key in typesVersions) {
if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) {
trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key);
}
}
}
const result = getPackageJsonTypesVersionsPaths(typesVersions);
if (!result) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor);
}
return;
}
const { version: bestVersionKey, paths: bestVersionPaths } = result;
if (typeof bestVersionPaths !== "object") {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths);
}
return;
}
return result;
}
let typeScriptVersion: Version | undefined;
/* @internal */
export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike<MapLike<string[]>>) {
if (!typeScriptVersion) typeScriptVersion = new Version(version);
for (const key in typesVersions) {
if (!hasProperty(typesVersions, key)) continue;
const keyRange = VersionRange.tryParse(key);
if (keyRange === undefined) {
continue;
}
// return the first entry whose range matches the current compiler version.
if (keyRange.test(typeScriptVersion)) {
return { version: key, paths: typesVersions[key] };
}
}
}
@@ -188,7 +260,8 @@ namespace ts {
*/
export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(options, host);
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled };
const failedLookupLocations: string[] = [];
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations };
const typeRoots = getEffectiveTypeRoots(options, host);
if (traceEnabled) {
@@ -210,8 +283,6 @@ namespace ts {
}
}
const failedLookupLocations: string[] = [];
let resolved = primaryLookup();
let primary = true;
if (!resolved) {
@@ -247,7 +318,7 @@ namespace ts {
trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory);
}
return resolvedTypeScriptOnly(
loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, failedLookupLocations,
loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate,
!directoryExists, moduleResolutionState));
});
}
@@ -266,7 +337,7 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
}
const result = loadModuleFromNodeModules(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState, /*cache*/ undefined);
const result = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined);
const resolvedFile = resolvedTypeScriptOnly(result && result.value);
if (!resolvedFile && traceEnabled) {
trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName);
@@ -304,7 +375,7 @@ namespace ts {
if (host.directoryExists(root)) {
for (const typeDirectivePath of host.getDirectories(root)) {
const normalized = normalizePath(typeDirectivePath);
const packageJsonPath = pathToPackageJson(combinePaths(root, normalized));
const packageJsonPath = combinePaths(root, normalized, "package.json");
// `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types.
// See `createNotNeededPackageJSON` in the types-publisher` repo.
// tslint:disable-next-line:no-null-keyword
@@ -527,7 +598,7 @@ namespace ts {
* 'typings' entry or file 'index' with some supported extension
* - Classic loader will only try to interpret '/a/b/c' as file.
*/
type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined;
type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined;
/**
* Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to
@@ -590,18 +661,18 @@ namespace ts {
* entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location.
*/
function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
state: ModuleResolutionState): Resolved | undefined {
if (!isExternalModuleNameRelative(moduleName)) {
return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, failedLookupLocations, state);
return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state);
}
else {
return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, failedLookupLocations, state);
return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state);
}
}
function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
state: ModuleResolutionState): Resolved | undefined {
if (!state.compilerOptions.rootDirs) {
return undefined;
@@ -646,7 +717,7 @@ namespace ts {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate);
}
const resolvedFileName = loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(containingDirectory, state.host), state);
const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state);
if (resolvedFileName) {
return resolvedFileName;
}
@@ -665,7 +736,7 @@ namespace ts {
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate);
}
const baseDirectory = getDirectoryPath(candidate);
const resolvedFileName = loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(baseDirectory, state.host), state);
const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state);
if (resolvedFileName) {
return resolvedFileName;
}
@@ -677,60 +748,28 @@ namespace ts {
return undefined;
}
function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
if (!state.compilerOptions.baseUrl) {
function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined {
const { baseUrl, paths } = state.compilerOptions;
if (!baseUrl) {
return undefined;
}
if (state.traceEnabled) {
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, state.compilerOptions.baseUrl, moduleName);
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName);
}
// string is for exact match
let matchedPattern: Pattern | string | undefined;
if (state.compilerOptions.paths) {
if (paths) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
}
matchedPattern = matchPatternOrExact(getOwnKeys(state.compilerOptions.paths), moduleName);
}
if (matchedPattern) {
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
const resolved = tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state);
if (resolved) {
return resolved.value;
}
return forEach(state.compilerOptions.paths![matchedPatternText], subst => {
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl!, path));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
}
// A path mapping may have an extension, in contrast to an import, which should omit it.
const extension = tryGetExtensionFromPath(candidate);
if (extension !== undefined) {
const path = tryFile(candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state);
if (path !== undefined) {
return noPackageId({ path, ext: extension });
}
}
return loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
});
}
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);
}
return loader(extensions, candidate, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
const candidate = normalizePath(combinePaths(baseUrl, moduleName));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate);
}
}
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, /*jsOnly*/ false);
return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
}
/**
@@ -748,11 +787,15 @@ namespace ts {
return resolvedModule.resolvedFileName;
}
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, /*jsOnly*/ false);
}
function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, jsOnly: boolean): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
const failedLookupLocations: string[] = [];
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
const result = jsOnly ?
tryResolve(Extensions.JavaScript) :
@@ -766,8 +809,8 @@ namespace ts {
return { resolvedModule: undefined, failedLookupLocations };
function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> {
const loader: ResolutionKindSpecificLoader = (extensions, candidate, failedLookupLocations, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ true);
const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, failedLookupLocations, state);
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true);
const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state);
if (resolved) {
return toSearchResult({ resolved, isExternalLibraryImport: stringContains(resolved.path, nodeModulesPathPart) });
}
@@ -776,7 +819,7 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]);
}
const resolved = loadModuleFromNodeModules(extensions, moduleName, containingDirectory, failedLookupLocations, state, cache);
const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache);
if (!resolved) return undefined;
let resolvedValue = resolved.value;
@@ -790,7 +833,7 @@ namespace ts {
}
else {
const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName));
const resolved = nodeLoadModuleByRelativeName(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true);
const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true);
// Treat explicit "node_modules" import as an external library import.
return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") });
}
@@ -810,7 +853,7 @@ namespace ts {
return real;
}
function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined {
function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined {
if (state.traceEnabled) {
trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]);
}
@@ -824,10 +867,11 @@ namespace ts {
onlyRecordFailures = true;
}
}
const resolvedFromFile = loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state);
const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state);
if (resolvedFromFile) {
const nm = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile) : undefined;
const packageId = nm && getPackageJsonInfo(nm.packageDirectory, nm.subModuleName, failedLookupLocations, /*onlyRecordFailures*/ false, state).packageId;
const packageInfo = nm && getPackageJsonInfo(nm.packageDirectory, nm.subModuleName, /*onlyRecordFailures*/ false, state);
const packageId = packageInfo && packageInfo.packageId;
return withPackageId(packageId, resolvedFromFile);
}
}
@@ -840,7 +884,7 @@ namespace ts {
onlyRecordFailures = true;
}
}
return loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, considerPackageJson);
return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson);
}
/*@internal*/
@@ -886,34 +930,28 @@ namespace ts {
if (endsWith(path, ".d.ts")) {
return path;
}
if (endsWith(path, "/index")) {
if (path === "index" || endsWith(path, "/index")) {
return path + ".d.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));
function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state));
}
/**
* @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
*/
function loadModuleFromFile(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
if (extensions === Extensions.Json) {
const extensionLess = tryRemoveExtension(candidate, Extension.Json);
return extensionLess === undefined ? undefined : tryAddingExtensions(extensionLess, extensions, failedLookupLocations, onlyRecordFailures, state);
return extensionLess === undefined ? undefined : tryAddingExtensions(extensionLess, extensions, onlyRecordFailures, state);
}
// First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts"
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, failedLookupLocations, onlyRecordFailures, state);
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, onlyRecordFailures, state);
if (resolvedByAddingExtension) {
return resolvedByAddingExtension;
}
@@ -926,12 +964,12 @@ namespace ts {
const extension = candidate.substring(extensionless.length);
trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension);
}
return tryAddingExtensions(extensionless, extensions, failedLookupLocations, onlyRecordFailures, state);
return tryAddingExtensions(extensionless, extensions, onlyRecordFailures, state);
}
}
/** Try to return an existing file that adds one of the `extensions` to `candidate`. */
function tryAddingExtensions(candidate: string, extensions: Extensions, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
function tryAddingExtensions(candidate: string, extensions: Extensions, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
if (!onlyRecordFailures) {
// check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
const directory = getDirectoryPath(candidate);
@@ -952,13 +990,13 @@ namespace ts {
}
function tryExtension(ext: Extension): PathAndExtension | undefined {
const path = tryFile(candidate + ext, failedLookupLocations, onlyRecordFailures, state);
const path = tryFile(candidate + ext, onlyRecordFailures, state);
return path === undefined ? undefined : { path, ext };
}
}
/** Return the file if it exists. */
function tryFile(fileName: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
if (!onlyRecordFailures) {
if (state.host.fileExists(fileName)) {
if (state.traceEnabled) {
@@ -972,47 +1010,48 @@ namespace ts {
}
}
}
failedLookupLocations.push(fileName);
state.failedLookupLocations.push(fileName);
return undefined;
}
function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) {
const { packageJsonContent, packageId } = considerPackageJson
? getPackageJsonInfo(candidate, "", failedLookupLocations, onlyRecordFailures, state)
: { packageJsonContent: undefined, packageId: undefined };
return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, packageJsonContent));
function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) {
const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, "", onlyRecordFailures, state) : undefined;
const packageId = packageInfo && packageInfo.packageId;
const packageJsonContent = packageInfo && packageInfo.packageJsonContent;
const versionPaths = packageJsonContent && readPackageJsonTypesVersionPaths(packageJsonContent, state);
return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths));
}
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, packageJsonContent: PackageJsonPathFields | undefined): PathAndExtension | undefined {
const fromPackageJson = packageJsonContent && loadModuleFromPackageJson(packageJsonContent, extensions, candidate, failedLookupLocations, state);
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, packageJsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined {
const fromPackageJson = packageJsonContent && loadModuleFromPackageJson(packageJsonContent, versionPaths, extensions, candidate, state);
if (fromPackageJson) {
return fromPackageJson;
}
const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host);
return loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocations, !directoryExists, state);
return loadModuleFromFile(extensions, combinePaths(candidate, "index"), !directoryExists, state);
}
function getPackageJsonInfo(
nodeModuleDirectory: string,
subModuleName: string,
failedLookupLocations: Push<string>,
onlyRecordFailures: boolean,
state: ModuleResolutionState,
): { found: boolean, packageJsonContent: PackageJsonPathFields | undefined, packageId: PackageId | undefined } {
interface PackageJsonInfo {
packageJsonContent: PackageJsonPathFields | undefined;
packageId: PackageId | undefined;
versionPaths: VersionPaths | undefined;
}
function getPackageJsonInfo(packageDirectory: string, subModuleName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
const { host, traceEnabled } = state;
const directoryExists = !onlyRecordFailures && directoryProbablyExists(nodeModuleDirectory, host);
const packageJsonPath = pathToPackageJson(nodeModuleDirectory);
const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host);
const packageJsonPath = combinePaths(packageDirectory, "package.json");
if (directoryExists && host.fileExists(packageJsonPath)) {
const packageJsonContent = readJson(packageJsonPath, host) as PackageJson;
if (subModuleName === "") { // looking up the root - need to handle types/typings/main redirects for subModuleName
const path = tryReadPackageJsonFields(/*readTypes*/ true, packageJsonContent, nodeModuleDirectory, state);
const path = readPackageJsonTypesFields(packageJsonContent, packageDirectory, state);
if (typeof path === "string") {
subModuleName = addExtensionAndIndex(path.substring(nodeModuleDirectory.length + 1));
subModuleName = addExtensionAndIndex(path.substring(packageDirectory.length + 1));
}
else {
const jsPath = tryReadPackageJsonFields(/*readTypes*/ false, packageJsonContent, nodeModuleDirectory, state);
if (typeof jsPath === "string" && jsPath.length > nodeModuleDirectory.length) {
const potentialSubModule = jsPath.substring(nodeModuleDirectory.length + 1);
const jsPath = readPackageJsonMainField(packageJsonContent, packageDirectory, state);
if (typeof jsPath === "string" && jsPath.length > packageDirectory.length) {
const potentialSubModule = jsPath.substring(packageDirectory.length + 1);
subModuleName = (forEach(supportedJavascriptExtensions, extension =>
tryRemoveExtension(potentialSubModule, extension)) || potentialSubModule) + Extension.Dts;
}
@@ -1021,9 +1060,12 @@ namespace ts {
}
}
}
if (!endsWith(subModuleName, Extension.Dts)) {
subModuleName = addExtensionAndIndex(subModuleName);
}
const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state);
const packageId: PackageId | undefined = typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string"
? { name: packageJsonContent.name, subModuleName, version: packageJsonContent.version }
: undefined;
@@ -1035,24 +1077,27 @@ namespace ts {
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath);
}
}
return { found: true, packageJsonContent, packageId };
return { packageJsonContent, packageId, versionPaths };
}
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 };
state.failedLookupLocations.push(packageJsonPath);
}
}
function loadModuleFromPackageJson(jsonContent: PackageJsonPathFields, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
let file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript && extensions !== Extensions.Json, jsonContent, candidate, state);
function loadModuleFromPackageJson(jsonContent: PackageJsonPathFields, versionPaths: VersionPaths | undefined, extensions: Extensions, candidate: string, state: ModuleResolutionState): PathAndExtension | undefined {
let file = extensions !== Extensions.JavaScript && extensions !== Extensions.Json
? readPackageJsonTypesFields(jsonContent, candidate, state)
: readPackageJsonMainField(jsonContent, candidate, state);
if (!file) {
if (extensions === Extensions.TypeScript) {
// When resolving typescript modules, try resolving using main field as well
file = tryReadPackageJsonFields(/*readTypes*/ false, jsonContent, candidate, state);
file = readPackageJsonMainField(jsonContent, candidate, state);
if (!file) {
return undefined;
}
@@ -1062,27 +1107,39 @@ namespace ts {
}
}
const onlyRecordFailures = !directoryProbablyExists(getDirectoryPath(file), state.host);
const fromFile = tryFile(file, failedLookupLocations, onlyRecordFailures, state);
if (fromFile) {
const resolved = resolvedIfExtensionMatches(extensions, fromFile);
if (resolved) {
return resolved;
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => {
const fromFile = tryFile(candidate, onlyRecordFailures, state);
if (fromFile) {
const resolved = resolvedIfExtensionMatches(extensions, fromFile);
if (resolved) {
return noPackageId(resolved);
}
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile);
}
}
// Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types"
const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions;
// Don't do package.json lookup recursively, because Node.js' package lookup doesn't.
return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false);
};
const onlyRecordFailures = !directoryProbablyExists(getDirectoryPath(file), state.host);
if (versionPaths && containsPath(candidate, file)) {
const moduleName = getRelativePathFromDirectory(candidate, file, /*ignoreCase*/ false);
if (state.traceEnabled) {
trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile);
trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, moduleName);
}
const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, loader, onlyRecordFailures, state);
if (result) {
return removeIgnoredPackageId(result.value);
}
}
// Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types"
const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions;
// Don't do package.json lookup recursively, because Node.js' package lookup doesn't.
const result = nodeLoadModuleByRelativeName(nextExtensions, file, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ false);
if (result) {
// It won't have a `packageId` set, because we disabled `considerPackageJson`.
Debug.assert(result.packageId === undefined);
return { path: result.path, ext: result.extension };
}
// It won't have a `packageId` set, because we disabled `considerPackageJson`.
return removeIgnoredPackageId(loader(extensions, file, onlyRecordFailures, state));
}
/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */
@@ -1105,34 +1162,8 @@ namespace ts {
}
}
function pathToPackageJson(directory: string): string {
return combinePaths(directory, "package.json");
}
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
const 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 packageId: PackageId | undefined;
const packageInfo = getPackageJsonInfo(candidate, "", failedLookupLocations, /*onlyRecordFailures*/ !nodeModulesFolderExists, state);
if (packageInfo.found) {
({ packageJsonContent, packageId } = packageInfo);
}
else {
const { packageName, rest } = getPackageName(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;
}
}
const pathAndExtension = loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
loadNodeModuleFromDirectoryWorker(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state, packageJsonContent);
return withPackageId(packageId, pathAndExtension);
}
/* @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);
@@ -1140,36 +1171,36 @@ namespace ts {
return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) };
}
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);
}
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);
function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache);
}
function loadModuleFromNodeModulesWorker(extensions: Extensions, moduleName: string, directory: string, failedLookupLocations: Push<string>, state: ModuleResolutionState, typesOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: 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 loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined);
}
function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult<Resolved> {
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => {
if (getBaseFileName(ancestorDirectory) !== "node_modules") {
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state.traceEnabled, state.host, failedLookupLocations);
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state);
if (resolutionFromCache) {
return resolutionFromCache;
}
return toSearchResult(loadModuleFromNodeModulesOneLevel(extensions, moduleName, ancestorDirectory, failedLookupLocations, state, typesOnly));
return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly));
}
});
}
/** 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 loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean): Resolved | undefined {
const nodeModulesFolder = combinePaths(directory, "node_modules");
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
if (!nodeModulesFolderExists && state.traceEnabled) {
trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder);
}
const packageResult = typesOnly ? undefined : loadModuleFromNodeModulesFolder(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, failedLookupLocations, state);
const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state);
if (packageResult) {
return packageResult;
}
@@ -1182,7 +1213,84 @@ namespace ts {
}
nodeModulesAtTypesExists = false;
}
return loadModuleFromNodeModulesFolder(Extensions.DtsOnly, mangleScopedPackage(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, failedLookupLocations, state);
return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state);
}
}
function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState): Resolved | undefined {
const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName));
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
let packageJsonContent: PackageJsonPathFields | undefined;
let packageId: PackageId | undefined;
let versionPaths: VersionPaths | undefined;
const packageInfo = getPackageJsonInfo(candidate, "", !nodeModulesDirectoryExists, state);
if (packageInfo) {
({ packageJsonContent, packageId, versionPaths } = packageInfo);
const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state);
if (fromFile) {
return noPackageId(fromFile);
}
const fromDirectory = loadNodeModuleFromDirectoryWorker(extensions, candidate, !nodeModulesDirectoryExists, state, packageJsonContent, versionPaths);
return withPackageId(packageId, fromDirectory);
}
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => {
const pathAndExtension =
loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) ||
loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths);
return withPackageId(packageId, pathAndExtension);
};
const { packageName, rest } = parsePackageName(moduleName);
if (rest !== "") { // If "rest" is empty, we just did this search above.
const packageDirectory = combinePaths(nodeModulesDirectory, 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 and path mappings.
const packageInfo = getPackageJsonInfo(packageDirectory, rest, !nodeModulesDirectoryExists, state);
if (packageInfo) ({ packageId, versionPaths } = packageInfo);
if (versionPaths) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, rest);
}
const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host);
const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, versionPaths.paths, loader, !packageDirectoryExists, state);
if (fromPaths) {
return fromPaths.value;
}
}
}
return loader(extensions, candidate, !nodeModulesDirectoryExists, state);
}
function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike<string[]>, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult<Resolved> {
const matchedPattern = matchPatternOrExact(getOwnKeys(paths), moduleName);
if (matchedPattern) {
const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName);
const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
}
const resolved = forEach(paths[matchedPatternText], subst => {
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
const candidate = normalizePath(combinePaths(baseDirectory, path));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
}
// A path mapping may have an extension, in contrast to an import, which should omit it.
const extension = tryGetExtensionFromPath(candidate);
if (extension !== undefined) {
const path = tryFile(candidate, onlyRecordFailures, state);
if (path !== undefined) {
return noPackageId({ path, ext: extension });
}
}
return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
});
return { value: resolved };
}
}
@@ -1190,8 +1298,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 +1308,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,36 +1323,36 @@ 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;
}
function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, traceEnabled: boolean, host: ModuleResolutionHost, failedLookupLocations: Push<string>): SearchResult<Resolved> {
function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult<Resolved> {
const result = cache && cache.get(containingDirectory);
if (result) {
if (traceEnabled) {
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
if (state.traceEnabled) {
trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
}
failedLookupLocations.push(...result.failedLookupLocations);
state.failedLookupLocations.push(...result.failedLookupLocations);
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
}
}
export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
const failedLookupLocations: string[] = [];
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
const containingDirectory = getDirectoryPath(containingFile);
const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript);
@@ -1252,7 +1360,7 @@ namespace ts {
return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations);
function tryResolve(extensions: Extensions): SearchResult<Resolved> {
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, failedLookupLocations, state);
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state);
if (resolvedUsingSettings) {
return { value: resolvedUsingSettings };
}
@@ -1261,24 +1369,24 @@ namespace ts {
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
// Climb up parent directories looking for a module.
const resolved = forEachAncestorDirectory(containingDirectory, directory => {
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, traceEnabled, host, failedLookupLocations);
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state);
if (resolutionFromCache) {
return resolutionFromCache;
}
const searchName = normalizePath(combinePaths(directory, moduleName));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, failedLookupLocations, /*onlyRecordFailures*/ false, state));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state));
});
if (resolved) {
return resolved;
}
if (extensions === Extensions.TypeScript) {
// If we didn't find the file normally, look it up in @types.
return loadModuleFromNodeModulesAtTypes(moduleName, containingDirectory, failedLookupLocations, state);
return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state);
}
}
else {
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state));
return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state));
}
}
}
@@ -1293,9 +1401,9 @@ namespace ts {
if (traceEnabled) {
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache);
}
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
const failedLookupLocations: string[] = [];
const resolved = loadModuleFromNodeModulesOneLevel(Extensions.DtsOnly, moduleName, globalCache, failedLookupLocations, state);
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false);
return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations);
}

View File

@@ -235,7 +235,8 @@ namespace ts.moduleSpecifiers {
const suffix = pattern.substr(indexOfStar + 1);
if (relativeToBaseUrl.length >= prefix.length + suffix.length &&
startsWith(relativeToBaseUrl, prefix) &&
endsWith(relativeToBaseUrl, suffix)) {
endsWith(relativeToBaseUrl, suffix) ||
!suffix && relativeToBaseUrl === removeTrailingDirectorySeparator(prefix)) {
const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length);
return key.replace("*", matchedStar);
}
@@ -264,6 +265,26 @@ namespace ts.moduleSpecifiers {
return undefined;
}
const packageRootPath = moduleFileName.substring(0, parts.packageRootIndex);
const packageJsonPath = combinePaths(packageRootPath, "package.json");
const packageJsonContent = host.fileExists!(packageJsonPath)
? JSON.parse(host.readFile!(packageJsonPath)!)
: undefined;
const versionPaths = packageJsonContent && packageJsonContent.typesVersions
? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions)
: undefined;
if (versionPaths) {
const subModuleName = moduleFileName.slice(parts.packageRootIndex + 1);
const fromPaths = tryGetModuleNameFromPaths(
removeFileExtension(subModuleName),
removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options),
versionPaths.paths
);
if (fromPaths !== undefined) {
moduleFileName = combinePaths(moduleFileName.slice(0, parts.packageRootIndex), fromPaths);
}
}
// Simplify the full file path to something that can be resolved by Node.
// If the module could be imported by a directory name, use that directory's name
@@ -274,23 +295,18 @@ namespace ts.moduleSpecifiers {
// If the module was found in @types, get the actual Node package name
const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1);
const packageName = getPackageNameFromAtTypesDirectory(nodeModulesDirectoryName);
const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName);
// For classic resolution, only allow importing from node_modules/@types, not other node_modules
return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName;
function getDirectoryOrExtensionlessFileName(path: string): string {
// If the file is the main module, it can be imported by the package name
const packageRootPath = path.substring(0, parts.packageRootIndex);
const packageJsonPath = combinePaths(packageRootPath, "package.json");
if (host.fileExists!(packageJsonPath)) { // TODO: GH#18217
const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!);
if (packageJsonContent) {
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
if (mainFileRelative) {
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
return packageRootPath;
}
if (packageJsonContent) {
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
if (mainFileRelative) {
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
return packageRootPath;
}
}
}

391
src/compiler/semver.ts Normal file
View File

@@ -0,0 +1,391 @@
/* @internal */
namespace ts {
// 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;
// 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;
// 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;
// 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, https://semver.org
*/
export class Version {
static readonly zero = new Version(0, 0, 0);
readonly major: number;
readonly minor: number;
readonly patch: number;
readonly prerelease: ReadonlyArray<string>;
readonly build: ReadonlyArray<string>;
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");
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 ? prerelease.split(".") : emptyArray;
this.build = build ? build.split(".") : emptyArray;
}
static tryParse(text: string) {
const result = tryParseComponents(text);
if (!result) return undefined;
const { major, minor, patch, prerelease, build } = result;
return new Version(major, minor, patch, prerelease, build);
}
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.
//
// 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 [...]
//
// 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);
}
increment(field: "major" | "minor" | "patch") {
switch (field) {
case "major": return new Version(this.major + 1, 0, 0);
case "minor": return new Version(this.major, this.minor + 1, 0);
case "patch": return new Version(this.major, this.minor, this.patch + 1);
default: return Debug.assertNever(field);
}
}
toString() {
let result = `${this.major}.${this.minor}.${this.patch}`;
if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`;
if (some(this.build)) result += `+${this.build.join(".")}`;
return result;
}
}
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<string>, right: ReadonlyArray<string>) {
// 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;
// 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 = numericIdentifierRegExp.test(leftIdentifier);
const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier);
if (leftIsNumeric || rightIsNumeric) {
// 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;
// https://semver.org/#spec-item-11
// > identifiers consisting of only digits are compared numerically
const result = compareValues(+leftIdentifier, +rightIdentifier);
if (result) return result;
}
else {
// 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;
}
}
// 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);
}
/**
* Describes a semantic version range, per https://github.com/npm/node-semver#ranges
*/
export class VersionRange {
private _alternatives: ReadonlyArray<ReadonlyArray<Comparator>>;
constructor(spec: string) {
this._alternatives = spec ? Debug.assertDefined(parseRange(spec), "Invalid range spec.") : emptyArray;
}
static tryParse(text: string) {
const sets = parseRange(text);
if (sets) {
const range = new VersionRange("");
range._alternatives = sets;
return range;
}
return undefined;
}
test(version: Version | string) {
if (typeof version === "string") version = new Version(version);
return testDisjunction(version, this._alternatives);
}
toString() {
return formatDisjunction(this._alternatives);
}
}
interface Comparator {
readonly operator: "<" | "<=" | ">" | ">=" | "=";
readonly operand: Version;
}
// https://github.com/npm/node-semver#range-grammar
//
// range-set ::= range ( logical-or range ) *
// range ::= hyphen | simple ( ' ' simple ) * | ''
// logical-or ::= ( ' ' ) * '||' ( ' ' ) *
const logicalOrRegExp = /\s*\|\|\s*/g;
const whitespaceRegExp = /\s+/g;
// https://github.com/npm/node-semver#range-grammar
//
// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )?
// xr ::= 'x' | 'X' | '*' | nr
// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) *
// qualifier ::= ( '-' pre )? ( '+' build )?
// pre ::= parts
// build ::= parts
// parts ::= part ( '.' part ) *
// part ::= nr | [-0-9A-Za-z]+
const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i;
// https://github.com/npm/node-semver#range-grammar
//
// hyphen ::= partial ' - ' partial
const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i;
// https://github.com/npm/node-semver#range-grammar
//
// simple ::= primitive | partial | tilde | caret
// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial
// tilde ::= '~' partial
// caret ::= '^' partial
const rangeRegExp = /^\s*(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i;
function parseRange(text: string) {
const alternatives: Comparator[][] = [];
for (const range of text.trim().split(logicalOrRegExp)) {
if (!range) continue;
const comparators: Comparator[] = [];
const match = hyphenRegExp.exec(range);
if (match) {
if (!parseHyphen(match[1], match[2], comparators)) return undefined;
}
else {
for (const simple of range.split(whitespaceRegExp)) {
const match = rangeRegExp.exec(simple);
if (!match || !parseComparator(match[1], match[2], comparators)) return undefined;
}
}
alternatives.push(comparators);
}
return alternatives;
}
function parsePartial(text: string) {
const match = partialRegExp.exec(text);
if (!match) return undefined;
const [, major, minor = "*", patch = "*", prerelease, build] = match;
const version = new Version(
isWildcard(major) ? 0 : parseInt(major, 10),
isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10),
isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10),
prerelease,
build);
return { version, major, minor, patch };
}
function parseHyphen(left: string, right: string, comparators: Comparator[]) {
const leftResult = parsePartial(left);
if (!leftResult) return false;
const rightResult = parsePartial(right);
if (!rightResult) return false;
if (!isWildcard(leftResult.major)) {
comparators.push(createComparator(">=", leftResult.version));
}
if (!isWildcard(rightResult.major)) {
comparators.push(
isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) :
isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) :
createComparator("<=", rightResult.version));
}
return true;
}
function parseComparator(operator: string, text: string, comparators: Comparator[]) {
const result = parsePartial(text);
if (!result) return false;
const { version, major, minor, patch } = result;
if (!isWildcard(major)) {
switch (operator) {
case "~":
comparators.push(createComparator(">=", version));
comparators.push(createComparator("<", version.increment(
isWildcard(minor) ? "major" :
"minor")));
break;
case "^":
comparators.push(createComparator(">=", version));
comparators.push(createComparator("<", version.increment(
version.major > 0 || isWildcard(minor) ? "major" :
version.minor > 0 || isWildcard(patch) ? "minor" :
"patch")));
break;
case "<":
case ">=":
comparators.push(createComparator(operator, version));
break;
case "<=":
case ">":
comparators.push(
isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major")) :
isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor")) :
createComparator(operator, version));
break;
case "=":
case undefined:
if (isWildcard(minor) || isWildcard(patch)) {
comparators.push(createComparator(">=", version));
comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor")));
}
else {
comparators.push(createComparator("=", version));
}
break;
default:
// unrecognized
return false;
}
}
else if (operator === "<" || operator === ">") {
comparators.push(createComparator("<", Version.zero));
}
return true;
}
function isWildcard(part: string) {
return part === "*" || part === "x" || part === "X";
}
function createComparator(operator: Comparator["operator"], operand: Version) {
return { operator, operand };
}
function testDisjunction(version: Version, alternatives: ReadonlyArray<ReadonlyArray<Comparator>>) {
// an empty disjunction is treated as "*" (all versions)
if (alternatives.length === 0) return true;
for (const alternative of alternatives) {
if (testAlternative(version, alternative)) return true;
}
return false;
}
function testAlternative(version: Version, comparators: ReadonlyArray<Comparator>) {
for (const comparator of comparators) {
if (!testComparator(version, comparator.operator, comparator.operand)) return false;
}
return true;
}
function testComparator(version: Version, operator: Comparator["operator"], operand: Version) {
const cmp = version.compareTo(operand);
switch (operator) {
case "<": return cmp < 0;
case "<=": return cmp <= 0;
case ">": return cmp > 0;
case ">=": return cmp >= 0;
case "=": return cmp === 0;
default: return Debug.assertNever(operator);
}
}
function formatDisjunction(alternatives: ReadonlyArray<ReadonlyArray<Comparator>>) {
return map(alternatives, formatAlternative).join(" || ") || "*";
}
function formatAlternative(comparators: ReadonlyArray<Comparator>) {
return map(comparators, formatComparator).join(" ");
}
function formatComparator(comparator: Comparator) {
return `${comparator.operator}${comparator.operand}`;
}
}

View File

@@ -289,6 +289,13 @@ namespace ts {
if (startsWith(fileName, "./") && hasExtension(fileName)) {
fileName = fileName.substring(2);
}
// omit references to files from node_modules (npm may disambiguate module
// references when installing this package, making the path is unreliable).
if (startsWith(fileName, "node_modules/") || fileName.indexOf("/node_modules/") !== -1) {
return;
}
references.push({ pos: -1, end: -1, fileName });
}
};

View File

@@ -9,6 +9,7 @@
"files": [
"core.ts",
"performance.ts",
"semver.ts",
"types.ts",
"sys.ts",

View File

@@ -14,7 +14,6 @@ namespace ts {
/* @internal */
namespace ts {
export const emptyArray: never[] = [] as never[];
export const resolvingEmptyArray: never[] = [] as never[];
export const emptyMap = createMap<never>() as ReadonlyMap<never> & ReadonlyPragmaMap;
export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap<never> = emptyMap as ReadonlyUnderscoreEscapedMap<never>;
@@ -3974,6 +3973,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 {

View File

@@ -397,6 +397,7 @@ namespace ts.server {
return this.lastRenameEntry = {
canRename: body.info.canRename,
fileToRename: body.info.fileToRename,
displayName: body.info.displayName,
fullDisplayName: body.info.fullDisplayName,
kind: body.info.kind,

View File

@@ -420,12 +420,12 @@ namespace FourSlash {
}
}
public goToEachRange(action: () => void) {
public goToEachRange(action: (range: Range) => void) {
const ranges = this.getRanges();
assert(ranges.length);
for (const range of ranges) {
this.selectRange(range);
action();
action(range);
}
}
@@ -1525,7 +1525,7 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyRenameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) {
public verifyRenameInfoSucceeded(displayName: string | undefined, fullDisplayName: string | undefined, kind: string | undefined, kindModifiers: string | undefined, fileToRename: string | undefined, expectedRange: Range | undefined): void {
const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition);
if (!renameInfo.canRename) {
this.raiseError("Rename did not succeed");
@@ -1535,12 +1535,15 @@ Actual: ${stringify(fullActual)}`);
this.validate("fullDisplayName", fullDisplayName, renameInfo.fullDisplayName);
this.validate("kind", kind, renameInfo.kind);
this.validate("kindModifiers", kindModifiers, renameInfo.kindModifiers);
this.validate("fileToRename", fileToRename, renameInfo.fileToRename);
if (this.getRanges().length !== 1) {
this.raiseError("Expected a single range to be selected in the test file.");
if (!expectedRange) {
if (this.getRanges().length !== 1) {
this.raiseError("Expected a single range to be selected in the test file.");
}
expectedRange = this.getRanges()[0];
}
const expectedRange = this.getRanges()[0];
if (renameInfo.triggerSpan.start !== expectedRange.pos ||
ts.textSpanEnd(renameInfo.triggerSpan) !== expectedRange.end) {
this.raiseError("Expected triggerSpan [" + expectedRange.pos + "," + expectedRange.end + "). Got [" +
@@ -3977,7 +3980,7 @@ namespace FourSlashInterface {
this.state.goToRangeStart(range);
}
public eachRange(action: () => void) {
public eachRange(action: (range: FourSlash.Range) => void) {
this.state.goToEachRange(action);
}
@@ -4456,8 +4459,8 @@ namespace FourSlashInterface {
this.state.verifySemanticClassifications(classifications);
}
public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) {
this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers);
public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, expectedRange?: FourSlash.Range) {
this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers, fileToRename, expectedRange);
}
public renameInfoFailed(message?: string) {

View File

@@ -82,4 +82,16 @@ namespace utils {
export function addUTF8ByteOrderMark(text: string) {
return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text;
}
export function theory<T extends any[]>(name: string, cb: (...args: T) => void, data: T[]) {
for (const entry of data) {
it(`${name}(${entry.map(formatTheoryDatum).join(", ")})`, () => cb(...entry));
}
}
function formatTheoryDatum(value: any) {
return typeof value === "function" ? value.name || "<anonymous function>" :
value === undefined ? "undefined" :
JSON.stringify(value);
}
}

View File

@@ -21,13 +21,13 @@ namespace ts.JsTyping {
export interface CachedTyping {
typingLocation: string;
version: Semver;
version: Version;
}
/* @internal */
export function isTypingUpToDate(cachedTyping: CachedTyping, availableTypingVersions: MapLike<string>) {
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 */

View File

@@ -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 <http://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."
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));
}
}
}

View File

@@ -16,7 +16,6 @@
"files": [
"shared.ts",
"types.ts",
"jsTyping.ts",
"semver.ts"
"jsTyping.ts"
]
}

View File

@@ -1093,6 +1093,12 @@ namespace ts.server.protocol {
*/
canRename: boolean;
/**
* File or directory to rename.
* If set, `getEditsForFileRename` should be called instead of `findRenameLocations`.
*/
fileToRename?: string;
/**
* Error message if item can not be renamed.
*/

View File

@@ -1096,7 +1096,7 @@ namespace ts.server {
return projectInfo;
}
private getRenameInfo(args: protocol.FileLocationRequestArgs) {
private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo {
const { file, project } = this.getFileAndProject(args);
const position = this.getPositionInFile(args, file);
return project.getLanguageService().getRenameInfo(file, position);

View File

@@ -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

View File

@@ -144,7 +144,14 @@ namespace ts.Completions {
getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target!, log, completionKind, preferences, propertyAccessToConvert, isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap);
}
addRange(entries, getKeywordCompletions(keywordFilters));
if (keywordFilters !== KeywordCompletionFilters.None) {
const entryNames = arrayToSet(entries, e => e.name);
for (const keywordEntry of getKeywordCompletions(keywordFilters)) {
if (!entryNames.has(keywordEntry.name)) {
entries.push(keywordEntry);
}
}
}
for (const literal of literals) {
entries.push(createCompletionEntryForLiteral(literal));
@@ -180,7 +187,7 @@ namespace ts.Completions {
return;
}
const realName = unescapeLeadingUnderscores(name);
if (addToSeen(uniqueNames, realName) && isIdentifierText(realName, target) && !isStringANonContextualKeyword(realName)) {
if (addToSeen(uniqueNames, realName) && isIdentifierText(realName, target)) {
entries.push({
name: realName,
kind: ScriptElementKind.warning,

View File

@@ -151,11 +151,41 @@ namespace ts.Completions.PathCompletions {
}
}
}
// check for a version redirect
const packageJsonPath = findPackageJson(baseDirectory, host);
if (packageJsonPath) {
const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined });
const typesVersions = (packageJson as any).typesVersions;
if (typeof typesVersions === "object") {
const versionResult = getPackageJsonTypesVersionsPaths(typesVersions);
const versionPaths = versionResult && versionResult.paths;
const rest = absolutePath.slice(ensureTrailingDirectorySeparator(baseDirectory).length);
if (versionPaths) {
addCompletionEntriesFromPaths(result, rest, baseDirectory, extensions, versionPaths, host);
}
}
}
}
return result;
}
function addCompletionEntriesFromPaths(result: NameAndKind[], fragment: string, baseDirectory: string, fileExtensions: ReadonlyArray<string>, paths: MapLike<string[]>, host: LanguageServiceHost) {
for (const path in paths) {
if (!hasProperty(paths, path)) continue;
const patterns = paths[path];
if (patterns) {
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseDirectory, fileExtensions, host)) {
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
if (!result.some(entry => entry.name === name)) {
result.push(nameAndKind(name, kind));
}
}
}
}
}
/**
* Check all of the declared modules and those in node modules. Possible sources of modules:
* Modules that are found by the type checker
@@ -171,19 +201,10 @@ namespace ts.Completions.PathCompletions {
const fileExtensions = getSupportedExtensionsForModuleResolution(compilerOptions);
if (baseUrl) {
const projectDir = compilerOptions.project || host.getCurrentDirectory();
const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl);
getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
for (const path in paths!) {
const patterns = paths![path];
if (paths!.hasOwnProperty(path) && patterns) {
for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseUrl, fileExtensions, host)) {
// Path mappings may provide a duplicate way to get to something we've already added, so don't add again.
if (!result.some(entry => entry.name === name)) {
result.push(nameAndKind(name, kind));
}
}
}
const absolute = normalizePath(combinePaths(projectDir, baseUrl));
getCompletionEntriesForDirectoryFragment(fragment, absolute, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result);
if (paths) {
addCompletionEntriesFromPaths(result, fragment, absolute, fileExtensions, paths, host);
}
}
@@ -329,7 +350,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 +384,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);
}
}
@@ -390,6 +411,18 @@ namespace ts.Completions.PathCompletions {
return paths;
}
function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined {
let packageJson: string | undefined;
forEachAncestorDirectory(directory, ancestor => {
if (ancestor === "node_modules") return true;
packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
if (packageJson) {
return true; // break out
}
});
return packageJson;
}
function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): ReadonlyArray<string> {
if (!host.readFile || !host.fileExists) return emptyArray;

View File

@@ -25,8 +25,9 @@ namespace ts.Rename {
return undefined;
}
// Can't rename a module name.
if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) return undefined;
if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) {
return getRenameInfoForModule(node, sourceFile, symbol);
}
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node);
const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName)
@@ -37,9 +38,28 @@ namespace ts.Rename {
return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(symbol), node, sourceFile);
}
function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined {
const moduleSourceFile = find(moduleSymbol.declarations, isSourceFile);
if (!moduleSourceFile) return undefined;
const withoutIndex = node.text.endsWith("/index") || node.text.endsWith("/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index");
const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex;
const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory;
return {
canRename: true,
fileToRename: name,
kind,
displayName: name,
localizedErrorMessage: undefined,
fullDisplayName: name,
kindModifiers: ScriptElementKindModifier.none,
triggerSpan: createTriggerSpanForNode(node, sourceFile),
};
}
function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfo {
return {
canRename: true,
fileToRename: undefined,
kind,
displayName,
localizedErrorMessage: undefined,

View File

@@ -128,14 +128,18 @@ namespace ts.SymbolDisplay {
let documentation: SymbolDisplayPart[] | undefined;
let tags: JSDocTagInfo[] | undefined;
const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol);
let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location);
let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown;
let hasAddedSymbolInfo = false;
const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location);
const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location);
let type: Type | undefined;
let printer: Printer;
let documentationFromAlias: SymbolDisplayPart[] | undefined;
let tagsFromAlias: JSDocTagInfo[] | undefined;
if (location.kind === SyntaxKind.ThisKeyword && !isThisExpression) {
return { displayParts: [keywordPart(SyntaxKind.ThisKeyword)], documentation: [], symbolKind: ScriptElementKind.primitiveType, tags: undefined };
}
// Class at constructor site need to be shown as constructor apart from property,method, vars
if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) {
// If it is accessor they are allowed only if location is at name of the accessor
@@ -285,7 +289,7 @@ namespace ts.SymbolDisplay {
addFullSymbolName(symbol);
writeTypeParametersOfSymbol(symbol, sourceFile);
}
if (symbolFlags & SymbolFlags.TypeAlias) {
if ((symbolFlags & SymbolFlags.TypeAlias) && (semanticMeaning & SemanticMeaning.Type)) {
prefixNextMeaning();
displayParts.push(keywordPart(SyntaxKind.TypeKeyword));
displayParts.push(spacePart());

View File

@@ -784,6 +784,11 @@ namespace ts {
export interface RenameInfo {
canRename: boolean;
/**
* File or directory to rename.
* If set, `getEditsForFileRename` should be called instead of `findRenameLocations`.
*/
fileToRename?: string;
localizedErrorMessage?: string;
displayName: string;
fullDisplayName: string;

View File

@@ -88,7 +88,7 @@ namespace ts {
if (node.kind === SyntaxKind.SourceFile) {
return SemanticMeaning.Value;
}
else if (node.parent.kind === SyntaxKind.ExportAssignment) {
else if (node.parent.kind === SyntaxKind.ExportAssignment || node.parent.kind === SyntaxKind.ExternalModuleReference) {
return SemanticMeaning.All;
}
else if (isInRightSideOfInternalImportEqualsDeclaration(node)) {

View File

@@ -74,6 +74,7 @@
"unittests/publicApi.ts",
"unittests/reuseProgramStructure.ts",
"unittests/session.ts",
"unittests/semver.ts",
"unittests/symbolWalker.ts",
"unittests/telemetry.ts",
"unittests/textChanges.ts",

View File

@@ -0,0 +1,228 @@
namespace ts {
import theory = utils.theory;
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);
});
it("increment", () => {
assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("major"), [2, 0, 0]);
assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("minor"), [1, 3, 0]);
assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("patch"), [1, 2, 4]);
});
});
describe("VersionRange", () => {
function assertRange(rangeText: string, versionText: string, inRange = true) {
const range = new VersionRange(rangeText);
const version = new Version(versionText);
assert.strictEqual(range.test(version), inRange, `Expected version '${version}' ${inRange ? `to be` : `to not be`} in range '${rangeText}' (${range})`);
}
theory("comparators", assertRange, [
["", "1.0.0"],
["*", "1.0.0"],
["1", "1.0.0"],
["1", "2.0.0", false],
["1.0", "1.0.0"],
["1.0", "1.1.0", false],
["1.0.0", "1.0.0"],
["1.0.0", "1.0.1", false],
["1.*", "1.0.0"],
["1.*", "2.0.0", false],
["1.x", "1.0.0"],
["1.x", "2.0.0", false],
["=1", "1.0.0"],
["=1", "1.1.0"],
["=1", "1.0.1"],
["=1.0", "1.0.0"],
["=1.0", "1.0.1"],
["=1.0.0", "1.0.0"],
["=*", "0.0.0"],
["=*", "1.0.0"],
[">1", "2"],
[">1.0", "1.1"],
[">1.0.0", "1.0.1"],
[">1.0.0", "1.0.1-pre"],
[">*", "0.0.0", false],
[">*", "1.0.0", false],
[">=1", "1.0.0"],
[">=1.0", "1.0.0"],
[">=1.0.0", "1.0.0"],
[">=1.0.0", "1.0.1-pre"],
[">=*", "0.0.0"],
[">=*", "1.0.0"],
["<2", "1.0.0"],
["<2.1", "2.0.0"],
["<2.0.1", "2.0.0"],
["<2.0.0", "2.0.0-pre"],
["<*", "0.0.0", false],
["<*", "1.0.0", false],
["<=2", "2.0.0"],
["<=2.1", "2.1.0"],
["<=2.0.1", "2.0.1"],
["<=*", "0.0.0"],
["<=*", "1.0.0"],
]);
theory("conjunctions", assertRange, [
[">1.0.0 <2.0.0", "1.0.1"],
[">1.0.0 <2.0.0", "2.0.0", false],
[">1.0.0 <2.0.0", "1.0.0", false],
[">1 >2", "3.0.0"],
]);
theory("disjunctions", assertRange, [
[">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "1.0.0"],
[">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "2.0.0", false],
[">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "3.0.0"],
]);
theory("hyphen", assertRange, [
["1.0.0 - 2.0.0", "1.0.0"],
["1.0.0 - 2.0.0", "2.0.0"],
["1.0.0 - 2.0.0", "3.0.0", false],
]);
theory("tilde", assertRange, [
["~0", "0.0.0"],
["~0", "0.1.0"],
["~0", "0.1.2"],
["~0", "0.1.9"],
["~0", "1.0.0", false],
["~0.1", "0.1.0"],
["~0.1", "0.1.2"],
["~0.1", "0.1.9"],
["~0.1", "0.2.0", false],
["~0.1.2", "0.1.2"],
["~0.1.2", "0.1.9"],
["~0.1.2", "0.2.0", false],
["~1", "1.0.0"],
["~1", "1.2.0"],
["~1", "1.2.3"],
["~1", "1.2.0"],
["~1", "1.2.3"],
["~1", "0.0.0", false],
["~1", "2.0.0", false],
["~1.2", "1.2.0"],
["~1.2", "1.2.3"],
["~1.2", "1.1.0", false],
["~1.2", "1.3.0", false],
["~1.2.3", "1.2.3"],
["~1.2.3", "1.2.9"],
["~1.2.3", "1.1.0", false],
["~1.2.3", "1.3.0", false],
]);
theory("caret", assertRange, [
["^0", "0.0.0"],
["^0", "0.1.0"],
["^0", "0.9.0"],
["^0", "0.1.2"],
["^0", "0.1.9"],
["^0", "1.0.0", false],
["^0.1", "0.1.0"],
["^0.1", "0.1.2"],
["^0.1", "0.1.9"],
["^0.1.2", "0.1.2"],
["^0.1.2", "0.1.9"],
["^0.1.2", "0.0.0", false],
["^0.1.2", "0.2.0", false],
["^0.1.2", "1.0.0", false],
["^1", "1.0.0"],
["^1", "1.2.0"],
["^1", "1.2.3"],
["^1", "1.9.0"],
["^1", "0.0.0", false],
["^1", "2.0.0", false],
["^1.2", "1.2.0"],
["^1.2", "1.2.3"],
["^1.2", "1.9.0"],
["^1.2", "1.1.0", false],
["^1.2", "2.0.0", false],
["^1.2.3", "1.2.3"],
["^1.2.3", "1.9.0"],
["^1.2.3", "1.2.2", false],
["^1.2.3", "2.0.0", false],
]);
});
});
}

View File

@@ -1322,7 +1322,7 @@ namespace ts.projectSystem {
content: ""
};
const host = createServerHost([f, node]);
const cache = createMapFromTemplate<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: Semver.parse("1.3.0") } });
const cache = createMapFromTemplate<JsTyping.CachedTyping>({ 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(<Path>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<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: Semver.parse("1.3.0") } });
const cache = createMapFromTemplate<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: new Version("1.3.0") } });
const logger = trackingLogger();
const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>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<JsTyping.CachedTyping>({
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<JsTyping.CachedTyping>({
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<JsTyping.CachedTyping>({
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";

View File

@@ -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);