Skip to content

Commit fb7df96

Browse files
author
Andy Hanson
committed
Fix #7590: Allow 'readonly' to be used in constructor parameters
1 parent cf4d94b commit fb7df96

14 files changed

+133
-8
lines changed

src/compiler/checker.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -12656,7 +12656,7 @@ namespace ts {
1265612656

1265712657
checkVariableLikeDeclaration(node);
1265812658
let func = getContainingFunction(node);
12659-
if (node.flags & NodeFlags.AccessibilityModifier) {
12659+
if (node.flags & NodeFlags.ConstructorParameterModifier) {
1266012660
func = getContainingFunction(node);
1266112661
if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) {
1266212662
error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation);
@@ -12992,7 +12992,7 @@ namespace ts {
1299212992
// or the containing class declares instance member variables with initializers.
1299312993
const superCallShouldBeFirst =
1299412994
forEach((<ClassDeclaration>node.parent).members, isInstancePropertyWithInitializer) ||
12995-
forEach(node.parameters, p => p.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected));
12995+
forEach(node.parameters, p => p.flags & NodeFlags.ConstructorParameterModifier);
1299612996

1299712997
// Skip past any prologue directives to find the first statement
1299812998
// to ensure that it was a super call.
@@ -17642,7 +17642,8 @@ namespace ts {
1764217642
if (flags & NodeFlags.Readonly) {
1764317643
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly");
1764417644
}
17645-
else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature) {
17645+
else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature &&
17646+
!(node.kind == SyntaxKind.Parameter && isParameterPropertyDeclaration(<ParameterDeclaration> node))) {
1764617647
return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature);
1764717648
}
1764817649
flags |= NodeFlags.Readonly;
@@ -17750,7 +17751,7 @@ namespace ts {
1775017751
else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & NodeFlags.Ambient) {
1775117752
return grammarErrorOnNode(lastDeclare, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare");
1775217753
}
17753-
else if (node.kind === SyntaxKind.Parameter && (flags & NodeFlags.AccessibilityModifier) && isBindingPattern((<ParameterDeclaration>node).name)) {
17754+
else if (node.kind === SyntaxKind.Parameter && (flags & NodeFlags.ConstructorParameterModifier) && isBindingPattern((<ParameterDeclaration>node).name)) {
1775417755
return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_a_binding_pattern);
1775517756
}
1775617757
if (flags & NodeFlags.Async) {

src/compiler/declarationEmitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ namespace ts {
10511051
function emitParameterProperties(constructorDeclaration: ConstructorDeclaration) {
10521052
if (constructorDeclaration) {
10531053
forEach(constructorDeclaration.parameters, param => {
1054-
if (param.flags & NodeFlags.AccessibilityModifier) {
1054+
if (param.flags & NodeFlags.ConstructorParameterModifier) {
10551055
emitPropertyDeclaration(param);
10561056
}
10571057
});

src/compiler/emitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4979,7 +4979,7 @@ const _super = (function (geti, seti) {
49794979

49804980
function emitParameterPropertyAssignments(node: ConstructorDeclaration) {
49814981
forEach(node.parameters, param => {
4982-
if (param.flags & NodeFlags.AccessibilityModifier) {
4982+
if (param.flags & NodeFlags.ConstructorParameterModifier) {
49834983
writeLine();
49844984
emitStart(param);
49854985
emitStart(param.name);

src/compiler/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,10 @@ namespace ts {
407407
HasAggregatedChildData = 1 << 29, // If we've computed data from children and cached it in this node
408408
HasJsxSpreadAttribute = 1 << 30,
409409

410-
Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async,
410+
Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async | Readonly,
411411
AccessibilityModifier = Public | Private | Protected,
412+
// Accessibility modifiers and 'readonly' can be attached to a parameter in a constructor to make it a property.
413+
ConstructorParameterModifier = AccessibilityModifier | Readonly,
412414
BlockScoped = Let | Const,
413415

414416
ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn,

src/compiler/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3012,7 +3012,7 @@ namespace ts {
30123012
}
30133013

30143014
export function isParameterPropertyDeclaration(node: ParameterDeclaration): boolean {
3015-
return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent);
3015+
return node.flags & NodeFlags.ConstructorParameterModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent);
30163016
}
30173017

30183018
export function startsWith(str: string, prefix: string): boolean {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInAmbientClass.ts(2,14): error TS2369: A parameter property is only allowed in a constructor implementation.
2+
3+
4+
==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInAmbientClass.ts (1 errors) ====
5+
declare class C{
6+
constructor(readonly x: number);
7+
~~~~~~~~~~~~~~~~~~
8+
!!! error TS2369: A parameter property is only allowed in a constructor implementation.
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//// [readonlyInAmbientClass.ts]
2+
declare class C{
3+
constructor(readonly x: number);
4+
}
5+
6+
//// [readonlyInAmbientClass.js]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts(4,1): error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.
2+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts(7,26): error TS1029: 'public' modifier must precede 'readonly' modifier.
3+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts(13,10): error TS2341: Property 'x' is private and only accessible within class 'F'.
4+
5+
6+
==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts (3 errors) ====
7+
class C {
8+
constructor(readonly x: number) {}
9+
}
10+
new C(1).x = 2;
11+
~~~~~~~~~~
12+
!!! error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.
13+
14+
class E {
15+
constructor(readonly public x: number) {}
16+
~~~~~~
17+
!!! error TS1029: 'public' modifier must precede 'readonly' modifier.
18+
}
19+
20+
class F {
21+
constructor(private readonly x: number) {}
22+
}
23+
new F(1).x;
24+
~
25+
!!! error TS2341: Property 'x' is private and only accessible within class 'F'.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//// [readonlyInConstructorParameters.ts]
2+
class C {
3+
constructor(readonly x: number) {}
4+
}
5+
new C(1).x = 2;
6+
7+
class E {
8+
constructor(readonly public x: number) {}
9+
}
10+
11+
class F {
12+
constructor(private readonly x: number) {}
13+
}
14+
new F(1).x;
15+
16+
//// [readonlyInConstructorParameters.js]
17+
var C = (function () {
18+
function C(x) {
19+
this.x = x;
20+
}
21+
return C;
22+
}());
23+
new C(1).x = 2;
24+
var E = (function () {
25+
function E(x) {
26+
this.x = x;
27+
}
28+
return E;
29+
}());
30+
var F = (function () {
31+
function F(x) {
32+
this.x = x;
33+
}
34+
return F;
35+
}());
36+
new F(1).x;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts(2,14): error TS1030: 'readonly' modifier already seen.
2+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts(3,26): error TS1030: 'readonly' modifier already seen.
3+
4+
5+
==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts (2 errors) ====
6+
class C {
7+
readonly readonly x: number;
8+
~~~~~~~~
9+
!!! error TS1030: 'readonly' modifier already seen.
10+
constructor(readonly readonly y: number) {}
11+
~~~~~~~~
12+
!!! error TS1030: 'readonly' modifier already seen.
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//// [readonlyReadonly.ts]
2+
class C {
3+
readonly readonly x: number;
4+
constructor(readonly readonly y: number) {}
5+
}
6+
7+
//// [readonlyReadonly.js]
8+
var C = (function () {
9+
function C(y) {
10+
this.y = y;
11+
}
12+
return C;
13+
}());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare class C{
2+
constructor(readonly x: number);
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class C {
2+
constructor(readonly x: number) {}
3+
}
4+
new C(1).x = 2;
5+
6+
class E {
7+
constructor(readonly public x: number) {}
8+
}
9+
10+
class F {
11+
constructor(private readonly x: number) {}
12+
}
13+
new F(1).x;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class C {
2+
readonly readonly x: number;
3+
constructor(readonly readonly y: number) {}
4+
}

0 commit comments

Comments
 (0)