Skip to content

Commit 4a963a2

Browse files
committed
initial revision of external module augmentations
1 parent 827fec6 commit 4a963a2

File tree

122 files changed

+6997
-108
lines changed

Some content is hidden

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

122 files changed

+6997
-108
lines changed

src/compiler/binder.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ namespace ts {
109109
let blockScopeContainer: Node;
110110
let lastContainer: Node;
111111
let seenThisKeyword: boolean;
112+
let isSourceFileExternalModule: boolean;
112113

113114
// state used by reachability checks
114115
let hasExplicitReturn: boolean;
@@ -129,8 +130,9 @@ namespace ts {
129130
function bindSourceFile(f: SourceFile, opts: CompilerOptions) {
130131
file = f;
131132
options = opts;
132-
inStrictMode = !!file.externalModuleIndicator;
133+
isSourceFileExternalModule = inStrictMode = !!file.externalModuleIndicator;
133134
classifiableNames = {};
135+
134136
Symbol = objectAllocator.getSymbolConstructor();
135137

136138
if (!file.locals) {
@@ -348,7 +350,12 @@ namespace ts {
348350
// 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol,
349351
// but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way
350352
// when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope.
351-
if (hasExportModifier || container.flags & NodeFlags.ExportContext) {
353+
354+
// NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge
355+
// during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation
356+
// and this case is specially handled. Module augmentations should only be merged with original module definition
357+
// and should never be merged directly with other augmentation and the latter case would be possible is automatic merge is allowed.
358+
if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) {
352359
const exportKind =
353360
(symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0) |
354361
(symbolFlags & SymbolFlags.Type ? SymbolFlags.ExportType : 0) |
@@ -844,6 +851,9 @@ namespace ts {
844851
function bindModuleDeclaration(node: ModuleDeclaration) {
845852
setExportContextFlag(node);
846853
if (node.name.kind === SyntaxKind.StringLiteral) {
854+
if (node.flags & NodeFlags.Export) {
855+
errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible);
856+
}
847857
declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes);
848858
}
849859
else {

src/compiler/checker.ts

Lines changed: 175 additions & 31 deletions
Large diffs are not rendered by default.

src/compiler/declarationEmitter.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ namespace ts {
5454
let writer = createAndSetNewTextWriterWithSymbolWriter();
5555

5656
let enclosingDeclaration: Node;
57+
let resultHasExternalModuleIndicator: boolean;
5758
let currentText: string;
5859
let currentLineMap: number[];
5960
let currentIdentifiers: Map<string>;
@@ -101,6 +102,7 @@ namespace ts {
101102
});
102103
}
103104

105+
resultHasExternalModuleIndicator = false;
104106
if (!isBundledEmit || !isExternalModule(sourceFile)) {
105107
noDeclare = false;
106108
emitSourceFile(sourceFile);
@@ -139,6 +141,14 @@ namespace ts {
139141
allSourcesModuleElementDeclarationEmitInfo = allSourcesModuleElementDeclarationEmitInfo.concat(moduleElementDeclarationEmitInfo);
140142
moduleElementDeclarationEmitInfo = [];
141143
}
144+
145+
if (!isBundledEmit && isExternalModule(sourceFile) && sourceFile.moduleAugmentations.length && !resultHasExternalModuleIndicator) {
146+
// if file was external module with augmentations - this fact should be preserved in .d.ts as well.
147+
// in case if we didn't write any external module specifiers in .d.ts we need to emit something
148+
// that will force compiler to think that this file is an external module - 'export {}' is a reasonable choice here.
149+
write("export {};");
150+
writeLine();
151+
}
142152
});
143153

144154
return {
@@ -720,16 +730,25 @@ namespace ts {
720730
writer.writeLine();
721731
}
722732

723-
function emitExternalModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration) {
733+
function emitExternalModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration) {
734+
// emitExternalModuleSpecifier is usualyl called when we emit something in the.d.ts file that will make it an external module (i.e. import/export declarations).
735+
// the only case when it is not true is when we call it to emit correct name for module augmentation - d.ts files with just module augmentations are not considered
736+
// external modules since they are indistingushable from script files with ambient modules. To fix this in such d.ts files we'll emit top level 'export {}'
737+
// so compiler will treat them as external modules.
738+
resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || parent.kind !== SyntaxKind.ModuleDeclaration;
724739
let moduleSpecifier: Node;
725740
if (parent.kind === SyntaxKind.ImportEqualsDeclaration) {
726741
const node = parent as ImportEqualsDeclaration;
727742
moduleSpecifier = getExternalModuleImportEqualsDeclarationExpression(node);
728743
}
744+
else if (parent.kind === SyntaxKind.ModuleDeclaration) {
745+
moduleSpecifier = (<ModuleDeclaration>parent).name;
746+
}
729747
else {
730748
const node = parent as (ImportDeclaration | ExportDeclaration);
731749
moduleSpecifier = node.moduleSpecifier;
732750
}
751+
733752
if (moduleSpecifier.kind === SyntaxKind.StringLiteral && isBundledEmit && (compilerOptions.out || compilerOptions.outFile)) {
734753
const moduleName = getExternalModuleNameFromDeclaration(host, resolver, parent);
735754
if (moduleName) {
@@ -789,7 +808,12 @@ namespace ts {
789808
else {
790809
write("module ");
791810
}
792-
writeTextOfNode(currentText, node.name);
811+
if (isExternalModuleAugmentation(node)) {
812+
emitExternalModuleSpecifier(node);
813+
}
814+
else {
815+
writeTextOfNode(currentText, node.name);
816+
}
793817
while (node.body.kind !== SyntaxKind.ModuleBlock) {
794818
node = <ModuleDeclaration>node.body;
795819
write(".");

src/compiler/diagnosticMessages.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,26 @@
17711771
"category": "Error",
17721772
"code": 2660
17731773
},
1774+
"Invalid module name in augmentation, module '{0}' cannot be found.": {
1775+
"category": "Error",
1776+
"code": 2661
1777+
},
1778+
"Module augmentation cannot introduce new names in the top level scope.": {
1779+
"category": "Error",
1780+
"code": 2662
1781+
},
1782+
"Exports are not permitted in module augmentations.": {
1783+
"category": "Error",
1784+
"code": 2663
1785+
},
1786+
"Imports are not permitted in module augmentations. Consider moving them to the enclosing external module.": {
1787+
"category": "Error",
1788+
"code": 2664
1789+
},
1790+
"'export' modifier cannot be applied to ambient modules and module augmentations since they are always visible.": {
1791+
"category": "Error",
1792+
"code": 2665
1793+
},
17741794
"Import declaration '{0}' is using private name '{1}'.": {
17751795
"category": "Error",
17761796
"code": 4000

src/compiler/program.ts

Lines changed: 99 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,24 @@ namespace ts {
347347
const currentDirectory = host.getCurrentDirectory();
348348
const resolveModuleNamesWorker = host.resolveModuleNames
349349
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
350-
: ((moduleNames: string[], containingFile: string) => map(moduleNames, moduleName => resolveModuleName(moduleName, containingFile, options, host).resolvedModule));
350+
: ((moduleNames: string[], containingFile: string) => {
351+
const resolvedModuleNames: ResolvedModule[] = [];
352+
// resolveModuleName does not store any results between calls.
353+
// lookup is a local cache to avoid resolving the same module name several times
354+
const lookup: Map<ResolvedModule> = {};
355+
for (const moduleName of moduleNames) {
356+
let resolvedName: ResolvedModule;
357+
if (hasProperty(lookup, moduleName)) {
358+
resolvedName = lookup[moduleName];
359+
}
360+
else {
361+
resolvedName = resolveModuleName(moduleName, containingFile, options, host).resolvedModule;
362+
lookup[moduleName] = resolvedName;
363+
}
364+
resolvedModuleNames.push(resolvedName);
365+
}
366+
return resolvedModuleNames;
367+
});
351368

352369
const filesByName = createFileMap<SourceFile>();
353370
// stores 'filename -> file association' ignoring case
@@ -484,15 +501,25 @@ namespace ts {
484501
return false;
485502
}
486503

487-
// check imports
504+
// check imports and module augmentations
488505
collectExternalModuleReferences(newSourceFile);
489506
if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) {
490507
// imports has changed
491508
return false;
492509
}
510+
if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) {
511+
// moduleAugmentations has changed
512+
return false;
513+
}
493514

494515
if (resolveModuleNamesWorker) {
495-
const moduleNames = map(newSourceFile.imports, name => name.text);
516+
const moduleNames: string[] = [];
517+
for (const moduleName of newSourceFile.imports) {
518+
moduleNames.push(moduleName.text);
519+
}
520+
for (const moduleName of newSourceFile.moduleAugmentations) {
521+
moduleNames.push(moduleName.text);
522+
}
496523
const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory));
497524
// ensure that module resolution results are still correct
498525
for (let i = 0; i < moduleNames.length; ++i) {
@@ -887,59 +914,75 @@ namespace ts {
887914
}
888915

889916
const isJavaScriptFile = isSourceFileJavaScript(file);
917+
const isExternalModuleFile = isExternalModule(file);
890918

891919
let imports: LiteralExpression[];
920+
let moduleAugmentations: LiteralExpression[];
921+
892922
for (const node of file.statements) {
893-
collect(node, /*allowRelativeModuleNames*/ true, /*collectOnlyRequireCalls*/ false);
923+
collectModuleReferences(node, /*inAmbientModule*/ false);
924+
if (isJavaScriptFile) {
925+
collectRequireCalls(node);
926+
}
894927
}
895928

896929
file.imports = imports || emptyArray;
930+
file.moduleAugmentations = moduleAugmentations || emptyArray;
897931

898932
return;
899933

900-
function collect(node: Node, allowRelativeModuleNames: boolean, collectOnlyRequireCalls: boolean): void {
901-
if (!collectOnlyRequireCalls) {
902-
switch (node.kind) {
903-
case SyntaxKind.ImportDeclaration:
904-
case SyntaxKind.ImportEqualsDeclaration:
905-
case SyntaxKind.ExportDeclaration:
906-
let moduleNameExpr = getExternalModuleName(node);
907-
if (!moduleNameExpr || moduleNameExpr.kind !== SyntaxKind.StringLiteral) {
908-
break;
909-
}
910-
if (!(<LiteralExpression>moduleNameExpr).text) {
911-
break;
912-
}
934+
function collectModuleReferences(node: Node, inAmbientModule: boolean): void {
935+
switch (node.kind) {
936+
case SyntaxKind.ImportDeclaration:
937+
case SyntaxKind.ImportEqualsDeclaration:
938+
case SyntaxKind.ExportDeclaration:
939+
let moduleNameExpr = getExternalModuleName(node);
940+
if (!moduleNameExpr || moduleNameExpr.kind !== SyntaxKind.StringLiteral) {
941+
break;
942+
}
943+
if (!(<LiteralExpression>moduleNameExpr).text) {
944+
break;
945+
}
913946

914-
if (allowRelativeModuleNames || !isExternalModuleNameRelative((<LiteralExpression>moduleNameExpr).text)) {
915-
(imports || (imports = [])).push(<LiteralExpression>moduleNameExpr);
947+
// TypeScript 1.0 spec (April 2014): 12.1.6
948+
// An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules
949+
// only through top - level external module names. Relative external module names are not permitted.
950+
if (!inAmbientModule || !isExternalModuleNameRelative((<LiteralExpression>moduleNameExpr).text)) {
951+
(imports || (imports = [])).push(<LiteralExpression>moduleNameExpr);
952+
}
953+
break;
954+
case SyntaxKind.ModuleDeclaration:
955+
if ((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral && (inAmbientModule || node.flags & NodeFlags.Ambient || isDeclarationFile(file))) {
956+
const moduleName = <LiteralExpression>(<ModuleDeclaration>node).name;
957+
// Ambient module declarations can be interpreted as augmentations for some existing external modules.
958+
// This will happen in two cases:
959+
// - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope
960+
// - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name
961+
// immediately nested in top level ambient module declaration .
962+
if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(moduleName.text))) {
963+
(moduleAugmentations || (moduleAugmentations = [])).push(moduleName);
916964
}
917-
break;
918-
case SyntaxKind.ModuleDeclaration:
919-
if ((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) {
920-
// TypeScript 1.0 spec (April 2014): 12.1.6
965+
else if (!inAmbientModule) {
921966
// An AmbientExternalModuleDeclaration declares an external module.
922967
// This type of declaration is permitted only in the global module.
923968
// The StringLiteral must specify a top - level external module name.
924969
// Relative external module names are not permitted
925-
forEachChild((<ModuleDeclaration>node).body, node => {
926-
// TypeScript 1.0 spec (April 2014): 12.1.6
927-
// An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules
928-
// only through top - level external module names. Relative external module names are not permitted.
929-
collect(node, /*allowRelativeModuleNames*/ false, collectOnlyRequireCalls);
930-
});
970+
971+
// NOTE: body of ambient module is always a module block
972+
for (const statement of (<ModuleBlock>(<ModuleDeclaration>node).body).statements) {
973+
collectModuleReferences(statement, /*inAmbientModule*/ true);
974+
}
931975
}
932-
break;
933-
}
976+
}
934977
}
978+
}
935979

936-
if (isJavaScriptFile) {
937-
if (isRequireCall(node)) {
938-
(imports || (imports = [])).push(<StringLiteral>(<CallExpression>node).arguments[0]);
939-
}
940-
else {
941-
forEachChild(node, node => collect(node, allowRelativeModuleNames, /*collectOnlyRequireCalls*/ true));
942-
}
980+
function collectRequireCalls(node: Node): void {
981+
if (isRequireCall(node)) {
982+
(imports || (imports = [])).push(<StringLiteral>(<CallExpression>node).arguments[0]);
983+
}
984+
else {
985+
forEachChild(node, collectRequireCalls);
943986
}
944987
}
945988
}
@@ -1069,14 +1112,28 @@ namespace ts {
10691112

10701113
function processImportedModules(file: SourceFile, basePath: string) {
10711114
collectExternalModuleReferences(file);
1072-
if (file.imports.length) {
1115+
if (file.imports.length || file.moduleAugmentations.length) {
10731116
file.resolvedModules = {};
1074-
const moduleNames = map(file.imports, name => name.text);
1117+
const moduleNames: string[] = [];
1118+
for (const name of file.imports) {
1119+
moduleNames.push(name.text);
1120+
}
1121+
for (const name of file.moduleAugmentations) {
1122+
moduleNames.push(name.text);
1123+
}
10751124
const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory));
1076-
for (let i = 0; i < file.imports.length; ++i) {
1125+
for (let i = 0; i < moduleNames.length; ++i) {
10771126
const resolution = resolutions[i];
10781127
setResolvedModule(file, moduleNames[i], resolution);
1079-
if (resolution && !options.noResolve) {
1128+
// add file to program only if:
1129+
// - resolution was successfull
1130+
// - noResolve is falsy
1131+
// - module name come from the list fo imports
1132+
const shouldAddFile = resolution &&
1133+
!options.noResolve &&
1134+
i < file.imports.length;
1135+
1136+
if (shouldAddFile) {
10801137
const importedFile = findSourceFile(resolution.resolvedFileName, toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), /*isDefaultLib*/ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
10811138

10821139
if (importedFile && resolution.isExternalLibraryImport) {

0 commit comments

Comments
 (0)