mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
--moduleResolution bundler (formerly known as hybrid) (#51669)
* WIP * Add extension error back unless noEmit is set * Add non-relative tests * Add error for importing from declaration file * Update unit test * Add explicit flag for importing from .ts extensions * Add module specifier resolution changes * Add auto-import tests * Disallow relative imports into node_modules * Ensure auto-imports don’t suggest ./node_modules; * Test a non-portable declaration emit issue * Test auto-importing TSX file * Update path completions * Fix lint due to merge * Remove minimal-specific stuff * Remove minimal tests * Update unit tests * Add options * Add customConditions option * Add first tests * CJS constructs are not allowed * Add another test * Fix extension adding/replacing priority * Update test to reflect the choice not to block on unrecognized extensions * Add auto-imports and string completions tests * Revamp string completions ending preferences * Comment test * Auto-imports of declaration files cannot use .ts extension * Have declaration file auto imports default to extensionless instead * Add test for custom conditions * Fix indentation * Add baseline showing resolvePackageJsonImports/Exports compatibility * Fix test and prevent CJS require from resolving * Update unit test baselines * Fix bad merge conflict resolution * Make resolvedUsingTsExtension optional * Update missed baselines * Revert now-unnecessary API implementation changes * Clean up * Update baselines to es5 emit * Rename to `bundler`
This commit is contained in:
@@ -84,6 +84,7 @@ import {
|
||||
getContainingClass,
|
||||
getEffectiveContainerForJSDocTemplateTag,
|
||||
getElementOrPropertyAccessName,
|
||||
getEmitModuleResolutionKind,
|
||||
getEmitScriptTarget,
|
||||
getEnclosingBlockScopeContainer,
|
||||
getErrorSpanForNode,
|
||||
@@ -235,6 +236,7 @@ import {
|
||||
ModifierFlags,
|
||||
ModuleBlock,
|
||||
ModuleDeclaration,
|
||||
ModuleResolutionKind,
|
||||
Mutable,
|
||||
NamespaceExportDeclaration,
|
||||
Node,
|
||||
@@ -3520,6 +3522,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
|
||||
if (!isBindingPattern(node.name)) {
|
||||
const possibleVariableDecl = node.kind === SyntaxKind.VariableDeclaration ? node : node.parent.parent;
|
||||
if (isInJSFile(node) &&
|
||||
getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Bundler &&
|
||||
isVariableDeclarationInitializedToBareOrAccessedRequire(possibleVariableDecl) &&
|
||||
!getJSDocTypeTag(node) &&
|
||||
!(getCombinedModifierFlags(node) & ModifierFlags.Export)
|
||||
|
||||
+55
-20
@@ -139,6 +139,7 @@ import {
|
||||
ElementFlags,
|
||||
EmitFlags,
|
||||
EmitHint,
|
||||
emitModuleKindIsNonNodeESM,
|
||||
EmitResolver,
|
||||
EmitTextWriter,
|
||||
emptyArray,
|
||||
@@ -317,6 +318,7 @@ import {
|
||||
getResolutionModeOverrideForClause,
|
||||
getResolvedExternalModuleName,
|
||||
getResolvedModule,
|
||||
getResolveJsonModule,
|
||||
getRestParameterElementType,
|
||||
getRootDeclaration,
|
||||
getScriptTargetFeatures,
|
||||
@@ -346,8 +348,8 @@ import {
|
||||
hasAccessorModifier,
|
||||
hasAmbientModifier,
|
||||
hasContextSensitiveParameters,
|
||||
HasDecorators,
|
||||
hasDecorators,
|
||||
HasDecorators,
|
||||
hasDynamicName,
|
||||
hasEffectiveModifier,
|
||||
hasEffectiveModifiers,
|
||||
@@ -356,8 +358,8 @@ import {
|
||||
hasExtension,
|
||||
HasIllegalDecorators,
|
||||
HasIllegalModifiers,
|
||||
HasInitializer,
|
||||
hasInitializer,
|
||||
HasInitializer,
|
||||
hasJSDocNodes,
|
||||
hasJSDocParameterTags,
|
||||
hasJsonModuleEmitEnabled,
|
||||
@@ -458,6 +460,7 @@ import {
|
||||
isConstructorTypeNode,
|
||||
isConstTypeReference,
|
||||
isDeclaration,
|
||||
isDeclarationFileName,
|
||||
isDeclarationName,
|
||||
isDeclarationReadonly,
|
||||
isDecorator,
|
||||
@@ -897,6 +900,7 @@ import {
|
||||
setTextRangePosEnd,
|
||||
setValueDeclaration,
|
||||
ShorthandPropertyAssignment,
|
||||
shouldAllowImportingTsExtension,
|
||||
shouldPreserveConstEnums,
|
||||
Signature,
|
||||
SignatureDeclaration,
|
||||
@@ -4543,6 +4547,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (
|
||||
namespace.valueDeclaration &&
|
||||
isInJSFile(namespace.valueDeclaration) &&
|
||||
getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler &&
|
||||
isVariableDeclaration(namespace.valueDeclaration) &&
|
||||
namespace.valueDeclaration.initializer &&
|
||||
isCommonJsRequire(namespace.valueDeclaration.initializer)
|
||||
@@ -4727,6 +4732,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
(isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name ||
|
||||
(isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal;
|
||||
const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat;
|
||||
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
|
||||
const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode);
|
||||
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule);
|
||||
const sourceFile = resolvedModule
|
||||
@@ -4737,11 +4743,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (resolutionDiagnostic) {
|
||||
error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName);
|
||||
}
|
||||
|
||||
if (resolvedModule.resolvedUsingTsExtension && isDeclarationFileName(moduleReference)) {
|
||||
const importOrExport =
|
||||
findAncestor(location, isImportDeclaration)?.importClause ||
|
||||
findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration));
|
||||
if (importOrExport && !importOrExport.isTypeOnly || findAncestor(location, isImportCall)) {
|
||||
error(
|
||||
errorNode,
|
||||
Diagnostics.A_declaration_file_cannot_be_imported_without_import_type_Did_you_mean_to_import_an_implementation_file_0_instead,
|
||||
getSuggestedImportSource(Debug.checkDefined(tryExtractTSExtension(moduleReference))));
|
||||
}
|
||||
}
|
||||
else if (resolvedModule.resolvedUsingTsExtension && !shouldAllowImportingTsExtension(compilerOptions, currentSourceFile.fileName)) {
|
||||
const tsExtension = Debug.checkDefined(tryExtractTSExtension(moduleReference));
|
||||
error(errorNode, Diagnostics.An_import_path_can_only_end_with_a_0_extension_when_allowImportingTsExtensions_is_enabled, tsExtension);
|
||||
}
|
||||
|
||||
if (sourceFile.symbol) {
|
||||
if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) {
|
||||
errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference);
|
||||
}
|
||||
if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) {
|
||||
if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) {
|
||||
const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration);
|
||||
const overrideClauseHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined;
|
||||
const overrideClause = overrideClauseHost && isImportTypeNode(overrideClauseHost) ? overrideClauseHost.assertions?.assertClause : overrideClauseHost?.assertClause;
|
||||
@@ -4849,25 +4872,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
else {
|
||||
const tsExtension = tryExtractTSExtension(moduleReference);
|
||||
const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference);
|
||||
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
|
||||
const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 ||
|
||||
moduleResolutionKind === ModuleResolutionKind.NodeNext;
|
||||
if (tsExtension) {
|
||||
const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
|
||||
const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension);
|
||||
let replacedImportSource = importSourceWithoutExtension;
|
||||
/**
|
||||
* Direct users to import source with .js extension if outputting an ES module.
|
||||
* @see https://github.com/microsoft/TypeScript/issues/42151
|
||||
*/
|
||||
if (moduleKind >= ModuleKind.ES2015) {
|
||||
replacedImportSource += tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js";
|
||||
}
|
||||
error(errorNode, diag, tsExtension, replacedImportSource);
|
||||
errorOnTSExtensionImport(tsExtension);
|
||||
}
|
||||
else if (!compilerOptions.resolveJsonModule &&
|
||||
else if (!getResolveJsonModule(compilerOptions) &&
|
||||
fileExtensionIs(moduleReference, Extension.Json) &&
|
||||
getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic &&
|
||||
moduleResolutionKind !== ModuleResolutionKind.Classic &&
|
||||
hasJsonModuleEmitEnabled(compilerOptions)) {
|
||||
error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference);
|
||||
}
|
||||
@@ -4889,6 +4901,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
function errorOnTSExtensionImport(tsExtension: string) {
|
||||
const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
|
||||
error(errorNode, diag, tsExtension, getSuggestedImportSource(tsExtension));
|
||||
}
|
||||
|
||||
function getSuggestedImportSource(tsExtension: string) {
|
||||
const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension);
|
||||
/**
|
||||
* Direct users to import source with .js extension if outputting an ES module.
|
||||
* @see https://github.com/microsoft/TypeScript/issues/42151
|
||||
*/
|
||||
if (emitModuleKindIsNonNodeESM(moduleKind) || mode === ModuleKind.ESNext) {
|
||||
return importSourceWithoutExtension + (tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js");
|
||||
}
|
||||
return importSourceWithoutExtension;
|
||||
}
|
||||
}
|
||||
|
||||
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
|
||||
@@ -33297,7 +33326,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
// In JavaScript files, calls to any identifier 'require' are treated as external module imports
|
||||
if (isInJSFile(node) && isCommonJsRequire(node)) {
|
||||
if (isInJSFile(node) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && isCommonJsRequire(node)) {
|
||||
return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral);
|
||||
}
|
||||
|
||||
@@ -42699,6 +42728,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// Import equals declaration is deprecated in es6 or above
|
||||
grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead);
|
||||
}
|
||||
else if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Bundler) {
|
||||
grammarErrorOnNode(node, Diagnostics.Import_assignment_is_not_allowed_when_moduleResolution_is_set_to_bundler_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42921,6 +42953,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// system modules does not support export assignment
|
||||
grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system);
|
||||
}
|
||||
else if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Bundler) {
|
||||
grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_moduleResolution_is_set_to_bundler_Consider_using_export_default_or_another_module_format_instead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44015,7 +44050,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// 4). type A = import("./f/*gotToDefinitionHere*/oo")
|
||||
if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) ||
|
||||
((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent as ImportDeclaration).moduleSpecifier === node) ||
|
||||
((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) ||
|
||||
((isInJSFile(node) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) ||
|
||||
(isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent)
|
||||
) {
|
||||
return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors);
|
||||
@@ -47321,4 +47356,4 @@ class SymbolTrackerImpl implements SymbolTracker {
|
||||
private onDiagnosticReported() {
|
||||
this.context.reportedDiagnostic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -965,6 +965,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
|
||||
classic: ModuleResolutionKind.Classic,
|
||||
node16: ModuleResolutionKind.Node16,
|
||||
nodenext: ModuleResolutionKind.NodeNext,
|
||||
bundler: ModuleResolutionKind.Bundler,
|
||||
})),
|
||||
affectsModuleResolution: true,
|
||||
paramType: Diagnostics.STRATEGY,
|
||||
@@ -1081,6 +1082,41 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.List_of_file_name_suffixes_to_search_when_resolving_a_module,
|
||||
},
|
||||
{
|
||||
name: "allowImportingTsExtensions",
|
||||
type: "boolean",
|
||||
affectsModuleResolution: true,
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_bundler_and_either_noEmit_or_emitDeclarationOnly_to_be_set,
|
||||
defaultValueDescription: false,
|
||||
},
|
||||
{
|
||||
name: "resolvePackageJsonExports",
|
||||
type: "boolean",
|
||||
affectsModuleResolution: true,
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.Use_the_package_json_exports_field_when_resolving_package_imports,
|
||||
defaultValueDescription: Diagnostics.true_when_moduleResolution_is_node16_nodenext_or_bundler_otherwise_false,
|
||||
},
|
||||
{
|
||||
name: "resolvePackageJsonImports",
|
||||
type: "boolean",
|
||||
affectsModuleResolution: true,
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.Use_the_package_json_imports_field_when_resolving_imports,
|
||||
defaultValueDescription: Diagnostics.true_when_moduleResolution_is_node16_nodenext_or_bundler_otherwise_false,
|
||||
},
|
||||
{
|
||||
name: "customConditions",
|
||||
type: "list",
|
||||
element: {
|
||||
name: "condition",
|
||||
type: "string",
|
||||
},
|
||||
affectsModuleResolution: true,
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.Conditions_to_set_in_addition_to_the_resolver_specific_defaults_when_resolving_imports,
|
||||
},
|
||||
|
||||
// Source Maps
|
||||
{
|
||||
|
||||
@@ -3563,6 +3563,10 @@
|
||||
"category": "Error",
|
||||
"code": 2845
|
||||
},
|
||||
"A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file '{0}' instead?": {
|
||||
"category": "Error",
|
||||
"code": 2846
|
||||
},
|
||||
|
||||
"Import declaration '{0}' is using private name '{1}'.": {
|
||||
"category": "Error",
|
||||
@@ -4221,6 +4225,26 @@
|
||||
"category": "Error",
|
||||
"code": 5095
|
||||
},
|
||||
"Option 'allowImportingTsExtensions' can only be used when 'moduleResolution' is set to 'bundler' and either 'noEmit' or 'emitDeclarationOnly' is set.": {
|
||||
"category": "Error",
|
||||
"code": 5096
|
||||
},
|
||||
"An import path can only end with a '{0}' extension when 'allowImportingTsExtensions' is enabled.": {
|
||||
"category": "Error",
|
||||
"code": 5097
|
||||
},
|
||||
"Option '{0}' can only be used when 'moduleResolution' is set to 'node16', 'nodenext', or 'bundler'.": {
|
||||
"category": "Error",
|
||||
"code": 5098
|
||||
},
|
||||
"Import assignment is not allowed when 'moduleResolution' is set to 'bundler'. Consider using 'import * as ns from \"mod\"', 'import {a} from \"mod\"', 'import d from \"mod\"', or another module format instead.": {
|
||||
"category": "Error",
|
||||
"code": 5099
|
||||
},
|
||||
"Export assignment cannot be used when 'moduleResolution' is set to 'bundler'. Consider using 'export default' or another module format instead.": {
|
||||
"category": "Error",
|
||||
"code": 5100
|
||||
},
|
||||
|
||||
"Generates a sourcemap for each corresponding '.d.ts' file.": {
|
||||
"category": "Message",
|
||||
@@ -4443,10 +4467,6 @@
|
||||
"category": "Message",
|
||||
"code": 6066
|
||||
},
|
||||
"Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).": {
|
||||
"category": "Message",
|
||||
"code": 6069
|
||||
},
|
||||
"Initializes a TypeScript project and creates a tsconfig.json file.": {
|
||||
"category": "Message",
|
||||
"code": 6070
|
||||
@@ -5430,6 +5450,26 @@
|
||||
"category": "Message",
|
||||
"code": 6406
|
||||
},
|
||||
"Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set.": {
|
||||
"category": "Message",
|
||||
"code": 6407
|
||||
},
|
||||
"Use the package.json 'exports' field when resolving package imports.": {
|
||||
"category": "Message",
|
||||
"code": 6408
|
||||
},
|
||||
"Use the package.json 'imports' field when resolving imports.": {
|
||||
"category": "Message",
|
||||
"code": 6409
|
||||
},
|
||||
"Conditions to set in addition to the resolver-specific defaults when resolving imports.": {
|
||||
"category": "Message",
|
||||
"code": 6410
|
||||
},
|
||||
"`true` when 'moduleResolution' is 'node16', 'nodenext', or 'bundler'; otherwise `false`.": {
|
||||
"category": "Message",
|
||||
"code": 6411
|
||||
},
|
||||
|
||||
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
|
||||
"category": "Message",
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
append,
|
||||
appendIfUnique,
|
||||
arrayFrom,
|
||||
arrayIsEqualTo,
|
||||
changeAnyExtension,
|
||||
CharacterCodes,
|
||||
combinePaths,
|
||||
@@ -9,10 +10,12 @@ import {
|
||||
comparePaths,
|
||||
Comparison,
|
||||
CompilerOptions,
|
||||
concatenate,
|
||||
contains,
|
||||
containsPath,
|
||||
createCompilerDiagnostic,
|
||||
Debug,
|
||||
deduplicate,
|
||||
Diagnostic,
|
||||
DiagnosticMessage,
|
||||
DiagnosticReporter,
|
||||
@@ -47,12 +50,14 @@ import {
|
||||
getPathsBasePath,
|
||||
getPossibleOriginalInputExtensionForExtension,
|
||||
getRelativePathFromDirectory,
|
||||
getResolveJsonModule,
|
||||
getRootLength,
|
||||
hasJSFileExtension,
|
||||
hasProperty,
|
||||
hasTrailingDirectorySeparator,
|
||||
hostGetCanonicalFileName,
|
||||
isArray,
|
||||
isDeclarationFileName,
|
||||
isExternalModuleNameRelative,
|
||||
isRootedDiskPath,
|
||||
isString,
|
||||
@@ -94,6 +99,7 @@ import {
|
||||
startsWith,
|
||||
stringContains,
|
||||
supportedDeclarationExtensions,
|
||||
supportedTSExtensionsFlat,
|
||||
supportedTSImplementationExtensions,
|
||||
toPath,
|
||||
tryExtractTSExtension,
|
||||
@@ -128,7 +134,7 @@ function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExten
|
||||
};
|
||||
}
|
||||
}
|
||||
return r && { path: r.path, extension: r.ext, packageId };
|
||||
return r && { path: r.path, extension: r.ext, packageId, resolvedUsingTsExtension: r.resolvedUsingTsExtension };
|
||||
}
|
||||
|
||||
function noPackageId(r: PathAndExtension | undefined): Resolved | undefined {
|
||||
@@ -138,7 +144,7 @@ function noPackageId(r: PathAndExtension | undefined): Resolved | undefined {
|
||||
function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined {
|
||||
if (r) {
|
||||
Debug.assert(r.packageId === undefined);
|
||||
return { path: r.path, ext: r.extension };
|
||||
return { path: r.path, ext: r.extension, resolvedUsingTsExtension: r.resolvedUsingTsExtension };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +163,7 @@ interface Resolved {
|
||||
* Note: This is a file name with preserved original casing, not a normalized `Path`.
|
||||
*/
|
||||
originalPath?: string | true;
|
||||
resolvedUsingTsExtension: boolean | undefined;
|
||||
}
|
||||
|
||||
/** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */
|
||||
@@ -164,6 +171,7 @@ interface PathAndExtension {
|
||||
path: string;
|
||||
// (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.)
|
||||
ext: Extension;
|
||||
resolvedUsingTsExtension: boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,7 +223,14 @@ function createResolvedModuleWithFailedLookupLocations(
|
||||
return resultFromCache;
|
||||
}
|
||||
return {
|
||||
resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId },
|
||||
resolvedModule: resolved && {
|
||||
resolvedFileName: resolved.path,
|
||||
originalPath: resolved.originalPath === true ? undefined : resolved.originalPath,
|
||||
extension: resolved.extension,
|
||||
isExternalLibraryImport,
|
||||
packageId: resolved.packageId,
|
||||
resolvedUsingTsExtension: !!resolved.resolvedUsingTsExtension,
|
||||
},
|
||||
failedLookupLocations: initializeResolutionField(failedLookupLocations),
|
||||
affectingLocations: initializeResolutionField(affectingLocations),
|
||||
resolutionDiagnostics: initializeResolutionField(diagnostics),
|
||||
@@ -488,7 +503,7 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
|
||||
|
||||
const failedLookupLocations: string[] = [];
|
||||
const affectingLocations: string[] = [];
|
||||
let features = getDefaultNodeResolutionFeatures(options);
|
||||
let features = getNodeResolutionFeatures(options);
|
||||
// Unlike `import` statements, whose mode-calculating APIs are all guaranteed to return `undefined` if we're in an un-mode-ed module resolution
|
||||
// setting, type references will return their target mode regardless of options because of how the parser works, so we guard against the mode being
|
||||
// set in a non-modal module resolution setting here. Do note that our behavior is not particularly well defined when these mode-overriding imports
|
||||
@@ -499,7 +514,7 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
|
||||
if (resolutionMode === ModuleKind.ESNext && (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext)) {
|
||||
features |= NodeResolutionFeatures.EsmMode;
|
||||
}
|
||||
const conditions = features & NodeResolutionFeatures.Exports ? features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"] : [];
|
||||
const conditions = features & NodeResolutionFeatures.Exports ? getConditions(options, !!(features & NodeResolutionFeatures.EsmMode)) : [];
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const moduleResolutionState: ModuleResolutionState = {
|
||||
compilerOptions: options,
|
||||
@@ -614,10 +629,44 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultNodeResolutionFeatures(options: CompilerOptions) {
|
||||
return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 ? NodeResolutionFeatures.Node16Default :
|
||||
getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext ? NodeResolutionFeatures.NodeNextDefault :
|
||||
NodeResolutionFeatures.None;
|
||||
function getNodeResolutionFeatures(options: CompilerOptions) {
|
||||
let features = NodeResolutionFeatures.None;
|
||||
switch (getEmitModuleResolutionKind(options)) {
|
||||
case ModuleResolutionKind.Node16:
|
||||
features = NodeResolutionFeatures.Node16Default;
|
||||
break;
|
||||
case ModuleResolutionKind.NodeNext:
|
||||
features = NodeResolutionFeatures.NodeNextDefault;
|
||||
break;
|
||||
case ModuleResolutionKind.Bundler:
|
||||
features = NodeResolutionFeatures.BundlerDefault;
|
||||
break;
|
||||
}
|
||||
if (options.resolvePackageJsonExports) {
|
||||
features |= NodeResolutionFeatures.Exports;
|
||||
}
|
||||
else if (options.resolvePackageJsonExports === false) {
|
||||
features &= ~NodeResolutionFeatures.Exports;
|
||||
}
|
||||
if (options.resolvePackageJsonImports) {
|
||||
features |= NodeResolutionFeatures.Imports;
|
||||
}
|
||||
else if (options.resolvePackageJsonImports === false) {
|
||||
features &= ~NodeResolutionFeatures.Imports;
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
function getConditions(options: CompilerOptions, esmMode?: boolean) {
|
||||
// conditions are only used by the node16/nodenext/bundler resolvers - there's no priority order in the list,
|
||||
// it's essentially a set (priority is determined by object insertion order in the object we look at).
|
||||
const conditions = esmMode || getEmitModuleResolutionKind(options) === ModuleResolutionKind.Bundler
|
||||
? ["node", "import"]
|
||||
: ["node", "require"];
|
||||
if (!options.noDtsResolution) {
|
||||
conditions.push("types");
|
||||
}
|
||||
return concatenate(conditions, options.customConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1235,6 +1284,9 @@ export function resolveModuleName(moduleName: string, containingFile: string, co
|
||||
case ModuleResolutionKind.Classic:
|
||||
result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference);
|
||||
break;
|
||||
case ModuleResolutionKind.Bundler:
|
||||
result = bundlerModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference);
|
||||
break;
|
||||
default:
|
||||
return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`);
|
||||
}
|
||||
@@ -1489,6 +1541,8 @@ export enum NodeResolutionFeatures {
|
||||
|
||||
NodeNextDefault = AllFeatures,
|
||||
|
||||
BundlerDefault = Imports | SelfName | Exports | ExportsPatternTrailers,
|
||||
|
||||
EsmMode = 1 << 5,
|
||||
}
|
||||
|
||||
@@ -1528,7 +1582,7 @@ function nodeNextModuleNameResolverWorker(features: NodeResolutionFeatures, modu
|
||||
// es module file or cjs-like input file, use a variant of the legacy cjs resolver that supports the selected modern features
|
||||
const esmMode = resolutionMode === ModuleKind.ESNext ? NodeResolutionFeatures.EsmMode : 0;
|
||||
let extensions = compilerOptions.noDtsResolution ? Extensions.ImplementationFiles : Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration;
|
||||
if (compilerOptions.resolveJsonModule) {
|
||||
if (getResolveJsonModule(compilerOptions)) {
|
||||
extensions |= Extensions.Json;
|
||||
}
|
||||
return nodeModuleNameResolverWorker(features | esmMode, moduleName, containingDirectory, compilerOptions, host, cache, extensions, /*isConfigLookup*/ false, redirectedReference);
|
||||
@@ -1547,6 +1601,15 @@ function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host:
|
||||
/*redirectedReferences*/ undefined);
|
||||
}
|
||||
|
||||
export function bundlerModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations {
|
||||
const containingDirectory = getDirectoryPath(containingFile);
|
||||
let extensions = compilerOptions.noDtsResolution ? Extensions.ImplementationFiles : Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration;
|
||||
if (getResolveJsonModule(compilerOptions)) {
|
||||
extensions |= Extensions.Json;
|
||||
}
|
||||
return nodeModuleNameResolverWorker(getNodeResolutionFeatures(compilerOptions), moduleName, containingDirectory, compilerOptions, host, cache, extensions, /*isConfigLookup*/ false, redirectedReference);
|
||||
}
|
||||
|
||||
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
|
||||
/** @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures
|
||||
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, isConfigLookup?: boolean): ResolvedModuleWithFailedLookupLocations {
|
||||
@@ -1556,10 +1619,10 @@ export function nodeModuleNameResolver(moduleName: string, containingFile: strin
|
||||
}
|
||||
else if (compilerOptions.noDtsResolution) {
|
||||
extensions = Extensions.ImplementationFiles;
|
||||
if (compilerOptions.resolveJsonModule) extensions |= Extensions.Json;
|
||||
if (getResolveJsonModule(compilerOptions)) extensions |= Extensions.Json;
|
||||
}
|
||||
else {
|
||||
extensions = compilerOptions.resolveJsonModule
|
||||
extensions = getResolveJsonModule(compilerOptions)
|
||||
? Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration | Extensions.Json
|
||||
: Extensions.TypeScript | Extensions.JavaScript | Extensions.Declaration;
|
||||
}
|
||||
@@ -1571,12 +1634,7 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
|
||||
|
||||
const failedLookupLocations: string[] = [];
|
||||
const affectingLocations: string[] = [];
|
||||
// conditions are only used by the node16/nodenext resolver - there's no priority order in the list,
|
||||
//it's essentially a set (priority is determined by object insertion order in the object we look at).
|
||||
const conditions = features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"];
|
||||
if (compilerOptions.noDtsResolution) {
|
||||
conditions.pop();
|
||||
}
|
||||
const conditions = getConditions(compilerOptions, !!(features & NodeResolutionFeatures.EsmMode));
|
||||
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const state: ModuleResolutionState = {
|
||||
@@ -1720,7 +1778,7 @@ function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string,
|
||||
}
|
||||
}
|
||||
// esm mode relative imports shouldn't do any directory lookups (either inside `package.json`
|
||||
// files or implicit `index.js`es). This is a notable depature from cjs norms, where `./foo/pkg`
|
||||
// files or implicit `index.js`es). This is a notable departure from cjs norms, where `./foo/pkg`
|
||||
// could have been redirected by `./foo/pkg/package.json` to an arbitrary location!
|
||||
if (!(state.features & NodeResolutionFeatures.EsmMode)) {
|
||||
return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson);
|
||||
@@ -1795,7 +1853,12 @@ function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecor
|
||||
function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
|
||||
// If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one;
|
||||
// e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts"
|
||||
if (hasJSFileExtension(candidate) || extensions & Extensions.Json && fileExtensionIs(candidate, Extension.Json)) {
|
||||
if (hasJSFileExtension(candidate) ||
|
||||
extensions & Extensions.Json && fileExtensionIs(candidate, Extension.Json) ||
|
||||
extensions & (Extensions.TypeScript | Extensions.Declaration)
|
||||
&& moduleResolutionSupportsResolvingTsExtensions(state.compilerOptions)
|
||||
&& fileExtensionIsOneOf(candidate, supportedTSExtensionsFlat)
|
||||
) {
|
||||
const extensionless = removeFileExtension(candidate);
|
||||
const extension = candidate.substring(extensionless.length);
|
||||
if (state.traceEnabled) {
|
||||
@@ -1810,7 +1873,7 @@ function loadJSOrExactTSFileName(extensions: Extensions, candidate: string, only
|
||||
extensions & Extensions.Declaration && fileExtensionIsOneOf(candidate, supportedDeclarationExtensions)
|
||||
) {
|
||||
const result = tryFile(candidate, onlyRecordFailures, state);
|
||||
return result !== undefined ? { path: candidate, ext: tryExtractTSExtension(candidate) as Extension } : undefined;
|
||||
return result !== undefined ? { path: candidate, ext: tryExtractTSExtension(candidate) as Extension, resolvedUsingTsExtension: undefined } : undefined;
|
||||
}
|
||||
|
||||
return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state);
|
||||
@@ -1854,6 +1917,13 @@ function tryAddingExtensions(candidate: string, extensions: Extensions, original
|
||||
if (result) return result;
|
||||
}
|
||||
return undefined;
|
||||
case Extension.Ts:
|
||||
case Extension.Tsx:
|
||||
case Extension.Dts:
|
||||
if (moduleResolutionSupportsResolvingTsExtensions(state.compilerOptions) && extensionIsOk(extensions, originalExtension)) {
|
||||
return tryExtension(originalExtension, /*resolvedUsingTsExtension*/ true);
|
||||
}
|
||||
// falls through
|
||||
default:
|
||||
return extensions & Extensions.TypeScript && (tryExtension(Extension.Ts) || tryExtension(Extension.Tsx))
|
||||
|| extensions & Extensions.Declaration && tryExtension(Extension.Dts)
|
||||
@@ -1863,9 +1933,9 @@ function tryAddingExtensions(candidate: string, extensions: Extensions, original
|
||||
|
||||
}
|
||||
|
||||
function tryExtension(ext: Extension): PathAndExtension | undefined {
|
||||
function tryExtension(ext: Extension, resolvedUsingTsExtension?: boolean): PathAndExtension | undefined {
|
||||
const path = tryFile(candidate + ext, onlyRecordFailures, state);
|
||||
return path === undefined ? undefined : { path, ext };
|
||||
return path === undefined ? undefined : { path, ext, resolvedUsingTsExtension };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1921,26 +1991,30 @@ export function getEntrypointsFromPackageJsonInfo(
|
||||
|
||||
let entrypoints: string[] | undefined;
|
||||
const extensions = Extensions.TypeScript | Extensions.Declaration | (resolveJs ? Extensions.JavaScript : 0);
|
||||
const features = getDefaultNodeResolutionFeatures(options);
|
||||
const requireState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options);
|
||||
requireState.conditions = ["node", "require", "types"];
|
||||
requireState.requestContainingDirectory = packageJsonInfo.packageDirectory;
|
||||
const requireResolution = loadNodeModuleFromDirectoryWorker(
|
||||
const features = getNodeResolutionFeatures(options);
|
||||
const loadPackageJsonMainState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options);
|
||||
loadPackageJsonMainState.conditions = getConditions(options);
|
||||
loadPackageJsonMainState.requestContainingDirectory = packageJsonInfo.packageDirectory;
|
||||
const mainResolution = loadNodeModuleFromDirectoryWorker(
|
||||
extensions,
|
||||
packageJsonInfo.packageDirectory,
|
||||
/*onlyRecordFailures*/ false,
|
||||
requireState,
|
||||
loadPackageJsonMainState,
|
||||
packageJsonInfo.contents.packageJsonContent,
|
||||
getVersionPathsOfPackageJsonInfo(packageJsonInfo, requireState));
|
||||
entrypoints = append(entrypoints, requireResolution?.path);
|
||||
getVersionPathsOfPackageJsonInfo(packageJsonInfo, loadPackageJsonMainState));
|
||||
entrypoints = append(entrypoints, mainResolution?.path);
|
||||
|
||||
if (features & NodeResolutionFeatures.Exports && packageJsonInfo.contents.packageJsonContent.exports) {
|
||||
for (const conditions of [["node", "import", "types"], ["node", "require", "types"]]) {
|
||||
const exportState = { ...requireState, failedLookupLocations: [], conditions };
|
||||
const conditionSets = deduplicate(
|
||||
[getConditions(options, /*esmMode*/ true), getConditions(options, /*esmMode*/ false)],
|
||||
arrayIsEqualTo
|
||||
);
|
||||
for (const conditions of conditionSets) {
|
||||
const loadPackageJsonExportsState = { ...loadPackageJsonMainState, failedLookupLocations: [], conditions };
|
||||
const exportResolutions = loadEntrypointsFromExportMap(
|
||||
packageJsonInfo,
|
||||
packageJsonInfo.contents.packageJsonContent.exports,
|
||||
exportState,
|
||||
loadPackageJsonExportsState,
|
||||
extensions);
|
||||
if (exportResolutions) {
|
||||
for (const resolution of exportResolutions) {
|
||||
@@ -2046,7 +2120,7 @@ export interface PackageJsonInfoContents {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function getPackageScopeForPath(fileName: string, state: ModuleResolutionState): PackageJsonInfo | undefined {
|
||||
export function getPackageScopeForPath(fileName: string, state: ModuleResolutionState): PackageJsonInfo | undefined {
|
||||
const parts = getPathComponents(fileName);
|
||||
parts.pop();
|
||||
while (parts.length > 0) {
|
||||
@@ -2179,9 +2253,9 @@ function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: st
|
||||
}
|
||||
|
||||
/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */
|
||||
function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined {
|
||||
function resolvedIfExtensionMatches(extensions: Extensions, path: string, resolvedUsingTsExtension?: boolean): PathAndExtension | undefined {
|
||||
const ext = tryGetExtensionFromPath(path);
|
||||
return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined;
|
||||
return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext, resolvedUsingTsExtension } : undefined;
|
||||
}
|
||||
|
||||
/** True if `extension` is one of the supported `extensions`. */
|
||||
@@ -2372,7 +2446,13 @@ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: Mo
|
||||
traceIfEnabled(state, Diagnostics.Using_0_subpath_1_with_target_2, "imports", key, combinedLookup);
|
||||
traceIfEnabled(state, Diagnostics.Resolving_module_0_from_1, combinedLookup, scope.packageDirectory + "/");
|
||||
const result = nodeModuleNameResolverWorker(state.features, combinedLookup, scope.packageDirectory + "/", state.compilerOptions, state.host, cache, extensions, /*isConfigLookup*/ false, redirectedReference);
|
||||
return toSearchResult(result.resolvedModule ? { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, originalPath: result.resolvedModule.originalPath } : undefined);
|
||||
return toSearchResult(result.resolvedModule ? {
|
||||
path: result.resolvedModule.resolvedFileName,
|
||||
extension: result.resolvedModule.extension,
|
||||
packageId: result.resolvedModule.packageId,
|
||||
originalPath: result.resolvedModule.originalPath,
|
||||
resolvedUsingTsExtension: result.resolvedModule.resolvedUsingTsExtension
|
||||
} : undefined);
|
||||
}
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName);
|
||||
@@ -2753,7 +2833,7 @@ function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, bas
|
||||
if (extension !== undefined) {
|
||||
const path = tryFile(candidate, onlyRecordFailures, state);
|
||||
if (path !== undefined) {
|
||||
return noPackageId({ path, ext: extension });
|
||||
return noPackageId({ path, ext: extension, resolvedUsingTsExtension: undefined });
|
||||
}
|
||||
}
|
||||
return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
|
||||
@@ -2813,7 +2893,15 @@ function tryFindNonRelativeModuleNameInCache(cache: NonRelativeModuleNameResolut
|
||||
trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
|
||||
}
|
||||
state.resultFromCache = result;
|
||||
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
|
||||
return {
|
||||
value: result.resolvedModule && {
|
||||
path: result.resolvedModule.resolvedFileName,
|
||||
originalPath: result.resolvedModule.originalPath || true,
|
||||
extension: result.resolvedModule.extension,
|
||||
packageId: result.resolvedModule.packageId,
|
||||
resolvedUsingTsExtension: result.resolvedModule.resolvedUsingTsExtension
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2880,6 +2968,18 @@ export function classicNameResolver(moduleName: string, containingFile: string,
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleResolutionSupportsResolvingTsExtensions(compilerOptions: CompilerOptions) {
|
||||
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Bundler;
|
||||
}
|
||||
|
||||
// Program errors validate that `noEmit` or `emitDeclarationOnly` is also set,
|
||||
// so this function doesn't check them to avoid propagating errors.
|
||||
export function shouldAllowImportingTsExtension(compilerOptions: CompilerOptions, fromFileName?: string) {
|
||||
return moduleResolutionSupportsResolvingTsExtensions(compilerOptions) && (
|
||||
!!compilerOptions.allowImportingTsExtensions ||
|
||||
fromFileName && isDeclarationFileName(fromFileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* A host may load a module from a global cache of typings.
|
||||
* This is the minumum code needed to expose that functionality; the rest is in the host.
|
||||
|
||||
@@ -34,9 +34,9 @@ import {
|
||||
GetCanonicalFileName,
|
||||
getDirectoryPath,
|
||||
getEmitModuleResolutionKind,
|
||||
getImpliedNodeFormatForFile,
|
||||
getModeForResolutionAtIndex,
|
||||
getModuleNameStringLiteralAt,
|
||||
getModuleSpecifierEndingPreference,
|
||||
getNodeModulePathParts,
|
||||
getNormalizedAbsolutePath,
|
||||
getOwnKeys,
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
Identifier,
|
||||
isAmbientModule,
|
||||
isApplicableVersionedTypesKey,
|
||||
isDeclarationFileName,
|
||||
isExternalModuleAugmentation,
|
||||
isExternalModuleNameRelative,
|
||||
isModuleBlock,
|
||||
@@ -71,9 +72,9 @@ import {
|
||||
ModuleDeclaration,
|
||||
ModuleKind,
|
||||
ModulePath,
|
||||
ModuleResolutionHost,
|
||||
ModuleResolutionKind,
|
||||
ModuleSpecifierCache,
|
||||
ModuleSpecifierEnding,
|
||||
ModuleSpecifierOptions,
|
||||
ModuleSpecifierResolutionHost,
|
||||
NodeFlags,
|
||||
@@ -89,6 +90,7 @@ import {
|
||||
ResolutionMode,
|
||||
resolvePath,
|
||||
ScriptKind,
|
||||
shouldAllowImportingTsExtension,
|
||||
some,
|
||||
SourceFile,
|
||||
startsWith,
|
||||
@@ -107,63 +109,65 @@ import {
|
||||
// Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers.
|
||||
|
||||
const enum RelativePreference { Relative, NonRelative, Shortest, ExternalNonRelative }
|
||||
// See UserPreferences#importPathEnding
|
||||
const enum Ending { Minimal, Index, JsExtension }
|
||||
|
||||
// Processed preferences
|
||||
interface Preferences {
|
||||
readonly relativePreference: RelativePreference;
|
||||
readonly ending: Ending;
|
||||
/**
|
||||
* @param syntaxImpliedNodeFormat Used when the import syntax implies ESM or CJS irrespective of the mode of the file.
|
||||
*/
|
||||
getAllowedEndingsInPreferredOrder(syntaxImpliedNodeFormat?: SourceFile["impliedNodeFormat"]): ModuleSpecifierEnding[];
|
||||
}
|
||||
|
||||
function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences {
|
||||
function getPreferences(
|
||||
{ importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences,
|
||||
compilerOptions: CompilerOptions,
|
||||
importingSourceFile: SourceFile,
|
||||
oldImportSpecifier?: string,
|
||||
): Preferences {
|
||||
const preferredEnding = getPreferredEnding();
|
||||
return {
|
||||
relativePreference:
|
||||
oldImportSpecifier !== undefined ? (isExternalModuleNameRelative(oldImportSpecifier) ?
|
||||
RelativePreference.Relative :
|
||||
RelativePreference.NonRelative) :
|
||||
importModuleSpecifierPreference === "relative" ? RelativePreference.Relative :
|
||||
importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative :
|
||||
importModuleSpecifierPreference === "project-relative" ? RelativePreference.ExternalNonRelative :
|
||||
RelativePreference.Shortest,
|
||||
ending: getEnding(),
|
||||
getAllowedEndingsInPreferredOrder: syntaxImpliedNodeFormat => {
|
||||
if ((syntaxImpliedNodeFormat ?? importingSourceFile.impliedNodeFormat) === ModuleKind.ESNext) {
|
||||
if (shouldAllowImportingTsExtension(compilerOptions, importingSourceFile.fileName)) {
|
||||
return [ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.JsExtension];
|
||||
}
|
||||
return [ModuleSpecifierEnding.JsExtension];
|
||||
}
|
||||
if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic) {
|
||||
return [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
|
||||
}
|
||||
switch (preferredEnding) {
|
||||
case ModuleSpecifierEnding.JsExtension: return [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index];
|
||||
case ModuleSpecifierEnding.TsExtension: return [ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Index];
|
||||
case ModuleSpecifierEnding.Index: return [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension];
|
||||
case ModuleSpecifierEnding.Minimal: return [ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
|
||||
default: Debug.assertNever(preferredEnding);
|
||||
}
|
||||
},
|
||||
};
|
||||
function getEnding(): Ending {
|
||||
switch (importModuleSpecifierEnding) {
|
||||
case "minimal": return Ending.Minimal;
|
||||
case "index": return Ending.Index;
|
||||
case "js": return Ending.JsExtension;
|
||||
default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension
|
||||
: getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal;
|
||||
|
||||
function getPreferredEnding(): ModuleSpecifierEnding {
|
||||
if (oldImportSpecifier !== undefined) {
|
||||
if (hasJSFileExtension(oldImportSpecifier)) return ModuleSpecifierEnding.JsExtension;
|
||||
if (endsWith(oldImportSpecifier, "/index")) return ModuleSpecifierEnding.Index;
|
||||
}
|
||||
return getModuleSpecifierEndingPreference(
|
||||
importModuleSpecifierEnding,
|
||||
importingSourceFile.impliedNodeFormat,
|
||||
compilerOptions,
|
||||
importingSourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences {
|
||||
return {
|
||||
relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative,
|
||||
ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ?
|
||||
Ending.JsExtension :
|
||||
getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal,
|
||||
};
|
||||
}
|
||||
|
||||
function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) {
|
||||
if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16
|
||||
&& getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) {
|
||||
return false;
|
||||
}
|
||||
return getImpliedNodeFormatForFile(importingSourceFileName, host.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS;
|
||||
}
|
||||
|
||||
function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost {
|
||||
return {
|
||||
fileExists: host.fileExists,
|
||||
readFile: Debug.checkDefined(host.readFile),
|
||||
directoryExists: host.directoryExists,
|
||||
getCurrentDirectory: host.getCurrentDirectory,
|
||||
realpath: host.realpath,
|
||||
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(),
|
||||
};
|
||||
}
|
||||
|
||||
// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`?
|
||||
// Because when this is called by the file renamer, `importingSourceFile` is the file being renamed,
|
||||
// while `importingSourceFileName` its *new* name. We need a source file just to get its
|
||||
@@ -178,7 +182,7 @@ export function updateModuleSpecifier(
|
||||
oldImportSpecifier: string,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
): string | undefined {
|
||||
const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}, options);
|
||||
const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences({}, compilerOptions, importingSourceFile, oldImportSpecifier), {}, options);
|
||||
if (res === oldImportSpecifier) return undefined;
|
||||
return res;
|
||||
}
|
||||
@@ -198,7 +202,7 @@ export function getModuleSpecifier(
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
): string {
|
||||
return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}, options);
|
||||
return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences({}, compilerOptions, importingSourceFile), {}, options);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -331,7 +335,7 @@ function computeModuleSpecifiers(
|
||||
options: ModuleSpecifierOptions = {},
|
||||
): readonly string[] {
|
||||
const info = getInfo(importingSourceFile.path, host);
|
||||
const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile);
|
||||
const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile);
|
||||
const existingSpecifier = forEach(modulePaths, modulePath => forEach(
|
||||
host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)),
|
||||
reason => {
|
||||
@@ -423,17 +427,18 @@ function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionH
|
||||
return { getCanonicalFileName, importingSourceFileName, sourceDirectory };
|
||||
}
|
||||
|
||||
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { ending, relativePreference }: Preferences): string;
|
||||
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { ending, relativePreference }: Preferences, pathsOnly?: boolean): string | undefined;
|
||||
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { ending, relativePreference }: Preferences, pathsOnly?: boolean): string | undefined {
|
||||
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, preferences: Preferences): string;
|
||||
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, preferences: Preferences, pathsOnly?: boolean): string | undefined;
|
||||
function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode, { getAllowedEndingsInPreferredOrder: getAllowedEndingsInPrefererredOrder, relativePreference }: Preferences, pathsOnly?: boolean): string | undefined {
|
||||
const { baseUrl, paths, rootDirs } = compilerOptions;
|
||||
if (pathsOnly && !paths) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { sourceDirectory, getCanonicalFileName } = info;
|
||||
const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) ||
|
||||
removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions);
|
||||
const allowedEndings = getAllowedEndingsInPrefererredOrder(importMode);
|
||||
const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, allowedEndings, compilerOptions) ||
|
||||
processEnding(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), allowedEndings, compilerOptions);
|
||||
if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) {
|
||||
return pathsOnly ? undefined : relativePath;
|
||||
}
|
||||
@@ -444,12 +449,12 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt
|
||||
return pathsOnly ? undefined : relativePath;
|
||||
}
|
||||
|
||||
const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, getAllowedEndings(ending, compilerOptions, importMode), host, compilerOptions);
|
||||
const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, allowedEndings, host, compilerOptions);
|
||||
if (pathsOnly) {
|
||||
return fromPaths;
|
||||
}
|
||||
|
||||
const maybeNonRelative = fromPaths === undefined && baseUrl !== undefined ? removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) : fromPaths;
|
||||
const maybeNonRelative = fromPaths === undefined && baseUrl !== undefined ? processEnding(relativeToBaseUrl, allowedEndings, compilerOptions) : fromPaths;
|
||||
if (!maybeNonRelative) {
|
||||
return relativePath;
|
||||
}
|
||||
@@ -508,10 +513,6 @@ export function countPathComponents(path: string): number {
|
||||
return count;
|
||||
}
|
||||
|
||||
function usesJsExtensionOnImports({ imports }: SourceFile): boolean {
|
||||
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false;
|
||||
}
|
||||
|
||||
function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) {
|
||||
return compareBooleans(b.isRedirect, a.isRedirect) || compareNumberOfDirectorySeparators(a.path, b.path);
|
||||
}
|
||||
@@ -698,19 +699,7 @@ function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeCh
|
||||
}
|
||||
}
|
||||
|
||||
function getAllowedEndings(preferredEnding: Ending, compilerOptions: CompilerOptions, importMode: ResolutionMode) {
|
||||
if (getEmitModuleResolutionKind(compilerOptions) >= ModuleResolutionKind.Node16 && importMode === ModuleKind.ESNext) {
|
||||
return [Ending.JsExtension];
|
||||
}
|
||||
switch (preferredEnding) {
|
||||
case Ending.JsExtension: return [Ending.JsExtension, Ending.Minimal, Ending.Index];
|
||||
case Ending.Index: return [Ending.Index, Ending.Minimal, Ending.JsExtension];
|
||||
case Ending.Minimal: return [Ending.Minimal, Ending.Index, Ending.JsExtension];
|
||||
default: Debug.assertNever(preferredEnding);
|
||||
}
|
||||
}
|
||||
|
||||
function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike<readonly string[]>, allowedEndings: Ending[], host: ModuleSpecifierResolutionHost, compilerOptions: CompilerOptions): string | undefined {
|
||||
function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike<readonly string[]>, allowedEndings: ModuleSpecifierEnding[], host: ModuleSpecifierResolutionHost, compilerOptions: CompilerOptions): string | undefined {
|
||||
for (const key in paths) {
|
||||
for (const patternText of paths[key]) {
|
||||
const pattern = normalizePath(patternText);
|
||||
@@ -751,9 +740,9 @@ function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike<rea
|
||||
// resolution, but as long criteria (b) above is met, I don't think its result needs to be the highest priority result
|
||||
// in module specifier generation. I have included it last, as it's difficult to tell exactly where it should be
|
||||
// sorted among the others for a particular value of `importModuleSpecifierEnding`.
|
||||
const candidates: { ending: Ending | undefined, value: string }[] = allowedEndings.map(ending => ({
|
||||
const candidates: { ending: ModuleSpecifierEnding | undefined, value: string }[] = allowedEndings.map(ending => ({
|
||||
ending,
|
||||
value: removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions)
|
||||
value: processEnding(relativeToBaseUrl, [ending], compilerOptions)
|
||||
}));
|
||||
if (tryGetExtensionFromPath(pattern)) {
|
||||
candidates.push({ ending: undefined, value: relativeToBaseUrl });
|
||||
@@ -774,23 +763,23 @@ function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike<rea
|
||||
}
|
||||
}
|
||||
else if (
|
||||
some(candidates, c => c.ending !== Ending.Minimal && pattern === c.value) ||
|
||||
some(candidates, c => c.ending === Ending.Minimal && pattern === c.value && validateEnding(c))
|
||||
some(candidates, c => c.ending !== ModuleSpecifierEnding.Minimal && pattern === c.value) ||
|
||||
some(candidates, c => c.ending === ModuleSpecifierEnding.Minimal && pattern === c.value && validateEnding(c))
|
||||
) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateEnding({ ending, value }: { ending: Ending | undefined, value: string }) {
|
||||
function validateEnding({ ending, value }: { ending: ModuleSpecifierEnding | undefined, value: string }) {
|
||||
// Optimization: `removeExtensionAndIndexPostFix` can query the file system (a good bit) if `ending` is `Minimal`, the basename
|
||||
// is 'index', and a `host` is provided. To avoid that until it's unavoidable, we ran the function with no `host` above. Only
|
||||
// here, after we've checked that the minimal ending is indeed a match (via the length and prefix/suffix checks / `some` calls),
|
||||
// do we check that the host-validated result is consistent with the answer we got before. If it's not, it falls back to the
|
||||
// `Ending.Index` result, which should already be in the list of candidates if `Minimal` was. (Note: the assumption here is
|
||||
// `ModuleSpecifierEnding.Index` result, which should already be in the list of candidates if `Minimal` was. (Note: the assumption here is
|
||||
// that every module resolution mode that supports dropping extensions also supports dropping `/index`. Like literally
|
||||
// everything else in this file, this logic needs to be updated if that's not true in some future module resolution mode.)
|
||||
return ending !== Ending.Minimal || value === removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions, host);
|
||||
return ending !== ModuleSpecifierEnding.Minimal || value === processEnding(relativeToBaseUrl, [ending], compilerOptions, host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,7 +854,7 @@ function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: s
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined {
|
||||
function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, allowedEndings: readonly ModuleSpecifierEnding[], compilerOptions: CompilerOptions): string | undefined {
|
||||
const normalizedTargetPaths = getPathsRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName);
|
||||
if (normalizedTargetPaths === undefined) {
|
||||
return undefined;
|
||||
@@ -879,10 +868,7 @@ function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileNam
|
||||
if (!shortest) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic
|
||||
? removeFileExtension(shortest)
|
||||
: removeExtensionAndIndexPostFix(shortest, ending, compilerOptions);
|
||||
return processEnding(shortest, allowedEndings, compilerOptions);
|
||||
}
|
||||
|
||||
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ResolutionMode): string | undefined {
|
||||
@@ -896,7 +882,8 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan
|
||||
|
||||
// Simplify the full file path to something that can be resolved by Node.
|
||||
|
||||
const preferences = getPreferences(host, userPreferences, options, importingSourceFile);
|
||||
const preferences = getPreferences(userPreferences, options, importingSourceFile);
|
||||
const allowedEndings = preferences.getAllowedEndingsInPreferredOrder();
|
||||
let moduleSpecifier = path;
|
||||
let isPackageRootPath = false;
|
||||
if (!packageNameOnly) {
|
||||
@@ -923,7 +910,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan
|
||||
// try with next level of directory
|
||||
packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1);
|
||||
if (packageRootIndex === -1) {
|
||||
moduleSpecifier = removeExtensionAndIndexPostFix(moduleFileName, preferences.ending, options, host);
|
||||
moduleSpecifier = processEnding(moduleFileName, allowedEndings, options, host);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -979,7 +966,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan
|
||||
const fromPaths = tryGetModuleNameFromPaths(
|
||||
subModuleName,
|
||||
versionPaths.paths,
|
||||
getAllowedEndings(preferences.ending, options, importMode),
|
||||
allowedEndings,
|
||||
host,
|
||||
options
|
||||
);
|
||||
@@ -1036,13 +1023,22 @@ function getPathsRelativeToRootDirs(path: string, rootDirs: readonly string[], g
|
||||
});
|
||||
}
|
||||
|
||||
function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions, host?: ModuleSpecifierResolutionHost): string {
|
||||
if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName;
|
||||
function processEnding(fileName: string, allowedEndings: readonly ModuleSpecifierEnding[], options: CompilerOptions, host?: ModuleSpecifierResolutionHost): string {
|
||||
if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
const noExtension = removeFileExtension(fileName);
|
||||
if (fileName === noExtension) return fileName;
|
||||
if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options);
|
||||
switch (ending) {
|
||||
case Ending.Minimal:
|
||||
if (fileName === noExtension) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) {
|
||||
return noExtension + getJSExtensionForFile(fileName, options);
|
||||
}
|
||||
|
||||
switch (allowedEndings[0]) {
|
||||
case ModuleSpecifierEnding.Minimal:
|
||||
const withoutIndex = removeSuffix(noExtension, "/index");
|
||||
if (host && withoutIndex !== noExtension && tryGetAnyFileFromPath(host, withoutIndex)) {
|
||||
// Can't remove index if there's a file by the same name as the directory.
|
||||
@@ -1050,12 +1046,23 @@ function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, option
|
||||
return noExtension;
|
||||
}
|
||||
return withoutIndex;
|
||||
case Ending.Index:
|
||||
case ModuleSpecifierEnding.Index:
|
||||
return noExtension;
|
||||
case Ending.JsExtension:
|
||||
case ModuleSpecifierEnding.JsExtension:
|
||||
return noExtension + getJSExtensionForFile(fileName, options);
|
||||
case ModuleSpecifierEnding.TsExtension:
|
||||
// For now, we don't know if this import is going to be type-only, which means we don't
|
||||
// know if a .d.ts extension is valid, so use no extension or a .js extension
|
||||
if (isDeclarationFileName(fileName)) {
|
||||
const extensionlessPriority = allowedEndings.findIndex(e => e === ModuleSpecifierEnding.Minimal || e === ModuleSpecifierEnding.Index);
|
||||
const jsPriority = allowedEndings.indexOf(ModuleSpecifierEnding.JsExtension);
|
||||
return extensionlessPriority !== -1 && extensionlessPriority < jsPriority
|
||||
? noExtension
|
||||
: noExtension + getJSExtensionForFile(fileName, options);
|
||||
}
|
||||
return fileName;
|
||||
default:
|
||||
return Debug.assertNever(ending);
|
||||
return Debug.assertNever(allowedEndings[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-5
@@ -132,6 +132,7 @@ import {
|
||||
getPropertyArrayElementValue,
|
||||
getPropertyAssignment,
|
||||
getResolvedModule,
|
||||
getResolveJsonModule,
|
||||
getRootLength,
|
||||
getSetExternalModuleIndicator,
|
||||
getSpellingSuggestion,
|
||||
@@ -215,6 +216,8 @@ import {
|
||||
ModuleResolutionHost,
|
||||
moduleResolutionIsEqualTo,
|
||||
ModuleResolutionKind,
|
||||
moduleResolutionSupportsPackageJsonExportsAndImports,
|
||||
moduleResolutionSupportsResolvingTsExtensions,
|
||||
Mutable,
|
||||
Node,
|
||||
NodeArray,
|
||||
@@ -3158,7 +3161,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
for (const node of file.statements) {
|
||||
collectModuleReferences(node, /*inAmbientModule*/ false);
|
||||
}
|
||||
if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) {
|
||||
|
||||
// `require` has no special meaning in `--moduleResolution bundler`
|
||||
const shouldProcessRequires = isJavaScriptFile && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Bundler;
|
||||
if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || shouldProcessRequires) {
|
||||
collectDynamicImportOrRequireCalls(file);
|
||||
}
|
||||
|
||||
@@ -3220,7 +3226,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
const r = /import|require/g;
|
||||
while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null
|
||||
const node = getNodeAtPosition(file, r.lastIndex);
|
||||
if (isJavaScriptFile && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) {
|
||||
if (shouldProcessRequires && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) {
|
||||
setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here
|
||||
imports = append(imports, node.arguments[0]);
|
||||
}
|
||||
@@ -4111,10 +4117,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
}
|
||||
}
|
||||
|
||||
if (options.resolveJsonModule) {
|
||||
if (getResolveJsonModule(options)) {
|
||||
if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs &&
|
||||
getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 &&
|
||||
getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) {
|
||||
getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext &&
|
||||
getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Bundler) {
|
||||
createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule");
|
||||
}
|
||||
// Any emit other than common js, amd, es2015 or esnext is error
|
||||
@@ -4205,6 +4212,21 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
|
||||
createOptionValueDiagnostic("importsNotUsedAsValues", Diagnostics.Option_preserveValueImports_can_only_be_used_when_module_is_set_to_es2015_or_later);
|
||||
}
|
||||
|
||||
if (options.allowImportingTsExtensions && !(moduleResolutionSupportsResolvingTsExtensions(options) && (options.noEmit || options.emitDeclarationOnly))) {
|
||||
createOptionValueDiagnostic("allowImportingTsExtensions", Diagnostics.Option_allowImportingTsExtensions_can_only_be_used_when_moduleResolution_is_set_to_bundler_and_either_noEmit_or_emitDeclarationOnly_is_set);
|
||||
}
|
||||
|
||||
const moduleResolution = getEmitModuleResolutionKind(options);
|
||||
if (options.resolvePackageJsonExports && !moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution)) {
|
||||
createOptionValueDiagnostic("resolvePackageJsonExports", Diagnostics.Option_0_can_only_be_used_when_moduleResolution_is_set_to_node16_nodenext_or_bundler, "resolvePackageJsonExports");
|
||||
}
|
||||
if (options.resolvePackageJsonImports && !moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution)) {
|
||||
createOptionValueDiagnostic("resolvePackageJsonImports", Diagnostics.Option_0_can_only_be_used_when_moduleResolution_is_set_to_node16_nodenext_or_bundler, "resolvePackageJsonImports");
|
||||
}
|
||||
if (options.customConditions && !moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution)) {
|
||||
createOptionValueDiagnostic("customConditions", Diagnostics.Option_0_can_only_be_used_when_moduleResolution_is_set_to_node16_nodenext_or_bundler, "customConditions");
|
||||
}
|
||||
|
||||
// If the emit is enabled make sure that every output file is unique and not overwriting any of the input files
|
||||
if (!options.noEmit && !options.suppressOutputPathCheck) {
|
||||
const emitHost = getEmitHost();
|
||||
@@ -4900,7 +4922,7 @@ export function getResolutionDiagnostic(options: CompilerOptions, { extension }:
|
||||
return getAllowJSCompilerOption(options) || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type;
|
||||
}
|
||||
function needResolveJsonModule() {
|
||||
return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used;
|
||||
return getResolveJsonModule(options) ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6887,6 +6887,7 @@ export enum ModuleResolutionKind {
|
||||
// In turn, we offer both a `NodeNext` moving resolution target, and a `Node16` version-anchored resolution target
|
||||
Node16 = 3,
|
||||
NodeNext = 99, // Not simply `Node16` so that compiled code linked against TS can use the `Next` value reliably (same as with `ModuleKind`)
|
||||
Bundler = 100,
|
||||
}
|
||||
|
||||
export enum ModuleDetectionKind {
|
||||
@@ -6946,6 +6947,7 @@ export type CompilerOptionsValue = string | number | boolean | (string | number)
|
||||
|
||||
export interface CompilerOptions {
|
||||
/** @internal */ all?: boolean;
|
||||
allowImportingTsExtensions?: boolean;
|
||||
allowJs?: boolean;
|
||||
/** @internal */ allowNonTsExtensions?: boolean;
|
||||
allowSyntheticDefaultImports?: boolean;
|
||||
@@ -6969,6 +6971,7 @@ export interface CompilerOptions {
|
||||
* @internal
|
||||
*/
|
||||
readonly configFile?: TsConfigSourceFile;
|
||||
customConditions?: string[];
|
||||
declaration?: boolean;
|
||||
declarationMap?: boolean;
|
||||
emitDeclarationOnly?: boolean;
|
||||
@@ -7055,6 +7058,8 @@ export interface CompilerOptions {
|
||||
incremental?: boolean;
|
||||
tsBuildInfoFile?: string;
|
||||
removeComments?: boolean;
|
||||
resolvePackageJsonExports?: boolean;
|
||||
resolvePackageJsonImports?: boolean;
|
||||
rootDir?: string;
|
||||
rootDirs?: string[];
|
||||
skipLibCheck?: boolean;
|
||||
@@ -7502,6 +7507,11 @@ export interface ResolvedModule {
|
||||
resolvedFileName: string;
|
||||
/** True if `resolvedFileName` comes from `node_modules`. */
|
||||
isExternalLibraryImport?: boolean;
|
||||
/**
|
||||
* True if the original module reference used a .ts extension to refer directly to a .ts file,
|
||||
* which should produce an error during checking if emit is enabled.
|
||||
*/
|
||||
resolvedUsingTsExtension?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+150
-6
@@ -11,6 +11,7 @@ import {
|
||||
AnyImportOrReExport,
|
||||
AnyImportSyntax,
|
||||
AnyValidImportOrReExport,
|
||||
append,
|
||||
arrayFrom,
|
||||
ArrayLiteralExpression,
|
||||
ArrayTypeNode,
|
||||
@@ -441,6 +442,7 @@ import {
|
||||
semanticDiagnosticsOptionDeclarations,
|
||||
SetAccessorDeclaration,
|
||||
ShorthandPropertyAssignment,
|
||||
shouldAllowImportingTsExtension,
|
||||
Signature,
|
||||
SignatureDeclaration,
|
||||
SignatureFlags,
|
||||
@@ -513,6 +515,7 @@ import {
|
||||
TypeReferenceNode,
|
||||
unescapeLeadingUnderscores,
|
||||
UnionOrIntersectionTypeNode,
|
||||
UserPreferences,
|
||||
ValidImportTypeNode,
|
||||
VariableDeclaration,
|
||||
VariableDeclarationInitializedTo,
|
||||
@@ -7693,6 +7696,10 @@ export function getEmitModuleKind(compilerOptions: {module?: CompilerOptions["mo
|
||||
getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 ? ModuleKind.ES2015 : ModuleKind.CommonJS;
|
||||
}
|
||||
|
||||
export function emitModuleKindIsNonNodeESM(moduleKind: ModuleKind) {
|
||||
return moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getEmitModuleResolutionKind(compilerOptions: CompilerOptions) {
|
||||
let moduleResolution = compilerOptions.moduleResolution;
|
||||
@@ -7768,11 +7775,62 @@ export function getESModuleInterop(compilerOptions: CompilerOptions) {
|
||||
|
||||
/** @internal */
|
||||
export function getAllowSyntheticDefaultImports(compilerOptions: CompilerOptions) {
|
||||
const moduleKind = getEmitModuleKind(compilerOptions);
|
||||
return compilerOptions.allowSyntheticDefaultImports !== undefined
|
||||
? compilerOptions.allowSyntheticDefaultImports
|
||||
: getESModuleInterop(compilerOptions) ||
|
||||
moduleKind === ModuleKind.System;
|
||||
if (compilerOptions.allowSyntheticDefaultImports !== undefined) {
|
||||
return compilerOptions.allowSyntheticDefaultImports;
|
||||
}
|
||||
return getESModuleInterop(compilerOptions)
|
||||
|| getEmitModuleKind(compilerOptions) === ModuleKind.System
|
||||
|| getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Bundler;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution: ModuleResolutionKind): boolean {
|
||||
return moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext
|
||||
|| moduleResolution === ModuleResolutionKind.Bundler;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getResolvePackageJsonExports(compilerOptions: CompilerOptions) {
|
||||
const moduleResolution = getEmitModuleResolutionKind(compilerOptions);
|
||||
if (!moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution)) {
|
||||
return false;
|
||||
}
|
||||
if (compilerOptions.resolvePackageJsonExports !== undefined) {
|
||||
return compilerOptions.resolvePackageJsonExports;
|
||||
}
|
||||
switch (moduleResolution) {
|
||||
case ModuleResolutionKind.Node16:
|
||||
case ModuleResolutionKind.NodeNext:
|
||||
case ModuleResolutionKind.Bundler:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getResolvePackageJsonImports(compilerOptions: CompilerOptions) {
|
||||
const moduleResolution = getEmitModuleResolutionKind(compilerOptions);
|
||||
if (!moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution)) {
|
||||
return false;
|
||||
}
|
||||
if (compilerOptions.resolvePackageJsonExports !== undefined) {
|
||||
return compilerOptions.resolvePackageJsonExports;
|
||||
}
|
||||
switch (moduleResolution) {
|
||||
case ModuleResolutionKind.Node16:
|
||||
case ModuleResolutionKind.NodeNext:
|
||||
case ModuleResolutionKind.Bundler:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getResolveJsonModule(compilerOptions: CompilerOptions) {
|
||||
if (compilerOptions.resolveJsonModule !== undefined) {
|
||||
return compilerOptions.resolveJsonModule;
|
||||
}
|
||||
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Bundler;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -8410,7 +8468,7 @@ export function getSupportedExtensionsWithJsonIfResolveJsonModule(options: Compi
|
||||
export function getSupportedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: readonly string[][]): readonly string[][];
|
||||
/** @internal */
|
||||
export function getSupportedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: readonly string[][]): readonly string[][] {
|
||||
if (!options || !options.resolveJsonModule) return supportedExtensions;
|
||||
if (!options || !getResolveJsonModule(options)) return supportedExtensions;
|
||||
if (supportedExtensions === allSupportedExtensions) return allSupportedExtensionsWithJson;
|
||||
if (supportedExtensions === supportedTSExtensions) return supportedTSExtensionsWithJson;
|
||||
return [...supportedExtensions, [Extension.Json]];
|
||||
@@ -8430,6 +8488,92 @@ export function hasTSFileExtension(fileName: string): boolean {
|
||||
return some(supportedTSExtensionsFlat, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Corresponds to UserPreferences#importPathEnding
|
||||
*/
|
||||
export const enum ModuleSpecifierEnding {
|
||||
Minimal,
|
||||
Index,
|
||||
JsExtension,
|
||||
TsExtension,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function usesExtensionsOnImports({ imports }: SourceFile, hasExtension: (text: string) => boolean = or(hasJSFileExtension, hasTSFileExtension)): boolean {
|
||||
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasExtension(text) : undefined) || false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getModuleSpecifierEndingPreference(preference: UserPreferences["importModuleSpecifierEnding"], resolutionMode: ResolutionMode, compilerOptions: CompilerOptions, sourceFile: SourceFile): ModuleSpecifierEnding {
|
||||
if (preference === "js" || resolutionMode === ModuleKind.ESNext) {
|
||||
// Extensions are explicitly requested or required. Now choose between .js and .ts.
|
||||
if (!shouldAllowImportingTsExtension(compilerOptions)) {
|
||||
return ModuleSpecifierEnding.JsExtension;
|
||||
}
|
||||
// `allowImportingTsExtensions` is a strong signal, so use .ts unless the file
|
||||
// already uses .js extensions and no .ts extensions.
|
||||
return inferPreference() !== ModuleSpecifierEnding.JsExtension
|
||||
? ModuleSpecifierEnding.TsExtension
|
||||
: ModuleSpecifierEnding.JsExtension;
|
||||
}
|
||||
if (preference === "minimal") {
|
||||
return ModuleSpecifierEnding.Minimal;
|
||||
}
|
||||
if (preference === "index") {
|
||||
return ModuleSpecifierEnding.Index;
|
||||
}
|
||||
|
||||
// No preference was specified.
|
||||
// Look at imports and/or requires to guess whether .js, .ts, or extensionless imports are preferred.
|
||||
// N.B. that `Index` detection is not supported since it would require file system probing to do
|
||||
// accurately, and more importantly, literally nobody wants `Index` and its existence is a mystery.
|
||||
if (!shouldAllowImportingTsExtension(compilerOptions)) {
|
||||
// If .ts imports are not valid, we only need to see one .js import to go with that.
|
||||
return usesExtensionsOnImports(sourceFile) ? ModuleSpecifierEnding.JsExtension : ModuleSpecifierEnding.Minimal;
|
||||
}
|
||||
|
||||
return inferPreference();
|
||||
|
||||
function inferPreference() {
|
||||
let usesJsExtensions = false;
|
||||
const specifiers = sourceFile.imports.length ? sourceFile.imports.map(i => i.text) :
|
||||
isSourceFileJS(sourceFile) ? getRequiresAtTopOfFile(sourceFile).map(r => r.arguments[0].text) :
|
||||
emptyArray;
|
||||
for (const specifier of specifiers) {
|
||||
if (pathIsRelative(specifier)) {
|
||||
if (hasTSFileExtension(specifier)) {
|
||||
return ModuleSpecifierEnding.TsExtension;
|
||||
}
|
||||
if (hasJSFileExtension(specifier)) {
|
||||
usesJsExtensions = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return usesJsExtensions ? ModuleSpecifierEnding.JsExtension : ModuleSpecifierEnding.Minimal;
|
||||
}
|
||||
}
|
||||
|
||||
function getRequiresAtTopOfFile(sourceFile: SourceFile): readonly RequireOrImportCall[] {
|
||||
let nonRequireStatementCount = 0;
|
||||
let requires: RequireOrImportCall[] | undefined;
|
||||
for (const statement of sourceFile.statements) {
|
||||
if (nonRequireStatementCount > 3) {
|
||||
break;
|
||||
}
|
||||
if (isRequireVariableStatement(statement)) {
|
||||
requires = concatenate(requires, statement.declarationList.declarations.map(d => d.initializer));
|
||||
}
|
||||
else if (isExpressionStatement(statement) && isRequireCall(statement.expression, /*requireStringLiteralLikeArgument*/ true)) {
|
||||
requires = append(requires, statement.expression);
|
||||
}
|
||||
else {
|
||||
nonRequireStatementCount++;
|
||||
}
|
||||
}
|
||||
return requires || emptyArray;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: readonly FileExtensionInfo[]) {
|
||||
if (!fileName) return false;
|
||||
|
||||
@@ -804,18 +804,6 @@ export class TestState {
|
||||
});
|
||||
}
|
||||
|
||||
private renderMarkers(markers: { text: string, fileName: string, position: number }[]) {
|
||||
const filesToDisplay = ts.deduplicate(markers.map(m => m.fileName), ts.equateValues);
|
||||
return filesToDisplay.map(fileName => {
|
||||
const markersToRender = markers.filter(m => m.fileName === fileName).sort((a, b) => b.position - a.position);
|
||||
let fileContent = this.tryGetFileContent(fileName) || "";
|
||||
for (const marker of markersToRender) {
|
||||
fileContent = fileContent.slice(0, marker.position) + `\x1b[1;4m/*${marker.text}*/\x1b[0;31m` + fileContent.slice(marker.position);
|
||||
}
|
||||
return `// @Filename: ${fileName}\n${fileContent}`;
|
||||
}).join("\n\n");
|
||||
}
|
||||
|
||||
private verifyDefinitionTextSpan(defs: ts.DefinitionInfoAndBoundSpan, startMarkerName: string) {
|
||||
const range = this.testData.ranges.find(range => this.markerName(range.marker!) === startMarkerName);
|
||||
|
||||
@@ -839,6 +827,22 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
private renderMarkers(markers: { text: string, fileName: string, position: number }[], useTerminalBoldSequence = true) {
|
||||
const filesToDisplay = ts.deduplicate(markers.map(m => m.fileName), ts.equateValues);
|
||||
return filesToDisplay.map(fileName => {
|
||||
const markersToRender = markers.filter(m => m.fileName === fileName).sort((a, b) => b.position - a.position);
|
||||
let fileContent = this.tryGetFileContent(fileName) || "";
|
||||
for (const marker of markersToRender) {
|
||||
fileContent = fileContent.slice(0, marker.position) + bold(`/*${marker.text}*/`) + fileContent.slice(marker.position);
|
||||
}
|
||||
return `// @Filename: ${fileName}\n${fileContent}`;
|
||||
}).join("\n\n");
|
||||
|
||||
function bold(text: string) {
|
||||
return useTerminalBoldSequence ? `\x1b[1;4m${text}\x1b[0;31m` : text;
|
||||
}
|
||||
}
|
||||
|
||||
public verifyGetEmitOutputForCurrentFile(expected: string): void {
|
||||
const emit = this.languageService.getEmitOutput(this.activeFile.fileName);
|
||||
if (emit.outputFiles.length !== 1) {
|
||||
@@ -3188,12 +3192,53 @@ export class TestState {
|
||||
if (!negative && !validBraceCompletion) {
|
||||
this.raiseError(`${position} is not a valid brace completion position for ${openingBrace}`);
|
||||
}
|
||||
|
||||
if (negative && validBraceCompletion) {
|
||||
this.raiseError(`${position} is a valid brace completion position for ${openingBrace}`);
|
||||
}
|
||||
}
|
||||
|
||||
public baselineAutoImports(markerName: string, preferences?: ts.UserPreferences) {
|
||||
const marker = this.getMarkerByName(markerName);
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile(`.baseline.md`);
|
||||
const completionPreferences = {
|
||||
includeCompletionsForModuleExports: true,
|
||||
includeCompletionsWithInsertText: true,
|
||||
allowIncompleteCompletions: true,
|
||||
includeCompletionsWithSnippetText: true,
|
||||
...preferences
|
||||
};
|
||||
|
||||
const ext = ts.getAnyExtensionFromPath(this.activeFile.fileName).slice(1);
|
||||
const lang = ["mts", "cts"].includes(ext) ? "ts" : ext;
|
||||
let baselineText = codeFence(this.renderMarkers([{ text: "|", fileName: marker.fileName, position: marker.position }], /*useTerminalBoldSequence*/ false), lang) + "\n\n";
|
||||
this.goToMarker(marker);
|
||||
|
||||
const completions = this.getCompletionListAtCaret(completionPreferences)!;
|
||||
|
||||
const autoImportCompletions = completions.entries.filter(c => c.hasAction && c.source && c.sortText === ts.Completions.SortText.AutoImportSuggestions);
|
||||
if (autoImportCompletions.length) {
|
||||
baselineText += `## From completions\n\n${autoImportCompletions.map(c => `- \`${c.name}\` from \`"${c.source}"\``).join("\n")}\n\n`;
|
||||
autoImportCompletions.forEach(c => {
|
||||
const details = this.getCompletionEntryDetails(c.name, c.source, c.data, completionPreferences);
|
||||
assert(details?.codeActions, `Entry '${c.name}' from "${c.source}" returned no code actions from completion details request`);
|
||||
assert(details.codeActions.length === 1, `Entry '${c.name}' from "${c.source}" returned more than one code action`);
|
||||
assert(details.codeActions[0].changes.length === 1, `Entry '${c.name}' from "${c.source}" returned a code action changing more than one file`);
|
||||
assert(details.codeActions[0].changes[0].fileName === this.activeFile.fileName, `Entry '${c.name}' from "${c.source}" returned a code action changing a different file`);
|
||||
const changes = details.codeActions[0].changes[0].textChanges;
|
||||
const completionChange: ts.TextChange = { newText: c.insertText || c.name, span: c.replacementSpan || completions.optionalReplacementSpan || { start: marker.position, length: 0 } };
|
||||
const sortedChanges = [...changes, completionChange].sort((a, b) => a.span.start - b.span.start);
|
||||
let newFileContent = this.activeFile.content;
|
||||
for (let i = sortedChanges.length - 1; i >= 0; i--) {
|
||||
newFileContent = newFileContent.substring(0, sortedChanges[i].span.start) + sortedChanges[i].newText + newFileContent.substring(sortedChanges[i].span.start + sortedChanges[i].span.length);
|
||||
}
|
||||
baselineText += codeFence(newFileContent, lang) + "\n\n";
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: do codefixes too
|
||||
Harness.Baseline.runBaseline(baselineFile, baselineText);
|
||||
}
|
||||
|
||||
public verifyJsxClosingTag(map: { [markerName: string]: ts.JsxClosingTagInfo | undefined }): void {
|
||||
for (const markerName in map) {
|
||||
this.goToMarker(markerName);
|
||||
@@ -4605,10 +4650,10 @@ function rangesOfDiffBetweenTwoStrings(source: string, target: string) {
|
||||
else {
|
||||
ranges.push({ start: index - 1, length: 1 });
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
else {
|
||||
ranges.push({ start: index - 1, length: 1 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (let index = 0; index < Math.max(source.length, target.length); index++) {
|
||||
@@ -4618,10 +4663,10 @@ function rangesOfDiffBetweenTwoStrings(source: string, target: string) {
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
}
|
||||
|
||||
// Adds an _ when the source string and the target string have a whitespace difference
|
||||
function highlightDifferenceBetweenStrings(source: string, target: string) {
|
||||
// Adds an _ when the source string and the target string have a whitespace difference
|
||||
function highlightDifferenceBetweenStrings(source: string, target: string) {
|
||||
const ranges = rangesOfDiffBetweenTwoStrings(source, target);
|
||||
let emTarget = target;
|
||||
ranges.forEach((range, index) => {
|
||||
@@ -4633,8 +4678,12 @@ function rangesOfDiffBetweenTwoStrings(source: string, target: string) {
|
||||
range.start + 1 + additionalOffset,
|
||||
range.start + range.length + 1 + additionalOffset
|
||||
);
|
||||
const after = emTarget.slice(range.start + range.length + 1 + additionalOffset, emTarget.length);
|
||||
const after = emTarget.slice(range.start + range.length + 1 + additionalOffset, emTarget.length);
|
||||
emTarget = before + lhs + between + rhs + after;
|
||||
});
|
||||
return emTarget;
|
||||
}
|
||||
}
|
||||
|
||||
function codeFence(code: string, lang?: string) {
|
||||
return `\`\`\`${lang || ""}\n${code}\n\`\`\``;
|
||||
}
|
||||
|
||||
@@ -488,6 +488,10 @@ export class Verify extends VerifyNegatable {
|
||||
this.state.verifyImportFixModuleSpecifiers(marker, moduleSpecifiers, preferences);
|
||||
}
|
||||
|
||||
public baselineAutoImports(marker: string, preferences?: ts.UserPreferences) {
|
||||
this.state.baselineAutoImports(marker, preferences);
|
||||
}
|
||||
|
||||
public navigationBar(json: any, options?: { checkSpans?: boolean }) {
|
||||
this.state.verifyNavigationBar(json, options);
|
||||
}
|
||||
|
||||
@@ -814,7 +814,8 @@ function getNewImportFixes(
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host);
|
||||
const getChecker = createGetChecker(program, host);
|
||||
const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(getEmitModuleResolutionKind(compilerOptions));
|
||||
const moduleResolution = getEmitModuleResolutionKind(compilerOptions);
|
||||
const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(moduleResolution);
|
||||
const getModuleSpecifiers = fromCacheOnly
|
||||
? (moduleSymbol: Symbol) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false })
|
||||
: (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences);
|
||||
|
||||
@@ -271,7 +271,7 @@ import {
|
||||
modifierToFlag,
|
||||
ModuleDeclaration,
|
||||
ModuleReference,
|
||||
moduleResolutionRespectsExports,
|
||||
moduleResolutionSupportsPackageJsonExportsAndImports,
|
||||
NamedImportBindings,
|
||||
Node,
|
||||
NodeArray,
|
||||
@@ -555,7 +555,7 @@ const enum KeywordCompletionFilters {
|
||||
|
||||
const enum GlobalsSearch { Continue, Success, Fail }
|
||||
|
||||
interface ModuleSpecifierResolutioContext {
|
||||
interface ModuleSpecifierResolutionContext {
|
||||
tryResolve: (exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult;
|
||||
resolvedAny: () => boolean;
|
||||
skippedAny: () => boolean;
|
||||
@@ -576,7 +576,7 @@ function resolvingModuleSpecifiers<TReturn>(
|
||||
preferences: UserPreferences,
|
||||
isForImportStatementCompletion: boolean,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
cb: (context: ModuleSpecifierResolutioContext) => TReturn,
|
||||
cb: (context: ModuleSpecifierResolutionContext) => TReturn,
|
||||
): TReturn {
|
||||
const start = timestamp();
|
||||
// Under `--moduleResolution nodenext`, we have to resolve module specifiers up front, because
|
||||
@@ -584,7 +584,7 @@ function resolvingModuleSpecifiers<TReturn>(
|
||||
// relative path into node_modules), and we want to filter those completions out entirely.
|
||||
// Import statement completions always need specifier resolution because the module specifier is
|
||||
// part of their `insertText`, not the `codeActions` creating edits away from the cursor.
|
||||
const needsFullResolution = isForImportStatementCompletion || moduleResolutionRespectsExports(getEmitModuleResolutionKind(program.getCompilerOptions()));
|
||||
const needsFullResolution = isForImportStatementCompletion || moduleResolutionSupportsPackageJsonExportsAndImports(getEmitModuleResolutionKind(program.getCompilerOptions()));
|
||||
let skippedAny = false;
|
||||
let ambientCount = 0;
|
||||
let resolvedCount = 0;
|
||||
|
||||
@@ -49,10 +49,12 @@ import {
|
||||
getEmitModuleResolutionKind,
|
||||
getLeadingCommentRanges,
|
||||
getModeForUsageLocation,
|
||||
getModuleSpecifierEndingPreference,
|
||||
getOwnKeys,
|
||||
getPackageJsonTypesVersionsPaths,
|
||||
getPathComponents,
|
||||
getReplacementSpanForContextToken,
|
||||
getResolvePackageJsonExports,
|
||||
getSupportedExtensions,
|
||||
getSupportedExtensionsWithJsonIfResolveJsonModule,
|
||||
getTokenAtPosition,
|
||||
@@ -89,7 +91,8 @@ import {
|
||||
mapDefined,
|
||||
MapLike,
|
||||
ModuleKind,
|
||||
ModuleResolutionKind,
|
||||
moduleResolutionUsesNodeModules,
|
||||
ModuleSpecifierEnding,
|
||||
moduleSpecifiers,
|
||||
Node,
|
||||
normalizePath,
|
||||
@@ -120,6 +123,7 @@ import {
|
||||
StringLiteralLike,
|
||||
StringLiteralType,
|
||||
stripQuotes,
|
||||
supportedTSImplementationExtensions,
|
||||
Symbol,
|
||||
SyntaxKind,
|
||||
textPart,
|
||||
@@ -525,49 +529,44 @@ function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile
|
||||
|
||||
const scriptPath = sourceFile.path;
|
||||
const scriptDirectory = getDirectoryPath(scriptPath);
|
||||
const extensionOptions = getExtensionOptions(compilerOptions, ReferenceKind.ModuleSpecifier, sourceFile, preferences, mode);
|
||||
|
||||
return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue))
|
||||
? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath, getIncludeExtensionOption())
|
||||
: getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, mode, compilerOptions, host, getIncludeExtensionOption(), typeChecker);
|
||||
|
||||
function getIncludeExtensionOption() {
|
||||
const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined;
|
||||
return preferences.importModuleSpecifierEnding === "js" || mode === ModuleKind.ESNext ? IncludeExtensionsOption.ModuleSpecifierCompletion : IncludeExtensionsOption.Exclude;
|
||||
}
|
||||
? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath, extensionOptions)
|
||||
: getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, mode, compilerOptions, host, extensionOptions, typeChecker);
|
||||
}
|
||||
|
||||
interface ExtensionOptions {
|
||||
readonly extensions: readonly Extension[];
|
||||
readonly includeExtensionsOption: IncludeExtensionsOption;
|
||||
readonly extensionsToSearch: readonly Extension[];
|
||||
readonly referenceKind: ReferenceKind;
|
||||
readonly importingSourceFile: SourceFile;
|
||||
readonly endingPreference?: UserPreferences["importModuleSpecifierEnding"];
|
||||
readonly resolutionMode?: ResolutionMode;
|
||||
}
|
||||
function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensionsOption = IncludeExtensionsOption.Exclude): ExtensionOptions {
|
||||
return { extensions: flatten(getSupportedExtensionsForModuleResolution(compilerOptions)), includeExtensionsOption };
|
||||
|
||||
function getExtensionOptions(compilerOptions: CompilerOptions, referenceKind: ReferenceKind, importingSourceFile: SourceFile, preferences?: UserPreferences, resolutionMode?: ResolutionMode): ExtensionOptions {
|
||||
return {
|
||||
extensionsToSearch: flatten(getSupportedExtensionsForModuleResolution(compilerOptions)),
|
||||
referenceKind,
|
||||
importingSourceFile,
|
||||
endingPreference: preferences?.importModuleSpecifierEnding,
|
||||
resolutionMode,
|
||||
};
|
||||
}
|
||||
function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, includeExtensions: IncludeExtensionsOption) {
|
||||
const extensionOptions = getExtensionOptions(compilerOptions, includeExtensions);
|
||||
function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, extensionOptions: ExtensionOptions) {
|
||||
if (compilerOptions.rootDirs) {
|
||||
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
|
||||
compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath);
|
||||
}
|
||||
else {
|
||||
return arrayFrom(getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, scriptPath).values());
|
||||
return arrayFrom(getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, /*moduleSpecifierIsRelative*/ false, scriptPath).values());
|
||||
}
|
||||
}
|
||||
|
||||
function isEmitResolutionKindUsingNodeModules(compilerOptions: CompilerOptions): boolean {
|
||||
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs ||
|
||||
getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 ||
|
||||
getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext;
|
||||
}
|
||||
|
||||
function isEmitModuleResolutionRespectingExportMaps(compilerOptions: CompilerOptions) {
|
||||
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 ||
|
||||
getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext;
|
||||
}
|
||||
|
||||
function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions): readonly Extension[][] {
|
||||
const extensions = getSupportedExtensions(compilerOptions);
|
||||
return isEmitResolutionKindUsingNodeModules(compilerOptions) ?
|
||||
const moduleResolution = getEmitModuleResolutionKind(compilerOptions);
|
||||
return moduleResolutionUsesNodeModules(moduleResolution) ?
|
||||
getSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, extensions) :
|
||||
extensions;
|
||||
}
|
||||
@@ -595,22 +594,22 @@ function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[]
|
||||
const basePath = compilerOptions.project || host.getCurrentDirectory();
|
||||
const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames());
|
||||
const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptDirectory, ignoreCase);
|
||||
return flatMap(baseDirectories, baseDirectory => arrayFrom(getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude).values()));
|
||||
return flatMap(baseDirectories, baseDirectory => arrayFrom(getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, /*moduleSpecifierIsRelative*/ true, exclude).values()));
|
||||
}
|
||||
|
||||
const enum IncludeExtensionsOption {
|
||||
Exclude,
|
||||
Include,
|
||||
ModuleSpecifierCompletion,
|
||||
const enum ReferenceKind {
|
||||
Filename,
|
||||
ModuleSpecifier,
|
||||
}
|
||||
/**
|
||||
* Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename.
|
||||
*/
|
||||
function getCompletionEntriesForDirectoryFragment(
|
||||
fragment: string,
|
||||
scriptPath: string,
|
||||
scriptDirectory: string,
|
||||
extensionOptions: ExtensionOptions,
|
||||
host: LanguageServiceHost,
|
||||
moduleSpecifierIsRelative: boolean,
|
||||
exclude?: string,
|
||||
result = createNameAndKindSet()
|
||||
): NameAndKindSet {
|
||||
@@ -634,23 +633,25 @@ function getCompletionEntriesForDirectoryFragment(
|
||||
|
||||
fragment = ensureTrailingDirectorySeparator(fragment);
|
||||
|
||||
const absolutePath = resolvePath(scriptPath, fragment);
|
||||
const absolutePath = resolvePath(scriptDirectory, fragment);
|
||||
const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath);
|
||||
|
||||
// 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 versionPaths = getPackageJsonTypesVersionsPaths(typesVersions)?.paths;
|
||||
if (versionPaths) {
|
||||
const packageDirectory = getDirectoryPath(packageJsonPath);
|
||||
const pathInPackage = absolutePath.slice(ensureTrailingDirectorySeparator(packageDirectory).length);
|
||||
if (addCompletionEntriesFromPaths(result, pathInPackage, packageDirectory, extensionOptions, host, versionPaths)) {
|
||||
// A true result means one of the `versionPaths` was matched, which will block relative resolution
|
||||
// to files and folders from here. All reachable paths given the pattern match are already added.
|
||||
return result;
|
||||
if (!moduleSpecifierIsRelative) {
|
||||
// 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 versionPaths = getPackageJsonTypesVersionsPaths(typesVersions)?.paths;
|
||||
if (versionPaths) {
|
||||
const packageDirectory = getDirectoryPath(packageJsonPath);
|
||||
const pathInPackage = absolutePath.slice(ensureTrailingDirectorySeparator(packageDirectory).length);
|
||||
if (addCompletionEntriesFromPaths(result, pathInPackage, packageDirectory, extensionOptions, host, versionPaths)) {
|
||||
// A true result means one of the `versionPaths` was matched, which will block relative resolution
|
||||
// to files and folders from here. All reachable paths given the pattern match are already added.
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -660,16 +661,16 @@ function getCompletionEntriesForDirectoryFragment(
|
||||
if (!tryDirectoryExists(host, baseDirectory)) return result;
|
||||
|
||||
// Enumerate the available files if possible
|
||||
const files = tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, /*include*/ ["./*"]);
|
||||
const files = tryReadDirectory(host, baseDirectory, extensionOptions.extensionsToSearch, /*exclude*/ undefined, /*include*/ ["./*"]);
|
||||
|
||||
if (files) {
|
||||
for (let filePath of files) {
|
||||
filePath = normalizePath(filePath);
|
||||
if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) {
|
||||
if (exclude && comparePaths(filePath, exclude, scriptDirectory, ignoreCase) === Comparison.EqualTo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name, extension } = getFilenameWithExtensionOption(getBaseFileName(filePath), host.getCompilationSettings(), extensionOptions.includeExtensionsOption);
|
||||
const { name, extension } = getFilenameWithExtensionOption(getBaseFileName(filePath), host.getCompilationSettings(), extensionOptions);
|
||||
result.add(nameAndKind(name, ScriptElementKind.scriptElement, extension));
|
||||
}
|
||||
}
|
||||
@@ -689,17 +690,32 @@ function getCompletionEntriesForDirectoryFragment(
|
||||
return result;
|
||||
}
|
||||
|
||||
function getFilenameWithExtensionOption(name: string, compilerOptions: CompilerOptions, includeExtensionsOption: IncludeExtensionsOption): { name: string, extension: Extension | undefined } {
|
||||
const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(name, compilerOptions);
|
||||
if (includeExtensionsOption === IncludeExtensionsOption.Exclude && !fileExtensionIsOneOf(name, [Extension.Json, Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs])) {
|
||||
return { name: removeFileExtension(name), extension: tryGetExtensionFromPath(name) };
|
||||
}
|
||||
else if ((fileExtensionIsOneOf(name, [Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs]) || includeExtensionsOption === IncludeExtensionsOption.ModuleSpecifierCompletion) && outputExtension) {
|
||||
return { name: changeExtension(name, outputExtension), extension: outputExtension };
|
||||
}
|
||||
else {
|
||||
function getFilenameWithExtensionOption(name: string, compilerOptions: CompilerOptions, extensionOptions: ExtensionOptions): { name: string, extension: Extension | undefined } {
|
||||
if (extensionOptions.referenceKind === ReferenceKind.Filename) {
|
||||
return { name, extension: tryGetExtensionFromPath(name) };
|
||||
}
|
||||
|
||||
const endingPreference = getModuleSpecifierEndingPreference(extensionOptions.endingPreference, extensionOptions.resolutionMode, compilerOptions, extensionOptions.importingSourceFile);
|
||||
if (endingPreference === ModuleSpecifierEnding.TsExtension) {
|
||||
if (fileExtensionIsOneOf(name, supportedTSImplementationExtensions)) {
|
||||
return { name, extension: tryGetExtensionFromPath(name) };
|
||||
}
|
||||
const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(name, compilerOptions);
|
||||
return outputExtension
|
||||
? { name: changeExtension(name, outputExtension), extension: outputExtension }
|
||||
: { name, extension: tryGetExtensionFromPath(name) };
|
||||
}
|
||||
|
||||
if ((endingPreference === ModuleSpecifierEnding.Minimal || endingPreference === ModuleSpecifierEnding.Index) &&
|
||||
fileExtensionIsOneOf(name, [Extension.Js, Extension.Jsx, Extension.Ts, Extension.Tsx, Extension.Dts])
|
||||
) {
|
||||
return { name: removeFileExtension(name), extension: tryGetExtensionFromPath(name) };
|
||||
}
|
||||
|
||||
const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(name, compilerOptions);
|
||||
return outputExtension
|
||||
? { name: changeExtension(name, outputExtension), extension: outputExtension }
|
||||
: { name, extension: tryGetExtensionFromPath(name) };
|
||||
}
|
||||
|
||||
/** @returns whether `fragment` was a match for any `paths` (which should indicate whether any other path completions should be offered) */
|
||||
@@ -786,17 +802,17 @@ function getCompletionEntriesForNonRelativeModules(
|
||||
mode: ResolutionMode,
|
||||
compilerOptions: CompilerOptions,
|
||||
host: LanguageServiceHost,
|
||||
includeExtensionsOption: IncludeExtensionsOption,
|
||||
extensionOptions: ExtensionOptions,
|
||||
typeChecker: TypeChecker,
|
||||
): readonly NameAndKind[] {
|
||||
const { baseUrl, paths } = compilerOptions;
|
||||
|
||||
const result = createNameAndKindSet();
|
||||
const extensionOptions = getExtensionOptions(compilerOptions, includeExtensionsOption);
|
||||
const moduleResolution = getEmitModuleResolutionKind(compilerOptions);
|
||||
if (baseUrl) {
|
||||
const projectDir = compilerOptions.project || host.getCurrentDirectory();
|
||||
const absolute = normalizePath(combinePaths(projectDir, baseUrl));
|
||||
getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*exclude*/ undefined, result);
|
||||
getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*moduleSpecifierIsRelative*/ false, /*exclude*/ undefined, result);
|
||||
if (paths) {
|
||||
addCompletionEntriesFromPaths(result, fragment, absolute, extensionOptions, host, paths);
|
||||
}
|
||||
@@ -809,7 +825,7 @@ function getCompletionEntriesForNonRelativeModules(
|
||||
|
||||
getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, fragmentDirectory, extensionOptions, result);
|
||||
|
||||
if (isEmitResolutionKindUsingNodeModules(compilerOptions)) {
|
||||
if (moduleResolutionUsesNodeModules(moduleResolution)) {
|
||||
// If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies.
|
||||
// (But do if we didn't find anything, e.g. 'package.json' missing.)
|
||||
let foundGlobal = false;
|
||||
@@ -826,10 +842,10 @@ function getCompletionEntriesForNonRelativeModules(
|
||||
let ancestorLookup: (directory: string) => void | undefined = ancestor => {
|
||||
const nodeModules = combinePaths(ancestor, "node_modules");
|
||||
if (tryDirectoryExists(host, nodeModules)) {
|
||||
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*exclude*/ undefined, result);
|
||||
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*moduleSpecifierIsRelative*/ false, /*exclude*/ undefined, result);
|
||||
}
|
||||
};
|
||||
if (fragmentDirectory && isEmitModuleResolutionRespectingExportMaps(compilerOptions)) {
|
||||
if (fragmentDirectory && getResolvePackageJsonExports(compilerOptions)) {
|
||||
const nodeModulesDirectoryLookup = ancestorLookup;
|
||||
ancestorLookup = ancestor => {
|
||||
const components = getPathComponents(fragment);
|
||||
@@ -967,13 +983,13 @@ function getModulesForPathsPattern(
|
||||
// done.
|
||||
const includeGlob = normalizedSuffix ? "**/*" + normalizedSuffix : "./*";
|
||||
|
||||
const matches = mapDefined(tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, [includeGlob]), match => {
|
||||
const matches = mapDefined(tryReadDirectory(host, baseDirectory, extensionOptions.extensionsToSearch, /*exclude*/ undefined, [includeGlob]), match => {
|
||||
const trimmedWithPattern = trimPrefixAndSuffix(match);
|
||||
if (trimmedWithPattern) {
|
||||
if (containsSlash(trimmedWithPattern)) {
|
||||
return directoryResult(getPathComponents(removeLeadingDirectorySeparator(trimmedWithPattern))[1]);
|
||||
}
|
||||
const { name, extension } = getFilenameWithExtensionOption(trimmedWithPattern, host.getCompilationSettings(), extensionOptions.includeExtensionsOption);
|
||||
const { name, extension } = getFilenameWithExtensionOption(trimmedWithPattern, host.getCompilationSettings(), extensionOptions);
|
||||
return nameAndKind(name, ScriptElementKind.scriptElement, extension);
|
||||
}
|
||||
});
|
||||
@@ -1030,8 +1046,8 @@ function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: num
|
||||
|
||||
const [, prefix, kind, toComplete] = match;
|
||||
const scriptPath = getDirectoryPath(sourceFile.path);
|
||||
const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, IncludeExtensionsOption.Include), host, sourceFile.path)
|
||||
: kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions))
|
||||
const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, ReferenceKind.Filename, sourceFile), host, /*moduleSpecifierIsRelative*/ true, sourceFile.path)
|
||||
: kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions, ReferenceKind.ModuleSpecifier, sourceFile))
|
||||
: Debug.fail();
|
||||
return addReplacementSpans(toComplete, range.pos + prefix.length, arrayFrom(names.values()));
|
||||
}
|
||||
@@ -1071,7 +1087,7 @@ function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: Com
|
||||
const baseDirectory = combinePaths(directory, typeDirectoryName);
|
||||
const remainingFragment = tryRemoveDirectoryPrefix(fragmentDirectory, packageName, hostGetCanonicalFileName(host));
|
||||
if (remainingFragment !== undefined) {
|
||||
getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*exclude*/ undefined, result);
|
||||
getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*moduleSpecifierIsRelative*/ false, /*exclude*/ undefined, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2374,6 +2374,7 @@ export function programContainsModules(program: Program): boolean {
|
||||
export function programContainsEsModules(program: Program): boolean {
|
||||
return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!s.externalModuleIndicator);
|
||||
}
|
||||
// TODO: this function is, at best, poorly named. Use sites are pretty suspicious.
|
||||
/** @internal */
|
||||
export function compilerOptionsIndicateEsModules(compilerOptions: CompilerOptions): boolean {
|
||||
return !!compilerOptions.module || getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 || !!compilerOptions.noEmit;
|
||||
@@ -2408,11 +2409,6 @@ export function getModuleSpecifierResolverHost(program: Program, host: LanguageS
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function moduleResolutionRespectsExports(moduleResolution: ModuleResolutionKind): boolean {
|
||||
return moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function moduleResolutionUsesNodeModules(moduleResolution: ModuleResolutionKind): boolean {
|
||||
return moduleResolution === ModuleResolutionKind.NodeJs || moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext;
|
||||
|
||||
@@ -135,8 +135,10 @@ class CompilerTest {
|
||||
"module",
|
||||
"moduleResolution",
|
||||
"moduleDetection",
|
||||
"allowImportingTsExtensions",
|
||||
"target",
|
||||
"jsx",
|
||||
"noEmit",
|
||||
"removeComments",
|
||||
"importHelpers",
|
||||
"importHelpers",
|
||||
@@ -161,6 +163,8 @@ class CompilerTest {
|
||||
"useUnknownInCatchVariables",
|
||||
"noUncheckedIndexedAccess",
|
||||
"noPropertyAccessFromIndexSignature",
|
||||
"resolvePackageJsonExports",
|
||||
"resolvePackageJsonImports",
|
||||
];
|
||||
private fileName: string;
|
||||
private justName: string;
|
||||
|
||||
@@ -170,4 +170,4 @@ export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: rea
|
||||
export function updateProgramText(files: readonly NamedSourceText[], fileName: string, newProgramText: string) {
|
||||
const file = ts.find(files, f => f.name === fileName)!;
|
||||
file.text = file.text.updateProgram(newProgramText);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user