eslint-plugin-th-rules 3.4.3 → 3.5.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/README.md +14 -12
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -0
- package/dist/rules/prefer-lodash-iteratee-shorthand.d.ts +19 -0
- package/dist/rules/prefer-lodash-iteratee-shorthand.d.ts.map +1 -0
- package/dist/rules/prefer-lodash-iteratee-shorthand.js +644 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,18 +166,20 @@ Do not edit below this line.
|
|
|
166
166
|
🎲 Set in the `recommendedTypescriptReact` configuration.\
|
|
167
167
|
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
|
|
168
168
|
|
|
169
|
-
| Name
|
|
170
|
-
|
|
|
171
|
-
| [no-boolean-coercion](docs/rules/no-boolean-coercion.md)
|
|
172
|
-
| [no-comments](docs/rules/no-comments.md)
|
|
173
|
-
| [no-default-export](docs/rules/no-default-export.md)
|
|
174
|
-
| [no-destructuring](docs/rules/no-destructuring.md)
|
|
175
|
-
| [no-explicit-nil-compare](docs/rules/no-explicit-nil-compare.md)
|
|
176
|
-
| [
|
|
177
|
-
| [prefer-
|
|
178
|
-
| [
|
|
179
|
-
| [
|
|
180
|
-
| [
|
|
169
|
+
| Name | Description | 💼 | 🔧 |
|
|
170
|
+
| :--------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------- | :- |
|
|
171
|
+
| [no-boolean-coercion](docs/rules/no-boolean-coercion.md) | Disallow Boolean(value) or !!value. Enforce explicit checks: !_.isNil(value) for scalars and !_.isEmpty(value) for strings, arrays, and objects. If the value is already boolean, remove coercion. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
172
|
+
| [no-comments](docs/rules/no-comments.md) | Disallow comments except for specified allowed patterns. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
173
|
+
| [no-default-export](docs/rules/no-default-export.md) | Convert unnamed default exports to named default exports based on the file name. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
174
|
+
| [no-destructuring](docs/rules/no-destructuring.md) | Disallow destructuring that does not meet certain conditions. | ✅ ⚛️ 🟦 🎲 | |
|
|
175
|
+
| [no-explicit-nil-compare](docs/rules/no-explicit-nil-compare.md) | Disallow direct comparisons to null or undefined. Use _.isNull(x) / _.isUndefined(x) instead. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
176
|
+
| [no-isnil-isempty-on-boolean](docs/rules/no-isnil-isempty-on-boolean.md) | Disallow _.isNil(...) / _.isEmpty(...) when the argument type is boolean or a union containing boolean (e.g., boolean \| undefined, boolean \| X). | ✅ ⚛️ 🟦 🎲 | |
|
|
177
|
+
| [prefer-explicit-nil-check](docs/rules/prefer-explicit-nil-check.md) | Disallow implicit truthy/falsy checks in boolean-test positions. Prefer explicit _.isNil(value) or _.isEmpty(value) (depending on the value type). | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
178
|
+
| [prefer-is-empty](docs/rules/prefer-is-empty.md) | Require _.isEmpty instead of length comparisons or boolean checks on .length. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
179
|
+
| [prefer-lodash-iteratee-shorthand](docs/rules/prefer-lodash-iteratee-shorthand.md) | Prefer Lodash iteratee shorthands. Example: _.find(collection, (x) => x.Y === z) -> _.find(collection, {Y: z}). Also prefers property shorthands like _.map(collection, (x) => _.get(x, path)) -> _.map(collection, path). | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
180
|
+
| [schemas-in-schemas-file](docs/rules/schemas-in-schemas-file.md) | Require Zod schema declarations to be placed in a .schemas.ts file. | ✅ ⚛️ 🟦 🎲 | |
|
|
181
|
+
| [top-level-functions](docs/rules/top-level-functions.md) | Require all top-level functions to be named regular functions. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
182
|
+
| [types-in-dts](docs/rules/types-in-dts.md) | Require TypeScript type declarations (type/interface/enum) to be placed in .d.ts files. | ✅ ⚛️ 🟦 🎲 | |
|
|
181
183
|
|
|
182
184
|
<!-- end auto-generated rules list -->
|
|
183
185
|
|
package/dist/plugin.d.ts
CHANGED
|
@@ -45,6 +45,9 @@ export declare const rules: {
|
|
|
45
45
|
'no-isnil-isempty-on-boolean': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
46
46
|
name: string;
|
|
47
47
|
};
|
|
48
|
+
'prefer-lodash-iteratee-shorthand': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useMatchesObject" | "usePropertyShorthand" | "useLodashMethod", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
49
|
+
name: string;
|
|
50
|
+
};
|
|
48
51
|
};
|
|
49
52
|
declare const plugin: {
|
|
50
53
|
rules: {
|
|
@@ -94,6 +97,9 @@ declare const plugin: {
|
|
|
94
97
|
'no-isnil-isempty-on-boolean': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
95
98
|
name: string;
|
|
96
99
|
};
|
|
100
|
+
'prefer-lodash-iteratee-shorthand': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useMatchesObject" | "usePropertyShorthand" | "useLodashMethod", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
101
|
+
name: string;
|
|
102
|
+
};
|
|
97
103
|
};
|
|
98
104
|
};
|
|
99
105
|
export default plugin;
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,KAAK
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAajB,CAAC;AAEF,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAY,CAAC;AACzB,eAAe,MAAM,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -9,6 +9,7 @@ import schemasInSchemasFile from './rules/schemas-in-schemas-file.js';
|
|
|
9
9
|
import topLevelFunctions from './rules/top-level-functions.js';
|
|
10
10
|
import typesInDts from './rules/types-in-dts.js';
|
|
11
11
|
import noIsNilOrIsEmptyOnBoolean from './rules/no-isnil-isempty-on-boolean.js';
|
|
12
|
+
import preferLodashIterateeShorthand from './rules/prefer-lodash-iteratee-shorthand.js';
|
|
12
13
|
export const rules = {
|
|
13
14
|
'no-boolean-coercion': noBooleanCoercion,
|
|
14
15
|
'no-comments': noComments,
|
|
@@ -21,6 +22,7 @@ export const rules = {
|
|
|
21
22
|
'top-level-functions': topLevelFunctions,
|
|
22
23
|
'types-in-dts': typesInDts,
|
|
23
24
|
'no-isnil-isempty-on-boolean': noIsNilOrIsEmptyOnBoolean,
|
|
25
|
+
'prefer-lodash-iteratee-shorthand': preferLodashIterateeShorthand,
|
|
24
26
|
};
|
|
25
27
|
const plugin = { rules };
|
|
26
28
|
export default plugin;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const PREDICATE_METHOD_NAMES: readonly ["find", "findLast", "findIndex", "findLastIndex", "filter", "reject", "some", "every", "partition", "dropWhile", "takeWhile", "remove", "findKey", "findLastKey"];
|
|
3
|
+
export declare const ITERATEE_METHOD_NAMES: readonly ["map", "flatMap", "flatMapDeep", "flatMapDepth", "forEach", "each", "groupBy", "keyBy", "countBy", "sortBy", "orderBy", "minBy", "maxBy", "sumBy", "meanBy", "uniqBy", "differenceBy", "intersectionBy", "unionBy", "xorBy", "sortedUniqBy"];
|
|
4
|
+
export declare const NATIVE_TO_LODASH_METHOD_NAMES: {
|
|
5
|
+
readonly find: "find";
|
|
6
|
+
readonly findLast: "findLast";
|
|
7
|
+
readonly findIndex: "findIndex";
|
|
8
|
+
readonly findLastIndex: "findLastIndex";
|
|
9
|
+
readonly filter: "filter";
|
|
10
|
+
readonly some: "some";
|
|
11
|
+
readonly every: "every";
|
|
12
|
+
readonly map: "map";
|
|
13
|
+
readonly flatMap: "flatMap";
|
|
14
|
+
};
|
|
15
|
+
declare const preferLodashIterateeShorthand: ESLintUtils.RuleModule<"useMatchesObject" | "usePropertyShorthand" | "useLodashMethod", [], unknown, ESLintUtils.RuleListener> & {
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
export default preferLodashIterateeShorthand;
|
|
19
|
+
//# sourceMappingURL=prefer-lodash-iteratee-shorthand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-lodash-iteratee-shorthand.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-lodash-iteratee-shorthand.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkB,WAAW,EAAgC,MAAM,0BAA0B,CAAC;AAYrG,eAAO,MAAM,sBAAsB,6KAezB,CAAC;AAEX,eAAO,MAAM,qBAAqB,wPAsBxB,CAAC;AAEX,eAAO,MAAM,6BAA6B;;;;;;;;;;CAUhC,CAAC;AAUX,QAAA,MAAM,6BAA6B;;CAwpBjC,CAAC;AAEH,eAAe,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/* eslint-disable th-rules/types-in-dts */
|
|
2
|
+
/* eslint-disable new-cap */
|
|
3
|
+
/* eslint-disable complexity */
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
6
|
+
const RULE_NAME = 'prefer-lodash-iteratee-shorthand';
|
|
7
|
+
export const PREDICATE_METHOD_NAMES = [
|
|
8
|
+
'find',
|
|
9
|
+
'findLast',
|
|
10
|
+
'findIndex',
|
|
11
|
+
'findLastIndex',
|
|
12
|
+
'filter',
|
|
13
|
+
'reject',
|
|
14
|
+
'some',
|
|
15
|
+
'every',
|
|
16
|
+
'partition',
|
|
17
|
+
'dropWhile',
|
|
18
|
+
'takeWhile',
|
|
19
|
+
'remove',
|
|
20
|
+
'findKey',
|
|
21
|
+
'findLastKey',
|
|
22
|
+
];
|
|
23
|
+
export const ITERATEE_METHOD_NAMES = [
|
|
24
|
+
'map',
|
|
25
|
+
'flatMap',
|
|
26
|
+
'flatMapDeep',
|
|
27
|
+
'flatMapDepth',
|
|
28
|
+
'forEach',
|
|
29
|
+
'each',
|
|
30
|
+
'groupBy',
|
|
31
|
+
'keyBy',
|
|
32
|
+
'countBy',
|
|
33
|
+
'sortBy',
|
|
34
|
+
'orderBy',
|
|
35
|
+
'minBy',
|
|
36
|
+
'maxBy',
|
|
37
|
+
'sumBy',
|
|
38
|
+
'meanBy',
|
|
39
|
+
'uniqBy',
|
|
40
|
+
'differenceBy',
|
|
41
|
+
'intersectionBy',
|
|
42
|
+
'unionBy',
|
|
43
|
+
'xorBy',
|
|
44
|
+
'sortedUniqBy',
|
|
45
|
+
];
|
|
46
|
+
export const NATIVE_TO_LODASH_METHOD_NAMES = {
|
|
47
|
+
find: 'find',
|
|
48
|
+
findLast: 'findLast',
|
|
49
|
+
findIndex: 'findIndex',
|
|
50
|
+
findLastIndex: 'findLastIndex',
|
|
51
|
+
filter: 'filter',
|
|
52
|
+
some: 'some',
|
|
53
|
+
every: 'every',
|
|
54
|
+
map: 'map',
|
|
55
|
+
flatMap: 'flatMap',
|
|
56
|
+
};
|
|
57
|
+
function typedKeys(object) {
|
|
58
|
+
return Object.keys(object);
|
|
59
|
+
}
|
|
60
|
+
const preferLodashIterateeShorthand = ESLintUtils.RuleCreator(() => `https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/${RULE_NAME}.md`)({
|
|
61
|
+
name: RULE_NAME,
|
|
62
|
+
meta: {
|
|
63
|
+
type: 'suggestion',
|
|
64
|
+
docs: {
|
|
65
|
+
description: 'Prefer Lodash iteratee shorthands. Example: _.find(collection, (x) => x.Y === z) -> _.find(collection, {Y: z}). Also prefers property shorthands like _.map(collection, (x) => _.get(x, path)) -> _.map(collection, path).',
|
|
66
|
+
},
|
|
67
|
+
fixable: 'code',
|
|
68
|
+
schema: [],
|
|
69
|
+
messages: {
|
|
70
|
+
useMatchesObject: 'Prefer Lodash iteratee shorthand. Use {{replacement}}.',
|
|
71
|
+
usePropertyShorthand: 'Prefer Lodash iteratee shorthand. Use {{replacement}}.',
|
|
72
|
+
useLodashMethod: 'Prefer Lodash iteratee shorthand. Use {{replacement}}.',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
defaultOptions: [],
|
|
76
|
+
create(context) {
|
|
77
|
+
const { sourceCode } = context;
|
|
78
|
+
const PREDICATE_METHODS = new Set(PREDICATE_METHOD_NAMES);
|
|
79
|
+
const ITERATEE_METHODS = new Set(ITERATEE_METHOD_NAMES);
|
|
80
|
+
const nativeMethodNames = typedKeys(NATIVE_TO_LODASH_METHOD_NAMES);
|
|
81
|
+
const NATIVE_METHODS = new Set(nativeMethodNames);
|
|
82
|
+
function ensureLodashImport(fixer) {
|
|
83
|
+
const imports = sourceCode.ast.body.filter((node) => node.type === AST_NODE_TYPES.ImportDeclaration);
|
|
84
|
+
const hasLodash = imports.some((imp) => imp.source.value === 'lodash' && imp.specifiers.some((s) => s.type === AST_NODE_TYPES.ImportDefaultSpecifier || s.type === AST_NODE_TYPES.ImportNamespaceSpecifier));
|
|
85
|
+
if (hasLodash)
|
|
86
|
+
return null;
|
|
87
|
+
const firstImport = imports[0];
|
|
88
|
+
return _.isNil(firstImport) ? fixer.insertTextBeforeRange([0, 0], `import _ from 'lodash';\n`) : fixer.insertTextBefore(firstImport, `import _ from 'lodash';\n`);
|
|
89
|
+
}
|
|
90
|
+
function unwrapChain(node) {
|
|
91
|
+
return node?.type === AST_NODE_TYPES.ChainExpression ? node.expression : node;
|
|
92
|
+
}
|
|
93
|
+
function unwrapChainExpr(node) {
|
|
94
|
+
const unwrapped = unwrapChain(node);
|
|
95
|
+
return !_.isNil(unwrapped) && !_.isEmpty(unwrapped.type) ? unwrapped : undefined;
|
|
96
|
+
}
|
|
97
|
+
function isIdentifier(node, name) {
|
|
98
|
+
return !_.isNil(node) && node.type === AST_NODE_TYPES.Identifier && node.name === name;
|
|
99
|
+
}
|
|
100
|
+
function isStringLiteral(node) {
|
|
101
|
+
return !_.isNil(node) && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string';
|
|
102
|
+
}
|
|
103
|
+
function isTemplateLiteralWithoutExpressions(node) {
|
|
104
|
+
return !_.isNil(node) && node.type === AST_NODE_TYPES.TemplateLiteral && _.isEmpty(node.expressions) && node.quasis.length === 1;
|
|
105
|
+
}
|
|
106
|
+
function isLodashIdentifier(node) {
|
|
107
|
+
return isIdentifier(node, '_');
|
|
108
|
+
}
|
|
109
|
+
function isLodashMember(node, methodName) {
|
|
110
|
+
const unwrapped = unwrapChain(node);
|
|
111
|
+
return (!_.isNil(unwrapped) &&
|
|
112
|
+
unwrapped.type === AST_NODE_TYPES.MemberExpression &&
|
|
113
|
+
!unwrapped.computed &&
|
|
114
|
+
unwrapped.property.type === AST_NODE_TYPES.Identifier &&
|
|
115
|
+
unwrapped.property.name === methodName &&
|
|
116
|
+
isLodashIdentifier(unwrapped.object));
|
|
117
|
+
}
|
|
118
|
+
function isLodashStaticCall(node, methodName) {
|
|
119
|
+
return isLodashMember(node.callee, methodName);
|
|
120
|
+
}
|
|
121
|
+
function isLodashWrapperCallExpression(node) {
|
|
122
|
+
const unwrapped = unwrapChain(node);
|
|
123
|
+
return !_.isNil(unwrapped) && unwrapped.type === AST_NODE_TYPES.CallExpression && unwrapped.callee.type === AST_NODE_TYPES.Identifier && unwrapped.callee.name === '_';
|
|
124
|
+
}
|
|
125
|
+
function isLodashWrapperMethodCall(node, methodName) {
|
|
126
|
+
const callee = unwrapChain(node.callee);
|
|
127
|
+
if (_.isNil(callee) || callee.type !== AST_NODE_TYPES.MemberExpression)
|
|
128
|
+
return false;
|
|
129
|
+
if (callee.computed)
|
|
130
|
+
return false;
|
|
131
|
+
if (callee.property.type !== AST_NODE_TYPES.Identifier)
|
|
132
|
+
return false;
|
|
133
|
+
if (callee.property.name !== methodName)
|
|
134
|
+
return false;
|
|
135
|
+
return isLodashWrapperCallExpression(callee.object);
|
|
136
|
+
}
|
|
137
|
+
function getFunctionParameterName(fn) {
|
|
138
|
+
if (fn.params.length !== 1)
|
|
139
|
+
return null;
|
|
140
|
+
const parameter = fn.params[0];
|
|
141
|
+
return parameter.type === AST_NODE_TYPES.Identifier ? parameter.name : null;
|
|
142
|
+
}
|
|
143
|
+
function getReturnedExpression(fn) {
|
|
144
|
+
if (fn.body.type === AST_NODE_TYPES.BlockStatement) {
|
|
145
|
+
if (fn.body.body.length !== 1)
|
|
146
|
+
return null;
|
|
147
|
+
const only = fn.body.body[0];
|
|
148
|
+
if (only.type !== AST_NODE_TYPES.ReturnStatement)
|
|
149
|
+
return null;
|
|
150
|
+
return only.argument ?? null;
|
|
151
|
+
}
|
|
152
|
+
return fn.body;
|
|
153
|
+
}
|
|
154
|
+
function isFunctionLike(node) {
|
|
155
|
+
return !_.isNil(node) && (node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression);
|
|
156
|
+
}
|
|
157
|
+
function isGetCallOnParameter(expr, parameterName) {
|
|
158
|
+
const unwrapped = unwrapChain(expr);
|
|
159
|
+
if (_.isNil(unwrapped) || unwrapped.type !== AST_NODE_TYPES.CallExpression)
|
|
160
|
+
return null;
|
|
161
|
+
const callee = unwrapChain(unwrapped.callee);
|
|
162
|
+
if (!isLodashMember(callee, 'get'))
|
|
163
|
+
return null;
|
|
164
|
+
if (unwrapped.arguments.length < 2)
|
|
165
|
+
return null;
|
|
166
|
+
const arg0 = unwrapped.arguments[0];
|
|
167
|
+
const arg1 = unwrapped.arguments[1];
|
|
168
|
+
if (_.isNil(arg0) || arg0.type === AST_NODE_TYPES.SpreadElement)
|
|
169
|
+
return null;
|
|
170
|
+
if (_.isNil(arg1) || arg1.type === AST_NODE_TYPES.SpreadElement)
|
|
171
|
+
return null;
|
|
172
|
+
const object = unwrapChain(arg0);
|
|
173
|
+
if (_.isNil(object) || object.type !== AST_NODE_TYPES.Identifier || object.name !== parameterName)
|
|
174
|
+
return null;
|
|
175
|
+
if (isStringLiteral(arg1)) {
|
|
176
|
+
return { kind: 'get', pathText: sourceCode.getText(arg1), pathIsStaticString: true };
|
|
177
|
+
}
|
|
178
|
+
if (isTemplateLiteralWithoutExpressions(arg1)) {
|
|
179
|
+
return { kind: 'get', pathText: sourceCode.getText(arg1), pathIsStaticString: true };
|
|
180
|
+
}
|
|
181
|
+
if (arg1.type === AST_NODE_TYPES.Identifier) {
|
|
182
|
+
return { kind: 'get', pathText: arg1.name, pathIsStaticString: false };
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function isMemberAccessOnParameter(expr, parameterName) {
|
|
187
|
+
const unwrapped = unwrapChain(expr);
|
|
188
|
+
if (_.isNil(unwrapped) || unwrapped.type !== AST_NODE_TYPES.MemberExpression)
|
|
189
|
+
return null;
|
|
190
|
+
const object = unwrapChain(unwrapped.object);
|
|
191
|
+
if (_.isNil(object) || object.type !== AST_NODE_TYPES.Identifier || object.name !== parameterName)
|
|
192
|
+
return null;
|
|
193
|
+
if (!unwrapped.computed && unwrapped.property.type === AST_NODE_TYPES.Identifier) {
|
|
194
|
+
const key = unwrapped.property.name;
|
|
195
|
+
return { kind: 'member', keyText: key, keyIsIdentifier: true };
|
|
196
|
+
}
|
|
197
|
+
if (unwrapped.computed && unwrapped.property.type === AST_NODE_TYPES.Literal && typeof unwrapped.property.value === 'string') {
|
|
198
|
+
return { kind: 'member', keyText: sourceCode.getText(unwrapped.property), keyIsIdentifier: false };
|
|
199
|
+
}
|
|
200
|
+
if (unwrapped.computed && unwrapped.property.type === AST_NODE_TYPES.Identifier) {
|
|
201
|
+
return { kind: 'member', keyText: unwrapped.property.name, keyIsIdentifier: false };
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
function extractPathFromExpression(expr, parameterName) {
|
|
206
|
+
return isMemberAccessOnParameter(expr, parameterName) ?? isGetCallOnParameter(expr, parameterName);
|
|
207
|
+
}
|
|
208
|
+
function normalizeStaticPathText(pathText) {
|
|
209
|
+
const trimmed = pathText.trim();
|
|
210
|
+
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
|
211
|
+
return trimmed.slice(1, -1);
|
|
212
|
+
}
|
|
213
|
+
if (trimmed.startsWith('`') && trimmed.endsWith('`')) {
|
|
214
|
+
return trimmed.slice(1, -1);
|
|
215
|
+
}
|
|
216
|
+
return trimmed;
|
|
217
|
+
}
|
|
218
|
+
function isValidIdentifierName(name) {
|
|
219
|
+
return /^[A-Za-z_$][\w$]*$/.test(name);
|
|
220
|
+
}
|
|
221
|
+
function toSingleQuotedStringLiteral(value) {
|
|
222
|
+
const escaped = value.replaceAll('\\', '\\\\').replaceAll("'", String.raw `\'`);
|
|
223
|
+
return `'${escaped}'`;
|
|
224
|
+
}
|
|
225
|
+
function containsIdentifier(node, name) {
|
|
226
|
+
const { visitorKeys } = sourceCode;
|
|
227
|
+
const seen = new Set();
|
|
228
|
+
const stack = [node];
|
|
229
|
+
while (!_.isEmpty(stack)) {
|
|
230
|
+
const current = stack.pop();
|
|
231
|
+
if (_.isNil(current))
|
|
232
|
+
continue;
|
|
233
|
+
if (seen.has(current))
|
|
234
|
+
continue;
|
|
235
|
+
seen.add(current);
|
|
236
|
+
if (current.type === AST_NODE_TYPES.Identifier && current.name === name)
|
|
237
|
+
return true;
|
|
238
|
+
const keys = visitorKeys[current.type] ?? [];
|
|
239
|
+
for (const key of keys) {
|
|
240
|
+
const value = current[key];
|
|
241
|
+
if (_.isNil(value))
|
|
242
|
+
continue;
|
|
243
|
+
if (Array.isArray(value)) {
|
|
244
|
+
for (const item of value) {
|
|
245
|
+
if (!item || typeof item !== 'object')
|
|
246
|
+
continue;
|
|
247
|
+
stack.push(item);
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (typeof value === 'object') {
|
|
252
|
+
stack.push(value);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
function parseLodashPathSegments(path) {
|
|
259
|
+
const segments = [];
|
|
260
|
+
let i = 0;
|
|
261
|
+
const { length } = path;
|
|
262
|
+
const skipDot = () => {
|
|
263
|
+
if (path[i] === '.')
|
|
264
|
+
i += 1;
|
|
265
|
+
};
|
|
266
|
+
const readIdent = () => {
|
|
267
|
+
const start = i;
|
|
268
|
+
if (start >= length)
|
|
269
|
+
return null;
|
|
270
|
+
const first = path[start];
|
|
271
|
+
if (_.isEmpty(first) || !/[A-Za-z_$]/.test(first))
|
|
272
|
+
return null;
|
|
273
|
+
i += 1;
|
|
274
|
+
while (i < length && /[\w$]/.test(path[i] ?? ''))
|
|
275
|
+
i += 1;
|
|
276
|
+
return path.slice(start, i);
|
|
277
|
+
};
|
|
278
|
+
const readBracketIndex = () => {
|
|
279
|
+
if (path[i] !== '[')
|
|
280
|
+
return null;
|
|
281
|
+
i += 1;
|
|
282
|
+
const start = i;
|
|
283
|
+
while (i < length && /\d/.test(path[i] ?? ''))
|
|
284
|
+
i += 1;
|
|
285
|
+
if (start === i)
|
|
286
|
+
return null;
|
|
287
|
+
if (path[i] !== ']')
|
|
288
|
+
return null;
|
|
289
|
+
const number_ = Number(path.slice(start, i));
|
|
290
|
+
i += 1;
|
|
291
|
+
return Number.isFinite(number_) ? number_ : null;
|
|
292
|
+
};
|
|
293
|
+
while (i < length) {
|
|
294
|
+
skipDot();
|
|
295
|
+
if (i >= length)
|
|
296
|
+
break;
|
|
297
|
+
if (path[i] === '[') {
|
|
298
|
+
const idx = readBracketIndex();
|
|
299
|
+
if (_.isNil(idx))
|
|
300
|
+
return null;
|
|
301
|
+
segments.push(idx);
|
|
302
|
+
skipDot();
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const ident = readIdent();
|
|
306
|
+
if (_.isNil(ident))
|
|
307
|
+
return null;
|
|
308
|
+
segments.push(ident);
|
|
309
|
+
skipDot();
|
|
310
|
+
}
|
|
311
|
+
return segments;
|
|
312
|
+
}
|
|
313
|
+
function buildNestedLiteralFromSegments(segments, valueText) {
|
|
314
|
+
if (_.isEmpty(segments))
|
|
315
|
+
return null;
|
|
316
|
+
const build = (idx) => {
|
|
317
|
+
const seg = segments[idx];
|
|
318
|
+
if (_.isNil(seg))
|
|
319
|
+
return null;
|
|
320
|
+
const tail = idx === segments.length - 1 ? valueText : build(idx + 1);
|
|
321
|
+
if (_.isNil(tail))
|
|
322
|
+
return null;
|
|
323
|
+
if (typeof seg === 'number') {
|
|
324
|
+
const holes = new Array(seg).fill('').join(',');
|
|
325
|
+
const prefix = seg === 0 ? '' : holes;
|
|
326
|
+
const comma = seg === 0 ? '' : ',';
|
|
327
|
+
return `[${prefix}${comma}${tail}]`;
|
|
328
|
+
}
|
|
329
|
+
const key = isValidIdentifierName(seg) ? seg : toSingleQuotedStringLiteral(seg);
|
|
330
|
+
return `{${key}: ${tail}}`;
|
|
331
|
+
};
|
|
332
|
+
return build(0);
|
|
333
|
+
}
|
|
334
|
+
function mergeObjectLiteralsIntoOne(literals) {
|
|
335
|
+
const trimmed = literals.map((s) => s.trim());
|
|
336
|
+
const allArrays = trimmed.every((s) => s.startsWith('['));
|
|
337
|
+
const anyArrays = trimmed.some((s) => s.startsWith('['));
|
|
338
|
+
if (anyArrays && !allArrays)
|
|
339
|
+
return null;
|
|
340
|
+
if (allArrays) {
|
|
341
|
+
return trimmed.length === 1 ? trimmed[0] : null;
|
|
342
|
+
}
|
|
343
|
+
const props = [];
|
|
344
|
+
for (const lit of trimmed) {
|
|
345
|
+
if (!lit.startsWith('{') || !lit.endsWith('}'))
|
|
346
|
+
return null;
|
|
347
|
+
const inner = lit.slice(1, -1).trim();
|
|
348
|
+
if (_.isEmpty(inner))
|
|
349
|
+
continue;
|
|
350
|
+
props.push(inner);
|
|
351
|
+
}
|
|
352
|
+
if (_.isEmpty(props))
|
|
353
|
+
return '{}';
|
|
354
|
+
return `{${props.join(', ')}}`;
|
|
355
|
+
}
|
|
356
|
+
function extractPredicateClausesFromExpression(expr, parameterName) {
|
|
357
|
+
const unwrapped = unwrapChainExpr(expr);
|
|
358
|
+
if (_.isNil(unwrapped))
|
|
359
|
+
return null;
|
|
360
|
+
const flattenAnd = (e) => {
|
|
361
|
+
const u = unwrapChainExpr(e) ?? e;
|
|
362
|
+
if (u.type === AST_NODE_TYPES.LogicalExpression && u.operator === '&&') {
|
|
363
|
+
return [...flattenAnd(u.left), ...flattenAnd(u.right)];
|
|
364
|
+
}
|
|
365
|
+
return [u];
|
|
366
|
+
};
|
|
367
|
+
const parts = flattenAnd(unwrapped);
|
|
368
|
+
const clauses = [];
|
|
369
|
+
for (const part of parts) {
|
|
370
|
+
const u = unwrapChainExpr(part) ?? part;
|
|
371
|
+
if (u.type !== AST_NODE_TYPES.BinaryExpression)
|
|
372
|
+
return null;
|
|
373
|
+
if (u.operator !== '===' && u.operator !== '==')
|
|
374
|
+
return null;
|
|
375
|
+
const left = unwrapChainExpr(u.left) ?? u.left;
|
|
376
|
+
const right = unwrapChainExpr(u.right) ?? u.right;
|
|
377
|
+
const leftPath = extractPathFromExpression(left, parameterName);
|
|
378
|
+
const rightPath = extractPathFromExpression(right, parameterName);
|
|
379
|
+
if (!_.isNil(leftPath) && _.isNil(rightPath)) {
|
|
380
|
+
if (containsIdentifier(right, parameterName))
|
|
381
|
+
return null;
|
|
382
|
+
clauses.push({ path: leftPath, valueExpr: right });
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (_.isNil(leftPath) && !_.isNil(rightPath)) {
|
|
386
|
+
if (containsIdentifier(left, parameterName))
|
|
387
|
+
return null;
|
|
388
|
+
clauses.push({ path: rightPath, valueExpr: left });
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
return clauses;
|
|
394
|
+
}
|
|
395
|
+
function buildObjectKeyTextForMemberPath(path) {
|
|
396
|
+
if (path.keyIsIdentifier) {
|
|
397
|
+
return path.keyText;
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
function buildMatchesObjectFromClauses(clauses) {
|
|
402
|
+
const literals = [];
|
|
403
|
+
for (const clause of clauses) {
|
|
404
|
+
const valueText = sourceCode.getText(clause.valueExpr);
|
|
405
|
+
if (clause.path.kind === 'member') {
|
|
406
|
+
const keyText = buildObjectKeyTextForMemberPath(clause.path);
|
|
407
|
+
if (_.isNil(keyText))
|
|
408
|
+
return null;
|
|
409
|
+
literals.push(`{${keyText}: ${valueText}}`);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (!clause.path.pathIsStaticString) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
const rawPath = normalizeStaticPathText(clause.path.pathText);
|
|
416
|
+
const segments = parseLodashPathSegments(rawPath);
|
|
417
|
+
if (_.isNil(segments))
|
|
418
|
+
return null;
|
|
419
|
+
const nested = buildNestedLiteralFromSegments(segments, valueText);
|
|
420
|
+
if (_.isNil(nested))
|
|
421
|
+
return null;
|
|
422
|
+
literals.push(nested);
|
|
423
|
+
}
|
|
424
|
+
return mergeObjectLiteralsIntoOne(literals);
|
|
425
|
+
}
|
|
426
|
+
function extractPropertyIterateeRewrite(fn) {
|
|
427
|
+
const parameterName = getFunctionParameterName(fn);
|
|
428
|
+
if (_.isNil(parameterName))
|
|
429
|
+
return null;
|
|
430
|
+
const expr = getReturnedExpression(fn);
|
|
431
|
+
if (_.isNil(expr))
|
|
432
|
+
return null;
|
|
433
|
+
const unwrapped = unwrapChainExpr(expr) ?? expr;
|
|
434
|
+
const member = isMemberAccessOnParameter(unwrapped, parameterName);
|
|
435
|
+
if (!_.isNil(member) && member.kind === 'member') {
|
|
436
|
+
if (!member.keyIsIdentifier && isValidIdentifierName(member.keyText)) {
|
|
437
|
+
return member.keyText;
|
|
438
|
+
}
|
|
439
|
+
if (member.keyIsIdentifier) {
|
|
440
|
+
return toSingleQuotedStringLiteral(member.keyText);
|
|
441
|
+
}
|
|
442
|
+
const normalized = normalizeStaticPathText(member.keyText);
|
|
443
|
+
return toSingleQuotedStringLiteral(normalized);
|
|
444
|
+
}
|
|
445
|
+
const get = isGetCallOnParameter(unwrapped, parameterName);
|
|
446
|
+
if (!_.isNil(get) && get.kind === 'get') {
|
|
447
|
+
return get.pathText;
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
function getMethodNameFromMemberCallee(callee) {
|
|
452
|
+
const unwrapped = unwrapChain(callee);
|
|
453
|
+
if (_.isNil(unwrapped) || unwrapped.type !== AST_NODE_TYPES.MemberExpression)
|
|
454
|
+
return null;
|
|
455
|
+
if (unwrapped.computed)
|
|
456
|
+
return null;
|
|
457
|
+
if (unwrapped.property.type !== AST_NODE_TYPES.Identifier)
|
|
458
|
+
return null;
|
|
459
|
+
return unwrapped.property.name;
|
|
460
|
+
}
|
|
461
|
+
function isNativeArrayMethodCall(node) {
|
|
462
|
+
const callee = unwrapChain(node.callee);
|
|
463
|
+
if (_.isNil(callee) || callee.type !== AST_NODE_TYPES.MemberExpression)
|
|
464
|
+
return null;
|
|
465
|
+
const method = getMethodNameFromMemberCallee(callee);
|
|
466
|
+
if (_.isNil(method) || !NATIVE_METHODS.has(method))
|
|
467
|
+
return null;
|
|
468
|
+
if (isLodashIdentifier(callee.object))
|
|
469
|
+
return null;
|
|
470
|
+
if (isLodashWrapperCallExpression(callee.object))
|
|
471
|
+
return null;
|
|
472
|
+
const collectionText = sourceCode.getText(callee.object);
|
|
473
|
+
return { methodName: method, collectionText };
|
|
474
|
+
}
|
|
475
|
+
function isLodashMethodCall(node) {
|
|
476
|
+
const method = getMethodNameFromMemberCallee(node.callee);
|
|
477
|
+
if (_.isNil(method))
|
|
478
|
+
return null;
|
|
479
|
+
if (isLodashStaticCall(node, method))
|
|
480
|
+
return { mode: 'static', methodName: method };
|
|
481
|
+
if (isLodashWrapperMethodCall(node, method))
|
|
482
|
+
return { mode: 'wrapper', methodName: method };
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
function reportAndFixReplaceCallWithLodash(callNode, methodName, collectionText, iterateeText, fixer) {
|
|
486
|
+
const replacement = `_.${methodName}(${collectionText}, ${iterateeText})`;
|
|
487
|
+
const target = callNode.parent?.type === AST_NODE_TYPES.ChainExpression ? callNode.parent : callNode;
|
|
488
|
+
const fixes = [fixer.replaceText(target, replacement)];
|
|
489
|
+
const importFix = ensureLodashImport(fixer);
|
|
490
|
+
if (!_.isNil(importFix))
|
|
491
|
+
fixes.push(importFix);
|
|
492
|
+
return fixes;
|
|
493
|
+
}
|
|
494
|
+
function reportAndFixReplaceArgument(nodeToFix, replacementText, fixer) {
|
|
495
|
+
const fixes = [fixer.replaceText(nodeToFix, replacementText)];
|
|
496
|
+
const importFix = ensureLodashImport(fixer);
|
|
497
|
+
if (!_.isNil(importFix))
|
|
498
|
+
fixes.push(importFix);
|
|
499
|
+
return fixes;
|
|
500
|
+
}
|
|
501
|
+
function getNativeIterateeKind(methodName) {
|
|
502
|
+
const lodashMethod = NATIVE_TO_LODASH_METHOD_NAMES[methodName];
|
|
503
|
+
if (PREDICATE_METHODS.has(lodashMethod))
|
|
504
|
+
return 'predicate';
|
|
505
|
+
if (ITERATEE_METHODS.has(lodashMethod))
|
|
506
|
+
return 'iteratee';
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
CallExpression(node) {
|
|
511
|
+
const nativeCall = isNativeArrayMethodCall(node);
|
|
512
|
+
if (!_.isNil(nativeCall)) {
|
|
513
|
+
const arg0 = node.arguments[0];
|
|
514
|
+
if (_.isNil(arg0) || arg0.type === AST_NODE_TYPES.SpreadElement)
|
|
515
|
+
return;
|
|
516
|
+
if (!isFunctionLike(arg0))
|
|
517
|
+
return;
|
|
518
|
+
const lodashMethod = NATIVE_TO_LODASH_METHOD_NAMES[nativeCall.methodName];
|
|
519
|
+
const kind = getNativeIterateeKind(nativeCall.methodName);
|
|
520
|
+
if (_.isNil(kind))
|
|
521
|
+
return;
|
|
522
|
+
if (kind === 'predicate') {
|
|
523
|
+
const expr = getReturnedExpression(arg0);
|
|
524
|
+
if (_.isNil(expr))
|
|
525
|
+
return;
|
|
526
|
+
const parameterName = getFunctionParameterName(arg0);
|
|
527
|
+
if (_.isNil(parameterName))
|
|
528
|
+
return;
|
|
529
|
+
const clauses = extractPredicateClausesFromExpression(expr, parameterName);
|
|
530
|
+
if (_.isNil(clauses) || _.isEmpty(clauses))
|
|
531
|
+
return;
|
|
532
|
+
const matchesObject = buildMatchesObjectFromClauses(clauses);
|
|
533
|
+
if (_.isNil(matchesObject))
|
|
534
|
+
return;
|
|
535
|
+
const replacement = `_.${lodashMethod}(${nativeCall.collectionText}, ${matchesObject})`;
|
|
536
|
+
context.report({
|
|
537
|
+
node,
|
|
538
|
+
messageId: 'useLodashMethod',
|
|
539
|
+
data: { replacement },
|
|
540
|
+
fix(fixer) {
|
|
541
|
+
return reportAndFixReplaceCallWithLodash(node, lodashMethod, nativeCall.collectionText, matchesObject, fixer);
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (kind === 'iteratee') {
|
|
547
|
+
const replacementIteratee = extractPropertyIterateeRewrite(arg0);
|
|
548
|
+
if (_.isNil(replacementIteratee))
|
|
549
|
+
return;
|
|
550
|
+
const replacement = `_.${lodashMethod}(${nativeCall.collectionText}, ${replacementIteratee})`;
|
|
551
|
+
context.report({
|
|
552
|
+
node,
|
|
553
|
+
messageId: 'useLodashMethod',
|
|
554
|
+
data: { replacement },
|
|
555
|
+
fix(fixer) {
|
|
556
|
+
return reportAndFixReplaceCallWithLodash(node, lodashMethod, nativeCall.collectionText, replacementIteratee, fixer);
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const lodashCall = isLodashMethodCall(node);
|
|
563
|
+
if (_.isNil(lodashCall))
|
|
564
|
+
return;
|
|
565
|
+
if (PREDICATE_METHODS.has(lodashCall.methodName)) {
|
|
566
|
+
const predicateIndex = lodashCall.mode === 'static' ? 1 : 0;
|
|
567
|
+
const predicateArg = node.arguments[predicateIndex];
|
|
568
|
+
if (_.isNil(predicateArg) || predicateArg.type === AST_NODE_TYPES.SpreadElement)
|
|
569
|
+
return;
|
|
570
|
+
if (isFunctionLike(predicateArg)) {
|
|
571
|
+
const expr = getReturnedExpression(predicateArg);
|
|
572
|
+
if (_.isNil(expr))
|
|
573
|
+
return;
|
|
574
|
+
const parameterName = getFunctionParameterName(predicateArg);
|
|
575
|
+
if (_.isNil(parameterName))
|
|
576
|
+
return;
|
|
577
|
+
const clauses = extractPredicateClausesFromExpression(expr, parameterName);
|
|
578
|
+
if (_.isNil(clauses) || _.isEmpty(clauses))
|
|
579
|
+
return;
|
|
580
|
+
const matchesObject = buildMatchesObjectFromClauses(clauses);
|
|
581
|
+
if (_.isNil(matchesObject))
|
|
582
|
+
return;
|
|
583
|
+
context.report({
|
|
584
|
+
node: predicateArg,
|
|
585
|
+
messageId: 'useMatchesObject',
|
|
586
|
+
data: { replacement: matchesObject },
|
|
587
|
+
fix(fixer) {
|
|
588
|
+
return reportAndFixReplaceArgument(predicateArg, matchesObject, fixer);
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (ITERATEE_METHODS.has(lodashCall.methodName)) {
|
|
595
|
+
const iterateeIndex = lodashCall.mode === 'static' ? 1 : 0;
|
|
596
|
+
const iterateeArg = node.arguments[iterateeIndex];
|
|
597
|
+
if (_.isNil(iterateeArg) || iterateeArg.type === AST_NODE_TYPES.SpreadElement)
|
|
598
|
+
return;
|
|
599
|
+
if (iterateeArg.type === AST_NODE_TYPES.ArrayExpression && (lodashCall.methodName === 'sortBy' || lodashCall.methodName === 'orderBy')) {
|
|
600
|
+
const rewrites = [];
|
|
601
|
+
for (const element of iterateeArg.elements) {
|
|
602
|
+
if (_.isNil(element) || element.type === AST_NODE_TYPES.SpreadElement)
|
|
603
|
+
continue;
|
|
604
|
+
if (!isFunctionLike(element))
|
|
605
|
+
continue;
|
|
606
|
+
const replacement = extractPropertyIterateeRewrite(element);
|
|
607
|
+
if (!_.isNil(replacement))
|
|
608
|
+
rewrites.push({ element, replacement });
|
|
609
|
+
}
|
|
610
|
+
if (_.isEmpty(rewrites))
|
|
611
|
+
return;
|
|
612
|
+
context.report({
|
|
613
|
+
node: iterateeArg,
|
|
614
|
+
messageId: 'usePropertyShorthand',
|
|
615
|
+
data: { replacement: sourceCode.getText(iterateeArg) },
|
|
616
|
+
fix(fixer) {
|
|
617
|
+
const fixes = rewrites.map(({ element, replacement }) => fixer.replaceText(element, replacement));
|
|
618
|
+
const importFix = ensureLodashImport(fixer);
|
|
619
|
+
if (!_.isNil(importFix))
|
|
620
|
+
fixes.push(importFix);
|
|
621
|
+
return fixes;
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (isFunctionLike(iterateeArg)) {
|
|
627
|
+
const replacement = extractPropertyIterateeRewrite(iterateeArg);
|
|
628
|
+
if (_.isNil(replacement))
|
|
629
|
+
return;
|
|
630
|
+
context.report({
|
|
631
|
+
node: iterateeArg,
|
|
632
|
+
messageId: 'usePropertyShorthand',
|
|
633
|
+
data: { replacement },
|
|
634
|
+
fix(fixer) {
|
|
635
|
+
return reportAndFixReplaceArgument(iterateeArg, replacement, fixer);
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
};
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
export default preferLodashIterateeShorthand;
|