prefer-readonly-parameter-types
関数の引数を`readonly`として型指定し、意図しない変更を防ぎます。
このルールには以下のものが要求されます。 型情報 を実行するには。
関数の引数を変更すると、混乱を招き、デバッグが困難になる可能性があります。関数の引数を変更しないように暗黙的に覚えているのは簡単ですが、引数を`readonly`として明示的に型指定することで、利用者に対して明確な契約を提供します。この契約により、関数が副作用を持つかどうかを、利用者がより簡単に判断できるようになります。
このルールを使用すると、関数の引数が読み取り専用型になるように強制できます。型は、以下の場合に読み取り専用とみなされます。
- プリミティブ型(`string`、`number`、`boolean`、`symbol`、または列挙型)である場合、
- 関数シグネチャ型である場合、
- その要素型が読み取り専用とみなされる読み取り専用配列型である場合。
- その要素がすべて読み取り専用とみなされる読み取り専用タプル型である場合。
- そのプロパティがすべて読み取り専用としてマークされており、その値がすべて読み取り専用とみなされるオブジェクト型である場合。
module.exports = {
"rules": {
"@typescript-eslint/prefer-readonly-parameter-types": "error"
}
};
Playgroundでこのルールを試してみてください ↗
例
- ❌ 正しくない
- ✅ 正しい
function array1(arg: string[]) {} // array is not readonly
function array2(arg: readonly string[][]) {} // array element is not readonly
function array3(arg: [string, number]) {} // tuple is not readonly
function array4(arg: readonly [string[], number]) {} // tuple element is not readonly
// the above examples work the same if you use ReadonlyArray<T> instead
function object1(arg: { prop: string }) {} // property is not readonly
function object2(arg: { readonly prop: string; prop2: string }) {} // not all properties are readonly
function object3(arg: { readonly prop: { prop2: string } }) {} // nested property is not readonly
// the above examples work the same if you use Readonly<T> instead
interface CustomArrayType extends ReadonlyArray<string> {
prop: string; // note: this property is mutable
}
function custom1(arg: CustomArrayType) {}
interface CustomFunction {
(): void;
prop: string; // note: this property is mutable
}
function custom2(arg: CustomFunction) {}
function union(arg: string[] | ReadonlyArray<number[]>) {} // not all types are readonly
// rule also checks function types
interface Foo {
(arg: string[]): void;
}
interface Foo {
new (arg: string[]): void;
}
const x = { foo(arg: string[]): void {} };
function foo(arg: string[]);
type Foo = (arg: string[]) => void;
interface Foo {
foo(arg: string[]): void;
}
Playgroundで開くfunction array1(arg: readonly string[]) {}
function array2(arg: readonly (readonly string[])[]) {}
function array3(arg: readonly [string, number]) {}
function array4(arg: readonly [readonly string[], number]) {}
// the above examples work the same if you use ReadonlyArray<T> instead
function object1(arg: { readonly prop: string }) {}
function object2(arg: { readonly prop: string; readonly prop2: string }) {}
function object3(arg: { readonly prop: { readonly prop2: string } }) {}
// the above examples work the same if you use Readonly<T> instead
interface CustomArrayType extends ReadonlyArray<string> {
readonly prop: string;
}
function custom1(arg: Readonly<CustomArrayType>) {}
// interfaces that extend the array types are not considered arrays, and thus must be made readonly.
interface CustomFunction {
(): void;
readonly prop: string;
}
function custom2(arg: CustomFunction) {}
function union(arg: readonly string[] | ReadonlyArray<number>) {}
function primitive1(arg: string) {}
function primitive2(arg: number) {}
function primitive3(arg: boolean) {}
function primitive4(arg: unknown) {}
function primitive5(arg: null) {}
function primitive6(arg: undefined) {}
function primitive7(arg: any) {}
function primitive8(arg: never) {}
function primitive9(arg: string | number | undefined) {}
function fnSig(arg: () => void) {}
enum Foo {
a,
b,
}
function enumArg(arg: Foo) {}
function symb1(arg: symbol) {}
const customSymbol = Symbol('a');
function symb2(arg: typeof customSymbol) {}
// function types
interface Foo {
(arg: readonly string[]): void;
}
interface Foo {
new (arg: readonly string[]): void;
}
const x = { foo(arg: readonly string[]): void {} };
function foo(arg: readonly string[]);
type Foo = (arg: readonly string[]) => void;
interface Foo {
foo(arg: readonly string[]): void;
}
Playgroundで開くオプション
このルールは次のオプションを受け入れます。
type Options = [
{
allow?: (
| {
from: 'file';
name: [string, ...string[]] | string;
path?: string;
}
| {
from: 'lib';
name: [string, ...string[]] | string;
}
| {
from: 'package';
name: [string, ...string[]] | string;
package: string;
}
| string
)[];
checkParameterProperties?: boolean;
ignoreInferredTypes?: boolean;
treatMethodsAsReadonly?: boolean;
},
];
const defaultOptions: Options = [
{
allow: [],
checkParameterProperties: true,
ignoreInferredTypes: false,
treatMethodsAsReadonly: false,
},
];
allow
たとえば、`HTMLElement`型や`@types/jquery`からの`JQueryStatic`型など、複雑な型の中には、読み取り専用にするのが容易ではないものがあります。このオプションを使用すると、そのような型のレポートをグローバルに無効にできます。
このオプションは、無視する型指定子の配列を受け取ります。配列内の各項目は、次の形式のいずれかである必要があります。
- ファイルで定義された型(`{ from: "file", name: "Foo", path: "src/foo-file.ts" }`、`path`はプロジェクトルートディレクトリからの相対パス(オプション))
- デフォルトライブラリの型(`{ from: "lib", name: "Foo" }`)
- パッケージからの型(`{ from: "package", name: "Foo", package: "foo-lib" }`、これは型定義パッケージの型にも有効です)。
さらに、型は単純な文字列として定義することもでき、その場合、その起源に関係なく型と一致します。
このルールに関するコードの例(
{
"allow": [
"$",
{ "from": "file", "name": "Foo" },
{ "from": "lib", "name": "HTMLElement" },
{ "from": "package", "name": "Bar", "package": "bar-lib" }
]
}
- ❌ 正しくない
- ✅ 正しい
interface ThisIsMutable {
prop: string;
}
interface Wrapper {
sub: ThisIsMutable;
}
interface WrapperWithOther {
readonly sub: Foo;
otherProp: string;
}
// Incorrect because ThisIsMutable is not readonly
function fn1(arg: ThisIsMutable) {}
// Incorrect because Wrapper.sub is not readonly
function fn2(arg: Wrapper) {}
// Incorrect because WrapperWithOther.otherProp is not readonly and not in the allowlist
function fn3(arg: WrapperWithOther) {}
Playgroundで開くimport { Foo } from 'some-lib';
import { Bar } from 'incorrect-lib';
interface HTMLElement {
prop: string;
}
// Incorrect because Foo is not a local type
function fn1(arg: Foo) {}
// Incorrect because HTMLElement is not from the default library
function fn2(arg: HTMLElement) {}
// Incorrect because Bar is not from "bar-lib"
function fn3(arg: Bar) {}
Playgroundで開くinterface Foo {
prop: string;
}
interface Wrapper {
readonly sub: Foo;
readonly otherProp: string;
}
// Works because Foo is allowed
function fn1(arg: Foo) {}
// Works even when Foo is nested somewhere in the type, with other properties still being checked
function fn2(arg: Wrapper) {}
Playgroundで開くimport { Bar } from 'bar-lib';
interface Foo {
prop: string;
}
// Works because Foo is a local type
function fn1(arg: Foo) {}
// Works because HTMLElement is from the default library
function fn2(arg: HTMLElement) {}
// Works because Bar is from "bar-lib"
function fn3(arg: Bar) {}
Playgroundで開くimport { Foo } from './foo';
// Works because Foo is still a local type - it has to be in the same package
function fn(arg: Foo) {}
Playgroundで開くcheckParameterProperties
このオプションを使用すると、パラメータプロパティのチェックを有効または無効にできます。パラメータプロパティはクラスにプロパティを作成するため、読み取り専用にすることを強制しない方が望ましい場合があります。
{checkParameterProperties: true}
を使用したこのルールのコード例
- ❌ 正しくない
- ✅ 正しい
class Foo {
constructor(private paramProp: string[]) {}
}
Playgroundで開くclass Foo {
constructor(private paramProp: readonly string[]) {}
}
Playgroundで開く{checkParameterProperties: false}
を使用したこのルールの**正しい**コード例
class Foo {
constructor(
private paramProp1: string[],
private paramProp2: readonly string[],
) {}
}
Playgroundで開くignoreInferredTypes
このオプションを使用すると、型を明示的に指定していないパラメータを無視できます。これは、外部の依存関係が変更可能なパラメータを持つコールバックを指定し、コールバックのパラメータを手動で注釈付けすることが望ましくない場合に望ましい場合があります。
{ignoreInferredTypes: true}
を使用したこのルールのコード例
- ❌ 正しくない
- ✅ 正しい
import { acceptsCallback, CallbackOptions } from 'external-dependency';
acceptsCallback((options: CallbackOptions) => {});
Playgroundで開くexternal-dependency.d.ts
export interface CallbackOptions {
prop: string;
}
type Callback = (options: CallbackOptions) => void;
type AcceptsCallback = (callback: Callback) => void;
export const acceptsCallback: AcceptsCallback;
import { acceptsCallback } from 'external-dependency';
acceptsCallback(options => {});
Playgroundで開くexternal-dependency.d.ts
export interface CallbackOptions {
prop: string;
}
type Callback = (options: CallbackOptions) => void;
type AcceptsCallback = (callback: Callback) => void;
export const acceptsCallback: AcceptsCallback;
treatMethodsAsReadonly
このオプションを使用すると、すべての変更可能なメソッドを読み取り専用であるかのように扱うことができます。これは、メソッドを再割り当てしない場合に望ましい場合があります。
{treatMethodsAsReadonly: false}
を使用したこのルールのコード例
- ❌ 正しくない
- ✅ 正しい
type MyType = {
readonly prop: string;
method(): string; // note: this method is mutable
};
function foo(arg: MyType) {}
Playgroundで開くtype MyType = Readonly<{
prop: string;
method(): string;
}>;
function foo(arg: MyType) {}
type MyOtherType = {
readonly prop: string;
readonly method: () => string;
};
function bar(arg: MyOtherType) {}
Playgroundで開く{treatMethodsAsReadonly: true}
を使用したこのルールの**正しい**コード例
type MyType = {
readonly prop: string;
method(): string; // note: this method is mutable
};
function foo(arg: MyType) {}
Playgroundで開く使用しない場合
プロジェクトでパラメータの強い不変性の保証を強制しようとしていない場合は、このルールを避けることができます。
このルールは、変更可能とみなすものに関して非常に厳格です。読み取り専用として自身を記述する多くの型は、配列やタプルなどの変更可能なプロパティを持つため、変更可能とみなされます。これらの制限を回避するには、ルールのオプションを使用する必要がある場合があります。特に、`allow`オプションを使用して、型を読み取り専用として明示的にマークできます。
型チェックされたlintルールは、従来のlintルールよりも強力ですが、型チェックされたlintingの構成も必要です。型チェックされたルールを有効にした後にパフォーマンスの低下が発生する場合は、パフォーマンスに関するトラブルシューティングを参照してください。