diff --git a/.eslintrc b/.eslintrc
index eb00ca638..02f1e9ed4 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -34,8 +34,10 @@
// Our rules.
"ts-immutable/immutable-data": "error",
"ts-immutable/no-let": "error",
- "ts-immutable/readonly-array": ["error", { "ignoreReturnType": true }],
- "ts-immutable/readonly-keyword": "error",
+ "ts-immutable/prefer-readonly-types": [
+ "error",
+ { "ignoreReturnType": true }
+ ],
"ts-immutable/no-method-signature": "error",
"ts-immutable/no-this": "error",
"ts-immutable/no-class": "error",
diff --git a/README.md b/README.md
index 9179a279e..2e9860aaf 100644
--- a/README.md
+++ b/README.md
@@ -99,13 +99,12 @@ In addition to immutable rules this project also contains a few rules for enforc
### Immutability rules
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| ------------------------------------------------------------ | -------------------------------------------------------------------------- | :--------------------------------------------: | :-------------------------------------------------: | :---------------------------------------------: | :------: | :---------------: |
-| [`readonly-keyword`](./docs/rules/readonly-keyword.md) | Enforce readonly modifiers are used where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
-| [`readonly-array`](./docs/rules/readonly-array.md) | Enforce readonly array over mutable arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
-| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
-| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
-| [`no-method-signature`](./docs/rules/no-method-signature.md) | Enforce property signatures with readonly modifiers over method signatures | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |
+| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
+| ---------------------------------------------------------------- | -------------------------------------------------------------------------- | :--------------------------------------------: | :-------------------------------------------------: | :---------------------------------------------: | :------: | :---------------: |
+| [`prefer-readonly-types`](./docs/rules/prefer-readonly-types.md) | Use readonly types and readonly modifiers where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
+| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
+| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
+| [`no-method-signature`](./docs/rules/no-method-signature.md) | Enforce property signatures with readonly modifiers over method signatures | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :thought_balloon: |
### Functional style rules
diff --git a/docs/rules/readonly-keyword.md b/docs/rules/prefer-readonly-types.md
similarity index 67%
rename from docs/rules/readonly-keyword.md
rename to docs/rules/prefer-readonly-types.md
index 35bed3653..752648641 100644
--- a/docs/rules/readonly-keyword.md
+++ b/docs/rules/prefer-readonly-types.md
@@ -1,10 +1,14 @@
-# Enforce readonly modifiers are used where possible (readonly-keyword)
+# Prefer readonly types over mutable types (prefer-readonly-types)
-This rule enforces use of the `readonly` modifier. The `readonly` modifier can appear on property signatures in interfaces, property declarations in classes, and index signatures.
+This rule enforces use of the readonly modifier and readonly types.
## Rule Details
-Below is some information about the `readonly` modifier and the benefits of using it:
+This rule enforces use of `readonly T[]` (`ReadonlyArray`) over `T[]` (`Array`).
+
+The readonly modifier must appear on property signatures in interfaces, property declarations in classes, and index signatures.
+
+### Benefits of using the `readonly` modifier
You might think that using `const` would eliminate mutation from your TypeScript code. **Wrong.** Turns out that there's a pretty big loophole in `const`.
@@ -52,25 +56,66 @@ const foo: { readonly [key: string]: number } = { a: 1, b: 2 };
foo["a"] = 3; // Error: Index signature only permits reading
```
+### Benefits of using `readonly T[]`
+
+Even if an array is declared with `const` it is still possible to mutate the contents of the array.
+
+```typescript
+interface Point {
+ readonly x: number;
+ readonly y: number;
+}
+const points: Array = [{ x: 23, y: 44 }];
+points.push({ x: 1, y: 2 }); // This is legal
+```
+
+Using the `ReadonlyArray` type or `readonly T[]` will stop this mutation:
+
+```typescript
+interface Point {
+ readonly x: number;
+ readonly y: number;
+}
+
+const points: ReadonlyArray = [{ x: 23, y: 44 }];
+// const points: readonly Point[] = [{ x: 23, y: 44 }]; // This is the alternative syntax for the line above
+
+points.push({ x: 1, y: 2 }); // Unresolved method push()
+```
+
## Options
The rule accepts an options object with the following properties:
```typescript
type Options = {
+ readonly checkImplicit: boolean
readonly ignoreClass?: boolean;
readonly ignoreInterface?: boolean;
readonly ignoreLocal?: boolean;
readonly ignorePattern?: string | Array;
+ readonly ignoreReturnType?: boolean;
};
const defaults = {
+ checkImplicit: false,
ignoreClass: false,
ignoreInterface: false,
- ignoreLocal: false
+ ignoreLocal: false,
+ ignoreReturnType: false
};
```
+### `checkImplicit`
+
+By default, this function only checks explicit types. Enabling this option will make the rule also check implicit types.
+
+Note: Checking implicit types is more expensive (slow).
+
+### `ignoreReturnType`
+
+Doesn't check the return type of functions.
+
### `ignoreClass`
A boolean to specify if checking for `readonly` should apply to classes. `false` by default.
diff --git a/docs/rules/readonly-array.md b/docs/rules/readonly-array.md
deleted file mode 100644
index 0f238497c..000000000
--- a/docs/rules/readonly-array.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# Prefer readonly array over mutable arrays (readonly-array)
-
-This rule enforces use of `ReadonlyArray` or `readonly T[]` instead of `Array` or `T[]`.
-
-## Rule Details
-
-Below is some information about the `ReadonlyArray` type and the benefits of using it:
-
-Even if an array is declared with `const` it is still possible to mutate the contents of the array.
-
-```typescript
-interface Point {
- readonly x: number;
- readonly y: number;
-}
-const points: Array = [{ x: 23, y: 44 }];
-points.push({ x: 1, y: 2 }); // This is legal
-```
-
-Using the `ReadonlyArray` type or `readonly T[]` will stop this mutation:
-
-```typescript
-interface Point {
- readonly x: number;
- readonly y: number;
-}
-
-const points: ReadonlyArray = [{ x: 23, y: 44 }];
-// const points: readonly Point[] = [{ x: 23, y: 44 }]; // This is the alternative syntax for the line above
-
-points.push({ x: 1, y: 2 }); // Unresolved method push()
-```
-
-## Options
-
-The rule accepts an options object with the following properties:
-
-```typescript
-type Options = {
- readonly ignoreReturnType?: boolean;
- readonly ignoreLocal?: boolean;
- readonly ignorePattern?: string | Array;
- readonly checkImplicit: boolean
-};
-
-const defaults = {
- ignoreReturnType: false,
- ignoreLocal: false,
- checkImplicit: false
-};
-```
-
-### `checkImplicit`
-
-By default, this function only checks explicit types. Enabling this option will make the rule also check implicit types.
-
-Note: Checking implicit types is more expensive (slow).
-
-### `ignoreReturnType`
-
-Doesn't check the return type of functions.
-
-### `ignoreLocal`
-
-See the [ignoreLocal](./options/ignore-local.md) docs.
-
-### `ignorePattern`
-
-See the [ignorePattern](./options/ignore-pattern.md) docs.
diff --git a/src/configs/all.ts b/src/configs/all.ts
index fd1c59d64..cbeb442bf 100644
--- a/src/configs/all.ts
+++ b/src/configs/all.ts
@@ -17,8 +17,7 @@ const config = {
rules: {
"ts-immutable/no-method-signature": "error",
"ts-immutable/no-mixed-interface": "error",
- "ts-immutable/readonly-array": "error",
- "ts-immutable/readonly-keyword": "error"
+ "ts-immutable/prefer-readonly-types": "error"
}
}
]
diff --git a/src/configs/immutable.ts b/src/configs/immutable.ts
index efff06d63..ab82b994b 100644
--- a/src/configs/immutable.ts
+++ b/src/configs/immutable.ts
@@ -14,8 +14,7 @@ const config = deepMerge([
files: ["*.ts", "*.tsx"],
rules: {
"ts-immutable/no-method-signature": "warn",
- "ts-immutable/readonly-array": "error",
- "ts-immutable/readonly-keyword": "error"
+ "ts-immutable/prefer-readonly-types": "error"
}
}
]
diff --git a/src/rules/index.ts b/src/rules/index.ts
index bbcec770d..5f5a645d5 100644
--- a/src/rules/index.ts
+++ b/src/rules/index.ts
@@ -37,13 +37,9 @@ import { name as noThisRuleName, rule as noThisRule } from "./no-this";
import { name as noThrowRuleName, rule as noThrowRule } from "./no-throw";
import { name as noTryRuleName, rule as noTryRule } from "./no-try";
import {
- name as readonlyArrayRuleName,
- rule as readonlyArrayRule
-} from "./readonly-array";
-import {
- name as readonlyKeywordRuleName,
- rule as readonlyKeywordRule
-} from "./readonly-keyword";
+ name as preferReadonlyTypesRuleName,
+ rule as preferReadonlyTypesRule
+} from "./prefer-readonly-types";
/**
* All of the custom rules.
@@ -63,6 +59,5 @@ export const rules = {
[noThisRuleName]: noThisRule,
[noThrowRuleName]: noThrowRule,
[noTryRuleName]: noTryRule,
- [readonlyArrayRuleName]: readonlyArrayRule,
- [readonlyKeywordRuleName]: readonlyKeywordRule
+ [preferReadonlyTypesRuleName]: preferReadonlyTypesRule
};
diff --git a/src/rules/readonly-array.ts b/src/rules/prefer-readonly-types.ts
similarity index 70%
rename from src/rules/readonly-array.ts
rename to src/rules/prefer-readonly-types.ts
index b2b59fab7..a65de1934 100644
--- a/src/rules/readonly-array.ts
+++ b/src/rules/prefer-readonly-types.ts
@@ -21,15 +21,20 @@ import {
isFunctionLike,
isIdentifier,
isTSArrayType,
+ isTSIndexSignature,
+ isTSParameterProperty,
+ isTSTupleType,
isTSTypeOperator
} from "../util/typeguard";
// The name of this rule.
-export const name = "readonly-array" as const;
+export const name = "prefer-readonly-types" as const;
// The options this rule can take.
type Options = ignore.IgnoreLocalOption &
ignore.IgnorePatternOption &
+ ignore.IgnoreClassOption &
+ ignore.IgnoreInterfaceOption &
ignore.IgnoreReturnTypeOption & {
readonly checkImplicit: boolean;
};
@@ -39,6 +44,8 @@ const schema: JSONSchema4 = [
deepMerge([
ignore.ignoreLocalOptionSchema,
ignore.ignorePatternOptionSchema,
+ ignore.ignoreClassOptionSchema,
+ ignore.ignoreInterfaceOptionSchema,
ignore.ignoreReturnTypeOptionSchema,
{
type: "object",
@@ -54,15 +61,20 @@ const schema: JSONSchema4 = [
// The default options for the rule.
const defaultOptions: Options = {
+ checkImplicit: false,
+ ignoreClass: false,
+ ignoreInterface: false,
ignoreLocal: false,
- ignoreReturnType: false,
- checkImplicit: false
+ ignoreReturnType: false
};
// The possible error messages.
const errorMessages = {
- generic: "Only readonly arrays allowed.",
- implicit: "Implicitly a mutable array. Only readonly arrays allowed."
+ array: "Only readonly arrays allowed.",
+ implicit: "Implicitly a mutable array. Only readonly arrays allowed.",
+ property: "A readonly modifier is required.",
+ tuple: "Only readonly tuples allowed.",
+ type: "Only readonly types allowed."
} as const;
// The meta data for this rule.
@@ -78,6 +90,11 @@ const meta: RuleMetaData = {
schema
};
+const mutableToImmutableTypes: ReadonlyMap = new Map<
+ string,
+ string
+>([["Array", "ReadonlyArray"], ["Map", "ReadonlyMap"], ["Set", "ReadonlySet"]]);
+
/**
* Check if the given ArrayType or TupleType violates this rule.
*/
@@ -96,7 +113,7 @@ function checkArrayOrTupleType(
? [
{
node,
- messageId: "generic",
+ messageId: isTSTupleType(node) ? "tuple" : "array",
fix:
node.parent && isTSArrayType(node.parent)
? fixer => [
@@ -117,21 +134,56 @@ function checkTypeReference(
node: TSESTree.TSTypeReference,
context: RuleContext,
options: Options
+): RuleResult {
+ if (isIdentifier(node.typeName)) {
+ const immutableType = mutableToImmutableTypes.get(node.typeName.name);
+ return {
+ context,
+ descriptors:
+ immutableType && (!options.ignoreReturnType || !isInReturnType(node))
+ ? [
+ {
+ node,
+ messageId: "type",
+ fix: fixer => fixer.replaceText(node.typeName, immutableType)
+ }
+ ]
+ : []
+ };
+ } else {
+ return {
+ context,
+ descriptors: []
+ };
+ }
+}
+
+/**
+ * Check if the given property/signature node violates this rule.
+ */
+function checkProperty(
+ node:
+ | TSESTree.ClassProperty
+ | TSESTree.TSIndexSignature
+ | TSESTree.TSParameterProperty
+ | TSESTree.TSPropertySignature,
+ context: RuleContext
): RuleResult {
return {
context,
- descriptors:
- isIdentifier(node.typeName) &&
- node.typeName.name === "Array" &&
- (!options.ignoreReturnType || !isInReturnType(node))
- ? [
- {
- node,
- messageId: "generic",
- fix: fixer => fixer.insertTextBefore(node, "Readonly")
- }
- ]
- : []
+ descriptors: node.readonly
+ ? []
+ : [
+ {
+ node,
+ messageId: "property",
+ fix: isTSIndexSignature(node)
+ ? fixer => fixer.insertTextBefore(node, "readonly ")
+ : isTSParameterProperty(node)
+ ? fixer => fixer.insertTextBefore(node.parameter, "readonly ")
+ : fixer => fixer.insertTextBefore(node.key, "readonly ")
+ }
+ ]
};
}
@@ -203,12 +255,17 @@ export const rule = createRule(
options
);
const _checkTypeReference = checkNode(checkTypeReference, context, options);
+ const _checkProperty = checkNode(checkProperty, context, options);
const _checkImplicitType = checkNode(checkImplicitType, context, options);
return {
TSArrayType: _checkArrayOrTupleType,
TSTupleType: _checkArrayOrTupleType,
TSTypeReference: _checkTypeReference,
+ ClassProperty: _checkProperty,
+ TSIndexSignature: _checkProperty,
+ TSParameterProperty: _checkProperty,
+ TSPropertySignature: _checkProperty,
...(options.checkImplicit
? {
VariableDeclaration: _checkImplicitType,
diff --git a/src/rules/readonly-keyword.ts b/src/rules/readonly-keyword.ts
deleted file mode 100644
index a3bbd9ddc..000000000
--- a/src/rules/readonly-keyword.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { TSESTree } from "@typescript-eslint/typescript-estree";
-import { all as deepMerge } from "deepmerge";
-import { JSONSchema4 } from "json-schema";
-
-import * as ignore from "../common/ignore-options";
-import {
- checkNode,
- createRule,
- RuleContext,
- RuleMetaData,
- RuleResult
-} from "../util/rule";
-import { isTSIndexSignature, isTSParameterProperty } from "../util/typeguard";
-
-// The name of this rule.
-export const name = "readonly-keyword" as const;
-
-// The options this rule can take.
-type Options = ignore.IgnoreLocalOption &
- ignore.IgnorePatternOption &
- ignore.IgnoreClassOption &
- ignore.IgnoreInterfaceOption;
-
-// The schema for the rule options.
-const schema: JSONSchema4 = [
- deepMerge([
- ignore.ignoreLocalOptionSchema,
- ignore.ignorePatternOptionSchema,
- ignore.ignoreClassOptionSchema,
- ignore.ignoreInterfaceOptionSchema
- ])
-];
-
-// The default options for the rule.
-const defaultOptions: Options = {
- ignoreClass: false,
- ignoreInterface: false,
- ignoreLocal: false
-};
-
-// The possible error messages.
-const errorMessages = {
- generic: "A readonly modifier is required."
-} as const;
-
-// The meta data for this rule.
-const meta: RuleMetaData = {
- type: "suggestion",
- docs: {
- description: "Enforce readonly modifiers are used where possible.",
- category: "Best Practices",
- recommended: "error"
- },
- messages: errorMessages,
- fixable: "code",
- schema
-};
-
-/**
- * Check if the given node violates this rule.
- */
-function check(
- node:
- | TSESTree.TSPropertySignature
- | TSESTree.TSIndexSignature
- | TSESTree.ClassProperty
- | TSESTree.TSParameterProperty,
- context: RuleContext
-): RuleResult {
- return {
- context,
- descriptors: node.readonly
- ? []
- : [
- {
- node,
- messageId: "generic",
- fix: isTSIndexSignature(node)
- ? fixer => fixer.insertTextBefore(node, "readonly ")
- : isTSParameterProperty(node)
- ? fixer => fixer.insertTextBefore(node.parameter, "readonly ")
- : fixer => fixer.insertTextBefore(node.key, "readonly ")
- }
- ]
- };
-}
-
-// Create the rule.
-export const rule = createRule(
- name,
- meta,
- defaultOptions,
- (context, options) => {
- const _checkNode = checkNode(check, context, options);
-
- return {
- ClassProperty: _checkNode,
- TSIndexSignature: _checkNode,
- TSPropertySignature: _checkNode,
- TSParameterProperty: _checkNode
- };
- }
-);
diff --git a/src/util/typeguard.ts b/src/util/typeguard.ts
index 5b6f15d04..c9e4e173a 100644
--- a/src/util/typeguard.ts
+++ b/src/util/typeguard.ts
@@ -179,6 +179,12 @@ export function isTSPropertySignature(
return node.type === AST_NODE_TYPES.TSPropertySignature;
}
+export function isTSTupleType(
+ node: TSESTree.Node
+): node is TSESTree.TSTupleType {
+ return node.type === AST_NODE_TYPES.TSTupleType;
+}
+
export function isTSTypeAliasDeclaration(
node: TSESTree.Node
): node is TSESTree.TSTypeAliasDeclaration {
diff --git a/tests/rules/_work.test.ts b/tests/rules/_work.test.ts
index 21f390b99..76a9a13ae 100644
--- a/tests/rules/_work.test.ts
+++ b/tests/rules/_work.test.ts
@@ -9,7 +9,7 @@ import { RuleTester } from "eslint";
* Step 1.
* Import the rule to test.
*/
-import { rule } from "../../src/rules/readonly-array";
+import { rule } from "../../src/rules/prefer-readonly-types";
import { typescript } from "../configs";
diff --git a/tests/rules/prefer-readonly-types.test.ts b/tests/rules/prefer-readonly-types.test.ts
new file mode 100644
index 000000000..a604b0b12
--- /dev/null
+++ b/tests/rules/prefer-readonly-types.test.ts
@@ -0,0 +1,1143 @@
+/**
+ * @file Tests for prefer-readonly-types.
+ */
+
+import dedent from "dedent";
+import { RuleTester } from "eslint";
+
+import { name, rule } from "../../src/rules/prefer-readonly-types";
+
+import { typescript } from "../configs";
+import {
+ InvalidTestCase,
+ processInvalidTestCase,
+ processValidTestCase,
+ ValidTestCase
+} from "../util";
+
+// Valid test cases.
+const valid: ReadonlyArray = [
+ // Should not fail on explicit ReadonlyArray parameter.
+ {
+ code: dedent`
+ function foo(...numbers: ReadonlyArray) {
+ }`,
+ optionsSet: [[]]
+ },
+ {
+ code: dedent`
+ function foo(...numbers: readonly number[]) {
+ }`,
+ optionsSet: [[]]
+ },
+ // Should not fail on explicit ReadonlyArray return type.
+ {
+ code: dedent`
+ function foo(): ReadonlyArray {
+ return [1, 2, 3];
+ }`,
+ optionsSet: [[]]
+ },
+ {
+ code: dedent`
+ const foo = (): ReadonlyArray => {
+ return [1, 2, 3];
+ }`,
+ optionsSet: [[]]
+ },
+ // ReadonlyArray Tuple.
+ {
+ code: dedent`
+ function foo(tuple: readonly [number, string, readonly [number, string]]) {
+ }`,
+ optionsSet: [[]]
+ },
+ // Should not fail on ReadonlyArray type alias.
+ {
+ code: `type Foo = ReadonlyArray;`,
+ optionsSet: [[]]
+ },
+ // Should not fail on ReadonlyArray type alias in local type.
+ {
+ code: dedent`
+ function foo() {
+ type Foo = ReadonlyArray;
+ }`,
+ optionsSet: [[]]
+ },
+ // Should not fail on ReadonlyArray in variable declaration.
+ {
+ code: `const foo: ReadonlyArray = [];`,
+ optionsSet: [[]]
+ },
+ // Ignore return type.
+ {
+ code: dedent`
+ function foo(...numbers: ReadonlyArray): Array {}
+ function bar(...numbers: readonly number[]): number[] {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type.
+ {
+ code: dedent`
+ const foo = function(...numbers: ReadonlyArray): Array {}
+ const bar = function(...numbers: readonly number[]): number[] {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type.
+ {
+ code: dedent`
+ const foo = (...numbers: ReadonlyArray): Array => {}
+ const bar = (...numbers: readonly number[]): number[] => {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type.
+ {
+ code: dedent`
+ class Foo {
+ foo(...numbers: ReadonlyArray): Array {
+ }
+ }
+ class Bar {
+ foo(...numbers: readonly number[]): number[] {
+ }
+ }`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type with Type Arguments.
+ {
+ code: dedent`
+ function foo(...numbers: ReadonlyArray): Promise> {}
+ function foo(...numbers: ReadonlyArray): Promise {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type with deep Type Arguments.
+ {
+ code: dedent`
+ type Foo = { readonly x: T; };
+ function foo(...numbers: ReadonlyArray): Promise>> {}
+ function foo(...numbers: ReadonlyArray): Promise> {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type with Type Arguments in a tuple.
+ {
+ code: dedent`
+ function foo(...numbers: ReadonlyArray): readonly [number, Array, number] {}
+ function foo(...numbers: ReadonlyArray): readonly [number, number[], number] {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type with Type Arguments Union.
+ {
+ code: dedent`
+ function foo(...numbers: ReadonlyArray): { readonly a: Array } | { readonly b: string[] } {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type with Type Arguments Intersection.
+ {
+ code: dedent`
+ function foo(...numbers: ReadonlyArray): { readonly a: Array } & { readonly b: string[] } {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Ignore return type with Type Arguments Conditional.
+ {
+ code: dedent`
+ function foo(x: T): T extends Array ? string : number[] {}`,
+ optionsSet: [[{ ignoreReturnType: true }]]
+ },
+ // Should not fail on implicit ReadonlyArray type in variable declaration.
+ {
+ code: dedent`
+ const foo = [1, 2, 3] as const`,
+ optionsSet: [[{ checkImplicit: true }]]
+ },
+ // Should not fail on implicit Array.
+ {
+ code: dedent`
+ const foo = [1, 2, 3]
+ function bar(param = [1, 2, 3]) {}`,
+ optionsSet: [[]]
+ },
+ // Interface with readonly modifiers should not produce failures.
+ {
+ code: dedent`
+ interface Foo {
+ readonly a: number,
+ readonly b: ReadonlyArray,
+ readonly c: () => string,
+ readonly d: { readonly [key: string]: string },
+ readonly [key: string]: string,
+ }`,
+ optionsSet: [[]]
+ },
+ // PropertySignature and IndexSignature members without readonly modifier
+ // should produce failures. Also verify that nested members are checked.
+ {
+ code: dedent`
+ interface Foo {
+ readonly a: number,
+ readonly b: ReadonlyArray,
+ readonly c: () => string,
+ readonly d: { readonly [key: string]: string },
+ readonly [key: string]: string,
+ readonly e: {
+ readonly a: number,
+ readonly b: ReadonlyArray,
+ readonly c: () => string,
+ readonly d: { readonly [key: string]: string },
+ readonly [key: string]: string,
+ }
+ }`,
+ optionsSet: [[]]
+ },
+ // Class with parameter properties.
+ {
+ code: dedent`
+ class Klass {
+ constructor (
+ nonParameterProp: string,
+ readonly readonlyProp: string,
+ public readonly publicReadonlyProp: string,
+ protected readonly protectedReadonlyProp: string,
+ private readonly privateReadonlyProp: string,
+ ) { }
+ }`,
+ optionsSet: [[]]
+ },
+ // CallSignature and MethodSignature cannot have readonly modifiers and should
+ // not produce failures.
+ {
+ code: dedent`
+ interface Foo {
+ (): void
+ foo(): void
+ }`,
+ optionsSet: [[]]
+ },
+ // The literal with indexer with readonly modifier should not produce failures.
+ {
+ code: `let foo: { readonly [key: string]: number };`,
+ optionsSet: [[]]
+ },
+ // Type literal in array template parameter with readonly should not produce failures.
+ {
+ code: `type foo = ReadonlyArray<{ readonly type: string, readonly code: string }>;`,
+ optionsSet: [[]]
+ },
+ // Type literal with readonly on members should not produce failures.
+ {
+ code: dedent`
+ let foo: {
+ readonly a: number,
+ readonly b: ReadonlyArray,
+ readonly c: () => string,
+ readonly d: { readonly [key: string]: string }
+ readonly [key: string]: string
+ };`,
+ optionsSet: [[]]
+ },
+ // Ignore Classes.
+ {
+ code: dedent`
+ class Klass {
+ foo: number;
+ private bar: number;
+ static baz: number;
+ private static qux: number;
+ }`,
+ optionsSet: [[{ ignoreClass: true }]]
+ },
+ // Ignore Interfaces.
+ {
+ code: dedent`
+ interface Foo {
+ foo: number,
+ bar: ReadonlyArray,
+ baz: () => string,
+ qux: { [key: string]: string }
+ }`,
+ optionsSet: [[{ ignoreInterface: true }]]
+ },
+ // Ignore Local.
+ {
+ code: dedent`
+ function foo() {
+ let foo: {
+ a: number,
+ b: ReadonlyArray,
+ c: () => string,
+ d: { [key: string]: string },
+ [key: string]: string,
+ readonly d: {
+ a: number,
+ b: ReadonlyArray,
+ c: () => string,
+ d: { [key: string]: string },
+ [key: string]: string,
+ }
+ }
+ };`,
+ optionsSet: [[{ ignoreLocal: true }]]
+ },
+ // Ignore Prefix.
+ {
+ code: dedent`
+ let foo: {
+ mutableA: number,
+ mutableB: ReadonlyArray,
+ mutableC: () => string,
+ mutableD: { readonly [key: string]: string },
+ mutableE: {
+ mutableA: number,
+ mutableB: ReadonlyArray,
+ mutableC: () => string,
+ mutableD: { readonly [key: string]: string },
+ }
+ };`,
+ optionsSet: [[{ ignorePattern: "^mutable" }]]
+ },
+ // Ignore Suffix.
+ {
+ code: dedent`
+ let foo: {
+ aMutable: number,
+ bMutable: ReadonlyArray,
+ cMutable: () => string,
+ dMutable: { readonly [key: string]: string },
+ eMutable: {
+ aMutable: number,
+ bMutable: ReadonlyArray,
+ cMutable: () => string,
+ dMutable: { readonly [key: string]: string },
+ }
+ };`,
+ optionsSet: [[{ ignorePattern: "Mutable$" }]]
+ }
+];
+
+// Invalid test cases.
+const invalid: ReadonlyArray = [
+ {
+ code: dedent`
+ function foo(...numbers: number[]) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(...numbers: readonly number[]) {
+ }`,
+ errors: [
+ {
+ messageId: "array",
+ type: "TSArrayType",
+ line: 1,
+ column: 26
+ }
+ ]
+ },
+ {
+ code: dedent`
+ function foo(...numbers: Array) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(...numbers: ReadonlyArray) {
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 26
+ }
+ ]
+ },
+ {
+ code: dedent`
+ function foo(numbers: Set) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(numbers: ReadonlySet) {
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 23
+ }
+ ]
+ },
+ {
+ code: dedent`
+ function foo(numbers: Map) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(numbers: ReadonlyMap) {
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 23
+ }
+ ]
+ },
+ // Should fail on Array type in interface.
+ {
+ code: dedent`
+ interface Foo {
+ readonly bar: Array
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ interface Foo {
+ readonly bar: ReadonlyArray
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 2,
+ column: 17
+ }
+ ]
+ },
+ // Should fail on Array type in index interface.
+ {
+ code: dedent`
+ interface Foo {
+ readonly [key: string]: {
+ readonly groups: Array
+ }
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ interface Foo {
+ readonly [key: string]: {
+ readonly groups: ReadonlyArray
+ }
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 3,
+ column: 22
+ }
+ ]
+ },
+ // Should fail on Array type as function return type and in local interface.
+ {
+ code: dedent`
+ function foo(): Array {
+ interface Foo {
+ readonly bar: Array
+ }
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(): ReadonlyArray {
+ interface Foo {
+ readonly bar: ReadonlyArray
+ }
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 17
+ },
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 3,
+ column: 19
+ }
+ ]
+ },
+ // Should fail on Array type as function return type and in local interface.
+ {
+ code: dedent`
+ const foo = (): Array => {
+ interface Foo {
+ readonly bar: Array
+ }
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ const foo = (): ReadonlyArray => {
+ interface Foo {
+ readonly bar: ReadonlyArray
+ }
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 17
+ },
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 3,
+ column: 19
+ }
+ ]
+ },
+ // Should fail on shorthand syntax Array type as return type.
+ {
+ code: dedent`
+ function foo(): number[] {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(): readonly number[] {
+ }`,
+ errors: [
+ {
+ messageId: "array",
+ type: "TSArrayType",
+ line: 1,
+ column: 17
+ }
+ ]
+ },
+ // Should fail on shorthand syntax Array type as return type.
+ {
+ code: `const foo = (): number[] => {}`,
+ optionsSet: [[]],
+ output: `const foo = (): readonly number[] => {}`,
+ errors: [
+ {
+ messageId: "array",
+ type: "TSArrayType",
+ line: 1,
+ column: 17
+ }
+ ]
+ },
+ // Should fail inside function.
+ {
+ code: dedent`
+ const foo = function (): string {
+ let bar: Array;
+ };`,
+ optionsSet: [[]],
+ output: dedent`
+ const foo = function (): string {
+ let bar: ReadonlyArray;
+ };`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 2,
+ column: 12
+ }
+ ]
+ },
+ // Tuples.
+ {
+ code: dedent`
+ function foo(tuple: [number, string]) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(tuple: readonly [number, string]) {
+ }`,
+ errors: [
+ {
+ messageId: "tuple",
+ type: "TSTupleType",
+ line: 1,
+ column: 21
+ }
+ ]
+ },
+ {
+ code: dedent`
+ function foo(tuple: [number, string, [number, string]]) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(tuple: readonly [number, string, readonly [number, string]]) {
+ }`,
+ errors: [
+ {
+ messageId: "tuple",
+ type: "TSTupleType",
+ line: 1,
+ column: 21
+ },
+ {
+ messageId: "tuple",
+ type: "TSTupleType",
+ line: 1,
+ column: 38
+ }
+ ]
+ },
+ {
+ code: dedent`
+ function foo(tuple: readonly [number, string, [number, string]]) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(tuple: readonly [number, string, readonly [number, string]]) {
+ }`,
+ errors: [
+ {
+ messageId: "tuple",
+ type: "TSTupleType",
+ line: 1,
+ column: 47
+ }
+ ]
+ },
+ {
+ code: dedent`
+ function foo(tuple: [number, string, readonly [number, string]]) {
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(tuple: readonly [number, string, readonly [number, string]]) {
+ }`,
+ errors: [
+ {
+ messageId: "tuple",
+ type: "TSTupleType",
+ line: 1,
+ column: 21
+ }
+ ]
+ },
+ // Should fail on Array as type literal member as function parameter.
+ {
+ code: dedent`
+ function foo(
+ param1: {
+ readonly bar: Array,
+ readonly baz: ReadonlyArray
+ }
+ ): {
+ readonly bar: Array,
+ readonly baz: ReadonlyArray
+ } {
+ let foo: {
+ readonly bar: Array,
+ readonly baz: ReadonlyArray
+ } = {
+ bar: ["hello"],
+ baz: ["world"]
+ };
+ return foo;
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(
+ param1: {
+ readonly bar: ReadonlyArray,
+ readonly baz: ReadonlyArray
+ }
+ ): {
+ readonly bar: ReadonlyArray,
+ readonly baz: ReadonlyArray
+ } {
+ let foo: {
+ readonly bar: ReadonlyArray,
+ readonly baz: ReadonlyArray
+ } = {
+ bar: ["hello"],
+ baz: ["world"]
+ };
+ return foo;
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 3,
+ column: 19
+ },
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 7,
+ column: 17
+ },
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 11,
+ column: 19
+ }
+ ]
+ },
+ // Should fail on Array type alias.
+ {
+ code: `type Foo = Array;`,
+ optionsSet: [[]],
+ output: `type Foo = ReadonlyArray;`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 12
+ }
+ ]
+ },
+ // Should fail on Array as type member.
+ {
+ code: dedent`
+ function foo() {
+ type Foo = {
+ readonly bar: Array
+ }
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo() {
+ type Foo = {
+ readonly bar: ReadonlyArray
+ }
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 3,
+ column: 19
+ }
+ ]
+ },
+ // Should fail on Array type alias in local type.
+ {
+ code: dedent`
+ function foo() {
+ type Foo = Array;
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo() {
+ type Foo = ReadonlyArray;
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 2,
+ column: 14
+ }
+ ]
+ },
+ // Should fail on Array as type member in local type.
+ {
+ code: dedent`
+ function foo() {
+ type Foo = {
+ readonly bar: Array
+ }
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo() {
+ type Foo = {
+ readonly bar: ReadonlyArray
+ }
+ }`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 3,
+ column: 19
+ }
+ ]
+ },
+ // Should fail on Array type in variable declaration.
+ {
+ code: `const foo: Array = [];`,
+ optionsSet: [[]],
+ output: `const foo: ReadonlyArray = [];`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 12
+ }
+ ]
+ },
+ // Should fail on shorthand Array syntax.
+ {
+ code: `const foo: number[] = [1, 2, 3];`,
+ optionsSet: [[]],
+ output: `const foo: readonly number[] = [1, 2, 3];`,
+ errors: [
+ {
+ messageId: "array",
+ type: "TSArrayType",
+ line: 1,
+ column: 12
+ }
+ ]
+ },
+ // Should fail on Array type being used as template param.
+ {
+ code: `let x: Foo>;`,
+ optionsSet: [[]],
+ output: `let x: Foo>;`,
+ errors: [
+ {
+ messageId: "type",
+ type: "TSTypeReference",
+ line: 1,
+ column: 12
+ }
+ ]
+ },
+ // Should fail on nested shorthand arrays.
+ {
+ code: `let x: readonly string[][];`,
+ optionsSet: [[]],
+ output: `let x: readonly (readonly string[])[];`,
+ errors: [
+ {
+ messageId: "array",
+ type: "TSArrayType",
+ line: 1,
+ column: 17
+ }
+ ]
+ },
+ // Should fail on implicit Array type in variable declaration.
+ {
+ code: dedent`
+ const foo = [1, 2, 3]
+ function bar(param = [1, 2, 3]) {}`,
+ optionsSet: [[{ checkImplicit: true }]],
+ output: dedent`
+ const foo: readonly unknown[] = [1, 2, 3]
+ function bar(param: readonly unknown[] = [1, 2, 3]) {}`,
+ errors: [
+ {
+ messageId: "implicit",
+ type: "VariableDeclarator",
+ line: 1,
+ column: 7
+ },
+ {
+ messageId: "implicit",
+ type: "AssignmentPattern",
+ line: 2,
+ column: 14
+ }
+ ]
+ },
+ // Class Property Signatures.
+ {
+ code: dedent`
+ class Klass {
+ foo: number;
+ private bar: number;
+ static baz: number;
+ private static qux: number;
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ class Klass {
+ readonly foo: number;
+ private readonly bar: number;
+ static readonly baz: number;
+ private static readonly qux: number;
+ }`,
+ errors: [
+ {
+ messageId: "property",
+ type: "ClassProperty",
+ line: 2,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "ClassProperty",
+ line: 3,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "ClassProperty",
+ line: 4,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "ClassProperty",
+ line: 5,
+ column: 3
+ }
+ ]
+ },
+ // Class Parameter Properties.
+ {
+ code: dedent`
+ class Klass {
+ constructor (
+ public publicProp: string,
+ protected protectedProp: string,
+ private privateProp: string,
+ ) { }
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ class Klass {
+ constructor (
+ public readonly publicProp: string,
+ protected readonly protectedProp: string,
+ private readonly privateProp: string,
+ ) { }
+ }`,
+ errors: [
+ {
+ messageId: "property",
+ type: "TSParameterProperty",
+ line: 3,
+ column: 5
+ },
+ {
+ messageId: "property",
+ type: "TSParameterProperty",
+ line: 4,
+ column: 5
+ },
+ {
+ messageId: "property",
+ type: "TSParameterProperty",
+ line: 5,
+ column: 5
+ }
+ ]
+ },
+ // Interface Index Signatures.
+ {
+ code: dedent`
+ interface Foo {
+ [key: string]: string
+ }
+ interface Bar {
+ [key: string]: { prop: string }
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ interface Foo {
+ readonly [key: string]: string
+ }
+ interface Bar {
+ readonly [key: string]: { readonly prop: string }
+ }`,
+ errors: [
+ {
+ messageId: "property",
+ type: "TSIndexSignature",
+ line: 2,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSIndexSignature",
+ line: 5,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 5,
+ column: 20
+ }
+ ]
+ },
+ // Function Index Signatures.
+ {
+ code: dedent`
+ function foo(): { [source: string]: string } {
+ return undefined;
+ }
+ function bar(param: { [source: string]: string }): void {
+ return undefined;
+ }`,
+ optionsSet: [[]],
+ output: dedent`
+ function foo(): { readonly [source: string]: string } {
+ return undefined;
+ }
+ function bar(param: { readonly [source: string]: string }): void {
+ return undefined;
+ }`,
+ errors: [
+ {
+ messageId: "property",
+ type: "TSIndexSignature",
+ line: 1,
+ column: 19
+ },
+ {
+ messageId: "property",
+ type: "TSIndexSignature",
+ line: 4,
+ column: 23
+ }
+ ]
+ },
+ // Type literal with indexer without readonly modifier should produce failures.
+ {
+ code: `let foo: { [key: string]: number };`,
+ optionsSet: [[]],
+ output: `let foo: { readonly [key: string]: number };`,
+ errors: [
+ {
+ messageId: "property",
+ type: "TSIndexSignature",
+ line: 1,
+ column: 12
+ }
+ ]
+ },
+ // Type literal in property template parameter without readonly should produce failures.
+ {
+ code: dedent`
+ type foo = ReadonlyArray<{
+ type: string,
+ code: string,
+ }>;`,
+ optionsSet: [[]],
+ output: dedent`
+ type foo = ReadonlyArray<{
+ readonly type: string,
+ readonly code: string,
+ }>;`,
+ errors: [
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 2,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 3,
+ column: 3
+ }
+ ]
+ },
+ // Type literal without readonly on members should produce failures.
+ // Also verify that nested members are checked.
+ {
+ code: dedent`
+ let foo: {
+ a: number,
+ b: ReadonlyArray,
+ c: () => string,
+ d: { readonly [key: string]: string },
+ [key: string]: string,
+ readonly e: {
+ a: number,
+ b: ReadonlyArray,
+ c: () => string,
+ d: { readonly [key: string]: string },
+ [key: string]: string,
+ }
+ };`,
+ optionsSet: [[]],
+ output: dedent`
+ let foo: {
+ readonly a: number,
+ readonly b: ReadonlyArray,
+ readonly c: () => string,
+ readonly d: { readonly [key: string]: string },
+ readonly [key: string]: string,
+ readonly e: {
+ readonly a: number,
+ readonly b: ReadonlyArray,
+ readonly c: () => string,
+ readonly d: { readonly [key: string]: string },
+ readonly [key: string]: string,
+ }
+ };`,
+ errors: [
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 2,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 3,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 4,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 5,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSIndexSignature",
+ line: 6,
+ column: 3
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 8,
+ column: 5
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 9,
+ column: 5
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 10,
+ column: 5
+ },
+ {
+ messageId: "property",
+ type: "TSPropertySignature",
+ line: 11,
+ column: 5
+ },
+ {
+ messageId: "property",
+ type: "TSIndexSignature",
+ line: 12,
+ column: 5
+ }
+ ]
+ }
+];
+
+describe("TypeScript", () => {
+ const ruleTester = new RuleTester(typescript);
+ ruleTester.run(name, rule, {
+ valid: processValidTestCase(valid),
+ invalid: processInvalidTestCase(invalid)
+ });
+});
diff --git a/tests/rules/readonly-array.test.ts b/tests/rules/readonly-array.test.ts
deleted file mode 100644
index 51eb2e4c0..000000000
--- a/tests/rules/readonly-array.test.ts
+++ /dev/null
@@ -1,659 +0,0 @@
-/**
- * @file Tests for readonly-array.
- */
-
-import dedent from "dedent";
-import { RuleTester } from "eslint";
-
-import { name, rule } from "../../src/rules/readonly-array";
-
-import { typescript } from "../configs";
-import {
- InvalidTestCase,
- processInvalidTestCase,
- processValidTestCase,
- ValidTestCase
-} from "../util";
-
-// Valid test cases.
-const valid: ReadonlyArray = [
- // Should not fail on explicit ReadonlyArray parameter.
- {
- code: dedent`
- function foo(...numbers: ReadonlyArray) {
- }`,
- optionsSet: [[]]
- },
- {
- code: dedent`
- function foo(...numbers: readonly number[]) {
- }`,
- optionsSet: [[]]
- },
- // Should not fail on explicit ReadonlyArray return type.
- {
- code: dedent`
- function foo(): ReadonlyArray {
- return [1, 2, 3];
- }`,
- optionsSet: [[]]
- },
- {
- code: dedent`
- const foo = (): ReadonlyArray => {
- return [1, 2, 3];
- }`,
- optionsSet: [[]]
- },
- // ReadonlyArray Tuple.
- {
- code: dedent`
- function foo(tuple: readonly [number, string, readonly [number, string]]) {
- }`,
- optionsSet: [[]]
- },
- // Should not fail on ReadonlyArray type alias.
- {
- code: `type Foo = ReadonlyArray;`,
- optionsSet: [[]]
- },
- // Should not fail on ReadonlyArray type alias in local type.
- {
- code: dedent`
- function foo() {
- type Foo = ReadonlyArray;
- }`,
- optionsSet: [[]]
- },
- // Should not fail on ReadonlyArray in variable declaration.
- {
- code: `const foo: ReadonlyArray = [];`,
- optionsSet: [[]]
- },
- // Ignore return type.
- {
- code: dedent`
- function foo(...numbers: ReadonlyArray): Array {}
- function bar(...numbers: readonly number[]): number[] {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type.
- {
- code: dedent`
- const foo = function(...numbers: ReadonlyArray): Array {}
- const bar = function(...numbers: readonly number[]): number[] {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type.
- {
- code: dedent`
- const foo = (...numbers: ReadonlyArray): Array => {}
- const bar = (...numbers: readonly number[]): number[] => {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type.
- {
- code: dedent`
- class Foo {
- foo(...numbers: ReadonlyArray): Array {
- }
- }
- class Bar {
- foo(...numbers: readonly number[]): number[] {
- }
- }`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type with Type Arguments.
- {
- code: dedent`
- function foo(...numbers: ReadonlyArray): Promise> {}
- function foo(...numbers: ReadonlyArray): Promise {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type with deep Type Arguments.
- {
- code: dedent`
- type Foo = { x: T; };
- function foo(...numbers: ReadonlyArray): Promise>> {}
- function foo(...numbers: ReadonlyArray): Promise> {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type with Type Arguments in a tuple.
- {
- code: dedent`
- function foo(...numbers: ReadonlyArray): readonly [number, Array, number] {}
- function foo(...numbers: ReadonlyArray): readonly [number, number[], number] {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type with Type Arguments Union.
- {
- code: dedent`
- function foo(...numbers: ReadonlyArray): { a: Array } | { b: string[] } {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type with Type Arguments Intersection.
- {
- code: dedent`
- function foo(...numbers: ReadonlyArray): { a: Array } & { b: string[] } {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Ignore return type with Type Arguments Conditional.
- {
- code: dedent`
- function foo(x: T): T extends Array ? string : number[] {}`,
- optionsSet: [[{ ignoreReturnType: true }]]
- },
- // Should not fail on implicit ReadonlyArray type in variable declaration.
- {
- code: dedent`
- const foo = [1, 2, 3] as const`,
- optionsSet: [[{ checkImplicit: true }]]
- },
- // Should not fail on implicit Array.
- {
- code: dedent`
- const foo = [1, 2, 3]
- function bar(param = [1, 2, 3]) {}`,
- optionsSet: [[]]
- }
-];
-
-// Invalid test cases.
-const invalid: ReadonlyArray = [
- {
- code: dedent`
- function foo(...numbers: number[]) {
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(...numbers: readonly number[]) {
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSArrayType",
- line: 1,
- column: 26
- }
- ]
- },
- {
- code: dedent`
- function foo(...numbers: Array) {
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(...numbers: ReadonlyArray) {
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 1,
- column: 26
- }
- ]
- },
- // Should fail on Array type in interface.
- {
- code: dedent`
- interface Foo {
- bar: Array
- }`,
- optionsSet: [[]],
- output: dedent`
- interface Foo {
- bar: ReadonlyArray
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 2,
- column: 8
- }
- ]
- },
- // Should fail on Array type in index interface.
- {
- code: dedent`interface Foo {
- [key: string]: {
- groups: Array
- }
- }`,
- optionsSet: [[]],
- output: dedent`interface Foo {
- [key: string]: {
- groups: ReadonlyArray
- }
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 3,
- column: 13
- }
- ]
- },
- // Should fail on Array type as function return type and in local interface.
- {
- code: dedent`
- function foo(): Array {
- interface Foo {
- bar: Array
- }
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(): ReadonlyArray {
- interface Foo {
- bar: ReadonlyArray
- }
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 1,
- column: 17
- },
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 3,
- column: 10
- }
- ]
- },
- // Should fail on Array type as function return type and in local interface.
- {
- code: dedent`
- const foo = (): Array => {
- interface Foo {
- bar: Array
- }
- }`,
- optionsSet: [[]],
- output: dedent`
- const foo = (): ReadonlyArray => {
- interface Foo {
- bar: ReadonlyArray
- }
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 1,
- column: 17
- },
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 3,
- column: 10
- }
- ]
- },
- // Should fail on shorthand syntax Array type as return type.
- {
- code: dedent`
- function foo(): number[] {
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(): readonly number[] {
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSArrayType",
- line: 1,
- column: 17
- }
- ]
- },
- // Should fail on shorthand syntax Array type as return type.
- {
- code: `const foo = (): number[] => {}`,
- optionsSet: [[]],
- output: `const foo = (): readonly number[] => {}`,
- errors: [
- {
- messageId: "generic",
- type: "TSArrayType",
- line: 1,
- column: 17
- }
- ]
- },
- // Should fail inside function.
- {
- code: dedent`
- const foo = function (): string {
- let bar: Array;
- };`,
- optionsSet: [[]],
- output: dedent`
- const foo = function (): string {
- let bar: ReadonlyArray;
- };`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 2,
- column: 12
- }
- ]
- },
- // Tuples.
- {
- code: dedent`
- function foo(tuple: [number, string]) {
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(tuple: readonly [number, string]) {
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTupleType",
- line: 1,
- column: 21
- }
- ]
- },
- {
- code: dedent`
- function foo(tuple: [number, string, [number, string]]) {
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(tuple: readonly [number, string, readonly [number, string]]) {
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTupleType",
- line: 1,
- column: 21
- },
- {
- messageId: "generic",
- type: "TSTupleType",
- line: 1,
- column: 38
- }
- ]
- },
- {
- code: dedent`
- function foo(tuple: readonly [number, string, [number, string]]) {
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(tuple: readonly [number, string, readonly [number, string]]) {
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTupleType",
- line: 1,
- column: 47
- }
- ]
- },
- {
- code: dedent`
- function foo(tuple: [number, string, readonly [number, string]]) {
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(tuple: readonly [number, string, readonly [number, string]]) {
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTupleType",
- line: 1,
- column: 21
- }
- ]
- },
- // Should fail on Array as type literal member as function parameter.
- {
- code: dedent`
- function foo(
- param1: {
- bar: Array,
- baz: ReadonlyArray
- }
- ): {
- bar: Array,
- baz: ReadonlyArray
- } {
- let foo: {
- bar: Array,
- baz: ReadonlyArray
- } = {
- bar: ["hello"],
- baz: ["world"]
- };
- return foo;
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(
- param1: {
- bar: ReadonlyArray,
- baz: ReadonlyArray
- }
- ): {
- bar: ReadonlyArray,
- baz: ReadonlyArray
- } {
- let foo: {
- bar: ReadonlyArray,
- baz: ReadonlyArray
- } = {
- bar: ["hello"],
- baz: ["world"]
- };
- return foo;
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 3,
- column: 10
- },
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 7,
- column: 8
- },
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 11,
- column: 10
- }
- ]
- },
- // Should fail on Array type alias.
- {
- code: `type Foo = Array;`,
- optionsSet: [[]],
- output: `type Foo = ReadonlyArray;`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 1,
- column: 12
- }
- ]
- },
- // Should fail on Array as type member.
- {
- code: dedent`
- function foo() {
- type Foo = {
- bar: Array
- }
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo() {
- type Foo = {
- bar: ReadonlyArray
- }
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 3,
- column: 10
- }
- ]
- },
- // Should fail on Array type alias in local type.
- {
- code: dedent`
- function foo() {
- type Foo = Array;
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo() {
- type Foo = ReadonlyArray;
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 2,
- column: 14
- }
- ]
- },
- // Should fail on Array as type member in local type.
- {
- code: dedent`
- function foo() {
- type Foo = {
- bar: Array
- }
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo() {
- type Foo = {
- bar: ReadonlyArray
- }
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 3,
- column: 10
- }
- ]
- },
- // Should fail on Array type in variable declaration.
- {
- code: `const foo: Array = [];`,
- optionsSet: [[]],
- output: `const foo: ReadonlyArray = [];`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 1,
- column: 12
- }
- ]
- },
- // Should fail on shorthand Array syntax.
- {
- code: `const foo: number[] = [1, 2, 3];`,
- optionsSet: [[]],
- output: `const foo: readonly number[] = [1, 2, 3];`,
- errors: [
- {
- messageId: "generic",
- type: "TSArrayType",
- line: 1,
- column: 12
- }
- ]
- },
- // Should fail on Array type being used as template param.
- {
- code: `let x: Foo>;`,
- optionsSet: [[]],
- output: `let x: Foo>;`,
- errors: [
- {
- messageId: "generic",
- type: "TSTypeReference",
- line: 1,
- column: 12
- }
- ]
- },
- // Should fail on nested shorthand arrays.
- {
- code: `let x: readonly string[][];`,
- optionsSet: [[]],
- output: `let x: readonly (readonly string[])[];`,
- errors: [
- {
- messageId: "generic",
- type: "TSArrayType",
- line: 1,
- column: 17
- }
- ]
- },
- // Should fail on implicit Array type in variable declaration.
- {
- code: dedent`
- const foo = [1, 2, 3]
- function bar(param = [1, 2, 3]) {}`,
- optionsSet: [[{ checkImplicit: true }]],
- output: dedent`
- const foo: readonly unknown[] = [1, 2, 3]
- function bar(param: readonly unknown[] = [1, 2, 3]) {}`,
- errors: [
- {
- messageId: "implicit",
- type: "VariableDeclarator",
- line: 1,
- column: 7
- },
- {
- messageId: "implicit",
- type: "AssignmentPattern",
- line: 2,
- column: 14
- }
- ]
- }
-];
-
-describe("TypeScript", () => {
- const ruleTester = new RuleTester(typescript);
- ruleTester.run(name, rule, {
- valid: processValidTestCase(valid),
- invalid: processInvalidTestCase(invalid)
- });
-});
diff --git a/tests/rules/readonly-keyword.test.ts b/tests/rules/readonly-keyword.test.ts
deleted file mode 100644
index 94c8912b9..000000000
--- a/tests/rules/readonly-keyword.test.ts
+++ /dev/null
@@ -1,480 +0,0 @@
-/**
- * @file Tests for readonly-keyword.
- */
-
-import dedent from "dedent";
-import { RuleTester } from "eslint";
-
-import { name, rule } from "../../src/rules/readonly-keyword";
-
-import { typescript } from "../configs";
-import {
- InvalidTestCase,
- processInvalidTestCase,
- processValidTestCase,
- ValidTestCase
-} from "../util";
-
-// Valid test cases.
-const valid: ReadonlyArray = [
- // Interface with readonly modifiers should not produce failures.
- {
- code: dedent`
- interface Foo {
- readonly a: number,
- readonly b: Array,
- readonly c: () => string,
- readonly d: { readonly [key: string]: string },
- readonly [key: string]: string,
- }`,
- optionsSet: [[]]
- },
- // PropertySignature and IndexSignature members without readonly modifier
- // should produce failures. Also verify that nested members are checked.
- {
- code: dedent`
- interface Foo {
- readonly a: number,
- readonly b: Array,
- readonly c: () => string,
- readonly d: { readonly [key: string]: string },
- readonly [key: string]: string,
- readonly e: {
- readonly a: number,
- readonly b: Array,
- readonly c: () => string,
- readonly d: { readonly [key: string]: string },
- readonly [key: string]: string,
- }
- }`,
- optionsSet: [[]]
- },
- // Class with parameter properties.
- {
- code: dedent`
- class Klass {
- constructor (
- nonParameterProp: string,
- readonly readonlyProp: string,
- public readonly publicReadonlyProp: string,
- protected readonly protectedReadonlyProp: string,
- private readonly privateReadonlyProp: string,
- ) { }
- }`,
- optionsSet: [[]]
- },
- // CallSignature and MethodSignature cannot have readonly modifiers and should
- // not produce failures.
- {
- code: dedent`
- interface Foo {
- (): void
- foo(): void
- }`,
- optionsSet: [[]]
- },
- // The literal with indexer with readonly modifier should not produce failures.
- {
- code: `let foo: { readonly [key: string]: number };`,
- optionsSet: [[]]
- },
- // Type literal in array template parameter with readonly should not produce failures.
- {
- code: `type foo = ReadonlyArray<{ readonly type: string, readonly code: string }>;`,
- optionsSet: [[]]
- },
- // Type literal with readonly on members should not produce failures.
- {
- code: dedent`
- let foo: {
- readonly a: number,
- readonly b: Array,
- readonly c: () => string,
- readonly d: { readonly [key: string]: string }
- readonly [key: string]: string
- };`,
- optionsSet: [[]]
- },
- // Ignore Classes.
- {
- code: dedent`
- class Klass {
- foo: number;
- private bar: number;
- static baz: number;
- private static qux: number;
- }`,
- optionsSet: [[{ ignoreClass: true }]]
- },
- // Ignore Interfaces.
- {
- code: dedent`
- interface Foo {
- foo: number,
- bar: Array,
- baz: () => string,
- qux: { [key: string]: string }
- }`,
- optionsSet: [[{ ignoreInterface: true }]]
- },
- // Ignore Local.
- {
- code: dedent`
- function foo() {
- let foo: {
- a: number,
- b: Array,
- c: () => string,
- d: { [key: string]: string },
- [key: string]: string,
- readonly d: {
- a: number,
- b: Array,
- c: () => string,
- d: { [key: string]: string },
- [key: string]: string,
- }
- }
- };`,
- optionsSet: [[{ ignoreLocal: true }]]
- },
- // Ignore Prefix.
- {
- code: dedent`
- let foo: {
- mutableA: number,
- mutableB: Array,
- mutableC: () => string,
- mutableD: { readonly [key: string]: string },
- mutableE: {
- mutableA: number,
- mutableB: Array,
- mutableC: () => string,
- mutableD: { readonly [key: string]: string },
- }
- };`,
- optionsSet: [[{ ignorePattern: "^mutable" }]]
- },
- // Ignore Suffix.
- {
- code: dedent`
- let foo: {
- aMutable: number,
- bMutable: Array,
- cMutable: () => string,
- dMutable: { readonly [key: string]: string },
- eMutable: {
- aMutable: number,
- bMutable: Array,
- cMutable: () => string,
- dMutable: { readonly [key: string]: string },
- }
- };`,
- optionsSet: [[{ ignorePattern: "Mutable$" }]]
- }
-];
-
-// Invalid test cases.
-const invalid: ReadonlyArray = [
- // Class Property Signatures.
- {
- code: dedent`
- class Klass {
- foo: number;
- private bar: number;
- static baz: number;
- private static qux: number;
- }`,
- optionsSet: [[]],
- output: dedent`
- class Klass {
- readonly foo: number;
- private readonly bar: number;
- static readonly baz: number;
- private static readonly qux: number;
- }`,
- errors: [
- {
- messageId: "generic",
- type: "ClassProperty",
- line: 2,
- column: 3
- },
- {
- messageId: "generic",
- type: "ClassProperty",
- line: 3,
- column: 3
- },
- {
- messageId: "generic",
- type: "ClassProperty",
- line: 4,
- column: 3
- },
- {
- messageId: "generic",
- type: "ClassProperty",
- line: 5,
- column: 3
- }
- ]
- },
- // Class Parameter Properties.
- {
- code: dedent`
- class Klass {
- constructor (
- public publicProp: string,
- protected protectedProp: string,
- private privateProp: string,
- ) { }
- }`,
- optionsSet: [[]],
- output: dedent`
- class Klass {
- constructor (
- public readonly publicProp: string,
- protected readonly protectedProp: string,
- private readonly privateProp: string,
- ) { }
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSParameterProperty",
- line: 3,
- column: 5
- },
- {
- messageId: "generic",
- type: "TSParameterProperty",
- line: 4,
- column: 5
- },
- {
- messageId: "generic",
- type: "TSParameterProperty",
- line: 5,
- column: 5
- }
- ]
- },
- // Interface Index Signatures.
- {
- code: dedent`
- interface Foo {
- [key: string]: string
- }
- interface Bar {
- [key: string]: { prop: string }
- }`,
- optionsSet: [[]],
- output: dedent`
- interface Foo {
- readonly [key: string]: string
- }
- interface Bar {
- readonly [key: string]: { readonly prop: string }
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSIndexSignature",
- line: 2,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSIndexSignature",
- line: 5,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 5,
- column: 20
- }
- ]
- },
- // Function Index Signatures.
- {
- code: dedent`
- function foo(): { [source: string]: string } {
- return undefined;
- }
- function bar(param: { [source: string]: string }): void {
- return undefined;
- }`,
- optionsSet: [[]],
- output: dedent`
- function foo(): { readonly [source: string]: string } {
- return undefined;
- }
- function bar(param: { readonly [source: string]: string }): void {
- return undefined;
- }`,
- errors: [
- {
- messageId: "generic",
- type: "TSIndexSignature",
- line: 1,
- column: 19
- },
- {
- messageId: "generic",
- type: "TSIndexSignature",
- line: 4,
- column: 23
- }
- ]
- },
- // Type literal with indexer without readonly modifier should produce failures.
- {
- code: `let foo: { [key: string]: number };`,
- optionsSet: [[]],
- output: `let foo: { readonly [key: string]: number };`,
- errors: [
- {
- messageId: "generic",
- type: "TSIndexSignature",
- line: 1,
- column: 12
- }
- ]
- },
- // Type literal in array template parameter without readonly should produce failures.
- {
- code: dedent`
- type foo = ReadonlyArray<{
- type: string,
- code: string,
- }>;`,
- optionsSet: [[]],
- output: dedent`
- type foo = ReadonlyArray<{
- readonly type: string,
- readonly code: string,
- }>;`,
- errors: [
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 2,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 3,
- column: 3
- }
- ]
- },
- // Type literal without readonly on members should produce failures.
- // Also verify that nested members are checked.
- {
- code: dedent`
- let foo: {
- a: number,
- b: Array,
- c: () => string,
- d: { readonly [key: string]: string },
- [key: string]: string,
- readonly e: {
- a: number,
- b: Array,
- c: () => string,
- d: { readonly [key: string]: string },
- [key: string]: string,
- }
- };`,
- optionsSet: [[]],
- output: dedent`
- let foo: {
- readonly a: number,
- readonly b: Array,
- readonly c: () => string,
- readonly d: { readonly [key: string]: string },
- readonly [key: string]: string,
- readonly e: {
- readonly a: number,
- readonly b: Array,
- readonly c: () => string,
- readonly d: { readonly [key: string]: string },
- readonly [key: string]: string,
- }
- };`,
- errors: [
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 2,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 3,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 4,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 5,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSIndexSignature",
- line: 6,
- column: 3
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 8,
- column: 5
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 9,
- column: 5
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 10,
- column: 5
- },
- {
- messageId: "generic",
- type: "TSPropertySignature",
- line: 11,
- column: 5
- },
- {
- messageId: "generic",
- type: "TSIndexSignature",
- line: 12,
- column: 5
- }
- ]
- }
-];
-
-describe("TypeScript", () => {
- const ruleTester = new RuleTester(typescript);
- ruleTester.run(name, rule, {
- valid: processValidTestCase(valid),
- invalid: processInvalidTestCase(invalid)
- });
-});
diff --git a/tests/util.ts b/tests/util.ts
index 94424e668..972fb851b 100644
--- a/tests/util.ts
+++ b/tests/util.ts
@@ -49,7 +49,7 @@ export function processInvalidTestCase(
];
},
[]
- /* eslint-disable ts-immutable/readonly-array */
+ /* eslint-disable-next-line ts-immutable/prefer-readonly-types */
) as Array;
}
@@ -92,5 +92,5 @@ export function createDummyRule(
};
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- return createRule<"generic", Array>("dummy", meta, [], create);
+ return createRule<"generic", ReadonlyArray>("dummy", meta, [], create);
}