Skip to content

Commit 0130f0f

Browse files
feat(prefer-immutable-types): allow overriding options based on where the type is declared
1 parent 0a6bd90 commit 0130f0f

File tree

8 files changed

+293
-140
lines changed

8 files changed

+293
-140
lines changed

docs/rules/functional-parameters.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,12 @@ Getters should always take zero parameters, and setter one. If for some reason y
187187

188188
### `overrides`
189189

190+
_Using this option requires type infomation._
191+
190192
Allows for applying overrides to the options based on where the function's type is defined.
191193
This can be used to override the settings for types coming from 3rd party libraries.
192194

193-
Note: Using this option requires type infomation.
194-
Note 2: Only the first matching override will be used.
195+
Note: Only the first matching override will be used.
195196

196197
#### `overrides[n].specifiers`
197198

docs/rules/prefer-immutable-types.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,22 @@ It allows for the ability to ignore violations based on the identifier (name) of
475475

476476
This option takes a `RegExp` string or an array of `RegExp` strings.
477477
It allows for the ability to ignore violations based on the type (as written, with whitespace removed) of the node in question.
478+
479+
### `overrides`
480+
481+
Allows for applying overrides to the options based on where the type is defined.
482+
This can be used to override the settings for types coming from 3rd party libraries.
483+
484+
Note: Only the first matching override will be used.
485+
486+
#### `overrides[n].specifiers`
487+
488+
A specifier, or an array of specifiers to match the function type against.
489+
490+
#### `overrides[n].options`
491+
492+
The options to use when a specifiers matches.
493+
494+
#### `overrides[n].disable`
495+
496+
If true, when a specifier matches, this rule will not be applied to the matching node.

src/options/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./ignore";
2+
export * from "./overrides";

src/options/overrides.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { type TSESTree } from "@typescript-eslint/utils";
2+
import { type RuleContext } from "@typescript-eslint/utils/ts-eslint";
3+
import typeMatchesSpecifier, {
4+
type TypeDeclarationSpecifier,
5+
} from "ts-declaration-location";
6+
7+
import { getTypeOfNode } from "../utils/rule";
8+
9+
/**
10+
* Options that can be overridden.
11+
*/
12+
export type OverridableOptions<CoreOptions> = CoreOptions & {
13+
overrides?: Array<
14+
{
15+
specifiers: TypeDeclarationSpecifier | TypeDeclarationSpecifier[];
16+
} & (
17+
| {
18+
options: CoreOptions;
19+
disable?: false;
20+
}
21+
| {
22+
disable: true;
23+
}
24+
)
25+
>;
26+
};
27+
28+
/**
29+
* Get the core options to use, taking into account overrides.
30+
*
31+
* @throws when there is a configuration error.
32+
*/
33+
export function getCoreOptions<
34+
CoreOptions extends object,
35+
Options extends readonly [Readonly<OverridableOptions<CoreOptions>>],
36+
>(
37+
node: TSESTree.Node,
38+
context: Readonly<RuleContext<string, Options>>,
39+
options: Readonly<Options>,
40+
): CoreOptions | null {
41+
const [optionsObject] = options;
42+
43+
const program = context.sourceCode.parserServices?.program ?? undefined;
44+
if (program === undefined) {
45+
return optionsObject;
46+
}
47+
48+
const type = getTypeOfNode(node, context);
49+
const found = optionsObject.overrides?.find((override) =>
50+
(Array.isArray(override.specifiers)
51+
? override.specifiers
52+
: [override.specifiers]
53+
).some((specifier) => typeMatchesSpecifier(program, specifier, type)),
54+
);
55+
56+
if (found !== undefined) {
57+
if (found.disable === true) {
58+
return null;
59+
}
60+
if (found.options === undefined) {
61+
// eslint-disable-next-line functional/no-throw-statements
62+
throw new Error("Configuration error: No options found for override.");
63+
}
64+
return found.options;
65+
}
66+
67+
return optionsObject;
68+
}

src/rules/functional-parameters.ts

Lines changed: 9 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,21 @@ import {
55
} from "@typescript-eslint/utils/json-schema";
66
import { type RuleContext } from "@typescript-eslint/utils/ts-eslint";
77
import { deepmerge } from "deepmerge-ts";
8-
import typeMatchesSpecifier, {
9-
type TypeDeclarationSpecifier,
10-
} from "ts-declaration-location";
118

129
import {
10+
getCoreOptions,
1311
ignoreIdentifierPatternOptionSchema,
1412
ignorePrefixSelectorOptionSchema,
1513
shouldIgnorePattern,
1614
type IgnoreIdentifierPatternOption,
1715
type IgnorePrefixSelectorOption,
16+
type OverridableOptions,
1817
} from "#eslint-plugin-functional/options";
1918
import { typeSpecifiersSchema } from "#eslint-plugin-functional/utils/common-schemas";
2019
import { ruleNameScope } from "#eslint-plugin-functional/utils/misc";
2120
import { type ESFunction } from "#eslint-plugin-functional/utils/node-types";
2221
import {
2322
createRuleUsingFunction,
24-
getTypeOfNode,
2523
type NamedCreateRuleCustomMeta,
2624
type RuleResult,
2725
} from "#eslint-plugin-functional/utils/rule";
@@ -71,21 +69,7 @@ type CoreOptions = {
7169
type Options = [
7270
IgnoreIdentifierPatternOption &
7371
IgnorePrefixSelectorOption &
74-
CoreOptions & {
75-
overrides?: Array<
76-
{
77-
specifiers: TypeDeclarationSpecifier | TypeDeclarationSpecifier[];
78-
} & (
79-
| {
80-
options: CoreOptions;
81-
disable?: false;
82-
}
83-
| {
84-
disable: true;
85-
}
86-
)
87-
>;
88-
},
72+
OverridableOptions<CoreOptions>,
8973
];
9074

9175
const coreOptionsPropertiesSchema: JSONSchema4ObjectSchema["properties"] = {
@@ -207,39 +191,6 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages, Options> = {
207191
schema,
208192
};
209193

210-
/**
211-
* Get the core options to use, taking into account overrides.
212-
*/
213-
function getCoreOptions(
214-
node: TSESTree.Node,
215-
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
216-
options: Readonly<Options>,
217-
): CoreOptions | null {
218-
const [optionsObject] = options;
219-
220-
const program = context.sourceCode.parserServices?.program ?? undefined;
221-
if (program === undefined) {
222-
return optionsObject;
223-
}
224-
225-
const type = getTypeOfNode(node, context);
226-
const found = optionsObject.overrides?.find((override) =>
227-
(Array.isArray(override.specifiers)
228-
? override.specifiers
229-
: [override.specifiers]
230-
).some((specifier) => typeMatchesSpecifier(program, specifier, type)),
231-
);
232-
233-
if (found !== undefined) {
234-
if (found.disable === true) {
235-
return null;
236-
}
237-
return found.options;
238-
}
239-
240-
return optionsObject;
241-
}
242-
243194
/**
244195
* Get the rest parameter violations.
245196
*/
@@ -324,7 +275,11 @@ function checkFunction(
324275
};
325276
}
326277

327-
const optionsToUse = getCoreOptions(node, context, options);
278+
const optionsToUse = getCoreOptions<CoreOptions, Options>(
279+
node,
280+
context,
281+
options,
282+
);
328283

329284
return {
330285
context,
@@ -367,7 +322,7 @@ function checkIdentifier(
367322
const optionsToUse =
368323
functionNode === null
369324
? optionsObject
370-
: getCoreOptions(functionNode, context, options);
325+
: getCoreOptions<CoreOptions, Options>(functionNode, context, options);
371326
if (optionsToUse === null) {
372327
return {
373328
context,

0 commit comments

Comments
 (0)