eslint-plugin-th-rules 3.5.0 → 3.6.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/plugin.d.ts +8 -6
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/no-destructuring.d.ts +9 -4
- package/dist/rules/no-destructuring.d.ts.map +1 -1
- package/dist/rules/no-destructuring.js +98 -31
- package/dist/rules/prefer-lodash-iteratee-shorthand.d.ts.map +1 -1
- package/dist/rules/prefer-lodash-iteratee-shorthand.js +117 -71
- package/package.json +1 -1
package/dist/plugin.d.ts
CHANGED
|
@@ -11,9 +11,10 @@ export declare const rules: {
|
|
|
11
11
|
'no-default-export': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnamed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
12
12
|
name: string;
|
|
13
13
|
};
|
|
14
|
-
'no-destructuring': import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
|
|
15
|
-
maximumDestructuredVariables
|
|
16
|
-
maximumLineLength
|
|
14
|
+
'no-destructuring': import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong" | "tooManyCumulative" | "directAccessRequired", [{
|
|
15
|
+
maximumDestructuredVariables?: number;
|
|
16
|
+
maximumLineLength?: number;
|
|
17
|
+
directAccessIdentifiers?: string[];
|
|
17
18
|
}], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
18
19
|
name: string;
|
|
19
20
|
};
|
|
@@ -63,9 +64,10 @@ declare const plugin: {
|
|
|
63
64
|
'no-default-export': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unnamed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
64
65
|
name: string;
|
|
65
66
|
};
|
|
66
|
-
'no-destructuring': import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
|
|
67
|
-
maximumDestructuredVariables
|
|
68
|
-
maximumLineLength
|
|
67
|
+
'no-destructuring': import("@typescript-eslint/utils/ts-eslint").RuleModule<"tooDeep" | "tooMany" | "tooLong" | "tooManyCumulative" | "directAccessRequired", [{
|
|
68
|
+
maximumDestructuredVariables?: number;
|
|
69
|
+
maximumLineLength?: number;
|
|
70
|
+
directAccessIdentifiers?: string[];
|
|
69
71
|
}], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
70
72
|
name: string;
|
|
71
73
|
};
|
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"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
maximumDestructuredVariables?: number;
|
|
5
|
+
maximumLineLength?: number;
|
|
6
|
+
directAccessIdentifiers?: string[];
|
|
7
|
+
}
|
|
8
|
+
];
|
|
9
|
+
type MessageIds = 'tooDeep' | 'tooMany' | 'tooLong' | 'tooManyCumulative' | 'directAccessRequired';
|
|
10
|
+
declare const noDestructuring: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
|
|
6
11
|
name: string;
|
|
7
12
|
};
|
|
8
13
|
export default noDestructuring;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-destructuring.d.ts","sourceRoot":"","sources":["../../src/rules/no-destructuring.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItF,QAAA,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"no-destructuring.d.ts","sourceRoot":"","sources":["../../src/rules/no-destructuring.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItF,KAAK,OAAO,GAAG;IACd;QACC,4BAA4B,CAAC,EAAE,MAAM,CAAC;QACtC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;KACnC;CACD,CAAC;AAEF,KAAK,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,GAAG,sBAAsB,CAAC;AAEnG,QAAA,MAAM,eAAe;;CA0NnB,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* eslint-disable new-cap */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
2
|
import _ from 'lodash';
|
|
4
3
|
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
5
|
-
const
|
|
4
|
+
const MAX_INDENT_SPACES = 3;
|
|
6
5
|
const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-destructuring.md')({
|
|
7
6
|
name: 'no-destructuring',
|
|
8
7
|
meta: {
|
|
@@ -16,69 +15,137 @@ const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh
|
|
|
16
15
|
properties: {
|
|
17
16
|
maximumDestructuredVariables: { type: 'integer', minimum: 0 },
|
|
18
17
|
maximumLineLength: { type: 'integer', minimum: 0 },
|
|
18
|
+
directAccessIdentifiers: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
items: { type: 'string', minLength: 1 },
|
|
21
|
+
},
|
|
19
22
|
},
|
|
20
23
|
additionalProperties: false,
|
|
21
24
|
},
|
|
22
25
|
],
|
|
23
26
|
messages: {
|
|
24
|
-
tooDeep: 'Destructuring at
|
|
27
|
+
tooDeep: 'Destructuring at an indentation above {{max}} is not allowed; found {{actual}}.',
|
|
25
28
|
tooMany: 'Destructuring of more than {{max}} variables is not allowed.',
|
|
26
29
|
tooLong: 'Destructuring spanning a line exceeding {{max}} characters is not allowed.',
|
|
30
|
+
tooManyCumulative: 'Too many destructured variables from "{{source}}" in the same scope. Max is {{max}}, total is {{total}}.',
|
|
31
|
+
directAccessRequired: 'Do not destructure from "{{identifier}}". Use direct member access, for example {{identifier}}.onChangeText.',
|
|
27
32
|
},
|
|
28
33
|
},
|
|
29
34
|
defaultOptions: [
|
|
30
35
|
{
|
|
31
36
|
maximumDestructuredVariables: 2,
|
|
32
37
|
maximumLineLength: 100,
|
|
38
|
+
directAccessIdentifiers: ['properties'],
|
|
33
39
|
},
|
|
34
40
|
],
|
|
35
41
|
create(context, [options]) {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
42
|
+
const maxVariables = options.maximumDestructuredVariables ?? 2;
|
|
43
|
+
const maxLineLength = options.maximumLineLength ?? 100;
|
|
44
|
+
const directAccessIdentifiers = new Set(options.directAccessIdentifiers ?? ['properties']);
|
|
45
|
+
const sourceTotalsByScope = new WeakMap();
|
|
46
|
+
function getLineText(lineNumber) {
|
|
47
|
+
return context.sourceCode.lines[lineNumber - 1] ?? '';
|
|
48
|
+
}
|
|
49
|
+
function getIndentSpacesForLine(lineNumber) {
|
|
50
|
+
const lineText = getLineText(lineNumber);
|
|
51
|
+
return lineText.search(/\S|$/);
|
|
52
|
+
}
|
|
53
|
+
function getMaxSpannedLineLength(startLine, endLine) {
|
|
54
|
+
let max = 0;
|
|
55
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
56
|
+
const text = getLineText(i);
|
|
57
|
+
if (text.length > max) {
|
|
58
|
+
max = text.length;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return max;
|
|
62
|
+
}
|
|
63
|
+
function getScopeNode(node) {
|
|
64
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
65
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
66
|
+
const a = ancestors[i];
|
|
67
|
+
if (a.type === AST_NODE_TYPES.Program ||
|
|
68
|
+
a.type === AST_NODE_TYPES.FunctionDeclaration ||
|
|
69
|
+
a.type === AST_NODE_TYPES.FunctionExpression ||
|
|
70
|
+
a.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
71
|
+
a.type === AST_NODE_TYPES.TSDeclareFunction) {
|
|
72
|
+
return a;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return context.sourceCode.ast;
|
|
76
|
+
}
|
|
77
|
+
function getOrCreateScopeMap(scope) {
|
|
78
|
+
const existing = sourceTotalsByScope.get(scope);
|
|
79
|
+
if (!_.isNil(existing)) {
|
|
80
|
+
return existing;
|
|
81
|
+
}
|
|
82
|
+
const created = new Map();
|
|
83
|
+
sourceTotalsByScope.set(scope, created);
|
|
84
|
+
return created;
|
|
85
|
+
}
|
|
86
|
+
function isDirectAccessForbiddenInitializer(init) {
|
|
87
|
+
if (_.isNil(init)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return init.type === AST_NODE_TYPES.Identifier && directAccessIdentifiers.has(init.name);
|
|
91
|
+
}
|
|
92
|
+
function reportIfNeeded(patternNode, reportNode, initExpression) {
|
|
39
93
|
if (patternNode?.type !== AST_NODE_TYPES.ObjectPattern || _.isNil(patternNode.loc)) {
|
|
40
94
|
return;
|
|
41
95
|
}
|
|
96
|
+
if (isDirectAccessForbiddenInitializer(initExpression)) {
|
|
97
|
+
context.report({
|
|
98
|
+
node: reportNode,
|
|
99
|
+
messageId: 'directAccessRequired',
|
|
100
|
+
data: { identifier: initExpression.name },
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
42
104
|
const startLine = patternNode.loc.start.line;
|
|
43
105
|
const endLine = patternNode.loc.end.line;
|
|
44
|
-
const
|
|
45
|
-
const indentCount = lineText.search(/\S|$/);
|
|
106
|
+
const indentSpaces = getIndentSpacesForLine(startLine);
|
|
46
107
|
const propertyCount = patternNode.properties?.length ?? 0;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const t = context.sourceCode.lines[i - 1] ?? '';
|
|
50
|
-
if (t.length > maxSpannedLineLength) {
|
|
51
|
-
maxSpannedLineLength = t.length;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (indentCount > MAX_TAB_COUNT) {
|
|
108
|
+
const maxSpannedLineLength = getMaxSpannedLineLength(startLine, endLine);
|
|
109
|
+
if (indentSpaces > MAX_INDENT_SPACES) {
|
|
55
110
|
context.report({
|
|
56
111
|
node: reportNode,
|
|
57
112
|
messageId: 'tooDeep',
|
|
58
|
-
data: {
|
|
59
|
-
max: MAX_TAB_COUNT,
|
|
60
|
-
actual: indentCount,
|
|
61
|
-
},
|
|
113
|
+
data: { max: MAX_INDENT_SPACES, actual: indentSpaces },
|
|
62
114
|
});
|
|
63
115
|
}
|
|
64
|
-
if (propertyCount >
|
|
116
|
+
if (propertyCount > maxVariables) {
|
|
65
117
|
context.report({
|
|
66
118
|
node: reportNode,
|
|
67
119
|
messageId: 'tooMany',
|
|
68
|
-
data: {
|
|
69
|
-
max: MAX_VARIABLES,
|
|
70
|
-
},
|
|
120
|
+
data: { max: maxVariables },
|
|
71
121
|
});
|
|
72
122
|
}
|
|
73
|
-
if (maxSpannedLineLength >
|
|
123
|
+
if (maxSpannedLineLength > maxLineLength) {
|
|
74
124
|
context.report({
|
|
75
125
|
node: reportNode,
|
|
76
126
|
messageId: 'tooLong',
|
|
77
|
-
data: {
|
|
78
|
-
max: MAX_LINE_LENGTH,
|
|
79
|
-
},
|
|
127
|
+
data: { max: maxLineLength },
|
|
80
128
|
});
|
|
81
129
|
}
|
|
130
|
+
if (!_.isNil(initExpression)) {
|
|
131
|
+
const scopeNode = getScopeNode(reportNode);
|
|
132
|
+
const scopeMap = getOrCreateScopeMap(scopeNode);
|
|
133
|
+
const sourceText = context.sourceCode.getText(initExpression);
|
|
134
|
+
const previousTotal = scopeMap.get(sourceText) ?? 0;
|
|
135
|
+
const newTotal = previousTotal + propertyCount;
|
|
136
|
+
scopeMap.set(sourceText, newTotal);
|
|
137
|
+
if (newTotal > maxVariables) {
|
|
138
|
+
context.report({
|
|
139
|
+
node: reportNode,
|
|
140
|
+
messageId: 'tooManyCumulative',
|
|
141
|
+
data: {
|
|
142
|
+
source: sourceText,
|
|
143
|
+
max: maxVariables,
|
|
144
|
+
total: newTotal,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
82
149
|
}
|
|
83
150
|
function checkParameters(parameters) {
|
|
84
151
|
for (const p of parameters || []) {
|
|
@@ -86,15 +153,15 @@ const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh
|
|
|
86
153
|
continue;
|
|
87
154
|
}
|
|
88
155
|
if (p.type === AST_NODE_TYPES.AssignmentPattern) {
|
|
89
|
-
reportIfNeeded(p.left, p);
|
|
156
|
+
reportIfNeeded(p.left, p, undefined);
|
|
90
157
|
continue;
|
|
91
158
|
}
|
|
92
|
-
reportIfNeeded(p, p);
|
|
159
|
+
reportIfNeeded(p, p, undefined);
|
|
93
160
|
}
|
|
94
161
|
}
|
|
95
162
|
return {
|
|
96
163
|
VariableDeclarator(node) {
|
|
97
|
-
reportIfNeeded(node.id, node);
|
|
164
|
+
reportIfNeeded(node.id, node, node.init);
|
|
98
165
|
},
|
|
99
166
|
FunctionDeclaration(node) {
|
|
100
167
|
checkParameters(node.params);
|
|
@@ -1 +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;;
|
|
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;;CAmsBjC,CAAC;AAEH,eAAe,6BAA6B,CAAC"}
|
|
@@ -100,6 +100,9 @@ const preferLodashIterateeShorthand = ESLintUtils.RuleCreator(() => `https://git
|
|
|
100
100
|
function isStringLiteral(node) {
|
|
101
101
|
return !_.isNil(node) && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string';
|
|
102
102
|
}
|
|
103
|
+
function isNumberLiteral(node) {
|
|
104
|
+
return !_.isNil(node) && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'number';
|
|
105
|
+
}
|
|
103
106
|
function isTemplateLiteralWithoutExpressions(node) {
|
|
104
107
|
return !_.isNil(node) && node.type === AST_NODE_TYPES.TemplateLiteral && _.isEmpty(node.expressions) && node.quasis.length === 1;
|
|
105
108
|
}
|
|
@@ -154,57 +157,6 @@ const preferLodashIterateeShorthand = ESLintUtils.RuleCreator(() => `https://git
|
|
|
154
157
|
function isFunctionLike(node) {
|
|
155
158
|
return !_.isNil(node) && (node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression);
|
|
156
159
|
}
|
|
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
160
|
function normalizeStaticPathText(pathText) {
|
|
209
161
|
const trimmed = pathText.trim();
|
|
210
162
|
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
|
@@ -222,6 +174,27 @@ const preferLodashIterateeShorthand = ESLintUtils.RuleCreator(() => `https://git
|
|
|
222
174
|
const escaped = value.replaceAll('\\', '\\\\').replaceAll("'", String.raw `\'`);
|
|
223
175
|
return `'${escaped}'`;
|
|
224
176
|
}
|
|
177
|
+
function toDoubleQuotedRaw(value) {
|
|
178
|
+
return value.replaceAll('\\', '\\\\').replaceAll('"', String.raw `\"`);
|
|
179
|
+
}
|
|
180
|
+
function buildLodashPathStringFromSegments(segments) {
|
|
181
|
+
if (_.isEmpty(segments))
|
|
182
|
+
return null;
|
|
183
|
+
let out = '';
|
|
184
|
+
for (const seg of segments) {
|
|
185
|
+
if (typeof seg === 'number') {
|
|
186
|
+
out += `[${seg}]`;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (isValidIdentifierName(seg)) {
|
|
190
|
+
out += _.isEmpty(out) ? seg : `.${seg}`;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const inner = toDoubleQuotedRaw(seg);
|
|
194
|
+
out += `["${inner}"]`;
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
225
198
|
function containsIdentifier(node, name) {
|
|
226
199
|
const { visitorKeys } = sourceCode;
|
|
227
200
|
const seen = new Set();
|
|
@@ -353,6 +326,80 @@ const preferLodashIterateeShorthand = ESLintUtils.RuleCreator(() => `https://git
|
|
|
353
326
|
return '{}';
|
|
354
327
|
return `{${props.join(', ')}}`;
|
|
355
328
|
}
|
|
329
|
+
function isGetCallOnParameter(expr, parameterName) {
|
|
330
|
+
const unwrapped = unwrapChain(expr);
|
|
331
|
+
if (_.isNil(unwrapped) || unwrapped.type !== AST_NODE_TYPES.CallExpression)
|
|
332
|
+
return null;
|
|
333
|
+
const callee = unwrapChain(unwrapped.callee);
|
|
334
|
+
if (!isLodashMember(callee, 'get'))
|
|
335
|
+
return null;
|
|
336
|
+
if (unwrapped.arguments.length < 2)
|
|
337
|
+
return null;
|
|
338
|
+
const arg0 = unwrapped.arguments[0];
|
|
339
|
+
const arg1 = unwrapped.arguments[1];
|
|
340
|
+
if (_.isNil(arg0) || arg0.type === AST_NODE_TYPES.SpreadElement)
|
|
341
|
+
return null;
|
|
342
|
+
if (_.isNil(arg1) || arg1.type === AST_NODE_TYPES.SpreadElement)
|
|
343
|
+
return null;
|
|
344
|
+
const object = unwrapChain(arg0);
|
|
345
|
+
if (_.isNil(object) || object.type !== AST_NODE_TYPES.Identifier || object.name !== parameterName)
|
|
346
|
+
return null;
|
|
347
|
+
if (isStringLiteral(arg1)) {
|
|
348
|
+
return { kind: 'get', pathText: sourceCode.getText(arg1), pathIsStaticString: true };
|
|
349
|
+
}
|
|
350
|
+
if (isTemplateLiteralWithoutExpressions(arg1)) {
|
|
351
|
+
return { kind: 'get', pathText: sourceCode.getText(arg1), pathIsStaticString: true };
|
|
352
|
+
}
|
|
353
|
+
if (arg1.type === AST_NODE_TYPES.Identifier) {
|
|
354
|
+
return { kind: 'get', pathText: arg1.name, pathIsStaticString: false };
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
function extractMemberPathSegments(expr, parameterName) {
|
|
359
|
+
const unwrapped = unwrapChainExpr(expr) ?? expr;
|
|
360
|
+
const segments = [];
|
|
361
|
+
let current = unwrapped;
|
|
362
|
+
while (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
363
|
+
if (current.computed) {
|
|
364
|
+
const prop = unwrapChain(current.property);
|
|
365
|
+
if (!_.isNil(prop) && prop.type === AST_NODE_TYPES.Identifier)
|
|
366
|
+
return null;
|
|
367
|
+
if (isStringLiteral(prop)) {
|
|
368
|
+
segments.unshift(prop.value);
|
|
369
|
+
}
|
|
370
|
+
else if (isNumberLiteral(prop)) {
|
|
371
|
+
segments.unshift(prop.value);
|
|
372
|
+
}
|
|
373
|
+
else if (isTemplateLiteralWithoutExpressions(prop)) {
|
|
374
|
+
const cooked = prop.quasis[0]?.value.cooked ?? prop.quasis[0]?.value.raw ?? '';
|
|
375
|
+
segments.unshift(cooked);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
if (current.property.type !== AST_NODE_TYPES.Identifier)
|
|
383
|
+
return null;
|
|
384
|
+
segments.unshift(current.property.name);
|
|
385
|
+
}
|
|
386
|
+
const object = unwrapChain(current.object);
|
|
387
|
+
if (_.isNil(object))
|
|
388
|
+
return null;
|
|
389
|
+
current = object;
|
|
390
|
+
}
|
|
391
|
+
if (current.type !== AST_NODE_TYPES.Identifier || current.name !== parameterName)
|
|
392
|
+
return null;
|
|
393
|
+
if (_.isEmpty(segments))
|
|
394
|
+
return null;
|
|
395
|
+
return segments;
|
|
396
|
+
}
|
|
397
|
+
function extractPathFromExpression(expr, parameterName) {
|
|
398
|
+
const memberSegments = extractMemberPathSegments(expr, parameterName);
|
|
399
|
+
if (!_.isNil(memberSegments))
|
|
400
|
+
return { kind: 'memberPath', segments: memberSegments };
|
|
401
|
+
return isGetCallOnParameter(expr, parameterName);
|
|
402
|
+
}
|
|
356
403
|
function extractPredicateClausesFromExpression(expr, parameterName) {
|
|
357
404
|
const unwrapped = unwrapChainExpr(expr);
|
|
358
405
|
if (_.isNil(unwrapped))
|
|
@@ -392,21 +439,15 @@ const preferLodashIterateeShorthand = ESLintUtils.RuleCreator(() => `https://git
|
|
|
392
439
|
}
|
|
393
440
|
return clauses;
|
|
394
441
|
}
|
|
395
|
-
function buildObjectKeyTextForMemberPath(path) {
|
|
396
|
-
if (path.keyIsIdentifier) {
|
|
397
|
-
return path.keyText;
|
|
398
|
-
}
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
401
442
|
function buildMatchesObjectFromClauses(clauses) {
|
|
402
443
|
const literals = [];
|
|
403
444
|
for (const clause of clauses) {
|
|
404
445
|
const valueText = sourceCode.getText(clause.valueExpr);
|
|
405
|
-
if (clause.path.kind === '
|
|
406
|
-
const
|
|
407
|
-
if (_.isNil(
|
|
446
|
+
if (clause.path.kind === 'memberPath') {
|
|
447
|
+
const nested = buildNestedLiteralFromSegments(clause.path.segments, valueText);
|
|
448
|
+
if (_.isNil(nested))
|
|
408
449
|
return null;
|
|
409
|
-
literals.push(
|
|
450
|
+
literals.push(nested);
|
|
410
451
|
continue;
|
|
411
452
|
}
|
|
412
453
|
if (!clause.path.pathIsStaticString) {
|
|
@@ -430,19 +471,24 @@ const preferLodashIterateeShorthand = ESLintUtils.RuleCreator(() => `https://git
|
|
|
430
471
|
const expr = getReturnedExpression(fn);
|
|
431
472
|
if (_.isNil(expr))
|
|
432
473
|
return null;
|
|
474
|
+
const memberSegments = extractMemberPathSegments(expr, parameterName);
|
|
475
|
+
if (!_.isNil(memberSegments)) {
|
|
476
|
+
const raw = buildLodashPathStringFromSegments(memberSegments);
|
|
477
|
+
if (_.isNil(raw))
|
|
478
|
+
return null;
|
|
479
|
+
return toSingleQuotedStringLiteral(raw);
|
|
480
|
+
}
|
|
433
481
|
const unwrapped = unwrapChainExpr(expr) ?? expr;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (!
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
482
|
+
if (unwrapped.type === AST_NODE_TYPES.MemberExpression) {
|
|
483
|
+
const object = unwrapChain(unwrapped.object);
|
|
484
|
+
if (!_.isNil(object) && object.type === AST_NODE_TYPES.Identifier && object.name === parameterName && unwrapped.computed) {
|
|
485
|
+
const prop = unwrapChain(unwrapped.property);
|
|
486
|
+
if (!_.isNil(prop) && prop.type === AST_NODE_TYPES.Identifier) {
|
|
487
|
+
return prop.name;
|
|
488
|
+
}
|
|
441
489
|
}
|
|
442
|
-
const normalized = normalizeStaticPathText(member.keyText);
|
|
443
|
-
return toSingleQuotedStringLiteral(normalized);
|
|
444
490
|
}
|
|
445
|
-
const get = isGetCallOnParameter(
|
|
491
|
+
const get = isGetCallOnParameter(expr, parameterName);
|
|
446
492
|
if (!_.isNil(get) && get.kind === 'get') {
|
|
447
493
|
return get.pathText;
|
|
448
494
|
}
|