Skip to content

Commit ad2634e

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

14 files changed

+133
-8
lines changed

src/compiler/checker.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -12658,7 +12658,7 @@ namespace ts {
1265812658

1265912659
checkVariableLikeDeclaration(node);
1266012660
let func = getContainingFunction(node);
12661-
if (node.flags & NodeFlags.AccessibilityModifier) {
12661+
if (node.flags & NodeFlags.ConstructorParameterModifier) {
1266212662
func = getContainingFunction(node);
1266312663
if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) {
1266412664
error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation);
@@ -12994,7 +12994,7 @@ namespace ts {
1299412994
// or the containing class declares instance member variables with initializers.
1299512995
const superCallShouldBeFirst =
1299612996
forEach((<ClassDeclaration>node.parent).members, isInstancePropertyWithInitializer) ||
12997-
forEach(node.parameters, p => p.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected));
12997+
forEach(node.parameters, p => p.flags & NodeFlags.ConstructorParameterModifier);
1299812998

1299912999
// Skip past any prologue directives to find the first statement
1300013000
// to ensure that it was a super call.
@@ -17651,7 +17651,8 @@ namespace ts {
1765117651
if (flags & NodeFlags.Readonly) {
1765217652
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly");
1765317653
}
17654-
else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature) {
17654+
else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature &&
17655+
!(node.kind == SyntaxKind.Parameter && isParameterPropertyDeclaration(<ParameterDeclaration> node))) {
1765517656
return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature);
1765617657
}
1765717658
flags |= NodeFlags.Readonly;
@@ -17759,7 +17760,7 @@ namespace ts {
1775917760
else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & NodeFlags.Ambient) {
1776017761
return grammarErrorOnNode(lastDeclare, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare");
1776117762
}
17762-
else if (node.kind === SyntaxKind.Parameter && (flags & NodeFlags.AccessibilityModifier) && isBindingPattern((<ParameterDeclaration>node).name)) {
17763+
else if (node.kind === SyntaxKind.Parameter && (flags & NodeFlags.ConstructorParameterModifier) && isBindingPattern((<ParameterDeclaration>node).name)) {
1776317764
return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_a_binding_pattern);
1776417765
}
1776517766
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
@@ -3021,7 +3021,7 @@ namespace ts {
30213021
}
30223022

30233023
export function isParameterPropertyDeclaration(node: ParameterDeclaration): boolean {
3024-
return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent);
3024+
return node.flags & NodeFlags.ConstructorParameterModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent);
30253025
}
30263026

30273027
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)