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 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: number;
16
- maximumLineLength: number;
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: number;
68
- maximumLineLength: number;
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
  };
@@ -1 +1 @@
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
+ {"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
- declare const noDestructuring: ESLintUtils.RuleModule<"tooDeep" | "tooMany" | "tooLong", [{
3
- maximumDestructuredVariables: number;
4
- maximumLineLength: number;
5
- }], unknown, ESLintUtils.RuleListener> & {
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;;;;;CAyInB,CAAC;AACH,eAAe,eAAe,CAAC"}
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 MAX_TAB_COUNT = 3;
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 a nesting level above {{max}} is not allowed; found {{actual}} levels of nesting.',
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 MAX_VARIABLES = options.maximumDestructuredVariables ?? 2;
37
- const MAX_LINE_LENGTH = options.maximumLineLength ?? 100;
38
- function reportIfNeeded(patternNode, reportNode = patternNode) {
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 lineText = context.sourceCode.lines[startLine - 1] ?? '';
45
- const indentCount = lineText.search(/\S|$/);
106
+ const indentSpaces = getIndentSpacesForLine(startLine);
46
107
  const propertyCount = patternNode.properties?.length ?? 0;
47
- let maxSpannedLineLength = 0;
48
- for (let i = startLine; i <= endLine; i++) {
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 > MAX_VARIABLES) {
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 > MAX_LINE_LENGTH) {
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;;CAwpBjC,CAAC;AAEH,eAAe,6BAA6B,CAAC"}
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 === 'member') {
406
- const keyText = buildObjectKeyTextForMemberPath(clause.path);
407
- if (_.isNil(keyText))
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(`{${keyText}: ${valueText}}`);
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
- 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);
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(unwrapped, parameterName);
491
+ const get = isGetCallOnParameter(expr, parameterName);
446
492
  if (!_.isNil(get) && get.kind === 'get') {
447
493
  return get.pathText;
448
494
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-th-rules",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "A List of custom ESLint rules created by Tomer Horowitz",
5
5
  "keywords": [
6
6
  "eslint",