From 311970af79afff23070bef4b0c39122982707d47 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Mon, 15 Apr 2024 02:02:38 +1200 Subject: [PATCH] feat(prefer-immutable-types): allow overriding options based on where the type is declared fix #800 --- docs/rules/prefer-immutable-types.md | 54 ++ src/options/overrides.ts | 18 + src/rules/prefer-immutable-types.ts | 498 ++++++++++-------- .../ts/parameters/invalid.ts | 31 ++ .../ts/parameters/valid.ts | 36 ++ 5 files changed, 430 insertions(+), 207 deletions(-) diff --git a/docs/rules/prefer-immutable-types.md b/docs/rules/prefer-immutable-types.md index 18be71d56..2c056696f 100644 --- a/docs/rules/prefer-immutable-types.md +++ b/docs/rules/prefer-immutable-types.md @@ -244,6 +244,37 @@ type Options = { ReadonlyDeep?: Array>; Immutable?: Array>; }; + + overrides?: Array<{ + match: Array< + | { + from: "file"; + path?: string; + name?: string | string[]; + pattern?: RegExp | RegExp[]; + ignoreName?: string | string[]; + ignorePattern?: RegExp | RegExp[]; + } + | { + from: "lib"; + name?: string | string[]; + pattern?: RegExp | RegExp[]; + ignoreName?: string | string[]; + ignorePattern?: RegExp | RegExp[]; + } + | { + from: "package"; + package?: string; + name?: string | string[]; + pattern?: RegExp | RegExp[]; + ignoreName?: string | string[]; + ignorePattern?: RegExp | RegExp[]; + } + >; + options: Omit; + inherit?: boolean; + disable: boolean; + }>; }; ``` @@ -475,3 +506,26 @@ It allows for the ability to ignore violations based on the identifier (name) of This option takes a `RegExp` string or an array of `RegExp` strings. It allows for the ability to ignore violations based on the type (as written, with whitespace removed) of the node in question. + +### `overrides` + +Allows for applying overrides to the options based on where the type is defined. +This can be used to override the settings for types coming from 3rd party libraries. + +Note: Only the first matching override will be used. + +#### `overrides[n].specifiers` + +A specifier, or an array of specifiers to match the function type against. + +#### `overrides[n].options` + +The options to use when a specifiers matches. + +#### `overrides[n].inherit` + +Inherit the root options? Default is `true`. + +#### `overrides[n].disable` + +If true, when a specifier matches, this rule will not be applied to the matching node. diff --git a/src/options/overrides.ts b/src/options/overrides.ts index f9c7f4fed..292d519cc 100644 --- a/src/options/overrides.ts +++ b/src/options/overrides.ts @@ -2,6 +2,7 @@ import { type TSESTree } from "@typescript-eslint/utils"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; import { deepmerge } from "deepmerge-ts"; import typeMatchesSpecifier from "ts-declaration-location"; +import { type Type, type TypeNode } from "typescript"; import { getTypeDataOfNode } from "#eslint-plugin-functional/utils/rule"; import { @@ -108,6 +109,23 @@ export function getCoreOptions< } const [type, typeNode] = getTypeDataOfNode(node, context); + return getCoreOptionsForType(type, typeNode, context, options); +} + +export function getCoreOptionsForType< + CoreOptions extends object, + Options extends Readonly>, +>( + type: Type, + typeNode: TypeNode | null, + context: Readonly>, + options: Readonly, +): CoreOptions | null { + const program = context.sourceCode.parserServices?.program ?? undefined; + if (program === undefined) { + return options; + } + const found = options.overrides?.find((override) => (Array.isArray(override.specifiers) ? override.specifiers diff --git a/src/rules/prefer-immutable-types.ts b/src/rules/prefer-immutable-types.ts index c5199e5a2..cec620fcf 100644 --- a/src/rules/prefer-immutable-types.ts +++ b/src/rules/prefer-immutable-types.ts @@ -9,13 +9,19 @@ import { } from "@typescript-eslint/utils/ts-eslint"; import { deepmerge } from "deepmerge-ts"; import { Immutability } from "is-immutable-type"; +import { type Type, type TypeNode } from "typescript"; import { type IgnoreClassesOption, + type OverridableOptions, + type RawOverridableOptions, + getCoreOptions, + getCoreOptionsForType, ignoreClassesOptionSchema, shouldIgnoreClasses, shouldIgnoreInFunction, shouldIgnorePattern, + upgradeRawOverridableOptions, } from "#eslint-plugin-functional/options"; import { ruleNameScope } from "#eslint-plugin-functional/utils/misc"; import { type ESFunctionType } from "#eslint-plugin-functional/utils/node-types"; @@ -24,6 +30,7 @@ import { type RuleResult, createRule, getReturnTypesOfFunction, + getTypeDataOfNode, getTypeImmutabilityOfNode, getTypeImmutabilityOfType, isImplementationOfOverload, @@ -42,6 +49,8 @@ import { isTSTypePredicate, } from "#eslint-plugin-functional/utils/type-guards"; +import { overridableOptionsSchema } from "../utils/schemas"; + /** * The name of this rule. */ @@ -55,7 +64,8 @@ export const fullName = `${ruleNameScope}/${name}`; type RawEnforcement = | Exclude | "None" - | false; + | false + | undefined; type Option = IgnoreClassesOption & { enforcement: RawEnforcement; @@ -64,6 +74,20 @@ type Option = IgnoreClassesOption & { ignoreTypePattern?: string[] | string; }; +type CoreOptions = Option & { + parameters?: Partial