eslint-config-typed 5.7.0 → 5.8.0
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.
- package/dist/plugins/total-functions/rules/no-partial-division.mjs +2 -2
- package/dist/plugins/total-functions/rules/no-partial-division.mjs.map +1 -1
- package/dist/plugins/ts-restrictions/rules/prefer-curried-call.d.mts +22 -0
- package/dist/plugins/ts-restrictions/rules/prefer-curried-call.d.mts.map +1 -0
- package/dist/plugins/ts-restrictions/rules/prefer-curried-call.mjs +284 -0
- package/dist/plugins/ts-restrictions/rules/prefer-curried-call.mjs.map +1 -0
- package/dist/plugins/ts-restrictions/rules/rules.d.mts +1 -0
- package/dist/plugins/ts-restrictions/rules/rules.d.mts.map +1 -1
- package/dist/plugins/ts-restrictions/rules/rules.mjs +2 -0
- package/dist/plugins/ts-restrictions/rules/rules.mjs.map +1 -1
- package/dist/rules/eslint-ts-restrictions-rules.d.mts +1 -0
- package/dist/rules/eslint-ts-restrictions-rules.d.mts.map +1 -1
- package/dist/rules/eslint-ts-restrictions-rules.mjs +1 -0
- package/dist/rules/eslint-ts-restrictions-rules.mjs.map +1 -1
- package/dist/types/rules/eslint-ts-restrictions-rules.d.mts +15 -0
- package/dist/types/rules/eslint-ts-restrictions-rules.d.mts.map +1 -1
- package/package.json +1 -1
- package/src/plugins/total-functions/rules/no-partial-division.mts +2 -2
- package/src/plugins/ts-restrictions/rules/prefer-curried-call.mts +393 -0
- package/src/plugins/ts-restrictions/rules/prefer-curried-call.test.mts +213 -0
- package/src/plugins/ts-restrictions/rules/rules.mts +2 -0
- package/src/rules/eslint-ts-restrictions-rules.mts +1 -0
- package/src/types/rules/eslint-ts-restrictions-rules.mts +16 -0
|
@@ -22,10 +22,10 @@ const noPartialDivision = createRule({
|
|
|
22
22
|
if (type.isIntersection()) {
|
|
23
23
|
const numberLiteralParts = intersectionTypeParts(type).filter((t) => isBigIntLiteral(t) || t.isNumberLiteral());
|
|
24
24
|
return (numberLiteralParts.length > 0 &&
|
|
25
|
-
numberLiteralParts.every(
|
|
25
|
+
numberLiteralParts.every(isSafeDenominator));
|
|
26
26
|
}
|
|
27
27
|
if (type.isUnion()) {
|
|
28
|
-
return unionTypeParts(type).every(
|
|
28
|
+
return unionTypeParts(type).every(isSafeDenominator);
|
|
29
29
|
}
|
|
30
30
|
return ((type.isNumberLiteral() && type.value !== 0) ||
|
|
31
31
|
(isBigIntLiteral(type) && type.value.base10Value !== '0'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-partial-division.mjs","sources":["../../../../src/plugins/total-functions/rules/no-partial-division.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;AASA;AAEO,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAC1C,IAAA,IAAI,EAAE,qBAAqB;AAC3B,IAAA,IAAI,EAAE;AACJ,QAAA,IAAI,EAAE,SAAS;AACf,QAAA,IAAI,EAAE;AACJ,YAAA,WAAW,EAAE,wBAAwB;AACtC,SAAA;AACD,QAAA,QAAQ,EAAE;AACR,YAAA,kBAAkB,EAChB,2GAA2G;AAC9G,SAAA;AACD,QAAA,MAAM,EAAE,EAAE;AACX,KAAA;AACD,IAAA,MAAM,EAAE,CAAC,OAAO,KAAI;QAClB,MAAM,cAAc,GAAG,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC;QAE7D,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,cAAc,EAAE;AAEvD,QAAA,MAAM,iBAAiB,GAAG,CAAC,IAAU,KAAa;AAChD,YAAA,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE;gBACzB,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,MAAM,CAC3D,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,CACjD;AAED,gBAAA,QACE,kBAAkB,CAAC,MAAM,GAAG,CAAC;AAC7B,oBAAA,kBAAkB,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"no-partial-division.mjs","sources":["../../../../src/plugins/total-functions/rules/no-partial-division.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;AASA;AAEO,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAC1C,IAAA,IAAI,EAAE,qBAAqB;AAC3B,IAAA,IAAI,EAAE;AACJ,QAAA,IAAI,EAAE,SAAS;AACf,QAAA,IAAI,EAAE;AACJ,YAAA,WAAW,EAAE,wBAAwB;AACtC,SAAA;AACD,QAAA,QAAQ,EAAE;AACR,YAAA,kBAAkB,EAChB,2GAA2G;AAC9G,SAAA;AACD,QAAA,MAAM,EAAE,EAAE;AACX,KAAA;AACD,IAAA,MAAM,EAAE,CAAC,OAAO,KAAI;QAClB,MAAM,cAAc,GAAG,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC;QAE7D,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,cAAc,EAAE;AAEvD,QAAA,MAAM,iBAAiB,GAAG,CAAC,IAAU,KAAa;AAChD,YAAA,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE;gBACzB,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,MAAM,CAC3D,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,CACjD;AAED,gBAAA,QACE,kBAAkB,CAAC,MAAM,GAAG,CAAC;AAC7B,oBAAA,kBAAkB,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAE/C;AAEA,YAAA,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE;gBAClB,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;YACtD;AAEA,YAAA,QACE,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;AAC3C,iBAAC,eAAe,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,KAAK,GAAG,CAAC;AAE7D,QAAA,CAAC;QAED,OAAO;AACL,YAAA,gBAAgB,EAAE,CAAC,IAAI,KAAI;AACzB,gBAAA,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG,EAAE;;oBAEzB;gBACF;gBAEA,IACE,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,OAAO;AAC1C,oBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC;AACtB,oBAAA,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,EACvB;;oBAEA;gBACF;gBAEA,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,UAAU,EAAE;AACjD,oBAAA,MAAM,eAAe,GAAG,cAAc,CAAC,qBAAqB,CAAC,GAAG,CAC9D,IAAI,CAAC,KAAK,CACX;oBAED,MAAM,mBAAmB,GACvB,OAAO,CAAC,iBAAiB,CAAC,eAAe,CAAC;AAE5C,oBAAA,IAAI,iBAAiB,CAAC,mBAAmB,CAAC,EAAE;wBAC1C;oBACF;gBACF;;gBAIA,OAAO,CAAC,MAAM,CAAC;oBACb,IAAI;AACJ,oBAAA,SAAS,EAAE,oBAAoB;AACvB,iBAAA,CAAC;YACb,CAAC;SACF;IACH,CAAC;AACD,IAAA,cAAc,EAAE,EAAE;AACV,CAAA;AAEV;AACA;AACA;AACA,MAAM,cAAc,GAAG,CAAC,GAAY,KAAyB;IAC3D,MAAM,iBAAiB,GACrB,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK;AACjC,UAAG;UACD,SAAS;IAEf,QACE,iBAAiB,KAAK,SAAS;AAC/B,QAAA,OAAO,iBAAiB,CAAC,WAAW,KAAK,QAAQ;AACjD,QAAA,OAAO,iBAAiB,CAAC,QAAQ,KAAK,SAAS;AAEnD,CAAC;AAED,MAAM,eAAe,GAAG,CAAC,IAAU;AACjC;AACA,cAAc,CAAE,IAAmC,EAAE,KAAK,CAAC;;;;"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = readonly [];
|
|
3
|
+
type MessageIds = 'useFunctionDirectly' | 'useCurriedForm';
|
|
4
|
+
/**
|
|
5
|
+
* Replaces a redundant arrow wrapper around a function call with a point-free
|
|
6
|
+
* form, using the callee's curried version when one exists:
|
|
7
|
+
*
|
|
8
|
+
* - `(a) => f(a)` → `f` — only when `f` is effectively unary, so passing it
|
|
9
|
+
* directly cannot change behavior even when the wrapper is used as a
|
|
10
|
+
* multi-argument callback (`arr.map((a) => f(a))` → `arr.map(f)`). This is
|
|
11
|
+
* what keeps the classic `arr.map((s) => parseInt(s))` case (where `parseInt`
|
|
12
|
+
* also reads the index) from being rewritten.
|
|
13
|
+
* - `(a) => f(a, ...rest)` → `f(...rest)` — only when `f` has a curried call
|
|
14
|
+
* signature accepting `...rest` and returning a one-argument function, i.e.
|
|
15
|
+
* a genuine curried version (`(a) => toPushed(a, x)` → `toPushed(x)`).
|
|
16
|
+
*
|
|
17
|
+
* The arrow's single parameter must appear exactly once, as the first argument
|
|
18
|
+
* of the call, so dropping the wrapper is behavior-preserving.
|
|
19
|
+
*/
|
|
20
|
+
export declare const preferCurriedCall: TSESLint.RuleModule<MessageIds, Options>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=prefer-curried-call.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-curried-call.d.mts","sourceRoot":"","sources":["../../../../src/plugins/ts-restrictions/rules/prefer-curried-call.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAGlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,qBAAqB,GAAG,gBAAgB,CAAC;AAE3D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CA+L7D,CAAC"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
+
import * as ts from 'typescript';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Replaces a redundant arrow wrapper around a function call with a point-free
|
|
6
|
+
* form, using the callee's curried version when one exists:
|
|
7
|
+
*
|
|
8
|
+
* - `(a) => f(a)` → `f` — only when `f` is effectively unary, so passing it
|
|
9
|
+
* directly cannot change behavior even when the wrapper is used as a
|
|
10
|
+
* multi-argument callback (`arr.map((a) => f(a))` → `arr.map(f)`). This is
|
|
11
|
+
* what keeps the classic `arr.map((s) => parseInt(s))` case (where `parseInt`
|
|
12
|
+
* also reads the index) from being rewritten.
|
|
13
|
+
* - `(a) => f(a, ...rest)` → `f(...rest)` — only when `f` has a curried call
|
|
14
|
+
* signature accepting `...rest` and returning a one-argument function, i.e.
|
|
15
|
+
* a genuine curried version (`(a) => toPushed(a, x)` → `toPushed(x)`).
|
|
16
|
+
*
|
|
17
|
+
* The arrow's single parameter must appear exactly once, as the first argument
|
|
18
|
+
* of the call, so dropping the wrapper is behavior-preserving.
|
|
19
|
+
*/
|
|
20
|
+
const preferCurriedCall = {
|
|
21
|
+
meta: {
|
|
22
|
+
type: 'suggestion',
|
|
23
|
+
docs: {
|
|
24
|
+
description: 'Replace a redundant arrow wrapper with the function itself (`(a) => f(a)` → `f`) or, when the callee has a curried version, its curried form (`(a) => f(a, ...rest)` → `f(...rest)`).',
|
|
25
|
+
},
|
|
26
|
+
fixable: 'code',
|
|
27
|
+
schema: [],
|
|
28
|
+
messages: {
|
|
29
|
+
useFunctionDirectly: 'Replace the redundant arrow wrapper `{{original}}` with `{{replacement}}`.',
|
|
30
|
+
useCurriedForm: 'Replace the arrow wrapper `{{original}}` with the curried call `{{replacement}}`.',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
create: (context) => {
|
|
34
|
+
const sourceCode = context.sourceCode;
|
|
35
|
+
const services = ESLintUtils.getParserServices(context);
|
|
36
|
+
const checker = services.program.getTypeChecker();
|
|
37
|
+
const getType = (
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
39
|
+
node) => checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(node));
|
|
40
|
+
/**
|
|
41
|
+
* `true` when `node` refers to a module/namespace object (e.g. a namespace
|
|
42
|
+
* imported as `Arr`). Referencing `Namespace.fn` as a value is safe because
|
|
43
|
+
* namespace members do not depend on a dynamic `this`, unlike instance
|
|
44
|
+
* methods (`obj.method`), which would lose their receiver.
|
|
45
|
+
*/
|
|
46
|
+
const isNamespaceObject = (
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
48
|
+
node) => {
|
|
49
|
+
const symbol = checker.getSymbolAtLocation(services.esTreeNodeToTSNodeMap.get(node));
|
|
50
|
+
if (symbol === undefined)
|
|
51
|
+
return false;
|
|
52
|
+
const resolved = (symbol.flags & ts.SymbolFlags.Alias) !== 0
|
|
53
|
+
? checker.getAliasedSymbol(symbol)
|
|
54
|
+
: symbol;
|
|
55
|
+
return ((resolved.flags &
|
|
56
|
+
(ts.SymbolFlags.ValueModule | ts.SymbolFlags.NamespaceModule)) !==
|
|
57
|
+
0);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* For the eta reduction `(a) => f(a)` → `f`, the callee must be safe to
|
|
61
|
+
* reference as a bare value: a plain identifier (a standalone function) or
|
|
62
|
+
* a namespace member (`Arr.isNonEmpty`). Instance-method callees are
|
|
63
|
+
* rejected because the point-free form would drop their `this`.
|
|
64
|
+
*/
|
|
65
|
+
const isEtaSafeCallee = (
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
67
|
+
callee) => {
|
|
68
|
+
if (callee.type === AST_NODE_TYPES.Identifier)
|
|
69
|
+
return true;
|
|
70
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression) {
|
|
71
|
+
return !callee.optional && isNamespaceObject(callee.object);
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* `true` when the arrow's single parameter is referenced exactly once, at
|
|
77
|
+
* `argument` — so it appears neither in the callee nor in any other
|
|
78
|
+
* argument, and dropping the wrapper cannot change behavior.
|
|
79
|
+
*/
|
|
80
|
+
const paramUsedOnlyAt = (
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
82
|
+
node, paramName,
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
84
|
+
argument) => {
|
|
85
|
+
const scope = sourceCode.getScope(node);
|
|
86
|
+
const variable = scope.variables.find((v) => v.name === paramName);
|
|
87
|
+
if (variable === undefined)
|
|
88
|
+
return false;
|
|
89
|
+
const [reference] = variable.references;
|
|
90
|
+
return (variable.references.length === 1 && reference?.identifier === argument);
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
ArrowFunctionExpression: (node) => {
|
|
94
|
+
if (node.async || node.params.length !== 1)
|
|
95
|
+
return;
|
|
96
|
+
const [param] = node.params;
|
|
97
|
+
if (param?.type !== AST_NODE_TYPES.Identifier ||
|
|
98
|
+
param.typeAnnotation !== undefined) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (node.body.type !== AST_NODE_TYPES.CallExpression)
|
|
102
|
+
return;
|
|
103
|
+
const call = node.body;
|
|
104
|
+
if (call.optional)
|
|
105
|
+
return;
|
|
106
|
+
const [firstArg, ...restArgs] = call.arguments;
|
|
107
|
+
if (firstArg?.type !== AST_NODE_TYPES.Identifier ||
|
|
108
|
+
firstArg.name !== param.name) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// A spread in the remaining arguments breaks positional arg counting.
|
|
112
|
+
if (restArgs.some((arg) => arg.type === AST_NODE_TYPES.SpreadElement)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!paramUsedOnlyAt(node, param.name, firstArg))
|
|
116
|
+
return;
|
|
117
|
+
const calleeType = getType(call.callee);
|
|
118
|
+
const calleeText = sourceCode.getText(call.callee);
|
|
119
|
+
if (restArgs.length === 0) {
|
|
120
|
+
// `(a) => f(a)` → `f`
|
|
121
|
+
if (!isEtaSafeCallee(call.callee) ||
|
|
122
|
+
!isEffectivelyUnary(calleeType)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
context.report({
|
|
126
|
+
node,
|
|
127
|
+
messageId: 'useFunctionDirectly',
|
|
128
|
+
data: {
|
|
129
|
+
original: sourceCode.getText(node),
|
|
130
|
+
replacement: calleeText,
|
|
131
|
+
},
|
|
132
|
+
fix: (fixer) => fixer.replaceText(node, calleeText),
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// `(a) => f(a, ...rest)` → `f(...rest)`
|
|
137
|
+
// Re-evaluating a side-effecting argument once (at currying time)
|
|
138
|
+
// instead of on every call would change behavior, so require the
|
|
139
|
+
// remaining arguments to be free of observable side effects.
|
|
140
|
+
if (!restArgs.every(isPureExpression) ||
|
|
141
|
+
!hasCurriedSignature(calleeType, restArgs.length)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const replacement = `${calleeText}(${restArgs
|
|
145
|
+
.map((arg) => sourceCode.getText(arg))
|
|
146
|
+
.join(', ')})`;
|
|
147
|
+
context.report({
|
|
148
|
+
node,
|
|
149
|
+
messageId: 'useCurriedForm',
|
|
150
|
+
data: {
|
|
151
|
+
original: sourceCode.getText(node),
|
|
152
|
+
replacement,
|
|
153
|
+
},
|
|
154
|
+
fix: (fixer) => fixer.replaceText(node, replacement),
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
defaultOptions: [],
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* `true` when every call signature of `type` accepts at most one argument, so
|
|
163
|
+
* `(a) => f(a)` is equivalent to `f` even when the wrapper is used as a
|
|
164
|
+
* multi-argument callback (the extra positional arguments are ignored either
|
|
165
|
+
* way). Requires at least one call signature.
|
|
166
|
+
*/
|
|
167
|
+
const isEffectivelyUnary = (
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
169
|
+
type) => {
|
|
170
|
+
const signatures = type.getCallSignatures();
|
|
171
|
+
return (signatures.length > 0 &&
|
|
172
|
+
signatures.every((signature) => signatureMaxArgs(signature) <= 1));
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* `true` when `type` has a curried call signature accepting exactly
|
|
176
|
+
* `restCount` arguments and returning a function that accepts one argument —
|
|
177
|
+
* i.e. `f(...rest)` is the curried form of `f(a, ...rest)`.
|
|
178
|
+
*/
|
|
179
|
+
const hasCurriedSignature = (
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
181
|
+
type, restCount) => type.getCallSignatures().some((signature) => {
|
|
182
|
+
if (!isCallableWith(signature, restCount))
|
|
183
|
+
return false;
|
|
184
|
+
return signature
|
|
185
|
+
.getReturnType()
|
|
186
|
+
.getCallSignatures()
|
|
187
|
+
.some((inner) => isCallableWith(inner, 1));
|
|
188
|
+
});
|
|
189
|
+
const isCallableWith = (
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
191
|
+
signature, argCount) => signatureMinArgs(signature) <= argCount &&
|
|
192
|
+
argCount <= signatureMaxArgs(signature);
|
|
193
|
+
/** The largest number of positional arguments the signature accepts. */
|
|
194
|
+
const signatureMaxArgs = (
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
196
|
+
signature) => {
|
|
197
|
+
const params = signature.getParameters();
|
|
198
|
+
const last = params.at(-1);
|
|
199
|
+
return last !== undefined && isRestParameter(last)
|
|
200
|
+
? Number.POSITIVE_INFINITY
|
|
201
|
+
: params.length;
|
|
202
|
+
};
|
|
203
|
+
/** The number of leading required parameters (before any optional/rest). */
|
|
204
|
+
const signatureMinArgs = (
|
|
205
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
206
|
+
signature) => {
|
|
207
|
+
const firstOptional = signature
|
|
208
|
+
.getParameters()
|
|
209
|
+
.findIndex(isOptionalParameter);
|
|
210
|
+
return firstOptional === -1
|
|
211
|
+
? signature.getParameters().length
|
|
212
|
+
: firstOptional;
|
|
213
|
+
};
|
|
214
|
+
/** A parameter that collects the rest of the arguments (`...args`). */
|
|
215
|
+
const isRestParameter = (
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
217
|
+
symbol) => {
|
|
218
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
219
|
+
return (declaration !== undefined &&
|
|
220
|
+
ts.isParameter(declaration) &&
|
|
221
|
+
declaration.dotDotDotToken !== undefined);
|
|
222
|
+
};
|
|
223
|
+
/** A parameter that may be omitted (`x?`, `x = ...`) or is a rest parameter. */
|
|
224
|
+
const isOptionalParameter = (
|
|
225
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
226
|
+
symbol) => {
|
|
227
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
228
|
+
return (declaration !== undefined &&
|
|
229
|
+
ts.isParameter(declaration) &&
|
|
230
|
+
(declaration.questionToken !== undefined ||
|
|
231
|
+
declaration.initializer !== undefined ||
|
|
232
|
+
declaration.dotDotDotToken !== undefined));
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* A conservative purity approximation: `true` only for expressions whose
|
|
236
|
+
* evaluation has no observable side effects and does not depend on evaluation
|
|
237
|
+
* count (so hoisting it out of the wrapper is safe). Anything not recognized
|
|
238
|
+
* (calls, `new`, `await`, assignments, updates) is treated as impure.
|
|
239
|
+
*/
|
|
240
|
+
const isPureExpression = (
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
242
|
+
node) => {
|
|
243
|
+
if (node.type === AST_NODE_TYPES.Literal ||
|
|
244
|
+
node.type === AST_NODE_TYPES.Identifier ||
|
|
245
|
+
node.type === AST_NODE_TYPES.ThisExpression ||
|
|
246
|
+
node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
247
|
+
node.type === AST_NODE_TYPES.FunctionExpression) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
if (node.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
251
|
+
return node.expressions.every(isPureExpression);
|
|
252
|
+
}
|
|
253
|
+
if (node.type === AST_NODE_TYPES.MemberExpression) {
|
|
254
|
+
return node.computed
|
|
255
|
+
? isPureExpression(node.object) && isPureExpression(node.property)
|
|
256
|
+
: isPureExpression(node.object);
|
|
257
|
+
}
|
|
258
|
+
if (node.type === AST_NODE_TYPES.UnaryExpression) {
|
|
259
|
+
return node.operator !== 'delete' && isPureExpression(node.argument);
|
|
260
|
+
}
|
|
261
|
+
if (node.type === AST_NODE_TYPES.BinaryExpression ||
|
|
262
|
+
node.type === AST_NODE_TYPES.LogicalExpression) {
|
|
263
|
+
return isPureExpression(node.left) && isPureExpression(node.right);
|
|
264
|
+
}
|
|
265
|
+
if (node.type === AST_NODE_TYPES.ConditionalExpression) {
|
|
266
|
+
return (isPureExpression(node.test) &&
|
|
267
|
+
isPureExpression(node.consequent) &&
|
|
268
|
+
isPureExpression(node.alternate));
|
|
269
|
+
}
|
|
270
|
+
if (node.type === AST_NODE_TYPES.ArrayExpression) {
|
|
271
|
+
return node.elements.every((element) => element === null ||
|
|
272
|
+
(element.type !== AST_NODE_TYPES.SpreadElement &&
|
|
273
|
+
isPureExpression(element)));
|
|
274
|
+
}
|
|
275
|
+
if (node.type === AST_NODE_TYPES.ObjectExpression) {
|
|
276
|
+
return node.properties.every((property) => property.type === AST_NODE_TYPES.Property &&
|
|
277
|
+
!property.computed &&
|
|
278
|
+
isPureExpression(property.value));
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export { preferCurriedCall };
|
|
284
|
+
//# sourceMappingURL=prefer-curried-call.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-curried-call.mjs","sources":["../../../../src/plugins/ts-restrictions/rules/prefer-curried-call.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@ export declare const tsRestrictionsRules: {
|
|
|
17
17
|
readonly 'no-string-spread': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noStringSpread", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
18
18
|
readonly 'no-unnecessary-array-from': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnecessaryArrayFrom", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
19
19
|
readonly 'no-unnecessary-coalesce-undefined': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnecessaryCoalesceUndefined", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
20
|
+
readonly 'prefer-curried-call': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useFunctionDirectly" | "useCurriedForm", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
20
21
|
readonly 'prefer-non-mutating-array-method': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferNonMutatingMethod", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
21
22
|
};
|
|
22
23
|
//# sourceMappingURL=rules.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../../../src/plugins/ts-restrictions/rules/rules.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../../../src/plugins/ts-restrictions/rules/rules.mts"],"names":[],"mappings":"AAUA,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;CASU,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { noRestrictedSyntax } from './no-restricted-syntax.mjs';
|
|
|
4
4
|
import { noStringSpread } from './no-string-spread.mjs';
|
|
5
5
|
import { noUnnecessaryArrayFrom } from './no-unnecessary-array-from.mjs';
|
|
6
6
|
import { noUnnecessaryCoalesceUndefined } from './no-unnecessary-coalesce-undefined.mjs';
|
|
7
|
+
import { preferCurriedCall } from './prefer-curried-call.mjs';
|
|
7
8
|
import { preferNonMutatingArrayMethod } from './prefer-non-mutating-array-method.mjs';
|
|
8
9
|
|
|
9
10
|
const tsRestrictionsRules = {
|
|
@@ -13,6 +14,7 @@ const tsRestrictionsRules = {
|
|
|
13
14
|
'no-string-spread': noStringSpread,
|
|
14
15
|
'no-unnecessary-array-from': noUnnecessaryArrayFrom,
|
|
15
16
|
'no-unnecessary-coalesce-undefined': noUnnecessaryCoalesceUndefined,
|
|
17
|
+
'prefer-curried-call': preferCurriedCall,
|
|
16
18
|
'prefer-non-mutating-array-method': preferNonMutatingArrayMethod,
|
|
17
19
|
};
|
|
18
20
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.mjs","sources":["../../../../src/plugins/ts-restrictions/rules/rules.mts"],"sourcesContent":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rules.mjs","sources":["../../../../src/plugins/ts-restrictions/rules/rules.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;AAUO,MAAM,mBAAmB,GAAG;AACjC,IAAA,kCAAkC,EAAE,8BAA8B;AAClE,IAAA,yBAAyB,EAAE,oBAAoB;AAC/C,IAAA,sBAAsB,EAAE,kBAAkB;AAC1C,IAAA,kBAAkB,EAAE,cAAc;AAClC,IAAA,2BAA2B,EAAE,sBAAsB;AACnD,IAAA,mCAAmC,EAAE,8BAA8B;AACnE,IAAA,qBAAqB,EAAE,iBAAiB;AACxC,IAAA,kCAAkC,EAAE,4BAA4B;;;;;"}
|
|
@@ -4,6 +4,7 @@ export declare const eslintTsRestrictionsRules: {
|
|
|
4
4
|
readonly 'ts-restrictions/no-string-spread': "error";
|
|
5
5
|
readonly 'ts-restrictions/no-unnecessary-array-from': "error";
|
|
6
6
|
readonly 'ts-restrictions/no-unnecessary-coalesce-undefined': "error";
|
|
7
|
+
readonly 'ts-restrictions/prefer-curried-call': "error";
|
|
7
8
|
readonly 'ts-restrictions/prefer-non-mutating-array-method': "error";
|
|
8
9
|
readonly 'ts-restrictions/check-destructuring-completeness': 2 | 1;
|
|
9
10
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eslint-ts-restrictions-rules.d.mts","sourceRoot":"","sources":["../../src/rules/eslint-ts-restrictions-rules.mts"],"names":[],"mappings":"AAKA,eAAO,MAAM,yBAAyB
|
|
1
|
+
{"version":3,"file":"eslint-ts-restrictions-rules.d.mts","sourceRoot":"","sources":["../../src/rules/eslint-ts-restrictions-rules.mts"],"names":[],"mappings":"AAKA,eAAO,MAAM,yBAAyB;;;;;;;;;CAUQ,CAAC"}
|
|
@@ -6,6 +6,7 @@ const eslintTsRestrictionsRules = {
|
|
|
6
6
|
'ts-restrictions/no-string-spread': 'error',
|
|
7
7
|
'ts-restrictions/no-unnecessary-array-from': 'error',
|
|
8
8
|
'ts-restrictions/no-unnecessary-coalesce-undefined': 'error',
|
|
9
|
+
'ts-restrictions/prefer-curried-call': 'error',
|
|
9
10
|
'ts-restrictions/prefer-non-mutating-array-method': 'error',
|
|
10
11
|
'ts-restrictions/check-destructuring-completeness': withDefaultOption('error'),
|
|
11
12
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eslint-ts-restrictions-rules.mjs","sources":["../../src/rules/eslint-ts-restrictions-rules.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAKO,MAAM,yBAAyB,GAAG;AACvC,IAAA,sCAAsC,EAAE,KAAK;AAC7C,IAAA,yCAAyC,EAAE,KAAK;AAChD,IAAA,kCAAkC,EAAE,OAAO;AAC3C,IAAA,2CAA2C,EAAE,OAAO;AACpD,IAAA,mDAAmD,EAAE,OAAO;AAC5D,IAAA,kDAAkD,EAAE,OAAO;AAC3D,IAAA,kDAAkD,EAChD,iBAAiB,CAAC,OAAO,CAAC;;;;;"}
|
|
1
|
+
{"version":3,"file":"eslint-ts-restrictions-rules.mjs","sources":["../../src/rules/eslint-ts-restrictions-rules.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAKO,MAAM,yBAAyB,GAAG;AACvC,IAAA,sCAAsC,EAAE,KAAK;AAC7C,IAAA,yCAAyC,EAAE,KAAK;AAChD,IAAA,kCAAkC,EAAE,OAAO;AAC3C,IAAA,2CAA2C,EAAE,OAAO;AACpD,IAAA,mDAAmD,EAAE,OAAO;AAC5D,IAAA,qCAAqC,EAAE,OAAO;AAC9C,IAAA,kDAAkD,EAAE,OAAO;AAC3D,IAAA,kDAAkD,EAChD,iBAAiB,CAAC,OAAO,CAAC;;;;;"}
|
|
@@ -245,6 +245,20 @@ declare namespace NoUnnecessaryArrayFrom {
|
|
|
245
245
|
declare namespace NoUnnecessaryCoalesceUndefined {
|
|
246
246
|
type RuleEntry = Linter.StringSeverity;
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* @description Replace a redundant arrow wrapper with the function itself (`(a) => f(a)` → `f`) or, when the callee has a curried version, its curried form (`(a) => f(a, ...rest)` → `f(...rest)`).
|
|
250
|
+
*
|
|
251
|
+
* ```md
|
|
252
|
+
* | key | value |
|
|
253
|
+
* | :--------- | :--------- |
|
|
254
|
+
* | type | suggestion |
|
|
255
|
+
* | deprecated | false |
|
|
256
|
+
* | fixable | code |
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
declare namespace PreferCurriedCall {
|
|
260
|
+
type RuleEntry = Linter.StringSeverity;
|
|
261
|
+
}
|
|
248
262
|
/**
|
|
249
263
|
* @description Disallow calling a mutating array method on a defensive `Array.from()` copy of an array (e.g. `Array.from(x).sort()`); use the non-mutating counterpart on the original array instead (e.g. `x.toSorted()`)
|
|
250
264
|
*
|
|
@@ -266,6 +280,7 @@ export type EslintTsRestrictionsRules = Readonly<{
|
|
|
266
280
|
'ts-restrictions/no-string-spread': NoStringSpread.RuleEntry;
|
|
267
281
|
'ts-restrictions/no-unnecessary-array-from': NoUnnecessaryArrayFrom.RuleEntry;
|
|
268
282
|
'ts-restrictions/no-unnecessary-coalesce-undefined': NoUnnecessaryCoalesceUndefined.RuleEntry;
|
|
283
|
+
'ts-restrictions/prefer-curried-call': PreferCurriedCall.RuleEntry;
|
|
269
284
|
'ts-restrictions/prefer-non-mutating-array-method': PreferNonMutatingArrayMethod.RuleEntry;
|
|
270
285
|
}>;
|
|
271
286
|
export type EslintTsRestrictionsRulesOption = Readonly<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eslint-ts-restrictions-rules.d.mts","sourceRoot":"","sources":["../../../src/types/rules/eslint-ts-restrictions-rules.mts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"eslint-ts-restrictions-rules.d.mts","sourceRoot":"","sources":["../../../src/types/rules/eslint-ts-restrictions-rules.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -35,12 +35,12 @@ export const noPartialDivision = createRule({
|
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
numberLiteralParts.length > 0 &&
|
|
38
|
-
numberLiteralParts.every(
|
|
38
|
+
numberLiteralParts.every(isSafeDenominator)
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (type.isUnion()) {
|
|
43
|
-
return unionTypeParts(type).every(
|
|
43
|
+
return unionTypeParts(type).every(isSafeDenominator);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
return (
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
ESLintUtils,
|
|
4
|
+
type TSESLint,
|
|
5
|
+
type TSESTree,
|
|
6
|
+
} from '@typescript-eslint/utils';
|
|
7
|
+
import * as ts from 'typescript';
|
|
8
|
+
|
|
9
|
+
type Options = readonly [];
|
|
10
|
+
|
|
11
|
+
type MessageIds = 'useFunctionDirectly' | 'useCurriedForm';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replaces a redundant arrow wrapper around a function call with a point-free
|
|
15
|
+
* form, using the callee's curried version when one exists:
|
|
16
|
+
*
|
|
17
|
+
* - `(a) => f(a)` → `f` — only when `f` is effectively unary, so passing it
|
|
18
|
+
* directly cannot change behavior even when the wrapper is used as a
|
|
19
|
+
* multi-argument callback (`arr.map((a) => f(a))` → `arr.map(f)`). This is
|
|
20
|
+
* what keeps the classic `arr.map((s) => parseInt(s))` case (where `parseInt`
|
|
21
|
+
* also reads the index) from being rewritten.
|
|
22
|
+
* - `(a) => f(a, ...rest)` → `f(...rest)` — only when `f` has a curried call
|
|
23
|
+
* signature accepting `...rest` and returning a one-argument function, i.e.
|
|
24
|
+
* a genuine curried version (`(a) => toPushed(a, x)` → `toPushed(x)`).
|
|
25
|
+
*
|
|
26
|
+
* The arrow's single parameter must appear exactly once, as the first argument
|
|
27
|
+
* of the call, so dropping the wrapper is behavior-preserving.
|
|
28
|
+
*/
|
|
29
|
+
export const preferCurriedCall: TSESLint.RuleModule<MessageIds, Options> = {
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'suggestion',
|
|
32
|
+
docs: {
|
|
33
|
+
description:
|
|
34
|
+
'Replace a redundant arrow wrapper with the function itself (`(a) => f(a)` → `f`) or, when the callee has a curried version, its curried form (`(a) => f(a, ...rest)` → `f(...rest)`).',
|
|
35
|
+
},
|
|
36
|
+
fixable: 'code',
|
|
37
|
+
schema: [],
|
|
38
|
+
messages: {
|
|
39
|
+
useFunctionDirectly:
|
|
40
|
+
'Replace the redundant arrow wrapper `{{original}}` with `{{replacement}}`.',
|
|
41
|
+
useCurriedForm:
|
|
42
|
+
'Replace the arrow wrapper `{{original}}` with the curried call `{{replacement}}`.',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
create: (context) => {
|
|
47
|
+
const sourceCode = context.sourceCode;
|
|
48
|
+
|
|
49
|
+
const services = ESLintUtils.getParserServices(context);
|
|
50
|
+
|
|
51
|
+
const checker = services.program.getTypeChecker();
|
|
52
|
+
|
|
53
|
+
const getType = (
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
55
|
+
node: TSESTree.Node,
|
|
56
|
+
): ts.Type =>
|
|
57
|
+
checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(node));
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `true` when `node` refers to a module/namespace object (e.g. a namespace
|
|
61
|
+
* imported as `Arr`). Referencing `Namespace.fn` as a value is safe because
|
|
62
|
+
* namespace members do not depend on a dynamic `this`, unlike instance
|
|
63
|
+
* methods (`obj.method`), which would lose their receiver.
|
|
64
|
+
*/
|
|
65
|
+
const isNamespaceObject = (
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
67
|
+
node: TSESTree.Node,
|
|
68
|
+
): boolean => {
|
|
69
|
+
const symbol = checker.getSymbolAtLocation(
|
|
70
|
+
services.esTreeNodeToTSNodeMap.get(node),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (symbol === undefined) return false;
|
|
74
|
+
|
|
75
|
+
const resolved =
|
|
76
|
+
(symbol.flags & ts.SymbolFlags.Alias) !== 0
|
|
77
|
+
? checker.getAliasedSymbol(symbol)
|
|
78
|
+
: symbol;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
(resolved.flags &
|
|
82
|
+
(ts.SymbolFlags.ValueModule | ts.SymbolFlags.NamespaceModule)) !==
|
|
83
|
+
0
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* For the eta reduction `(a) => f(a)` → `f`, the callee must be safe to
|
|
89
|
+
* reference as a bare value: a plain identifier (a standalone function) or
|
|
90
|
+
* a namespace member (`Arr.isNonEmpty`). Instance-method callees are
|
|
91
|
+
* rejected because the point-free form would drop their `this`.
|
|
92
|
+
*/
|
|
93
|
+
const isEtaSafeCallee = (
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
95
|
+
callee: TSESTree.Expression,
|
|
96
|
+
): boolean => {
|
|
97
|
+
if (callee.type === AST_NODE_TYPES.Identifier) return true;
|
|
98
|
+
|
|
99
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression) {
|
|
100
|
+
return !callee.optional && isNamespaceObject(callee.object);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* `true` when the arrow's single parameter is referenced exactly once, at
|
|
108
|
+
* `argument` — so it appears neither in the callee nor in any other
|
|
109
|
+
* argument, and dropping the wrapper cannot change behavior.
|
|
110
|
+
*/
|
|
111
|
+
const paramUsedOnlyAt = (
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
113
|
+
node: TSESTree.ArrowFunctionExpression,
|
|
114
|
+
paramName: string,
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
116
|
+
argument: TSESTree.Node,
|
|
117
|
+
): boolean => {
|
|
118
|
+
const scope = sourceCode.getScope(node);
|
|
119
|
+
|
|
120
|
+
const variable = scope.variables.find((v) => v.name === paramName);
|
|
121
|
+
|
|
122
|
+
if (variable === undefined) return false;
|
|
123
|
+
|
|
124
|
+
const [reference] = variable.references;
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
variable.references.length === 1 && reference?.identifier === argument
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
ArrowFunctionExpression: (node) => {
|
|
133
|
+
if (node.async || node.params.length !== 1) return;
|
|
134
|
+
|
|
135
|
+
const [param] = node.params;
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
param?.type !== AST_NODE_TYPES.Identifier ||
|
|
139
|
+
param.typeAnnotation !== undefined
|
|
140
|
+
) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (node.body.type !== AST_NODE_TYPES.CallExpression) return;
|
|
145
|
+
|
|
146
|
+
const call = node.body;
|
|
147
|
+
|
|
148
|
+
if (call.optional) return;
|
|
149
|
+
|
|
150
|
+
const [firstArg, ...restArgs] = call.arguments;
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
firstArg?.type !== AST_NODE_TYPES.Identifier ||
|
|
154
|
+
firstArg.name !== param.name
|
|
155
|
+
) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A spread in the remaining arguments breaks positional arg counting.
|
|
160
|
+
if (restArgs.some((arg) => arg.type === AST_NODE_TYPES.SpreadElement)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!paramUsedOnlyAt(node, param.name, firstArg)) return;
|
|
165
|
+
|
|
166
|
+
const calleeType = getType(call.callee);
|
|
167
|
+
|
|
168
|
+
const calleeText = sourceCode.getText(call.callee);
|
|
169
|
+
|
|
170
|
+
if (restArgs.length === 0) {
|
|
171
|
+
// `(a) => f(a)` → `f`
|
|
172
|
+
if (
|
|
173
|
+
!isEtaSafeCallee(call.callee) ||
|
|
174
|
+
!isEffectivelyUnary(calleeType)
|
|
175
|
+
) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
context.report({
|
|
180
|
+
node,
|
|
181
|
+
messageId: 'useFunctionDirectly',
|
|
182
|
+
data: {
|
|
183
|
+
original: sourceCode.getText(node),
|
|
184
|
+
replacement: calleeText,
|
|
185
|
+
},
|
|
186
|
+
fix: (fixer) => fixer.replaceText(node, calleeText),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// `(a) => f(a, ...rest)` → `f(...rest)`
|
|
193
|
+
// Re-evaluating a side-effecting argument once (at currying time)
|
|
194
|
+
// instead of on every call would change behavior, so require the
|
|
195
|
+
// remaining arguments to be free of observable side effects.
|
|
196
|
+
if (
|
|
197
|
+
!restArgs.every(isPureExpression) ||
|
|
198
|
+
!hasCurriedSignature(calleeType, restArgs.length)
|
|
199
|
+
) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const replacement = `${calleeText}(${restArgs
|
|
204
|
+
.map((arg) => sourceCode.getText(arg))
|
|
205
|
+
.join(', ')})`;
|
|
206
|
+
|
|
207
|
+
context.report({
|
|
208
|
+
node,
|
|
209
|
+
messageId: 'useCurriedForm',
|
|
210
|
+
data: {
|
|
211
|
+
original: sourceCode.getText(node),
|
|
212
|
+
replacement,
|
|
213
|
+
},
|
|
214
|
+
fix: (fixer) => fixer.replaceText(node, replacement),
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
defaultOptions: [],
|
|
220
|
+
} as const;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* `true` when every call signature of `type` accepts at most one argument, so
|
|
224
|
+
* `(a) => f(a)` is equivalent to `f` even when the wrapper is used as a
|
|
225
|
+
* multi-argument callback (the extra positional arguments are ignored either
|
|
226
|
+
* way). Requires at least one call signature.
|
|
227
|
+
*/
|
|
228
|
+
const isEffectivelyUnary = (
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
230
|
+
type: ts.Type,
|
|
231
|
+
): boolean => {
|
|
232
|
+
const signatures = type.getCallSignatures();
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
signatures.length > 0 &&
|
|
236
|
+
signatures.every((signature) => signatureMaxArgs(signature) <= 1)
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* `true` when `type` has a curried call signature accepting exactly
|
|
242
|
+
* `restCount` arguments and returning a function that accepts one argument —
|
|
243
|
+
* i.e. `f(...rest)` is the curried form of `f(a, ...rest)`.
|
|
244
|
+
*/
|
|
245
|
+
const hasCurriedSignature = (
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
247
|
+
type: ts.Type,
|
|
248
|
+
restCount: number,
|
|
249
|
+
): boolean =>
|
|
250
|
+
type.getCallSignatures().some((signature) => {
|
|
251
|
+
if (!isCallableWith(signature, restCount)) return false;
|
|
252
|
+
|
|
253
|
+
return signature
|
|
254
|
+
.getReturnType()
|
|
255
|
+
.getCallSignatures()
|
|
256
|
+
.some((inner) => isCallableWith(inner, 1));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const isCallableWith = (
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
261
|
+
signature: ts.Signature,
|
|
262
|
+
argCount: number,
|
|
263
|
+
): boolean =>
|
|
264
|
+
signatureMinArgs(signature) <= argCount &&
|
|
265
|
+
argCount <= signatureMaxArgs(signature);
|
|
266
|
+
|
|
267
|
+
/** The largest number of positional arguments the signature accepts. */
|
|
268
|
+
const signatureMaxArgs = (
|
|
269
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
270
|
+
signature: ts.Signature,
|
|
271
|
+
): number => {
|
|
272
|
+
const params = signature.getParameters();
|
|
273
|
+
|
|
274
|
+
const last = params.at(-1);
|
|
275
|
+
|
|
276
|
+
return last !== undefined && isRestParameter(last)
|
|
277
|
+
? Number.POSITIVE_INFINITY
|
|
278
|
+
: params.length;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/** The number of leading required parameters (before any optional/rest). */
|
|
282
|
+
const signatureMinArgs = (
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
284
|
+
signature: ts.Signature,
|
|
285
|
+
): number => {
|
|
286
|
+
const firstOptional = signature
|
|
287
|
+
.getParameters()
|
|
288
|
+
.findIndex(isOptionalParameter);
|
|
289
|
+
|
|
290
|
+
return firstOptional === -1
|
|
291
|
+
? signature.getParameters().length
|
|
292
|
+
: firstOptional;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/** A parameter that collects the rest of the arguments (`...args`). */
|
|
296
|
+
const isRestParameter = (
|
|
297
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
298
|
+
symbol: ts.Symbol,
|
|
299
|
+
): boolean => {
|
|
300
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
declaration !== undefined &&
|
|
304
|
+
ts.isParameter(declaration) &&
|
|
305
|
+
declaration.dotDotDotToken !== undefined
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/** A parameter that may be omitted (`x?`, `x = ...`) or is a rest parameter. */
|
|
310
|
+
const isOptionalParameter = (
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
312
|
+
symbol: ts.Symbol,
|
|
313
|
+
): boolean => {
|
|
314
|
+
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
declaration !== undefined &&
|
|
318
|
+
ts.isParameter(declaration) &&
|
|
319
|
+
(declaration.questionToken !== undefined ||
|
|
320
|
+
declaration.initializer !== undefined ||
|
|
321
|
+
declaration.dotDotDotToken !== undefined)
|
|
322
|
+
);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* A conservative purity approximation: `true` only for expressions whose
|
|
327
|
+
* evaluation has no observable side effects and does not depend on evaluation
|
|
328
|
+
* count (so hoisting it out of the wrapper is safe). Anything not recognized
|
|
329
|
+
* (calls, `new`, `await`, assignments, updates) is treated as impure.
|
|
330
|
+
*/
|
|
331
|
+
const isPureExpression = (
|
|
332
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
333
|
+
node: TSESTree.Node,
|
|
334
|
+
): boolean => {
|
|
335
|
+
if (
|
|
336
|
+
node.type === AST_NODE_TYPES.Literal ||
|
|
337
|
+
node.type === AST_NODE_TYPES.Identifier ||
|
|
338
|
+
node.type === AST_NODE_TYPES.ThisExpression ||
|
|
339
|
+
node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
340
|
+
node.type === AST_NODE_TYPES.FunctionExpression
|
|
341
|
+
) {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (node.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
346
|
+
return node.expressions.every(isPureExpression);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (node.type === AST_NODE_TYPES.MemberExpression) {
|
|
350
|
+
return node.computed
|
|
351
|
+
? isPureExpression(node.object) && isPureExpression(node.property)
|
|
352
|
+
: isPureExpression(node.object);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (node.type === AST_NODE_TYPES.UnaryExpression) {
|
|
356
|
+
return node.operator !== 'delete' && isPureExpression(node.argument);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
node.type === AST_NODE_TYPES.BinaryExpression ||
|
|
361
|
+
node.type === AST_NODE_TYPES.LogicalExpression
|
|
362
|
+
) {
|
|
363
|
+
return isPureExpression(node.left) && isPureExpression(node.right);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (node.type === AST_NODE_TYPES.ConditionalExpression) {
|
|
367
|
+
return (
|
|
368
|
+
isPureExpression(node.test) &&
|
|
369
|
+
isPureExpression(node.consequent) &&
|
|
370
|
+
isPureExpression(node.alternate)
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (node.type === AST_NODE_TYPES.ArrayExpression) {
|
|
375
|
+
return node.elements.every(
|
|
376
|
+
(element) =>
|
|
377
|
+
element === null ||
|
|
378
|
+
(element.type !== AST_NODE_TYPES.SpreadElement &&
|
|
379
|
+
isPureExpression(element)),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (node.type === AST_NODE_TYPES.ObjectExpression) {
|
|
384
|
+
return node.properties.every(
|
|
385
|
+
(property) =>
|
|
386
|
+
property.type === AST_NODE_TYPES.Property &&
|
|
387
|
+
!property.computed &&
|
|
388
|
+
isPureExpression(property.value),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return false;
|
|
393
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import parser from '@typescript-eslint/parser';
|
|
2
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
3
|
+
import dedent from 'dedent';
|
|
4
|
+
import { preferCurriedCall } from './prefer-curried-call.mjs';
|
|
5
|
+
|
|
6
|
+
const tester = new RuleTester({
|
|
7
|
+
languageOptions: {
|
|
8
|
+
parser,
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
projectService: {
|
|
13
|
+
allowDefaultProject: ['*.ts*'],
|
|
14
|
+
},
|
|
15
|
+
tsconfigRootDir: `${import.meta.dirname}/../../../..`,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** A curried, data-first function declaration shared by many test cases. */
|
|
21
|
+
const curriedDecls = dedent`
|
|
22
|
+
declare function toPushed<Ar extends readonly unknown[], V>(
|
|
23
|
+
array: Ar,
|
|
24
|
+
v: V,
|
|
25
|
+
): readonly [...Ar, V];
|
|
26
|
+
declare function toPushed<V>(
|
|
27
|
+
v: V,
|
|
28
|
+
): <Ar extends readonly unknown[]>(array: Ar) => readonly [...Ar, V];
|
|
29
|
+
|
|
30
|
+
declare function take<Ar extends readonly unknown[]>(
|
|
31
|
+
array: Ar,
|
|
32
|
+
n: number,
|
|
33
|
+
): Ar;
|
|
34
|
+
declare function take(
|
|
35
|
+
n: number,
|
|
36
|
+
): <Ar extends readonly unknown[]>(array: Ar) => Ar;
|
|
37
|
+
|
|
38
|
+
declare namespace Arr {
|
|
39
|
+
function toPushed<Ar extends readonly unknown[], V>(
|
|
40
|
+
array: Ar,
|
|
41
|
+
v: V,
|
|
42
|
+
): readonly [...Ar, V];
|
|
43
|
+
function toPushed<V>(
|
|
44
|
+
v: V,
|
|
45
|
+
): <Ar extends readonly unknown[]>(array: Ar) => readonly [...Ar, V];
|
|
46
|
+
|
|
47
|
+
function isNonEmpty<E>(array: readonly E[]): boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare function isNonEmpty<E>(array: readonly E[]): boolean;
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
describe('prefer-curried-call', () => {
|
|
54
|
+
tester.run('prefer-curried-call', preferCurriedCall, {
|
|
55
|
+
valid: [
|
|
56
|
+
{
|
|
57
|
+
name: 'non-curried multi-argument function is left untouched',
|
|
58
|
+
code: dedent`
|
|
59
|
+
declare function add(a: number, b: number): number;
|
|
60
|
+
declare const xs: readonly number[];
|
|
61
|
+
const ys = xs.map((a) => add(a, 1));
|
|
62
|
+
`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'eta reduction is skipped when the function is not unary (parseInt/radix footgun)',
|
|
66
|
+
code: dedent`
|
|
67
|
+
declare const xs: readonly string[];
|
|
68
|
+
const ys = xs.map((a) => parseInt(a));
|
|
69
|
+
`,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'eta reduction is skipped for instance methods (would drop `this`)',
|
|
73
|
+
code: dedent`
|
|
74
|
+
declare const obj: { method(a: number): boolean };
|
|
75
|
+
declare const xs: readonly number[];
|
|
76
|
+
const ys = xs.map((a) => obj.method(a));
|
|
77
|
+
`,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'parameter used more than once is left untouched',
|
|
81
|
+
code: dedent`
|
|
82
|
+
${curriedDecls}
|
|
83
|
+
declare const xss: readonly (readonly number[])[];
|
|
84
|
+
const ys = xss.map((a) => toPushed(a, a));
|
|
85
|
+
`,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'parameter that is not the first argument is left untouched',
|
|
89
|
+
code: dedent`
|
|
90
|
+
${curriedDecls}
|
|
91
|
+
declare const x: readonly number[];
|
|
92
|
+
declare const xss: readonly (readonly number[])[];
|
|
93
|
+
const ys = xss.map((a) => toPushed(x, a));
|
|
94
|
+
`,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'impure remaining argument is left untouched (evaluation timing)',
|
|
98
|
+
code: dedent`
|
|
99
|
+
${curriedDecls}
|
|
100
|
+
declare function sideEffect(): number;
|
|
101
|
+
declare const xss: readonly (readonly number[])[];
|
|
102
|
+
const ys = xss.map((a) => toPushed(a, sideEffect()));
|
|
103
|
+
`,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'annotated parameter is left untouched',
|
|
107
|
+
code: dedent`
|
|
108
|
+
${curriedDecls}
|
|
109
|
+
const f = (a: readonly number[]) => toPushed(a, 5);
|
|
110
|
+
`,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'async wrapper is left untouched',
|
|
114
|
+
code: dedent`
|
|
115
|
+
declare function load<E>(array: readonly E[]): Promise<readonly E[]>;
|
|
116
|
+
declare const xss: readonly (readonly number[])[];
|
|
117
|
+
const ys = xss.map(async (a) => load(a));
|
|
118
|
+
`,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'already-curried callee (a call expression) is left untouched for eta',
|
|
122
|
+
code: dedent`
|
|
123
|
+
${curriedDecls}
|
|
124
|
+
declare const xss: readonly (readonly number[])[];
|
|
125
|
+
const ys = xss.map((a) => take(3)(a));
|
|
126
|
+
`,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'multi-argument arrow is left untouched',
|
|
130
|
+
code: dedent`
|
|
131
|
+
${curriedDecls}
|
|
132
|
+
declare const xss: readonly (readonly number[])[];
|
|
133
|
+
const ys = xss.map((a, i) => toPushed(a, i));
|
|
134
|
+
`,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
|
|
138
|
+
invalid: [
|
|
139
|
+
{
|
|
140
|
+
name: 'eta reduction of a standalone unary function',
|
|
141
|
+
code: dedent`
|
|
142
|
+
${curriedDecls}
|
|
143
|
+
declare const xss: readonly (readonly number[])[];
|
|
144
|
+
const ys = xss.map((a) => isNonEmpty(a));
|
|
145
|
+
`,
|
|
146
|
+
output: dedent`
|
|
147
|
+
${curriedDecls}
|
|
148
|
+
declare const xss: readonly (readonly number[])[];
|
|
149
|
+
const ys = xss.map(isNonEmpty);
|
|
150
|
+
`,
|
|
151
|
+
errors: [{ messageId: 'useFunctionDirectly' }],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'eta reduction of a namespace-member unary function',
|
|
155
|
+
code: dedent`
|
|
156
|
+
${curriedDecls}
|
|
157
|
+
declare const xss: readonly (readonly number[])[];
|
|
158
|
+
const ys = xss.map((a) => Arr.isNonEmpty(a));
|
|
159
|
+
`,
|
|
160
|
+
output: dedent`
|
|
161
|
+
${curriedDecls}
|
|
162
|
+
declare const xss: readonly (readonly number[])[];
|
|
163
|
+
const ys = xss.map(Arr.isNonEmpty);
|
|
164
|
+
`,
|
|
165
|
+
errors: [{ messageId: 'useFunctionDirectly' }],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'curried form of a standalone two-argument function',
|
|
169
|
+
code: dedent`
|
|
170
|
+
${curriedDecls}
|
|
171
|
+
declare const xss: readonly (readonly number[])[];
|
|
172
|
+
const ys = xss.map((a) => toPushed(a, 5));
|
|
173
|
+
`,
|
|
174
|
+
output: dedent`
|
|
175
|
+
${curriedDecls}
|
|
176
|
+
declare const xss: readonly (readonly number[])[];
|
|
177
|
+
const ys = xss.map(toPushed(5));
|
|
178
|
+
`,
|
|
179
|
+
errors: [{ messageId: 'useCurriedForm' }],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'curried form of a namespace-member function (the motivating example)',
|
|
183
|
+
code: dedent`
|
|
184
|
+
${curriedDecls}
|
|
185
|
+
declare const newElem: number;
|
|
186
|
+
declare const xss: readonly (readonly number[])[];
|
|
187
|
+
const ys = xss.map((a) => Arr.toPushed(a, newElem));
|
|
188
|
+
`,
|
|
189
|
+
output: dedent`
|
|
190
|
+
${curriedDecls}
|
|
191
|
+
declare const newElem: number;
|
|
192
|
+
declare const xss: readonly (readonly number[])[];
|
|
193
|
+
const ys = xss.map(Arr.toPushed(newElem));
|
|
194
|
+
`,
|
|
195
|
+
errors: [{ messageId: 'useCurriedForm' }],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'curried form with a counted argument (take)',
|
|
199
|
+
code: dedent`
|
|
200
|
+
${curriedDecls}
|
|
201
|
+
declare const xss: readonly (readonly number[])[];
|
|
202
|
+
const ys = xss.map((a) => take(a, 3));
|
|
203
|
+
`,
|
|
204
|
+
output: dedent`
|
|
205
|
+
${curriedDecls}
|
|
206
|
+
declare const xss: readonly (readonly number[])[];
|
|
207
|
+
const ys = xss.map(take(3));
|
|
208
|
+
`,
|
|
209
|
+
errors: [{ messageId: 'useCurriedForm' }],
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -5,6 +5,7 @@ import { noRestrictedSyntax } from './no-restricted-syntax.mjs';
|
|
|
5
5
|
import { noStringSpread } from './no-string-spread.mjs';
|
|
6
6
|
import { noUnnecessaryArrayFrom } from './no-unnecessary-array-from.mjs';
|
|
7
7
|
import { noUnnecessaryCoalesceUndefined } from './no-unnecessary-coalesce-undefined.mjs';
|
|
8
|
+
import { preferCurriedCall } from './prefer-curried-call.mjs';
|
|
8
9
|
import { preferNonMutatingArrayMethod } from './prefer-non-mutating-array-method.mjs';
|
|
9
10
|
|
|
10
11
|
export const tsRestrictionsRules = {
|
|
@@ -14,5 +15,6 @@ export const tsRestrictionsRules = {
|
|
|
14
15
|
'no-string-spread': noStringSpread,
|
|
15
16
|
'no-unnecessary-array-from': noUnnecessaryArrayFrom,
|
|
16
17
|
'no-unnecessary-coalesce-undefined': noUnnecessaryCoalesceUndefined,
|
|
18
|
+
'prefer-curried-call': preferCurriedCall,
|
|
17
19
|
'prefer-non-mutating-array-method': preferNonMutatingArrayMethod,
|
|
18
20
|
} as const satisfies ESLintPlugin['rules'];
|
|
@@ -9,6 +9,7 @@ export const eslintTsRestrictionsRules = {
|
|
|
9
9
|
'ts-restrictions/no-string-spread': 'error',
|
|
10
10
|
'ts-restrictions/no-unnecessary-array-from': 'error',
|
|
11
11
|
'ts-restrictions/no-unnecessary-coalesce-undefined': 'error',
|
|
12
|
+
'ts-restrictions/prefer-curried-call': 'error',
|
|
12
13
|
'ts-restrictions/prefer-non-mutating-array-method': 'error',
|
|
13
14
|
'ts-restrictions/check-destructuring-completeness':
|
|
14
15
|
withDefaultOption('error'),
|
|
@@ -279,6 +279,21 @@ namespace NoUnnecessaryCoalesceUndefined {
|
|
|
279
279
|
export type RuleEntry = Linter.StringSeverity;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/**
|
|
283
|
+
* @description Replace a redundant arrow wrapper with the function itself (`(a) => f(a)` → `f`) or, when the callee has a curried version, its curried form (`(a) => f(a, ...rest)` → `f(...rest)`).
|
|
284
|
+
*
|
|
285
|
+
* ```md
|
|
286
|
+
* | key | value |
|
|
287
|
+
* | :--------- | :--------- |
|
|
288
|
+
* | type | suggestion |
|
|
289
|
+
* | deprecated | false |
|
|
290
|
+
* | fixable | code |
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
namespace PreferCurriedCall {
|
|
294
|
+
export type RuleEntry = Linter.StringSeverity;
|
|
295
|
+
}
|
|
296
|
+
|
|
282
297
|
/**
|
|
283
298
|
* @description Disallow calling a mutating array method on a defensive `Array.from()` copy of an array (e.g. `Array.from(x).sort()`); use the non-mutating counterpart on the original array instead (e.g. `x.toSorted()`)
|
|
284
299
|
*
|
|
@@ -301,6 +316,7 @@ export type EslintTsRestrictionsRules = Readonly<{
|
|
|
301
316
|
'ts-restrictions/no-string-spread': NoStringSpread.RuleEntry;
|
|
302
317
|
'ts-restrictions/no-unnecessary-array-from': NoUnnecessaryArrayFrom.RuleEntry;
|
|
303
318
|
'ts-restrictions/no-unnecessary-coalesce-undefined': NoUnnecessaryCoalesceUndefined.RuleEntry;
|
|
319
|
+
'ts-restrictions/prefer-curried-call': PreferCurriedCall.RuleEntry;
|
|
304
320
|
'ts-restrictions/prefer-non-mutating-array-method': PreferNonMutatingArrayMethod.RuleEntry;
|
|
305
321
|
}>;
|
|
306
322
|
|