Skip to content

Commit 2f838a6

Browse files
feat(functional-parameters): allows applying overrides to the options based on the function's type
fix #575
1 parent d629853 commit 2f838a6

File tree

4 files changed

+317
-84
lines changed

4 files changed

+317
-84
lines changed

docs/rules/functional-parameters.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
Disallow use of rest parameters, the `arguments` keyword and enforces that functions take at least 1 parameter.
1515

16+
Note: type information is only required when using the [overrides](#overrides) option.
17+
1618
## Rule Details
1719

1820
In functions, `arguments` is a special variable that is implicitly available.
@@ -76,6 +78,36 @@ type Options = {
7678
};
7779
ignoreIdentifierPattern?: string[] | string;
7880
ignorePrefixSelector?: string[] | string;
81+
overrides?: Array<{
82+
match: Array<
83+
| {
84+
from: "file";
85+
path?: string;
86+
name?: string | string[];
87+
pattern?: RegExp | RegExp[];
88+
ignoreName?: string | string[];
89+
ignorePattern?: RegExp | RegExp[];
90+
}
91+
| {
92+
from: "lib";
93+
name?: string | string[];
94+
pattern?: RegExp | RegExp[];
95+
ignoreName?: string | string[];
96+
ignorePattern?: RegExp | RegExp[];
97+
}
98+
| {
99+
from: "package";
100+
package?: string;
101+
name?: string | string[];
102+
pattern?: RegExp | RegExp[];
103+
ignoreName?: string | string[];
104+
ignorePattern?: RegExp | RegExp[];
105+
}
106+
>;
107+
options: Omit<Options, "overrides">;
108+
inherit?: boolean;
109+
disable: boolean;
110+
}>;
79111
};
80112
```
81113

@@ -210,3 +242,31 @@ const sum = [1, 2, 3].reduce((carry, current) => current, 0);
210242

211243
This option takes a RegExp string or an array of RegExp strings.
212244
It allows for the ability to ignore violations based on a function's name.
245+
246+
### `overrides`
247+
248+
_Using this option requires type infomation._
249+
250+
Allows for applying overrides to the options based on the function's type.
251+
This can be used to override the settings for types coming from 3rd party libraries.
252+
253+
Note: Only the first matching override will be used.
254+
255+
#### `overrides[n].specifiers`
256+
257+
A specifier, or an array of specifiers to match the function type against.
258+
259+
In the case of reference types, both the type and its generics will be recursively checked.
260+
If any of them match, the specifier will be considered a match.
261+
262+
#### `overrides[n].options`
263+
264+
The options to use when a specifiers matches.
265+
266+
#### `overrides[n].inherit`
267+
268+
Inherit the root options? Default is `true`.
269+
270+
#### `overrides[n].disable`
271+
272+
If true, when a specifier matches, this rule will not be applied to the matching node.

src/rules/functional-parameters.ts

Lines changed: 116 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import { deepmerge } from "deepmerge-ts";
99
import {
1010
type IgnoreIdentifierPatternOption,
1111
type IgnorePrefixSelectorOption,
12+
type OverridableOptions,
13+
type RawOverridableOptions,
14+
getCoreOptions,
1215
ignoreIdentifierPatternOptionSchema,
1316
ignorePrefixSelectorOptionSchema,
1417
shouldIgnorePattern,
18+
upgradeRawOverridableOptions,
1519
} from "#/options";
1620
import { ruleNameScope } from "#/utils/misc";
1721
import { type ESFunction } from "#/utils/node-types";
@@ -20,7 +24,9 @@ import {
2024
type RuleResult,
2125
createRuleUsingFunction,
2226
} from "#/utils/rule";
27+
import { overridableOptionsSchema } from "#/utils/schemas";
2328
import {
29+
getEnclosingFunction,
2430
isArgument,
2531
isGetter,
2632
isIIFE,
@@ -45,83 +51,82 @@ export const fullName = `${ruleNameScope}/${name}`;
4551
*/
4652
type ParameterCountOptions = "atLeastOne" | "exactlyOne";
4753

54+
type CoreOptions = IgnoreIdentifierPatternOption &
55+
IgnorePrefixSelectorOption & {
56+
allowRestParameter: boolean;
57+
allowArgumentsKeyword: boolean;
58+
enforceParameterCount:
59+
| ParameterCountOptions
60+
| false
61+
| {
62+
count: ParameterCountOptions;
63+
ignoreLambdaExpression: boolean;
64+
ignoreIIFE: boolean;
65+
ignoreGettersAndSetters: boolean;
66+
};
67+
};
68+
4869
/**
4970
* The options this rule can take.
5071
*/
51-
type Options = [
52-
IgnoreIdentifierPatternOption &
53-
IgnorePrefixSelectorOption & {
54-
allowRestParameter: boolean;
55-
allowArgumentsKeyword: boolean;
56-
enforceParameterCount:
57-
| ParameterCountOptions
58-
| false
59-
| {
60-
count: ParameterCountOptions;
61-
ignoreLambdaExpression: boolean;
62-
ignoreIIFE: boolean;
63-
ignoreGettersAndSetters: boolean;
64-
};
65-
},
66-
];
72+
type RawOptions = [RawOverridableOptions<CoreOptions>];
73+
type Options = OverridableOptions<CoreOptions>;
6774

68-
/**
69-
* The schema for the rule options.
70-
*/
71-
const schema: JSONSchema4[] = [
75+
const coreOptionsPropertiesSchema = deepmerge(
76+
ignoreIdentifierPatternOptionSchema,
77+
ignorePrefixSelectorOptionSchema,
7278
{
73-
type: "object",
74-
properties: deepmerge(
75-
ignoreIdentifierPatternOptionSchema,
76-
ignorePrefixSelectorOptionSchema,
77-
{
78-
allowRestParameter: {
79+
allowRestParameter: {
80+
type: "boolean",
81+
},
82+
allowArgumentsKeyword: {
83+
type: "boolean",
84+
},
85+
enforceParameterCount: {
86+
oneOf: [
87+
{
7988
type: "boolean",
89+
enum: [false],
8090
},
81-
allowArgumentsKeyword: {
82-
type: "boolean",
91+
{
92+
type: "string",
93+
enum: ["atLeastOne", "exactlyOne"],
8394
},
84-
enforceParameterCount: {
85-
oneOf: [
86-
{
87-
type: "boolean",
88-
enum: [false],
89-
},
90-
{
95+
{
96+
type: "object",
97+
properties: {
98+
count: {
9199
type: "string",
92100
enum: ["atLeastOne", "exactlyOne"],
93101
},
94-
{
95-
type: "object",
96-
properties: {
97-
count: {
98-
type: "string",
99-
enum: ["atLeastOne", "exactlyOne"],
100-
},
101-
ignoreGettersAndSetters: {
102-
type: "boolean",
103-
},
104-
ignoreLambdaExpression: {
105-
type: "boolean",
106-
},
107-
ignoreIIFE: {
108-
type: "boolean",
109-
},
110-
},
111-
additionalProperties: false,
102+
ignoreGettersAndSetters: {
103+
type: "boolean",
104+
},
105+
ignoreLambdaExpression: {
106+
type: "boolean",
112107
},
113-
],
108+
ignoreIIFE: {
109+
type: "boolean",
110+
},
111+
},
112+
additionalProperties: false,
114113
},
115-
} satisfies JSONSchema4ObjectSchema["properties"],
116-
),
117-
additionalProperties: false,
114+
],
115+
},
118116
},
117+
) as NonNullable<JSONSchema4ObjectSchema["properties"]>;
118+
119+
/**
120+
* The schema for the rule options.
121+
*/
122+
const schema: JSONSchema4[] = [
123+
overridableOptionsSchema(coreOptionsPropertiesSchema),
119124
];
120125

121126
/**
122127
* The default options for the rule.
123128
*/
124-
const defaultOptions: Options = [
129+
const defaultOptions: RawOptions = [
125130
{
126131
allowRestParameter: false,
127132
allowArgumentsKeyword: false,
@@ -156,7 +161,7 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages> = {
156161
description: "Enforce functional parameters.",
157162
recommended: "recommended",
158163
recommendedSeverity: "error",
159-
requiresTypeChecking: false,
164+
requiresTypeChecking: true,
160165
},
161166
messages: errorMessages,
162167
schema,
@@ -166,9 +171,9 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages> = {
166171
* Get the rest parameter violations.
167172
*/
168173
function getRestParamViolations(
169-
[{ allowRestParameter }]: Readonly<Options>,
174+
{ allowRestParameter }: Readonly<CoreOptions>,
170175
node: ESFunction,
171-
): RuleResult<keyof typeof errorMessages, Options>["descriptors"] {
176+
): RuleResult<keyof typeof errorMessages, RawOptions>["descriptors"] {
172177
return !allowRestParameter &&
173178
node.params.length > 0 &&
174179
isRestElement(node.params.at(-1))
@@ -185,9 +190,9 @@ function getRestParamViolations(
185190
* Get the parameter count violations.
186191
*/
187192
function getParamCountViolations(
188-
[{ enforceParameterCount }]: Readonly<Options>,
193+
{ enforceParameterCount }: Readonly<CoreOptions>,
189194
node: ESFunction,
190-
): RuleResult<keyof typeof errorMessages, Options>["descriptors"] {
195+
): RuleResult<keyof typeof errorMessages, RawOptions>["descriptors"] {
191196
if (
192197
enforceParameterCount === false ||
193198
(node.params.length === 0 &&
@@ -233,11 +238,24 @@ function getParamCountViolations(
233238
*/
234239
function checkFunction(
235240
node: ESFunction,
236-
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
237-
options: Readonly<Options>,
238-
): RuleResult<keyof typeof errorMessages, Options> {
239-
const [optionsObject] = options;
240-
const { ignoreIdentifierPattern } = optionsObject;
241+
context: Readonly<RuleContext<keyof typeof errorMessages, RawOptions>>,
242+
rawOptions: Readonly<RawOptions>,
243+
): RuleResult<keyof typeof errorMessages, RawOptions> {
244+
const options = upgradeRawOverridableOptions(rawOptions[0]);
245+
const optionsToUse = getCoreOptions<CoreOptions, Options>(
246+
node,
247+
context,
248+
options,
249+
);
250+
251+
if (optionsToUse === null) {
252+
return {
253+
context,
254+
descriptors: [],
255+
};
256+
}
257+
258+
const { ignoreIdentifierPattern } = optionsToUse;
241259

242260
if (shouldIgnorePattern(node, context, ignoreIdentifierPattern)) {
243261
return {
@@ -249,8 +267,8 @@ function checkFunction(
249267
return {
250268
context,
251269
descriptors: [
252-
...getRestParamViolations(options, node),
253-
...getParamCountViolations(options, node),
270+
...getRestParamViolations(optionsToUse, node),
271+
...getParamCountViolations(optionsToUse, node),
254272
],
255273
};
256274
}
@@ -260,11 +278,31 @@ function checkFunction(
260278
*/
261279
function checkIdentifier(
262280
node: TSESTree.Identifier,
263-
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
264-
options: Readonly<Options>,
265-
): RuleResult<keyof typeof errorMessages, Options> {
266-
const [optionsObject] = options;
267-
const { ignoreIdentifierPattern } = optionsObject;
281+
context: Readonly<RuleContext<keyof typeof errorMessages, RawOptions>>,
282+
rawOptions: Readonly<RawOptions>,
283+
): RuleResult<keyof typeof errorMessages, RawOptions> {
284+
if (node.name !== "arguments") {
285+
return {
286+
context,
287+
descriptors: [],
288+
};
289+
}
290+
291+
const functionNode = getEnclosingFunction(node);
292+
const options = upgradeRawOverridableOptions(rawOptions[0]);
293+
const optionsToUse =
294+
functionNode === null
295+
? options
296+
: getCoreOptions<CoreOptions, Options>(functionNode, context, options);
297+
298+
if (optionsToUse === null) {
299+
return {
300+
context,
301+
descriptors: [],
302+
};
303+
}
304+
305+
const { ignoreIdentifierPattern } = optionsToUse;
268306

269307
if (shouldIgnorePattern(node, context, ignoreIdentifierPattern)) {
270308
return {
@@ -273,15 +311,12 @@ function checkIdentifier(
273311
};
274312
}
275313

276-
const { allowArgumentsKeyword } = optionsObject;
314+
const { allowArgumentsKeyword } = optionsToUse;
277315

278316
return {
279317
context,
280318
descriptors:
281-
!allowArgumentsKeyword &&
282-
node.name === "arguments" &&
283-
!isPropertyName(node) &&
284-
!isPropertyAccess(node)
319+
!allowArgumentsKeyword && !isPropertyName(node) && !isPropertyAccess(node)
285320
? [
286321
{
287322
node,
@@ -295,7 +330,7 @@ function checkIdentifier(
295330
// Create the rule.
296331
export const rule = createRuleUsingFunction<
297332
keyof typeof errorMessages,
298-
Options
333+
RawOptions
299334
>(name, meta, defaultOptions, (context, options) => {
300335
const [optionsObject] = options;
301336
const { ignorePrefixSelector } = optionsObject;

0 commit comments

Comments
 (0)