Skip to content

Commit 2fc2abe

Browse files
committed
Initial support for module: node12
1 parent faefc72 commit 2fc2abe

File tree

265 files changed

+4178
-473
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

265 files changed

+4178
-473
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2370,7 +2370,7 @@ namespace ts {
23702370

23712371
function checkStrictModeLabeledStatement(node: LabeledStatement) {
23722372
// Grammar checking for labeledStatement
2373-
if (inStrictMode && options.target! >= ScriptTarget.ES2015) {
2373+
if (inStrictMode && getEmitScriptTarget(options) >= ScriptTarget.ES2015) {
23742374
if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) {
23752375
errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here);
23762376
}

src/compiler/checker.ts

Lines changed: 51 additions & 40 deletions
Large diffs are not rendered by default.

src/compiler/commandLineParser.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,9 @@ namespace ts {
381381
es6: ModuleKind.ES2015,
382382
es2015: ModuleKind.ES2015,
383383
es2020: ModuleKind.ES2020,
384-
esnext: ModuleKind.ESNext
384+
esnext: ModuleKind.ESNext,
385+
node12: ModuleKind.Node12,
386+
nodenext: ModuleKind.NodeNext,
385387
})),
386388
affectsModuleResolution: true,
387389
affectsEmit: true,

src/compiler/diagnosticMessages.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@
944944
"category": "Error",
945945
"code": 1322
946946
},
947-
"Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', or 'umd'.": {
947+
"Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', 'umd', 'node12', or 'nodenext'.": {
948948
"category": "Error",
949949
"code": 1323
950950
},
@@ -1020,7 +1020,7 @@
10201020
"category": "Error",
10211021
"code": 1342
10221022
},
1023-
"The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system'.": {
1023+
"The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', 'system', 'node12', or 'nodenext'.": {
10241024
"category": "Error",
10251025
"code": 1343
10261026
},
@@ -1152,7 +1152,7 @@
11521152
"category": "Message",
11531153
"code": 1377
11541154
},
1155-
"Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.": {
1155+
"Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext', 'system', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": {
11561156
"category": "Error",
11571157
"code": 1378
11581158
},
@@ -1368,14 +1368,22 @@
13681368
"category": "Error",
13691369
"code": 1431
13701370
},
1371-
"Top-level 'for await' loops are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.": {
1371+
"Top-level 'for await' loops are only allowed when the 'module' option is set to 'esnext', 'system', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": {
13721372
"category": "Error",
13731373
"code": 1432
13741374
},
13751375
"Decorators may not be applied to 'this' parameters.": {
13761376
"category": "Error",
13771377
"code": 1433
13781378
},
1379+
"The 'import.meta' meta-property is not allowed in files which will build into CommonJS output.": {
1380+
"category": "Error",
1381+
"code": 1434
1382+
},
1383+
"Module '{0}' cannot be imported using this construct. The specifier only resolves to an es module, which cannot be imported synchronously. Use dynamic import instead.": {
1384+
"category": "Error",
1385+
"code": 1435
1386+
},
13791387

13801388
"The types of '{0}' are incompatible between these types.": {
13811389
"category": "Error",

src/compiler/factory/emitHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ namespace ts {
133133
// ES2018 Helpers
134134

135135
function createAssignHelper(attributesSegments: Expression[]) {
136-
if (context.getCompilerOptions().target! >= ScriptTarget.ES2015) {
136+
if (getEmitScriptTarget(context.getCompilerOptions()) >= ScriptTarget.ES2015) {
137137
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "assign"),
138138
/*typeArguments*/ undefined,
139139
attributesSegments);

src/compiler/factory/nodeFactory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5055,6 +5055,7 @@ namespace ts {
50555055
node.transformFlags =
50565056
propagateChildrenFlags(node.statements) |
50575057
propagateChildFlags(node.endOfFileToken);
5058+
node.impliedNodeFormat = source.impliedNodeFormat;
50585059
return node;
50595060
}
50605061

src/compiler/factory/utilities.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ namespace ts {
405405
if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) {
406406
let namedBindings: NamedImportBindings | undefined;
407407
const moduleKind = getEmitModuleKind(compilerOptions);
408-
if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) {
408+
if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || sourceFile.impliedNodeFormat === ModuleKind.ESNext) {
409409
// use named imports
410410
const helpers = getEmitHelpers(sourceFile);
411411
if (helpers) {
@@ -462,9 +462,9 @@ namespace ts {
462462
}
463463

464464
const moduleKind = getEmitModuleKind(compilerOptions);
465-
let create = (hasExportStarsToExportValues || (compilerOptions.esModuleInterop && hasImportStarOrImportDefault))
465+
let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault))
466466
&& moduleKind !== ModuleKind.System
467-
&& moduleKind < ModuleKind.ES2015;
467+
&& (moduleKind < ModuleKind.ES2015 || node.impliedNodeFormat === ModuleKind.CommonJS);
468468
if (!create) {
469469
const helpers = getEmitHelpers(node);
470470
if (helpers) {

src/compiler/moduleNameResolver.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ namespace ts {
113113
typesVersions?: MapLike<MapLike<string[]>>;
114114
main?: string;
115115
tsconfig?: string;
116+
type?: string;
116117
}
117118

118119
interface PackageJson extends PackageJsonPathFields {
@@ -816,7 +817,20 @@ namespace ts {
816817
else {
817818
let moduleResolution = compilerOptions.moduleResolution;
818819
if (moduleResolution === undefined) {
819-
moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
820+
switch (getEmitModuleKind(compilerOptions)) {
821+
case ModuleKind.CommonJS:
822+
moduleResolution = ModuleResolutionKind.NodeJs;
823+
break;
824+
case ModuleKind.Node12:
825+
moduleResolution = ModuleResolutionKind.Node12;
826+
break;
827+
case ModuleKind.NodeNext:
828+
moduleResolution = ModuleResolutionKind.NodeNext;
829+
break;
830+
default:
831+
moduleResolution = ModuleResolutionKind.Classic;
832+
break;
833+
}
820834
if (traceEnabled) {
821835
trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]);
822836
}
@@ -829,6 +843,8 @@ namespace ts {
829843

830844
perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/);
831845
switch (moduleResolution) {
846+
case ModuleResolutionKind.Node12:
847+
case ModuleResolutionKind.NodeNext: // TODO: Implement node12/nodenext resolution rules
832848
case ModuleResolutionKind.NodeJs:
833849
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference);
834850
break;
@@ -1310,7 +1326,8 @@ namespace ts {
13101326
versionPaths: VersionPaths | undefined;
13111327
}
13121328

1313-
function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
1329+
/*@internal*/
1330+
export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
13141331
const { host, traceEnabled } = state;
13151332
const packageJsonPath = combinePaths(packageDirectory, "package.json");
13161333
if (onlyRecordFailures) {

src/compiler/moduleSpecifiers.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,14 @@ namespace ts.moduleSpecifiers {
683683
return ext;
684684
case Extension.TsBuildInfo:
685685
return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported:: FileName:: ${fileName}`);
686+
case Extension.Dmts:
687+
case Extension.Mts:
688+
case Extension.Mjs:
689+
return Extension.Mjs;
690+
case Extension.Dcts:
691+
case Extension.Cts:
692+
case Extension.Cjs:
693+
return Extension.Cjs;
686694
default:
687695
return Debug.assertNever(ext);
688696
}

src/compiler/program.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,55 @@ namespace ts {
738738
configFileParseResult.errors;
739739
}
740740

741+
/**
742+
* A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the
743+
* `options` parameter.
744+
*
745+
* @param fileName The normalized absolute path to check the format of (it need not exist on disk)
746+
* @param packageJsonInfoCache (Optional) A cache for package file lookups - it's best to have a cache when this function is called often
747+
* @param host The ModuleResolutionHost which can perform the filesystem lookups for package json data
748+
* @param options The compiler options to perform the analysis under - relevant options are `moduleResolution` and `traceResolution`
749+
* @returns `undefined` if the path has no relevant implied format, `ModuleKind.ESNext` for esm format, and `ModuleKind.CommonJS` for cjs format
750+
*/
751+
export function getImpliedNodeFormatForFile(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleKind.ESNext | ModuleKind.CommonJS | undefined {
752+
switch (getEmitModuleResolutionKind(options)) {
753+
case ModuleResolutionKind.Node12:
754+
case ModuleResolutionKind.NodeNext:
755+
return fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Mjs]) ? ModuleKind.ESNext :
756+
fileExtensionIsOneOf(fileName, [Extension.Dcts, Extension.Cts, Extension.Cjs]) ? ModuleKind.CommonJS :
757+
fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx]) ? lookupFromPackageJson() :
758+
undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline
759+
default:
760+
return undefined;
761+
}
762+
function lookupFromPackageJson(): ModuleKind.ESNext | ModuleKind.CommonJS {
763+
const state: {
764+
host: ModuleResolutionHost;
765+
compilerOptions: CompilerOptions;
766+
traceEnabled: boolean;
767+
failedLookupLocations: Push<string>;
768+
resultFromCache?: ResolvedModuleWithFailedLookupLocations;
769+
packageJsonInfoCache: PackageJsonInfoCache | undefined;
770+
} = {
771+
host,
772+
compilerOptions: options,
773+
traceEnabled: isTraceEnabled(options, host),
774+
failedLookupLocations: [],
775+
packageJsonInfoCache
776+
};
777+
const parts = getPathComponents(fileName);
778+
parts.pop();
779+
while (parts.length > 0) {
780+
const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state);
781+
if (pkg) {
782+
return pkg.packageJsonContent?.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS;
783+
}
784+
parts.pop();
785+
}
786+
return ModuleKind.CommonJS;
787+
}
788+
}
789+
741790
/**
742791
* Determine if source file needs to be re-created even if its text hasn't changed
743792
*/
@@ -1453,8 +1502,8 @@ namespace ts {
14531502

14541503
for (const oldSourceFile of oldSourceFiles) {
14551504
let newSourceFile = host.getSourceFileByPath
1456-
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile)
1457-
: host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217
1505+
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile)
1506+
: host.getSourceFile(oldSourceFile.fileName, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217
14581507

14591508
if (!newSourceFile) {
14601509
return StructureIsReused.Not;
@@ -2609,7 +2658,7 @@ namespace ts {
26092658
// We haven't looked for this file, do so now and cache result
26102659
const file = host.getSourceFile(
26112660
fileName,
2612-
options.target!,
2661+
getEmitScriptTarget(options),
26132662
hostErrorMessage => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_read_file_0_Colon_1, [fileName, hostErrorMessage]),
26142663
shouldCreateNewSourceFile
26152664
);
@@ -2642,6 +2691,10 @@ namespace ts {
26422691
file.path = path;
26432692
file.resolvedPath = toPath(fileName);
26442693
file.originalFileName = originalFileName;
2694+
// It's a _little odd_ that we can't set `impliedNodeFormat` until the program step - but it's the first and only time we have a resolution cache
2695+
// and a freshly made source file node on hand at the same time, and we need both to set the field. Persisting the resolution cache all the way
2696+
// to the check and emit steps would be bad - so we much prefer detecting and storing the format information on the source file node upfront.
2697+
file.impliedNodeFormat = getImpliedNodeFormatForFile(file.resolvedPath, moduleResolutionCache?.getPackageJsonInfoCache(), host, options);
26452698
addFileIncludeReason(file, reason);
26462699

26472700
if (host.useCaseSensitiveFileNames()) {
@@ -3176,7 +3229,7 @@ namespace ts {
31763229
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict");
31773230
}
31783231

3179-
const languageVersion = options.target || ScriptTarget.ES3;
3232+
const languageVersion = getEmitScriptTarget(options);
31803233

31813234
const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile);
31823235
if (options.isolatedModules) {
@@ -3461,7 +3514,7 @@ namespace ts {
34613514
message = Diagnostics.File_is_library_specified_here;
34623515
break;
34633516
}
3464-
const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === options.target ? key : undefined);
3517+
const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined);
34653518
configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined;
34663519
message = Diagnostics.File_is_default_library_for_target_specified_here;
34673520
break;

src/compiler/transformer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ namespace ts {
88
return transformECMAScriptModule;
99
case ModuleKind.System:
1010
return transformSystemModule;
11+
case ModuleKind.Node12:
12+
case ModuleKind.NodeNext:
13+
return transformNodeModule;
1114
default:
1215
return transformModule;
1316
}

src/compiler/transformers/classFields.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ namespace ts {
139139
function transformSourceFile(node: SourceFile) {
140140
const options = context.getCompilerOptions();
141141
if (node.isDeclarationFile
142-
|| useDefineForClassFields && options.target === ScriptTarget.ESNext) {
142+
|| useDefineForClassFields && getEmitScriptTarget(options) === ScriptTarget.ESNext) {
143143
return node;
144144
}
145145
const visited = visitEachChild(node, visitor, context);

src/compiler/transformers/jsx.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ namespace ts {
292292
// When there are no attributes, React wants "null"
293293
}
294294
else {
295-
const target = compilerOptions.target;
295+
const target = getEmitScriptTarget(compilerOptions);
296296
if (target && target >= ScriptTarget.ES2018) {
297297
objectProperties = factory.createObjectLiteralExpression(
298298
flatten<SpreadAssignment | PropertyAssignment>(

0 commit comments

Comments
 (0)