Skip to content

Commit

Permalink
Allow export = and export default to alias any EntityNameExpressi…
Browse files Browse the repository at this point in the history
…on, not just identifiers.
  • Loading branch information
Andy Hanson committed Jul 29, 2016
1 parent 97bbbd7 commit f9fd496
Show file tree
Hide file tree
Showing 12 changed files with 613 additions and 26 deletions.
13 changes: 6 additions & 7 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1887,18 +1887,17 @@ namespace ts {
}

function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
const boundExpression = node.kind === SyntaxKind.ExportAssignment ? (<ExportAssignment>node).expression : (<BinaryExpression>node).right;
if (!container.symbol || !container.symbol.exports) {
// Export assignment in some sort of block construct
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
}
else if (boundExpression.kind === SyntaxKind.Identifier && node.kind === SyntaxKind.ExportAssignment) {
// An export default clause with an identifier exports all meanings of that identifier
declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.Alias, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
}
else {
// An export default clause with an expression exports a value
declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
const flags = node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node)
// An export default clause with an EntityNameExpression exports all meanings of that identifier
? SymbolFlags.Alias
// An export default clause with any other expression exports a value
: SymbolFlags.Property;
declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ namespace ts {
}

function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration {
return forEach(symbol.declarations, d => isAliasSymbolDeclaration(d) ? d : undefined);
return find(symbol.declarations, d => isAliasSymbolDeclaration(d) ? d : undefined);
}

function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration): Symbol {
Expand Down Expand Up @@ -1175,7 +1175,7 @@ namespace ts {
}

function getTargetOfExportAssignment(node: ExportAssignment): Symbol {
return resolveEntityName(<Identifier>node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace);
return resolveEntityName(<EntityNameExpression>node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace);
}

function getTargetOfAliasDeclaration(node: Declaration): Symbol {
Expand Down
15 changes: 13 additions & 2 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ namespace ts {
* returns a truthy value, then returns that value.
* If no such value is found, the callback is applied to each element of array and undefined is returned.
*/
export function forEach<T, U>(array: T[], callback: (element: T, index: number) => U): U {
export function forEach<T, U>(array: T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
if (array) {
for (let i = 0, len = array.length; i < len; i++) {
const result = callback(array[i], i);
Expand All @@ -93,6 +93,17 @@ namespace ts {
return undefined;
}

/** Like `forEach`, but assumes existence of array and fails if no truthy value is found. */
export function find<T, U>(array: T[], callback: (element: T, index: number) => U | undefined): U {
for (let i = 0, len = array.length; i < len; i++) {
const result = callback(array[i], i);
if (result) {
return result;
}
}
Debug.fail();
}

export function contains<T>(array: T[], value: T): boolean {
if (array) {
for (const v of array) {
Expand Down Expand Up @@ -941,7 +952,7 @@ namespace ts {
* [^./] # matches everything up to the first . character (excluding directory seperators)
* (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension
*/
const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*";
const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*";
const singleAsteriskRegexFragmentOther = "[^/]*";

export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") {
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1693,16 +1693,20 @@ namespace ts {
// import * as <symbol> from ...
// import { x as <symbol> } from ...
// export { x as <symbol> } from ...
// export = ...
// export default ...
// export = <EntityNameExpression>
// export default <EntityNameExpression>
export function isAliasSymbolDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
node.kind === SyntaxKind.NamespaceExportDeclaration ||
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
node.kind === SyntaxKind.NamespaceImport ||
node.kind === SyntaxKind.ImportSpecifier ||
node.kind === SyntaxKind.ExportSpecifier ||
node.kind === SyntaxKind.ExportAssignment && (<ExportAssignment>node).expression.kind === SyntaxKind.Identifier;
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node);
}

export function exportAssignmentIsAlias(node: ExportAssignment): boolean {
return isEntityNameExpression(node.expression);
}

export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) {
Expand Down
76 changes: 72 additions & 4 deletions tests/baselines/reference/exportDefaultProperty.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,76 @@
//// [exportDefaultProperty.ts]
export default "".length
//// [tests/cases/compiler/exportDefaultProperty.ts] ////

//// [declarations.d.ts]
// This test is just like exportEqualsProperty, but with `export default`.

declare namespace foo.bar {
export type X = number;
export const X: number;
}

declare module "foobar" {
export default foo.bar;
}

declare module "foobarx" {
export default foo.bar.X;
}

//// [exportDefaultProperty.js]
//// [a.ts]
namespace A {
export class B { constructor(b: number) {} }
export namespace B { export const b: number = 0; }
}
export default A.B;

//// [b.ts]
export default "foo".length;

//// [index.ts]
/// <reference path="declarations.d.ts" />
import fooBar from "foobar";
import X = fooBar.X;
import X2 from "foobarx";
const x: X = X;
const x2: X2 = X2;

import B from "./a";
const b: B = new B(B.b);

import fooLength from "./b";
fooLength + 1;


//// [a.js]
"use strict";
var A;
(function (A) {
var B = (function () {
function B(b) {
}
return B;
}());
A.B = B;
var B;
(function (B) {
B.b = 0;
})(B = A.B || (A.B = {}));
})(A || (A = {}));
exports.__esModule = true;
exports["default"] = "".length;
exports["default"] = A.B;
//// [b.js]
"use strict";
exports.__esModule = true;
exports["default"] = "foo".length;
//// [index.js]
"use strict";
/// <reference path="declarations.d.ts" />
var foobar_1 = require("foobar");
var X = foobar_1["default"].X;
var foobarx_1 = require("foobarx");
var x = X;
var x2 = foobarx_1["default"];
var a_1 = require("./a");
var b = new a_1["default"](a_1["default"].b);
var b_1 = require("./b");
b_1["default"] + 1;
93 changes: 90 additions & 3 deletions tests/baselines/reference/exportDefaultProperty.symbols
Original file line number Diff line number Diff line change
@@ -1,5 +1,92 @@
=== tests/cases/compiler/exportDefaultProperty.ts ===
export default "".length
>"".length : Symbol(String.length, Decl(lib.d.ts, --, --))
=== tests/cases/compiler/index.ts ===
/// <reference path="declarations.d.ts" />
import fooBar from "foobar";
>fooBar : Symbol(fooBar, Decl(index.ts, 1, 6))

import X = fooBar.X;
>X : Symbol(X, Decl(index.ts, 1, 28))
>fooBar : Symbol(fooBar, Decl(index.ts, 1, 6))
>X : Symbol(fooBar.X, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))

import X2 from "foobarx";
>X2 : Symbol(X2, Decl(index.ts, 3, 6))

const x: X = X;
>x : Symbol(x, Decl(index.ts, 4, 5))
>X : Symbol(X, Decl(index.ts, 1, 28))
>X : Symbol(X, Decl(index.ts, 1, 28))

const x2: X2 = X2;
>x2 : Symbol(x2, Decl(index.ts, 5, 5))
>X2 : Symbol(X2, Decl(index.ts, 3, 6))
>X2 : Symbol(X2, Decl(index.ts, 3, 6))

import B from "./a";
>B : Symbol(B, Decl(index.ts, 7, 6))

const b: B = new B(B.b);
>b : Symbol(b, Decl(index.ts, 8, 5))
>B : Symbol(B, Decl(index.ts, 7, 6))
>B : Symbol(B, Decl(index.ts, 7, 6))
>B.b : Symbol(B.b, Decl(a.ts, 2, 37))
>B : Symbol(B, Decl(index.ts, 7, 6))
>b : Symbol(B.b, Decl(a.ts, 2, 37))

import fooLength from "./b";
>fooLength : Symbol(fooLength, Decl(index.ts, 10, 6))

fooLength + 1;
>fooLength : Symbol(fooLength, Decl(index.ts, 10, 6))

=== tests/cases/compiler/declarations.d.ts ===
// This test is just like exportEqualsProperty, but with `export default`.

declare namespace foo.bar {
>foo : Symbol(foo, Decl(declarations.d.ts, 0, 0))
>bar : Symbol(bar, Decl(declarations.d.ts, 2, 22))

export type X = number;
>X : Symbol(X, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))

export const X: number;
>X : Symbol(X, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
}

declare module "foobar" {
export default foo.bar;
>foo.bar : Symbol(default, Decl(declarations.d.ts, 2, 22))
>foo : Symbol(foo, Decl(declarations.d.ts, 0, 0))
>bar : Symbol(default, Decl(declarations.d.ts, 2, 22))
}

declare module "foobarx" {
export default foo.bar.X;
>foo.bar.X : Symbol(default, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
>foo.bar : Symbol(foo.bar, Decl(declarations.d.ts, 2, 22))
>foo : Symbol(foo, Decl(declarations.d.ts, 0, 0))
>bar : Symbol(foo.bar, Decl(declarations.d.ts, 2, 22))
>X : Symbol(default, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
}

=== tests/cases/compiler/a.ts ===
namespace A {
>A : Symbol(A, Decl(a.ts, 0, 0))

export class B { constructor(b: number) {} }
>B : Symbol(B, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))
>b : Symbol(b, Decl(a.ts, 1, 33))

export namespace B { export const b: number = 0; }
>B : Symbol(B, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))
>b : Symbol(b, Decl(a.ts, 2, 37))
}
export default A.B;
>A.B : Symbol(default, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))
>A : Symbol(A, Decl(a.ts, 0, 0))
>B : Symbol(default, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))

=== tests/cases/compiler/b.ts ===
export default "foo".length;
>"foo".length : Symbol(String.length, Decl(lib.d.ts, --, --))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))

99 changes: 95 additions & 4 deletions tests/baselines/reference/exportDefaultProperty.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,97 @@
=== tests/cases/compiler/exportDefaultProperty.ts ===
export default "".length
>"".length : number
>"" : string
=== tests/cases/compiler/index.ts ===
/// <reference path="declarations.d.ts" />
import fooBar from "foobar";
>fooBar : typeof fooBar

import X = fooBar.X;
>X : number
>fooBar : typeof fooBar
>X : number

import X2 from "foobarx";
>X2 : number

const x: X = X;
>x : number
>X : number
>X : number

const x2: X2 = X2;
>x2 : number
>X2 : number
>X2 : number

import B from "./a";
>B : typeof B

const b: B = new B(B.b);
>b : B
>B : B
>new B(B.b) : B
>B : typeof B
>B.b : number
>B : typeof B
>b : number

import fooLength from "./b";
>fooLength : number

fooLength + 1;
>fooLength + 1 : number
>fooLength : number
>1 : number

=== tests/cases/compiler/declarations.d.ts ===
// This test is just like exportEqualsProperty, but with `export default`.

declare namespace foo.bar {
>foo : typeof foo
>bar : typeof bar

export type X = number;
>X : number

export const X: number;
>X : number
}

declare module "foobar" {
export default foo.bar;
>foo.bar : typeof default
>foo : typeof foo
>bar : typeof default
}

declare module "foobarx" {
export default foo.bar.X;
>foo.bar.X : number
>foo.bar : typeof foo.bar
>foo : typeof foo
>bar : typeof foo.bar
>X : number
}

=== tests/cases/compiler/a.ts ===
namespace A {
>A : typeof A

export class B { constructor(b: number) {} }
>B : B
>b : number

export namespace B { export const b: number = 0; }
>B : typeof B
>b : number
>0 : number
}
export default A.B;
>A.B : typeof default
>A : typeof A
>B : typeof default

=== tests/cases/compiler/b.ts ===
export default "foo".length;
>"foo".length : number
>"foo" : string
>length : number

Loading

0 comments on commit f9fd496

Please sign in to comment.