Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --module preserve, support require in --moduleResolution bundler #56785

Merged
merged 32 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bc50c75
Add --module preserve
andrewbranch Dec 14, 2023
1fb3d75
Update diagnostic wording in baselines
andrewbranch Dec 14, 2023
76aefde
REVERT ME: esnext -> preserve for RWC testing
andrewbranch Dec 14, 2023
9623430
Revert "REVERT ME: esnext -> preserve for RWC testing"
andrewbranch Dec 14, 2023
b0aa238
Resolve ImportEquals as require
andrewbranch Dec 14, 2023
facd177
Resolve require in JS
andrewbranch Dec 14, 2023
60f2d96
Allow TLA and import attributes
andrewbranch Dec 14, 2023
aea6e59
Remove outdated test comment
andrewbranch Dec 14, 2023
295104d
REVERT ME: esnext -> preserve for RWC testing
andrewbranch Dec 14, 2023
e7bdce6
Revert "REVERT ME: esnext -> preserve for RWC testing"
andrewbranch Dec 14, 2023
db0ca33
Fix resolution of JSX synthesized import
andrewbranch Dec 14, 2023
186e329
Fix more TLA stuff
andrewbranch Dec 14, 2023
4142f99
REVERT ME: esnext -> preserve for RWC testing
andrewbranch Dec 14, 2023
739e153
Revert "REVERT ME: esnext -> preserve for RWC testing"
andrewbranch Dec 14, 2023
6799c53
Add big test
andrewbranch Dec 15, 2023
cf07180
Add coverage from existing tests
andrewbranch Dec 15, 2023
739e718
Remove unused baselines
andrewbranch Dec 15, 2023
c25e1cc
Fix getSymbolAtLocation for bundler
andrewbranch Dec 15, 2023
7f70385
Remove extraneous `export {}` emit from --module preserve
andrewbranch Dec 15, 2023
94bd2ee
Add dynamic import to tests
andrewbranch Dec 15, 2023
3c2ebd7
Use referenced project options to determine import mode
andrewbranch Jan 11, 2024
6c24f29
Merge branch 'main' into feature/bundler-require
andrewbranch Jan 16, 2024
ea0407e
Update baselines from main merge
andrewbranch Jan 16, 2024
ff7d405
Create and use program.getModeForUsageLocation
andrewbranch Jan 16, 2024
521031d
Fix missed location
andrewbranch Jan 16, 2024
2a205c4
Fix more missed locations
andrewbranch Jan 16, 2024
c4c170a
Update API baseline
andrewbranch Jan 17, 2024
902fa13
Old module specifier nodes not bound in watch/builder programs or som…
andrewbranch Jan 17, 2024
2121c2d
Need ! until LKG bump
andrewbranch Jan 17, 2024
2c33ae3
Guard against undefined `compilerOptions` in changed public API; expo…
andrewbranch Jan 17, 2024
3111995
Fix old program resolution lookups
andrewbranch Jan 19, 2024
eeba653
Update protocol module/moduleResolution
andrewbranch Jan 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ import {
setValueDeclaration,
ShorthandPropertyAssignment,
shouldPreserveConstEnums,
shouldResolveJsRequire,
SignatureDeclaration,
skipParentheses,
sliceAfter,
Expand Down Expand Up @@ -3559,7 +3558,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
const possibleVariableDecl = node.kind === SyntaxKind.VariableDeclaration ? node : node.parent.parent;
if (
isInJSFile(node) &&
shouldResolveJsRequire(options) &&
isVariableDeclarationInitializedToBareOrAccessedRequire(possibleVariableDecl) &&
!getJSDocTypeTag(node) &&
!(getCombinedModifierFlags(node) & ModifierFlags.Export)
Expand Down
45 changes: 26 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,6 @@ import {
getLineAndCharacterOfPosition,
getLocalSymbolForExportDefault,
getMembersOfDeclaration,
getModeForUsageLocation,
getModifiers,
getModuleInstanceState,
getNameFromImportAttribute,
Expand Down Expand Up @@ -960,7 +959,6 @@ import {
ShorthandPropertyAssignment,
shouldAllowImportingTsExtension,
shouldPreserveConstEnums,
shouldResolveJsRequire,
Signature,
SignatureDeclaration,
SignatureFlags,
Expand Down Expand Up @@ -4060,7 +4058,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getUsageModeForExpression(usage: Expression) {
return isStringLiteralLike(usage) ? getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined;
return isStringLiteralLike(usage) ? host.getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined;
}

function isESMFormatImportImportingCommonjsFormatFile(usageMode: ResolutionMode, targetMode: ResolutionMode) {
Expand All @@ -4074,7 +4072,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) {
const usageMode = file && getUsageModeForExpression(usage);
if (file && usageMode !== undefined) {
if (file && usageMode !== undefined && ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) {
const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat);
if (usageMode === ModuleKind.ESNext || result) {
return result;
Expand Down Expand Up @@ -5006,13 +5004,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const currentSourceFile = getSourceFileOfNode(location);
const contextSpecifier = isStringLiteralLike(location)
? location
: findAncestor(location, isImportCall)?.arguments[0] ||
: (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name ||
(isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal ||
(isVariableDeclaration(location) && location.initializer && isRequireCall(location.initializer, /*requireStringLiteralLikeArgument*/ true) ? location.initializer.arguments[0] : undefined) ||
findAncestor(location, isImportCall)?.arguments[0] ||
findAncestor(location, isImportDeclaration)?.moduleSpecifier ||
findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression ||
findAncestor(location, isExportDeclaration)?.moduleSpecifier ||
(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;
findAncestor(location, isExportDeclaration)?.moduleSpecifier;
const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? host.getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat;
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const resolvedModule = host.getResolvedModule(currentSourceFile, moduleReference, mode)?.resolvedModule;
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile);
Expand Down Expand Up @@ -32110,7 +32109,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const errorMessage = isClassic
? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_nodenext_or_to_add_aliases_to_the_paths_option
: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations;
const mod = resolveExternalModule(location!, runtimeImportSpecifier, errorMessage, location!);
// Synthesized JSX import is either first or after tslib
const jsxImportIndex = compilerOptions.importHelpers ? 1 : 0;
const specifier = file?.imports[jsxImportIndex];
if (specifier) {
Debug.assert(nodeIsSynthesized(specifier) && specifier.text === runtimeImportSpecifier, `Expected sourceFile.imports[${jsxImportIndex}] to be the synthesized JSX runtime import`);
}
const mod = resolveExternalModule(specifier || location!, runtimeImportSpecifier, errorMessage, location!);
const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined;
if (links) {
links.jsxImplicitImportContainer = result || false;
Expand Down Expand Up @@ -35838,7 +35843,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

// In JavaScript files, calls to any identifier 'require' are treated as external module imports
if (isInJSFile(node) && shouldResolveJsRequire(compilerOptions) && isCommonJsRequire(node)) {
if (isInJSFile(node) && isCommonJsRequire(node)) {
return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral);
}

Expand Down Expand Up @@ -37772,15 +37777,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// fallthrough
case ModuleKind.ES2022:
case ModuleKind.ESNext:
case ModuleKind.Preserve:
case ModuleKind.System:
if (languageVersion >= ScriptTarget.ES2017) {
break;
}
// fallthrough
default:
span ??= getSpanOfTokenAtPosition(sourceFile, node.pos);
const message = isAwaitExpression(node) ? Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher :
Diagnostics.Top_level_await_using_statements_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher;
const message = isAwaitExpression(node) ? Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher :
Diagnostics.Top_level_await_using_statements_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher;
diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message));
hasError = true;
break;
Expand Down Expand Up @@ -46084,14 +46090,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier);
if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) {
if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.Preserve) {
const message = isImportAttributes
? moduleKind === ModuleKind.NodeNext
? Diagnostics.Import_attributes_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls
: Diagnostics.Import_attributes_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext
: Diagnostics.Import_attributes_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve
: moduleKind === ModuleKind.NodeNext
? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls
: Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext;
: Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve;
return grammarErrorOnNode(node, message);
}

Expand Down Expand Up @@ -46175,7 +46181,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
else {
if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) {
if (moduleKind >= ModuleKind.ES2015 && moduleKind !== ModuleKind.Preserve && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) {
// 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);
}
Expand Down Expand Up @@ -46461,6 +46467,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Forbid export= in esm implementation files, and esm mode declaration files
if (
moduleKind >= ModuleKind.ES2015 &&
moduleKind !== ModuleKind.Preserve &&
((node.flags & NodeFlags.Ambient && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext) ||
(!(node.flags & NodeFlags.Ambient) && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS))
) {
Expand Down Expand Up @@ -47633,7 +47640,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false)) || isImportCall(node.parent)) ||
((isInJSFile(node) && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false)) || isImportCall(node.parent)) ||
(isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent)
) {
return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors);
Expand Down Expand Up @@ -50032,7 +50039,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// fallthrough
default:
diagnostics.add(
createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher),
createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher),
);
break;
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ export const moduleOptionDeclaration: CommandLineOptionOfCustomType = {
esnext: ModuleKind.ESNext,
node16: ModuleKind.Node16,
nodenext: ModuleKind.NodeNext,
preserve: ModuleKind.Preserve,
})),
affectsSourceFile: true,
affectsModuleResolution: true,
Expand Down
14 changes: 7 additions & 7 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,7 @@
"category": "Message",
"code": 1377
},
"Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": {
"Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', 'nodenext', or 'preserve', and the 'target' option is set to 'es2017' or higher.": {
"category": "Error",
"code": 1378
},
Expand Down Expand Up @@ -1424,7 +1424,7 @@
"category": "Error",
"code": 1431
},
"Top-level 'for await' loops are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": {
"Top-level 'for await' loops are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', 'nodenext', or 'preserve', and the 'target' option is set to 'es2017' or higher.": {
"category": "Error",
"code": 1432
},
Expand Down Expand Up @@ -3595,15 +3595,15 @@
"category": "Error",
"code": 2820
},
"Import assertions are only supported when the '--module' option is set to 'esnext' or 'nodenext'.": {
"Import assertions are only supported when the '--module' option is set to 'esnext', 'nodenext', or 'preserve'.": {
"category": "Error",
"code": 2821
},
"Import assertions cannot be used with type-only imports or exports.": {
"category": "Error",
"code": 2822
},
"Import attributes are only supported when the '--module' option is set to 'esnext' or 'nodenext'.": {
"Import attributes are only supported when the '--module' option is set to 'esnext', 'nodenext', or 'preserve'.": {
"category": "Error",
"code": 2823
},
Expand Down Expand Up @@ -3683,7 +3683,7 @@
"category": "Error",
"code": 2853
},
"Top-level 'await using' statements are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": {
"Top-level 'await using' statements are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', 'nodenext', or 'preserve', and the 'target' option is set to 'es2017' or higher.": {
"category": "Error",
"code": 2854
},
Expand Down Expand Up @@ -4309,7 +4309,7 @@
"category": "Error",
"code": 5070
},
"Option '--resolveJsonModule' can only be specified when module code generation is 'commonjs', 'amd', 'es2015' or 'esNext'.": {
"Option '--resolveJsonModule' cannot be specified when 'module' is set to 'none', 'system', or 'umd'.": {
"category": "Error",
"code": 5071
},
Expand Down Expand Up @@ -4401,7 +4401,7 @@
"category": "Error",
"code": 5094
},
"Option '{0}' can only be used when 'module' is set to 'es2015' or later.": {
"Option '{0}' can only be used when 'module' is set to 'preserve' or to 'es2015' or later.": {
"category": "Error",
"code": 5095
},
Expand Down
16 changes: 1 addition & 15 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import {
getCompilerOptionValue,
getDirectoryPath,
GetEffectiveTypeRootsHost,
getEmitModuleKind,
getEmitModuleResolutionKind,
getNormalizedAbsolutePath,
getOwnKeys,
Expand Down Expand Up @@ -1417,20 +1416,7 @@ export function resolveModuleName(moduleName: string, containingFile: string, co
else {
let moduleResolution = compilerOptions.moduleResolution;
if (moduleResolution === undefined) {
switch (getEmitModuleKind(compilerOptions)) {
case ModuleKind.CommonJS:
moduleResolution = ModuleResolutionKind.Node10;
break;
case ModuleKind.Node16:
moduleResolution = ModuleResolutionKind.Node16;
break;
case ModuleKind.NodeNext:
moduleResolution = ModuleResolutionKind.NodeNext;
break;
default:
moduleResolution = ModuleResolutionKind.Classic;
break;
}
moduleResolution = getEmitModuleResolutionKind(compilerOptions);
if (traceEnabled) {
trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ function computeModuleSpecifiers(
if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined;
// If the candidate import mode doesn't match the mode we're generating for, don't consider it
// TODO: maybe useful to keep around as an alternative option for certain contexts where the mode is overridable
if (importingSourceFile.impliedNodeFormat && importingSourceFile.impliedNodeFormat !== getModeForResolutionAtIndex(importingSourceFile, reason.index)) return undefined;
if (importingSourceFile.impliedNodeFormat && importingSourceFile.impliedNodeFormat !== getModeForResolutionAtIndex(importingSourceFile, reason.index, compilerOptions)) return undefined;
const specifier = getModuleNameStringLiteralAt(importingSourceFile, reason.index).text;
// If the preference is for non relative and the module specifier is relative, ignore it
return preferences.relativePreference !== RelativePreference.NonRelative || !pathIsRelative(specifier) ?
Expand Down
Loading
Loading