@blumintinc/eslint-plugin-blumint 1.14.0 → 1.16.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.
Files changed (68) hide show
  1. package/README.md +36 -14
  2. package/lib/index.js +20 -12
  3. package/lib/rules/avoid-utils-directory.js +0 -4
  4. package/lib/rules/consistent-callback-naming.js +68 -3
  5. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  6. package/lib/rules/dynamic-https-errors.js +119 -49
  7. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +86 -331
  9. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  10. package/lib/rules/enforce-date-ttime.js +156 -0
  11. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  12. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  13. package/lib/rules/enforce-dynamic-imports.d.ts +2 -1
  14. package/lib/rules/enforce-dynamic-imports.js +42 -21
  15. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  16. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  17. package/lib/rules/enforce-global-constants.js +3 -3
  18. package/lib/rules/enforce-id-capitalization.js +1 -1
  19. package/lib/rules/enforce-memoize-async.js +66 -15
  20. package/lib/rules/enforce-mui-rounded-icons.js +42 -1
  21. package/lib/rules/enforce-props-argument-name.js +42 -16
  22. package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
  23. package/lib/rules/enforce-transform-memoization.js +1 -1
  24. package/lib/rules/enforce-verb-noun-naming.js +3817 -4641
  25. package/lib/rules/global-const-style.js +25 -4
  26. package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
  27. package/lib/rules/memo-compare-deeply-complex-props.js +183 -6
  28. package/lib/rules/memo-nested-react-components.js +243 -103
  29. package/lib/rules/no-array-length-in-deps.js +74 -3
  30. package/lib/rules/no-async-foreach.js +7 -2
  31. package/lib/rules/no-circular-references.d.ts +2 -1
  32. package/lib/rules/no-circular-references.js +150 -489
  33. package/lib/rules/no-compositing-layer-props.js +31 -0
  34. package/lib/rules/no-console-error.js +12 -10
  35. package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
  36. package/lib/rules/no-entire-object-hook-deps.js +147 -65
  37. package/lib/rules/no-excessive-parent-chain.js +3 -0
  38. package/lib/rules/no-explicit-return-type.js +6 -0
  39. package/lib/rules/no-hungarian.js +119 -24
  40. package/lib/rules/no-inline-component-prop.js +16 -7
  41. package/lib/rules/no-margin-properties.js +7 -38
  42. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  43. package/lib/rules/no-passthrough-getters.js +83 -1
  44. package/lib/rules/no-redundant-this-params.js +50 -1
  45. package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
  46. package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
  47. package/lib/rules/no-unnecessary-verb-suffix.js +79 -0
  48. package/lib/rules/no-unused-props.js +215 -37
  49. package/lib/rules/no-useless-fragment.js +10 -2
  50. package/lib/rules/parallelize-async-operations.js +117 -54
  51. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
  52. package/lib/rules/prefer-params-over-parent-id.js +1 -1
  53. package/lib/rules/prefer-settings-object.js +27 -10
  54. package/lib/rules/prefer-type-alias-over-typeof-constant.js +75 -4
  55. package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
  56. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  57. package/lib/rules/prevent-children-clobber.js +9 -5
  58. package/lib/rules/react-memoize-literals.js +218 -13
  59. package/lib/rules/require-https-error-cause.js +30 -11
  60. package/lib/rules/require-memo.js +17 -9
  61. package/lib/rules/require-migration-script-metadata.d.ts +9 -0
  62. package/lib/rules/require-migration-script-metadata.js +206 -0
  63. package/lib/rules/warn-https-error-message-user-friendly.d.ts +1 -0
  64. package/lib/rules/warn-https-error-message-user-friendly.js +239 -0
  65. package/lib/utils/ASTHelpers.d.ts +49 -1
  66. package/lib/utils/ASTHelpers.js +394 -112
  67. package/package.json +7 -6
  68. package/release-manifest.json +166 -0
@@ -17,8 +17,10 @@ const COMMON_TYPES = [
17
17
  'Symbol',
18
18
  'BigInt',
19
19
  ];
20
- // Combined type markers (former Hungarian prefixes and type suffixes)
21
- const TYPE_MARKERS = [
20
+ // Abbreviation type markers (e.g. str, arr, obj). No English word is spelled this
21
+ // way, so their presence as a segment — even in the middle of a name — is
22
+ // unambiguously a type tag (strName, USER_STR_NAME, ConfigArrSettings).
23
+ const ABBREVIATION_MARKERS = [
22
24
  'str',
23
25
  'num',
24
26
  'int',
@@ -27,6 +29,10 @@ const TYPE_MARKERS = [
27
29
  'obj',
28
30
  'fn',
29
31
  'func',
32
+ ];
33
+ // Combined type markers (former Hungarian prefixes and type suffixes)
34
+ const TYPE_MARKERS = [
35
+ ...ABBREVIATION_MARKERS,
30
36
  'array',
31
37
  ...COMMON_TYPES,
32
38
  'Class',
@@ -34,6 +40,20 @@ const TYPE_MARKERS = [
34
40
  //'Type', people like to use 'type' as a general purpose noun
35
41
  'Enum',
36
42
  ];
43
+ const ABBREVIATION_MARKER_SET = new Set(ABBREVIATION_MARKERS);
44
+ // Single-letter Hungarian type prefixes (b=boolean, i=integer/index).
45
+ // Only matched as a strict camelCase prefix (e.g. bIsActive, iCount); never as
46
+ // a suffix/middle/SCREAMING_SNAKE segment, where a lone letter is almost always
47
+ // a real word fragment (tab, lib, ui) rather than a type tag.
48
+ const SINGLE_LETTER_PREFIXES = new Set(['b', 'i']);
49
+ // Full type-concept words (spelled out). When one of these appears as a clean
50
+ // PascalCase segment inside a multi-word TYPE name (alias/interface/class), it
51
+ // denotes a type concept/relation (e.g. StringToNumber, CapitalizedString,
52
+ // PromiseOrValue) rather than a redundant Hungarian type tag — comparable to the
53
+ // allowed compound noun PhoneNumber. Abbreviation markers (str/arr/obj/...) are
54
+ // deliberately excluded: no English word is spelled that way, so their presence
55
+ // as a segment is unambiguously a type tag even inside a type name.
56
+ const FULL_TYPE_WORDS = new Set([...COMMON_TYPES, 'Func'].map((word) => word.toLowerCase()));
37
57
  // Allowed descriptive suffixes that should not be flagged as Hungarian notation
38
58
  const ALLOWED_SUFFIXES = [
39
59
  'Formatted',
@@ -190,6 +210,30 @@ const BUILT_IN_METHODS = new Set([
190
210
  'catch',
191
211
  'finally',
192
212
  ]);
213
+ // Split a PascalCase/camelCase identifier into its word segments
214
+ // (e.g. "StringToNumber" -> ["String","To","Number"], "FuncKeys" -> ["Func","Keys"]).
215
+ function splitCamelSegments(name) {
216
+ return name.match(/[A-Z]+(?![a-z])|[A-Z]?[a-z0-9]+|[A-Z]/g) ?? [];
217
+ }
218
+ // A TYPE name (alias/interface/class) is exempt from a full-type-word marker when
219
+ // that marker is one clean PascalCase segment among OTHER descriptive segments —
220
+ // i.e. the word denotes a type concept/relation, not a redundant type tag.
221
+ // Examples: StringToNumber, CapitalizedString, PromiseOrValue, FuncKeys.
222
+ // Abbreviation markers (str/arr/obj/...) never qualify, so genuine Hungarian type
223
+ // names like UserStrName / ConfigArrSettings / UserObjData still fire.
224
+ function isSemanticTypeConcept(typeName) {
225
+ const segments = splitCamelSegments(typeName);
226
+ if (segments.length < 2) {
227
+ return false;
228
+ }
229
+ const fullTypeWordSegments = segments.filter((segment) => FULL_TYPE_WORDS.has(segment.toLowerCase()));
230
+ if (fullTypeWordSegments.length === 0) {
231
+ return false;
232
+ }
233
+ // At least one segment must be a non-type-word descriptor so the name reads as a
234
+ // concept (e.g. Extract+Number) rather than bare type tags glued together.
235
+ return segments.some((segment) => !FULL_TYPE_WORDS.has(segment.toLowerCase()));
236
+ }
193
237
  exports.noHungarian = (0, createRule_1.createRule)({
194
238
  name: 'no-hungarian',
195
239
  meta: {
@@ -208,8 +252,28 @@ exports.noHungarian = (0, createRule_1.createRule)({
208
252
  create(context) {
209
253
  // Track identifiers that have already been checked to prevent double reporting
210
254
  const checkedIdentifiers = new Set();
211
- // Check if a variable name contains a type marker with proper word boundaries
212
- function hasTypeMarker(variableName) {
255
+ // Names declared as generic type parameters (e.g. TNumber, TKey). The leading
256
+ // `T` is the TypeScript convention for "Type parameter", not a Hungarian tag,
257
+ // so neither the declaration nor any reference to it is ever flagged.
258
+ const typeParameterNames = new Set();
259
+ // Check if a variable name contains a type marker with proper word boundaries.
260
+ // `isTypeName` is true for PascalCase type declarations (type aliases,
261
+ // interfaces, classes), enabling the semantic-type-concept exemption.
262
+ function hasTypeMarker(variableName, isTypeName = false) {
263
+ // Type names whose type-word denotes a concept/relation (StringToNumber,
264
+ // CapitalizedString, FuncKeys, PromiseOrValue) are not Hungarian — the word
265
+ // is part of the type's meaning, like the allowed compound noun PhoneNumber.
266
+ if (isTypeName && isSemanticTypeConcept(variableName)) {
267
+ return false;
268
+ }
269
+ // Single-letter Hungarian prefixes (bIsActive, iCount): a lone b/i directly
270
+ // followed by an uppercase letter is a type tag. Restricted to camelCase
271
+ // (lowercase first letter) to avoid PascalCase type names like IButton.
272
+ if (variableName.length > 1 &&
273
+ SINGLE_LETTER_PREFIXES.has(variableName[0]) &&
274
+ /[A-Z]/.test(variableName[1])) {
275
+ return true;
276
+ }
213
277
  // Check if the variable name is exactly one of the allowed compound nouns
214
278
  // or if it contains one of the allowed compound nouns but is not a prefix like "strPhoneNumber"
215
279
  for (const compoundNoun of ALLOWED_COMPOUND_NOUNS) {
@@ -244,22 +308,28 @@ exports.noHungarian = (0, createRule_1.createRule)({
244
308
  if (!variableName.includes('_')) {
245
309
  return false;
246
310
  }
311
+ const parts = variableName.split('_');
312
+ const lastIndex = parts.length - 1;
247
313
  return TYPE_MARKERS.some((marker) => {
248
314
  const markerUpper = marker.toUpperCase();
249
- // Check if it's a prefix (PREFIX_REST)
250
- if (variableName.startsWith(markerUpper + '_') &&
251
- variableName.length > markerUpper.length + 1) {
252
- return true;
253
- }
254
- // Check if it's a suffix (REST_SUFFIX)
255
- if (variableName.endsWith('_' + markerUpper) &&
256
- variableName.length > markerUpper.length + 1) {
257
- return true;
258
- }
259
- // Check if it's in the middle (PART_MARKER_PART)
260
- const parts = variableName.split('_');
261
- // Only consider exact matches for parts, not substrings
262
- return parts.some((part) => part === markerUpper);
315
+ const normalizedMarker = marker.toLowerCase();
316
+ const isAbbreviation = ABBREVIATION_MARKER_SET.has(normalizedMarker);
317
+ return parts.some((part, index) => {
318
+ if (part !== markerUpper) {
319
+ return false;
320
+ }
321
+ // Abbreviation markers (STR/ARR/OBJ/...) are type tags in any
322
+ // position no English word is spelled that way.
323
+ if (isAbbreviation) {
324
+ return true;
325
+ }
326
+ // A FULL type word tags the entity's type only at the start (prefix),
327
+ // the end (suffix), or directly before the final noun
328
+ // (..._STRING_NAME). Buried deeper (EDITABLE_WRAPPER_NUMBER_PROPS_…)
329
+ // it qualifies an intermediate segment — a descriptive variant, not a
330
+ // type tag.
331
+ return (index === 0 || index === lastIndex || index === lastIndex - 1);
332
+ });
263
333
  });
264
334
  }
265
335
  // For camelCase, PascalCase, etc.
@@ -328,9 +398,27 @@ exports.noHungarian = (0, createRule_1.createRule)({
328
398
  }
329
399
  return false;
330
400
  }
331
- // Check identifier for type markers (Hungarian notation)
332
- function checkIdentifier(node) {
401
+ // Determine whether an identifier is (or references) a generic type parameter.
402
+ function isTypeParameter(node) {
403
+ // The type-parameter declaration itself: <TNumber>
404
+ if (node.parent &&
405
+ node.parent.type === utils_1.AST_NODE_TYPES.TSTypeParameter &&
406
+ node.parent.name === node) {
407
+ return true;
408
+ }
409
+ // A reference to a declared type parameter (e.g. x: TNumber).
410
+ return typeParameterNames.has(node.name);
411
+ }
412
+ // Check identifier for type markers (Hungarian notation).
413
+ // `isTypeName` enables the semantic-type-concept exemption for type
414
+ // declarations (aliases, interfaces, classes).
415
+ function checkIdentifier(node, isTypeName = false) {
333
416
  const name = node.name;
417
+ // Generic type parameters (TNumber, TKey, ...) are a TypeScript naming
418
+ // convention, never Hungarian — skip the declaration and all references.
419
+ if (isTypeParameter(node)) {
420
+ return;
421
+ }
334
422
  // Create a unique ID for this node to avoid checking it twice
335
423
  // Use the name along with source location for uniqueness
336
424
  const nodeId = `${name}:${node.loc.start.line}:${node.loc.start.column}`;
@@ -344,7 +432,7 @@ exports.noHungarian = (0, createRule_1.createRule)({
344
432
  if (isExternalOrBuiltIn(node))
345
433
  return;
346
434
  // Check for type markers
347
- if (hasTypeMarker(name)) {
435
+ if (hasTypeMarker(name, isTypeName)) {
348
436
  context.report({
349
437
  node,
350
438
  messageId: 'noHungarian',
@@ -388,10 +476,17 @@ exports.noHungarian = (0, createRule_1.createRule)({
388
476
  }
389
477
  }
390
478
  },
479
+ // Record generic type-parameter names so neither the declaration nor any
480
+ // reference to them is flagged (the leading `T` is a TS convention).
481
+ TSTypeParameter(node) {
482
+ if (node.name.type === utils_1.AST_NODE_TYPES.Identifier) {
483
+ typeParameterNames.add(node.name.name);
484
+ }
485
+ },
391
486
  // Check class declarations
392
487
  ClassDeclaration(node) {
393
488
  if (node.id) {
394
- checkIdentifier(node.id);
489
+ checkIdentifier(node.id, true);
395
490
  }
396
491
  // Check class methods and properties
397
492
  for (const member of node.body.body) {
@@ -421,11 +516,11 @@ exports.noHungarian = (0, createRule_1.createRule)({
421
516
  },
422
517
  // Check type aliases
423
518
  TSTypeAliasDeclaration(node) {
424
- checkIdentifier(node.id);
519
+ checkIdentifier(node.id, true);
425
520
  },
426
521
  // Check interface declarations
427
522
  TSInterfaceDeclaration(node) {
428
- checkIdentifier(node.id);
523
+ checkIdentifier(node.id, true);
429
524
  },
430
525
  };
431
526
  },
@@ -190,9 +190,9 @@ function isInModuleScope(node) {
190
190
  }
191
191
  return true;
192
192
  }
193
- function isComponentLikeFunction(fn, displayName) {
193
+ function isComponentLikeFunction(fn, context, displayName) {
194
194
  const body = fn.body;
195
- const hasJSX = !!body && ASTHelpers_1.ASTHelpers.returnsJSX(body);
195
+ const hasJSX = !!body && ASTHelpers_1.ASTHelpers.returnsJSX(body, context);
196
196
  const expressionBody = fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
197
197
  fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement
198
198
  ? fn.body
@@ -204,7 +204,15 @@ function isComponentLikeFunction(fn, displayName) {
204
204
  }
205
205
  function findVariableInScopes(context, identifier) {
206
206
  const sourceCode = context.sourceCode;
207
- let scope = sourceCode.getScope?.(identifier) ?? context.getScope();
207
+ let scope = null;
208
+ try {
209
+ // Tolerate ESLint API variations across versions/configurations.
210
+ // Some contexts may not provide getScope or may throw unexpectedly.
211
+ scope = sourceCode.getScope?.(identifier) ?? context.getScope();
212
+ }
213
+ catch {
214
+ scope = null;
215
+ }
208
216
  while (scope) {
209
217
  const variable = scope.variables.find((v) => v.name === identifier.name);
210
218
  if (variable)
@@ -340,7 +348,7 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
340
348
  }
341
349
  if (!fnNode)
342
350
  return false;
343
- return isComponentLikeFunction(fnNode, displayName);
351
+ return isComponentLikeFunction(fnNode, context, displayName);
344
352
  }
345
353
  function report(node, propName, componentName) {
346
354
  context.report({
@@ -394,13 +402,14 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
394
402
  return;
395
403
  }
396
404
  const fnNode = findObjectPropertyFunction(defNode.init, member.property.name);
397
- if (fnNode && isComponentLikeFunction(fnNode, member.property.name)) {
405
+ if (fnNode &&
406
+ isComponentLikeFunction(fnNode, context, member.property.name)) {
398
407
  report(member, propName, member.property.name);
399
408
  }
400
409
  }
401
410
  function handleInlineFunctionExpression(fn, propName) {
402
411
  const explicitName = fn.type === utils_1.AST_NODE_TYPES.FunctionExpression ? fn.id?.name : undefined;
403
- if (!isComponentLikeFunction(fn, explicitName)) {
412
+ if (!isComponentLikeFunction(fn, context, explicitName)) {
404
413
  return;
405
414
  }
406
415
  const displayName = (fn.type === utils_1.AST_NODE_TYPES.FunctionExpression && fn.id?.name) ||
@@ -411,7 +420,7 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
411
420
  const fnNode = getFunctionFromCall(call);
412
421
  if (!fnNode)
413
422
  return;
414
- if (isComponentLikeFunction(fnNode)) {
423
+ if (isComponentLikeFunction(fnNode, context)) {
415
424
  report(call, propName, INLINE_COMPONENT_NAME);
416
425
  }
417
426
  }
@@ -67,7 +67,13 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
67
67
  const normalizedName = normalizePropertyName(propertyName);
68
68
  return MARGIN_PROPERTIES.has(normalizedName);
69
69
  }
70
- // Check if a node is within an sx prop context or theme override context
70
+ /**
71
+ * True if node is in a flagged MUI styling context (sx attribute, sx
72
+ * object property, or css() call). Excludes createTheme styleOverrides:
73
+ * theme margins ARE the container-controlled styling (viewport insets,
74
+ * resets of MUI's built-in margins, internal layout), not the
75
+ * sibling-spacing this rule targets.
76
+ */
71
77
  function isMuiStylingContext(node) {
72
78
  let current = node;
73
79
  while (current?.parent) {
@@ -83,13 +89,6 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
83
89
  current.parent.key.name === 'sx') {
84
90
  return true;
85
91
  }
86
- // Check for theme overrides (MUI's createTheme)
87
- if (current.parent.type === utils_1.AST_NODE_TYPES.Property &&
88
- current.parent.key.type === utils_1.AST_NODE_TYPES.Identifier &&
89
- (current.parent.key.name === 'styleOverrides' ||
90
- current.parent.key.name === 'components')) {
91
- return true;
92
- }
93
92
  // Check for MUI's css function
94
93
  if (current.parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
95
94
  current.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -290,36 +289,6 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
290
289
  checkObjectExpression(arg);
291
290
  }
292
291
  }
293
- // Handle createTheme for MUI theme overrides
294
- if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
295
- node.callee.name === 'createTheme' &&
296
- node.arguments.length > 0 &&
297
- node.arguments[0].type === utils_1.AST_NODE_TYPES.ObjectExpression) {
298
- const themeObj = node.arguments[0];
299
- // Find components property in theme object
300
- const componentsProperty = themeObj.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
301
- prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
302
- prop.key.name === 'components' &&
303
- prop.value.type === utils_1.AST_NODE_TYPES.ObjectExpression);
304
- if (componentsProperty &&
305
- componentsProperty.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
306
- // Check each component override
307
- componentsProperty.value.properties.forEach((componentProp) => {
308
- if (componentProp.type === utils_1.AST_NODE_TYPES.Property &&
309
- componentProp.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
310
- // Find styleOverrides property
311
- const styleOverrides = componentProp.value.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
312
- prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
313
- prop.key.name === 'styleOverrides' &&
314
- prop.value.type === utils_1.AST_NODE_TYPES.ObjectExpression);
315
- if (styleOverrides &&
316
- styleOverrides.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
317
- checkObjectExpression(styleOverrides.value);
318
- }
319
- }
320
- });
321
- }
322
- }
323
292
  },
324
293
  };
325
294
  },
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
- export declare const noPassthroughGetters: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noPassthroughGetter", never[], {
1
+ import { TSESTree, TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noPassthroughGetters: TSESLint.RuleModule<"noPassthroughGetter", never[], {
3
3
  MethodDefinition(node: TSESTree.MethodDefinition): void;
4
4
  }>;
@@ -1,9 +1,33 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.noPassthroughGetters = void 0;
4
27
  const createRule_1 = require("../utils/createRule");
5
28
  const utils_1 = require("@typescript-eslint/utils");
6
29
  const getMethodName_1 = require("../utils/getMethodName");
30
+ const ts = __importStar(require("typescript"));
7
31
  const COMPLEX_EXPRESSION_TYPES = new Set([
8
32
  utils_1.AST_NODE_TYPES.CallExpression,
9
33
  utils_1.AST_NODE_TYPES.TemplateLiteral,
@@ -14,7 +38,7 @@ const COMPLEX_EXPRESSION_TYPES = new Set([
14
38
  ]);
15
39
  exports.noPassthroughGetters = (0, createRule_1.createRule)({
16
40
  create(context) {
17
- const sourceCode = context.getSourceCode();
41
+ const sourceCode = context.sourceCode;
18
42
  return {
19
43
  // Target getter methods in classes
20
44
  MethodDefinition(node) {
@@ -26,6 +50,10 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
26
50
  if (node.decorators && node.decorators.length > 0) {
27
51
  return;
28
52
  }
53
+ // Check if this getter satisfies an interface or overrides a base class member
54
+ if (isRequiredByInterfaceOrBaseClass(node, sourceCode)) {
55
+ return;
56
+ }
29
57
  const methodBody = node.value.body;
30
58
  if (!methodBody) {
31
59
  return;
@@ -74,6 +102,60 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
74
102
  }
75
103
  },
76
104
  };
105
+ /**
106
+ * Check if the getter is required by an implemented interface or overrides a base class member
107
+ */
108
+ function isRequiredByInterfaceOrBaseClass(node, sourceCode) {
109
+ const parserServices = sourceCode.parserServices;
110
+ if (!parserServices ||
111
+ !parserServices.program ||
112
+ !parserServices.esTreeNodeToTSNodeMap) {
113
+ return false;
114
+ }
115
+ const checker = parserServices.program.getTypeChecker();
116
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
117
+ if (!tsNode) {
118
+ return false;
119
+ }
120
+ const symbol = checker.getSymbolAtLocation(tsNode.name);
121
+ if (!symbol ||
122
+ !tsNode.parent ||
123
+ !(ts.isClassDeclaration(tsNode.parent) ||
124
+ ts.isClassExpression(tsNode.parent))) {
125
+ return false;
126
+ }
127
+ const classNode = tsNode.parent;
128
+ const classType = checker.getTypeAtLocation(classNode);
129
+ const classSymbol = classType.getSymbol();
130
+ if (!classSymbol) {
131
+ return false;
132
+ }
133
+ const instanceType = checker.getDeclaredTypeOfSymbol(classSymbol);
134
+ const name = symbol.getName();
135
+ // Check base classes
136
+ const baseTypes = instanceType.getBaseTypes() || [];
137
+ for (const baseType of baseTypes) {
138
+ if (baseType.getProperty(name)) {
139
+ return true;
140
+ }
141
+ }
142
+ // Check interfaces
143
+ if (classNode.heritageClauses) {
144
+ for (const clause of classNode.heritageClauses) {
145
+ // Only check implemented interfaces as base classes are already checked via getBaseTypes()
146
+ if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
147
+ continue;
148
+ }
149
+ for (const typeNode of clause.types) {
150
+ const type = checker.getTypeAtLocation(typeNode);
151
+ if (type.getProperty(name)) {
152
+ return true;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ return false;
158
+ }
77
159
  /**
78
160
  * Check if the node is a simple property access from a constructor parameter
79
161
  * like this.settings.property or this.settings['property'] or this.settings.nested.deep.property
@@ -21,6 +21,7 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
21
21
  defaultOptions: [],
22
22
  create(context) {
23
23
  const classInfoMap = new WeakMap();
24
+ const classStatsMap = new WeakMap();
24
25
  function isFunctionLike(node) {
25
26
  return (node?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
26
27
  node?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
@@ -412,6 +413,24 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
412
413
  return {
413
414
  'ClassDeclaration, ClassExpression'(node) {
414
415
  collectClassInfo(node);
416
+ classStatsMap.set(node, { methods: new Map() });
417
+ },
418
+ 'ClassDeclaration, ClassExpression:exit'(node) {
419
+ const stats = classStatsMap.get(node);
420
+ if (!stats) {
421
+ return;
422
+ }
423
+ for (const [methodName, methodStats] of stats.methods) {
424
+ for (const [argIndex, argProperties] of methodStats.argStats) {
425
+ for (const [, propStats] of argProperties) {
426
+ if (propStats.callsWithProperty === methodStats.totalCalls) {
427
+ for (const violation of propStats.violations) {
428
+ reportAccess(methodName, violation.methodMeta, argIndex, violation.access);
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
415
434
  },
416
435
  CallExpression(node) {
417
436
  const methodName = getMethodNameFromCallee(node.callee);
@@ -442,6 +461,18 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
442
461
  return;
443
462
  }
444
463
  }
464
+ const stats = classStatsMap.get(classNode);
465
+ if (!stats) {
466
+ return;
467
+ }
468
+ let methodStats = stats.methods.get(methodName);
469
+ if (!methodStats) {
470
+ methodStats = { totalCalls: 0, argStats: new Map() };
471
+ stats.methods.set(methodName, methodStats);
472
+ }
473
+ const currentMethodStats = methodStats;
474
+ currentMethodStats.totalCalls++;
475
+ const seenInThisCall = new Set();
445
476
  node.arguments.forEach((arg, index) => {
446
477
  if (!arg) {
447
478
  return;
@@ -449,7 +480,25 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
449
480
  const targetNode = arg.type === utils_1.AST_NODE_TYPES.SpreadElement ? arg.argument : arg;
450
481
  const accesses = collectThisAccesses(targetNode);
451
482
  for (const access of accesses) {
452
- reportAccess(methodName, methodMeta, index, access);
483
+ const key = `${index}:${access.propertyName}`;
484
+ let argMap = currentMethodStats.argStats.get(index);
485
+ if (!argMap) {
486
+ argMap = new Map();
487
+ currentMethodStats.argStats.set(index, argMap);
488
+ }
489
+ let propStats = argMap.get(access.propertyName);
490
+ if (!propStats) {
491
+ propStats = { callsWithProperty: 0, violations: [] };
492
+ argMap.set(access.propertyName, propStats);
493
+ }
494
+ propStats.violations.push({
495
+ methodMeta: methodMeta,
496
+ access,
497
+ });
498
+ if (!seenInThisCall.has(key)) {
499
+ propStats.callsWithProperty++;
500
+ seenInThisCall.add(key);
501
+ }
453
502
  }
454
503
  });
455
504
  },
@@ -274,7 +274,7 @@ exports.noUnmemoizedMemoWithoutProps = (0, createRule_1.createRule)({
274
274
  if (!node.body) {
275
275
  return;
276
276
  }
277
- const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body);
277
+ const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
278
278
  if (!returnsJsx) {
279
279
  return;
280
280
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noUnnecessaryDestructuringRename = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
6
7
  const BINDABLE_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
7
8
  const RESERVED_BINDINGS = new Set([
8
9
  'break',
@@ -113,11 +114,7 @@ exports.noUnnecessaryDestructuringRename = (0, createRule_1.createRule)({
113
114
  const sourceCode = context.getSourceCode();
114
115
  const candidates = [];
115
116
  const getDeclaredVariables = (node) => {
116
- const sourceCodeWithDeclarations = sourceCode;
117
- if (typeof sourceCodeWithDeclarations.getDeclaredVariables === 'function') {
118
- return sourceCodeWithDeclarations.getDeclaredVariables(node);
119
- }
120
- return context.getDeclaredVariables(node);
117
+ return ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, node);
121
118
  };
122
119
  function collectDeclaredVariablesUpTree(node) {
123
120
  const variables = [];