From 36aef113c498f8effc7d77e39c23ae46f8ac9d2f Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sat, 25 May 2024 23:37:44 +1200 Subject: [PATCH] feat(prefer-immutable-types): allow for changing suggestion messages --- docs/rules/prefer-immutable-types.md | 17 ++++- src/rules/prefer-immutable-types.ts | 75 +++++++++++++------ .../ts/parameters/invalid.ts | 46 +++++++++--- .../ts/return-types/invalid.ts | 10 ++- .../ts/variables/invalid.ts | 25 +++++-- 5 files changed, 128 insertions(+), 45 deletions(-) diff --git a/docs/rules/prefer-immutable-types.md b/docs/rules/prefer-immutable-types.md index 1c02fc0ac..eb4397adc 100644 --- a/docs/rules/prefer-immutable-types.md +++ b/docs/rules/prefer-immutable-types.md @@ -240,9 +240,15 @@ type Options = { }; suggestions?: { - ReadonlyShallow?: Array>; - ReadonlyDeep?: Array>; - Immutable?: Array>; + ReadonlyShallow?: Array< + Array<{ pattern: string; replace: string; message?: string }> + >; + ReadonlyDeep?: Array< + Array<{ pattern: string; replace: string; message?: string }> + >; + Immutable?: Array< + Array<{ pattern: string; replace: string; message?: string }> + >; }; overrides?: Array<{ @@ -293,14 +299,19 @@ const defaults = { pattern: "^([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\[\\])$", replace: "readonly $1", + message: "Prepend with readonly.", }, { pattern: "^(Array|Map|Set)<(.+)>$", replace: "Readonly$1<$2>", + message: "Use Readonly$1 instead of $1.", }, + ], + [ { pattern: "^(.+)$", replace: "Readonly<$1>", + message: "Surround with Readonly.", }, ], ], diff --git a/src/rules/prefer-immutable-types.ts b/src/rules/prefer-immutable-types.ts index b19748e2e..895dcbc33 100644 --- a/src/rules/prefer-immutable-types.ts +++ b/src/rules/prefer-immutable-types.ts @@ -93,6 +93,8 @@ type FixerConfigRaw = { replace: string; }; +type SuggestionsConfigRaw = Array; + type FixerConfigRawMap = Partial< Record< "ReadonlyShallow" | "ReadonlyDeep" | "Immutable", @@ -103,7 +105,7 @@ type FixerConfigRawMap = Partial< type SuggestionConfigRawMap = Partial< Record< "ReadonlyShallow" | "ReadonlyDeep" | "Immutable", - FixerConfigRaw[][] | undefined + SuggestionsConfigRaw[] | undefined > >; @@ -112,7 +114,7 @@ type FixerConfig = { replace: string; }; -type SuggestionsConfig = FixerConfig[]; +type SuggestionsConfig = Array; /** * The options this rule can take. @@ -216,6 +218,7 @@ const suggestionsSchema: JSONSchema4 = { properties: { pattern: { type: "string" }, replace: { type: "string" }, + message: { type: "string" }, }, additionalProperties: false, }, @@ -286,14 +289,19 @@ const defaultOptions: RawOptions = [ pattern: "^([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\[\\])$", replace: "readonly $1", + message: "Prepend with readonly.", }, { pattern: "^(Array|Map|Set)<(.+)>$", replace: "Readonly$1<$2>", + message: "Use Readonly$1 instead of $1.", }, + ], + [ { pattern: "^(.+)$", replace: "Readonly<$1>", + message: "Surround with Readonly.", }, ], ], @@ -314,6 +322,7 @@ const errorMessages = { propertyImmutability: 'Property should have an immutability of at least "{{ expected }}" (actual: "{{ actual }}").', propertyModifier: "Property should have a readonly modifier.", + userDefined: "{{ message }}", } as const; /** @@ -342,7 +351,7 @@ type Descriptor = RuleResult< type AllFixers = { fix: ReportFixFunction | null; - suggestionFixers: ReportFixFunction[] | null; + suggestionFixers: Array<{ fix: ReportFixFunction; message: string }> | null; }; /** @@ -394,14 +403,26 @@ function getConfiguredSuggestionFixers( suggestionsConfigs: ReadonlyArray, ) { return suggestionsConfigs - .map((configs): NonNullable | null => { - const config = configs.find((c) => c.pattern.test(text)); - if (config === undefined) { - return null; - } - return (fixer) => - fixer.replaceText(node, text.replace(config.pattern, config.replace)); - }) + .map( + ( + configs, + ): { fix: NonNullable; message: string } | null => { + const config = configs.find((c) => c.pattern.test(text)); + if (config === undefined) { + return null; + } + return { + fix: (fixer) => + fixer.replaceText( + node, + text.replace(config.pattern, config.replace), + ), + message: + config.message ?? + `Replace with: ${text.replace(config.pattern, config.replace)}`, + }; + }, + ) .filter(isDefined); } @@ -596,9 +617,11 @@ function getParameterTypeViolations( data, fix, suggest: - suggestionFixers?.map((fix) => ({ - messageId, - data, + suggestionFixers?.map(({ fix, message }) => ({ + messageId: "userDefined", + data: { + message, + }, fix, })) ?? null, }; @@ -722,9 +745,11 @@ function getReturnTypeViolations( data, fix, suggest: - suggestionFixers?.map((fix) => ({ - messageId, - data, + suggestionFixers?.map(({ fix, message }) => ({ + messageId: "userDefined", + data: { + message, + }, fix, })) ?? null, }, @@ -795,9 +820,11 @@ function getReturnTypeViolations( data, fix, suggest: - suggestionFixers?.map((fix) => ({ - messageId, - data, + suggestionFixers?.map(({ fix, message }) => ({ + messageId: "userDefined", + data: { + message, + }, fix, })) ?? null, }, @@ -995,9 +1022,11 @@ function checkVariable( data, fix, suggest: - suggestionFixers?.map((fix) => ({ - messageId, - data, + suggestionFixers?.map(({ fix, message }) => ({ + messageId: "userDefined", + data: { + message, + }, fix, })) ?? null, }; diff --git a/tests/rules/prefer-immutable-types/ts/parameters/invalid.ts b/tests/rules/prefer-immutable-types/ts/parameters/invalid.ts index 59048afed..44d5a84f6 100644 --- a/tests/rules/prefer-immutable-types/ts/parameters/invalid.ts +++ b/tests/rules/prefer-immutable-types/ts/parameters/invalid.ts @@ -46,7 +46,8 @@ const tests: Array< column: 14, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { message: "Surround with Readonly." }, output: "function foo(arg1: Readonly<{ foo: string }>, arg2: { foo: number }) {}", }, @@ -59,7 +60,8 @@ const tests: Array< column: 37, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { message: "Surround with Readonly." }, output: "function foo(arg1: { foo: string }, arg2: Readonly<{ foo: number }>) {}", }, @@ -179,7 +181,8 @@ const tests: Array< column: 5, suggestions: [ { - messageId: "propertyModifier", + messageId: "userDefined", + data: { message: "Prepend with readonly." }, output: dedent` class Klass { constructor ( @@ -199,7 +202,8 @@ const tests: Array< column: 5, suggestions: [ { - messageId: "propertyModifier", + messageId: "userDefined", + data: { message: "Prepend with readonly." }, output: dedent` class Klass { constructor ( @@ -219,7 +223,8 @@ const tests: Array< column: 5, suggestions: [ { - messageId: "propertyModifier", + messageId: "userDefined", + data: { message: "Prepend with readonly." }, output: dedent` class Klass { constructor ( @@ -245,7 +250,8 @@ const tests: Array< column: 46, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { message: "Surround with Readonly." }, output: "function foo(arg0: { foo: string | number }, arg1: Readonly<{ foo: string | number }>): arg0 is { foo: number } {}", }, @@ -295,7 +301,8 @@ const tests: Array< column: 14, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { message: "Replace with: ReadonlyDeep<{ foo: string }>" }, output: "function foo(arg1: ReadonlyDeep<{ foo: string }>) {}", }, ], @@ -329,7 +336,10 @@ const tests: Array< column: 14, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { + message: "Replace with: ReadonlyDeep<{ foo: { bar: string } }>", + }, output: "function foo(arg1: ReadonlyDeep<{ foo: { bar: string } }>) {}", }, @@ -357,7 +367,10 @@ const tests: Array< column: 14, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { + message: "Use ReadonlyArray instead of Array.", + }, output: dedent` function foo(arg: ReadonlyArray) {} function foo(arg: string[]) {} @@ -378,7 +391,10 @@ const tests: Array< column: 14, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { + message: "Prepend with readonly.", + }, output: dedent` function foo(arg: Array) {} function foo(arg: readonly string[]) {} @@ -399,7 +415,10 @@ const tests: Array< column: 14, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { + message: "Use ReadonlySet instead of Set.", + }, output: dedent` function foo(arg: Array) {} function foo(arg: string[]) {} @@ -420,7 +439,10 @@ const tests: Array< column: 14, suggestions: [ { - messageId: "parameter", + messageId: "userDefined", + data: { + message: "Use ReadonlyMap instead of Map.", + }, output: dedent` function foo(arg: Array) {} function foo(arg: string[]) {} diff --git a/tests/rules/prefer-immutable-types/ts/return-types/invalid.ts b/tests/rules/prefer-immutable-types/ts/return-types/invalid.ts index 6cc042458..99213853d 100644 --- a/tests/rules/prefer-immutable-types/ts/return-types/invalid.ts +++ b/tests/rules/prefer-immutable-types/ts/return-types/invalid.ts @@ -68,7 +68,10 @@ const tests: Array< column: 26, suggestions: [ { - messageId: "returnType", + messageId: "userDefined", + data: { + message: "Surround with Readonly.", + }, output: dedent` function foo(arg: number): Readonly<{ foo: string }>; function foo(arg: string): Readonly<{ foo: number }>; @@ -85,7 +88,10 @@ const tests: Array< column: 27, suggestions: [ { - messageId: "returnType", + messageId: "userDefined", + data: { + message: "Surround with Readonly.", + }, output: dedent` function foo(arg: number): { foo: string }; function foo(arg: string): Readonly<{ foo: number }>; diff --git a/tests/rules/prefer-immutable-types/ts/variables/invalid.ts b/tests/rules/prefer-immutable-types/ts/variables/invalid.ts index a4a28f42f..be723c42a 100644 --- a/tests/rules/prefer-immutable-types/ts/variables/invalid.ts +++ b/tests/rules/prefer-immutable-types/ts/variables/invalid.ts @@ -65,7 +65,10 @@ const tests: Array< column: 7, suggestions: [ { - messageId: "variable", + messageId: "userDefined", + data: { + message: "Surround with Readonly.", + }, output: dedent` const foo: Readonly<{ foo: string }> = {} as any, bar: Readonly<{ foo: number }> = {} as any; @@ -168,7 +171,10 @@ const tests: Array< column: 3, suggestions: [ { - messageId: "propertyModifier", + messageId: "userDefined", + data: { + message: "Prepend with readonly.", + }, output: dedent` class Klass { readonly foo: number; @@ -187,7 +193,10 @@ const tests: Array< column: 3, suggestions: [ { - messageId: "propertyModifier", + messageId: "userDefined", + data: { + message: "Prepend with readonly.", + }, output: dedent` class Klass { foo: number; @@ -206,7 +215,10 @@ const tests: Array< column: 3, suggestions: [ { - messageId: "propertyModifier", + messageId: "userDefined", + data: { + message: "Prepend with readonly.", + }, output: dedent` class Klass { foo: number; @@ -225,7 +237,10 @@ const tests: Array< column: 3, suggestions: [ { - messageId: "propertyModifier", + messageId: "userDefined", + data: { + message: "Prepend with readonly.", + }, output: dedent` class Klass { foo: number;