diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ab1433320f..296d3ead67 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12658,7 +12658,7 @@ namespace ts { checkVariableLikeDeclaration(node); let func = getContainingFunction(node); - if (node.flags & NodeFlags.AccessibilityModifier) { + if (node.flags & NodeFlags.ConstructorParameterModifier) { func = getContainingFunction(node); if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); @@ -12994,7 +12994,7 @@ namespace ts { // or the containing class declares instance member variables with initializers. const superCallShouldBeFirst = forEach((node.parent).members, isInstancePropertyWithInitializer) || - forEach(node.parameters, p => p.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected)); + forEach(node.parameters, p => p.flags & NodeFlags.ConstructorParameterModifier); // Skip past any prologue directives to find the first statement // to ensure that it was a super call. @@ -17651,7 +17651,8 @@ namespace ts { if (flags & NodeFlags.Readonly) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); } - else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature) { + else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && + !(node.kind == SyntaxKind.Parameter && isParameterPropertyDeclaration( node))) { return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); } flags |= NodeFlags.Readonly; @@ -17759,7 +17760,7 @@ namespace ts { else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & NodeFlags.Ambient) { return grammarErrorOnNode(lastDeclare, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); } - else if (node.kind === SyntaxKind.Parameter && (flags & NodeFlags.AccessibilityModifier) && isBindingPattern((node).name)) { + else if (node.kind === SyntaxKind.Parameter && (flags & NodeFlags.ConstructorParameterModifier) && isBindingPattern((node).name)) { return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_a_binding_pattern); } if (flags & NodeFlags.Async) { diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index aceec93322..90ac0c0719 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -1051,7 +1051,7 @@ namespace ts { function emitParameterProperties(constructorDeclaration: ConstructorDeclaration) { if (constructorDeclaration) { forEach(constructorDeclaration.parameters, param => { - if (param.flags & NodeFlags.AccessibilityModifier) { + if (param.flags & NodeFlags.ConstructorParameterModifier) { emitPropertyDeclaration(param); } }); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index a1f02f520d..0e14adf07b 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -4979,7 +4979,7 @@ const _super = (function (geti, seti) { function emitParameterPropertyAssignments(node: ConstructorDeclaration) { forEach(node.parameters, param => { - if (param.flags & NodeFlags.AccessibilityModifier) { + if (param.flags & NodeFlags.ConstructorParameterModifier) { writeLine(); emitStart(param); emitStart(param.name); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c367bffea3..0899b01620 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -407,8 +407,10 @@ namespace ts { HasAggregatedChildData = 1 << 29, // If we've computed data from children and cached it in this node HasJsxSpreadAttribute = 1 << 30, - Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async, + Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async | Readonly, AccessibilityModifier = Public | Private | Protected, + // Accessibility modifiers and 'readonly' can be attached to a parameter in a constructor to make it a property. + ConstructorParameterModifier = AccessibilityModifier | Readonly, BlockScoped = Let | Const, ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 208723159c..fc5fb8aac0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3021,7 +3021,7 @@ namespace ts { } export function isParameterPropertyDeclaration(node: ParameterDeclaration): boolean { - return node.flags & NodeFlags.AccessibilityModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent); + return node.flags & NodeFlags.ConstructorParameterModifier && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent); } export function startsWith(str: string, prefix: string): boolean { diff --git a/tests/baselines/reference/readonlyInAmbientClass.errors.txt b/tests/baselines/reference/readonlyInAmbientClass.errors.txt new file mode 100644 index 0000000000..303f04038f --- /dev/null +++ b/tests/baselines/reference/readonlyInAmbientClass.errors.txt @@ -0,0 +1,9 @@ +tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInAmbientClass.ts(2,14): error TS2369: A parameter property is only allowed in a constructor implementation. + + +==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInAmbientClass.ts (1 errors) ==== + declare class C{ + constructor(readonly x: number); + ~~~~~~~~~~~~~~~~~~ +!!! error TS2369: A parameter property is only allowed in a constructor implementation. + } \ No newline at end of file diff --git a/tests/baselines/reference/readonlyInAmbientClass.js b/tests/baselines/reference/readonlyInAmbientClass.js new file mode 100644 index 0000000000..9be7c1702c --- /dev/null +++ b/tests/baselines/reference/readonlyInAmbientClass.js @@ -0,0 +1,6 @@ +//// [readonlyInAmbientClass.ts] +declare class C{ + constructor(readonly x: number); +} + +//// [readonlyInAmbientClass.js] diff --git a/tests/baselines/reference/readonlyInConstructorParameters.errors.txt b/tests/baselines/reference/readonlyInConstructorParameters.errors.txt new file mode 100644 index 0000000000..6e4b549ce7 --- /dev/null +++ b/tests/baselines/reference/readonlyInConstructorParameters.errors.txt @@ -0,0 +1,25 @@ +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. +tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts(7,26): error TS1029: 'public' modifier must precede 'readonly' modifier. +tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts(13,10): error TS2341: Property 'x' is private and only accessible within class 'F'. + + +==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts (3 errors) ==== + class C { + constructor(readonly x: number) {} + } + new C(1).x = 2; + ~~~~~~~~~~ +!!! error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property. + + class E { + constructor(readonly public x: number) {} + ~~~~~~ +!!! error TS1029: 'public' modifier must precede 'readonly' modifier. + } + + class F { + constructor(private readonly x: number) {} + } + new F(1).x; + ~ +!!! error TS2341: Property 'x' is private and only accessible within class 'F'. \ No newline at end of file diff --git a/tests/baselines/reference/readonlyInConstructorParameters.js b/tests/baselines/reference/readonlyInConstructorParameters.js new file mode 100644 index 0000000000..41e9c57cd7 --- /dev/null +++ b/tests/baselines/reference/readonlyInConstructorParameters.js @@ -0,0 +1,36 @@ +//// [readonlyInConstructorParameters.ts] +class C { + constructor(readonly x: number) {} +} +new C(1).x = 2; + +class E { + constructor(readonly public x: number) {} +} + +class F { + constructor(private readonly x: number) {} +} +new F(1).x; + +//// [readonlyInConstructorParameters.js] +var C = (function () { + function C(x) { + this.x = x; + } + return C; +}()); +new C(1).x = 2; +var E = (function () { + function E(x) { + this.x = x; + } + return E; +}()); +var F = (function () { + function F(x) { + this.x = x; + } + return F; +}()); +new F(1).x; diff --git a/tests/baselines/reference/readonlyReadonly.errors.txt b/tests/baselines/reference/readonlyReadonly.errors.txt new file mode 100644 index 0000000000..b1ea129303 --- /dev/null +++ b/tests/baselines/reference/readonlyReadonly.errors.txt @@ -0,0 +1,13 @@ +tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts(2,14): error TS1030: 'readonly' modifier already seen. +tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts(3,26): error TS1030: 'readonly' modifier already seen. + + +==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts (2 errors) ==== + class C { + readonly readonly x: number; + ~~~~~~~~ +!!! error TS1030: 'readonly' modifier already seen. + constructor(readonly readonly y: number) {} + ~~~~~~~~ +!!! error TS1030: 'readonly' modifier already seen. + } \ No newline at end of file diff --git a/tests/baselines/reference/readonlyReadonly.js b/tests/baselines/reference/readonlyReadonly.js new file mode 100644 index 0000000000..0b804e147b --- /dev/null +++ b/tests/baselines/reference/readonlyReadonly.js @@ -0,0 +1,13 @@ +//// [readonlyReadonly.ts] +class C { + readonly readonly x: number; + constructor(readonly readonly y: number) {} +} + +//// [readonlyReadonly.js] +var C = (function () { + function C(y) { + this.y = y; + } + return C; +}()); diff --git a/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInAmbientClass.ts b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInAmbientClass.ts new file mode 100644 index 0000000000..ca710532dd --- /dev/null +++ b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInAmbientClass.ts @@ -0,0 +1,3 @@ +declare class C{ + constructor(readonly x: number); +} \ No newline at end of file diff --git a/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts new file mode 100644 index 0000000000..dbcdff7a2d --- /dev/null +++ b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyInConstructorParameters.ts @@ -0,0 +1,13 @@ +class C { + constructor(readonly x: number) {} +} +new C(1).x = 2; + +class E { + constructor(readonly public x: number) {} +} + +class F { + constructor(private readonly x: number) {} +} +new F(1).x; \ No newline at end of file diff --git a/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts new file mode 100644 index 0000000000..8fb611a018 --- /dev/null +++ b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyReadonly.ts @@ -0,0 +1,4 @@ +class C { + readonly readonly x: number; + constructor(readonly readonly y: number) {} +} \ No newline at end of file