Skip to content

Commit

Permalink
feat(type-declaration-immutability): add support for in-editor sugges…
Browse files Browse the repository at this point in the history
…tions

fix #797
  • Loading branch information
RebeccaStevens committed Apr 1, 2024
1 parent 065a1c7 commit 7a0a790
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ The [below section](#rules) gives details on which rules are enabled by each rul
| [no-let](docs/rules/no-let.md) | Disallow mutable variables. | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | | | | |
| [prefer-immutable-types](docs/rules/prefer-immutable-types.md) | Require function parameters to be typed as certain immutability | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | 🔧 | 💡 | 💭 | |
| [prefer-readonly-type](docs/rules/prefer-readonly-type.md) | Prefer readonly types over mutable types. | | | | 🔧 | | 💭 ||
| [type-declaration-immutability](docs/rules/type-declaration-immutability.md) | Enforce the immutability of types based on patterns. | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | 🔧 | | 💭 | |
| [type-declaration-immutability](docs/rules/type-declaration-immutability.md) | Enforce the immutability of types based on patterns. | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | 🔧 | 💡 | 💭 | |

### No Other Paradigms

Expand Down
8 changes: 7 additions & 1 deletion docs/rules/type-declaration-immutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

💼 This rule is enabled in the following configs: ☑️ `lite`, `no-mutations`, ✅ `recommended`, 🔒 `strict`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).

💭 This rule requires [type information](https://typescript-eslint.io/linting/typed-linting).

Expand Down Expand Up @@ -86,6 +86,7 @@ type Options = {
| { pattern: string; replace: string }
| Array<{ pattern: string; replace: string }>
| false;
suggestions?: Array<{ pattern: string; replace: string }> | false;
}>;
ignoreInterfaces: boolean;
ignoreIdentifierPattern: string[] | string;
Expand All @@ -102,6 +103,7 @@ const defaults = {
immutability: "Immutable",
comparator: "AtLeast",
fixer: false,
suggestions: false,
},
],
ignoreInterfaces: false,
Expand Down Expand Up @@ -186,6 +188,10 @@ immutability. This can be thought of as `<`, `<=`, `==`, `>=` or `>`.
Configure the fixer for this rule to work with your setup.
If not set, or set to `false`, the fixer will be disabled.

#### `suggestions`

Configure any suggestions for this rule to work with your setup.

### `ignoreInterfaces`

A boolean to specify whether interfaces should be exempt from these rules.
Expand Down
75 changes: 72 additions & 3 deletions src/rules/type-declaration-immutability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ type FixerConfig = {
replace: string;
};

type SuggestionsConfig = FixerConfig[];

/**
* The options this rule can take.
*/
Expand All @@ -71,6 +73,7 @@ type Options = [
| RuleEnforcementComparator
| keyof typeof RuleEnforcementComparator;
fixer?: FixerConfigRaw | FixerConfigRaw[] | false;
suggestions?: FixerConfigRaw[] | false;
}>;
ignoreInterfaces: boolean;
},
Expand Down Expand Up @@ -107,6 +110,26 @@ const fixerSchema: JSONSchema4 = {
],
};

const suggestionsSchema: JSONSchema4 = {
oneOf: [
{
type: "boolean",
enum: [false],
},
{
type: "array",
items: {
type: "object",
properties: {
pattern: { type: "string" },
replace: { type: "string" },
},
additionalProperties: false,
},
},
],
};

/**
* The schema for the rule options.
*/
Expand Down Expand Up @@ -138,6 +161,7 @@ const schema: JSONSchema4[] = [
enum: Object.values(RuleEnforcementComparator),
},
fixer: fixerSchema,
suggestions: suggestionsSchema,
},
required: ["identifiers", "immutability"],
additionalProperties: false,
Expand Down Expand Up @@ -195,6 +219,7 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages, Options> = {
},
messages: errorMessages,
fixable: "code",
hasSuggestions: true,
schema,
};

Expand All @@ -206,6 +231,7 @@ export type ImmutabilityRule = {
immutability: Immutability;
comparator: RuleEnforcementComparator;
fixers: FixerConfig[] | false;
suggestions: SuggestionsConfig | false;
};

type Descriptor = RuleResult<
Expand Down Expand Up @@ -245,11 +271,20 @@ function getRules(options: Readonly<Options>): ImmutabilityRule[] {
pattern: new RegExp(r.pattern, "us"),
}));

const suggestions =
rule.suggestions === undefined || rule.suggestions === false
? false
: rule.suggestions.map((r) => ({
...r,
pattern: new RegExp(r.pattern, "us"),
}));

return {
identifiers,
immutability,
comparator,
fixers,
suggestions,
};
});
}
Expand Down Expand Up @@ -297,6 +332,27 @@ function getConfiguredFixer<T extends TSESTree.Node>(
fixer.replaceText(node, text.replace(config.pattern, config.replace));
}

/**
* Get the suggestions that uses the user config.
*/
function getConfiguredSuggestions<T extends TSESTree.Node>(
node: T,
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
configs: FixerConfig[],
messageId: keyof typeof errorMessages,
): NonNullable<Descriptor["suggest"]> | null {
const text = context.sourceCode.getText(node);
const matchingConfig = configs.filter((c) => c.pattern.test(text));
if (matchingConfig.length === 0) {
return null;
}
return matchingConfig.map((config) => ({
fix: (fixer) =>
fixer.replaceText(node, text.replace(config.pattern, config.replace)),
messageId,
}));
}

/**
* Compare the actual immutability to the expected immutability.
*/
Expand Down Expand Up @@ -337,24 +393,37 @@ function getResults(
};
}

const messageId = RuleEnforcementComparator[
rule.comparator
] as keyof typeof RuleEnforcementComparator;

const fix =
rule.fixers === false || isTSInterfaceDeclaration(node)
? null
: getConfiguredFixer(node.typeAnnotation, context, rule.fixers);

const suggest =
rule.suggestions === false || isTSInterfaceDeclaration(node)
? null
: getConfiguredSuggestions(
node.typeAnnotation,
context,
rule.suggestions,
messageId,
);

return {
context,
descriptors: [
{
node: node.id,
messageId: RuleEnforcementComparator[
rule.comparator
] as keyof typeof RuleEnforcementComparator,
messageId,
data: {
actual: Immutability[immutability],
expected: Immutability[rule.immutability],
},
fix,
suggest,
},
],
};
Expand Down

0 comments on commit 7a0a790

Please sign in to comment.