diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5fdfc7f02be..d6ede3049c2 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -53,7 +53,8 @@ namespace ts { return state; } else if (node.kind === SyntaxKind.ModuleDeclaration) { - return getModuleInstanceState((node).body); + const body = (node).body; + return body ? getModuleInstanceState(body) : ModuleInstanceState.NonInstantiated; } else { return ModuleInstanceState.Instantiated; @@ -1256,7 +1257,7 @@ namespace ts { function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { const body = node.kind === SyntaxKind.SourceFile ? node : (node).body; - if (body.kind === SyntaxKind.SourceFile || body.kind === SyntaxKind.ModuleBlock) { + if (body && (body.kind === SyntaxKind.SourceFile || body.kind === SyntaxKind.ModuleBlock)) { for (const stat of (body).statements) { if (stat.kind === SyntaxKind.ExportDeclaration || stat.kind === SyntaxKind.ExportAssignment) { return true; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 06ef2463402..50f1ce0d208 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -380,6 +380,7 @@ namespace ts { } function mergeSymbol(target: Symbol, source: Symbol) { + //TODO: how to merge w/ shorthand ambient module? if (!(target.flags & getExcludedSymbolFlags(source.flags))) { if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { // reset flag when merging instantiated module into value module that has only const enums @@ -986,7 +987,9 @@ namespace ts { const moduleSymbol = resolveExternalModuleName(node, (node.parent).moduleSpecifier); if (moduleSymbol) { - const exportDefaultSymbol = moduleSymbol.exports["export="] ? + const exportDefaultSymbol = isShorthandAmbientModule(moduleSymbol.valueDeclaration) ? + moduleSymbol : + moduleSymbol.exports["export="] ? getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") : resolveSymbol(moduleSymbol.exports["default"]); @@ -1060,6 +1063,10 @@ namespace ts { if (targetSymbol) { const name = specifier.propertyName || specifier.name; if (name.text) { + if (isShorthandAmbientModule(moduleSymbol.valueDeclaration)) { + return moduleSymbol; + } + let symbolFromVariable: Symbol; // First check if module was specified with "export=". If so, get the member from the resolved type if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports["export="]) { @@ -3220,9 +3227,14 @@ namespace ts { function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { - const type = createObjectType(TypeFlags.Anonymous, symbol); - links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ? - addNullableKind(type, TypeFlags.Undefined) : type; + if (symbol.valueDeclaration.kind === SyntaxKind.ModuleDeclaration && isShorthandAmbientModule(symbol.valueDeclaration)) { + links.type = anyType; + } + else { + const type = createObjectType(TypeFlags.Anonymous, symbol); + links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ? + addNullableKind(type, TypeFlags.Undefined) : type; + } } return links.type; } @@ -16010,7 +16022,7 @@ namespace ts { // - augmentation for a global scope is always applied // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Merged); - if (checkBody) { + if (checkBody && node.body) { // body of ambient external module is always a module block for (const statement of (node.body).statements) { checkModuleAugmentationElement(statement, isGlobalAugmentation); @@ -16037,7 +16049,9 @@ namespace ts { } } } - checkSourceElement(node.body); + if (node.body) { + checkSourceElement(node.body); + } } function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 397b135bf23..f44298a4206 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -853,21 +853,26 @@ namespace ts { writeTextOfNode(currentText, node.name); } } - while (node.body.kind !== SyntaxKind.ModuleBlock) { + while (node.body && node.body.kind !== SyntaxKind.ModuleBlock) { node = node.body; write("."); writeTextOfNode(currentText, node.name); } const prevEnclosingDeclaration = enclosingDeclaration; - enclosingDeclaration = node; - write(" {"); - writeLine(); - increaseIndent(); - emitLines((node.body).statements); - decreaseIndent(); - write("}"); - writeLine(); - enclosingDeclaration = prevEnclosingDeclaration; + if (node.body) { + enclosingDeclaration = node; + write(" {"); + writeLine(); + increaseIndent(); + emitLines((node.body).statements); + decreaseIndent(); + write("}"); + writeLine(); + enclosingDeclaration = prevEnclosingDeclaration; + } + else { + write(";"); + } } function writeTypeAliasDeclaration(node: TypeAliasDeclaration) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f225b4cacf8..88895531d16 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -6246,7 +6246,7 @@ const _super = (function (geti, seti) { } function getInnerMostModuleDeclarationFromDottedModule(moduleDeclaration: ModuleDeclaration): ModuleDeclaration { - if (moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { + if (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { const recursiveInnerModule = getInnerMostModuleDeclarationFromDottedModule(moduleDeclaration.body); return recursiveInnerModule || moduleDeclaration.body; } @@ -6295,6 +6295,7 @@ const _super = (function (geti, seti) { write(getGeneratedNameForNode(node)); emitEnd(node.name); write(") "); + // node.body must exist, as this is a non-ambient module if (node.body.kind === SyntaxKind.ModuleBlock) { const saveConvertedLoopState = convertedLoopState; const saveTempFlags = tempFlags; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6f042627c6b..8af258ebfc8 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5330,7 +5330,14 @@ namespace ts { else { node.name = parseLiteralNode(/*internName*/ true); } - node.body = parseModuleBlock(); + + if (token === SyntaxKind.OpenBraceToken) { + node.body = parseModuleBlock(); + } + else { + parseSemicolon(); + } + return finishNode(node); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bab554927e7..d94e25c786e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1712,9 +1712,12 @@ namespace ts { // The StringLiteral must specify a top - level external module name. // Relative external module names are not permitted - // NOTE: body of ambient module is always a module block - for (const statement of ((node).body).statements) { - collectModuleReferences(statement, /*inAmbientModule*/ true); + // NOTE: body of ambient module is always a module block, if it exists + const body = (node).body; + if (body) { + for (const statement of body.statements) { + collectModuleReferences(statement, /*inAmbientModule*/ true); + } } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c7e14a10031..5f56c017c7e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1301,7 +1301,7 @@ namespace ts { // @kind(SyntaxKind.ModuleDeclaration) export interface ModuleDeclaration extends DeclarationStatement { name: Identifier | LiteralExpression; - body: ModuleBlock | ModuleDeclaration; + body?: ModuleBlock | ModuleDeclaration; } // @kind(SyntaxKind.ModuleBlock) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 74ea3459b23..56de65b5dc0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -372,6 +372,11 @@ namespace ts { ((node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(node)); } + export function isShorthandAmbientModule(node: Node): boolean { + // The only kind of module that can be missing a body is a shorthand ambient module. + return node.kind === SyntaxKind.ModuleDeclaration && (!(node).body); + } + export function isBlockScopedContainerTopLevel(node: Node): boolean { return node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.ModuleDeclaration || diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 92cf64eac94..59dc4a38283 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -188,7 +188,10 @@ namespace ts.NavigationBar { case SyntaxKind.ModuleDeclaration: let moduleDeclaration = node; topLevelNodes.push(node); - addTopLevelNodes((getInnermostModule(moduleDeclaration).body).statements, topLevelNodes); + const inner = getInnermostModule(moduleDeclaration); + if (inner.body) { + addTopLevelNodes((inner.body).statements, topLevelNodes); + } break; case SyntaxKind.FunctionDeclaration: @@ -453,7 +456,8 @@ namespace ts.NavigationBar { function createModuleItem(node: ModuleDeclaration): NavigationBarItem { const moduleName = getModuleName(node); - const childItems = getItemsWorker(getChildNodes((getInnermostModule(node).body).statements), createChildItem); + const body = getInnermostModule(node).body; + const childItems = body ? getItemsWorker(getChildNodes(body.statements), createChildItem) : []; return getNavigationBarItem(moduleName, ts.ScriptElementKind.moduleElement, @@ -611,7 +615,7 @@ namespace ts.NavigationBar { } function getInnermostModule(node: ModuleDeclaration): ModuleDeclaration { - while (node.body.kind === SyntaxKind.ModuleDeclaration) { + while (node.body && node.body.kind === SyntaxKind.ModuleDeclaration) { node = node.body; } diff --git a/src/services/services.ts b/src/services/services.ts index 90740b257c6..6bc40b80b31 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -415,7 +415,7 @@ namespace ts { } // If this is left side of dotted module declaration, there is no doc comments associated with this node - if (declaration.kind === SyntaxKind.ModuleDeclaration && (declaration).body.kind === SyntaxKind.ModuleDeclaration) { + if (declaration.kind === SyntaxKind.ModuleDeclaration && (declaration).body && (declaration).body.kind === SyntaxKind.ModuleDeclaration) { return; } diff --git a/tests/baselines/reference/ambientShorthand.js b/tests/baselines/reference/ambientShorthand.js new file mode 100644 index 00000000000..76a3b482994 --- /dev/null +++ b/tests/baselines/reference/ambientShorthand.js @@ -0,0 +1,24 @@ +//// [tests/cases/conformance/ambient/ambientShorthand.ts] //// + +//// [declarations.d.ts] +declare module "jquery" +// Semicolon is optional +declare module "fs"; + +//// [user.ts] +/// +import foo, {bar} from "jquery"; +import * as baz from "fs"; +foo(bar, baz); + + +//// [user.js] +"use strict"; +/// +var jquery_1 = require("jquery"); +var baz = require("fs"); +jquery_1["default"](jquery_1.bar, baz); + + +//// [user.d.ts] +/// diff --git a/tests/baselines/reference/ambientShorthand.symbols b/tests/baselines/reference/ambientShorthand.symbols new file mode 100644 index 00000000000..91cefe8d4c4 --- /dev/null +++ b/tests/baselines/reference/ambientShorthand.symbols @@ -0,0 +1,20 @@ +=== tests/cases/conformance/ambient/user.ts === +/// +import foo, {bar} from "jquery"; +>foo : Symbol(foo, Decl(user.ts, 1, 6)) +>bar : Symbol(bar, Decl(user.ts, 1, 13)) + +import * as baz from "fs"; +>baz : Symbol(baz, Decl(user.ts, 2, 6)) + +foo(bar, baz); +>foo : Symbol(foo, Decl(user.ts, 1, 6)) +>bar : Symbol(bar, Decl(user.ts, 1, 13)) +>baz : Symbol(baz, Decl(user.ts, 2, 6)) + +=== tests/cases/conformance/ambient/declarations.d.ts === +declare module "jquery" +No type information for this code.// Semicolon is optional +No type information for this code.declare module "fs"; +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/ambientShorthand.types b/tests/baselines/reference/ambientShorthand.types new file mode 100644 index 00000000000..12b18953e88 --- /dev/null +++ b/tests/baselines/reference/ambientShorthand.types @@ -0,0 +1,21 @@ +=== tests/cases/conformance/ambient/user.ts === +/// +import foo, {bar} from "jquery"; +>foo : any +>bar : any + +import * as baz from "fs"; +>baz : any + +foo(bar, baz); +>foo(bar, baz) : any +>foo : any +>bar : any +>baz : any + +=== tests/cases/conformance/ambient/declarations.d.ts === +declare module "jquery" +No type information for this code.// Semicolon is optional +No type information for this code.declare module "fs"; +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/ambientShorthand_declarationEmit.js b/tests/baselines/reference/ambientShorthand_declarationEmit.js new file mode 100644 index 00000000000..d758b57efd2 --- /dev/null +++ b/tests/baselines/reference/ambientShorthand_declarationEmit.js @@ -0,0 +1,9 @@ +//// [ambientShorthand_declarationEmit.ts] +declare module "foo"; + + +//// [ambientShorthand_declarationEmit.js] + + +//// [ambientShorthand_declarationEmit.d.ts] +declare module "foo"; \ No newline at end of file diff --git a/tests/baselines/reference/ambientShorthand_declarationEmit.symbols b/tests/baselines/reference/ambientShorthand_declarationEmit.symbols new file mode 100644 index 00000000000..f1b3284b0f2 --- /dev/null +++ b/tests/baselines/reference/ambientShorthand_declarationEmit.symbols @@ -0,0 +1,4 @@ +=== tests/cases/conformance/ambient/ambientShorthand_declarationEmit.ts === +declare module "foo"; +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/baselines/reference/ambientShorthand_declarationEmit.types b/tests/baselines/reference/ambientShorthand_declarationEmit.types new file mode 100644 index 00000000000..f1b3284b0f2 --- /dev/null +++ b/tests/baselines/reference/ambientShorthand_declarationEmit.types @@ -0,0 +1,4 @@ +=== tests/cases/conformance/ambient/ambientShorthand_declarationEmit.ts === +declare module "foo"; +No type information for this code. +No type information for this code. \ No newline at end of file diff --git a/tests/cases/conformance/ambient/ambientShorthand.ts b/tests/cases/conformance/ambient/ambientShorthand.ts new file mode 100644 index 00000000000..3070abff595 --- /dev/null +++ b/tests/cases/conformance/ambient/ambientShorthand.ts @@ -0,0 +1,10 @@ +// @Filename: declarations.d.ts +declare module "jquery" +// Semicolon is optional +declare module "fs"; + +// @Filename: user.ts +/// +import foo, {bar} from "jquery"; +import * as baz from "fs"; +foo(bar, baz); diff --git a/tests/cases/conformance/ambient/ambientShorthand_declarationEmit.ts b/tests/cases/conformance/ambient/ambientShorthand_declarationEmit.ts new file mode 100644 index 00000000000..13053868e5b --- /dev/null +++ b/tests/cases/conformance/ambient/ambientShorthand_declarationEmit.ts @@ -0,0 +1,2 @@ +// @declaration: true +declare module "foo";