Skip to content

Commit 4119e45

Browse files
committed
Private Name Support in the Checker (#5)
- [x] treat private names as unique: - case 1: cannot say that a variable is of a class type unless the variable points to an instance of the class - see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesUnique.ts) - case 2: private names in class hierarchies do not conflict - see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesNoConflictWhenInheriting.ts) - [x] `#constructor` is reserved - see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNameConstructorReserved.ts) - check in `bindWorker`, where every node is visited - [x] Accessibility modifiers can't be used with private names - see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesNoAccessibilityModifiers.ts) - implemented in `checkAccessibilityModifiers`, using `ModifierFlags.AccessibilityModifier` - [x] `delete #foo` not allowed - [x] Private name accesses not allowed outside of the defining class - see test: https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNameNotAccessibleOutsideDefiningClass.ts - see [test](https://github.com/mheiber/TypeScript/tree/checker/tests/cases/conformance/classes/members/privateNames/privateNamesNoDelete.ts) - implemented in `checkDeleteExpression` - [x] Do [the right thing](https://gist.github.com/mheiber/b6fc7adb426c2e1cdaceb5d7786fc630) for nested classes mv private name tests together more checker tests for private names update naming and cleanup for check private names for private name support in the checker: - make names more consistent - remove unnecessary checks - add utility function to public API - other small cleanup Move getPropertyNameForUniqueESSymbol to utility for consistency with other calculation of special property names (starting with __), move the calculation of property names for unique es symbols to `utilities.ts`. private name tests strict+es6 Update private name tests to use 'strict' type checking and to target es6 instead of default. Makes the js output easier to read and tests more surface area with other checker features. error message for private names in obj literals Disallow decorating private-named properties because the spec is still in flux. Signed-off-by: Max Heiber <[email protected]>
1 parent 98548cc commit 4119e45

File tree

133 files changed

+3298
-62
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+3298
-62
lines changed

src/compiler/binder.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,15 @@ namespace ts {
278278
Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
279279
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
280280
}
281-
if (isPrivateName(node)) {
282-
return nodePosToString(node) as __String;
281+
if (isPrivateName(name)) {
282+
// containingClass exists because private names only allowed inside classes
283+
const containingClass = getContainingClass(name.parent);
284+
if (!containingClass) {
285+
// we're in a case where there's a private name outside a class (invalid)
286+
return undefined;
287+
}
288+
const containingClassSymbol = containingClass.symbol;
289+
return getPropertyNameForPrivateNameDescription(containingClassSymbol, name.escapedText);
283290
}
284291
return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
285292
}
@@ -337,6 +344,10 @@ namespace ts {
337344

338345
const isDefaultExport = hasModifier(node, ModifierFlags.Default);
339346

347+
// need this before getDeclarationName
348+
if (isNamedDeclaration(node)) {
349+
node.name.parent = node;
350+
}
340351
// The exported symbol for an export default function/class node is always named "default"
341352
const name = isDefaultExport && parent ? InternalSymbolName.Default : getDeclarationName(node);
342353

@@ -389,11 +400,6 @@ namespace ts {
389400
symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name));
390401
}
391402
else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) {
392-
// Assignment declarations are allowed to merge with variables, no matter what other flags they have.
393-
if (isNamedDeclaration(node)) {
394-
node.name.parent = node;
395-
}
396-
397403
// Report errors every position with duplicate declaration
398404
// Report errors on previous encountered declarations
399405
let message = symbol.flags & SymbolFlags.BlockScopedVariable
@@ -1851,6 +1857,18 @@ namespace ts {
18511857
return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode;
18521858
}
18531859

1860+
// The binder visits every node, so this is a good place to check for
1861+
// the reserved private name (there is only one)
1862+
function checkPrivateName(node: PrivateName) {
1863+
if (node.escapedText === "#constructor") {
1864+
// Report error only if there are no parse errors in file
1865+
if (!file.parseDiagnostics.length) {
1866+
file.bindDiagnostics.push(createDiagnosticForNode(node,
1867+
Diagnostics.constructor_is_a_reserved_word, declarationNameToString(node)));
1868+
}
1869+
}
1870+
}
1871+
18541872
function checkStrictModeBinaryExpression(node: BinaryExpression) {
18551873
if (inStrictMode && isLeftHandSideExpression(node.left) && isAssignmentOperator(node.operatorToken.kind)) {
18561874
// ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an
@@ -2123,6 +2141,8 @@ namespace ts {
21232141
node.flowNode = currentFlow;
21242142
}
21252143
return checkStrictModeIdentifier(<Identifier>node);
2144+
case SyntaxKind.PrivateName:
2145+
return checkPrivateName(node as PrivateName);
21262146
case SyntaxKind.PropertyAccessExpression:
21272147
case SyntaxKind.ElementAccessExpression:
21282148
if (currentFlow && isNarrowableReference(<Expression>node)) {

src/compiler/checker.ts

Lines changed: 106 additions & 36 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4521,6 +4521,35 @@
45214521
"category": "Error",
45224522
"code": 17019
45234523
},
4524+
"Accessibility modifiers cannot be used with private names.": {
4525+
"category": "Error",
4526+
"code": 17020
4527+
},
4528+
"The operand of a delete operator cannot be a private-named member.": {
4529+
"category": "Error",
4530+
"code": 17021
4531+
},
4532+
"'#constructor' is a reserved word.": {
4533+
"category": "Error",
4534+
"code": 17022
4535+
},
4536+
"Property '{0}' is not accessible outside class '{1}' because it has a private name.": {
4537+
"category": "Error",
4538+
"code": 17023
4539+
},
4540+
"This usage of '{0}' refers to the private-named member declared in its enclosing class. While type '{1}' has a private-named member with the same spelling, its declaration and accessibility are distinct.": {
4541+
"category": "Error",
4542+
"code": 17024
4543+
},
4544+
"Property '{0}' is missing in type '{1}'. While type '{1}' has a private-named member with the same spelling, its declaration and accessibility are distinct.": {
4545+
"category": "Error",
4546+
"code": 17025
4547+
},
4548+
"Private names are not allowed outside class bodies.": {
4549+
"category": "Error",
4550+
"code": 17026
4551+
},
4552+
45244553

45254554
"Circularity detected while resolving configuration: {0}": {
45264555
"category": "Error",
@@ -4538,7 +4567,6 @@
45384567
"category": "Error",
45394568
"code": 18003
45404569
},
4541-
45424570
"File is a CommonJS module; it may be converted to an ES6 module.": {
45434571
"category": "Suggestion",
45444572
"code": 80001

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3107,6 +3107,7 @@ namespace ts {
31073107
getDeclaredTypeOfSymbol(symbol: Symbol): Type;
31083108
getPropertiesOfType(type: Type): Symbol[];
31093109
getPropertyOfType(type: Type, propertyName: string): Symbol | undefined;
3110+
getPropertyForPrivateName(apparentType: Type, leftType: Type, right: PrivateName, errorNode: Node | undefined): Symbol | undefined;
31103111
/* @internal */ getTypeOfPropertyOfType(type: Type, propertyName: string): Type | undefined;
31113112
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined;
31123113
getSignaturesOfType(type: Type, kind: SignatureKind): ReadonlyArray<Signature>;

src/compiler/utilities.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,10 @@ namespace ts {
15481548
export function nodeCanBeDecorated(node: ClassElement, parent: Node): boolean;
15491549
export function nodeCanBeDecorated(node: Node, parent: Node, grandparent: Node): boolean;
15501550
export function nodeCanBeDecorated(node: Node, parent?: Node, grandparent?: Node): boolean {
1551+
// private names cannot be used with decorators yet
1552+
if (isNamedDeclaration(node) && isPrivateName(node.name)) {
1553+
return false;
1554+
}
15511555
switch (node.kind) {
15521556
case SyntaxKind.ClassDeclaration:
15531557
// classes are valid targets
@@ -2792,10 +2796,18 @@ namespace ts {
27922796
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateName ? node.escapedText : escapeLeadingUnderscores(node.text);
27932797
}
27942798

2799+
export function getPropertyNameForUniqueESSymbol(symbol: Symbol): __String {
2800+
return `__@${getSymbolId(symbol)}@${symbol.escapedName}` as __String;
2801+
}
2802+
27952803
export function getPropertyNameForKnownSymbolName(symbolName: string): __String {
27962804
return "__@" + symbolName as __String;
27972805
}
27982806

2807+
export function getPropertyNameForPrivateNameDescription(containingClassSymbol: Symbol, description: __String): __String {
2808+
return `__#${getSymbolId(containingClassSymbol)}@${description}` as __String;
2809+
}
2810+
27992811
export function isKnownSymbol(symbol: Symbol): boolean {
28002812
return startsWith(symbol.escapedName as string, "__@");
28012813
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,6 +1929,7 @@ declare namespace ts {
19291929
getDeclaredTypeOfSymbol(symbol: Symbol): Type;
19301930
getPropertiesOfType(type: Type): Symbol[];
19311931
getPropertyOfType(type: Type, propertyName: string): Symbol | undefined;
1932+
getPropertyForPrivateName(apparentType: Type, leftType: Type, right: PrivateName, errorNode: Node | undefined): Symbol | undefined;
19321933
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined;
19331934
getSignaturesOfType(type: Type, kind: SignatureKind): ReadonlyArray<Signature>;
19341935
getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,6 +1929,7 @@ declare namespace ts {
19291929
getDeclaredTypeOfSymbol(symbol: Symbol): Type;
19301930
getPropertiesOfType(type: Type): Symbol[];
19311931
getPropertyOfType(type: Type, propertyName: string): Symbol | undefined;
1932+
getPropertyForPrivateName(apparentType: Type, leftType: Type, right: PrivateName, errorNode: Node | undefined): Symbol | undefined;
19321933
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined;
19331934
getSignaturesOfType(type: Type, kind: SignatureKind): ReadonlyArray<Signature>;
19341935
getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
EmitSkipped: false
2+
FileName : ./dist/index.js
3+
"use strict";
4+
Object.defineProperty(exports, "__esModule", { value: true });
5+
var Foo = /** @class */ (function () {
6+
function Foo() {
7+
}
8+
Foo.prototype.methodName = function (propName) { };
9+
Foo.prototype.otherMethod = function () {
10+
if (Math.random() > 0.5) {
11+
return { x: 42 };
12+
}
13+
return { y: "yes" };
14+
};
15+
return Foo;
16+
}());
17+
exports.Foo = Foo;
18+
FileName : ./dist/index.d.ts.map
19+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../tests/cases/fourslash/index.ts"],"names":[],"mappings":"AAAA,qBAAa,GAAG;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IACpC,WAAW;;;;;;;CAMd;AAED,MAAM,WAAW,QAAQ;IACrB,MAAM,EAAE,MAAM,CAAC;CAClB"}FileName : ./dist/index.d.ts
20+
export declare class Foo {
21+
member: string;
22+
methodName(propName: SomeType): void;
23+
otherMethod(): {
24+
x: number;
25+
y?: undefined;
26+
} | {
27+
y: string;
28+
x?: undefined;
29+
};
30+
}
31+
export interface SomeType {
32+
member: number;
33+
}
34+
//# sourceMappingURL=index.d.ts.map
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
EmitSkipped: false
2+
FileName : ./dist/index.js.map
3+
{"version":3,"file":"index.js","sourceRoot":"/tests/cases/fourslash/","sources":["index.ts"],"names":[],"mappings":";;AAAA;IAAA;IASA,CAAC;IAPG,wBAAU,GAAV,UAAW,QAAkB,IAAS,CAAC;IACvC,yBAAW,GAAX;QACI,IAAI,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE;YACrB,OAAO,EAAC,CAAC,EAAE,EAAE,EAAC,CAAC;SAClB;QACD,OAAO,EAAC,CAAC,EAAE,KAAK,EAAC,CAAC;IACtB,CAAC;IACL,UAAC;AAAD,CAAC,AATD,IASC;AATY,kBAAG"}FileName : ./dist/index.js
4+
"use strict";
5+
Object.defineProperty(exports, "__esModule", { value: true });
6+
var Foo = /** @class */ (function () {
7+
function Foo() {
8+
}
9+
Foo.prototype.methodName = function (propName) { };
10+
Foo.prototype.otherMethod = function () {
11+
if (Math.random() > 0.5) {
12+
return { x: 42 };
13+
}
14+
return { y: "yes" };
15+
};
16+
return Foo;
17+
}());
18+
exports.Foo = Foo;
19+
//# sourceMappingURL=index.js.mapFileName : ./dist/index.d.ts.map
20+
{"version":3,"file":"index.d.ts","sourceRoot":"/tests/cases/fourslash/","sources":["index.ts"],"names":[],"mappings":"AAAA,qBAAa,GAAG;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IACpC,WAAW;;;;;;;CAMd;AAED,MAAM,WAAW,QAAQ;IACrB,MAAM,EAAE,MAAM,CAAC;CAClB"}FileName : ./dist/index.d.ts
21+
export declare class Foo {
22+
member: string;
23+
methodName(propName: SomeType): void;
24+
otherMethod(): {
25+
x: number;
26+
y?: undefined;
27+
} | {
28+
y: string;
29+
x?: undefined;
30+
};
31+
}
32+
export interface SomeType {
33+
member: number;
34+
}
35+
//# sourceMappingURL=index.d.ts.map
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
EmitSkipped: false
2+
FileName : ./dist/index.js
3+
"use strict";
4+
Object.defineProperty(exports, "__esModule", { value: true });
5+
var Foo = /** @class */ (function () {
6+
function Foo() {
7+
}
8+
Foo.prototype.methodName = function (propName) { };
9+
Foo.prototype.otherMethod = function () {
10+
if (Math.random() > 0.5) {
11+
return { x: 42 };
12+
}
13+
return { y: "yes" };
14+
};
15+
return Foo;
16+
}());
17+
exports.Foo = Foo;
18+
FileName : ./dist/index.d.ts.map
19+
{"version":3,"file":"index.d.ts","sourceRoot":"/tests/cases/fourslash/","sources":["index.ts"],"names":[],"mappings":"AAAA,qBAAa,GAAG;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IACpC,WAAW;;;;;;;CAMd;AAED,MAAM,WAAW,QAAQ;IACrB,MAAM,EAAE,MAAM,CAAC;CAClB"}FileName : ./dist/index.d.ts
20+
export declare class Foo {
21+
member: string;
22+
methodName(propName: SomeType): void;
23+
otherMethod(): {
24+
x: number;
25+
y?: undefined;
26+
} | {
27+
y: string;
28+
x?: undefined;
29+
};
30+
}
31+
export interface SomeType {
32+
member: number;
33+
}
34+
//# sourceMappingURL=index.d.ts.map

0 commit comments

Comments
 (0)