-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: readonly method #22315
Comments
I'd like to add the the Readonly mapped type currently work on method event if the readonly keyword it self is not allowed. class SomeClass {
//public readonly SomeMethod() {} // 'readonly' modifier can only appear on a property declaration or index signature.
public readonly SomeOtherMethod() {}
}
const someObject = new SomeClass();
someObject.SomeOtherMethod = () => {}; // OK
const someOtherObject = new SomeClass() as Readonly<typeof SomeClass>
someOtherObject.SomeOtherMethod = () => {}; // Cannot assign to 'SomeOtherMethod ' because it is a constant or a read-only property. A flag under the strict umbrella to make all method readonly by default and force the use of property function for "assignable method" would also be welcome. |
@feeddageek It's allowed when you store method as arrow function class property: class SomeClass {
public readonly SomeOtherMethod = () => {};
}
const someObject = new SomeClass();
someObject.SomeOtherMethod = () => { /* new impl */ }; // error TS2540: Cannot assign to 'SomeOtherMethod' because it is a read-only property. |
Another workaround (based on @feeddageek idea) is to enforce the use of a factory that returns a "readonly version" of the object: class Person {
/** Factory */
static readonly from: (name: string) => Readonly<Person> = (name) => new Person(name)
name: string
protected constructor(name: string) {
this.name = name
}
displayName(): string {
return name
}
}
const p = Person.from("Louise")
p.displayName = () => "Impersonation" // does not compile :) Note hat all properties are also made readonly. |
@patrykuszynski the problem with using readonly function property is that they are not compiled on the prototype, they are build into the instance by the constructor. class Test {
constructor(public property: string) {}
public inProto() {
return `inProto: ${this.property}`;
}
public inConstructor = () => {
return `inConstructor: ${this.property}`;
}
}
const test1 = new Test('Object 1');
const test2 = new Test('Object 2');
console.log(test1.inProto === test2.inProto); // true
console.log(test1.inConstructor === test2.inConstructor); // false
test2.inProto = test1.inProto; // this does nothing since `test1.inProto === test2.inProto`
test2.inConstructor = test1.inConstructor;
console.log(test2.inProto()); // inProto: Object 2
console.log(test2.inConstructor()); // inConstructor: Object 1
const notCapture = test1.inProto;
const capture = test1.inConstructor;
console.log(notCapture()); // inProto: undefined
console.log(capture()); // inConstructor: Object 1 (the capture of errata: |
This is also an issue with namespaces. allowing const/readonly would make this less verbose and making it the default would stop library authors from being surprised if an implementation gets swapped out. namespace Foo {
export function doSomething() {
console.log("Hello World");
}
}
Foo.doSomething = () => { console.log("Overriden") }
Foo.doSomething(); // Prints "Overridden"
namespace SafeFoo {
export const doSomething = () => {
console.log("Hello World");
}
}
SafeFoo.doSomething = () => { console.log("Overriden") } // Becomes an error. |
This would however break declaration merging/module augmentation since you can't define functions in an ambient context. declare module global {
export namespace Foo {
const doSomethingElse : () => void;
}
}
// Defining this function in-place isn't allowed either.
Foo.doSomethingElse = () => { console.log("Do Something else."); } // errors |
FYI, I filed #47003 which is basically the same request, and Ryan replied with a pointer to this issue and a comment that there hasn't been much interest so at this point action is unlikely. It occurred to me that the bar is probably lower to make a linter rule, which I think could be just as effective at catching accidental assignments as a language-level "readonly" modifier. I haven't asked yet but if I do I'll make sure to ping this issue. |
I'd really like to see this happen as currently using classes with one or more methods as a function argument lead to a linter error when https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md is enabled. It's also quite inconsistent that a class property can be made immutable but a method cannot |
Related discussion: #58296 (comment) |
Problem:
A method (function written with the ES6 method syntax) can currently be replaced with another function by a simple assignment.
As an example consider the following snippet of code:
Because the
readonly
modifier is not usable for methods, this is not possible to prevent this kind of assignments.Proposal:
A method is always readonly.
The following codes are identical:
Compatibility:
This is a breaking change. However, method syntax is recent and mostly used in classes. Codes which assign a function to a method are certainly rare.
Temporary workaround:
Do not use method syntax in your classes and interfaces. Note hat this leads to very verbose codes.
If you use an interface, the verbosity is acceptable. However you get also strict variance.
The text was updated successfully, but these errors were encountered: