Allow modifies on type members + introduce "readonly" modifier

This commit is contained in:
Anders Hejlsberg 2016-01-13 18:34:38 -08:00
parent 911d07a81b
commit b46efc9b55
7 changed files with 107 additions and 94 deletions

View file

@ -15675,9 +15675,17 @@ namespace ts {
return;
}
let lastStatic: Node, lastPrivate: Node, lastProtected: Node, lastDeclare: Node, lastAsync: Node;
let lastStatic: Node, lastPrivate: Node, lastProtected: Node, lastDeclare: Node, lastAsync: Node, lastReadonly: Node;
let flags = 0;
for (const modifier of node.modifiers) {
if (modifier.kind !== SyntaxKind.ReadonlyKeyword) {
if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind));
}
if (node.kind === SyntaxKind.IndexSignature) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind));
}
}
switch (modifier.kind) {
case SyntaxKind.ConstKeyword:
if (node.kind !== SyntaxKind.EnumDeclaration && node.parent.kind === SyntaxKind.ClassDeclaration) {
@ -15706,6 +15714,9 @@ namespace ts {
else if (flags & NodeFlags.Static) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static");
}
else if (flags & NodeFlags.Readonly) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly");
}
else if (flags & NodeFlags.Async) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async");
}
@ -15727,6 +15738,9 @@ namespace ts {
if (flags & NodeFlags.Static) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static");
}
else if (flags & NodeFlags.Readonly) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly");
}
else if (flags & NodeFlags.Async) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async");
}
@ -15743,6 +15757,20 @@ namespace ts {
lastStatic = modifier;
break;
case SyntaxKind.ReadonlyKeyword:
if (flags & NodeFlags.Readonly) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly");
}
else if (flags & NodeFlags.Async) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "readonly", "async");
}
else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_element, "readonly");
}
flags |= NodeFlags.Readonly;
lastReadonly = modifier;
break;
case SyntaxKind.ExportKeyword:
if (flags & NodeFlags.Export) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export");
@ -15839,6 +15867,9 @@ namespace ts {
else if (flags & NodeFlags.Async) {
return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async");
}
else if (flags & NodeFlags.Readonly) {
return grammarErrorOnNode(lastReadonly, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly");
}
return;
}
else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & NodeFlags.Ambient) {
@ -15984,15 +16015,9 @@ namespace ts {
}
}
function checkGrammarForIndexSignatureModifier(node: SignatureDeclaration): void {
if (node.flags & NodeFlags.Modifier) {
grammarErrorOnFirstToken(node, Diagnostics.Modifiers_not_permitted_on_index_signature_members);
}
}
function checkGrammarIndexSignature(node: SignatureDeclaration) {
// Prevent cascading error by short-circuit
return checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarIndexSignatureParameters(node) || checkGrammarForIndexSignatureModifier(node);
return checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarIndexSignatureParameters(node);
}
function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray<TypeNode>): boolean {

View file

@ -203,6 +203,14 @@
"category": "Error",
"code": 1068
},
"'{0}' modifier cannot appear on a type member.": {
"category": "Error",
"code": 1070
},
"'{0}' modifier cannot appear on an index signature.": {
"category": "Error",
"code": 1071
},
"A '{0}' modifier cannot be used with an import declaration.": {
"category": "Error",
"code": 1079
@ -423,10 +431,6 @@
"category": "Error",
"code": 1144
},
"Modifiers not permitted on index signature members.": {
"category": "Error",
"code": 1145
},
"Declaration expected.": {
"category": "Error",
"code": 1146

View file

@ -1192,7 +1192,7 @@ namespace ts {
case ParsingContext.SwitchClauses:
return token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword;
case ParsingContext.TypeMembers:
return isStartOfTypeMember();
return lookAhead(isTypeMemberStart);
case ParsingContext.ClassMembers:
// We allow semicolons as class elements (as specified by ES6) as long as we're
// not in error recovery. If we're in error recovery, we don't want an errant
@ -2233,13 +2233,13 @@ namespace ts {
return finishNode(node);
}
function parsePropertyOrMethodSignature(): PropertySignature | MethodSignature {
const fullStart = scanner.getStartPos();
function parsePropertyOrMethodSignature(fullStart: number, modifiers: ModifiersArray): PropertySignature | MethodSignature {
const name = parsePropertyName();
const questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
const method = <MethodSignature>createNode(SyntaxKind.MethodSignature, fullStart);
setModifiers(method, modifiers);
method.name = name;
method.questionToken = questionToken;
@ -2251,6 +2251,7 @@ namespace ts {
}
else {
const property = <PropertySignature>createNode(SyntaxKind.PropertySignature, fullStart);
setModifiers(property, modifiers);
property.name = name;
property.questionToken = questionToken;
property.type = parseTypeAnnotation();
@ -2267,77 +2268,51 @@ namespace ts {
}
}
function isStartOfTypeMember(): boolean {
switch (token) {
case SyntaxKind.OpenParenToken:
case SyntaxKind.LessThanToken:
case SyntaxKind.OpenBracketToken: // Both for indexers and computed properties
return true;
default:
if (isModifierKind(token)) {
const result = lookAhead(isStartOfIndexSignatureDeclaration);
if (result) {
return result;
}
}
return isLiteralPropertyName() && lookAhead(isTypeMemberWithLiteralPropertyName);
function isTypeMemberStart(): boolean {
let idToken: SyntaxKind;
// Return true if we have the start of a signature member
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
return true;
}
}
function isStartOfIndexSignatureDeclaration() {
// Eat up all modifiers, but hold on to the last one in case it is actually an identifier
while (isModifierKind(token)) {
idToken = token;
nextToken();
}
return isIndexSignature();
}
function isTypeMemberWithLiteralPropertyName() {
nextToken();
return token === SyntaxKind.OpenParenToken ||
token === SyntaxKind.LessThanToken ||
token === SyntaxKind.QuestionToken ||
token === SyntaxKind.ColonToken ||
canParseSemicolon();
// Index signatures and computed property names are type members
if (token === SyntaxKind.OpenBracketToken) {
return true;
}
// Try to get the first property-like token following all modifiers
if (isLiteralPropertyName()) {
idToken = token;
nextToken();
}
// If we were able to get any potential identifier, check that it is
// the start of a member declaration
if (idToken) {
return token === SyntaxKind.OpenParenToken ||
token === SyntaxKind.LessThanToken ||
token === SyntaxKind.QuestionToken ||
token === SyntaxKind.ColonToken ||
canParseSemicolon();
}
return false;
}
function parseTypeMember(): TypeElement {
switch (token) {
case SyntaxKind.OpenParenToken:
case SyntaxKind.LessThanToken:
return parseSignatureMember(SyntaxKind.CallSignature);
case SyntaxKind.OpenBracketToken:
// Indexer or computed property
return isIndexSignature()
? parseIndexSignatureDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined)
: parsePropertyOrMethodSignature();
case SyntaxKind.NewKeyword:
if (lookAhead(isStartOfConstructSignature)) {
return parseSignatureMember(SyntaxKind.ConstructSignature);
}
// fall through.
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
return parsePropertyOrMethodSignature();
default:
// Index declaration as allowed as a type member. But as per the grammar,
// they also allow modifiers. So we have to check for an index declaration
// that might be following modifiers. This ensures that things work properly
// when incrementally parsing as the parser will produce the Index declaration
// if it has the same text regardless of whether it is inside a class or an
// object type.
if (isModifierKind(token)) {
const result = tryParse(parseIndexSignatureWithModifiers);
if (result) {
return result;
}
}
if (tokenIsIdentifierOrKeyword(token)) {
return parsePropertyOrMethodSignature();
}
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
return parseSignatureMember(SyntaxKind.CallSignature);
}
if (token === SyntaxKind.NewKeyword && lookAhead(isStartOfConstructSignature)) {
return parseSignatureMember(SyntaxKind.ConstructSignature);
}
const fullStart = getNodePos();
const modifiers = parseModifiers();
if (isIndexSignature()) {
return parseIndexSignatureDeclaration(fullStart, /*decorators*/ undefined, modifiers);
}
return parsePropertyOrMethodSignature(fullStart, modifiers);
}
function parseIndexSignatureWithModifiers() {
@ -4423,6 +4398,7 @@ namespace ts {
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.ReadonlyKeyword:
nextToken();
// ASI takes effect for this modifier.
if (scanner.hasPrecedingLineBreak()) {
@ -4501,6 +4477,7 @@ namespace ts {
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.ReadonlyKeyword:
// When these don't start a declaration, they may be the start of a class member if an identifier
// immediately follows. Otherwise they're an identifier in an expression statement.
return isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine);
@ -4582,6 +4559,7 @@ namespace ts {
case SyntaxKind.PublicKeyword:
case SyntaxKind.AbstractKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.ReadonlyKeyword:
if (isStartOfDeclaration()) {
return parseDeclaration();
}
@ -4868,6 +4846,7 @@ namespace ts {
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.ReadonlyKeyword:
return true;
default:
return false;

View file

@ -836,6 +836,7 @@ namespace ts {
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.DeclareKeyword:
diagnostics.push(createDiagnosticForNode(modifier, Diagnostics._0_can_only_be_used_in_a_ts_file, tokenToString(modifier.kind)));
return true;

View file

@ -93,6 +93,7 @@ namespace ts {
"private": SyntaxKind.PrivateKeyword,
"protected": SyntaxKind.ProtectedKeyword,
"public": SyntaxKind.PublicKeyword,
"readonly": SyntaxKind.ReadonlyKeyword,
"require": SyntaxKind.RequireKeyword,
"return": SyntaxKind.ReturnKeyword,
"set": SyntaxKind.SetKeyword,

View file

@ -163,6 +163,7 @@ namespace ts {
IsKeyword,
ModuleKeyword,
NamespaceKeyword,
ReadonlyKeyword,
RequireKeyword,
NumberKeyword,
SetKeyword,
@ -375,24 +376,24 @@ namespace ts {
Private = 1 << 4, // Property/Method
Protected = 1 << 5, // Property/Method
Static = 1 << 6, // Property/Method
Abstract = 1 << 7, // Class/Method/ConstructSignature
Async = 1 << 8, // Property/Method/Function
Default = 1 << 9, // Function/Class (export default declaration)
MultiLine = 1 << 10, // Multi-line array or object literal
Synthetic = 1 << 11, // Synthetic node (for full fidelity)
DeclarationFile = 1 << 12, // Node is a .d.ts file
Let = 1 << 13, // Variable declaration
Const = 1 << 14, // Variable declaration
OctalLiteral = 1 << 15, // Octal numeric literal
Namespace = 1 << 16, // Namespace declaration
ExportContext = 1 << 17, // Export context (initialized by binding)
ContainsThis = 1 << 18, // Interface contains references to "this"
HasImplicitReturn = 1 << 19, // If function implicitly returns on one of codepaths (initialized by binding)
HasExplicitReturn = 1 << 20, // If function has explicit reachable return on one of codepaths (initialized by binding)
Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async,
Readonly = 1 << 7, // Property/Method
Abstract = 1 << 8, // Class/Method/ConstructSignature
Async = 1 << 9, // Property/Method/Function
Default = 1 << 10, // Function/Class (export default declaration)
MultiLine = 1 << 11, // Multi-line array or object literal
Synthetic = 1 << 12, // Synthetic node (for full fidelity)
DeclarationFile = 1 << 13, // Node is a .d.ts file
Let = 1 << 14, // Variable declaration
Const = 1 << 15, // Variable declaration
OctalLiteral = 1 << 16, // Octal numeric literal
Namespace = 1 << 17, // Namespace declaration
ExportContext = 1 << 18, // Export context (initialized by binding)
ContainsThis = 1 << 19, // Interface contains references to "this"
HasImplicitReturn = 1 << 20, // If function implicitly returns on one of codepaths (initialized by binding)
HasExplicitReturn = 1 << 21, // If function has explicit reachable return on one of codepaths (initialized by binding)
Modifier = Export | Ambient | Public | Private | Protected | Static | Readonly | Abstract | Default | Async,
AccessibilityModifier = Public | Private | Protected,
BlockScoped = Let | Const,
ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn
}

View file

@ -1548,6 +1548,7 @@ namespace ts {
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.StaticKeyword:
return true;
}
@ -2279,6 +2280,7 @@ namespace ts {
case SyntaxKind.ConstKeyword: return NodeFlags.Const;
case SyntaxKind.DefaultKeyword: return NodeFlags.Default;
case SyntaxKind.AsyncKeyword: return NodeFlags.Async;
case SyntaxKind.ReadonlyKeyword: return NodeFlags.Readonly;
}
return 0;
}