Skip to content

Commit b6871c9

Browse files
committed
Improved error messages for property declarations
1 parent 883b8d9 commit b6871c9

14 files changed

+144
-11
lines changed

src/compiler/checker.ts

+5
Original file line numberDiff line numberDiff line change
@@ -15324,6 +15324,11 @@ namespace ts {
1532415324
let flags = 0;
1532515325
for (const modifier of node.modifiers) {
1532615326
switch (modifier.kind) {
15327+
case SyntaxKind.ConstKeyword:
15328+
if (node.parent.kind === SyntaxKind.ClassDeclaration && node.kind !== SyntaxKind.EnumDeclaration) {
15329+
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword));
15330+
}
15331+
break;
1532715332
case SyntaxKind.PublicKeyword:
1532815333
case SyntaxKind.ProtectedKeyword:
1532915334
case SyntaxKind.PrivateKeyword:

src/compiler/diagnosticMessages.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,10 @@
783783
"category": "Error",
784784
"code": 1245
785785
},
786-
786+
"A class member cannot have the '{0}' keyword.": {
787+
"category": "Error",
788+
"code": 1246
789+
},
787790
"'with' statements are not allowed in an async function block.": {
788791
"category": "Error",
789792
"code": 1300

src/compiler/parser.ts

+29-9
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,14 @@ namespace ts {
11341134
return token === t && tryParse(nextTokenCanFollowModifier);
11351135
}
11361136

1137+
function nextTokenIsOnSameLineAndCanFollowModifier() {
1138+
nextToken();
1139+
if (scanner.hasPrecedingLineBreak()) {
1140+
return false;
1141+
}
1142+
return canFollowModifier();
1143+
}
1144+
11371145
function nextTokenCanFollowModifier() {
11381146
if (token === SyntaxKind.ConstKeyword) {
11391147
// 'const' is only a modifier if followed by 'enum'.
@@ -1154,11 +1162,7 @@ namespace ts {
11541162
return canFollowModifier();
11551163
}
11561164

1157-
nextToken();
1158-
if (scanner.hasPrecedingLineBreak()) {
1159-
return false;
1160-
}
1161-
return canFollowModifier();
1165+
return nextTokenIsOnSameLineAndCanFollowModifier();
11621166
}
11631167

11641168
function parseAnyContextualModifier(): boolean {
@@ -4903,15 +4907,31 @@ namespace ts {
49034907
return decorators;
49044908
}
49054909

4906-
function parseModifiers(): ModifiersArray {
4910+
/*
4911+
* There are situations in which a modifier like 'const' will appear unexpectedly, such as on a class member.
4912+
* In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect
4913+
* and turns it into a standalone declaration), then it is better to parse it and report an error later.
4914+
*
4915+
* In such situations, 'permitInvalidConstAsModifier' should be set to true.
4916+
*/
4917+
function parseModifiers(permitInvalidConstAsModifier?: boolean): ModifiersArray {
49074918
let flags = 0;
49084919
let modifiers: ModifiersArray;
49094920
while (true) {
49104921
const modifierStart = scanner.getStartPos();
49114922
const modifierKind = token;
49124923

4913-
if (!parseAnyContextualModifier()) {
4914-
break;
4924+
if (token === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) {
4925+
// We need to ensure that any subsequent modifiers appear on the same line
4926+
// so that when 'const' is a standalone declaration, we don't issue an error.
4927+
if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) {
4928+
break;
4929+
}
4930+
}
4931+
else {
4932+
if (!parseAnyContextualModifier()) {
4933+
break;
4934+
}
49154935
}
49164936

49174937
if (!modifiers) {
@@ -4956,7 +4976,7 @@ namespace ts {
49564976

49574977
const fullStart = getNodePos();
49584978
const decorators = parseDecorators();
4959-
const modifiers = parseModifiers();
4979+
const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true);
49604980

49614981
const accessor = tryParseAccessorDeclaration(fullStart, decorators, modifiers);
49624982
if (accessor) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
tests/cases/compiler/ClassDeclaration26.ts(2,22): error TS1005: ';' expected.
2+
tests/cases/compiler/ClassDeclaration26.ts(4,5): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
3+
tests/cases/compiler/ClassDeclaration26.ts(4,20): error TS1005: '=' expected.
4+
tests/cases/compiler/ClassDeclaration26.ts(4,23): error TS1005: '=>' expected.
5+
tests/cases/compiler/ClassDeclaration26.ts(5,1): error TS1128: Declaration or statement expected.
6+
7+
8+
==== tests/cases/compiler/ClassDeclaration26.ts (5 errors) ====
9+
class C {
10+
public const var export foo = 10;
11+
~~~~~~
12+
!!! error TS1005: ';' expected.
13+
14+
var constructor() { }
15+
~~~
16+
!!! error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
17+
~
18+
!!! error TS1005: '=' expected.
19+
~
20+
!!! error TS1005: '=>' expected.
21+
}
22+
~
23+
!!! error TS1128: Declaration or statement expected.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [ClassDeclaration26.ts]
2+
class C {
3+
public const var export foo = 10;
4+
5+
var constructor() { }
6+
}
7+
8+
//// [ClassDeclaration26.js]
9+
var C = (function () {
10+
function C() {
11+
this.foo = 10;
12+
}
13+
return C;
14+
})();
15+
var constructor = function () { };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
tests/cases/compiler/ClassDeclarationWithInvalidConstOnPropertyDeclaration.ts(2,3): error TS1246: A class member cannot have the 'const' keyword.
2+
3+
4+
==== tests/cases/compiler/ClassDeclarationWithInvalidConstOnPropertyDeclaration.ts (1 errors) ====
5+
class AtomicNumbers {
6+
static const H = 1;
7+
~~~~~~~~~~~~~~~~~~~
8+
!!! error TS1246: A class member cannot have the 'const' keyword.
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//// [ClassDeclarationWithInvalidConstOnPropertyDeclaration.ts]
2+
class AtomicNumbers {
3+
static const H = 1;
4+
}
5+
6+
//// [ClassDeclarationWithInvalidConstOnPropertyDeclaration.js]
7+
var AtomicNumbers = (function () {
8+
function AtomicNumbers() {
9+
}
10+
AtomicNumbers.H = 1;
11+
return AtomicNumbers;
12+
})();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//// [ClassDeclarationWithInvalidConstOnPropertyDeclaration2.ts]
2+
class C {
3+
const
4+
x = 10;
5+
}
6+
7+
//// [ClassDeclarationWithInvalidConstOnPropertyDeclaration2.js]
8+
var C = (function () {
9+
function C() {
10+
this.x = 10;
11+
}
12+
return C;
13+
})();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=== tests/cases/compiler/ClassDeclarationWithInvalidConstOnPropertyDeclaration2.ts ===
2+
class C {
3+
>C : Symbol(C, Decl(ClassDeclarationWithInvalidConstOnPropertyDeclaration2.ts, 0, 0))
4+
5+
const
6+
>const : Symbol(const, Decl(ClassDeclarationWithInvalidConstOnPropertyDeclaration2.ts, 0, 9))
7+
8+
x = 10;
9+
>x : Symbol(x, Decl(ClassDeclarationWithInvalidConstOnPropertyDeclaration2.ts, 1, 9))
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/compiler/ClassDeclarationWithInvalidConstOnPropertyDeclaration2.ts ===
2+
class C {
3+
>C : C
4+
5+
const
6+
>const : any
7+
8+
x = 10;
9+
>x : number
10+
>10 : number
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class C {
2+
public const var export foo = 10;
3+
4+
var constructor() { }
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class AtomicNumbers {
2+
static const H = 1;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class C {
2+
const
3+
x = 10;
4+
}

tests/cases/fourslash/getOccurrencesConst04.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference path='fourslash.ts' />
22

33
////export const class C {
4-
//// private static c/*1*/onst foo;
4+
//// private static const f/*1*/oo;
55
//// constructor(public con/*2*/st foo) {
66
//// }
77
////}

0 commit comments

Comments
 (0)