Skip to content

Commit 7a0a790

Browse files
feat(type-declaration-immutability): add support for in-editor suggestions
fix #797
1 parent 065a1c7 commit 7a0a790

File tree

3 files changed

+80
-5
lines changed

3 files changed

+80
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ The [below section](#rules) gives details on which rules are enabled by each rul
126126
| [no-let](docs/rules/no-let.md) | Disallow mutable variables. | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | | | | |
127127
| [prefer-immutable-types](docs/rules/prefer-immutable-types.md) | Require function parameters to be typed as certain immutability | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | 🔧 | 💡 | 💭 | |
128128
| [prefer-readonly-type](docs/rules/prefer-readonly-type.md) | Prefer readonly types over mutable types. | | | | 🔧 | | 💭 ||
129-
| [type-declaration-immutability](docs/rules/type-declaration-immutability.md) | Enforce the immutability of types based on patterns. | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | 🔧 | | 💭 | |
129+
| [type-declaration-immutability](docs/rules/type-declaration-immutability.md) | Enforce the immutability of types based on patterns. | ☑️ ✅ 🔒 ![badge-no-mutations][] | | | 🔧 | 💡 | 💭 | |
130130

131131
### No Other Paradigms
132132

docs/rules/type-declaration-immutability.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

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

5-
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
5+
🔧💡 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).
66

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

@@ -86,6 +86,7 @@ type Options = {
8686
| { pattern: string; replace: string }
8787
| Array<{ pattern: string; replace: string }>
8888
| false;
89+
suggestions?: Array<{ pattern: string; replace: string }> | false;
8990
}>;
9091
ignoreInterfaces: boolean;
9192
ignoreIdentifierPattern: string[] | string;
@@ -102,6 +103,7 @@ const defaults = {
102103
immutability: "Immutable",
103104
comparator: "AtLeast",
104105
fixer: false,
106+
suggestions: false,
105107
},
106108
],
107109
ignoreInterfaces: false,
@@ -186,6 +188,10 @@ immutability. This can be thought of as `<`, `<=`, `==`, `>=` or `>`.
186188
Configure the fixer for this rule to work with your setup.
187189
If not set, or set to `false`, the fixer will be disabled.
188190

191+
#### `suggestions`
192+
193+
Configure any suggestions for this rule to work with your setup.
194+
189195
### `ignoreInterfaces`
190196

191197
A boolean to specify whether interfaces should be exempt from these rules.

src/rules/type-declaration-immutability.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ type FixerConfig = {
5656
replace: string;
5757
};
5858

59+
type SuggestionsConfig = FixerConfig[];
60+
5961
/**
6062
* The options this rule can take.
6163
*/
@@ -71,6 +73,7 @@ type Options = [
7173
| RuleEnforcementComparator
7274
| keyof typeof RuleEnforcementComparator;
7375
fixer?: FixerConfigRaw | FixerConfigRaw[] | false;
76+
suggestions?: FixerConfigRaw[] | false;
7477
}>;
7578
ignoreInterfaces: boolean;
7679
},
@@ -107,6 +110,26 @@ const fixerSchema: JSONSchema4 = {
107110
],
108111
};
109112

113+
const suggestionsSchema: JSONSchema4 = {
114+
oneOf: [
115+
{
116+
type: "boolean",
117+
enum: [false],
118+
},
119+
{
120+
type: "array",
121+
items: {
122+
type: "object",
123+
properties: {
124+
pattern: { type: "string" },
125+
replace: { type: "string" },
126+
},
127+
additionalProperties: false,
128+
},
129+
},
130+
],
131+
};
132+
110133
/**
111134
* The schema for the rule options.
112135
*/
@@ -138,6 +161,7 @@ const schema: JSONSchema4[] = [
138161
enum: Object.values(RuleEnforcementComparator),
139162
},
140163
fixer: fixerSchema,
164+
suggestions: suggestionsSchema,
141165
},
142166
required: ["identifiers", "immutability"],
143167
additionalProperties: false,
@@ -195,6 +219,7 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages, Options> = {
195219
},
196220
messages: errorMessages,
197221
fixable: "code",
222+
hasSuggestions: true,
198223
schema,
199224
};
200225

@@ -206,6 +231,7 @@ export type ImmutabilityRule = {
206231
immutability: Immutability;
207232
comparator: RuleEnforcementComparator;
208233
fixers: FixerConfig[] | false;
234+
suggestions: SuggestionsConfig | false;
209235
};
210236

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

274+
const suggestions =
275+
rule.suggestions === undefined || rule.suggestions === false
276+
? false
277+
: rule.suggestions.map((r) => ({
278+
...r,
279+
pattern: new RegExp(r.pattern, "us"),
280+
}));
281+
248282
return {
249283
identifiers,
250284
immutability,
251285
comparator,
252286
fixers,
287+
suggestions,
253288
};
254289
});
255290
}
@@ -297,6 +332,27 @@ function getConfiguredFixer<T extends TSESTree.Node>(
297332
fixer.replaceText(node, text.replace(config.pattern, config.replace));
298333
}
299334

335+
/**
336+
* Get the suggestions that uses the user config.
337+
*/
338+
function getConfiguredSuggestions<T extends TSESTree.Node>(
339+
node: T,
340+
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
341+
configs: FixerConfig[],
342+
messageId: keyof typeof errorMessages,
343+
): NonNullable<Descriptor["suggest"]> | null {
344+
const text = context.sourceCode.getText(node);
345+
const matchingConfig = configs.filter((c) => c.pattern.test(text));
346+
if (matchingConfig.length === 0) {
347+
return null;
348+
}
349+
return matchingConfig.map((config) => ({
350+
fix: (fixer) =>
351+
fixer.replaceText(node, text.replace(config.pattern, config.replace)),
352+
messageId,
353+
}));
354+
}
355+
300356
/**
301357
* Compare the actual immutability to the expected immutability.
302358
*/
@@ -337,24 +393,37 @@ function getResults(
337393
};
338394
}
339395

396+
const messageId = RuleEnforcementComparator[
397+
rule.comparator
398+
] as keyof typeof RuleEnforcementComparator;
399+
340400
const fix =
341401
rule.fixers === false || isTSInterfaceDeclaration(node)
342402
? null
343403
: getConfiguredFixer(node.typeAnnotation, context, rule.fixers);
344404

405+
const suggest =
406+
rule.suggestions === false || isTSInterfaceDeclaration(node)
407+
? null
408+
: getConfiguredSuggestions(
409+
node.typeAnnotation,
410+
context,
411+
rule.suggestions,
412+
messageId,
413+
);
414+
345415
return {
346416
context,
347417
descriptors: [
348418
{
349419
node: node.id,
350-
messageId: RuleEnforcementComparator[
351-
rule.comparator
352-
] as keyof typeof RuleEnforcementComparator,
420+
messageId,
353421
data: {
354422
actual: Immutability[immutability],
355423
expected: Immutability[rule.immutability],
356424
},
357425
fix,
426+
suggest,
358427
},
359428
],
360429
};

0 commit comments

Comments
 (0)