Skip to content

Commit 97bbbd7

Browse files
author
Andy Hanson
committed
Introduce the EntityNameExpression type
1 parent 878cf85 commit 97bbbd7

File tree

8 files changed

+113
-67
lines changed

8 files changed

+113
-67
lines changed

src/compiler/checker.ts

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -968,28 +968,39 @@ namespace ts {
968968

969969

970970
function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean {
971-
let parentClassExpression = errorLocation;
972-
while (parentClassExpression) {
973-
const kind = parentClassExpression.kind;
974-
if (kind === SyntaxKind.Identifier || kind === SyntaxKind.PropertyAccessExpression) {
975-
parentClassExpression = parentClassExpression.parent;
976-
continue;
977-
}
978-
if (kind === SyntaxKind.ExpressionWithTypeArguments) {
979-
break;
980-
}
971+
const parentExpression = climbToSupportedExpressionWithTypeArguments(errorLocation);
972+
if (!parentExpression) {
981973
return false;
982974
}
983-
if (!parentClassExpression) {
984-
return false;
985-
}
986-
const expression = (<ExpressionWithTypeArguments>parentClassExpression).expression;
975+
const expression = parentExpression.expression;
976+
987977
if (resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) {
988978
error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression));
989979
return true;
990980
}
991981
return false;
992982
}
983+
/**
984+
* Climbs up parents to a SupportedExpressionWIthTypeArguments.
985+
* Does *not* just climb to an ExpressionWithTypeArguments; instead, ensures that this really is supported.
986+
*/
987+
function climbToSupportedExpressionWithTypeArguments(node: Node): SupportedExpressionWithTypeArguments | undefined {
988+
while (node) {
989+
switch (node.kind) {
990+
case SyntaxKind.Identifier:
991+
case SyntaxKind.PropertyAccessExpression:
992+
node = node.parent;
993+
break;
994+
case SyntaxKind.ExpressionWithTypeArguments:
995+
Debug.assert(isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node));
996+
return <SupportedExpressionWithTypeArguments>node;
997+
default:
998+
return undefined;
999+
}
1000+
}
1001+
return undefined;
1002+
}
1003+
9931004

9941005
function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void {
9951006
Debug.assert((result.flags & SymbolFlags.BlockScopedVariable) !== 0);
@@ -1274,7 +1285,7 @@ namespace ts {
12741285
}
12751286

12761287
// Resolves a qualified name and any involved aliases
1277-
function resolveEntityName(name: EntityName | Expression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean): Symbol {
1288+
function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean): Symbol | undefined {
12781289
if (nodeIsMissing(name)) {
12791290
return undefined;
12801291
}
@@ -1289,7 +1300,7 @@ namespace ts {
12891300
}
12901301
}
12911302
else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) {
1292-
const left = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).left : (<PropertyAccessExpression>name).expression;
1303+
const left = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).left : (<PropertyAccessEntityNameExpression>name).expression;
12931304
const right = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).right : (<PropertyAccessExpression>name).name;
12941305

12951306
const namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors);
@@ -1845,7 +1856,7 @@ namespace ts {
18451856
}
18461857
}
18471858

1848-
function isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult {
1859+
function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult {
18491860
// get symbol of the first identifier of the entityName
18501861
let meaning: SymbolFlags;
18511862
if (entityName.parent.kind === SyntaxKind.TypeQuery || isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent)) {
@@ -5022,7 +5033,7 @@ namespace ts {
50225033
return getDeclaredTypeOfSymbol(symbol);
50235034
}
50245035

5025-
function getTypeReferenceName(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): LeftHandSideExpression | EntityName {
5036+
function getTypeReferenceName(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): EntityNameOrEntityNameExpression | undefined {
50265037
switch (node.kind) {
50275038
case SyntaxKind.TypeReference:
50285039
return (<TypeReferenceNode>node).typeName;
@@ -5031,8 +5042,9 @@ namespace ts {
50315042
case SyntaxKind.ExpressionWithTypeArguments:
50325043
// We only support expressions that are simple qualified names. For other
50335044
// expressions this produces undefined.
5034-
if (isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node)) {
5035-
return (<ExpressionWithTypeArguments>node).expression;
5045+
const expr = <ExpressionWithTypeArguments>node;
5046+
if (isSupportedExpressionWithTypeArguments(expr)) {
5047+
return expr.expression;
50365048
}
50375049

50385050
// fall through;
@@ -5043,7 +5055,7 @@ namespace ts {
50435055

50445056
function resolveTypeReferenceName(
50455057
node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference,
5046-
typeReferenceName: LeftHandSideExpression | EntityName) {
5058+
typeReferenceName: EntityNameExpression | EntityName) {
50475059

50485060
if (!typeReferenceName) {
50495061
return unknownSymbol;
@@ -5084,15 +5096,14 @@ namespace ts {
50845096
const typeReferenceName = getTypeReferenceName(node);
50855097
symbol = resolveTypeReferenceName(node, typeReferenceName);
50865098
type = getTypeReferenceType(node, symbol);
5087-
5088-
links.resolvedSymbol = symbol;
5089-
links.resolvedType = type;
50905099
}
50915100
else {
50925101
// We only support expressions that are simple qualified names. For other expressions this produces undefined.
5093-
const typeNameOrExpression = node.kind === SyntaxKind.TypeReference ? (<TypeReferenceNode>node).typeName :
5094-
isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node) ? (<ExpressionWithTypeArguments>node).expression :
5095-
undefined;
5102+
const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference
5103+
? (<TypeReferenceNode>node).typeName
5104+
: isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node)
5105+
? (<SupportedExpressionWithTypeArguments>node).expression
5106+
: undefined;
50965107
symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol;
50975108
type = symbol === unknownSymbol ? unknownType :
50985109
symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? getTypeFromClassOrInterfaceReference(node, symbol) :
@@ -16951,20 +16962,21 @@ namespace ts {
1695116962
}
1695216963
}
1695316964

16954-
function getFirstIdentifier(node: EntityName | Expression): Identifier {
16955-
while (true) {
16956-
if (node.kind === SyntaxKind.QualifiedName) {
16957-
node = (<QualifiedName>node).left;
16958-
}
16959-
else if (node.kind === SyntaxKind.PropertyAccessExpression) {
16960-
node = (<PropertyAccessExpression>node).expression;
16961-
}
16962-
else {
16963-
break;
16964-
}
16965+
function getFirstIdentifier(node: EntityNameOrEntityNameExpression): Identifier {
16966+
switch (node.kind) {
16967+
case SyntaxKind.Identifier:
16968+
return <Identifier>node;
16969+
case SyntaxKind.QualifiedName:
16970+
do {
16971+
node = (<QualifiedName>node).left;
16972+
} while (node.kind !== SyntaxKind.Identifier);
16973+
return <Identifier>node;
16974+
case SyntaxKind.PropertyAccessExpression:
16975+
do {
16976+
node = (<PropertyAccessEntityNameExpression>node).expression;
16977+
} while (node.kind !== SyntaxKind.Identifier);
16978+
return <Identifier>node;
1696516979
}
16966-
Debug.assert(node.kind === SyntaxKind.Identifier);
16967-
return <Identifier>node;
1696816980
}
1696916981

1697016982
function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean {
@@ -17663,7 +17675,7 @@ namespace ts {
1766317675
return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined;
1766417676
}
1766517677

17666-
function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol {
17678+
function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined {
1766717679
if (isDeclarationName(entityName)) {
1766817680
return getSymbolOfNode(entityName.parent);
1766917681
}
@@ -17682,8 +17694,8 @@ namespace ts {
1768217694
}
1768317695
}
1768417696

17685-
if (entityName.parent.kind === SyntaxKind.ExportAssignment) {
17686-
return resolveEntityName(<Identifier>entityName,
17697+
if (entityName.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(<Identifier | PropertyAccessExpression>entityName)) {
17698+
return resolveEntityName(<EntityNameExpression>entityName,
1768717699
/*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
1768817700
}
1768917701

@@ -17697,7 +17709,7 @@ namespace ts {
1769717709
}
1769817710

1769917711
if (isRightSideOfQualifiedNameOrPropertyAccess(entityName)) {
17700-
entityName = <QualifiedName | PropertyAccessExpression>entityName.parent;
17712+
entityName = <QualifiedName | PropertyAccessEntityNameExpression>entityName.parent;
1770117713
}
1770217714

1770317715
if (isHeritageClauseElementIdentifier(<EntityName>entityName)) {
@@ -18410,7 +18422,7 @@ namespace ts {
1841018422
};
1841118423

1841218424
// defined here to avoid outer scope pollution
18413-
function getTypeReferenceDirectivesForEntityName(node: EntityName | PropertyAccessExpression): string[] {
18425+
function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] {
1841418426
// program does not have any files with type reference directives - bail out
1841518427
if (!fileToDirective) {
1841618428
return undefined;

src/compiler/declarationEmitter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ namespace ts {
441441
}
442442
}
443443

444-
function emitEntityName(entityName: EntityName | PropertyAccessExpression) {
444+
function emitEntityName(entityName: EntityNameOrEntityNameExpression) {
445445
const visibilityResult = resolver.isEntityNameVisible(entityName,
446446
// Aliases can be written asynchronously so use correct enclosing declaration
447447
entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration ? entityName.parent : enclosingDeclaration);
@@ -454,7 +454,7 @@ namespace ts {
454454
function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) {
455455
if (isSupportedExpressionWithTypeArguments(node)) {
456456
Debug.assert(node.expression.kind === SyntaxKind.Identifier || node.expression.kind === SyntaxKind.PropertyAccessExpression);
457-
emitEntityName(<Identifier | PropertyAccessExpression>node.expression);
457+
emitEntityName(node.expression);
458458
if (node.typeArguments) {
459459
write("<");
460460
emitCommaList(node.typeArguments, emitType);

src/compiler/types.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -982,13 +982,19 @@ namespace ts {
982982
multiLine?: boolean;
983983
}
984984

985+
export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression;
986+
export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;
987+
985988
// @kind(SyntaxKind.PropertyAccessExpression)
986989
export interface PropertyAccessExpression extends MemberExpression, Declaration {
987990
expression: LeftHandSideExpression;
988991
name: Identifier;
989992
}
990-
991-
export type IdentifierOrPropertyAccess = Identifier | PropertyAccessExpression;
993+
/** Brand for a PropertyAccessExpression which, like a QualifiedName, consists of a sequence of identifiers separated by dots. */
994+
export interface PropertyAccessEntityNameExpression extends PropertyAccessExpression {
995+
_propertyAccessExpressionLikeQualifiedNameBrand?: any;
996+
expression: EntityNameExpression;
997+
}
992998

993999
// @kind(SyntaxKind.ElementAccessExpression)
9941000
export interface ElementAccessExpression extends MemberExpression {
@@ -1008,6 +1014,10 @@ namespace ts {
10081014
expression: LeftHandSideExpression;
10091015
typeArguments?: NodeArray<TypeNode>;
10101016
}
1017+
export interface SupportedExpressionWithTypeArguments extends ExpressionWithTypeArguments {
1018+
_supportedExpressionWithTypeArgumentsBrand?: any;
1019+
expression: EntityNameExpression;
1020+
}
10111021

10121022
// @kind(SyntaxKind.NewExpression)
10131023
export interface NewExpression extends CallExpression, PrimaryExpression { }
@@ -2021,7 +2031,7 @@ namespace ts {
20212031
writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
20222032
writeBaseConstructorTypeOfClass(node: ClassLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
20232033
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessibilityResult;
2024-
isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult;
2034+
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult;
20252035
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
20262036
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
20272037
getReferencedValueDeclaration(reference: Identifier): Declaration;
@@ -2030,7 +2040,7 @@ namespace ts {
20302040
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
20312041
isArgumentsLocalBinding(node: Identifier): boolean;
20322042
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile;
2033-
getTypeReferenceDirectivesForEntityName(name: EntityName | PropertyAccessExpression): string[];
2043+
getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[];
20342044
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[];
20352045
}
20362046

src/compiler/utilities.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,14 +1033,14 @@ namespace ts {
10331033
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
10341034
}
10351035

1036-
1037-
export function getEntityNameFromTypeNode(node: TypeNode): EntityName | Expression {
1036+
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
10381037
if (node) {
10391038
switch (node.kind) {
10401039
case SyntaxKind.TypeReference:
10411040
return (<TypeReferenceNode>node).typeName;
10421041
case SyntaxKind.ExpressionWithTypeArguments:
1043-
return (<ExpressionWithTypeArguments>node).expression;
1042+
Debug.assert(isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node));
1043+
return (<SupportedExpressionWithTypeArguments>node).expression;
10441044
case SyntaxKind.Identifier:
10451045
case SyntaxKind.QualifiedName:
10461046
return (<EntityName><Node>node);
@@ -2680,24 +2680,28 @@ namespace ts {
26802680
isClassLike(node.parent.parent);
26812681
}
26822682

2683-
// Returns false if this heritage clause element's expression contains something unsupported
2684-
// (i.e. not a name or dotted name).
2685-
export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): boolean {
2686-
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
2683+
export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): node is SupportedExpressionWithTypeArguments {
2684+
return isEntityNameExpression(node.expression);
26872685
}
26882686

2689-
function isSupportedExpressionWithTypeArgumentsRest(node: Expression): boolean {
2690-
if (node.kind === SyntaxKind.Identifier) {
2691-
return true;
2692-
}
2693-
else if (isPropertyAccessExpression(node)) {
2694-
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
2695-
}
2696-
else {
2697-
return false;
2687+
export function isEntityNameExpression(node: Expression): node is EntityNameExpression {
2688+
for (; ; ) {
2689+
switch (node.kind) {
2690+
case SyntaxKind.Identifier:
2691+
return true;
2692+
case SyntaxKind.PropertyAccessExpression:
2693+
node = (<PropertyAccessExpression>node).expression;
2694+
break;
2695+
default:
2696+
return false;
2697+
}
26982698
}
26992699
}
27002700

2701+
export function isPropertyAccessAnEntityNameExpression(node: PropertyAccessExpression): node is PropertyAccessEntityNameExpression {
2702+
return isEntityNameExpression(node.expression);
2703+
}
2704+
27012705
export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) {
27022706
return (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node) ||
27032707
(node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//// [exportDefaultProperty.ts]
2+
export default "".length
3+
4+
5+
//// [exportDefaultProperty.js]
6+
"use strict";
7+
exports.__esModule = true;
8+
exports["default"] = "".length;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
=== tests/cases/compiler/exportDefaultProperty.ts ===
2+
export default "".length
3+
>"".length : Symbol(String.length, Decl(lib.d.ts, --, --))
4+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
5+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
=== tests/cases/compiler/exportDefaultProperty.ts ===
2+
export default "".length
3+
>"".length : number
4+
>"" : string
5+
>length : number
6+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "".length

0 commit comments

Comments
 (0)