From 63e1d5918294abfe17ccb2ed6cb0fcd69da46e2d Mon Sep 17 00:00:00 2001 From: James Browning Date: Thu, 21 Sep 2023 18:10:10 +1200 Subject: [PATCH 1/5] Add private field support to prefer-readonly --- .../docs/rules/prefer-readonly.md | 9 +- .../src/rules/prefer-readonly.ts | 5 +- .../tests/rules/prefer-readonly.test.ts | 885 +++++++++++++++++- 3 files changed, 893 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly.md b/packages/eslint-plugin/docs/rules/prefer-readonly.md index 774b55b39ffa..8c5aa938024a 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly.md @@ -22,6 +22,7 @@ class Container { // These member variables could be marked as readonly private neverModifiedMember = true; private onlyModifiedInConstructor: number; + #neverModifiedPrivateField = 3; public constructor( onlyModifiedInConstructor: number, @@ -43,12 +44,18 @@ class Container { // Protected members might be modified by child classes protected protectedMember: number; - // This is modified later on by the class + // These are modified later on by the class private modifiedLater = 'unchanged'; public mutate() { this.modifiedLater = 'mutated'; } + + #modifiedLaterPrivateField = 'unchanged'; + + public mutatePrivateField() { + this.#modifiedLaterPrivateField = 'mutated'; + } } ``` diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index 713a703a56f0..6857706521d5 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -288,7 +288,10 @@ class ClassScope { public addDeclaredVariable(node: ParameterOrPropertyDeclaration): void { if ( - !tsutils.isModifierFlagSet(node, ts.ModifierFlags.Private) || + !( + tsutils.isModifierFlagSet(node, ts.ModifierFlags.Private) || + node.name.kind === ts.SyntaxKind.PrivateIdentifier + ) || tsutils.isModifierFlagSet(node, ts.ModifierFlags.Readonly) || ts.isComputedPropertyName(node.name) ) { diff --git a/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts index f1b57419adc8..bd9159a93ec9 100644 --- a/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts @@ -52,6 +52,11 @@ ruleTester.run('prefer-readonly', rule, { private static readonly correctlyReadonlyStatic = 7; } `, + ` + class TestReadonlyStatic { + static readonly #correctlyReadonlyStatic = 7; + } + `, ` class TestModifiableStatic { private static correctlyModifiableStatic = 7; @@ -61,6 +66,15 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableStatic { + static #correctlyModifiableStatic = 7; + + public constructor() { + TestModifiableStatic.#correctlyModifiableStatic += 1; + } + } + `, ` class TestModifiableByParameterProperty { private static readonly correctlyModifiableByParameterProperty = 7; @@ -72,11 +86,27 @@ ruleTester.run('prefer-readonly', rule, { ) {} } `, + ` + class TestModifiableByParameterProperty { + static readonly #correctlyModifiableByParameterProperty = 7; + + public constructor( + public correctlyModifiablePublicParameter: number = (() => { + return (TestModifiableStatic.#correctlyModifiableByParameterProperty += 1); + })(), + ) {} + } + `, ` class TestReadonlyInline { private readonly correctlyReadonlyInline = 7; } `, + ` + class TestReadonlyInline { + readonly #correctlyReadonlyInline = 7; + } + `, ` class TestReadonlyDelayed { private readonly correctlyReadonlyDelayed = 7; @@ -86,6 +116,15 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestReadonlyDelayed { + readonly #correctlyReadonlyDelayed = 7; + + public constructor() { + this.#correctlyReadonlyDelayed += 1; + } + } + `, ` class TestModifiableInline { private correctlyModifiableInline = 7; @@ -103,6 +142,23 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableInline { + #correctlyModifiableInline = 7; + + public mutate() { + this.#correctlyModifiableInline += 1; + + return class { + #correctlyModifiableInline = 7; + + mutate() { + this.#correctlyModifiableInline += 1; + } + }; + } + } + `, ` class TestModifiableDelayed { private correctlyModifiableDelayed = 7; @@ -112,6 +168,15 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableDelayed { + #correctlyModifiableDelayed = 7; + + public mutate() { + this.#correctlyModifiableDelayed += 1; + } + } + `, ` class TestModifiableDeleted { private correctlyModifiableDeleted = 7; @@ -132,6 +197,17 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableWithinConstructor { + #correctlyModifiableWithinConstructor = 7; + + public constructor() { + (() => { + this.#correctlyModifiableWithinConstructor += 1; + })(); + } + } + `, ` class TestModifiableWithinConstructorArrowFunction { private correctlyModifiableWithinConstructorArrowFunction = 7; @@ -143,6 +219,17 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableWithinConstructorArrowFunction { + #correctlyModifiableWithinConstructorArrowFunction = 7; + + public constructor() { + (() => { + this.#correctlyModifiableWithinConstructorArrowFunction += 1; + })(); + } + } + `, ` class TestModifiableWithinConstructorInFunctionExpression { private correctlyModifiableWithinConstructorInFunctionExpression = 7; @@ -156,6 +243,19 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableWithinConstructorInFunctionExpression { + #correctlyModifiableWithinConstructorInFunctionExpression = 7; + + public constructor() { + const self = this; + + (() => { + self.#correctlyModifiableWithinConstructorInFunctionExpression += 1; + })(); + } + } + `, ` class TestModifiableWithinConstructorInGetAccessor { private correctlyModifiableWithinConstructorInGetAccessor = 7; @@ -171,6 +271,21 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableWithinConstructorInGetAccessor { + #correctlyModifiableWithinConstructorInGetAccessor = 7; + + public constructor() { + const self = this; + + const confusingObject = { + get accessor() { + return (self.#correctlyModifiableWithinConstructorInGetAccessor += 1); + }, + }; + } + } + `, ` class TestModifiableWithinConstructorInMethodDeclaration { private correctlyModifiableWithinConstructorInMethodDeclaration = 7; @@ -186,6 +301,21 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableWithinConstructorInMethodDeclaration { + #correctlyModifiableWithinConstructorInMethodDeclaration = 7; + + public constructor() { + const self = this; + + const confusingObject = { + methodDeclaration() { + self.#correctlyModifiableWithinConstructorInMethodDeclaration = 7; + }, + }; + } + } + `, ` class TestModifiableWithinConstructorInSetAccessor { private correctlyModifiableWithinConstructorInSetAccessor = 7; @@ -201,6 +331,21 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiableWithinConstructorInSetAccessor { + #correctlyModifiableWithinConstructorInSetAccessor = 7; + + public constructor() { + const self = this; + + const confusingObject = { + set accessor(value: number) { + self.#correctlyModifiableWithinConstructorInSetAccessor += value; + }, + }; + } + } + `, ` class TestModifiablePostDecremented { private correctlyModifiablePostDecremented = 7; @@ -210,6 +355,15 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiablePostDecremented { + #correctlyModifiablePostDecremented = 7; + + public mutate() { + this.#correctlyModifiablePostDecremented -= 1; + } + } + `, ` class TestyModifiablePostIncremented { private correctlyModifiablePostIncremented = 7; @@ -219,6 +373,15 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestyModifiablePostIncremented { + #correctlyModifiablePostIncremented = 7; + + public mutate() { + this.#correctlyModifiablePostIncremented += 1; + } + } + `, ` class TestModifiablePreDecremented { private correctlyModifiablePreDecremented = 7; @@ -228,6 +391,15 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiablePreDecremented { + #correctlyModifiablePreDecremented = 7; + + public mutate() { + --this.#correctlyModifiablePreDecremented; + } + } + `, ` class TestModifiablePreIncremented { private correctlyModifiablePreIncremented = 7; @@ -237,6 +409,15 @@ ruleTester.run('prefer-readonly', rule, { } } `, + ` + class TestModifiablePreIncremented { + #correctlyModifiablePreIncremented = 7; + + public mutate() { + ++this.#correctlyModifiablePreIncremented; + } + } + `, ` class TestProtectedModifiable { protected protectedModifiable = 7; @@ -273,6 +454,18 @@ ruleTester.run('prefer-readonly', rule, { }, ], }, + { + code: ` + class TestCorrectlyNonInlineLambdas { + #correctlyNonInlineLambda = 7; + } + `, + options: [ + { + onlyInlineLambdas: true, + }, + ], + }, ` class TestComputedParameter { public mutate() { @@ -294,6 +487,18 @@ class Foo { }, { code: ` +class Foo { + #value: number = 0; + + bar(newValue: { value: number }) { + ({ value: this.#value } = newValue); + return this.#value; + } +} + `, + }, + { + code: ` function ClassWithName {}>(Base: TBase) { return class extends Base { private _name: string; @@ -307,12 +512,25 @@ function ClassWithName {}>(Base: TBase) { }, { code: ` +function ClassWithName {}>(Base: TBase) { + return class extends Base { + #name: string; + + public test(value: string) { + this.#name = value; + } + }; +} + `, + }, + { + code: ` class Foo { - private value: Record = {}; + #value: Record = {}; bar(newValue: Record) { - ({ ...this.value } = newValue); - return this.value; + ({ ...this.#value } = newValue); + return this.#value; } } `, @@ -331,6 +549,18 @@ class Foo { }, { code: ` +class Foo { + #value: number[] = []; + + bar(newValue: number[]) { + [...this.#value] = newValue; + return this.#value; + } +} + `, + }, + { + code: ` class Foo { private value: number = 0; @@ -338,6 +568,18 @@ class Foo { [this.value] = newValue; return this.value; } +} + `, + }, + { + code: ` +class Foo { + #value: number = 0; + + bar(newValue: number[]) { + [this.#value] = newValue; + return this.#value; + } } `, }, @@ -354,6 +596,19 @@ class Foo { } `, }, + { + code: ` + class Test { + #testObj = { + prop: '', + }; + + public test(): void { + this.#testObj = ''; + } + } + `, + }, { code: ` class TestObject { @@ -369,6 +624,21 @@ class Foo { } `, }, + { + code: ` + class TestObject { + public prop: number; + } + + class Test { + #testObj = new TestObject(); + + public test(): void { + this.#testObj = new TestObject(); + } + } + `, + }, ], invalid: [ { @@ -391,6 +661,26 @@ class Foo { } `, }, + { + code: ` + class TestIncorrectlyModifiableStatic { + static #incorrectlyModifiableStatic = 7; + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiableStatic', + }, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiableStatic { + static readonly #incorrectlyModifiableStatic = 7; + } + `, + }, { code: ` class TestIncorrectlyModifiableStaticArrow { @@ -411,6 +701,26 @@ class Foo { } `, }, + { + code: ` + class TestIncorrectlyModifiableStaticArrow { + static #incorrectlyModifiableStaticArrow = () => 7; + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiableStaticArrow', + }, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiableStaticArrow { + static readonly #incorrectlyModifiableStaticArrow = () => 7; + } + `, + }, { code: ` class TestIncorrectlyModifiableInline { @@ -453,7 +763,47 @@ class Foo { }, { code: ` - class TestIncorrectlyModifiableDelayed { + class TestIncorrectlyModifiableInline { + #incorrectlyModifiableInline = 7; + + public createConfusingChildClass() { + return class { + #incorrectlyModifiableInline = 7; + }; + } + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiableInline', + }, + line: 3, + messageId: 'preferReadonly', + }, + { + data: { + name: '#incorrectlyModifiableInline', + }, + line: 7, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiableInline { + readonly #incorrectlyModifiableInline = 7; + + public createConfusingChildClass() { + return class { + readonly #incorrectlyModifiableInline = 7; + }; + } + } + `, + }, + { + code: ` + class TestIncorrectlyModifiableDelayed { private incorrectlyModifiableDelayed = 7; public constructor() { @@ -479,6 +829,34 @@ class Foo { } `, }, + { + code: ` + class TestIncorrectlyModifiableDelayed { + #incorrectlyModifiableDelayed = 7; + + public constructor() { + this.#incorrectlyModifiableDelayed = 7; + } + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiableDelayed', + }, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiableDelayed { + readonly #incorrectlyModifiableDelayed = 7; + + public constructor() { + this.#incorrectlyModifiableDelayed = 7; + } + } + `, + }, { code: ` class TestChildClassExpressionModifiable { @@ -520,6 +898,47 @@ class Foo { } `, }, + { + code: ` + class TestChildClassExpressionModifiable { + #childClassExpressionModifiable = 7; + + public createConfusingChildClass() { + return class { + #childClassExpressionModifiable = 7; + + mutate() { + this.#childClassExpressionModifiable += 1; + } + }; + } + } + `, + errors: [ + { + data: { + name: '#childClassExpressionModifiable', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestChildClassExpressionModifiable { + readonly #childClassExpressionModifiable = 7; + + public createConfusingChildClass() { + return class { + #childClassExpressionModifiable = 7; + + mutate() { + this.#childClassExpressionModifiable += 1; + } + }; + } + } + `, + }, { code: ` class TestIncorrectlyModifiablePostMinus { @@ -549,6 +968,35 @@ class Foo { } `, }, + { + code: ` + class TestIncorrectlyModifiablePostMinus { + #incorrectlyModifiablePostMinus = 7; + + public mutate() { + this.#incorrectlyModifiablePostMinus - 1; + } + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiablePostMinus', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiablePostMinus { + readonly #incorrectlyModifiablePostMinus = 7; + + public mutate() { + this.#incorrectlyModifiablePostMinus - 1; + } + } + `, + }, { code: ` class TestIncorrectlyModifiablePostPlus { @@ -578,6 +1026,35 @@ class Foo { } `, }, + { + code: ` + class TestIncorrectlyModifiablePostPlus { + #incorrectlyModifiablePostPlus = 7; + + public mutate() { + this.#incorrectlyModifiablePostPlus + 1; + } + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiablePostPlus', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiablePostPlus { + readonly #incorrectlyModifiablePostPlus = 7; + + public mutate() { + this.#incorrectlyModifiablePostPlus + 1; + } + } + `, + }, { code: ` class TestIncorrectlyModifiablePreMinus { @@ -607,6 +1084,35 @@ class Foo { } `, }, + { + code: ` + class TestIncorrectlyModifiablePreMinus { + #incorrectlyModifiablePreMinus = 7; + + public mutate() { + -this.#incorrectlyModifiablePreMinus; + } + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiablePreMinus', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiablePreMinus { + readonly #incorrectlyModifiablePreMinus = 7; + + public mutate() { + -this.#incorrectlyModifiablePreMinus; + } + } + `, + }, { code: ` class TestIncorrectlyModifiablePrePlus { @@ -636,6 +1142,35 @@ class Foo { } `, }, + { + code: ` + class TestIncorrectlyModifiablePrePlus { + #incorrectlyModifiablePrePlus = 7; + + public mutate() { + +this.#incorrectlyModifiablePrePlus; + } + } + `, + errors: [ + { + data: { + name: '#incorrectlyModifiablePrePlus', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + output: ` + class TestIncorrectlyModifiablePrePlus { + readonly #incorrectlyModifiablePrePlus = 7; + + public mutate() { + +this.#incorrectlyModifiablePrePlus; + } + } + `, + }, { code: ` class TestOverlappingClassVariable { @@ -772,6 +1307,31 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` +function ClassWithName {}>(Base: TBase) { + return class extends Base { + #name: string; + }; +} + `, + output: ` +function ClassWithName {}>(Base: TBase) { + return class extends Base { + readonly #name: string; + }; +} + `, + errors: [ + { + data: { + name: '#name', + }, + line: 4, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -805,6 +1365,39 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = { + prop: '', + }; + + public test(): void { + this.#testObj.prop = ''; + } + } + `, + output: ` + class Test { + readonly #testObj = { + prop: '', + }; + + public test(): void { + this.#testObj.prop = ''; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class TestObject { @@ -842,6 +1435,43 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class TestObject { + public prop: number; + } + + class Test { + #testObj = new TestObject(); + + public test(): void { + this.#testObj.prop = 10; + } + } + `, + output: ` + class TestObject { + public prop: number; + } + + class Test { + readonly #testObj = new TestObject(); + + public test(): void { + this.#testObj.prop = 10; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 7, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -873,6 +1503,37 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = { + prop: '', + }; + public test(): void { + this.#testObj.prop; + } + } + `, + output: ` + class Test { + readonly #testObj = { + prop: '', + }; + public test(): void { + this.#testObj.prop; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -900,6 +1561,33 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj?.prop; + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj?.prop; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -927,6 +1615,33 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj!.prop; + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj!.prop; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -954,6 +1669,33 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj.prop.prop = ''; + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj.prop.prop = ''; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -981,6 +1723,33 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj.prop.doesSomething(); + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj.prop.doesSomething(); + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -1008,6 +1777,33 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj?.prop.prop; + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj?.prop.prop; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -1035,6 +1831,33 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj?.prop?.prop; + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj?.prop?.prop; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -1062,6 +1885,33 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj.prop?.prop; + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj.prop?.prop; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, { code: ` class Test { @@ -1089,5 +1939,32 @@ function ClassWithName {}>(Base: TBase) { }, ], }, + { + code: ` + class Test { + #testObj = {}; + public test(): void { + this.#testObj!.prop?.prop; + } + } + `, + output: ` + class Test { + readonly #testObj = {}; + public test(): void { + this.#testObj!.prop?.prop; + } + } + `, + errors: [ + { + data: { + name: '#testObj', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + }, ], }); From d315bf957a592bf6999830a92dc01694ebc0c873 Mon Sep 17 00:00:00 2001 From: James Browning Date: Thu, 21 Sep 2023 18:13:50 +1200 Subject: [PATCH 2/5] Fix change in prefer-readonly docs --- packages/eslint-plugin/docs/rules/prefer-readonly.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly.md b/packages/eslint-plugin/docs/rules/prefer-readonly.md index 8c5aa938024a..4eaff3ea0623 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly.md @@ -44,13 +44,14 @@ class Container { // Protected members might be modified by child classes protected protectedMember: number; - // These are modified later on by the class + // This is modified later on by the class private modifiedLater = 'unchanged'; public mutate() { this.modifiedLater = 'mutated'; } + // This is modified later on by the class #modifiedLaterPrivateField = 'unchanged'; public mutatePrivateField() { From 0400de54bdbdbcdbfb906760c0024471d312c371 Mon Sep 17 00:00:00 2001 From: James Browning Date: Thu, 21 Sep 2023 18:28:00 +1200 Subject: [PATCH 3/5] Restore missing test --- .../tests/rules/prefer-readonly.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts index bd9159a93ec9..d35ffa66edb6 100644 --- a/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts @@ -525,6 +525,18 @@ function ClassWithName {}>(Base: TBase) { }, { code: ` +class Foo { + private value: Record = {}; + + bar(newValue: Record) { + ({ ...this.value } = newValue); + return this.value; + } +} + `, + }, + { + code: ` class Foo { #value: Record = {}; From 7ffcccc95727fec644c6728cb7790917597b5483 Mon Sep 17 00:00:00 2001 From: James Browning Date: Thu, 19 Oct 2023 23:29:24 +1300 Subject: [PATCH 4/5] Update docs to mention private fields --- packages/eslint-plugin/docs/rules/prefer-readonly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly.md b/packages/eslint-plugin/docs/rules/prefer-readonly.md index 4eaff3ea0623..48d74209ef78 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly.md @@ -6,7 +6,7 @@ description: "Require private members to be marked as `readonly` if they're neve > > See **https://typescript-eslint.io/rules/prefer-readonly** for documentation. -Member variables with the privacy `private` are never permitted to be modified outside of their declaring class. +Private member variables (whether using the `private` modifier, or private fields) are never permitted to be modified outside of their declaring class. If that class never modifies their value, they may safely be marked as `readonly`. This rule reports on private members are marked as `readonly` if they're never modified outside of the constructor. From 397cb609f0ead0a675f0a9b7d6998d681e247d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Thu, 19 Oct 2023 11:39:12 -0400 Subject: [PATCH 5/5] Update packages/eslint-plugin/docs/rules/prefer-readonly.md --- packages/eslint-plugin/docs/rules/prefer-readonly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly.md b/packages/eslint-plugin/docs/rules/prefer-readonly.md index 48d74209ef78..fb4bff187d7b 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly.md @@ -6,7 +6,7 @@ description: "Require private members to be marked as `readonly` if they're neve > > See **https://typescript-eslint.io/rules/prefer-readonly** for documentation. -Private member variables (whether using the `private` modifier, or private fields) are never permitted to be modified outside of their declaring class. +Private member variables (whether using the `private` modifier or private `#` fields) are never permitted to be modified outside of their declaring class. If that class never modifies their value, they may safely be marked as `readonly`. This rule reports on private members are marked as `readonly` if they're never modified outside of the constructor.