Skip to content

Commit 2ef436f

Browse files
Merge pull request #5559 from MartyIX/issue-4045
Improve error messages for property declarations
2 parents 3ce91c4 + f15fe5b commit 2ef436f

14 files changed

+148
-13
lines changed

src/compiler/checker.ts

+5
Original file line numberDiff line numberDiff line change
@@ -15492,6 +15492,11 @@ namespace ts {
1549215492
let flags = 0;
1549315493
for (const modifier of node.modifiers) {
1549415494
switch (modifier.kind) {
15495+
case SyntaxKind.ConstKeyword:
15496+
if (node.kind !== SyntaxKind.EnumDeclaration && node.parent.kind === SyntaxKind.ClassDeclaration) {
15497+
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword));
15498+
}
15499+
break;
1549515500
case SyntaxKind.PublicKeyword:
1549615501
case SyntaxKind.ProtectedKeyword:
1549715502
case SyntaxKind.PrivateKeyword:

src/compiler/diagnosticMessages.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,10 @@
791791
"category": "Error",
792792
"code": 1247
793793
},
794-
794+
"A class member cannot have the '{0}' keyword.": {
795+
"category": "Error",
796+
"code": 1248
797+
},
795798
"'with' statements are not allowed in an async function block.": {
796799
"category": "Error",
797800
"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 {
@@ -4923,15 +4927,31 @@ namespace ts {
49234927
return decorators;
49244928
}
49254929

4926-
function parseModifiers(): ModifiersArray {
4930+
/*
4931+
* There are situations in which a modifier like 'const' will appear unexpectedly, such as on a class member.
4932+
* In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect
4933+
* and turns it into a standalone declaration), then it is better to parse it and report an error later.
4934+
*
4935+
* In such situations, 'permitInvalidConstAsModifier' should be set to true.
4936+
*/
4937+
function parseModifiers(permitInvalidConstAsModifier?: boolean): ModifiersArray {
49274938
let flags = 0;
49284939
let modifiers: ModifiersArray;
49294940
while (true) {
49304941
const modifierStart = scanner.getStartPos();
49314942
const modifierKind = token;
49324943

4933-
if (!parseAnyContextualModifier()) {
4934-
break;
4944+
if (token === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) {
4945+
// We need to ensure that any subsequent modifiers appear on the same line
4946+
// so that when 'const' is a standalone declaration, we don't issue an error.
4947+
if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) {
4948+
break;
4949+
}
4950+
}
4951+
else {
4952+
if (!parseAnyContextualModifier()) {
4953+
break;
4954+
}
49354955
}
49364956

49374957
if (!modifiers) {
@@ -4976,7 +4996,7 @@ namespace ts {
49764996

49774997
const fullStart = getNodePos();
49784998
const decorators = parseDecorators();
4979-
const modifiers = parseModifiers();
4999+
const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true);
49805000

49815001
const accessor = tryParseAccessorDeclaration(fullStart, decorators, modifiers);
49825002
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 TS1248: 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 TS1248: 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+
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
/// <reference path='fourslash.ts' />
22

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

99
goTo.marker("1");
10-
verify.occurrencesAtPositionCount(1);
10+
verify.occurrencesAtPositionCount(0);
1111
goTo.marker("2");
12+
verify.occurrencesAtPositionCount(1);
13+
goTo.marker("3");
1214
verify.occurrencesAtPositionCount(0);

0 commit comments

Comments
 (0)