Skip to content

Commit b3a91d6

Browse files
committed
Prototype UMD support
1 parent 8a050ea commit b3a91d6

26 files changed

+503
-11
lines changed

src/compiler/binder.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,8 @@ namespace ts {
13451345
case SyntaxKind.ImportSpecifier:
13461346
case SyntaxKind.ExportSpecifier:
13471347
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
1348+
case SyntaxKind.GlobalModuleExportDeclaration:
1349+
return bindGlobalModuleExportDeclaration(<GlobalModuleExportDeclaration>node);
13481350
case SyntaxKind.ImportClause:
13491351
return bindImportClause(<ImportClause>node);
13501352
case SyntaxKind.ExportDeclaration:
@@ -1394,6 +1396,11 @@ namespace ts {
13941396
}
13951397
}
13961398

1399+
function bindGlobalModuleExportDeclaration(node: GlobalModuleExportDeclaration) {
1400+
file.symbol.globalExports = file.symbol.globalExports || {};
1401+
declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
1402+
}
1403+
13971404
function bindExportDeclaration(node: ExportDeclaration) {
13981405
if (!container.symbol || !container.symbol.exports) {
13991406
// Export * in some sort of block construct

src/compiler/checker.ts

+9
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

@@ -16185,6 +16191,9 @@ namespace ts {
1618516191
if (file.moduleAugmentations.length) {
1618616192
(augmentations || (augmentations = [])).push(file.moduleAugmentations);
1618716193
}
16194+
if (file.wasReferenced && file.symbol && file.symbol.globalExports) {
16195+
mergeSymbolTable(globals, file.symbol.globalExports);
16196+
}
1618816197
});
1618916198

1619016199
if (augmentations) {

src/compiler/parser.ts

+30-1
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:
@@ -4394,7 +4397,8 @@ namespace ts {
43944397
continue;
43954398

43964399
case SyntaxKind.GlobalKeyword:
4397-
return nextToken() === SyntaxKind.OpenBraceToken;
4400+
nextToken();
4401+
return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.Identifier || token === SyntaxKind.ExportKeyword;
43984402

43994403
case SyntaxKind.ImportKeyword:
44004404
nextToken();
@@ -4580,6 +4584,12 @@ namespace ts {
45804584
case SyntaxKind.EnumKeyword:
45814585
return parseEnumDeclaration(fullStart, decorators, modifiers);
45824586
case SyntaxKind.GlobalKeyword:
4587+
if (lookAhead(isGlobalModuleExportDeclaration)) {
4588+
return parseGlobalModuleExportDeclaration(fullStart, decorators, modifiers);
4589+
}
4590+
else {
4591+
return parseModuleDeclaration(fullStart, decorators, modifiers);
4592+
}
45834593
case SyntaxKind.ModuleKeyword:
45844594
case SyntaxKind.NamespaceKeyword:
45854595
return parseModuleDeclaration(fullStart, decorators, modifiers);
@@ -4603,6 +4613,11 @@ namespace ts {
46034613
}
46044614
}
46054615

4616+
function isGlobalModuleExportDeclaration() {
4617+
nextToken();
4618+
return token === SyntaxKind.ExportKeyword;
4619+
}
4620+
46064621
function nextTokenIsIdentifierOrStringLiteralOnSameLine() {
46074622
nextToken();
46084623
return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token === SyntaxKind.StringLiteral);
@@ -5258,6 +5273,20 @@ namespace ts {
52585273
return nextToken() === SyntaxKind.SlashToken;
52595274
}
52605275

5276+
function parseGlobalModuleExportDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): GlobalModuleExportDeclaration {
5277+
const exportDeclaration = <GlobalModuleExportDeclaration>createNode(SyntaxKind.GlobalModuleExportDeclaration, fullStart);
5278+
exportDeclaration.decorators = decorators;
5279+
exportDeclaration.modifiers = modifiers;
5280+
parseExpected(SyntaxKind.GlobalKeyword);
5281+
parseExpected(SyntaxKind.ExportKeyword);
5282+
5283+
exportDeclaration.name = parseIdentifier();
5284+
5285+
parseExpected(SyntaxKind.SemicolonToken);
5286+
5287+
return finishNode(exportDeclaration);
5288+
}
5289+
52615290
function parseImportDeclarationOrImportEqualsDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ImportEqualsDeclaration | ImportDeclaration {
52625291
parseExpected(SyntaxKind.ImportKeyword);
52635292
const afterImportPos = scanner.getStartPos();

src/compiler/program.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,7 @@ namespace ts {
12731273
}
12741274

12751275
function processRootFile(fileName: string, isDefaultLib: boolean) {
1276-
processSourceFile(normalizePath(fileName), isDefaultLib);
1276+
processSourceFile(normalizePath(fileName), isDefaultLib, true);
12771277
}
12781278

12791279
function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean {
@@ -1367,15 +1367,15 @@ namespace ts {
13671367
}
13681368
}
13691369

1370-
function processSourceFile(fileName: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) {
1370+
function processSourceFile(fileName: string, isDefaultLib: boolean, isReference: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) {
13711371
let diagnosticArgument: string[];
13721372
let diagnostic: DiagnosticMessage;
13731373
if (hasExtension(fileName)) {
13741374
if (!options.allowNonTsExtensions && !forEach(supportedExtensions, extension => fileExtensionIs(host.getCanonicalFileName(fileName), extension))) {
13751375
diagnostic = Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1;
13761376
diagnosticArgument = [fileName, "'" + supportedExtensions.join("', '") + "'"];
13771377
}
1378-
else if (!findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd)) {
1378+
else if (!findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd)) {
13791379
diagnostic = Diagnostics.File_0_not_found;
13801380
diagnosticArgument = [fileName];
13811381
}
@@ -1385,13 +1385,13 @@ namespace ts {
13851385
}
13861386
}
13871387
else {
1388-
const nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd);
1388+
const nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd);
13891389
if (!nonTsFile) {
13901390
if (options.allowNonTsExtensions) {
13911391
diagnostic = Diagnostics.File_0_not_found;
13921392
diagnosticArgument = [fileName];
13931393
}
1394-
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, toPath(fileName + extension, currentDirectory, getCanonicalFileName), isDefaultLib, refFile, refPos, refEnd))) {
1394+
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, toPath(fileName + extension, currentDirectory, getCanonicalFileName), isDefaultLib, isReference, refFile, refPos, refEnd))) {
13951395
diagnostic = Diagnostics.File_0_not_found;
13961396
fileName += ".ts";
13971397
diagnosticArgument = [fileName];
@@ -1420,7 +1420,7 @@ namespace ts {
14201420
}
14211421

14221422
// Get source file from normalized fileName
1423-
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
1423+
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, isReference: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
14241424
if (filesByName.contains(path)) {
14251425
const file = filesByName.get(path);
14261426
// try to check if we've already seen this file but with a different casing in path
@@ -1429,6 +1429,10 @@ namespace ts {
14291429
reportFileNamesDifferOnlyInCasingError(fileName, file.fileName, refFile, refPos, refEnd);
14301430
}
14311431

1432+
if (file) {
1433+
file.wasReferenced = file.wasReferenced || isReference;
1434+
}
1435+
14321436
return file;
14331437
}
14341438

@@ -1445,6 +1449,7 @@ namespace ts {
14451449

14461450
filesByName.set(path, file);
14471451
if (file) {
1452+
file.wasReferenced = file.wasReferenced || isReference;
14481453
file.path = path;
14491454

14501455
if (host.useCaseSensitiveFileNames()) {
@@ -1482,7 +1487,7 @@ namespace ts {
14821487
function processReferencedFiles(file: SourceFile, basePath: string) {
14831488
forEach(file.referencedFiles, ref => {
14841489
const referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName);
1485-
processSourceFile(referencedFileName, /*isDefaultLib*/ false, file, ref.pos, ref.end);
1490+
processSourceFile(referencedFileName, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
14861491
});
14871492
}
14881493

@@ -1508,7 +1513,7 @@ namespace ts {
15081513
i < file.imports.length;
15091514

15101515
if (shouldAddFile) {
1511-
const importedFile = findSourceFile(resolution.resolvedFileName, toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), /*isDefaultLib*/ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
1516+
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);
15121517

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

src/compiler/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ namespace ts {
273273
ModuleDeclaration,
274274
ModuleBlock,
275275
CaseBlock,
276+
GlobalModuleExportDeclaration,
276277
ImportEqualsDeclaration,
277278
ImportDeclaration,
278279
ImportClause,
@@ -1323,6 +1324,12 @@ namespace ts {
13231324
name: Identifier;
13241325
}
13251326

1327+
// @kind(SyntaxKind.GlobalModuleImport)
1328+
export interface GlobalModuleExportDeclaration extends DeclarationStatement {
1329+
name: Identifier;
1330+
moduleReference: LiteralLikeNode;
1331+
}
1332+
13261333
// @kind(SyntaxKind.ExportDeclaration)
13271334
export interface ExportDeclaration extends DeclarationStatement {
13281335
exportClause?: NamedExports;
@@ -1535,6 +1542,8 @@ namespace ts {
15351542
/* @internal */ externalModuleIndicator: Node;
15361543
// The first node that causes this file to be a CommonJS module
15371544
/* @internal */ commonJsModuleIndicator: Node;
1545+
// True if the file was a root file in a compilation or a /// reference targets
1546+
wasReferenced?: boolean;
15381547

15391548
/* @internal */ identifiers: Map<string>;
15401549
/* @internal */ nodeCount: number;
@@ -1991,6 +2000,7 @@ namespace ts {
19912000

19922001
members?: SymbolTable; // Class, interface or literal instance members
19932002
exports?: SymbolTable; // Module exports
2003+
globalExports?: SymbolTable; // Conditional global UMD exports
19942004
/* @internal */ id?: number; // Unique id (used to look up SymbolLinks)
19952005
/* @internal */ mergeId?: number; // Merge id (used to look up merged symbol)
19962006
/* @internal */ parent?: Symbol; // Parent symbol

src/compiler/utilities.ts

+1
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

+1-1
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

+2-1
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

+21
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;
+33
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

+35
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+
+19
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)