eslint-plugin-typefest 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +19 -3
  2. package/dist/_internal/constrained-type-at-location.d.ts.map +1 -1
  3. package/dist/_internal/constrained-type-at-location.js.map +1 -1
  4. package/dist/_internal/function-type-reference-patterns.d.ts +34 -0
  5. package/dist/_internal/function-type-reference-patterns.d.ts.map +1 -0
  6. package/dist/_internal/function-type-reference-patterns.js +103 -0
  7. package/dist/_internal/function-type-reference-patterns.js.map +1 -0
  8. package/dist/_internal/rule-catalog.d.ts.map +1 -1
  9. package/dist/_internal/rule-catalog.js +11 -0
  10. package/dist/_internal/rule-catalog.js.map +1 -1
  11. package/dist/_internal/rules-registry.d.ts.map +1 -1
  12. package/dist/_internal/rules-registry.js +22 -0
  13. package/dist/_internal/rules-registry.js.map +1 -1
  14. package/dist/_internal/set-membership.d.ts.map +1 -1
  15. package/dist/_internal/set-membership.js.map +1 -1
  16. package/dist/_internal/type-checker-compat.d.ts.map +1 -1
  17. package/dist/_internal/type-checker-compat.js.map +1 -1
  18. package/dist/_internal/type-reference-node.d.ts +8 -0
  19. package/dist/_internal/type-reference-node.d.ts.map +1 -1
  20. package/dist/_internal/type-reference-node.js +14 -0
  21. package/dist/_internal/type-reference-node.js.map +1 -1
  22. package/dist/_internal/typefest-config-references.d.ts +2 -1
  23. package/dist/_internal/typefest-config-references.d.ts.map +1 -1
  24. package/dist/_internal/typefest-config-references.js +11 -2
  25. package/dist/_internal/typefest-config-references.js.map +1 -1
  26. package/dist/_internal/typescript-eslint-node-autofix.js.map +1 -1
  27. package/dist/plugin.cjs +1094 -138
  28. package/dist/plugin.cjs.map +4 -4
  29. package/dist/plugin.d.ts.map +1 -1
  30. package/dist/plugin.js +5 -0
  31. package/dist/plugin.js.map +1 -1
  32. package/dist/rules/prefer-ts-extras-object-map-values.d.ts +14 -0
  33. package/dist/rules/prefer-ts-extras-object-map-values.d.ts.map +1 -0
  34. package/dist/rules/prefer-ts-extras-object-map-values.js +227 -0
  35. package/dist/rules/prefer-ts-extras-object-map-values.js.map +1 -0
  36. package/dist/rules/prefer-type-fest-asyncify.d.ts +7 -0
  37. package/dist/rules/prefer-type-fest-asyncify.d.ts.map +1 -0
  38. package/dist/rules/prefer-type-fest-asyncify.js +79 -0
  39. package/dist/rules/prefer-type-fest-asyncify.js.map +1 -0
  40. package/dist/rules/prefer-type-fest-conditional-except.d.ts +7 -0
  41. package/dist/rules/prefer-type-fest-conditional-except.d.ts.map +1 -0
  42. package/dist/rules/prefer-type-fest-conditional-except.js +94 -0
  43. package/dist/rules/prefer-type-fest-conditional-except.js.map +1 -0
  44. package/dist/rules/prefer-type-fest-conditional-keys.d.ts +7 -0
  45. package/dist/rules/prefer-type-fest-conditional-keys.d.ts.map +1 -0
  46. package/dist/rules/prefer-type-fest-conditional-keys.js +78 -0
  47. package/dist/rules/prefer-type-fest-conditional-keys.js.map +1 -0
  48. package/dist/rules/prefer-type-fest-distributed-omit.d.ts +7 -0
  49. package/dist/rules/prefer-type-fest-distributed-omit.d.ts.map +1 -0
  50. package/dist/rules/prefer-type-fest-distributed-omit.js +67 -0
  51. package/dist/rules/prefer-type-fest-distributed-omit.js.map +1 -0
  52. package/dist/rules/prefer-type-fest-distributed-pick.d.ts +7 -0
  53. package/dist/rules/prefer-type-fest-distributed-pick.d.ts.map +1 -0
  54. package/dist/rules/prefer-type-fest-distributed-pick.js +95 -0
  55. package/dist/rules/prefer-type-fest-distributed-pick.js.map +1 -0
  56. package/dist/rules/prefer-type-fest-merge.d.ts +7 -0
  57. package/dist/rules/prefer-type-fest-merge.d.ts.map +1 -0
  58. package/dist/rules/prefer-type-fest-merge.js +93 -0
  59. package/dist/rules/prefer-type-fest-merge.js.map +1 -0
  60. package/dist/rules/prefer-type-fest-pick-index-signature.d.ts +7 -0
  61. package/dist/rules/prefer-type-fest-pick-index-signature.d.ts.map +1 -0
  62. package/dist/rules/prefer-type-fest-pick-index-signature.js +98 -0
  63. package/dist/rules/prefer-type-fest-pick-index-signature.js.map +1 -0
  64. package/dist/rules/prefer-type-fest-set-return-type.d.ts +7 -0
  65. package/dist/rules/prefer-type-fest-set-return-type.d.ts.map +1 -0
  66. package/dist/rules/prefer-type-fest-set-return-type.js +53 -0
  67. package/dist/rules/prefer-type-fest-set-return-type.js.map +1 -0
  68. package/dist/rules/prefer-type-fest-stringified.d.ts +7 -0
  69. package/dist/rules/prefer-type-fest-stringified.d.ts.map +1 -0
  70. package/dist/rules/prefer-type-fest-stringified.js +73 -0
  71. package/dist/rules/prefer-type-fest-stringified.js.map +1 -0
  72. package/dist/rules/prefer-type-fest-union-to-intersection.d.ts +7 -0
  73. package/dist/rules/prefer-type-fest-union-to-intersection.d.ts.map +1 -0
  74. package/dist/rules/prefer-type-fest-union-to-intersection.js +114 -0
  75. package/dist/rules/prefer-type-fest-union-to-intersection.js.map +1 -0
  76. package/docs/rules/getting-started.md +2 -1
  77. package/docs/rules/guides/preset-selection-strategy.md +7 -0
  78. package/docs/rules/overview.md +1 -0
  79. package/docs/rules/prefer-ts-extras-object-map-values.md +146 -0
  80. package/docs/rules/prefer-type-fest-asyncify.md +93 -0
  81. package/docs/rules/prefer-type-fest-conditional-except.md +141 -0
  82. package/docs/rules/prefer-type-fest-conditional-keys.md +93 -0
  83. package/docs/rules/prefer-type-fest-distributed-omit.md +92 -0
  84. package/docs/rules/prefer-type-fest-distributed-pick.md +92 -0
  85. package/docs/rules/prefer-type-fest-merge.md +161 -0
  86. package/docs/rules/prefer-type-fest-pick-index-signature.md +93 -0
  87. package/docs/rules/prefer-type-fest-set-return-type.md +93 -0
  88. package/docs/rules/prefer-type-fest-stringified.md +134 -0
  89. package/docs/rules/prefer-type-fest-union-to-intersection.md +118 -0
  90. package/docs/rules/presets/experimental.md +163 -0
  91. package/docs/rules/presets/index.md +13 -0
  92. package/package.json +50 -48
@@ -0,0 +1,114 @@
1
+ import { areEquivalentTypeNodes } from "../_internal/normalize-expression-text.js";
2
+ import { reportWithOptionalFix } from "../_internal/rule-reporting.js";
3
+ import { unwrapParenthesizedTypeNode } from "../_internal/type-reference-node.js";
4
+ import { createTypedRule } from "../_internal/typed-rule.js";
5
+ const isDistributiveConditionalExtendsType = (node) => {
6
+ const normalizedNode = unwrapParenthesizedTypeNode(node);
7
+ return (normalizedNode.type === "TSAnyKeyword" ||
8
+ normalizedNode.type === "TSUnknownKeyword");
9
+ };
10
+ const getSingleFunctionParameterType = (node) => {
11
+ if (node.params.length !== 1) {
12
+ return null;
13
+ }
14
+ const [onlyParameter] = node.params;
15
+ if (onlyParameter?.type !== "Identifier" ||
16
+ onlyParameter.typeAnnotation === undefined) {
17
+ return null;
18
+ }
19
+ return unwrapParenthesizedTypeNode(onlyParameter.typeAnnotation.typeAnnotation);
20
+ };
21
+ const matchesUnionToIntersectionTrueType = (node, inferredTypeParameterName, unionType) => {
22
+ const normalizedNode = unwrapParenthesizedTypeNode(node);
23
+ if (normalizedNode.type === "TSTypeReference" &&
24
+ normalizedNode.typeName.type === "Identifier" &&
25
+ normalizedNode.typeName.name === inferredTypeParameterName) {
26
+ return true;
27
+ }
28
+ if (normalizedNode.type !== "TSIntersectionType" ||
29
+ normalizedNode.types.length !== 2) {
30
+ return false;
31
+ }
32
+ const [leftType, rightType] = normalizedNode.types;
33
+ if (leftType === undefined || rightType === undefined) {
34
+ return false;
35
+ }
36
+ const isInferredIdentifier = (typeNode) => typeNode.type === "TSTypeReference" &&
37
+ typeNode.typeName.type === "Identifier" &&
38
+ typeNode.typeName.name === inferredTypeParameterName;
39
+ return ((isInferredIdentifier(leftType) &&
40
+ areEquivalentTypeNodes(unwrapParenthesizedTypeNode(rightType), unwrapParenthesizedTypeNode(unionType))) ||
41
+ (isInferredIdentifier(rightType) &&
42
+ areEquivalentTypeNodes(unwrapParenthesizedTypeNode(leftType), unwrapParenthesizedTypeNode(unionType))));
43
+ };
44
+ const isUnionToIntersectionEquivalent = (node) => {
45
+ if (node.falseType.type !== "TSNeverKeyword") {
46
+ return false;
47
+ }
48
+ const distributedWrapper = unwrapParenthesizedTypeNode(node.checkType);
49
+ if (distributedWrapper.type !== "TSConditionalType") {
50
+ return false;
51
+ }
52
+ if (distributedWrapper.falseType.type !== "TSNeverKeyword" ||
53
+ !isDistributiveConditionalExtendsType(distributedWrapper.extendsType)) {
54
+ return false;
55
+ }
56
+ const distributedTrueType = unwrapParenthesizedTypeNode(distributedWrapper.trueType);
57
+ if (distributedTrueType.type !== "TSFunctionType") {
58
+ return false;
59
+ }
60
+ const distributedParameterType = getSingleFunctionParameterType(distributedTrueType);
61
+ if (distributedParameterType === null ||
62
+ !areEquivalentTypeNodes(distributedParameterType, unwrapParenthesizedTypeNode(distributedWrapper.checkType))) {
63
+ return false;
64
+ }
65
+ const extractorFunctionType = unwrapParenthesizedTypeNode(node.extendsType);
66
+ if (extractorFunctionType.type !== "TSFunctionType") {
67
+ return false;
68
+ }
69
+ const extractorParameterType = getSingleFunctionParameterType(extractorFunctionType);
70
+ if (extractorParameterType?.type !== "TSInferType") {
71
+ return false;
72
+ }
73
+ return matchesUnionToIntersectionTrueType(node.trueType, extractorParameterType.typeParameter.name.name, distributedWrapper.checkType);
74
+ };
75
+ /**
76
+ * ESLint rule definition for `prefer-type-fest-union-to-intersection`.
77
+ */
78
+ const preferTypeFestUnionToIntersectionRule = createTypedRule({
79
+ create(context) {
80
+ return {
81
+ TSConditionalType(node) {
82
+ if (!isUnionToIntersectionEquivalent(node)) {
83
+ return;
84
+ }
85
+ reportWithOptionalFix({
86
+ context,
87
+ fix: null,
88
+ messageId: "preferUnionToIntersection",
89
+ node,
90
+ });
91
+ },
92
+ };
93
+ },
94
+ defaultOptions: [],
95
+ meta: {
96
+ deprecated: false,
97
+ docs: {
98
+ description: "require TypeFest UnionToIntersection over custom distributive conditional helpers that turn unions into intersections.",
99
+ frozen: false,
100
+ recommended: false,
101
+ requiresTypeChecking: false,
102
+ typefestConfigs: ["typefest.configs.experimental"],
103
+ url: "https://nick2bad4u.github.io/eslint-plugin-typefest/docs/rules/prefer-type-fest-union-to-intersection",
104
+ },
105
+ messages: {
106
+ preferUnionToIntersection: "Prefer `UnionToIntersection<Union>` from type-fest over custom distributive conditional helpers that convert a union into an intersection.",
107
+ },
108
+ schema: [],
109
+ type: "suggestion",
110
+ },
111
+ name: "prefer-type-fest-union-to-intersection",
112
+ });
113
+ export default preferTypeFestUnionToIntersectionRule;
114
+ //# sourceMappingURL=prefer-type-fest-union-to-intersection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-type-fest-union-to-intersection.js","sourceRoot":"","sources":["../../src/rules/prefer-type-fest-union-to-intersection.ts"],"names":[],"mappings":"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"}
@@ -64,7 +64,8 @@ Use this pattern when you only extend rules and want full control over parser se
64
64
  2. Fix violations in small batches.
65
65
  3. Move to `recommended-type-checked` when you are ready for typed rules.
66
66
  4. Move to `strict` once your baseline is stable.
67
- 5. Use `all` only when you explicitly want every rule, including experimental rules.
67
+ 5. Use `all` when you want every stable rule.
68
+ 6. Use `experimental` only when you want report-only candidate rules under active evaluation.
68
69
 
69
70
  ## Need a subset instead of a full preset?
70
71
 
@@ -53,6 +53,13 @@ Choose this when:
53
53
  - You want full plugin coverage and can manage incremental cleanup.
54
54
  - You actively maintain migration and suppression hygiene.
55
55
 
56
+ ### `experimental`
57
+
58
+ Choose this when:
59
+
60
+ - You want everything from `all` plus lower-confidence candidate rules.
61
+ - You are comfortable evaluating report-only diagnostics before broader rollout.
62
+
56
63
  ### Domain overlays
57
64
 
58
65
  Layer these when they match your codebase goals:
@@ -39,6 +39,7 @@ That is enough for TypeScript files (`**/*.{ts,tsx,mts,cts}`).
39
39
  | 🟠 `typefest.configs["recommended-type-checked"]` | [Recommended (type-checked)](./presets/recommended-type-checked.md) |
40
40
  | 🔴 `typefest.configs.strict` | [Strict](./presets/strict.md) |
41
41
  | 🟣 `typefest.configs.all` | [All](./presets/all.md) |
42
+ | 🧪 `typefest.configs.experimental` | [Experimental](./presets/experimental.md) |
42
43
  | 💠 `typefest.configs["type-fest/types"]` | [type-fest/types](./presets/type-fest-types.md) |
43
44
  | ✴️ `typefest.configs["ts-extras/type-guards"]` | [ts-extras/type-guards](./presets/ts-extras-type-guards.md) |
44
45
 
@@ -0,0 +1,146 @@
1
+ # prefer-ts-extras-object-map-values
2
+
3
+ Prefer [`objectMapValues`](https://github.com/sindresorhus/ts-extras/blob/main/source/object-map-values.ts) from `ts-extras` over key-preserving `objectFromEntries(objectEntries(...).map(...))` chains.
4
+
5
+ This rule lives only in the experimental preset and reports without autofixing.
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule focuses on `ts-extras` entry pipelines whose callback preserves the original key and only changes the value.
10
+
11
+ - `objectFromEntries(objectEntries(value).map(([key, item]) => [key, nextValue]))`
12
+ - the same shape with a block body that immediately returns `[key, nextValue]`
13
+
14
+ It intentionally skips broader patterns that can encode different intent.
15
+
16
+ ## What this rule reports
17
+
18
+ This rule reports ts-extras object remapping chains when all of the following are true:
19
+
20
+ - the outer call is `objectFromEntries(...)`
21
+ - the inner source is `objectEntries(...)`
22
+ - the intermediate step is a direct `.map(...)` call
23
+ - the callback is an arrow function with a strict `[key, value]` tuple parameter
24
+ - the callback returns a two-item tuple whose first item is the original `key`
25
+
26
+ The rule is currently **report-only**. It does not autofix or suggest a replacement yet.
27
+
28
+ ## Why this rule exists
29
+
30
+ `objectMapValues` reduces noise in a common value-only remapping pattern.
31
+
32
+ - The callback contract matches the intent directly: `(value, key) => nextValue`.
33
+ - Readers do not need to mentally unpack an entries pipeline to see that keys stay the same.
34
+ - The code avoids a temporary array of tuples in authored source.
35
+
36
+ Because this pattern appears in semantically different forms, the rule stays narrow on purpose and reports only equivalent key-preserving remaps.
37
+
38
+ ## ❌ Incorrect
39
+
40
+ ```ts
41
+ import {objectEntries, objectFromEntries} from "ts-extras";
42
+
43
+ const statusById = {
44
+ alpha: "up",
45
+ beta: "down",
46
+ } as const;
47
+
48
+ const labels = objectFromEntries(
49
+ objectEntries(statusById).map(([key, value]) => [key, `${key}:${value}`])
50
+ );
51
+ ```
52
+
53
+ ## ✅ Correct
54
+
55
+ ```ts
56
+ import {objectMapValues} from "ts-extras";
57
+
58
+ const statusById = {
59
+ alpha: "up",
60
+ beta: "down",
61
+ } as const;
62
+
63
+ const labels = objectMapValues(
64
+ statusById,
65
+ (value, key) => `${key}:${value}`
66
+ );
67
+ ```
68
+
69
+ ## Behavior and migration notes
70
+
71
+ - This rule only reports chains that preserve the original key unchanged.
72
+ - It ignores native `Object.entries` / `Object.fromEntries` pipelines to avoid overlapping with the stable `objectEntries` and `objectFromEntries` adoption rules.
73
+ - It ignores `.map(...)` calls that pass a `thisArg`, because `objectMapValues` does not have an equivalent callback binding parameter.
74
+ - It ignores function-expression callbacks for now, because the experimental rule avoids assuming `this` behavior is irrelevant.
75
+
76
+ ## Additional examples
77
+
78
+ ### ❌ Incorrect — Additional example
79
+
80
+ ```ts
81
+ import {objectEntries, objectFromEntries} from "ts-extras";
82
+
83
+ const statusById = {
84
+ alpha: "up",
85
+ beta: "down",
86
+ } as const;
87
+
88
+ const uppercased = objectFromEntries(
89
+ objectEntries(statusById).map(([key, value]) => [key, value.toUpperCase()])
90
+ );
91
+ ```
92
+
93
+ ### ✅ Correct — Additional example
94
+
95
+ ```ts
96
+ import {objectMapValues} from "ts-extras";
97
+
98
+ const statusById = {
99
+ alpha: "up",
100
+ beta: "down",
101
+ } as const;
102
+
103
+ const uppercased = objectMapValues(statusById, (value) => value.toUpperCase());
104
+ ```
105
+
106
+ ## ESLint flat config example
107
+
108
+ ```ts
109
+ import typefest from "eslint-plugin-typefest";
110
+
111
+ export default [typefest.configs.experimental];
112
+ ```
113
+
114
+ ## When not to use it
115
+
116
+ Disable this rule if your team prefers explicit entry-tuple pipelines for readability, or if the remapping logic changes keys often enough that `objectMapValues` is not the dominant style.
117
+
118
+ ## Package documentation
119
+
120
+ ts-extras package documentation:
121
+
122
+ Source file: [`source/object-map-values.ts`](https://github.com/sindresorhus/ts-extras/blob/main/source/object-map-values.ts)
123
+
124
+ ```ts
125
+ import {objectMapValues} from "ts-extras";
126
+
127
+ const object = {a: 1, b: 2, c: 3};
128
+
129
+ const mapped = objectMapValues(object, value => String(value));
130
+ //=> {a?: string; b?: string; c?: string}
131
+ ```
132
+
133
+ > **Rule catalog ID:** R086
134
+
135
+ ## Further reading
136
+
137
+ - [`ts-extras` README](https://github.com/sindresorhus/ts-extras)
138
+ - [`ts-extras` package reference](https://www.npmjs.com/package/ts-extras)
139
+ - [MDN: Array.prototype.map()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
140
+ - [MDN: Object.fromEntries()](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries)
141
+
142
+ ## Adoption resources
143
+
144
+ - [Rule adoption checklist](./guides/adoption-checklist.md)
145
+ - [Rollout and fix safety](./guides/rollout-and-fix-safety.md)
146
+ - [Preset selection strategy](./guides/preset-selection-strategy.md)
@@ -0,0 +1,93 @@
1
+ # prefer-type-fest-asyncify
2
+
3
+ Prefer [`Asyncify`](https://github.com/sindresorhus/type-fest/blob/main/source/asyncify.d.ts) from `type-fest` over manual asyncified function-type wrappers.
4
+
5
+ This rule lives only in the experimental preset and reports without autofixing.
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule focuses on two narrow shapes:
10
+
11
+ - direct function-type wrappers of the form `(...args: Parameters<Function>) => Promise<Awaited<ReturnType<Function>>>`
12
+ - `SetReturnType<Function, Promise<Awaited<ReturnType<Function>>>>` compositions
13
+
14
+ It intentionally skips broader promise-returning wrappers so the report signal stays specific to `Asyncify`.
15
+
16
+ ## What this rule reports
17
+
18
+ This rule reports when all of the following are true:
19
+
20
+ - the wrapper keeps the original parameter list through `Parameters<Function>` or `SetReturnType<Function, ...>`
21
+ - the new return type is exactly `Promise<Awaited<ReturnType<Function>>>`
22
+
23
+ The rule is currently **report-only**. It does not autofix or suggest a replacement yet.
24
+
25
+ ## Why this rule exists
26
+
27
+ `Asyncify<Function>` communicates the intent directly.
28
+
29
+ - Readers can tell immediately that a synchronous function type is being boxed into an async equivalent.
30
+ - The helper avoids repeating the `Promise<Awaited<ReturnType<...>>>` boilerplate.
31
+ - It composes naturally with other Type-Fest function helpers.
32
+
33
+ ## ❌ Incorrect
34
+
35
+ ```ts
36
+ type AsyncVersion<Function_ extends (...arguments_: any[]) => any> =
37
+ (...arguments_: Parameters<Function_>) =>
38
+ Promise<Awaited<ReturnType<Function_>>>;
39
+ ```
40
+
41
+ ## ✅ Correct
42
+
43
+ ```ts
44
+ import type {Asyncify} from "type-fest";
45
+
46
+ type AsyncVersion<Function_ extends (...arguments_: any[]) => any> =
47
+ Asyncify<Function_>;
48
+ ```
49
+
50
+ ## Behavior and migration notes
51
+
52
+ - This rule intentionally overlaps with `SetReturnType` only for the exact asyncified return shape, and it takes precedence there.
53
+ - It reports both the raw wrapper and the `SetReturnType<..., Promise<Awaited<ReturnType<...>>>>` composition.
54
+ - It ignores non-async `SetReturnType` usage.
55
+
56
+ ## ESLint flat config example
57
+
58
+ ```ts
59
+ import typefest from "eslint-plugin-typefest";
60
+
61
+ export default [typefest.configs.experimental];
62
+ ```
63
+
64
+ ## When not to use it
65
+
66
+ Disable this rule if your team prefers the explicit wrapper signature or if you want to reserve `Asyncify` for only a subset of wrapper types.
67
+
68
+ ## Package documentation
69
+
70
+ TypeFest package documentation:
71
+
72
+ Source file: [`source/asyncify.d.ts`](https://github.com/sindresorhus/type-fest/blob/main/source/asyncify.d.ts)
73
+
74
+ ```ts
75
+ import type {Asyncify} from "type-fest";
76
+
77
+ type AsyncVersion<Function_ extends (...arguments_: any[]) => any> =
78
+ Asyncify<Function_>;
79
+ ```
80
+
81
+ > **Rule catalog ID:** R093
82
+
83
+ ## Further reading
84
+
85
+ - [`type-fest` README](https://github.com/sindresorhus/type-fest)
86
+ - [TypeScript Handbook: Awaited](https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype)
87
+ - [TypeScript Handbook: ReturnType](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype)
88
+
89
+ ## Adoption resources
90
+
91
+ - [Rule adoption checklist](./guides/adoption-checklist.md)
92
+ - [Rollout and fix safety](./guides/rollout-and-fix-safety.md)
93
+ - [Preset selection strategy](./guides/preset-selection-strategy.md)
@@ -0,0 +1,141 @@
1
+ # prefer-type-fest-conditional-except
2
+
3
+ Prefer [`ConditionalExcept`](https://github.com/sindresorhus/type-fest/blob/main/source/conditional-except.d.ts) from `type-fest` over `Except<Base, ConditionalKeys<Base, Condition>>` compositions.
4
+
5
+ This rule lives only in the experimental preset and reports without autofixing.
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule focuses on direct `type-fest` compositions that already use both `Except` and `ConditionalKeys` together.
10
+
11
+ - `Except<Base, ConditionalKeys<Base, Condition>>`
12
+ - the same shape when `Except` and `ConditionalKeys` are imported under local aliases from `type-fest`
13
+
14
+ It intentionally skips builtin `Omit<Base, ConditionalKeys<Base, Condition>>` shapes to avoid overlapping with the stable `prefer-type-fest-except` rule.
15
+
16
+ ## What this rule reports
17
+
18
+ This rule reports `type-fest` compositions when all of the following are true:
19
+
20
+ - the outer type reference resolves to `Except`
21
+ - the second type argument resolves to `ConditionalKeys`
22
+ - the base type passed to `Except` matches the base type passed to `ConditionalKeys`
23
+
24
+ The rule is currently **report-only**. It does not autofix or suggest a replacement yet.
25
+
26
+ ## Why this rule exists
27
+
28
+ `ConditionalExcept<Base, Condition>` expresses the intent of conditional omission directly.
29
+
30
+ - Readers do not need to mentally expand two helper types to understand the result.
31
+ - The single helper form is shorter and easier to scan in large type aliases.
32
+ - The intent stays aligned with the upstream Type-Fest utility that already models this exact composition.
33
+
34
+ ## ❌ Incorrect
35
+
36
+ ```ts
37
+ import type {ConditionalKeys, Except} from "type-fest";
38
+
39
+ type Example = {
40
+ count: number;
41
+ enabled: boolean;
42
+ name: string;
43
+ };
44
+
45
+ type NonStrings = Except<Example, ConditionalKeys<Example, string>>;
46
+ ```
47
+
48
+ ## ✅ Correct
49
+
50
+ ```ts
51
+ import type {ConditionalExcept} from "type-fest";
52
+
53
+ type Example = {
54
+ count: number;
55
+ enabled: boolean;
56
+ name: string;
57
+ };
58
+
59
+ type NonStrings = ConditionalExcept<Example, string>;
60
+ ```
61
+
62
+ ## Behavior and migration notes
63
+
64
+ - This rule only reports the direct `Except<..., ConditionalKeys<...>>` composition.
65
+ - It ignores builtin `Omit` forms on purpose to avoid duplicate reporting with `prefer-type-fest-except`.
66
+ - It ignores compositions where the `ConditionalKeys` base type does not exactly match the `Except` base type.
67
+
68
+ ## Additional examples
69
+
70
+ ### ❌ Incorrect — Additional example
71
+
72
+ ```ts
73
+ import type {ConditionalKeys as MatchingKeys, Except as StrictExcept} from "type-fest";
74
+
75
+ type Example = {
76
+ count: number;
77
+ enabled: boolean;
78
+ name: string;
79
+ };
80
+
81
+ type NonStrings = StrictExcept<Example, MatchingKeys<Example, string>>;
82
+ ```
83
+
84
+ ### ✅ Correct — Additional example
85
+
86
+ ```ts
87
+ import type {ConditionalExcept} from "type-fest";
88
+
89
+ type Example = {
90
+ count: number;
91
+ enabled: boolean;
92
+ name: string;
93
+ };
94
+
95
+ type NonStrings = ConditionalExcept<Example, string>;
96
+ ```
97
+
98
+ ## ESLint flat config example
99
+
100
+ ```ts
101
+ import typefest from "eslint-plugin-typefest";
102
+
103
+ export default [typefest.configs.experimental];
104
+ ```
105
+
106
+ ## When not to use it
107
+
108
+ Disable this rule if your team prefers explicit helper-type composition over the shorter canonical alias, or if you want `Except` and `ConditionalKeys` to stay visible for teaching purposes.
109
+
110
+ ## Package documentation
111
+
112
+ TypeFest package documentation:
113
+
114
+ Source file: [`source/conditional-except.d.ts`](https://github.com/sindresorhus/type-fest/blob/main/source/conditional-except.d.ts)
115
+
116
+ ```ts
117
+ import type {ConditionalExcept} from "type-fest";
118
+
119
+ type Example = {
120
+ a: string;
121
+ b: string | number;
122
+ c: () => void;
123
+ };
124
+
125
+ type NonStringKeysOnly = ConditionalExcept<Example, string>;
126
+ //=> {b: string | number; c: () => void}
127
+ ```
128
+
129
+ > **Rule catalog ID:** R087
130
+
131
+ ## Further reading
132
+
133
+ - [`type-fest` README](https://github.com/sindresorhus/type-fest)
134
+ - [`type-fest` package reference](https://www.npmjs.com/package/type-fest)
135
+ - [TypeScript Handbook: Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html)
136
+
137
+ ## Adoption resources
138
+
139
+ - [Rule adoption checklist](./guides/adoption-checklist.md)
140
+ - [Rollout and fix safety](./guides/rollout-and-fix-safety.md)
141
+ - [Preset selection strategy](./guides/preset-selection-strategy.md)
@@ -0,0 +1,93 @@
1
+ # prefer-type-fest-conditional-keys
2
+
3
+ Prefer [`ConditionalKeys`](https://github.com/sindresorhus/type-fest/blob/main/source/conditional-keys.d.ts) from `type-fest` over manual `keyof`-remapped key filters.
4
+
5
+ This rule lives only in the experimental preset and reports without autofixing.
6
+
7
+ ## Targeted pattern scope
8
+
9
+ This rule focuses on the common pattern that filters keys by value condition through `keyof` over a mapped type:
10
+
11
+ - `keyof { [K in keyof Base as Base[K] extends Condition ? K : never]: ... }`
12
+
13
+ It intentionally skips more elaborate helpers that involve strictness wrappers, tuple-to-object conversions, or additional conditional layers.
14
+
15
+ ## What this rule reports
16
+
17
+ This rule reports `keyof`-wrapped mapped types when all of the following are true:
18
+
19
+ - the key constraint is `keyof Base`
20
+ - the key remap condition is `Base[K] extends Condition ? K : never`
21
+ - the outer type operator is `keyof`
22
+
23
+ The rule is currently **report-only**. It does not autofix or suggest a replacement yet.
24
+
25
+ ## Why this rule exists
26
+
27
+ `ConditionalKeys<Base, Condition>` is easier to read than the raw remapping recipe.
28
+
29
+ - The helper name explains the intent immediately.
30
+ - Readers do not need to mentally evaluate the mapped type and outer `keyof`.
31
+ - Standardizing on Type-Fest reduces duplicated local helper patterns.
32
+
33
+ ## ❌ Incorrect
34
+
35
+ ```ts
36
+ type KeysMatching<Base, Condition> = keyof {
37
+ [Key in keyof Base as Base[Key] extends Condition
38
+ ? Key
39
+ : never]: never;
40
+ };
41
+ ```
42
+
43
+ ## ✅ Correct
44
+
45
+ ```ts
46
+ import type {ConditionalKeys} from "type-fest";
47
+
48
+ type KeysMatching<Base, Condition> = ConditionalKeys<Base, Condition>;
49
+ ```
50
+
51
+ ## Behavior and migration notes
52
+
53
+ - This rule targets only the narrow `keyof`-remapped key-filter pattern.
54
+ - It ignores plain mapped types that are not wrapped in `keyof`.
55
+ - It also ignores more elaborate Type-Fest-internal strictness variants to avoid speculative reporting.
56
+
57
+ ## ESLint flat config example
58
+
59
+ ```ts
60
+ import typefest from "eslint-plugin-typefest";
61
+
62
+ export default [typefest.configs.experimental];
63
+ ```
64
+
65
+ ## When not to use it
66
+
67
+ Disable this rule if your project prefers the explicit remapped-key form or if your helper intentionally diverges from Type-Fest semantics.
68
+
69
+ ## Package documentation
70
+
71
+ TypeFest package documentation:
72
+
73
+ Source file: [`source/conditional-keys.d.ts`](https://github.com/sindresorhus/type-fest/blob/main/source/conditional-keys.d.ts)
74
+
75
+ ```ts
76
+ import type {ConditionalKeys} from "type-fest";
77
+
78
+ type KeysMatching<Base, Condition> = ConditionalKeys<Base, Condition>;
79
+ ```
80
+
81
+ > **Rule catalog ID:** R095
82
+
83
+ ## Further reading
84
+
85
+ - [`type-fest` README](https://github.com/sindresorhus/type-fest)
86
+ - [TypeScript Handbook: Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)
87
+ - [TypeScript Handbook: Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html)
88
+
89
+ ## Adoption resources
90
+
91
+ - [Rule adoption checklist](./guides/adoption-checklist.md)
92
+ - [Rollout and fix safety](./guides/rollout-and-fix-safety.md)
93
+ - [Preset selection strategy](./guides/preset-selection-strategy.md)