Skip to content

Initial support for module: node12 #44501

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

Closed
wants to merge 8 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
@@ -2374,7 +2374,7 @@ namespace ts {

function checkStrictModeLabeledStatement(node: LabeledStatement) {
// Grammar checking for labeledStatement
if (inStrictMode && options.target! >= ScriptTarget.ES2015) {
if (inStrictMode && getEmitScriptTarget(options) >= ScriptTarget.ES2015) {
if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) {
errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here);
}
115 changes: 75 additions & 40 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
@@ -394,7 +394,9 @@ namespace ts {
es6: ModuleKind.ES2015,
es2015: ModuleKind.ES2015,
es2020: ModuleKind.ES2020,
esnext: ModuleKind.ESNext
esnext: ModuleKind.ESNext,
node12: ModuleKind.Node12,
nodenext: ModuleKind.NodeNext,
})),
affectsModuleResolution: true,
affectsEmit: true,
17 changes: 13 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
@@ -920,7 +920,7 @@
"category": "Error",
"code": 1322
},
"Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', or 'umd'.": {
"Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', 'umd', 'node12', or 'nodenext'.": {
"category": "Error",
"code": 1323
},
@@ -992,7 +992,7 @@
"category": "Error",
"code": 1342
},
"The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system'.": {
"The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', 'system', 'node12', or 'nodenext'.": {
"category": "Error",
"code": 1343
},
@@ -1116,7 +1116,7 @@
"category": "Message",
"code": 1377
},
"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.": {
"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.": {
"category": "Error",
"code": 1378
},
@@ -1324,7 +1324,7 @@
"category": "Error",
"code": 1431
},
"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.": {
"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.": {
"category": "Error",
"code": 1432
},
@@ -1393,6 +1393,15 @@
"code": 1450
},

"The 'import.meta' meta-property is not allowed in files which will build into CommonJS output.": {
"category": "Error",
"code": 1470
},
"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.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
"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.": {
"Module '{0}' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use a dynamic import instead.": {

"category": "Error",
"code": 1471
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
"code": 2200
2 changes: 1 addition & 1 deletion src/compiler/factory/emitHelpers.ts
Original file line number Diff line number Diff line change
@@ -136,7 +136,7 @@ namespace ts {
// ES2018 Helpers

function createAssignHelper(attributesSegments: Expression[]) {
if (context.getCompilerOptions().target! >= ScriptTarget.ES2015) {
if (getEmitScriptTarget(context.getCompilerOptions()) >= ScriptTarget.ES2015) {
return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "assign"),
/*typeArguments*/ undefined,
attributesSegments);
1 change: 1 addition & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
@@ -5175,6 +5175,7 @@ namespace ts {
node.transformFlags =
propagateChildrenFlags(node.statements) |
propagateChildFlags(node.endOfFileToken);
node.impliedNodeFormat = source.impliedNodeFormat;
return node;
}

6 changes: 3 additions & 3 deletions src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
@@ -481,7 +481,7 @@ namespace ts {
if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) {
let namedBindings: NamedImportBindings | undefined;
const moduleKind = getEmitModuleKind(compilerOptions);
if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) {
if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || sourceFile.impliedNodeFormat === ModuleKind.ESNext) {
// use named imports
const helpers = getEmitHelpers(sourceFile);
if (helpers) {
@@ -539,9 +539,9 @@ namespace ts {
}

const moduleKind = getEmitModuleKind(compilerOptions);
let create = (hasExportStarsToExportValues || (compilerOptions.esModuleInterop && hasImportStarOrImportDefault))
let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault))
&& moduleKind !== ModuleKind.System
&& moduleKind < ModuleKind.ES2015;
&& (moduleKind < ModuleKind.ES2015 || node.impliedNodeFormat === ModuleKind.CommonJS);
if (!create) {
const helpers = getEmitHelpers(node);
if (helpers) {
21 changes: 19 additions & 2 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
@@ -113,6 +113,7 @@ namespace ts {
typesVersions?: MapLike<MapLike<string[]>>;
main?: string;
tsconfig?: string;
type?: string;
}

interface PackageJson extends PackageJsonPathFields {
@@ -826,7 +827,20 @@ namespace ts {
else {
let moduleResolution = compilerOptions.moduleResolution;
if (moduleResolution === undefined) {
moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
switch (getEmitModuleKind(compilerOptions)) {
case ModuleKind.CommonJS:
moduleResolution = ModuleResolutionKind.NodeJs;
break;
case ModuleKind.Node12:
moduleResolution = ModuleResolutionKind.Node12;
break;
case ModuleKind.NodeNext:
moduleResolution = ModuleResolutionKind.NodeNext;
break;
default:
moduleResolution = ModuleResolutionKind.Classic;
break;
}
if (traceEnabled) {
trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]);
}
@@ -839,6 +853,8 @@ namespace ts {

perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/);
switch (moduleResolution) {
case ModuleResolutionKind.Node12:
case ModuleResolutionKind.NodeNext: // TODO: Implement node12/nodenext resolution rules
case ModuleResolutionKind.NodeJs:
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference);
break;
@@ -1319,7 +1335,8 @@ namespace ts {
versionPaths: VersionPaths | undefined;
}

function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
/*@internal*/
export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
const { host, traceEnabled } = state;
const packageJsonPath = combinePaths(packageDirectory, "package.json");
if (onlyRecordFailures) {
8 changes: 8 additions & 0 deletions src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
@@ -763,6 +763,14 @@ namespace ts.moduleSpecifiers {
case Extension.Jsx:
case Extension.Json:
return ext;
case Extension.Dmts:
case Extension.Mts:
case Extension.Mjs:
return Extension.Mjs;
case Extension.Dcts:
case Extension.Cts:
case Extension.Cjs:
return Extension.Cjs;
default:
return undefined;
}
63 changes: 58 additions & 5 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
@@ -738,6 +738,55 @@ namespace ts {
configFileParseResult.errors;
}

/**
* A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the
* `options` parameter.
*
* @param fileName The normalized absolute path to check the format of (it need not exist on disk)
* @param [packageJsonInfoCache] A cache for package file lookups - it's best to have a cache when this function is called often
* @param host The ModuleResolutionHost which can perform the filesystem lookups for package json data
* @param options The compiler options to perform the analysis under - relevant options are `moduleResolution` and `traceResolution`
* @returns `undefined` if the path has no relevant implied format, `ModuleKind.ESNext` for esm format, and `ModuleKind.CommonJS` for cjs format
*/
export function getImpliedNodeFormatForFile(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleKind.ESNext | ModuleKind.CommonJS | undefined {
switch (getEmitModuleResolutionKind(options)) {
case ModuleResolutionKind.Node12:
case ModuleResolutionKind.NodeNext:
return fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Mjs]) ? ModuleKind.ESNext :
fileExtensionIsOneOf(fileName, [Extension.Dcts, Extension.Cts, Extension.Cjs]) ? ModuleKind.CommonJS :
fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx]) ? lookupFromPackageJson() :
undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline
default:
return undefined;
}
function lookupFromPackageJson(): ModuleKind.ESNext | ModuleKind.CommonJS {
const state: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: if getPackageJsonInfo is going to be exported, it might be nice if its parameter types (ModuleResolutionState) were too. Alternatively, Parameters<typeof getPackageJsonInfo>[2]?

host: ModuleResolutionHost;
compilerOptions: CompilerOptions;
traceEnabled: boolean;
failedLookupLocations: Push<string>;
resultFromCache?: ResolvedModuleWithFailedLookupLocations;
packageJsonInfoCache: PackageJsonInfoCache | undefined;
} = {
host,
compilerOptions: options,
traceEnabled: isTraceEnabled(options, host),
failedLookupLocations: [],
packageJsonInfoCache
};
const parts = getPathComponents(fileName);
parts.pop();
while (parts.length > 0) {
const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state);
if (pkg) {
return pkg.packageJsonContent?.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS;
}
parts.pop();
}
Comment on lines +777 to +785
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use forEachAncestorDirectory?

return ModuleKind.CommonJS;
}
}

/**
* Determine if source file needs to be re-created even if its text hasn't changed
*/
@@ -1455,8 +1504,8 @@ namespace ts {

for (const oldSourceFile of oldSourceFiles) {
let newSourceFile = host.getSourceFileByPath
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile)
: host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile)
: host.getSourceFile(oldSourceFile.fileName, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217

if (!newSourceFile) {
return StructureIsReused.Not;
@@ -2616,7 +2665,7 @@ namespace ts {
// We haven't looked for this file, do so now and cache result
const file = host.getSourceFile(
fileName,
options.target!,
getEmitScriptTarget(options),
hostErrorMessage => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_read_file_0_Colon_1, [fileName, hostErrorMessage]),
shouldCreateNewSourceFile
);
@@ -2649,6 +2698,10 @@ namespace ts {
file.path = path;
file.resolvedPath = toPath(fileName);
file.originalFileName = originalFileName;
// 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
// 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
// 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.
file.impliedNodeFormat = getImpliedNodeFormatForFile(file.resolvedPath, moduleResolutionCache?.getPackageJsonInfoCache(), host, options);
addFileIncludeReason(file, reason);

if (host.useCaseSensitiveFileNames()) {
@@ -3200,7 +3253,7 @@ namespace ts {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict");
}

const languageVersion = options.target || ScriptTarget.ES3;
const languageVersion = getEmitScriptTarget(options);

const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile);
if (options.isolatedModules) {
@@ -3489,7 +3542,7 @@ namespace ts {
message = Diagnostics.File_is_library_specified_here;
break;
}
const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === options.target ? key : undefined);
const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined);
configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined;
message = Diagnostics.File_is_default_library_for_target_specified_here;
break;
3 changes: 3 additions & 0 deletions src/compiler/transformer.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,9 @@ namespace ts {
return transformECMAScriptModule;
case ModuleKind.System:
return transformSystemModule;
case ModuleKind.Node12:
case ModuleKind.NodeNext:
return transformNodeModule;
default:
return transformModule;
}
2 changes: 1 addition & 1 deletion src/compiler/transformers/classFields.ts
Original file line number Diff line number Diff line change
@@ -172,7 +172,7 @@ namespace ts {
function transformSourceFile(node: SourceFile) {
const options = context.getCompilerOptions();
if (node.isDeclarationFile
|| useDefineForClassFields && options.target === ScriptTarget.ESNext) {
|| useDefineForClassFields && getEmitScriptTarget(options) === ScriptTarget.ESNext) {
return node;
}
const visited = visitEachChild(node, visitor, context);
2 changes: 1 addition & 1 deletion src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
@@ -292,7 +292,7 @@ namespace ts {
// When there are no attributes, React wants "null"
}
else {
const target = compilerOptions.target;
const target = getEmitScriptTarget(compilerOptions);
if (target && target >= ScriptTarget.ES2018) {
objectProperties = factory.createObjectLiteralExpression(
flatten<SpreadAssignment | PropertyAssignment>(
Loading