Skip to content

Commit

Permalink
feat(prefer-immutable-types): allow overriding options based on where…
Browse files Browse the repository at this point in the history
… the type is declared

fix #800
  • Loading branch information
RebeccaStevens committed Apr 22, 2024
1 parent 4a927d8 commit 389d79d
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 89 deletions.
54 changes: 54 additions & 0 deletions docs/rules/prefer-immutable-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,37 @@ type Options = {
ReadonlyDeep?: Array<Array<{ pattern: string; replace: string }>>;
Immutable?: Array<Array<{ pattern: string; replace: string }>>;
};

overrides?: Array<{
match: Array<
| {
from: "file";
path?: string;
name?: string | string[];
pattern?: RegExp | RegExp[];
ignoreName?: string | string[];
ignorePattern?: RegExp | RegExp[];
}
| {
from: "lib";
name?: string | string[];
pattern?: RegExp | RegExp[];
ignoreName?: string | string[];
ignorePattern?: RegExp | RegExp[];
}
| {
from: "package";
package?: string;
name?: string | string[];
pattern?: RegExp | RegExp[];
ignoreName?: string | string[];
ignorePattern?: RegExp | RegExp[];
}
>;
options: Omit<Options, "overrides">;
inherit?: boolean;
disable: boolean;
}>;
};
```

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

This option takes a `RegExp` string or an array of `RegExp` strings.
It allows for the ability to ignore violations based on the type (as written, with whitespace removed) of the node in question.

### `overrides`

Allows for applying overrides to the options based on where the type is defined.
This can be used to override the settings for types coming from 3rd party libraries.

Note: Only the first matching override will be used.

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

A specifier, or an array of specifiers to match the function type against.

#### `overrides[n].options`

The options to use when a specifiers matches.

#### `overrides[n].inherit`

Inherit the root options? Default is `true`.

#### `overrides[n].disable`

If true, when a specifier matches, this rule will not be applied to the matching node.
197 changes: 108 additions & 89 deletions src/rules/prefer-immutable-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { Immutability } from "is-immutable-type";

import {
type IgnoreClassesOption,
type OverridableOptions,
getCoreOptions,
ignoreClassesOptionSchema,
shouldIgnoreClasses,
shouldIgnoreInFunction,
Expand Down Expand Up @@ -42,6 +44,8 @@ import {
isTSTypePredicate,
} from "#eslint-plugin-functional/utils/type-guards";

import { overridableOptionsSchema } from "../utils/schemas";

/**
* The name of this rule.
*/
Expand All @@ -55,7 +59,8 @@ export const fullName = `${ruleNameScope}/${name}`;
type RawEnforcement =
| Exclude<Immutability | keyof typeof Immutability, "Unknown" | "Mutable">
| "None"
| false;
| false
| undefined;

type Option = IgnoreClassesOption & {
enforcement: RawEnforcement;
Expand All @@ -64,6 +69,20 @@ type Option = IgnoreClassesOption & {
ignoreTypePattern?: string[] | string;
};

type CoreOptions = Option & {
parameters?: Partial<Option> | RawEnforcement;
returnTypes?: Partial<Option> | RawEnforcement;
variables?:
| Partial<
Option & {
ignoreInFunctions?: boolean;
}
>
| RawEnforcement;
fixer?: FixerConfigRawMap;
suggestions?: SuggestionConfigRawMap;
};

type FixerConfigRaw = {
pattern: string;
replace: string;
Expand Down Expand Up @@ -93,21 +112,7 @@ type SuggestionsConfig = FixerConfig[];
/**
* The options this rule can take.
*/
type Options = [
Option & {
parameters?: Partial<Option> | RawEnforcement;
returnTypes?: Partial<Option> | RawEnforcement;
variables?:
| Partial<
Option & {
ignoreInFunctions?: boolean;
}
>
| RawEnforcement;
fixer?: FixerConfigRawMap;
suggestions?: SuggestionConfigRawMap;
},
];
type Options = [OverridableOptions<CoreOptions>];

/**
* The enum options for the level of enforcement.
Expand Down Expand Up @@ -211,53 +216,53 @@ const suggestionsSchema: JSONSchema4 = {
},
};

/**
* The schema for the rule options.
*/
const schema: JSONSchema4[] = [
{
type: "object",
properties: deepmerge(optionExpandedSchema, {
parameters: optionSchema,
returnTypes: optionSchema,
variables: {
oneOf: [
{
type: "object",
properties: deepmerge(optionExpandedSchema, {
ignoreInFunctions: {
type: "boolean",
},
}),
additionalProperties: false,
},
{
type: ["string", "number", "boolean"],
enum: enforcementEnumOptions,
},
],
},
fixer: {
const coreOptionsPropertiesSchema: NonNullable<
JSONSchema4ObjectSchema["properties"]
> = deepmerge(optionExpandedSchema, {
parameters: optionSchema,
returnTypes: optionSchema,
variables: {
oneOf: [
{
type: "object",
properties: {
ReadonlyShallow: fixerSchema,
ReadonlyDeep: fixerSchema,
Immutable: fixerSchema,
},
properties: deepmerge(optionExpandedSchema, {
ignoreInFunctions: {
type: "boolean",
},
}),
additionalProperties: false,
},
suggestions: {
type: "object",
properties: {
ReadonlyShallow: suggestionsSchema,
ReadonlyDeep: suggestionsSchema,
Immutable: suggestionsSchema,
},
additionalProperties: false,
{
type: ["string", "number", "boolean"],
enum: enforcementEnumOptions,
},
}),
],
},
fixer: {
type: "object",
properties: {
ReadonlyShallow: fixerSchema,
ReadonlyDeep: fixerSchema,
Immutable: fixerSchema,
},
additionalProperties: false,
},
suggestions: {
type: "object",
properties: {
ReadonlyShallow: suggestionsSchema,
ReadonlyDeep: suggestionsSchema,
Immutable: suggestionsSchema,
},
additionalProperties: false,
},
});

/**
* The schema for the rule options.
*/
const schema: JSONSchema4[] = [
overridableOptionsSchema(coreOptionsPropertiesSchema),
];

/**
Expand Down Expand Up @@ -398,7 +403,7 @@ function getConfiguredSuggestionFixers(
* Get the level of enforcement from the raw value given.
*/
function parseEnforcement(rawEnforcement: RawEnforcement) {
return rawEnforcement === "None"
return rawEnforcement === "None" || rawEnforcement === undefined
? false
: typeof rawEnforcement === "string"
? Immutability[rawEnforcement]
Expand Down Expand Up @@ -454,35 +459,32 @@ function parseSuggestionsConfigs(
function getParameterTypeViolations(
node: ESFunctionType,
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
options: Readonly<Options>,
options: Readonly<CoreOptions>,
): Descriptor[] {
const [optionsObject] = options;
const {
parameters: rawOption,
fixer: rawFixerConfig,
suggestions: rawSuggestionsConfigs,
} = optionsObject;
} = options;
const {
enforcement: rawEnforcement,
ignoreInferredTypes,
ignoreClasses,
ignoreNamePattern,
ignoreTypePattern,
} = {
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
ignoreClasses: optionsObject.ignoreClasses,
ignoreNamePattern: optionsObject.ignoreNamePattern,
ignoreTypePattern: optionsObject.ignoreTypePattern,
ignoreInferredTypes: options.ignoreInferredTypes,
ignoreClasses: options.ignoreClasses,
ignoreNamePattern: options.ignoreNamePattern,
ignoreTypePattern: options.ignoreTypePattern,
...(typeof rawOption === "object"
? rawOption
: {
enforcement: rawOption,
}),
};

const enforcement = parseEnforcement(
rawEnforcement ?? optionsObject.enforcement,
);
const enforcement = parseEnforcement(rawEnforcement ?? options.enforcement);
if (
enforcement === false ||
shouldIgnoreClasses(node, context, ignoreClasses)
Expand Down Expand Up @@ -592,31 +594,28 @@ function getParameterTypeViolations(
function getReturnTypeViolations(
node: ESFunctionType,
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
options: Readonly<Options>,
options: Readonly<CoreOptions>,
): Descriptor[] {
const [optionsObject] = options;
const {
returnTypes: rawOption,
fixer: rawFixerConfig,
suggestions: rawSuggestionsConfigs,
} = optionsObject;
} = options;
const {
enforcement: rawEnforcement,
ignoreInferredTypes,
ignoreClasses,
ignoreNamePattern,
ignoreTypePattern,
} = {
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
ignoreClasses: optionsObject.ignoreClasses,
ignoreNamePattern: optionsObject.ignoreNamePattern,
ignoreTypePattern: optionsObject.ignoreTypePattern,
ignoreInferredTypes: options.ignoreInferredTypes,
ignoreClasses: options.ignoreClasses,
ignoreNamePattern: options.ignoreNamePattern,
ignoreTypePattern: options.ignoreTypePattern,
...(typeof rawOption === "object" ? rawOption : { enforcement: rawOption }),
};

const enforcement = parseEnforcement(
rawEnforcement ?? optionsObject.enforcement,
);
const enforcement = parseEnforcement(rawEnforcement ?? options.enforcement);

if (
enforcement === false ||
Expand Down Expand Up @@ -743,10 +742,19 @@ function checkFunction(
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
options: Readonly<Options>,
): RuleResult<keyof typeof errorMessages, Options> {
const descriptors = [
...getParameterTypeViolations(node, context, options),
...getReturnTypeViolations(node, context, options),
];
const optionsToUse = getCoreOptions<CoreOptions, Options>(
node,
context,
options,
);

const descriptors =
optionsToUse === null
? []
: [
...getParameterTypeViolations(node, context, optionsToUse),
...getReturnTypeViolations(node, context, optionsToUse),
];

return {
context,
Expand All @@ -762,13 +770,24 @@ function checkVariable(
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
options: Readonly<Options>,
): RuleResult<keyof typeof errorMessages, Options> {
const [optionsObject] = options;
const optionsToUse = getCoreOptions<CoreOptions, Options>(
node,
context,
options,
);

if (optionsToUse === null) {
return {
context,
descriptors: [],
};
}

const {
variables: rawOption,
fixer: rawFixerConfig,
suggestions: rawSuggestionsConfigs,
} = optionsObject;
} = optionsToUse;
const {
enforcement: rawEnforcement,
ignoreInferredTypes,
Expand All @@ -777,16 +796,16 @@ function checkVariable(
ignoreTypePattern,
ignoreInFunctions,
} = {
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
ignoreClasses: optionsObject.ignoreClasses,
ignoreNamePattern: optionsObject.ignoreNamePattern,
ignoreTypePattern: optionsObject.ignoreTypePattern,
ignoreInferredTypes: optionsToUse.ignoreInferredTypes,
ignoreClasses: optionsToUse.ignoreClasses,
ignoreNamePattern: optionsToUse.ignoreNamePattern,
ignoreTypePattern: optionsToUse.ignoreTypePattern,
ignoreInFunctions: false,
...(typeof rawOption === "object" ? rawOption : { enforcement: rawOption }),
};

const enforcement = parseEnforcement(
rawEnforcement ?? optionsObject.enforcement,
rawEnforcement ?? optionsToUse.enforcement,
);

if (
Expand Down
Loading

0 comments on commit 389d79d

Please sign in to comment.