Skip to content

Commit 389d79d

Browse files
feat(prefer-immutable-types): allow overriding options based on where the type is declared
fix #800
1 parent 4a927d8 commit 389d79d

File tree

4 files changed

+229
-89
lines changed

4 files changed

+229
-89
lines changed

docs/rules/prefer-immutable-types.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,37 @@ type Options = {
244244
ReadonlyDeep?: Array<Array<{ pattern: string; replace: string }>>;
245245
Immutable?: Array<Array<{ pattern: string; replace: string }>>;
246246
};
247+
248+
overrides?: Array<{
249+
match: Array<
250+
| {
251+
from: "file";
252+
path?: string;
253+
name?: string | string[];
254+
pattern?: RegExp | RegExp[];
255+
ignoreName?: string | string[];
256+
ignorePattern?: RegExp | RegExp[];
257+
}
258+
| {
259+
from: "lib";
260+
name?: string | string[];
261+
pattern?: RegExp | RegExp[];
262+
ignoreName?: string | string[];
263+
ignorePattern?: RegExp | RegExp[];
264+
}
265+
| {
266+
from: "package";
267+
package?: string;
268+
name?: string | string[];
269+
pattern?: RegExp | RegExp[];
270+
ignoreName?: string | string[];
271+
ignorePattern?: RegExp | RegExp[];
272+
}
273+
>;
274+
options: Omit<Options, "overrides">;
275+
inherit?: boolean;
276+
disable: boolean;
277+
}>;
247278
};
248279
```
249280

@@ -475,3 +506,26 @@ It allows for the ability to ignore violations based on the identifier (name) of
475506

476507
This option takes a `RegExp` string or an array of `RegExp` strings.
477508
It allows for the ability to ignore violations based on the type (as written, with whitespace removed) of the node in question.
509+
510+
### `overrides`
511+
512+
Allows for applying overrides to the options based on where the type is defined.
513+
This can be used to override the settings for types coming from 3rd party libraries.
514+
515+
Note: Only the first matching override will be used.
516+
517+
#### `overrides[n].specifiers`
518+
519+
A specifier, or an array of specifiers to match the function type against.
520+
521+
#### `overrides[n].options`
522+
523+
The options to use when a specifiers matches.
524+
525+
#### `overrides[n].inherit`
526+
527+
Inherit the root options? Default is `true`.
528+
529+
#### `overrides[n].disable`
530+
531+
If true, when a specifier matches, this rule will not be applied to the matching node.

src/rules/prefer-immutable-types.ts

Lines changed: 108 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { Immutability } from "is-immutable-type";
1212

1313
import {
1414
type IgnoreClassesOption,
15+
type OverridableOptions,
16+
getCoreOptions,
1517
ignoreClassesOptionSchema,
1618
shouldIgnoreClasses,
1719
shouldIgnoreInFunction,
@@ -42,6 +44,8 @@ import {
4244
isTSTypePredicate,
4345
} from "#eslint-plugin-functional/utils/type-guards";
4446

47+
import { overridableOptionsSchema } from "../utils/schemas";
48+
4549
/**
4650
* The name of this rule.
4751
*/
@@ -55,7 +59,8 @@ export const fullName = `${ruleNameScope}/${name}`;
5559
type RawEnforcement =
5660
| Exclude<Immutability | keyof typeof Immutability, "Unknown" | "Mutable">
5761
| "None"
58-
| false;
62+
| false
63+
| undefined;
5964

6065
type Option = IgnoreClassesOption & {
6166
enforcement: RawEnforcement;
@@ -64,6 +69,20 @@ type Option = IgnoreClassesOption & {
6469
ignoreTypePattern?: string[] | string;
6570
};
6671

72+
type CoreOptions = Option & {
73+
parameters?: Partial<Option> | RawEnforcement;
74+
returnTypes?: Partial<Option> | RawEnforcement;
75+
variables?:
76+
| Partial<
77+
Option & {
78+
ignoreInFunctions?: boolean;
79+
}
80+
>
81+
| RawEnforcement;
82+
fixer?: FixerConfigRawMap;
83+
suggestions?: SuggestionConfigRawMap;
84+
};
85+
6786
type FixerConfigRaw = {
6887
pattern: string;
6988
replace: string;
@@ -93,21 +112,7 @@ type SuggestionsConfig = FixerConfig[];
93112
/**
94113
* The options this rule can take.
95114
*/
96-
type Options = [
97-
Option & {
98-
parameters?: Partial<Option> | RawEnforcement;
99-
returnTypes?: Partial<Option> | RawEnforcement;
100-
variables?:
101-
| Partial<
102-
Option & {
103-
ignoreInFunctions?: boolean;
104-
}
105-
>
106-
| RawEnforcement;
107-
fixer?: FixerConfigRawMap;
108-
suggestions?: SuggestionConfigRawMap;
109-
},
110-
];
115+
type Options = [OverridableOptions<CoreOptions>];
111116

112117
/**
113118
* The enum options for the level of enforcement.
@@ -211,53 +216,53 @@ const suggestionsSchema: JSONSchema4 = {
211216
},
212217
};
213218

214-
/**
215-
* The schema for the rule options.
216-
*/
217-
const schema: JSONSchema4[] = [
218-
{
219-
type: "object",
220-
properties: deepmerge(optionExpandedSchema, {
221-
parameters: optionSchema,
222-
returnTypes: optionSchema,
223-
variables: {
224-
oneOf: [
225-
{
226-
type: "object",
227-
properties: deepmerge(optionExpandedSchema, {
228-
ignoreInFunctions: {
229-
type: "boolean",
230-
},
231-
}),
232-
additionalProperties: false,
233-
},
234-
{
235-
type: ["string", "number", "boolean"],
236-
enum: enforcementEnumOptions,
237-
},
238-
],
239-
},
240-
fixer: {
219+
const coreOptionsPropertiesSchema: NonNullable<
220+
JSONSchema4ObjectSchema["properties"]
221+
> = deepmerge(optionExpandedSchema, {
222+
parameters: optionSchema,
223+
returnTypes: optionSchema,
224+
variables: {
225+
oneOf: [
226+
{
241227
type: "object",
242-
properties: {
243-
ReadonlyShallow: fixerSchema,
244-
ReadonlyDeep: fixerSchema,
245-
Immutable: fixerSchema,
246-
},
228+
properties: deepmerge(optionExpandedSchema, {
229+
ignoreInFunctions: {
230+
type: "boolean",
231+
},
232+
}),
247233
additionalProperties: false,
248234
},
249-
suggestions: {
250-
type: "object",
251-
properties: {
252-
ReadonlyShallow: suggestionsSchema,
253-
ReadonlyDeep: suggestionsSchema,
254-
Immutable: suggestionsSchema,
255-
},
256-
additionalProperties: false,
235+
{
236+
type: ["string", "number", "boolean"],
237+
enum: enforcementEnumOptions,
257238
},
258-
}),
239+
],
240+
},
241+
fixer: {
242+
type: "object",
243+
properties: {
244+
ReadonlyShallow: fixerSchema,
245+
ReadonlyDeep: fixerSchema,
246+
Immutable: fixerSchema,
247+
},
259248
additionalProperties: false,
260249
},
250+
suggestions: {
251+
type: "object",
252+
properties: {
253+
ReadonlyShallow: suggestionsSchema,
254+
ReadonlyDeep: suggestionsSchema,
255+
Immutable: suggestionsSchema,
256+
},
257+
additionalProperties: false,
258+
},
259+
});
260+
261+
/**
262+
* The schema for the rule options.
263+
*/
264+
const schema: JSONSchema4[] = [
265+
overridableOptionsSchema(coreOptionsPropertiesSchema),
261266
];
262267

263268
/**
@@ -398,7 +403,7 @@ function getConfiguredSuggestionFixers(
398403
* Get the level of enforcement from the raw value given.
399404
*/
400405
function parseEnforcement(rawEnforcement: RawEnforcement) {
401-
return rawEnforcement === "None"
406+
return rawEnforcement === "None" || rawEnforcement === undefined
402407
? false
403408
: typeof rawEnforcement === "string"
404409
? Immutability[rawEnforcement]
@@ -454,35 +459,32 @@ function parseSuggestionsConfigs(
454459
function getParameterTypeViolations(
455460
node: ESFunctionType,
456461
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
457-
options: Readonly<Options>,
462+
options: Readonly<CoreOptions>,
458463
): Descriptor[] {
459-
const [optionsObject] = options;
460464
const {
461465
parameters: rawOption,
462466
fixer: rawFixerConfig,
463467
suggestions: rawSuggestionsConfigs,
464-
} = optionsObject;
468+
} = options;
465469
const {
466470
enforcement: rawEnforcement,
467471
ignoreInferredTypes,
468472
ignoreClasses,
469473
ignoreNamePattern,
470474
ignoreTypePattern,
471475
} = {
472-
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
473-
ignoreClasses: optionsObject.ignoreClasses,
474-
ignoreNamePattern: optionsObject.ignoreNamePattern,
475-
ignoreTypePattern: optionsObject.ignoreTypePattern,
476+
ignoreInferredTypes: options.ignoreInferredTypes,
477+
ignoreClasses: options.ignoreClasses,
478+
ignoreNamePattern: options.ignoreNamePattern,
479+
ignoreTypePattern: options.ignoreTypePattern,
476480
...(typeof rawOption === "object"
477481
? rawOption
478482
: {
479483
enforcement: rawOption,
480484
}),
481485
};
482486

483-
const enforcement = parseEnforcement(
484-
rawEnforcement ?? optionsObject.enforcement,
485-
);
487+
const enforcement = parseEnforcement(rawEnforcement ?? options.enforcement);
486488
if (
487489
enforcement === false ||
488490
shouldIgnoreClasses(node, context, ignoreClasses)
@@ -592,31 +594,28 @@ function getParameterTypeViolations(
592594
function getReturnTypeViolations(
593595
node: ESFunctionType,
594596
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
595-
options: Readonly<Options>,
597+
options: Readonly<CoreOptions>,
596598
): Descriptor[] {
597-
const [optionsObject] = options;
598599
const {
599600
returnTypes: rawOption,
600601
fixer: rawFixerConfig,
601602
suggestions: rawSuggestionsConfigs,
602-
} = optionsObject;
603+
} = options;
603604
const {
604605
enforcement: rawEnforcement,
605606
ignoreInferredTypes,
606607
ignoreClasses,
607608
ignoreNamePattern,
608609
ignoreTypePattern,
609610
} = {
610-
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
611-
ignoreClasses: optionsObject.ignoreClasses,
612-
ignoreNamePattern: optionsObject.ignoreNamePattern,
613-
ignoreTypePattern: optionsObject.ignoreTypePattern,
611+
ignoreInferredTypes: options.ignoreInferredTypes,
612+
ignoreClasses: options.ignoreClasses,
613+
ignoreNamePattern: options.ignoreNamePattern,
614+
ignoreTypePattern: options.ignoreTypePattern,
614615
...(typeof rawOption === "object" ? rawOption : { enforcement: rawOption }),
615616
};
616617

617-
const enforcement = parseEnforcement(
618-
rawEnforcement ?? optionsObject.enforcement,
619-
);
618+
const enforcement = parseEnforcement(rawEnforcement ?? options.enforcement);
620619

621620
if (
622621
enforcement === false ||
@@ -743,10 +742,19 @@ function checkFunction(
743742
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
744743
options: Readonly<Options>,
745744
): RuleResult<keyof typeof errorMessages, Options> {
746-
const descriptors = [
747-
...getParameterTypeViolations(node, context, options),
748-
...getReturnTypeViolations(node, context, options),
749-
];
745+
const optionsToUse = getCoreOptions<CoreOptions, Options>(
746+
node,
747+
context,
748+
options,
749+
);
750+
751+
const descriptors =
752+
optionsToUse === null
753+
? []
754+
: [
755+
...getParameterTypeViolations(node, context, optionsToUse),
756+
...getReturnTypeViolations(node, context, optionsToUse),
757+
];
750758

751759
return {
752760
context,
@@ -762,13 +770,24 @@ function checkVariable(
762770
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
763771
options: Readonly<Options>,
764772
): RuleResult<keyof typeof errorMessages, Options> {
765-
const [optionsObject] = options;
773+
const optionsToUse = getCoreOptions<CoreOptions, Options>(
774+
node,
775+
context,
776+
options,
777+
);
778+
779+
if (optionsToUse === null) {
780+
return {
781+
context,
782+
descriptors: [],
783+
};
784+
}
766785

767786
const {
768787
variables: rawOption,
769788
fixer: rawFixerConfig,
770789
suggestions: rawSuggestionsConfigs,
771-
} = optionsObject;
790+
} = optionsToUse;
772791
const {
773792
enforcement: rawEnforcement,
774793
ignoreInferredTypes,
@@ -777,16 +796,16 @@ function checkVariable(
777796
ignoreTypePattern,
778797
ignoreInFunctions,
779798
} = {
780-
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
781-
ignoreClasses: optionsObject.ignoreClasses,
782-
ignoreNamePattern: optionsObject.ignoreNamePattern,
783-
ignoreTypePattern: optionsObject.ignoreTypePattern,
799+
ignoreInferredTypes: optionsToUse.ignoreInferredTypes,
800+
ignoreClasses: optionsToUse.ignoreClasses,
801+
ignoreNamePattern: optionsToUse.ignoreNamePattern,
802+
ignoreTypePattern: optionsToUse.ignoreTypePattern,
784803
ignoreInFunctions: false,
785804
...(typeof rawOption === "object" ? rawOption : { enforcement: rawOption }),
786805
};
787806

788807
const enforcement = parseEnforcement(
789-
rawEnforcement ?? optionsObject.enforcement,
808+
rawEnforcement ?? optionsToUse.enforcement,
790809
);
791810

792811
if (

0 commit comments

Comments
 (0)