Skip to content

Commit 4a927d8

Browse files
feat(functional-parameters): allow overriding options based on where the function type is declared
fix #575
1 parent ffaaa6b commit 4a927d8

File tree

8 files changed

+423
-83
lines changed

8 files changed

+423
-83
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ The [below section](#rules) gives details on which rules are enabled by each rul
105105

106106
### Currying
107107

108-
| Name | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | 💭 ||
109-
| :----------------------------------------------------------- | :----------------------------- | :--------------------------- | :-- | :-- | :-- | :-- | :-- | :-- |
110-
| [functional-parameters](docs/rules/functional-parameters.md) | Enforce functional parameters. | ☑️ ✅ 🔒 ![badge-currying][] | | | | | | |
108+
| Name | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | 💭 ||
109+
| :----------------------------------------------------------- | :----------------------------- | :--------------------------- | :-- | :---------------------------- | :-- | :-- | :-- | :-- |
110+
| [functional-parameters](docs/rules/functional-parameters.md) | Enforce functional parameters. | ☑️ ✅ 🔒 ![badge-currying][] | | ![badge-disableTypeChecked][] | | | 💭 | |
111111

112112
### No Exceptions
113113

docs/rules/functional-parameters.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
# Enforce functional parameters (`functional/functional-parameters`)
22

3-
💼 This rule is enabled in the following configs: `currying`, ☑️ `lite`, ✅ `recommended`, 🔒 `strict`.
3+
💼🚫 This rule is enabled in the following configs: `currying`, ☑️ `lite`, ✅ `recommended`, 🔒 `strict`. This rule is _disabled_ in the `disableTypeChecked` config.
4+
5+
💭 This rule requires [type information](https://typescript-eslint.io/linting/typed-linting).
46

57
<!-- end auto-generated rule header -->
68

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

11+
Note: type information is only required when using the [overrides](#overrides) option.
12+
913
## Rule Details
1014

1115
In functions, `arguments` is a special variable that is implicitly available.
@@ -67,6 +71,36 @@ type Options = {
6771
};
6872
ignoreIdentifierPattern?: string[] | string;
6973
ignorePrefixSelector?: string[] | string;
74+
overrides?: Array<{
75+
match: Array<
76+
| {
77+
from: "file";
78+
path?: string;
79+
name?: string | string[];
80+
pattern?: RegExp | RegExp[];
81+
ignoreName?: string | string[];
82+
ignorePattern?: RegExp | RegExp[];
83+
}
84+
| {
85+
from: "lib";
86+
name?: string | string[];
87+
pattern?: RegExp | RegExp[];
88+
ignoreName?: string | string[];
89+
ignorePattern?: RegExp | RegExp[];
90+
}
91+
| {
92+
from: "package";
93+
package?: string;
94+
name?: string | string[];
95+
pattern?: RegExp | RegExp[];
96+
ignoreName?: string | string[];
97+
ignorePattern?: RegExp | RegExp[];
98+
}
99+
>;
100+
options: Omit<Options, "overrides">;
101+
inherit?: boolean;
102+
disable: boolean;
103+
}>;
70104
};
71105
```
72106

@@ -196,3 +230,28 @@ const sum = [1, 2, 3].reduce((carry, current) => current, 0);
196230

197231
This option takes a RegExp string or an array of RegExp strings.
198232
It allows for the ability to ignore violations based on a function's name.
233+
234+
### `overrides`
235+
236+
_Using this option requires type infomation._
237+
238+
Allows for applying overrides to the options based on where the function's type is defined.
239+
This can be used to override the settings for types coming from 3rd party libraries.
240+
241+
Note: Only the first matching override will be used.
242+
243+
#### `overrides[n].specifiers`
244+
245+
A specifier, or an array of specifiers to match the function type against.
246+
247+
#### `overrides[n].options`
248+
249+
The options to use when a specifiers matches.
250+
251+
#### `overrides[n].inherit`
252+
253+
Inherit the root options? Default is `true`.
254+
255+
#### `overrides[n].disable`
256+
257+
If true, when a specifier matches, this rule will not be applied to the matching node.

src/rules/functional-parameters.ts

Lines changed: 101 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { deepmerge } from "deepmerge-ts";
99
import {
1010
type IgnoreIdentifierPatternOption,
1111
type IgnorePrefixSelectorOption,
12+
type OverridableOptions,
13+
getCoreOptions,
1214
ignoreIdentifierPatternOptionSchema,
1315
ignorePrefixSelectorOptionSchema,
1416
shouldIgnorePattern,
@@ -20,7 +22,9 @@ import {
2022
type RuleResult,
2123
createRuleUsingFunction,
2224
} from "#eslint-plugin-functional/utils/rule";
25+
import { overridableOptionsSchema } from "#eslint-plugin-functional/utils/schemas";
2326
import {
27+
getEnclosingFunction,
2428
isArgument,
2529
isGetter,
2630
isIIFE,
@@ -45,77 +49,75 @@ export const fullName = `${ruleNameScope}/${name}`;
4549
*/
4650
type ParameterCountOptions = "atLeastOne" | "exactlyOne";
4751

52+
type CoreOptions = 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+
4867
/**
4968
* The options this rule can take.
5069
*/
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-
];
70+
type Options = [OverridableOptions<CoreOptions>];
6771

68-
/**
69-
* The schema for the rule options.
70-
*/
71-
const schema: JSONSchema4[] = [
72+
const coreOptionsPropertiesSchema = deepmerge(
73+
ignoreIdentifierPatternOptionSchema,
74+
ignorePrefixSelectorOptionSchema,
7275
{
73-
type: "object",
74-
properties: deepmerge(
75-
ignoreIdentifierPatternOptionSchema,
76-
ignorePrefixSelectorOptionSchema,
77-
{
78-
allowRestParameter: {
76+
allowRestParameter: {
77+
type: "boolean",
78+
},
79+
allowArgumentsKeyword: {
80+
type: "boolean",
81+
},
82+
enforceParameterCount: {
83+
oneOf: [
84+
{
7985
type: "boolean",
86+
enum: [false],
8087
},
81-
allowArgumentsKeyword: {
82-
type: "boolean",
88+
{
89+
type: "string",
90+
enum: ["atLeastOne", "exactlyOne"],
8391
},
84-
enforceParameterCount: {
85-
oneOf: [
86-
{
87-
type: "boolean",
88-
enum: [false],
89-
},
90-
{
92+
{
93+
type: "object",
94+
properties: {
95+
count: {
9196
type: "string",
9297
enum: ["atLeastOne", "exactlyOne"],
9398
},
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,
99+
ignoreGettersAndSetters: {
100+
type: "boolean",
101+
},
102+
ignoreLambdaExpression: {
103+
type: "boolean",
112104
},
113-
],
105+
ignoreIIFE: {
106+
type: "boolean",
107+
},
108+
},
109+
additionalProperties: false,
114110
},
115-
} satisfies JSONSchema4ObjectSchema["properties"],
116-
),
117-
additionalProperties: false,
111+
],
112+
},
118113
},
114+
) as NonNullable<JSONSchema4ObjectSchema["properties"]>;
115+
116+
/**
117+
* The schema for the rule options.
118+
*/
119+
const schema: JSONSchema4[] = [
120+
overridableOptionsSchema(coreOptionsPropertiesSchema),
119121
];
120122

121123
/**
@@ -156,6 +158,7 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages, Options> = {
156158
description: "Enforce functional parameters.",
157159
recommended: "recommended",
158160
recommendedSeverity: "error",
161+
requiresTypeChecking: true,
159162
},
160163
messages: errorMessages,
161164
schema,
@@ -165,7 +168,7 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages, Options> = {
165168
* Get the rest parameter violations.
166169
*/
167170
function getRestParamViolations(
168-
[{ allowRestParameter }]: Readonly<Options>,
171+
{ allowRestParameter }: Readonly<CoreOptions>,
169172
node: ESFunction,
170173
): RuleResult<keyof typeof errorMessages, Options>["descriptors"] {
171174
return !allowRestParameter &&
@@ -184,7 +187,7 @@ function getRestParamViolations(
184187
* Get the parameter count violations.
185188
*/
186189
function getParamCountViolations(
187-
[{ enforceParameterCount }]: Readonly<Options>,
190+
{ enforceParameterCount }: Readonly<CoreOptions>,
188191
node: ESFunction,
189192
): RuleResult<keyof typeof errorMessages, Options>["descriptors"] {
190193
if (
@@ -235,8 +238,20 @@ function checkFunction(
235238
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
236239
options: Readonly<Options>,
237240
): RuleResult<keyof typeof errorMessages, Options> {
238-
const [optionsObject] = options;
239-
const { ignoreIdentifierPattern } = optionsObject;
241+
const optionsToUse = getCoreOptions<CoreOptions, Options>(
242+
node,
243+
context,
244+
options,
245+
);
246+
247+
if (optionsToUse === null) {
248+
return {
249+
context,
250+
descriptors: [],
251+
};
252+
}
253+
254+
const { ignoreIdentifierPattern } = optionsToUse;
240255

241256
if (shouldIgnorePattern(node, context, ignoreIdentifierPattern)) {
242257
return {
@@ -248,8 +263,8 @@ function checkFunction(
248263
return {
249264
context,
250265
descriptors: [
251-
...getRestParamViolations(options, node),
252-
...getParamCountViolations(options, node),
266+
...getRestParamViolations(optionsToUse, node),
267+
...getParamCountViolations(optionsToUse, node),
253268
],
254269
};
255270
}
@@ -262,8 +277,27 @@ function checkIdentifier(
262277
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
263278
options: Readonly<Options>,
264279
): RuleResult<keyof typeof errorMessages, Options> {
265-
const [optionsObject] = options;
266-
const { ignoreIdentifierPattern } = optionsObject;
280+
if (node.name !== "arguments") {
281+
return {
282+
context,
283+
descriptors: [],
284+
};
285+
}
286+
287+
const functionNode = getEnclosingFunction(node);
288+
const optionsToUse =
289+
functionNode === null
290+
? options[0]
291+
: getCoreOptions<CoreOptions, Options>(functionNode, context, options);
292+
293+
if (optionsToUse === null) {
294+
return {
295+
context,
296+
descriptors: [],
297+
};
298+
}
299+
300+
const { ignoreIdentifierPattern } = optionsToUse;
267301

268302
if (shouldIgnorePattern(node, context, ignoreIdentifierPattern)) {
269303
return {
@@ -272,15 +306,12 @@ function checkIdentifier(
272306
};
273307
}
274308

275-
const { allowArgumentsKeyword } = optionsObject;
309+
const { allowArgumentsKeyword } = optionsToUse;
276310

277311
return {
278312
context,
279313
descriptors:
280-
!allowArgumentsKeyword &&
281-
node.name === "arguments" &&
282-
!isPropertyName(node) &&
283-
!isPropertyAccess(node)
314+
!allowArgumentsKeyword && !isPropertyName(node) && !isPropertyAccess(node)
284315
? [
285316
{
286317
node,

0 commit comments

Comments
 (0)