Skip to content

Commit 91d8764

Browse files
committed
Prototype UMD support
1 parent f34dcdd commit 91d8764

26 files changed

+503
-11
lines changed

src/compiler/binder.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,8 @@ namespace ts {
13491349
case SyntaxKind.ImportSpecifier:
13501350
case SyntaxKind.ExportSpecifier:
13511351
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
1352+
case SyntaxKind.GlobalModuleExportDeclaration:
1353+
return bindGlobalModuleExportDeclaration(<GlobalModuleExportDeclaration>node);
13521354
case SyntaxKind.ImportClause:
13531355
return bindImportClause(<ImportClause>node);
13541356
case SyntaxKind.ExportDeclaration:
@@ -1398,6 +1400,11 @@ namespace ts {
13981400
}
13991401
}
14001402

1403+
function bindGlobalModuleExportDeclaration(node: GlobalModuleExportDeclaration) {
1404+
file.symbol.globalExports = file.symbol.globalExports || {};
1405+
declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
1406+
}
1407+
14011408
function bindExportDeclaration(node: ExportDeclaration) {
14021409
if (!container.symbol || !container.symbol.exports) {
14031410
// Export * in some sort of block construct

src/compiler/checker.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,10 @@ namespace ts {
981981
return getExternalModuleMember(<ImportDeclaration>node.parent.parent.parent, node);
982982
}
983983

984+
function getTargetOfGlobalModuleExportDeclaration(node: GlobalModuleExportDeclaration): Symbol {
985+
return node.parent.symbol;
986+
}
987+
984988
function getTargetOfExportSpecifier(node: ExportSpecifier): Symbol {
985989
return (<ExportDeclaration>node.parent.parent).moduleSpecifier ?
986990
getExternalModuleMember(<ExportDeclaration>node.parent.parent, node) :
@@ -1005,6 +1009,8 @@ namespace ts {
10051009
return getTargetOfExportSpecifier(<ExportSpecifier>node);
10061010
case SyntaxKind.ExportAssignment:
10071011
return getTargetOfExportAssignment(<ExportAssignment>node);
1012+
case SyntaxKind.GlobalModuleExportDeclaration:
1013+
return getTargetOfGlobalModuleExportDeclaration(<GlobalModuleExportDeclaration>node);
10081014
}
10091015
}
10101016

@@ -16300,6 +16306,9 @@ namespace ts {
1630016306
if (file.moduleAugmentations.length) {
1630116307
(augmentations || (augmentations = [])).push(file.moduleAugmentations);
1630216308
}
16309+
if (file.wasReferenced && file.symbol && file.symbol.globalExports) {
16310+
mergeSymbolTable(globals, file.symbol.globalExports);
16311+
}
1630316312
});
1630416313

1630516314
if (augmentations) {

src/compiler/parser.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ namespace ts {
301301
case SyntaxKind.ImportClause:
302302
return visitNode(cbNode, (<ImportClause>node).name) ||
303303
visitNode(cbNode, (<ImportClause>node).namedBindings);
304+
case SyntaxKind.GlobalModuleExportDeclaration:
305+
return visitNode(cbNode, (<GlobalModuleExportDeclaration>node).name);
306+
304307
case SyntaxKind.NamespaceImport:
305308
return visitNode(cbNode, (<NamespaceImport>node).name);
306309
case SyntaxKind.NamedImports:
@@ -4418,7 +4421,8 @@ namespace ts {
44184421
continue;
44194422

44204423
case SyntaxKind.GlobalKeyword:
4421-
return nextToken() === SyntaxKind.OpenBraceToken;
4424+
nextToken();
4425+
return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.Identifier || token === SyntaxKind.ExportKeyword;
44224426

44234427
case SyntaxKind.ImportKeyword:
44244428
nextToken();
@@ -4604,6 +4608,12 @@ namespace ts {
46044608
case SyntaxKind.EnumKeyword:
46054609
return parseEnumDeclaration(fullStart, decorators, modifiers);
46064610
case SyntaxKind.GlobalKeyword:
4611+
if (lookAhead(isGlobalModuleExportDeclaration)) {
4612+
return parseGlobalModuleExportDeclaration(fullStart, decorators, modifiers);
4613+
}
4614+
else {
4615+
return parseModuleDeclaration(fullStart, decorators, modifiers);
4616+
}
46074617
case SyntaxKind.ModuleKeyword:
46084618
case SyntaxKind.NamespaceKeyword:
46094619
return parseModuleDeclaration(fullStart, decorators, modifiers);
@@ -4627,6 +4637,11 @@ namespace ts {
46274637
}
46284638
}
46294639

4640+
function isGlobalModuleExportDeclaration() {
4641+
nextToken();
4642+
return token === SyntaxKind.ExportKeyword;
4643+
}
4644+
46304645
function nextTokenIsIdentifierOrStringLiteralOnSameLine() {
46314646
nextToken();
46324647
return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token === SyntaxKind.StringLiteral);
@@ -5282,6 +5297,20 @@ namespace ts {
52825297
return nextToken() === SyntaxKind.SlashToken;
52835298
}
52845299

5300+
function parseGlobalModuleExportDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): GlobalModuleExportDeclaration {
5301+
const exportDeclaration = <GlobalModuleExportDeclaration>createNode(SyntaxKind.GlobalModuleExportDeclaration, fullStart);
5302+
exportDeclaration.decorators = decorators;
5303+
exportDeclaration.modifiers = modifiers;
5304+
parseExpected(SyntaxKind.GlobalKeyword);
5305+
parseExpected(SyntaxKind.ExportKeyword);
5306+
5307+
exportDeclaration.name = parseIdentifier();
5308+
5309+
parseExpected(SyntaxKind.SemicolonToken);
5310+
5311+
return finishNode(exportDeclaration);
5312+
}
5313+
52855314
function parseImportDeclarationOrImportEqualsDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ImportEqualsDeclaration | ImportDeclaration {
52865315
parseExpected(SyntaxKind.ImportKeyword);
52875316
const afterImportPos = scanner.getStartPos();

src/compiler/program.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,7 +1282,7 @@ namespace ts {
12821282
}
12831283

12841284
function processRootFile(fileName: string, isDefaultLib: boolean) {
1285-
processSourceFile(normalizePath(fileName), isDefaultLib);
1285+
processSourceFile(normalizePath(fileName), isDefaultLib, true);
12861286
}
12871287

12881288
function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean {
@@ -1376,15 +1376,15 @@ namespace ts {
13761376
}
13771377
}
13781378

1379-
function processSourceFile(fileName: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) {
1379+
function processSourceFile(fileName: string, isDefaultLib: boolean, isReference: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) {
13801380
let diagnosticArgument: string[];
13811381
let diagnostic: DiagnosticMessage;
13821382
if (hasExtension(fileName)) {
13831383
if (!options.allowNonTsExtensions && !forEach(supportedExtensions, extension => fileExtensionIs(host.getCanonicalFileName(fileName), extension))) {
13841384
diagnostic = Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1;
13851385
diagnosticArgument = [fileName, "'" + supportedExtensions.join("', '") + "'"];
13861386
}
1387-
else if (!findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd)) {
1387+
else if (!findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd)) {
13881388
diagnostic = Diagnostics.File_0_not_found;
13891389
diagnosticArgument = [fileName];
13901390
}
@@ -1394,13 +1394,13 @@ namespace ts {
13941394
}
13951395
}
13961396
else {
1397-
const nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd);
1397+
const nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd);
13981398
if (!nonTsFile) {
13991399
if (options.allowNonTsExtensions) {
14001400
diagnostic = Diagnostics.File_0_not_found;
14011401
diagnosticArgument = [fileName];
14021402
}
1403-
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, toPath(fileName + extension, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd))) {
1403+
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, toPath(fileName + extension, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd))) {
14041404
diagnostic = Diagnostics.File_0_not_found;
14051405
fileName += ".ts";
14061406
diagnosticArgument = [fileName];
@@ -1429,7 +1429,7 @@ namespace ts {
14291429
}
14301430

14311431
// Get source file from normalized fileName
1432-
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
1432+
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, isReference: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
14331433
if (filesByName.contains(path)) {
14341434
const file = filesByName.get(path);
14351435
// try to check if we've already seen this file but with a different casing in path
@@ -1438,6 +1438,10 @@ namespace ts {
14381438
reportFileNamesDifferOnlyInCasingError(fileName, file.fileName, refFile, refPos, refEnd);
14391439
}
14401440

1441+
if (file) {
1442+
file.wasReferenced = file.wasReferenced || isReference;
1443+
}
1444+
14411445
return file;
14421446
}
14431447

@@ -1454,6 +1458,7 @@ namespace ts {
14541458

14551459
filesByName.set(path, file);
14561460
if (file) {
1461+
file.wasReferenced = file.wasReferenced || isReference;
14571462
file.path = path;
14581463

14591464
if (host.useCaseSensitiveFileNames()) {
@@ -1491,7 +1496,7 @@ namespace ts {
14911496
function processReferencedFiles(file: SourceFile, basePath: string) {
14921497
forEach(file.referencedFiles, ref => {
14931498
const referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName);
1494-
processSourceFile(referencedFileName, /*isDefaultLib*/ false, file, ref.pos, ref.end);
1499+
processSourceFile(referencedFileName, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
14951500
});
14961501
}
14971502

@@ -1517,7 +1522,7 @@ namespace ts {
15171522
i < file.imports.length;
15181523

15191524
if (shouldAddFile) {
1520-
const importedFile = findSourceFile(resolution.resolvedFileName, toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), /*isDefaultLib*/ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
1525+
const importedFile = findSourceFile(resolution.resolvedFileName, toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), /*isDefaultLib*/ false, /*isReference*/ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
15211526

15221527
if (importedFile && resolution.isExternalLibraryImport) {
15231528
// Since currently irrespective of allowJs, we only look for supportedTypeScript extension external module files,

src/compiler/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ namespace ts {
274274
ModuleDeclaration,
275275
ModuleBlock,
276276
CaseBlock,
277+
GlobalModuleExportDeclaration,
277278
ImportEqualsDeclaration,
278279
ImportDeclaration,
279280
ImportClause,
@@ -1324,6 +1325,12 @@ namespace ts {
13241325
name: Identifier;
13251326
}
13261327

1328+
// @kind(SyntaxKind.GlobalModuleImport)
1329+
export interface GlobalModuleExportDeclaration extends DeclarationStatement {
1330+
name: Identifier;
1331+
moduleReference: LiteralLikeNode;
1332+
}
1333+
13271334
// @kind(SyntaxKind.ExportDeclaration)
13281335
export interface ExportDeclaration extends DeclarationStatement {
13291336
exportClause?: NamedExports;
@@ -1537,6 +1544,8 @@ namespace ts {
15371544
/* @internal */ externalModuleIndicator: Node;
15381545
// The first node that causes this file to be a CommonJS module
15391546
/* @internal */ commonJsModuleIndicator: Node;
1547+
// True if the file was a root file in a compilation or a /// reference targets
1548+
wasReferenced?: boolean;
15401549

15411550
/* @internal */ identifiers: Map<string>;
15421551
/* @internal */ nodeCount: number;
@@ -1994,6 +2003,7 @@ namespace ts {
19942003

19952004
members?: SymbolTable; // Class, interface or literal instance members
19962005
exports?: SymbolTable; // Module exports
2006+
globalExports?: SymbolTable; // Conditional global UMD exports
19972007
/* @internal */ id?: number; // Unique id (used to look up SymbolLinks)
19982008
/* @internal */ mergeId?: number; // Merge id (used to look up merged symbol)
19992009
/* @internal */ parent?: Symbol; // Parent symbol

src/compiler/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,7 @@ namespace ts {
14751475
// export default ...
14761476
export function isAliasSymbolDeclaration(node: Node): boolean {
14771477
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
1478+
node.kind === SyntaxKind.GlobalModuleExportDeclaration ||
14781479
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
14791480
node.kind === SyntaxKind.NamespaceImport ||
14801481
node.kind === SyntaxKind.ImportSpecifier ||

src/harness/compilerRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class CompilerBaselineRunner extends RunnerBase {
8888
toBeCompiled = [];
8989
otherFiles = [];
9090

91-
if (/require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
91+
if (testCaseContent.settings["noImplicitReferences"] || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
9292
toBeCompiled.push({ unitName: this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content });
9393
units.forEach(unit => {
9494
if (unit.name !== lastUnit.name) {

src/harness/harness.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,8 @@ namespace Harness {
896896
{ name: "fileName", type: "string" },
897897
{ name: "libFiles", type: "string" },
898898
{ name: "noErrorTruncation", type: "boolean" },
899-
{ name: "suppressOutputPathCheck", type: "boolean" }
899+
{ name: "suppressOutputPathCheck", type: "boolean" },
900+
{ name: "noImplicitReferences", type: "boolean" }
900901
];
901902

902903
let optionsIndex: ts.Map<ts.CommandLineOption>;

tests/baselines/reference/umd1.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [tests/cases/conformance/externalModules/umd1.ts] ////
2+
3+
//// [foo.d.ts]
4+
5+
export var x: number;
6+
export function fn(): void;
7+
export interface Thing { n: typeof x }
8+
declare global export Foo;
9+
10+
//// [a.ts]
11+
/// <reference path="foo.d.ts" />
12+
Foo.fn();
13+
let x: Foo.Thing;
14+
let y: number = x.n;
15+
16+
17+
//// [a.js]
18+
/// <reference path="foo.d.ts" />
19+
exports.Foo.fn();
20+
var x;
21+
var y = x.n;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/conformance/externalModules/a.ts ===
2+
/// <reference path="foo.d.ts" />
3+
Foo.fn();
4+
>Foo.fn : Symbol(Foo.fn, Decl(foo.d.ts, 1, 21))
5+
>Foo : Symbol(Foo, Decl(foo.d.ts, 3, 38))
6+
>fn : Symbol(Foo.fn, Decl(foo.d.ts, 1, 21))
7+
8+
let x: Foo.Thing;
9+
>x : Symbol(x, Decl(a.ts, 2, 3))
10+
>Foo : Symbol(Foo, Decl(foo.d.ts, 3, 38))
11+
>Thing : Symbol(Foo.Thing, Decl(foo.d.ts, 2, 27))
12+
13+
let y: number = x.n;
14+
>y : Symbol(y, Decl(a.ts, 3, 3))
15+
>x.n : Symbol(Foo.Thing.n, Decl(foo.d.ts, 3, 24))
16+
>x : Symbol(x, Decl(a.ts, 2, 3))
17+
>n : Symbol(Foo.Thing.n, Decl(foo.d.ts, 3, 24))
18+
19+
=== tests/cases/conformance/externalModules/foo.d.ts ===
20+
21+
export var x: number;
22+
>x : Symbol(x, Decl(foo.d.ts, 1, 10))
23+
24+
export function fn(): void;
25+
>fn : Symbol(fn, Decl(foo.d.ts, 1, 21))
26+
27+
export interface Thing { n: typeof x }
28+
>Thing : Symbol(Thing, Decl(foo.d.ts, 2, 27))
29+
>n : Symbol(n, Decl(foo.d.ts, 3, 24))
30+
>x : Symbol(x, Decl(foo.d.ts, 1, 10))
31+
32+
declare global export Foo;
33+

tests/baselines/reference/umd1.types

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/conformance/externalModules/a.ts ===
2+
/// <reference path="foo.d.ts" />
3+
Foo.fn();
4+
>Foo.fn() : void
5+
>Foo.fn : () => void
6+
>Foo : typeof Foo
7+
>fn : () => void
8+
9+
let x: Foo.Thing;
10+
>x : Foo.Thing
11+
>Foo : any
12+
>Thing : Foo.Thing
13+
14+
let y: number = x.n;
15+
>y : number
16+
>x.n : number
17+
>x : Foo.Thing
18+
>n : number
19+
20+
=== tests/cases/conformance/externalModules/foo.d.ts ===
21+
22+
export var x: number;
23+
>x : number
24+
25+
export function fn(): void;
26+
>fn : () => void
27+
28+
export interface Thing { n: typeof x }
29+
>Thing : Thing
30+
>n : number
31+
>x : number
32+
33+
declare global export Foo;
34+
>Foo : any
35+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
tests/cases/conformance/externalModules/a.ts(1,1): error TS2304: Cannot find name 'Foo'.
2+
tests/cases/conformance/externalModules/a.ts(2,8): error TS2503: Cannot find namespace 'Foo'.
3+
4+
5+
==== tests/cases/conformance/externalModules/a.ts (2 errors) ====
6+
Foo.fn();
7+
~~~
8+
!!! error TS2304: Cannot find name 'Foo'.
9+
let x: Foo.Thing;
10+
~~~
11+
!!! error TS2503: Cannot find namespace 'Foo'.
12+
let y: number = x.n;
13+
14+
==== tests/cases/conformance/externalModules/foo.d.ts (0 errors) ====
15+
16+
export var x: number;
17+
export function fn(): void;
18+
declare global export Foo;
19+

0 commit comments

Comments
 (0)