Skip to content

Commit

Permalink
feat(functional-parameters): add support for ignoring selector prefixes
Browse files Browse the repository at this point in the history
re #207 re #244
  • Loading branch information
RebeccaStevens committed Oct 24, 2021
1 parent f1538f1 commit da92221
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 29 deletions.
32 changes: 30 additions & 2 deletions docs/rules/functional-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type Options = {
ignoreIIFE: boolean;
};
ignorePattern?: string[] | string;
ignorePrefixSelector?: string[] | string;
}
```
Expand All @@ -68,7 +69,8 @@ const defaults = {
enforceParameterCount: {
count: "atLeastOne",
ignoreIIFE: true
}
},
ignorePrefixSelector: undefined,
}
```

Expand All @@ -78,7 +80,8 @@ Note: the `lite` ruleset overrides the default options to:
const liteDefaults = {
allowRestParameter: false,
allowArgumentsKeyword: false,
enforceParameterCount: false
enforceParameterCount: false,
ignorePrefixSelector: undefined,
}
```

Expand Down Expand Up @@ -136,6 +139,31 @@ See [enforceParameterCount](#enforceparametercount).

If true, this option allows for the use of [IIFEs](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) that do not have any parameters.

### `ignorePrefixSelector`

This allows for ignore functions where one of the given selectors matches the parent node in the AST of the function node.\
For more information see [ESLint Selectors](https://eslint.org/docs/developer-guide/selectors).

Example:

With the following config:

```json
{
"enforceParameterCount": "exactlyOne",
"ignorePrefixSelector": "CallExpression[callee.property.name='reduce']"
},
```

The following inline callback won't be flaged:

```js
const sum = [1, 2, 3].reduce(
(carry, current) => current,
0
);
```

### `ignorePattern`

Patterns will be matched against function names.
Expand Down
17 changes: 17 additions & 0 deletions src/common/ignore-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,23 @@ export const ignoreInterfaceOptionSchema: JSONSchema4 = {
additionalProperties: false,
};

export type IgnorePrefixSelectorOption = {
readonly ignorePrefixSelector?: ReadonlyArray<string> | string;
};

export const ignorePrefixSelectorOptionSchema: JSONSchema4 = {
type: "object",
properties: {
ignorePrefixSelector: {
type: ["string", "array"],
items: {
type: "string",
},
},
},
additionalProperties: false,
};

/**
* Get the identifier text of the given node.
*/
Expand Down
72 changes: 48 additions & 24 deletions src/rules/functional-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import type { TSESTree } from "@typescript-eslint/experimental-utils";
import { deepmerge } from "deepmerge-ts";
import type { JSONSchema4 } from "json-schema";

import type { IgnorePatternOption } from "~/common/ignore-options";
import type {
IgnorePatternOption,
IgnorePrefixSelectorOption,
} from "~/common/ignore-options";
import {
shouldIgnorePattern,
ignorePatternOptionSchema,
ignorePrefixSelectorOptionSchema,
} from "~/common/ignore-options";
import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
import { createRuleUsingFunction } from "~/util/rule";
import { isIIFE, isPropertyAccess, isPropertyName } from "~/util/tree";
import { isRestElement } from "~/util/typeguard";

Expand All @@ -18,21 +22,22 @@ export const name = "functional-parameters" as const;
type ParameterCountOptions = "atLeastOne" | "exactlyOne";

// The options this rule can take.
type Options = IgnorePatternOption & {
readonly allowRestParameter: boolean;
readonly allowArgumentsKeyword: boolean;
readonly enforceParameterCount:
| ParameterCountOptions
| false
| {
readonly count: ParameterCountOptions;
readonly ignoreIIFE: boolean;
};
};
type Options = IgnorePatternOption &
IgnorePrefixSelectorOption & {
readonly allowRestParameter: boolean;
readonly allowArgumentsKeyword: boolean;
readonly enforceParameterCount:
| ParameterCountOptions
| false
| {
readonly count: ParameterCountOptions;
readonly ignoreIIFE: boolean;
};
};

// The schema for the rule options.
const schema: JSONSchema4 = [
deepmerge(ignorePatternOptionSchema, {
deepmerge(ignorePatternOptionSchema, ignorePrefixSelectorOptionSchema, {
type: "object",
properties: {
allowRestParameter: {
Expand Down Expand Up @@ -232,14 +237,33 @@ function checkIdentifier(
}

// Create the rule.
export const rule = createRule<keyof typeof errorMessages, Options>(
name,
meta,
defaultOptions,
{
FunctionDeclaration: checkFunction,
FunctionExpression: checkFunction,
ArrowFunctionExpression: checkFunction,
export const rule = createRuleUsingFunction<
keyof typeof errorMessages,
Options
>(name, meta, defaultOptions, (context, options) => {
const baseFunctionSelectors = [
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
];

const ignoreSelectors: ReadonlyArray<string> | undefined =
options.ignorePrefixSelector === undefined
? undefined
: Array.isArray(options.ignorePrefixSelector)
? options.ignorePrefixSelector
: [options.ignorePrefixSelector];

const fullFunctionSelectors = baseFunctionSelectors.flatMap((baseSelector) =>
ignoreSelectors === undefined
? [baseSelector]
: `:not(:matches(${ignoreSelectors.join(",")})) > ${baseSelector}`
);

return {
...Object.fromEntries(
fullFunctionSelectors.map((selector) => [selector, checkFunction])
),
Identifier: checkIdentifier,
}
);
};
});
31 changes: 28 additions & 3 deletions src/util/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ export function createRule<
meta: RuleMetaData<MessageIds>,
defaultOptions: Options,
ruleFunctionsMap: RuleFunctionsMap<MessageIds, Options>
): Rule.RuleModule {
return createRuleUsingFunction(
name,
meta,
defaultOptions,
() => ruleFunctionsMap
);
}

/**
* Create a rule.
*/
export function createRuleUsingFunction<
MessageIds extends string,
Options extends BaseOptions
>(
name: string,
meta: RuleMetaData<MessageIds>,
defaultOptions: Options,
createFunction: (
context: TSESLint.RuleContext<MessageIds, readonly [Options]>,
options: Options
) => RuleFunctionsMap<MessageIds, Options>
): Rule.RuleModule {
return ESLintUtils.RuleCreator(
(name) =>
Expand All @@ -100,13 +123,15 @@ export function createRule<
create: (
context: TSESLint.RuleContext<MessageIds, readonly [Options]>,
[options]: readonly [Options]
) =>
Object.fromEntries(
) => {
const ruleFunctionsMap = createFunction(context, options);
return Object.fromEntries(
Object.entries(ruleFunctionsMap).map(([nodeSelector, ruleFunction]) => [
nodeSelector,
checkNode(ruleFunction, context, options),
])
),
);
},
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
} as any) as any;
}
Expand Down
31 changes: 31 additions & 0 deletions tests/rules/functional-parameters/es3/invalid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,37 @@ const tests: ReadonlyArray<InvalidTestCase> = [
},
],
},
{
code: dedent`
[1, 2, 3]
.map(
function(element, index) {
return element + index;
}
)
.reduce(
function(carry, current) {
return carry + current;
},
0
);
`,
optionsSet: [[{ enforceParameterCount: "exactlyOne" }]],
errors: [
{
messageId: "paramCountExactlyOne",
type: "FunctionExpression",
line: 3,
column: 5,
},
{
messageId: "paramCountExactlyOne",
type: "FunctionExpression",
line: 8,
column: 5,
},
],
},
];

export default tests;
63 changes: 63 additions & 0 deletions tests/rules/functional-parameters/es3/valid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,69 @@ const tests: ReadonlyArray<ValidTestCase> = [
[{ ignorePattern: "^foo", enforceParameterCount: "exactlyOne" }],
],
},
{
code: dedent`
[1, 2, 3].reduce(
function(carry, current) {
return carry + current;
},
0
);
`,
optionsSet: [
[
{
ignorePrefixSelector: "CallExpression[callee.property.name='reduce']",
enforceParameterCount: "exactlyOne",
},
],
],
},
{
code: dedent`
[1, 2, 3].map(
function(element, index) {
return element + index;
},
0
);
`,
optionsSet: [
[
{
enforceParameterCount: "exactlyOne",
ignorePrefixSelector: "CallExpression[callee.property.name='map']",
},
],
],
},
{
code: dedent`
[1, 2, 3]
.map(
function(element, index) {
return element + index;
}
)
.reduce(
function(carry, current) {
return carry + current;
},
0
);
`,
optionsSet: [
[
{
enforceParameterCount: "exactlyOne",
ignorePrefixSelector: [
"CallExpression[callee.property.name='reduce']",
"CallExpression[callee.property.name='map']",
],
},
],
],
},
];

export default tests;
Loading

0 comments on commit da92221

Please sign in to comment.