Skip to content

Commit

Permalink
Initial support for module: node12
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham committed Jun 8, 2021
1 parent faefc72 commit 2fc2abe
Show file tree
Hide file tree
Showing 265 changed files with 4,178 additions and 473 deletions.
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2370,7 +2370,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);
}
Expand Down
91 changes: 51 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
Expand Up @@ -381,7 +381,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,
Expand Down
16 changes: 12 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,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
},
Expand Down Expand Up @@ -1020,7 +1020,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
},
Expand Down Expand Up @@ -1152,7 +1152,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
},
Expand Down Expand Up @@ -1368,14 +1368,22 @@
"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
},
"Decorators may not be applied to 'this' parameters.": {
"category": "Error",
"code": 1433
},
"The 'import.meta' meta-property is not allowed in files which will build into CommonJS output.": {
"category": "Error",
"code": 1434
},
"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.": {
"category": "Error",
"code": 1435
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/factory/emitHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,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);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5055,6 +5055,7 @@ namespace ts {
node.transformFlags =
propagateChildrenFlags(node.statements) |
propagateChildFlags(node.endOfFileToken);
node.impliedNodeFormat = source.impliedNodeFormat;
return node;
}

Expand Down
6 changes: 3 additions & 3 deletions src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,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) {
Expand Down Expand Up @@ -462,9 +462,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) {
Expand Down
21 changes: 19 additions & 2 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ namespace ts {
typesVersions?: MapLike<MapLike<string[]>>;
main?: string;
tsconfig?: string;
type?: string;
}

interface PackageJson extends PackageJsonPathFields {
Expand Down Expand Up @@ -816,7 +817,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]);
}
Expand All @@ -829,6 +843,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;
Expand Down Expand Up @@ -1310,7 +1326,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) {
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,14 @@ namespace ts.moduleSpecifiers {
return ext;
case Extension.TsBuildInfo:
return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported:: FileName:: ${fileName}`);
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 Debug.assertNever(ext);
}
Expand Down
63 changes: 58 additions & 5 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (Optional) 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: {
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();
}
return ModuleKind.CommonJS;
}
}

/**
* Determine if source file needs to be re-created even if its text hasn't changed
*/
Expand Down Expand Up @@ -1453,8 +1502,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;
Expand Down Expand Up @@ -2609,7 +2658,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
);
Expand Down Expand Up @@ -2642,6 +2691,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()) {
Expand Down Expand Up @@ -3176,7 +3229,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) {
Expand Down Expand Up @@ -3461,7 +3514,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;
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ namespace ts {
return transformECMAScriptModule;
case ModuleKind.System:
return transformSystemModule;
case ModuleKind.Node12:
case ModuleKind.NodeNext:
return transformNodeModule;
default:
return transformModule;
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/classFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,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);
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand Down
Loading

0 comments on commit 2fc2abe

Please sign in to comment.