Skip to content

Commit

Permalink
Support for 'export default' with expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg committed Feb 26, 2015
1 parent a0eff60 commit 0e8b6df
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 42 deletions.
32 changes: 14 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,9 +693,14 @@ module ts {
// The two types of exports are mutually exclusive.
error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements);
}
if (node.exportName.text) {
var meaning = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace;
var exportSymbol = resolveName(node, node.exportName.text, meaning, Diagnostics.Cannot_find_name_0, node.exportName);
if (node.expression.kind === SyntaxKind.Identifier && (<Identifier>node.expression).text) {
var exportSymbol = resolveName(node, (<Identifier>node.expression).text, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace,
Diagnostics.Cannot_find_name_0, <Identifier>node.expression);
}
else {
var exportSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "*default*");

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2015

Contributor

This seems like a somewhat odd constant value. Coudl this be a problem if someone actually names something "*default*" (i.e.with a string literal) somewhere in their code?

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2015

Contributor

Could you comment what original code this case handles?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Mar 4, 2015

Author Member

This is no longer present in the final code.

exportSymbol.parent = containerSymbol;
(<TransientSymbol>exportSymbol).type = checkExpression(node.expression);
}
symbolLinks.exportAssignmentSymbol = exportSymbol || unknownSymbol;
}
Expand Down Expand Up @@ -9537,19 +9542,6 @@ module ts {
if (!isInAmbientContext(node) && node.name.kind === SyntaxKind.StringLiteral) {
grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names);
}
else if (node.name.kind === SyntaxKind.Identifier && node.body.kind === SyntaxKind.ModuleBlock) {
var statements = (<ModuleBlock>node.body).statements;
for (var i = 0, n = statements.length; i < n; i++) {
var statement = statements[i];

// TODO: AndersH: No reason to do a separate pass over the statements for this check, we should
// just fold it into checkExportAssignment.
if (statement.kind === SyntaxKind.ExportAssignment) {
// Export assignments are not allowed in an internal module
grammarErrorOnNode(statement, Diagnostics.An_export_assignment_cannot_be_used_in_an_internal_module);
}
}
}
}

checkCollisionWithCapturedThisVariable(node, node.name);
Expand Down Expand Up @@ -9704,11 +9696,14 @@ module ts {
}

function checkExportAssignment(node: ExportAssignment) {
if (node.parent.kind === SyntaxKind.ModuleBlock && (<ModuleDeclaration>node.parent.parent).name.kind === SyntaxKind.Identifier) {
error(node, Diagnostics.An_export_assignment_cannot_be_used_in_an_internal_module);
return;
}
// Grammar checking
if (!checkGrammarModifiers(node) && (node.flags & NodeFlags.Modifier)) {
grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers);
}

var container = node.parent;
if (container.kind !== SyntaxKind.SourceFile) {
// In a module, the immediate parent will be a block, so climb up one more parent
Expand Down Expand Up @@ -9906,6 +9901,7 @@ module ts {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.EnumMember:
case SyntaxKind.ExportAssignment:
case SyntaxKind.SourceFile:
forEachChild(node, checkFunctionExpressionBodies);
break;
Expand Down Expand Up @@ -10165,7 +10161,7 @@ module ts {
}

if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) {
return (<ExportAssignment>nodeOnRightSide.parent).exportName === nodeOnRightSide && <ExportAssignment>nodeOnRightSide.parent;
return (<ExportAssignment>nodeOnRightSide.parent).expression === <Node>nodeOnRightSide && <ExportAssignment>nodeOnRightSide.parent;
}

return undefined;
Expand Down
14 changes: 6 additions & 8 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -700,8 +700,8 @@ module ts {
}

function emitExportAssignment(node: ExportAssignment) {
write("export = ");
writeTextOfNode(currentSourceFile, node.exportName);
write(node.isExportEquals ? "export = " : "export default ");
writeTextOfNode(currentSourceFile, node.expression);
write(";");
writeLine();
}
Expand Down Expand Up @@ -4759,15 +4759,14 @@ module ts {
emitCaptureThisForNodeIfNecessary(node);
emitLinesStartingAt(node.statements, startIndex);
emitTempDeclarations(/*newLine*/ true);
// TODO: Handle export default expressions
var exportName = resolver.getExportAssignmentName(node);
if (exportName) {
writeLine();
var exportAssignment = getFirstExportAssignment(node);
emitStart(exportAssignment);
write("return ");
emitStart(exportAssignment.exportName);
write(exportName);
emitEnd(exportAssignment.exportName);
emit(exportAssignment.expression);
write(";");
emitEnd(exportAssignment);
}
Expand All @@ -4780,15 +4779,14 @@ module ts {
emitCaptureThisForNodeIfNecessary(node);
emitLinesStartingAt(node.statements, startIndex);
emitTempDeclarations(/*newLine*/ true);
// TODO: Handle export default expressions
var exportName = resolver.getExportAssignmentName(node);
if (exportName) {
writeLine();
var exportAssignment = getFirstExportAssignment(node);
emitStart(exportAssignment);
write("module.exports = ");
emitStart(exportAssignment.exportName);
write(exportName);
emitEnd(exportAssignment.exportName);
emit(exportAssignment.expression);
write(";");
emitEnd(exportAssignment);
}
Expand Down
23 changes: 17 additions & 6 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ module ts {
visitNode(cbNode, (<ImportOrExportSpecifier>node).name);
case SyntaxKind.ExportAssignment:
return visitNodes(cbNodes, node.modifiers) ||
visitNode(cbNode, (<ExportAssignment>node).exportName);
visitNode(cbNode, (<ExportAssignment>node).expression);
case SyntaxKind.TemplateExpression:
return visitNode(cbNode, (<TemplateExpression>node).head) || visitNodes(cbNodes, (<TemplateExpression>node).templateSpans);
case SyntaxKind.TemplateSpan:
Expand Down Expand Up @@ -1500,6 +1500,10 @@ module ts {
}
if (token === SyntaxKind.ExportKeyword) {
nextToken();
if (token === SyntaxKind.DefaultKeyword) {
nextToken();
return token === SyntaxKind.ClassKeyword || token === SyntaxKind.FunctionKeyword;
}
return token !== SyntaxKind.AsteriskToken && token !== SyntaxKind.OpenBraceToken && canFollowModifier();
}
nextToken();
Expand Down Expand Up @@ -4828,10 +4832,17 @@ module ts {
return finishNode(node);
}

function parseExportAssignmentTail(fullStart: number, modifiers: ModifiersArray): ExportAssignment {
function parseExportAssignment(fullStart: number, modifiers: ModifiersArray): ExportAssignment {
var node = <ExportAssignment>createNode(SyntaxKind.ExportAssignment, fullStart);
setModifiers(node, modifiers);
node.exportName = parseIdentifier();
if (parseOptional(SyntaxKind.EqualsToken)) {
node.isExportEquals = true;
}
else {
parseExpected(SyntaxKind.DefaultKeyword);
}
//node.exportName = parseIdentifier();

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2015

Contributor

remove commented code.

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Mar 4, 2015

Author Member

Again, not present in the final code.

node.expression = parseAssignmentExpressionOrHigher();
parseSemicolon();
return finishNode(node);
}
Expand Down Expand Up @@ -4898,7 +4909,7 @@ module ts {
function nextTokenCanFollowExportKeyword() {
nextToken();
return token === SyntaxKind.EqualsToken || token === SyntaxKind.AsteriskToken ||
token === SyntaxKind.OpenBraceToken || isDeclarationStart();
token === SyntaxKind.OpenBraceToken || token === SyntaxKind.DefaultKeyword || isDeclarationStart();
}

function nextTokenIsDeclarationStart() {
Expand All @@ -4915,8 +4926,8 @@ module ts {
var modifiers = parseModifiers();
if (token === SyntaxKind.ExportKeyword) {
nextToken();
if (parseOptional(SyntaxKind.EqualsToken)) {
return parseExportAssignmentTail(fullStart, modifiers);
if (token === SyntaxKind.DefaultKeyword || token === SyntaxKind.EqualsToken) {
return parseExportAssignment(fullStart, modifiers);
}
if (token === SyntaxKind.AsteriskToken || token === SyntaxKind.OpenBraceToken) {
return parseExportDeclaration(fullStart, modifiers);
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,8 @@ module ts {
export type ExportSpecifier = ImportOrExportSpecifier;

export interface ExportAssignment extends Statement, ModuleElement {
exportName: Identifier;

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2015

Contributor

it seems odd to use the name ExportAssignment to refer to "export default". Could it just be an ExportStatement, with a kind of ExportDefaultStatement or ExportEqualsStatement?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Mar 4, 2015

Author Member

It should probably be ExportDefaultDeclaration.

isExportEquals?: boolean;
expression: Expression;
}

export interface FileReference extends TextRange {
Expand Down
2 changes: 1 addition & 1 deletion src/services/breakpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ module ts.BreakpointResolver {

case SyntaxKind.ExportAssignment:
// span on export = id
return textSpan(node, (<ExportAssignment>node).exportName);
return textSpan(node, (<ExportAssignment>node).expression);

case SyntaxKind.ImportEqualsDeclaration:
// import statement without including semicolon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export function x(){

// @Filename: foo2.ts
import foo1 = require('./foo1');
export = foo1.x; // Error, export assignment must be identifier only
export = foo1.x; // Ok
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
// @Filename: foo1.ts
var x = 10;
export = typeof x; // Error
export = typeof x; // Ok

// @Filename: foo2.ts
export = "sausages"; // Error
export = "sausages"; // Ok

// @Filename: foo3.ts
export = class Foo3 {}; // Error
export = class Foo3 {}; // Error, not an expression

// @Filename: foo4.ts
export = true; // Error
export = true; // Ok

// @Filename: foo5.ts
export = undefined; // Valid. undefined is an identifier in JavaScript/TypeScript

// @Filename: foo6.ts
export = void; // Error
export = void; // Error, void operator requires an argument

// @Filename: foo7.ts
export = Date || String; // Error
export = Date || String; // Ok

// @Filename: foo8.ts
export = null; // Error
export = null; // Ok

0 comments on commit 0e8b6df

Please sign in to comment.