Skip to content

Commit 98603f9

Browse files
committed
Merge pull request #6532 from Microsoft/readonlyMembers
Readonly properties and index signatures
2 parents fd879bb + cbb195b commit 98603f9

File tree

66 files changed

+1397
-812
lines changed

Some content is hidden

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

66 files changed

+1397
-812
lines changed

src/compiler/checker.ts

Lines changed: 267 additions & 212 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
"category": "Error",
6868
"code": 1023
6969
},
70+
"'readonly' modifier can only appear on a property declaration or index signature.": {
71+
"category": "Error",
72+
"code": 1024
73+
},
7074
"Accessibility modifier already seen.": {
7175
"category": "Error",
7276
"code": 1028
@@ -203,6 +207,14 @@
203207
"category": "Error",
204208
"code": 1068
205209
},
210+
"'{0}' modifier cannot appear on a type member.": {
211+
"category": "Error",
212+
"code": 1070
213+
},
214+
"'{0}' modifier cannot appear on an index signature.": {
215+
"category": "Error",
216+
"code": 1071
217+
},
206218
"A '{0}' modifier cannot be used with an import declaration.": {
207219
"category": "Error",
208220
"code": 1079
@@ -423,10 +435,6 @@
423435
"category": "Error",
424436
"code": 1144
425437
},
426-
"Modifiers not permitted on index signature members.": {
427-
"category": "Error",
428-
"code": 1145
429-
},
430438
"Declaration expected.": {
431439
"category": "Error",
432440
"code": 1146
@@ -1379,11 +1387,11 @@
13791387
"category": "Error",
13801388
"code": 2448
13811389
},
1382-
"The operand of an increment or decrement operator cannot be a constant.": {
1390+
"The operand of an increment or decrement operator cannot be a constant or a read-only property.": {
13831391
"category": "Error",
13841392
"code": 2449
13851393
},
1386-
"Left-hand side of assignment expression cannot be a constant.": {
1394+
"Left-hand side of assignment expression cannot be a constant or a read-only property.": {
13871395
"category": "Error",
13881396
"code": 2450
13891397
},
@@ -1515,11 +1523,11 @@
15151523
"category": "Error",
15161524
"code": 2484
15171525
},
1518-
"The left-hand side of a 'for...of' statement cannot be a previously defined constant.": {
1526+
"The left-hand side of a 'for...of' statement cannot be a constant or a read-only property.": {
15191527
"category": "Error",
15201528
"code": 2485
15211529
},
1522-
"The left-hand side of a 'for...in' statement cannot be a previously defined constant.": {
1530+
"The left-hand side of a 'for...in' statement cannot be a constant or a read-only property.": {
15231531
"category": "Error",
15241532
"code": 2486
15251533
},

src/compiler/parser.ts

Lines changed: 44 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,7 +1172,7 @@ namespace ts {
11721172
case ParsingContext.SwitchClauses:
11731173
return token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword;
11741174
case ParsingContext.TypeMembers:
1175-
return isStartOfTypeMember();
1175+
return lookAhead(isTypeMemberStart);
11761176
case ParsingContext.ClassMembers:
11771177
// We allow semicolons as class elements (as specified by ES6) as long as we're
11781178
// not in error recovery. If we're in error recovery, we don't want an errant
@@ -2214,13 +2214,13 @@ namespace ts {
22142214
return finishNode(node);
22152215
}
22162216

2217-
function parsePropertyOrMethodSignature(): PropertySignature | MethodSignature {
2218-
const fullStart = scanner.getStartPos();
2217+
function parsePropertyOrMethodSignature(fullStart: number, modifiers: ModifiersArray): PropertySignature | MethodSignature {
22192218
const name = parsePropertyName();
22202219
const questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
22212220

22222221
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
22232222
const method = <MethodSignature>createNode(SyntaxKind.MethodSignature, fullStart);
2223+
setModifiers(method, modifiers);
22242224
method.name = name;
22252225
method.questionToken = questionToken;
22262226

@@ -2232,6 +2232,7 @@ namespace ts {
22322232
}
22332233
else {
22342234
const property = <PropertySignature>createNode(SyntaxKind.PropertySignature, fullStart);
2235+
setModifiers(property, modifiers);
22352236
property.name = name;
22362237
property.questionToken = questionToken;
22372238
property.type = parseTypeAnnotation();
@@ -2248,86 +2249,51 @@ namespace ts {
22482249
}
22492250
}
22502251

2251-
function isStartOfTypeMember(): boolean {
2252-
switch (token) {
2253-
case SyntaxKind.OpenParenToken:
2254-
case SyntaxKind.LessThanToken:
2255-
case SyntaxKind.OpenBracketToken: // Both for indexers and computed properties
2256-
return true;
2257-
default:
2258-
if (isModifierKind(token)) {
2259-
const result = lookAhead(isStartOfIndexSignatureDeclaration);
2260-
if (result) {
2261-
return result;
2262-
}
2263-
}
2264-
2265-
return isLiteralPropertyName() && lookAhead(isTypeMemberWithLiteralPropertyName);
2252+
function isTypeMemberStart(): boolean {
2253+
let idToken: SyntaxKind;
2254+
// Return true if we have the start of a signature member
2255+
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
2256+
return true;
22662257
}
2267-
}
2268-
2269-
function isStartOfIndexSignatureDeclaration() {
2258+
// Eat up all modifiers, but hold on to the last one in case it is actually an identifier
22702259
while (isModifierKind(token)) {
2260+
idToken = token;
22712261
nextToken();
22722262
}
2273-
2274-
return isIndexSignature();
2275-
}
2276-
2277-
function isTypeMemberWithLiteralPropertyName() {
2278-
nextToken();
2279-
return token === SyntaxKind.OpenParenToken ||
2280-
token === SyntaxKind.LessThanToken ||
2281-
token === SyntaxKind.QuestionToken ||
2282-
token === SyntaxKind.ColonToken ||
2283-
canParseSemicolon();
2263+
// Index signatures and computed property names are type members
2264+
if (token === SyntaxKind.OpenBracketToken) {
2265+
return true;
2266+
}
2267+
// Try to get the first property-like token following all modifiers
2268+
if (isLiteralPropertyName()) {
2269+
idToken = token;
2270+
nextToken();
2271+
}
2272+
// If we were able to get any potential identifier, check that it is
2273+
// the start of a member declaration
2274+
if (idToken) {
2275+
return token === SyntaxKind.OpenParenToken ||
2276+
token === SyntaxKind.LessThanToken ||
2277+
token === SyntaxKind.QuestionToken ||
2278+
token === SyntaxKind.ColonToken ||
2279+
canParseSemicolon();
2280+
}
2281+
return false;
22842282
}
22852283

22862284
function parseTypeMember(): TypeElement {
2287-
switch (token) {
2288-
case SyntaxKind.OpenParenToken:
2289-
case SyntaxKind.LessThanToken:
2290-
return parseSignatureMember(SyntaxKind.CallSignature);
2291-
case SyntaxKind.OpenBracketToken:
2292-
// Indexer or computed property
2293-
return isIndexSignature()
2294-
? parseIndexSignatureDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined)
2295-
: parsePropertyOrMethodSignature();
2296-
case SyntaxKind.NewKeyword:
2297-
if (lookAhead(isStartOfConstructSignature)) {
2298-
return parseSignatureMember(SyntaxKind.ConstructSignature);
2299-
}
2300-
// fall through.
2301-
case SyntaxKind.StringLiteral:
2302-
case SyntaxKind.NumericLiteral:
2303-
return parsePropertyOrMethodSignature();
2304-
default:
2305-
// Index declaration as allowed as a type member. But as per the grammar,
2306-
// they also allow modifiers. So we have to check for an index declaration
2307-
// that might be following modifiers. This ensures that things work properly
2308-
// when incrementally parsing as the parser will produce the Index declaration
2309-
// if it has the same text regardless of whether it is inside a class or an
2310-
// object type.
2311-
if (isModifierKind(token)) {
2312-
const result = tryParse(parseIndexSignatureWithModifiers);
2313-
if (result) {
2314-
return result;
2315-
}
2316-
}
2317-
2318-
if (tokenIsIdentifierOrKeyword(token)) {
2319-
return parsePropertyOrMethodSignature();
2320-
}
2285+
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
2286+
return parseSignatureMember(SyntaxKind.CallSignature);
23212287
}
2322-
}
2323-
2324-
function parseIndexSignatureWithModifiers() {
2325-
const fullStart = scanner.getStartPos();
2326-
const decorators = parseDecorators();
2288+
if (token === SyntaxKind.NewKeyword && lookAhead(isStartOfConstructSignature)) {
2289+
return parseSignatureMember(SyntaxKind.ConstructSignature);
2290+
}
2291+
const fullStart = getNodePos();
23272292
const modifiers = parseModifiers();
2328-
return isIndexSignature()
2329-
? parseIndexSignatureDeclaration(fullStart, decorators, modifiers)
2330-
: undefined;
2293+
if (isIndexSignature()) {
2294+
return parseIndexSignatureDeclaration(fullStart, /*decorators*/ undefined, modifiers);
2295+
}
2296+
return parsePropertyOrMethodSignature(fullStart, modifiers);
23312297
}
23322298

23332299
function isStartOfConstructSignature() {
@@ -4404,6 +4370,7 @@ namespace ts {
44044370
case SyntaxKind.PrivateKeyword:
44054371
case SyntaxKind.ProtectedKeyword:
44064372
case SyntaxKind.PublicKeyword:
4373+
case SyntaxKind.ReadonlyKeyword:
44074374
nextToken();
44084375
// ASI takes effect for this modifier.
44094376
if (scanner.hasPrecedingLineBreak()) {
@@ -4486,6 +4453,7 @@ namespace ts {
44864453
case SyntaxKind.PrivateKeyword:
44874454
case SyntaxKind.ProtectedKeyword:
44884455
case SyntaxKind.StaticKeyword:
4456+
case SyntaxKind.ReadonlyKeyword:
44894457
// When these don't start a declaration, they may be the start of a class member if an identifier
44904458
// immediately follows. Otherwise they're an identifier in an expression statement.
44914459
return isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine);
@@ -4567,6 +4535,7 @@ namespace ts {
45674535
case SyntaxKind.PublicKeyword:
45684536
case SyntaxKind.AbstractKeyword:
45694537
case SyntaxKind.StaticKeyword:
4538+
case SyntaxKind.ReadonlyKeyword:
45704539
case SyntaxKind.GlobalKeyword:
45714540
if (isStartOfDeclaration()) {
45724541
return parseDeclaration();
@@ -4855,6 +4824,7 @@ namespace ts {
48554824
case SyntaxKind.PrivateKeyword:
48564825
case SyntaxKind.ProtectedKeyword:
48574826
case SyntaxKind.StaticKeyword:
4827+
case SyntaxKind.ReadonlyKeyword:
48584828
return true;
48594829
default:
48604830
return false;

src/compiler/program.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ namespace ts {
857857
case SyntaxKind.PublicKeyword:
858858
case SyntaxKind.PrivateKeyword:
859859
case SyntaxKind.ProtectedKeyword:
860+
case SyntaxKind.ReadonlyKeyword:
860861
case SyntaxKind.DeclareKeyword:
861862
diagnostics.push(createDiagnosticForNode(modifier, Diagnostics._0_can_only_be_used_in_a_ts_file, tokenToString(modifier.kind)));
862863
return true;

src/compiler/scanner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ namespace ts {
9898
"private": SyntaxKind.PrivateKeyword,
9999
"protected": SyntaxKind.ProtectedKeyword,
100100
"public": SyntaxKind.PublicKeyword,
101+
"readonly": SyntaxKind.ReadonlyKeyword,
101102
"require": SyntaxKind.RequireKeyword,
102103
"global": SyntaxKind.GlobalKeyword,
103104
"return": SyntaxKind.ReturnKeyword,

src/compiler/types.ts

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ namespace ts {
163163
IsKeyword,
164164
ModuleKeyword,
165165
NamespaceKeyword,
166+
ReadonlyKeyword,
166167
RequireKeyword,
167168
NumberKeyword,
168169
SetKeyword,
@@ -369,32 +370,33 @@ namespace ts {
369370
}
370371

371372
export const enum NodeFlags {
372-
None = 0,
373-
Export = 1 << 1, // Declarations
374-
Ambient = 1 << 2, // Declarations
375-
Public = 1 << 3, // Property/Method
376-
Private = 1 << 4, // Property/Method
377-
Protected = 1 << 5, // Property/Method
378-
Static = 1 << 6, // Property/Method
379-
Abstract = 1 << 7, // Class/Method/ConstructSignature
380-
Async = 1 << 8, // Property/Method/Function
381-
Default = 1 << 9, // Function/Class (export default declaration)
382-
MultiLine = 1 << 10, // Multi-line array or object literal
383-
Synthetic = 1 << 11, // Synthetic node (for full fidelity)
384-
DeclarationFile = 1 << 12, // Node is a .d.ts file
385-
Let = 1 << 13, // Variable declaration
386-
Const = 1 << 14, // Variable declaration
387-
OctalLiteral = 1 << 15, // Octal numeric literal
388-
Namespace = 1 << 16, // Namespace declaration
389-
ExportContext = 1 << 17, // Export context (initialized by binding)
390-
ContainsThis = 1 << 18, // Interface contains references to "this"
391-
HasImplicitReturn = 1 << 19, // If function implicitly returns on one of codepaths (initialized by binding)
392-
HasExplicitReturn = 1 << 20, // If function has explicit reachable return on one of codepaths (initialized by binding)
393-
GlobalAugmentation = 1 << 21, // Set if module declaration is an augmentation for the global scope
394-
HasClassExtends = 1 << 22, // If the file has a non-ambient class with an extends clause in ES5 or lower (initialized by binding)
395-
HasDecorators = 1 << 23, // If the file has decorators (initialized by binding)
396-
HasParamDecorators = 1 << 24, // If the file has parameter decorators (initialized by binding)
397-
HasAsyncFunctions = 1 << 25, // If the file has async functions (initialized by binding)
373+
None = 0,
374+
Export = 1 << 0, // Declarations
375+
Ambient = 1 << 1, // Declarations
376+
Public = 1 << 2, // Property/Method
377+
Private = 1 << 3, // Property/Method
378+
Protected = 1 << 4, // Property/Method
379+
Static = 1 << 5, // Property/Method
380+
Readonly = 1 << 6, // Property/Method
381+
Abstract = 1 << 7, // Class/Method/ConstructSignature
382+
Async = 1 << 8, // Property/Method/Function
383+
Default = 1 << 9, // Function/Class (export default declaration)
384+
MultiLine = 1 << 10, // Multi-line array or object literal
385+
Synthetic = 1 << 11, // Synthetic node (for full fidelity)
386+
DeclarationFile = 1 << 12, // Node is a .d.ts file
387+
Let = 1 << 13, // Variable declaration
388+
Const = 1 << 14, // Variable declaration
389+
OctalLiteral = 1 << 15, // Octal numeric literal
390+
Namespace = 1 << 16, // Namespace declaration
391+
ExportContext = 1 << 17, // Export context (initialized by binding)
392+
ContainsThis = 1 << 18, // Interface contains references to "this"
393+
HasImplicitReturn = 1 << 19, // If function implicitly returns on one of codepaths (initialized by binding)
394+
HasExplicitReturn = 1 << 20, // If function has explicit reachable return on one of codepaths (initialized by binding)
395+
GlobalAugmentation = 1 << 21, // Set if module declaration is an augmentation for the global scope
396+
HasClassExtends = 1 << 22, // If the file has a non-ambient class with an extends clause in ES5 or lower (initialized by binding)
397+
HasDecorators = 1 << 23, // If the file has decorators (initialized by binding)
398+
HasParamDecorators = 1 << 24, // If the file has parameter decorators (initialized by binding)
399+
HasAsyncFunctions = 1 << 25, // If the file has async functions (initialized by binding)
398400

399401
Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async,
400402
AccessibilityModifier = Public | Private | Protected,
@@ -2075,6 +2077,7 @@ namespace ts {
20752077
resolvedAwaitedType?: Type; // Cached awaited type of type node
20762078
resolvedSignature?: Signature; // Cached signature of signature node or call expression
20772079
resolvedSymbol?: Symbol; // Cached name resolution result
2080+
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
20782081
flags?: NodeCheckFlags; // Set of flags specific to Node
20792082
enumMemberValue?: number; // Constant value of enum member
20802083
isVisible?: boolean; // Is this node visible
@@ -2182,8 +2185,8 @@ namespace ts {
21822185
declaredProperties: Symbol[]; // Declared members
21832186
declaredCallSignatures: Signature[]; // Declared call signatures
21842187
declaredConstructSignatures: Signature[]; // Declared construct signatures
2185-
declaredStringIndexType: Type; // Declared string index type
2186-
declaredNumberIndexType: Type; // Declared numeric index type
2188+
declaredStringIndexInfo: IndexInfo; // Declared string indexing info
2189+
declaredNumberIndexInfo: IndexInfo; // Declared numeric indexing info
21872190
}
21882191

21892192
// Type references (TypeFlags.Reference). When a class or interface has type parameters or
@@ -2235,8 +2238,8 @@ namespace ts {
22352238
properties: Symbol[]; // Properties
22362239
callSignatures: Signature[]; // Call signatures of type
22372240
constructSignatures: Signature[]; // Construct signatures of type
2238-
stringIndexType?: Type; // String index type
2239-
numberIndexType?: Type; // Numeric index type
2241+
stringIndexInfo?: IndexInfo; // String indexing info
2242+
numberIndexInfo?: IndexInfo; // Numeric indexing info
22402243
}
22412244

22422245
/* @internal */
@@ -2299,6 +2302,12 @@ namespace ts {
22992302
Number,
23002303
}
23012304

2305+
export interface IndexInfo {
2306+
type: Type;
2307+
isReadonly: boolean;
2308+
declaration?: SignatureDeclaration;
2309+
}
2310+
23022311
/* @internal */
23032312
export interface TypeMapper {
23042313
(t: TypeParameter): Type;

src/compiler/utilities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,7 @@ namespace ts {
15921592
case SyntaxKind.PublicKeyword:
15931593
case SyntaxKind.PrivateKeyword:
15941594
case SyntaxKind.ProtectedKeyword:
1595+
case SyntaxKind.ReadonlyKeyword:
15951596
case SyntaxKind.StaticKeyword:
15961597
return true;
15971598
}
@@ -2323,6 +2324,7 @@ namespace ts {
23232324
case SyntaxKind.ConstKeyword: return NodeFlags.Const;
23242325
case SyntaxKind.DefaultKeyword: return NodeFlags.Default;
23252326
case SyntaxKind.AsyncKeyword: return NodeFlags.Async;
2327+
case SyntaxKind.ReadonlyKeyword: return NodeFlags.Readonly;
23262328
}
23272329
return 0;
23282330
}

0 commit comments

Comments
 (0)