diff --git a/.eslintrc.json b/.eslintrc.json
index 12616a7d2..8936a30d2 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -39,6 +39,7 @@
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"import/no-relative-parent-imports": "error",
+ "functional/prefer-readonly-type": "off",
"node/no-unsupported-features/es-builtins": "off",
"node/no-unsupported-features/es-syntax": "off",
"promise/prefer-await-to-callbacks": "off",
@@ -57,7 +58,44 @@
{
"files": ["./src/**/*"],
"extends": ["plugin:eslint-plugin/recommended"],
- "rules": {}
+ "rules": {
+ "functional/no-expression-statement": "error"
+ }
+ },
+ // Rule util file.
+ {
+ "files": ["./src/util/rule.ts"],
+ "rules": {
+ "@typescript-eslint/prefer-readonly-parameter-types": "warn"
+ }
+ },
+ // Typeguard files.
+ {
+ "files": ["./src/util/typeguard.ts"],
+ "rules": {
+ "jsdoc/require-jsdoc": "off"
+ }
+ },
+ // Test files.
+ {
+ "files": ["./tests/**/*"],
+ "rules": {
+ "jsdoc/require-jsdoc": "off"
+ }
+ },
+ // CZ Adapter files.
+ {
+ "files": ["./cz-adapter/**/*"],
+ "rules": {
+ "jsdoc/require-jsdoc": "off"
+ }
+ },
+ {
+ "files": ["**/*.md/**"],
+ "rules": {
+ "@typescript-eslint/array-type": "off",
+ "functional/no-mixed-type": "off"
+ }
},
// FIXME: This override is defined in the upsteam; it shouldn't need to be redefined here. Why?
{
diff --git a/README.md b/README.md
index 8ee950803..a8bb0cfa1 100644
--- a/README.md
+++ b/README.md
@@ -186,12 +186,13 @@ The [below section](#supported-rules) gives details on which rules are enabled b
:see_no_evil: = `no-mutations` Ruleset.
-| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
-| -------------------------------------------------------------- | -------------------------------------------------------------------------- | :---------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
-| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
-| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
-| [`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: |
-| [`prefer-readonly-type`](./docs/rules/prefer-readonly-type.md) | Use readonly types and readonly modifiers where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
+| Name | Description | :see_no_evil: | :hear_no_evil: | :speak_no_evil: | :wrench: | :blue_heart: |
+| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | :---------------------------------------------: | :--------------------------------------: | :----------------------------------------------: | :------: | :---------------: |
+| [`immutable-data`](./docs/rules/immutable-data.md) | Disallow mutating objects and arrays | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :blue_heart: |
+| [`no-let`](./docs/rules/no-let.md) | Disallow mutable variables | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | |
+| [`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: |
+| [`prefer-readonly-type`](./docs/rules/prefer-readonly-type.md) | Use readonly types and readonly modifiers where possible | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :wrench: | :thought_balloon: |
+| [`prefer-readonly-return-types`](./docs/rules/prefer-readonly-return-types.md) | Enforce use of readonly types for function return values | | | | | :thought_balloon: |
### No Object-Orientation Rules
diff --git a/cz-adapter/engine.ts b/cz-adapter/engine.ts
index 68f6f111e..e6f7c52f5 100644
--- a/cz-adapter/engine.ts
+++ b/cz-adapter/engine.ts
@@ -5,16 +5,16 @@ import { rules } from "~/rules";
import type { Options } from "./options";
-type Answers = {
- readonly type: string;
- readonly scope?: string;
- readonly scopeRules?: string;
- readonly subject: string;
- readonly body?: string;
- readonly isBreaking: boolean;
- readonly isIssueAffected: boolean;
- readonly issues?: string;
-};
+type Answers = Readonly<{
+ type: string;
+ scope?: string;
+ scopeRules?: string;
+ subject: string;
+ body?: string;
+ isBreaking: boolean;
+ isIssueAffected: boolean;
+ issues?: string;
+}>;
type CZ = any;
diff --git a/cz-adapter/options.ts b/cz-adapter/options.ts
index 1059d45fe..d788c6e49 100644
--- a/cz-adapter/options.ts
+++ b/cz-adapter/options.ts
@@ -1,4 +1,5 @@
import { types as conventionalCommitTypes } from "conventional-commit-types";
+import type { ReadonlyDeep } from "type-fest";
// Override the descriptions of some of the types.
const types = {
@@ -48,13 +49,13 @@ const types = {
},
};
-const defaults: {
- readonly defaultType: string | undefined;
- readonly defaultScope: string | undefined;
- readonly defaultSubject: string | undefined;
- readonly defaultBody: string | undefined;
- readonly defaultIssues: string | undefined;
-} = {
+const defaults: Readonly<{
+ defaultType: string | undefined;
+ defaultScope: string | undefined;
+ defaultSubject: string | undefined;
+ defaultBody: string | undefined;
+ defaultIssues: string | undefined;
+}> = {
defaultType: process.env.CZ_TYPE,
defaultScope: process.env.CZ_SCOPE,
defaultSubject: process.env.CZ_SUBJECT,
@@ -71,6 +72,6 @@ const options = {
maxLineWidth: 100,
};
-export type Options = typeof options;
+export type Options = ReadonlyDeep;
export default options;
diff --git a/docs/rules/functional-parameters.md b/docs/rules/functional-parameters.md
index e4def09e5..52c521407 100644
--- a/docs/rules/functional-parameters.md
+++ b/docs/rules/functional-parameters.md
@@ -112,7 +112,7 @@ Any function that take takes multiple parameter can be rewritten as a higher-ord
Example:
-
+
```js
// This function
diff --git a/docs/rules/prefer-readonly-return-types.md b/docs/rules/prefer-readonly-return-types.md
new file mode 100644
index 000000000..939c5ca4e
--- /dev/null
+++ b/docs/rules/prefer-readonly-return-types.md
@@ -0,0 +1,268 @@
+# Requires that function return values are typed as readonly (prefer-readonly-return-types)
+
+This rules work just like [prefer-readonly-parameter-types](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md).
+
+This rule should only be used in a purely functional environment to help ensure that values are not being mutated.
+
+## Rule Details
+
+This rule allows you to enforce that function return values resolve to a readonly type.
+
+Examples of **incorrect** code for this rule:
+
+
+
+```ts
+/* eslint functional/prefer-readonly-type-declaration: "error" */
+
+function array1(): string[] {} // array is not readonly
+function array2(): readonly string[][] {} // array element is not readonly
+function array3(): [string, number] {} // tuple is not readonly
+function array4(): readonly [string[], number] {} // tuple element is not readonly
+// the above examples work the same if you use ReadonlyArray instead
+
+function object1(): { prop: string } {} // property is not readonly
+function object2(): { readonly prop: string; prop2: string } {} // not all properties are readonly
+function object3(): { readonly prop: { prop2: string } } {} // nested property is not readonly
+// the above examples work the same if you use Readonly instead
+
+interface CustomArrayType extends ReadonlyArray {
+ prop: string; // note: this property is mutable
+}
+function custom1(): CustomArrayType {}
+
+interface CustomFunction {
+ (): void;
+ prop: string; // note: this property is mutable
+}
+function custom2(): CustomFunction {}
+
+function union(): string[] | ReadonlyArray {} // not all types are readonly
+
+// rule also checks function types
+interface Foo {
+ (): string[];
+}
+interface Foo {
+ new (): string[];
+}
+const x = { foo(): string[]; };
+function foo(): string[];
+type Foo = () => string[];
+interface Foo {
+ foo(): string[];
+}
+```
+
+Examples of **correct** code for this rule:
+
+```ts
+/* eslint functional/prefer-readonly-return-types: "error" */
+
+function array1(): readonly string[] {}
+function array2(): readonly (readonly string[])[] {}
+function array3(): readonly [string, number] {}
+function array4(): readonly [readonly string[], number] {}
+// the above examples work the same if you use ReadonlyArray instead
+
+function object1(): { readonly prop: string } {}
+function object2(): { readonly prop: string; readonly prop2: string } {}
+function object3(): { readonly prop: { readonly prop2: string } } {}
+// the above examples work the same if you use Readonly instead
+
+interface CustomArrayType extends ReadonlyArray {
+ readonly prop: string;
+}
+function custom1(): Readonly {}
+// interfaces that extend the array types are not considered arrays, and thus must be made readonly.
+
+interface CustomFunction {
+ (): void;
+ readonly prop: string;
+}
+function custom2(): CustomFunction {}
+
+function union(): readonly string[] | ReadonlyArray {}
+
+function primitive1(): string {}
+function primitive2(): number {}
+function primitive3(): boolean {}
+function primitive4(): unknown {}
+function primitive5(): null {}
+function primitive6(): undefined {}
+function primitive7(): any {}
+function primitive8(): never {}
+function primitive9(): number | string | undefined {}
+
+function fnSig(): () => void {}
+
+enum Foo { A, B }
+function enum1(): Foo {}
+
+function symb1(): symbol {}
+const customSymbol = Symbol('a');
+function symb2(): typeof customSymbol {}
+
+// function types
+interface Foo {
+ (): readonly string[];
+}
+interface Foo {
+ new (): readonly string[];
+}
+const x = { foo(): readonly string[]; };
+function foo(): readonly string[];
+type Foo = () => readonly string[];
+interface Foo {
+ foo(): readonly string[];
+}
+```
+
+The default options:
+
+```ts
+const defaults = {
+ allowLocalMutation: false,
+ ignoreClass: false,
+ ignoreCollections: false,
+ ignoreInferredTypes: false,
+ ignoreInterface: false,
+ treatMethodsAsReadonly: false,
+}
+```
+
+### `treatMethodsAsReadonly`
+
+This option allows you to treat all mutable methods as though they were readonly. This may be desirable in when you are never reassigning methods.
+
+Examples of **incorrect** code for this rule with `{treatMethodsAsReadonly: false}`:
+
+```ts
+type MyType = {
+ readonly prop: string;
+ method(): string; // note: this method is mutable
+};
+function foo(arg: MyType) {}
+```
+
+Examples of **correct** code for this rule with `{treatMethodsAsReadonly: false}`:
+
+```ts
+type MyType = Readonly<{
+ prop: string;
+ method(): string;
+}>;
+function foo(): MyType {}
+type MyOtherType = {
+ readonly prop: string;
+ readonly method: () => string;
+};
+function bar(): MyOtherType {}
+```
+
+Examples of **correct** code for this rule with `{treatMethodsAsReadonly: true}`:
+
+```ts
+type MyType = {
+ readonly prop: string;
+ method(): string; // note: this method is mutable
+};
+function foo(): MyType {}
+```
+
+### `ignoreClass`
+
+If set, classes will not be checked.
+
+Examples of **incorrect** code for the `{ "ignoreClass": false }` option:
+
+
+
+```ts
+/* eslint functional/prefer-readonly-type-declaration: ["error", { "ignoreClass": false }] */
+
+class {
+ myprop: string;
+}
+```
+
+Examples of **correct** code for the `{ "ignoreClass": true }` option:
+
+```ts
+/* eslint functional/prefer-readonly-type-declaration: ["error", { "ignoreClass": true }] */
+
+class {
+ myprop: string;
+}
+```
+
+### `ignoreInterface`
+
+If set, interfaces will not be checked.
+
+Examples of **incorrect** code for the `{ "ignoreInterface": false }` option:
+
+
+
+```ts
+/* eslint functional/prefer-readonly-type-declaration: ["error", { "ignoreInterface": false }] */
+
+interface I {
+ myprop: string;
+}
+```
+
+Examples of **correct** code for the `{ "ignoreInterface": true }` option:
+
+```ts
+/* eslint functional/prefer-readonly-type-declaration: ["error", { "ignoreInterface": true }] */
+
+interface I {
+ myprop: string;
+}
+```
+
+### `ignoreCollections`
+
+If set, collections (Array, Tuple, Set, and Map) will not be required to be readonly when used outside of type aliases and interfaces.
+
+Examples of **incorrect** code for the `{ "ignoreCollections": false }` option:
+
+
+
+```ts
+/* eslint functional/prefer-readonly-type-declaration: ["error", { "ignoreCollections": false }] */
+
+const foo: number[] = [];
+const bar: [string, string] = ["foo", "bar"];
+const baz: Set = new Set();
+const qux: Map = new Map();
+```
+
+Examples of **correct** code for the `{ "ignoreCollections": true }` option:
+
+```ts
+/* eslint functional/prefer-readonly-type-declaration: ["error", { "ignoreCollections": true }] */
+
+const foo: number[] = [];
+const bar: [string, string] = ["foo", "bar"];
+const baz: Set = new Set();
+const qux: Map = new Map();
+```
+
+### `ignoreInferredTypes`
+
+This option allows you to ignore types that aren't explicitly specified.
+
+### `allowLocalMutation`
+
+See the [allowLocalMutation](./options/allow-local-mutation.md) docs.
+
+### `ignorePattern`
+
+Use the given regex pattern(s) to match against the type's name (for objects this is the property's name not the object's name).
+
+Note: If using this option to require mutable properties are marked as mutable via a naming convention (e.g. `{ "ignorePattern": "^[Mm]utable.+" }`),
+type aliases and interfaces names will still need to comply with the `readonlyAliasPatterns` and `mutableAliasPatterns` options.
+
+See the [ignorePattern](./options/ignore-pattern.md) docs for more info.
diff --git a/package.json b/package.json
index 180138dd2..ddfd9e648 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,8 @@
"verify": "yarn build && yarn lint && yarn build-tests && yarn test-compiled && rimraf build"
},
"dependencies": {
- "@typescript-eslint/experimental-utils": "^5.0.0",
+ "@typescript-eslint/utils": "^5.10.0",
+ "@typescript-eslint/type-utils": "^5.10.0",
"deepmerge-ts": "^2.0.1",
"escape-string-regexp": "^4.0.0"
},
@@ -71,7 +72,7 @@
"@commitlint/config-conventional": "^14.1.0",
"@google/semantic-release-replace-plugin": "^1.1.0",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
- "@rebeccastevens/eslint-config": "^1.1.5",
+ "@rebeccastevens/eslint-config": "^1.2.1",
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.0.5",
@@ -87,8 +88,8 @@
"@types/estree": "^0.0.50",
"@types/node": "16.11.0",
"@types/rollup-plugin-auto-external": "^2.0.2",
- "@typescript-eslint/eslint-plugin": "^5.0.0",
- "@typescript-eslint/parser": "^5.0.0",
+ "@typescript-eslint/eslint-plugin": "^5.10.0",
+ "@typescript-eslint/parser": "^5.10.0",
"ava": "^3.15.0",
"babel-eslint": "^10.1.0",
"chalk": "^4.1.2",
@@ -131,6 +132,7 @@
"tsconfig-paths": "^3.10.1",
"tslib": "^2.0.3",
"tsutils": "^3.21.0",
+ "type-fest": "^2.9.0",
"typescript": "^4.4.4",
"word-wrap": "^1.2.3"
},
diff --git a/src/common/ignore-options.ts b/src/common/ignore-options.ts
index 227091f30..97a6db9d8 100644
--- a/src/common/ignore-options.ts
+++ b/src/common/ignore-options.ts
@@ -1,8 +1,9 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { TSESLint, TSESTree } from "@typescript-eslint/utils";
import escapeRegExp from "escape-string-regexp";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { BaseOptions, RuleContext } from "~/util/rule";
+import type { BaseOptions } from "~/util/rule";
import {
getKeyOfValueInObjectExpression,
inClass,
@@ -30,96 +31,122 @@ import {
isVariableDeclaration,
} from "~/util/typeguard";
+/**
+ * The option to allow local mutations.
+ */
export type AllowLocalMutationOption = {
readonly allowLocalMutation: boolean;
};
-export const allowLocalMutationOptionSchema: JSONSchema4 = {
- type: "object",
- properties: {
- allowLocalMutation: {
- type: "boolean",
- },
+/**
+ * The schema for the option to allow local mutations.
+ */
+export const allowLocalMutationOptionSchema: JSONSchema4["properties"] = {
+ allowLocalMutation: {
+ type: "boolean",
},
- additionalProperties: false,
};
+/**
+ * The option to ignore patterns.
+ */
export type IgnorePatternOption = {
readonly ignorePattern?: ReadonlyArray | string;
};
-export const ignorePatternOptionSchema: JSONSchema4 = {
- type: "object",
- properties: {
- ignorePattern: {
- type: ["string", "array"],
- items: {
- type: "string",
- },
+/**
+ * The schema for the option to ignore patterns.
+ */
+export const ignorePatternOptionSchema: JSONSchema4["properties"] = {
+ ignorePattern: {
+ type: ["string", "array"],
+ items: {
+ type: "string",
},
},
- additionalProperties: false,
};
+/**
+ * The option to ignore accessor patterns.
+ */
export type IgnoreAccessorPatternOption = {
readonly ignoreAccessorPattern?: ReadonlyArray | string;
};
-export const ignoreAccessorPatternOptionSchema: JSONSchema4 = {
- type: "object",
- properties: {
- ignoreAccessorPattern: {
- type: ["string", "array"],
- items: {
- type: "string",
- },
+/**
+ * The schema for the option to ignore accessor patterns.
+ */
+export const ignoreAccessorPatternOptionSchema: JSONSchema4["properties"] = {
+ ignoreAccessorPattern: {
+ type: ["string", "array"],
+ items: {
+ type: "string",
},
},
- additionalProperties: false,
};
+/**
+ * The option to ignore classes.
+ */
export type IgnoreClassOption = {
readonly ignoreClass: boolean | "fieldsOnly";
};
-export const ignoreClassOptionSchema: JSONSchema4 = {
- type: "object",
- properties: {
- ignoreClass: {
- oneOf: [
- {
- type: "boolean",
- },
- {
- type: "string",
- enum: ["fieldsOnly"],
- },
- ],
- },
+/**
+ * The schema for the option to ignore classes.
+ */
+export const ignoreClassOptionSchema: JSONSchema4["properties"] = {
+ ignoreClass: {
+ oneOf: [
+ {
+ type: "boolean",
+ },
+ {
+ type: "string",
+ enum: ["fieldsOnly"],
+ },
+ ],
},
- additionalProperties: false,
};
+/**
+ * The option to ignore interfaces.
+ */
export type IgnoreInterfaceOption = {
readonly ignoreInterface: boolean;
};
-export const ignoreInterfaceOptionSchema: JSONSchema4 = {
- type: "object",
- properties: {
- ignoreInterface: {
- type: "boolean",
- },
+/**
+ * The schema for the option to ignore interfaces.
+ */
+export const ignoreInterfaceOptionSchema: JSONSchema4["properties"] = {
+ ignoreInterface: {
+ type: "boolean",
+ },
+};
+
+/**
+ * The option to ignore inferred types.
+ */
+export type IgnoreInferredTypesOption = {
+ readonly ignoreInferredTypes: boolean;
+};
+
+/**
+ * The schema for the option to ignore inferred types.
+ */
+export const ignoreInferredTypesOptionSchema: JSONSchema4["properties"] = {
+ ignoreInferredTypes: {
+ type: "boolean",
},
- additionalProperties: false,
};
/**
* Get the identifier text of the given node.
*/
function getNodeIdentifierText(
- node: TSESTree.Node | null | undefined,
- context: RuleContext
+ node: ReadonlyDeep | null | undefined,
+ context: ReadonlyDeep>
): string | undefined {
if (!isDefined(node)) {
return undefined;
@@ -143,7 +170,7 @@ function getNodeIdentifierText(
: isUnaryExpression(node)
? getNodeIdentifierText(node.argument, context)
: isExpressionStatement(node)
- ? context.getSourceCode().getText(node)
+ ? context.getSourceCode().getText(node as TSESTree.Node)
: isTSArrayType(node) ||
isTSIndexSignature(node) ||
isTSTupleType(node) ||
@@ -169,8 +196,8 @@ function getNodeIdentifierText(
* Get all the identifier texts of the given node.
*/
function getNodeIdentifierTexts(
- node: TSESTree.Node,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep>
): ReadonlyArray {
return (
isVariableDeclaration(node)
@@ -270,11 +297,11 @@ function shouldIgnoreViaAccessorPattern(
* - AllowLocalMutationOption.
*/
export function shouldIgnoreLocalMutation(
- node: TSESTree.Node,
- context: RuleContext,
- options: Partial
+ node: ReadonlyDeep,
+ context: ReadonlyDeep>,
+ { allowLocalMutation }: Partial
): boolean {
- return options.allowLocalMutation === true && inFunctionBody(node);
+ return allowLocalMutation === true && inFunctionBody(node);
}
/**
@@ -283,13 +310,13 @@ export function shouldIgnoreLocalMutation(
* - IgnoreClassOption.
*/
export function shouldIgnoreClass(
- node: TSESTree.Node,
- context: RuleContext,
- options: Partial
+ node: ReadonlyDeep,
+ context: ReadonlyDeep>,
+ { ignoreClass }: Partial
): boolean {
return (
- (options.ignoreClass === true && inClass(node)) ||
- (options.ignoreClass === "fieldsOnly" &&
+ (ignoreClass === true && inClass(node)) ||
+ (ignoreClass === "fieldsOnly" &&
(isPropertyDefinition(node) ||
(isAssignmentExpression(node) &&
inClass(node) &&
@@ -304,11 +331,24 @@ export function shouldIgnoreClass(
* - IgnoreInterfaceOption.
*/
export function shouldIgnoreInterface(
- node: TSESTree.Node,
- context: RuleContext,
- options: Partial
+ node: ReadonlyDeep,
+ context: ReadonlyDeep>,
+ { ignoreInterface }: Partial
): boolean {
- return options.ignoreInterface === true && inInterface(node);
+ return ignoreInterface === true && inInterface(node);
+}
+
+/**
+ * Should the given node be allowed base off the following rule options?
+ *
+ * - IgnoreInterfaceOption.
+ */
+export function shouldIgnoreInferredTypes(
+ node: ReadonlyDeep,
+ context: ReadonlyDeep>,
+ { ignoreInferredTypes }: Partial
+) {
+ return ignoreInferredTypes === true && node === null;
}
/**
@@ -318,9 +358,12 @@ export function shouldIgnoreInterface(
* - IgnorePatternOption.
*/
export function shouldIgnorePattern(
- node: TSESTree.Node,
- context: RuleContext,
- options: Partial
+ node: ReadonlyDeep,
+ context: ReadonlyDeep>,
+ {
+ ignorePattern,
+ ignoreAccessorPattern,
+ }: Partial
): boolean {
const texts = getNodeIdentifierTexts(node, context);
@@ -330,14 +373,12 @@ export function shouldIgnorePattern(
return (
// Ignore if ignorePattern is set and a pattern matches.
- (options.ignorePattern !== undefined &&
- texts.every((text) =>
- shouldIgnoreViaPattern(text, options.ignorePattern!)
- )) ||
+ (ignorePattern !== undefined &&
+ texts.every((text) => shouldIgnoreViaPattern(text, ignorePattern))) ||
// Ignore if ignoreAccessorPattern is set and an accessor pattern matches.
- (options.ignoreAccessorPattern !== undefined &&
+ (ignoreAccessorPattern !== undefined &&
texts.every((text) =>
- shouldIgnoreViaAccessorPattern(text, options.ignoreAccessorPattern!)
+ shouldIgnoreViaAccessorPattern(text, ignoreAccessorPattern)
))
);
}
diff --git a/src/configs/all.ts b/src/configs/all.ts
index 0db73d9d0..952b37034 100644
--- a/src/configs/all.ts
+++ b/src/configs/all.ts
@@ -21,6 +21,7 @@ const config: Linter.Config = {
rules: {
"functional/no-method-signature": "error",
"functional/no-mixed-type": "error",
+ "functional/prefer-readonly-return-types": "error",
"functional/prefer-readonly-type": "error",
"functional/prefer-tacit": ["error", { assumeTypes: false }],
"functional/no-return-void": "error",
diff --git a/src/configs/off.ts b/src/configs/off.ts
index 7fa6a594d..bf3bda5a1 100644
--- a/src/configs/off.ts
+++ b/src/configs/off.ts
@@ -2,6 +2,9 @@ import type { Linter } from "eslint";
import all from "./all";
+/**
+ * Turn the given rules off.
+ */
function turnRulesOff(rules: ReadonlyArray): Linter.Config["rules"] {
return rules === undefined
? undefined
diff --git a/src/index.ts b/src/index.ts
index 9fcf255bb..7a5541946 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -13,9 +13,12 @@ import off from "~/configs/off";
import stylistic from "~/configs/stylistic";
import { rules } from "~/rules";
+/**
+ * The config type object ESLint is expecting.
+ */
type EslintPluginConfig = {
- readonly rules: Record;
- readonly configs: Record;
+ rules: Record;
+ configs: Record;
};
const config: EslintPluginConfig = {
diff --git a/src/rules/functional-parameters.ts b/src/rules/functional-parameters.ts
index da5aa9e52..b21f53a7b 100644
--- a/src/rules/functional-parameters.ts
+++ b/src/rules/functional-parameters.ts
@@ -1,40 +1,53 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
import type { IgnorePatternOption } from "~/common/ignore-options";
import {
shouldIgnorePattern,
ignorePatternOptionSchema,
} from "~/common/ignore-options";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
import { isIIFE, isPropertyAccess, isPropertyName } from "~/util/tree";
import { isRestElement } from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "functional-parameters" as const;
+/**
+ * The parameter count options this rule can take.
+ */
type ParameterCountOptions = "atLeastOne" | "exactlyOne";
-// The options this rule can take.
-type Options = IgnorePatternOption & {
- readonly allowRestParameter: boolean;
- readonly allowArgumentsKeyword: boolean;
- readonly enforceParameterCount:
- | ParameterCountOptions
- | false
- | {
- readonly count: ParameterCountOptions;
- readonly ignoreIIFE: boolean;
- };
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ IgnorePatternOption &
+ Readonly<{
+ allowRestParameter: boolean;
+ allowArgumentsKeyword: boolean;
+ enforceParameterCount:
+ | ParameterCountOptions
+ | false
+ | Readonly<{
+ count: ParameterCountOptions;
+ ignoreIIFE: boolean;
+ }>;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
- deepmerge(ignorePatternOptionSchema, {
+ {
type: "object",
- properties: {
+ properties: deepmerge(ignorePatternOptionSchema, {
allowRestParameter: {
type: "boolean",
},
@@ -66,22 +79,28 @@ const schema: JSONSchema4 = [
},
],
},
- },
+ }),
additionalProperties: false,
- }),
+ },
];
-// The default options for the rule.
-const defaultOptions: Options = {
- allowRestParameter: false,
- allowArgumentsKeyword: false,
- enforceParameterCount: {
- count: "atLeastOne",
- ignoreIIFE: true,
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ allowRestParameter: false,
+ allowArgumentsKeyword: false,
+ enforceParameterCount: {
+ count: "atLeastOne",
+ ignoreIIFE: true,
+ },
},
-};
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
restParam:
"Unexpected rest parameter. Use a regular parameter of type array instead.",
@@ -91,8 +110,10 @@ const errorMessages = {
paramCountExactlyOne: "Functions must have exactly one parameter.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Enforce functional parameters.",
@@ -106,11 +127,11 @@ const meta: RuleMetaData = {
* Get the rest parameter violations.
*/
function getRestParamViolations(
- allowRestParameter: Options["allowRestParameter"],
+ [{ allowRestParameter }]: Options,
node:
- | TSESTree.ArrowFunctionExpression
- | TSESTree.FunctionDeclaration
- | TSESTree.FunctionExpression
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep
): RuleResult["descriptors"] {
return !allowRestParameter &&
node.params.length > 0 &&
@@ -128,11 +149,11 @@ function getRestParamViolations(
* Get the parameter count violations.
*/
function getParamCountViolations(
- enforceParameterCount: Options["enforceParameterCount"],
+ [{ enforceParameterCount }]: Options,
node:
- | TSESTree.ArrowFunctionExpression
- | TSESTree.FunctionDeclaration
- | TSESTree.FunctionExpression
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep
): RuleResult["descriptors"] {
if (
enforceParameterCount === false ||
@@ -177,13 +198,17 @@ function getParamCountViolations(
*/
function checkFunction(
node:
- | TSESTree.ArrowFunctionExpression
- | TSESTree.FunctionDeclaration
- | TSESTree.FunctionExpression,
- context: RuleContext,
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
- if (shouldIgnorePattern(node, context, options)) {
+ const [optionsObject] = options;
+
+ if (shouldIgnorePattern(node, context, optionsObject)) {
return {
context,
descriptors: [],
@@ -193,8 +218,8 @@ function checkFunction(
return {
context,
descriptors: [
- ...getRestParamViolations(options.allowRestParameter, node),
- ...getParamCountViolations(options.enforceParameterCount, node),
+ ...getRestParamViolations(options, node),
+ ...getParamCountViolations(options, node),
],
};
}
@@ -203,21 +228,27 @@ function checkFunction(
* Check if the given identifier is for the "arguments" keyword.
*/
function checkIdentifier(
- node: TSESTree.Identifier,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
- if (shouldIgnorePattern(node, context, options)) {
+ const [optionsObject] = options;
+
+ if (shouldIgnorePattern(node, context, optionsObject)) {
return {
context,
descriptors: [],
};
}
+ const { allowArgumentsKeyword } = optionsObject;
+
return {
context,
descriptors:
- !options.allowArgumentsKeyword &&
+ !allowArgumentsKeyword &&
node.name === "arguments" &&
!isPropertyName(node) &&
!isPropertyAccess(node)
diff --git a/src/rules/immutable-data.ts b/src/rules/immutable-data.ts
index 4096544e9..afe5a2ecf 100644
--- a/src/rules/immutable-data.ts
+++ b/src/rules/immutable-data.ts
@@ -1,6 +1,7 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
import type {
IgnoreAccessorPatternOption,
@@ -16,7 +17,7 @@ import {
} from "~/common/ignore-options";
import { isExpected } from "~/util/misc";
import { createRule, getTypeOfNode } from "~/util/rule";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { inConstructor } from "~/util/tree";
import {
isArrayConstructorType,
@@ -29,31 +30,40 @@ import {
isObjectConstructorType,
} from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "immutable-data" as const;
-// The options this rule can take.
-type Options = IgnoreAccessorPatternOption &
- IgnoreClassOption &
- IgnorePatternOption & {
- readonly ignoreImmediateMutation: boolean;
- readonly assumeTypes:
- | boolean
- | {
- readonly forArrays: boolean;
- readonly forObjects: boolean;
- };
- };
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ IgnoreAccessorPatternOption &
+ IgnoreClassOption &
+ IgnorePatternOption &
+ Readonly<{
+ ignoreImmediateMutation: boolean;
+ assumeTypes:
+ | boolean
+ | Readonly<{
+ forArrays: boolean;
+ forObjects: boolean;
+ }>;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
- deepmerge(
- ignorePatternOptionSchema,
- ignoreAccessorPatternOptionSchema,
- ignoreClassOptionSchema,
- {
- type: "object",
- properties: {
+ {
+ type: "object",
+ properties: deepmerge(
+ ignorePatternOptionSchema,
+ ignoreAccessorPatternOptionSchema,
+ ignoreClassOptionSchema,
+ {
ignoreImmediateMutation: {
type: "boolean",
},
@@ -76,31 +86,39 @@ const schema: JSONSchema4 = [
},
],
},
- },
- additionalProperties: false,
- }
- ),
+ }
+ ),
+ additionalProperties: false,
+ },
];
-// The default options for the rule.
-const defaultOptions: Options = {
- ignoreClass: false,
- ignoreImmediateMutation: true,
- assumeTypes: {
- forArrays: true,
- forObjects: true,
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ ignoreClass: false,
+ ignoreImmediateMutation: true,
+ assumeTypes: {
+ forArrays: true,
+ forObjects: true,
+ },
},
-};
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Modifying an existing object/array is not allowed.",
object: "Modifying properties of existing object not allowed.",
array: "Modifying an array is not allowed.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Enforce treating data as immutable.",
@@ -165,14 +183,18 @@ const objectConstructorMutatorFunctions = new Set([
* Check if the given assignment expression violates this rule.
*/
function checkAssignmentExpression(
- node: TSESTree.AssignmentExpression,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+
if (
!isMemberExpression(node.left) ||
- shouldIgnoreClass(node, context, options) ||
- shouldIgnorePattern(node, context, options)
+ shouldIgnoreClass(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject)
) {
return {
context,
@@ -192,14 +214,18 @@ function checkAssignmentExpression(
* Check if the given node violates this rule.
*/
function checkUnaryExpression(
- node: TSESTree.UnaryExpression,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+
if (
!isMemberExpression(node.argument) ||
- shouldIgnoreClass(node, context, options) ||
- shouldIgnorePattern(node, context, options)
+ shouldIgnoreClass(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject)
) {
return {
context,
@@ -218,14 +244,18 @@ function checkUnaryExpression(
* Check if the given node violates this rule.
*/
function checkUpdateExpression(
- node: TSESTree.UpdateExpression,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+
if (
!isMemberExpression(node.argument) ||
- shouldIgnoreClass(node.argument, context, options) ||
- shouldIgnorePattern(node.argument, context, options)
+ shouldIgnoreClass(node.argument, context, optionsObject) ||
+ shouldIgnorePattern(node.argument, context, optionsObject)
) {
return {
context,
@@ -247,8 +277,10 @@ function checkUpdateExpression(
* a mutator method call.
*/
function isInChainCallAndFollowsNew(
- node: TSESTree.MemberExpression,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
assumeArrayTypes: boolean
): boolean {
return (
@@ -285,16 +317,20 @@ function isInChainCallAndFollowsNew(
* Check if the given node violates this rule.
*/
function checkCallExpression(
- node: TSESTree.CallExpression,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+
// Not potential object mutation?
if (
!isMemberExpression(node.callee) ||
!isIdentifier(node.callee.property) ||
- shouldIgnoreClass(node.callee.object, context, options) ||
- shouldIgnorePattern(node.callee.object, context, options)
+ shouldIgnoreClass(node.callee.object, context, optionsObject) ||
+ shouldIgnorePattern(node.callee.object, context, optionsObject)
) {
return {
context,
@@ -302,14 +338,16 @@ function checkCallExpression(
};
}
+ const { assumeTypes, ignoreImmediateMutation } = optionsObject;
+
const assumeTypesForArrays =
- options.assumeTypes === true ||
- (options.assumeTypes !== false && options.assumeTypes.forArrays === true);
+ assumeTypes === true ||
+ (assumeTypes !== false && assumeTypes.forArrays === true);
// Array mutation?
if (
arrayMutatorMethods.has(node.callee.property.name) &&
- (!options.ignoreImmediateMutation ||
+ (!ignoreImmediateMutation ||
!isInChainCallAndFollowsNew(
node.callee,
context,
@@ -328,8 +366,8 @@ function checkCallExpression(
}
const assumeTypesForObjects =
- options.assumeTypes === true ||
- (options.assumeTypes !== false && options.assumeTypes.forObjects === true);
+ assumeTypes === true ||
+ (assumeTypes !== false && assumeTypes.forObjects === true);
// Non-array object mutation (ex. Object.assign on identifier)?
if (
@@ -337,8 +375,8 @@ function checkCallExpression(
node.arguments.length >= 2 &&
(isIdentifier(node.arguments[0]) ||
isMemberExpression(node.arguments[0])) &&
- !shouldIgnoreClass(node.arguments[0], context, options) &&
- !shouldIgnorePattern(node.arguments[0], context, options) &&
+ !shouldIgnoreClass(node.arguments[0], context, optionsObject) &&
+ !shouldIgnorePattern(node.arguments[0], context, optionsObject) &&
isObjectConstructorType(
getTypeOfNode(node.callee.object, context),
assumeTypesForObjects,
diff --git a/src/rules/index.ts b/src/rules/index.ts
index 06eb7baef..ea40af84d 100644
--- a/src/rules/index.ts
+++ b/src/rules/index.ts
@@ -12,6 +12,7 @@ import * as noReturnVoid from "./no-return-void";
import * as noThisExpression from "./no-this-expression";
import * as noThrowStatement from "./no-throw-statement";
import * as noTryStatement from "./no-try-statement";
+import * as preferReadonlyReturnTypes from "./prefer-readonly-return-types";
import * as preferReadonlyTypes from "./prefer-readonly-type";
import * as preferTacit from "./prefer-tacit";
@@ -33,6 +34,7 @@ export const rules = {
[noThisExpression.name]: noThisExpression.rule,
[noThrowStatement.name]: noThrowStatement.rule,
[noTryStatement.name]: noTryStatement.rule,
+ [preferReadonlyReturnTypes.name]: preferReadonlyReturnTypes.rule,
[preferReadonlyTypes.name]: preferReadonlyTypes.rule,
[preferTacit.name]: preferTacit.rule,
};
diff --git a/src/rules/no-class.ts b/src/rules/no-class.ts
index 4b81a96c9..d32ce40d0 100644
--- a/src/rules/no-class.ts
+++ b/src/rules/no-class.ts
@@ -1,28 +1,41 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-class" as const;
-// The options this rule can take.
-type Options = {};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [{}];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [];
-// The default options for the rule.
-const defaultOptions: Options = {};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [{}];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Unexpected class, use functions not classes.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow classes.",
@@ -36,8 +49,12 @@ const meta: RuleMetaData = {
* Check if the given class node violates this rule.
*/
function checkClass(
- node: TSESTree.ClassDeclaration | TSESTree.ClassExpression,
- context: RuleContext
+ node:
+ | ReadonlyDeep
+ | ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): RuleResult {
// All class nodes violate this rule.
return { context, descriptors: [{ node, messageId: "generic" }] };
diff --git a/src/rules/no-conditional-statement.ts b/src/rules/no-conditional-statement.ts
index 5842e8dc5..ff6382539 100644
--- a/src/rules/no-conditional-statement.ts
+++ b/src/rules/no-conditional-statement.ts
@@ -1,9 +1,10 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
import type { Type } from "typescript";
import tsutils from "~/conditional-imports/tsutils";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule, getTypeOfNode } from "~/util/rule";
import {
isBlockStatement,
@@ -17,15 +18,23 @@ import {
isThrowStatement,
} from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-conditional-statement" as const;
-// The options this rule can take.
-type Options = {
- readonly allowReturningBranches: boolean | "ifExhaustive";
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ Readonly<{
+ allowReturningBranches: boolean | "ifExhaustive";
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
{
type: "object",
@@ -46,10 +55,14 @@ const schema: JSONSchema4 = [
},
];
-// The default options for the rule.
-const defaultOptions: Options = { allowReturningBranches: false };
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [{ allowReturningBranches: false }];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
incompleteBranch:
"Incomplete branch, every branch in a conditional statement must contain a return statement.",
@@ -63,8 +76,10 @@ const errorMessages = {
"Unexpected switch, use a conditional expression (ternary operator) instead.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow conditional statements.",
@@ -81,7 +96,7 @@ const meta: RuleMetaData = {
* @returns A violation rule result.
*/
function incompleteBranchViolation(
- node: TSESTree.Node
+ node: ReadonlyDeep
): RuleResult["descriptors"] {
return [{ node, messageId: "incompleteBranch" }];
}
@@ -90,9 +105,11 @@ function incompleteBranchViolation(
* Get a function that tests if the given statement is never returning.
*/
function getIsNeverExpressions(
- context: RuleContext
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
) {
- return (statement: TSESTree.Statement) => {
+ return (statement: ReadonlyDeep) => {
if (isExpressionStatement(statement)) {
const expressionStatementType = getTypeOfNode(
statement.expression,
@@ -108,11 +125,8 @@ function getIsNeverExpressions(
/**
* Is the given statement, when inside an if statement, a returning branch?
- *
- * @param statement
- * @returns
*/
-function isIfReturningBranch(statement: TSESTree.Statement) {
+function isIfReturningBranch(statement: ReadonlyDeep) {
return (
// Another instance of this rule will check nested if statements.
isIfStatement(statement) ||
@@ -128,8 +142,10 @@ function isIfReturningBranch(statement: TSESTree.Statement) {
* are allowed.
*/
function getIfBranchViolations(
- node: TSESTree.IfStatement,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): RuleResult["descriptors"] {
const branches = [node.consequent, node.alternate];
const violations = branches.filter>(
@@ -172,11 +188,8 @@ function getIfBranchViolations(
/**
* Is the given statement, when inside a switch statement, a returning branch?
- *
- * @param statement
- * @returns
*/
-function isSwitchReturningBranch(statement: TSESTree.Statement) {
+function isSwitchReturningBranch(statement: ReadonlyDeep) {
return (
// Another instance of this rule will check nested switch statements.
isSwitchStatement(statement) ||
@@ -190,8 +203,10 @@ function isSwitchReturningBranch(statement: TSESTree.Statement) {
* statements are allowed.
*/
function getSwitchViolations(
- node: TSESTree.SwitchStatement,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): RuleResult["descriptors"] {
const isNeverExpressions = getIsNeverExpressions(context);
@@ -228,7 +243,9 @@ function getSwitchViolations(
/**
* Does the given if statement violate this rule if it must be exhaustive.
*/
-function isExhaustiveIfViolation(node: TSESTree.IfStatement): boolean {
+function isExhaustiveIfViolation(
+ node: ReadonlyDeep
+): boolean {
return node.alternate === null;
}
@@ -236,8 +253,10 @@ function isExhaustiveIfViolation(node: TSESTree.IfStatement): boolean {
* Does the given typed switch statement violate this rule if it must be exhaustive.
*/
function isExhaustiveTypeSwitchViolation(
- node: TSESTree.SwitchStatement,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): boolean {
if (tsutils === undefined) {
return true;
@@ -261,8 +280,10 @@ function isExhaustiveTypeSwitchViolation(
* Does the given switch statement violate this rule if it must be exhaustive.
*/
function isExhaustiveSwitchViolation(
- node: TSESTree.SwitchStatement,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): boolean {
return (
// No cases defined.
@@ -276,16 +297,20 @@ function isExhaustiveSwitchViolation(
* Check if the given IfStatement violates this rule.
*/
function checkIfStatement(
- node: TSESTree.IfStatement,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [{ allowReturningBranches }] = options;
+
return {
context,
descriptors:
- options.allowReturningBranches === false
+ allowReturningBranches === false
? [{ node, messageId: "unexpectedIf" }]
- : options.allowReturningBranches === "ifExhaustive"
+ : allowReturningBranches === "ifExhaustive"
? isExhaustiveIfViolation(node)
? [{ node, messageId: "incompleteIf" }]
: getIfBranchViolations(node, context)
@@ -297,16 +322,20 @@ function checkIfStatement(
* Check if the given SwitchStatement violates this rule.
*/
function checkSwitchStatement(
- node: TSESTree.SwitchStatement,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [{ allowReturningBranches }] = options;
+
return {
context,
descriptors:
- options.allowReturningBranches === false
+ allowReturningBranches === false
? [{ node, messageId: "unexpectedSwitch" }]
- : options.allowReturningBranches === "ifExhaustive"
+ : allowReturningBranches === "ifExhaustive"
? isExhaustiveSwitchViolation(node, context)
? [{ node, messageId: "incompleteSwitch" }]
: getSwitchViolations(node, context)
diff --git a/src/rules/no-expression-statement.ts b/src/rules/no-expression-statement.ts
index 6710f4080..3c9ea32b5 100644
--- a/src/rules/no-expression-statement.ts
+++ b/src/rules/no-expression-statement.ts
@@ -1,6 +1,7 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
import type { IgnorePatternOption } from "~/common/ignore-options";
import {
@@ -8,43 +9,60 @@ import {
ignorePatternOptionSchema,
} from "~/common/ignore-options";
import { isDirectivePrologue } from "~/util/misc";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule, getTypeOfNode } from "~/util/rule";
import { isVoidType } from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-expression-statement" as const;
-// The options this rule can take.
-type Options = IgnorePatternOption & {
- readonly ignoreVoid: boolean;
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ IgnorePatternOption &
+ Readonly<{
+ ignoreVoid: boolean;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
- deepmerge(ignorePatternOptionSchema, {
+ {
type: "object",
- properties: {
+ properties: deepmerge(ignorePatternOptionSchema, {
ignoreVoid: {
type: "boolean",
},
- },
+ }),
additionalProperties: false,
- }),
+ },
];
-// The default options for the rule.
-const defaultOptions: Options = {
- ignoreVoid: false,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ ignoreVoid: false,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Using expressions to cause side-effects not allowed.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow expression statements.",
@@ -58,11 +76,15 @@ const meta: RuleMetaData = {
* Check if the given ExpressionStatement violates this rule.
*/
function checkExpressionStatement(
- node: TSESTree.ExpressionStatement,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
- if (shouldIgnorePattern(node, context, options)) {
+ const [optionsObject] = options;
+
+ if (shouldIgnorePattern(node, context, optionsObject)) {
return {
context,
descriptors: [],
@@ -77,7 +99,9 @@ function checkExpressionStatement(
};
}
- if (options.ignoreVoid === true) {
+ const { ignoreVoid } = optionsObject;
+
+ if (ignoreVoid === true) {
const type = getTypeOfNode(node.expression, context);
return {
diff --git a/src/rules/no-let.ts b/src/rules/no-let.ts
index be6f02a51..fe4c9c832 100644
--- a/src/rules/no-let.ts
+++ b/src/rules/no-let.ts
@@ -1,6 +1,7 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
import type {
AllowLocalMutationOption,
@@ -12,45 +13,66 @@ import {
allowLocalMutationOptionSchema,
ignorePatternOptionSchema,
} from "~/common/ignore-options";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
import { inForLoopInitializer } from "~/util/tree";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-let" as const;
-// The options this rule can take.
-type Options = AllowLocalMutationOption &
- IgnorePatternOption & {
- readonly allowInForLoopInit: boolean;
- };
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ AllowLocalMutationOption &
+ IgnorePatternOption &
+ Readonly<{
+ allowInForLoopInit: boolean;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
- deepmerge(allowLocalMutationOptionSchema, ignorePatternOptionSchema, {
+ {
type: "object",
- properties: {
- allowInForLoopInit: {
- type: "boolean",
- },
- },
+ properties: deepmerge(
+ allowLocalMutationOptionSchema,
+ ignorePatternOptionSchema,
+ {
+ allowInForLoopInit: {
+ type: "boolean",
+ },
+ }
+ ),
additionalProperties: false,
- }),
+ },
];
-// The default options for the rule.
-const defaultOptions: Options = {
- allowInForLoopInit: false,
- allowLocalMutation: false,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ allowInForLoopInit: false,
+ allowLocalMutation: false,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Unexpected let, use const instead.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow mutable variables.",
@@ -65,15 +87,20 @@ const meta: RuleMetaData = {
* Check if the given VariableDeclaration violates this rule.
*/
function checkVariableDeclaration(
- node: TSESTree.VariableDeclaration,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+ const { allowInForLoopInit } = optionsObject;
+
if (
node.kind !== "let" ||
- shouldIgnoreLocalMutation(node, context, options) ||
- shouldIgnorePattern(node, context, options) ||
- (options.allowInForLoopInit && inForLoopInitializer(node))
+ shouldIgnoreLocalMutation(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject) ||
+ (allowInForLoopInit && inForLoopInitializer(node))
) {
return {
context,
diff --git a/src/rules/no-loop-statement.ts b/src/rules/no-loop-statement.ts
index 732d1dcbc..b3311ea47 100644
--- a/src/rules/no-loop-statement.ts
+++ b/src/rules/no-loop-statement.ts
@@ -1,28 +1,41 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-loop-statement" as const;
-// The options this rule can take.
-type Options = {};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [{}];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [];
-// The default options for the rule.
-const defaultOptions: Options = {};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [{}];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Unexpected loop, use map or reduce instead.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow imperative loops.",
@@ -37,12 +50,14 @@ const meta: RuleMetaData = {
*/
function checkLoop(
node:
- | TSESTree.DoWhileStatement
- | TSESTree.ForInStatement
- | TSESTree.ForOfStatement
- | TSESTree.ForStatement
- | TSESTree.WhileStatement,
- context: RuleContext
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): RuleResult {
// All loops violate this rule.
return { context, descriptors: [{ node, messageId: "generic" }] };
diff --git a/src/rules/no-method-signature.ts b/src/rules/no-method-signature.ts
index 4cc46997a..1359c4087 100644
--- a/src/rules/no-method-signature.ts
+++ b/src/rules/no-method-signature.ts
@@ -1,19 +1,28 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
import { inReadonly } from "~/util/tree";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-method-signature" as const;
-// The options this rule can take.
-type Options = {
- readonly ignoreIfReadonly: boolean;
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ Readonly<{
+ ignoreIfReadonly: boolean;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
{
type: "object",
@@ -27,19 +36,27 @@ const schema: JSONSchema4 = [
},
];
-// The default options for the rule.
-const defaultOptions: Options = {
- ignoreIfReadonly: true,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ ignoreIfReadonly: true,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic:
"Method signature is mutable, use property signature with readonly modifier instead.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description:
@@ -54,10 +71,14 @@ const meta: RuleMetaData = {
* Check if the given TSMethodSignature violates this rule.
*/
function checkTSMethodSignature(
- node: TSESTree.TSMethodSignature,
- context: RuleContext,
- { ignoreIfReadonly }: Options
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
+ options: Options
): RuleResult {
+ const [{ ignoreIfReadonly }] = options;
+
if (ignoreIfReadonly && inReadonly(node)) {
return { context, descriptors: [] };
}
diff --git a/src/rules/no-mixed-type.ts b/src/rules/no-mixed-type.ts
index b392c1ab3..0b38ff9ac 100644
--- a/src/rules/no-mixed-type.ts
+++ b/src/rules/no-mixed-type.ts
@@ -1,21 +1,30 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
-import { AST_NODE_TYPES } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import { AST_NODE_TYPES } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
import { isTSPropertySignature, isTSTypeLiteral } from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-mixed-type" as const;
-// The options this rule can take.
-type Options = {
- readonly checkInterfaces: boolean;
- readonly checkTypeLiterals: boolean;
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ Readonly<{
+ checkInterfaces: boolean;
+ checkTypeLiterals: boolean;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
{
type: "object",
@@ -31,19 +40,27 @@ const schema: JSONSchema4 = [
},
];
-// The default options for the rule.
-const defaultOptions: Options = {
- checkInterfaces: true,
- checkTypeLiterals: true,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ checkInterfaces: true,
+ checkTypeLiterals: true,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Only the same kind of members allowed in types.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description:
@@ -58,7 +75,7 @@ const meta: RuleMetaData = {
* Does the given type elements violate the rule.
*/
function hasTypeElementViolations(
- typeElements: ReadonlyArray
+ typeElements: ReadonlyArray>
): boolean {
type CarryType = {
readonly prevMemberType: AST_NODE_TYPES | undefined;
@@ -101,14 +118,18 @@ function hasTypeElementViolations(
* Check if the given TSInterfaceDeclaration violates this rule.
*/
function checkTSInterfaceDeclaration(
- node: TSESTree.TSInterfaceDeclaration,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [{ checkInterfaces }] = options;
+
return {
context,
descriptors:
- options.checkInterfaces && hasTypeElementViolations(node.body.body)
+ checkInterfaces && hasTypeElementViolations(node.body.body)
? [{ node, messageId: "generic" }]
: [],
};
@@ -118,14 +139,18 @@ function checkTSInterfaceDeclaration(
* Check if the given TSTypeAliasDeclaration violates this rule.
*/
function checkTSTypeAliasDeclaration(
- node: TSESTree.TSTypeAliasDeclaration,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [{ checkTypeLiterals }] = options;
+
return {
context,
descriptors:
- options.checkTypeLiterals &&
+ checkTypeLiterals &&
isTSTypeLiteral(node.typeAnnotation) &&
hasTypeElementViolations(node.typeAnnotation.members)
? [{ node, messageId: "generic" }]
diff --git a/src/rules/no-promise-reject.ts b/src/rules/no-promise-reject.ts
index 0846f29f0..9548a9acc 100644
--- a/src/rules/no-promise-reject.ts
+++ b/src/rules/no-promise-reject.ts
@@ -1,29 +1,42 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
import { isIdentifier, isMemberExpression } from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-promise-reject" as const;
-// The options this rule can take.
-type Options = {};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [{}];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [];
-// The default options for the rule.
-const defaultOptions: Options = {};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [{}];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Unexpected reject, return an error instead.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow try-catch[-finally] and try-finally patterns.",
@@ -37,8 +50,10 @@ const meta: RuleMetaData = {
* Check if the given CallExpression violates this rule.
*/
function checkCallExpression(
- node: TSESTree.CallExpression,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): RuleResult {
return {
context,
diff --git a/src/rules/no-return-void.ts b/src/rules/no-return-void.ts
index 1e235c642..3b74db63c 100644
--- a/src/rules/no-return-void.ts
+++ b/src/rules/no-return-void.ts
@@ -1,7 +1,8 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule, getTypeOfNode } from "~/util/rule";
import {
isFunctionLike,
@@ -13,17 +14,25 @@ import {
isVoidType,
} from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-return-void" as const;
-// The options this rule can take.
-type Options = {
- readonly allowNull: boolean;
- readonly allowUndefined: boolean;
- readonly ignoreImplicit: boolean;
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ Readonly<{
+ allowNull: boolean;
+ allowUndefined: boolean;
+ ignoreImplicit: boolean;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
{
type: "object",
@@ -42,20 +51,28 @@ const schema: JSONSchema4 = [
},
];
-// The default options for the rule.
-const defaultOptions: Options = {
- allowNull: true,
- allowUndefined: true,
- ignoreImplicit: false,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ allowNull: true,
+ allowUndefined: true,
+ ignoreImplicit: false,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Function must return a value.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow functions that don't return anything.",
@@ -70,15 +87,19 @@ const meta: RuleMetaData = {
*/
function checkFunction(
node:
- | TSESTree.ArrowFunctionExpression
- | TSESTree.FunctionDeclaration
- | TSESTree.FunctionExpression
- | TSESTree.TSFunctionType,
- context: RuleContext,
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [{ ignoreImplicit, allowNull, allowUndefined }] = options;
+
if (node.returnType === undefined) {
- if (!options.ignoreImplicit && isFunctionLike(node)) {
+ if (!ignoreImplicit && isFunctionLike(node)) {
const functionType = getTypeOfNode(node, context);
const returnType = functionType
?.getCallSignatures()?.[0]
@@ -87,8 +108,8 @@ function checkFunction(
if (
returnType !== undefined &&
(isVoidType(returnType) ||
- (!options.allowNull && isNullType(returnType)) ||
- (!options.allowUndefined && isUndefinedType(returnType)))
+ (!allowNull && isNullType(returnType)) ||
+ (!allowUndefined && isUndefinedType(returnType)))
) {
return {
context,
@@ -98,9 +119,8 @@ function checkFunction(
}
} else if (
isTSVoidKeyword(node.returnType.typeAnnotation) ||
- (!options.allowNull && isTSNullKeyword(node.returnType.typeAnnotation)) ||
- (!options.allowUndefined &&
- isTSUndefinedKeyword(node.returnType.typeAnnotation))
+ (!allowNull && isTSNullKeyword(node.returnType.typeAnnotation)) ||
+ (!allowUndefined && isTSUndefinedKeyword(node.returnType.typeAnnotation))
) {
return {
context,
diff --git a/src/rules/no-this-expression.ts b/src/rules/no-this-expression.ts
index d35d1d2d6..96f012856 100644
--- a/src/rules/no-this-expression.ts
+++ b/src/rules/no-this-expression.ts
@@ -1,28 +1,41 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-this-expression" as const;
-// The options this rule can take.
-type Options = {};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [{}];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [];
-// The default options for the rule.
-const defaultOptions: Options = {};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [{}];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Unexpected this, use functions not classes.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow this access.",
@@ -36,8 +49,10 @@ const meta: RuleMetaData = {
* Check if the given ThisExpression violates this rule.
*/
function checkThisExpression(
- node: TSESTree.ThisExpression,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): RuleResult {
// All throw statements violate this rule.
return { context, descriptors: [{ node, messageId: "generic" }] };
diff --git a/src/rules/no-throw-statement.ts b/src/rules/no-throw-statement.ts
index 7dafde0eb..5fcb7c00e 100644
--- a/src/rules/no-throw-statement.ts
+++ b/src/rules/no-throw-statement.ts
@@ -1,28 +1,41 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-throw-statement" as const;
-// The options this rule can take.
-type Options = {};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [{}];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [];
-// The default options for the rule.
-const defaultOptions: Options = {};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [{}];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Unexpected throw, throwing exceptions is not functional.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow throwing exceptions.",
@@ -36,8 +49,10 @@ const meta: RuleMetaData = {
* Check if the given ThrowStatement violates this rule.
*/
function checkThrowStatement(
- node: TSESTree.ThrowStatement,
- context: RuleContext
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >
): RuleResult {
// All throw statements violate this rule.
return { context, descriptors: [{ node, messageId: "generic" }] };
diff --git a/src/rules/no-try-statement.ts b/src/rules/no-try-statement.ts
index 208e456ea..cf88e7a73 100644
--- a/src/rules/no-try-statement.ts
+++ b/src/rules/no-try-statement.ts
@@ -1,19 +1,28 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "no-try-statement" as const;
-// The options this rule can take.
-type Options = {
- readonly allowCatch: boolean;
- readonly allowFinally: boolean;
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ Readonly<{
+ allowCatch: boolean;
+ allowFinally: boolean;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
{
type: "object",
@@ -29,20 +38,28 @@ const schema: JSONSchema4 = [
},
];
-// The default options for the rule.
-const defaultOptions: Options = {
- allowCatch: false,
- allowFinally: false,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ allowCatch: false,
+ allowFinally: false,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
catch: "Unexpected try-catch, this pattern is not functional.",
finally: "Unexpected try-finally, this pattern is not functional.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Disallow try-catch[-finally] and try-finally patterns.",
@@ -56,16 +73,20 @@ const meta: RuleMetaData = {
* Check if the given TryStatement violates this rule.
*/
function checkTryStatement(
- node: TSESTree.TryStatement,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [{ allowCatch, allowFinally }] = options;
+
return {
context,
descriptors:
- !options.allowCatch && node.handler !== null
+ !allowCatch && node.handler !== null
? [{ node, messageId: "catch" }]
- : !options.allowFinally && node.finalizer !== null
+ : !allowFinally && node.finalizer !== null
? [{ node, messageId: "finally" }]
: [],
};
diff --git a/src/rules/prefer-readonly-return-types.ts b/src/rules/prefer-readonly-return-types.ts
new file mode 100644
index 000000000..d80717862
--- /dev/null
+++ b/src/rules/prefer-readonly-return-types.ts
@@ -0,0 +1,158 @@
+import type { ReadonlynessOptions } from "@typescript-eslint/type-utils";
+import {
+ readonlynessOptionsDefaults,
+ readonlynessOptionsSchema,
+} from "@typescript-eslint/type-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import { deepmerge } from "deepmerge-ts";
+import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
+
+import type {
+ AllowLocalMutationOption,
+ IgnoreClassOption,
+ IgnoreInferredTypesOption,
+ IgnoreInterfaceOption,
+ IgnorePatternOption,
+} from "~/common/ignore-options";
+import {
+ allowLocalMutationOptionSchema,
+ ignoreClassOptionSchema,
+ ignoreInferredTypesOptionSchema,
+ ignoreInterfaceOptionSchema,
+ ignorePatternOptionSchema,
+ shouldIgnoreClass,
+ shouldIgnoreInferredTypes,
+ shouldIgnoreInterface,
+ shouldIgnoreLocalMutation,
+ shouldIgnorePattern,
+} from "~/common/ignore-options";
+import type { RuleResult } from "~/util/rule";
+import { isReadonly, createRule } from "~/util/rule";
+import { isFunctionLike, isTSFunctionType } from "~/util/typeguard";
+
+/**
+ * The name of this rule.
+ */
+export const name = "prefer-readonly-return-types" as const;
+
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ AllowLocalMutationOption &
+ IgnoreClassOption &
+ IgnoreInferredTypesOption &
+ IgnoreInterfaceOption &
+ IgnorePatternOption &
+ ReadonlynessOptions
+];
+
+/**
+ * The schema for the rule options.
+ */
+const schema: JSONSchema4 = [
+ {
+ type: "object",
+ properties: deepmerge(
+ allowLocalMutationOptionSchema,
+ ignoreClassOptionSchema,
+ ignoreInferredTypesOptionSchema,
+ ignoreInterfaceOptionSchema,
+ ignorePatternOptionSchema,
+ readonlynessOptionsSchema.properties
+ ),
+ additionalProperties: false,
+ },
+];
+
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ ignoreClass: false,
+ ignoreInterface: false,
+ allowLocalMutation: false,
+ ignoreInferredTypes: false,
+ ...readonlynessOptionsDefaults,
+ },
+];
+
+/**
+ * The possible error messages.
+ */
+const errorMessages = {
+ returnTypeShouldBeReadonly: "Return type should be readonly.",
+} as const;
+
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
+ type: "suggestion",
+ docs: {
+ description: "Prefer readonly return types over mutable one.",
+ recommended: "error",
+ },
+ messages: errorMessages,
+ fixable: "code",
+ schema,
+};
+
+/**
+ * Check if the given TypeAnnotation violates this rule.
+ */
+function checkTypeAnnotation(
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
+ [optionsObject]: Options
+): RuleResult {
+ if (
+ !isReturnType(node) ||
+ shouldIgnoreInferredTypes(node.typeAnnotation, context, optionsObject) ||
+ shouldIgnoreClass(node.typeAnnotation, context, optionsObject) ||
+ shouldIgnoreInterface(node.typeAnnotation, context, optionsObject) ||
+ shouldIgnoreLocalMutation(node.typeAnnotation, context, optionsObject) ||
+ shouldIgnorePattern(node.typeAnnotation, context, optionsObject) ||
+ isReadonly(node.typeAnnotation, context, optionsObject)
+ ) {
+ return {
+ context,
+ descriptors: [],
+ };
+ }
+
+ return {
+ context,
+ descriptors: [
+ {
+ node: node.typeAnnotation,
+ messageId: "returnTypeShouldBeReadonly",
+ },
+ ],
+ };
+}
+
+/**
+ * Is the given node a return type?
+ */
+function isReturnType(node: ReadonlyDeep) {
+ return (
+ node.parent !== undefined &&
+ (isFunctionLike(node.parent) || isTSFunctionType(node.parent)) &&
+ node.parent.returnType === node
+ );
+}
+
+// Create the rule.
+export const rule = createRule(
+ name,
+ meta,
+ defaultOptions,
+ {
+ TSTypeAnnotation: checkTypeAnnotation,
+ }
+);
diff --git a/src/rules/prefer-readonly-type.ts b/src/rules/prefer-readonly-type.ts
index 36626ea13..59c40bfa2 100644
--- a/src/rules/prefer-readonly-type.ts
+++ b/src/rules/prefer-readonly-type.ts
@@ -1,6 +1,7 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
import type {
AllowLocalMutationOption,
@@ -18,7 +19,7 @@ import {
ignoreInterfaceOptionSchema,
ignorePatternOptionSchema,
} from "~/common/ignore-options";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule, getTypeOfNode } from "~/util/rule";
import { isInReturnType } from "~/util/tree";
import {
@@ -34,29 +35,38 @@ import {
isTSTypeOperator,
} from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "prefer-readonly-type" as const;
-// The options this rule can take.
-type Options = AllowLocalMutationOption &
- IgnoreClassOption &
- IgnoreInterfaceOption &
- IgnorePatternOption & {
- readonly allowMutableReturnType: boolean;
- readonly checkImplicit: boolean;
- readonly ignoreCollections: boolean;
- };
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ AllowLocalMutationOption &
+ IgnoreClassOption &
+ IgnoreInterfaceOption &
+ IgnorePatternOption &
+ Readonly<{
+ allowMutableReturnType: boolean;
+ checkImplicit: boolean;
+ ignoreCollections: boolean;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
- deepmerge(
- allowLocalMutationOptionSchema,
- ignorePatternOptionSchema,
- ignoreClassOptionSchema,
- ignoreInterfaceOptionSchema,
- {
- type: "object",
- properties: {
+ {
+ type: "object",
+ properties: deepmerge(
+ allowLocalMutationOptionSchema,
+ ignorePatternOptionSchema,
+ ignoreClassOptionSchema,
+ ignoreInterfaceOptionSchema,
+ {
allowMutableReturnType: {
type: "boolean",
},
@@ -66,23 +76,29 @@ const schema: JSONSchema4 = [
ignoreCollections: {
type: "boolean",
},
- },
- additionalProperties: false,
- }
- ),
+ }
+ ),
+ additionalProperties: false,
+ },
];
-// The default options for the rule.
-const defaultOptions: Options = {
- checkImplicit: false,
- ignoreClass: false,
- ignoreInterface: false,
- ignoreCollections: false,
- allowLocalMutation: false,
- allowMutableReturnType: false,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ checkImplicit: false,
+ ignoreClass: false,
+ ignoreInterface: false,
+ ignoreCollections: false,
+ allowLocalMutation: false,
+ allowMutableReturnType: false,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
array: "Only readonly arrays allowed.",
implicit: "Implicitly a mutable array. Only readonly arrays allowed.",
@@ -91,8 +107,10 @@ const errorMessages = {
type: "Only readonly types allowed.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta = {
type: "suggestion",
docs: {
description: "Prefer readonly array over mutable arrays.",
@@ -120,16 +138,21 @@ const mutableTypeRegex = new RegExp(
* Check if the given ArrayType or TupleType violates this rule.
*/
function checkArrayOrTupleType(
- node: TSESTree.TSArrayType | TSESTree.TSTupleType,
- context: RuleContext,
+ node: ReadonlyDeep | ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+ const { allowMutableReturnType, ignoreCollections } = optionsObject;
+
if (
- shouldIgnoreClass(node, context, options) ||
- shouldIgnoreInterface(node, context, options) ||
- shouldIgnoreLocalMutation(node, context, options) ||
- shouldIgnorePattern(node, context, options) ||
- options.ignoreCollections
+ shouldIgnoreClass(node, context, optionsObject) ||
+ shouldIgnoreInterface(node, context, optionsObject) ||
+ shouldIgnoreLocalMutation(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject) ||
+ ignoreCollections
) {
return {
context,
@@ -143,7 +166,7 @@ function checkArrayOrTupleType(
(node.parent === undefined ||
!isTSTypeOperator(node.parent) ||
node.parent.operator !== "readonly") &&
- (!options.allowMutableReturnType || !isInReturnType(node))
+ (!allowMutableReturnType || !isInReturnType(node))
? [
{
node,
@@ -151,10 +174,17 @@ function checkArrayOrTupleType(
fix:
node.parent !== undefined && isTSArrayType(node.parent)
? (fixer) => [
- fixer.insertTextBefore(node, "(readonly "),
- fixer.insertTextAfter(node, ")"),
+ fixer.insertTextBefore(
+ node as TSESTree.Node,
+ "(readonly "
+ ),
+ fixer.insertTextAfter(node as TSESTree.Node, ")"),
]
- : (fixer) => fixer.insertTextBefore(node, "readonly "),
+ : (fixer) =>
+ fixer.insertTextBefore(
+ node as TSESTree.Node,
+ "readonly "
+ ),
},
]
: [],
@@ -165,15 +195,19 @@ function checkArrayOrTupleType(
* Check if the given TSMappedType violates this rule.
*/
function checkMappedType(
- node: TSESTree.TSMappedType,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+
if (
- shouldIgnoreClass(node, context, options) ||
- shouldIgnoreInterface(node, context, options) ||
- shouldIgnoreLocalMutation(node, context, options) ||
- shouldIgnorePattern(node, context, options)
+ shouldIgnoreClass(node, context, optionsObject) ||
+ shouldIgnoreInterface(node, context, optionsObject) ||
+ shouldIgnoreLocalMutation(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject)
) {
return {
context,
@@ -204,15 +238,20 @@ function checkMappedType(
* Check if the given TypeReference violates this rule.
*/
function checkTypeReference(
- node: TSESTree.TSTypeReference,
- context: RuleContext,
+ node: ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+ const { allowMutableReturnType, ignoreCollections } = optionsObject;
+
if (
- shouldIgnoreClass(node, context, options) ||
- shouldIgnoreInterface(node, context, options) ||
- shouldIgnoreLocalMutation(node, context, options) ||
- shouldIgnorePattern(node, context, options)
+ shouldIgnoreClass(node, context, optionsObject) ||
+ shouldIgnoreInterface(node, context, optionsObject) ||
+ shouldIgnoreLocalMutation(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject)
) {
return {
context,
@@ -221,10 +260,7 @@ function checkTypeReference(
}
if (isIdentifier(node.typeName)) {
- if (
- options.ignoreCollections &&
- mutableTypeRegex.test(node.typeName.name)
- ) {
+ if (ignoreCollections && mutableTypeRegex.test(node.typeName.name)) {
return {
context,
descriptors: [],
@@ -236,12 +272,16 @@ function checkTypeReference(
descriptors:
immutableType !== undefined &&
immutableType.length > 0 &&
- (!options.allowMutableReturnType || !isInReturnType(node))
+ (!allowMutableReturnType || !isInReturnType(node))
? [
{
node,
messageId: "type",
- fix: (fixer) => fixer.replaceText(node.typeName, immutableType),
+ fix: (fixer) =>
+ fixer.replaceText(
+ node.typeName as TSESTree.Node,
+ immutableType
+ ),
},
]
: [],
@@ -258,18 +298,23 @@ function checkTypeReference(
*/
function checkProperty(
node:
- | TSESTree.PropertyDefinition
- | TSESTree.TSIndexSignature
- | TSESTree.TSParameterProperty
- | TSESTree.TSPropertySignature,
- context: RuleContext,
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+ const { allowMutableReturnType } = optionsObject;
+
if (
- shouldIgnoreClass(node, context, options) ||
- shouldIgnoreInterface(node, context, options) ||
- shouldIgnoreLocalMutation(node, context, options) ||
- shouldIgnorePattern(node, context, options)
+ shouldIgnoreClass(node, context, optionsObject) ||
+ shouldIgnoreInterface(node, context, optionsObject) ||
+ shouldIgnoreLocalMutation(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject)
) {
return {
context,
@@ -281,18 +326,26 @@ function checkProperty(
context,
descriptors:
node.readonly !== true &&
- (!options.allowMutableReturnType || !isInReturnType(node))
+ (!allowMutableReturnType || !isInReturnType(node))
? [
{
node,
messageId: "property",
fix:
isTSIndexSignature(node) || isTSPropertySignature(node)
- ? (fixer) => fixer.insertTextBefore(node, "readonly ")
+ ? (fixer) =>
+ fixer.insertTextBefore(node as TSESTree.Node, "readonly ")
: isTSParameterProperty(node)
? (fixer) =>
- fixer.insertTextBefore(node.parameter, "readonly ")
- : (fixer) => fixer.insertTextBefore(node.key, "readonly "),
+ fixer.insertTextBefore(
+ node.parameter as TSESTree.Node,
+ "readonly "
+ )
+ : (fixer) =>
+ fixer.insertTextBefore(
+ node.key as TSESTree.Node,
+ "readonly "
+ ),
},
]
: [],
@@ -304,19 +357,24 @@ function checkProperty(
*/
function checkImplicitType(
node:
- | TSESTree.ArrowFunctionExpression
- | TSESTree.FunctionDeclaration
- | TSESTree.FunctionExpression
- | TSESTree.VariableDeclaration,
- context: RuleContext,
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep
+ | ReadonlyDeep,
+ context: ReadonlyDeep<
+ TSESLint.RuleContext
+ >,
options: Options
): RuleResult {
+ const [optionsObject] = options;
+ const { checkImplicit, ignoreCollections } = optionsObject;
+
if (
- !options.checkImplicit ||
- shouldIgnoreClass(node, context, options) ||
- shouldIgnoreInterface(node, context, options) ||
- shouldIgnoreLocalMutation(node, context, options) ||
- shouldIgnorePattern(node, context, options)
+ !checkImplicit ||
+ shouldIgnoreClass(node, context, optionsObject) ||
+ shouldIgnoreInterface(node, context, optionsObject) ||
+ shouldIgnoreLocalMutation(node, context, optionsObject) ||
+ shouldIgnorePattern(node, context, optionsObject)
) {
return {
context,
@@ -358,7 +416,7 @@ function checkImplicitType(
declarator.id.typeAnnotation === undefined &&
declarator.init !== null &&
isArrayType(getTypeOfNode(declarator.init, context)) &&
- !options.ignoreCollections
+ !ignoreCollections
? [
{
node: declarator.node,
diff --git a/src/rules/prefer-tacit.ts b/src/rules/prefer-tacit.ts
index acbed68c5..6e3d10079 100644
--- a/src/rules/prefer-tacit.ts
+++ b/src/rules/prefer-tacit.ts
@@ -1,12 +1,13 @@
-import type { TSESTree } from "@typescript-eslint/experimental-utils";
-import type { ReportDescriptor } from "@typescript-eslint/experimental-utils/dist/ts-eslint";
+import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
+import type { ReportDescriptor } from "@typescript-eslint/utils/dist/ts-eslint";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";
+import type { ReadonlyDeep } from "type-fest";
import type { FunctionLikeDeclaration, Type } from "typescript";
import type { IgnorePatternOption } from "~/common/ignore-options";
import { ignorePatternOptionSchema } from "~/common/ignore-options";
-import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
+import type { RuleResult } from "~/util/rule";
import { createRule, getESTreeNode, getTypeOfNode } from "~/util/rule";
import {
isBlockStatement,
@@ -18,23 +19,32 @@ import {
isTSFunctionType,
} from "~/util/typeguard";
-// The name of this rule.
+/**
+ * The name of this rule.
+ */
export const name = "prefer-tacit" as const;
-// The options this rule can take.
-type Options = IgnorePatternOption & {
- readonly assumeTypes:
- | false
- | {
- readonly allowFixer: boolean;
- };
-};
+/**
+ * The options this rule can take.
+ */
+type Options = readonly [
+ IgnorePatternOption &
+ Readonly<{
+ assumeTypes:
+ | false
+ | Readonly<{
+ allowFixer: boolean;
+ }>;
+ }>
+];
-// The schema for the rule options.
+/**
+ * The schema for the rule options.
+ */
const schema: JSONSchema4 = [
- deepmerge(ignorePatternOptionSchema, {
+ {
type: "object",
- properties: {
+ properties: deepmerge(ignorePatternOptionSchema, {
ignoreImmediateMutation: {
type: "boolean",
},
@@ -55,23 +65,31 @@ const schema: JSONSchema4 = [
},
],
},
- },
+ }),
additionalProperties: false,
- }),
+ },
];
-// The default options for the rule.
-const defaultOptions: Options = {
- assumeTypes: false,
-};
+/**
+ * The default options for the rule.
+ */
+const defaultOptions: Options = [
+ {
+ assumeTypes: false,
+ },
+];
-// The possible error messages.
+/**
+ * The possible error messages.
+ */
const errorMessages = {
generic: "Potentially unnecessary function wrapper.",
} as const;
-// The meta data for this rule.
-const meta: RuleMetaData = {
+/**
+ * The meta data for this rule.
+ */
+const meta: ESLintUtils.NamedCreateRuleMeta