@blumintinc/eslint-plugin-blumint 1.12.6 → 1.13.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 (141) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -17
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  6. package/lib/rules/dynamic-https-errors.js +48 -10
  7. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  8. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
  9. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  10. package/lib/rules/enforce-assert-throws.js +493 -51
  11. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  12. package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
  13. package/lib/rules/enforce-callable-types.js +4 -4
  14. package/lib/rules/enforce-callback-memo.js +143 -18
  15. package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
  16. package/lib/rules/enforce-console-error.d.ts +3 -0
  17. package/lib/rules/enforce-console-error.js +250 -0
  18. package/lib/rules/enforce-css-media-queries.js +24 -46
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  20. package/lib/rules/enforce-dynamic-imports.js +1 -1
  21. package/lib/rules/enforce-exported-function-types.js +3 -3
  22. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  23. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
  24. package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
  25. package/lib/rules/enforce-firestore-facade.js +347 -157
  26. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  27. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  28. package/lib/rules/enforce-global-constants.d.ts +4 -1
  29. package/lib/rules/enforce-global-constants.js +240 -12
  30. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  31. package/lib/rules/enforce-memoize-async.js +96 -27
  32. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  33. package/lib/rules/enforce-memoize-getters.js +123 -0
  34. package/lib/rules/enforce-microdiff.js +2 -1
  35. package/lib/rules/enforce-positive-naming.js +5 -7
  36. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  37. package/lib/rules/enforce-props-argument-name.js +165 -151
  38. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  39. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  40. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  41. package/lib/rules/enforce-querykey-ts.js +274 -0
  42. package/lib/rules/enforce-singular-type-names.js +9 -5
  43. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  44. package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
  45. package/lib/rules/enforce-verb-noun-naming.js +85 -10
  46. package/lib/rules/extract-global-constants.js +57 -45
  47. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
  49. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  50. package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
  51. package/lib/rules/generic-starts-with-t.js +8 -2
  52. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  53. package/lib/rules/memoize-root-level-hocs.js +242 -0
  54. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  55. package/lib/rules/no-array-length-in-deps.js +308 -0
  56. package/lib/rules/no-async-array-filter.js +5 -2
  57. package/lib/rules/no-complex-cloud-params.js +15 -2
  58. package/lib/rules/no-compositing-layer-props.js +4 -2
  59. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  60. package/lib/rules/no-entire-object-hook-deps.js +317 -63
  61. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  62. package/lib/rules/no-excessive-parent-chain.js +273 -0
  63. package/lib/rules/no-firestore-object-arrays.js +345 -30
  64. package/lib/rules/no-hungarian.js +13 -13
  65. package/lib/rules/no-margin-properties.d.ts +7 -1
  66. package/lib/rules/no-margin-properties.js +16 -3
  67. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  68. package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
  69. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  70. package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
  71. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  72. package/lib/rules/no-passthrough-getters.js +170 -0
  73. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  74. package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
  75. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  76. package/lib/rules/no-restricted-properties-fix.js +172 -0
  77. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  78. package/lib/rules/no-separate-loading-state.js +142 -0
  79. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  80. package/lib/rules/no-stale-state-across-await.js +185 -0
  81. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  82. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  83. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  84. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  85. package/lib/rules/no-type-assertion-returns.js +6 -0
  86. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  87. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  88. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  89. package/lib/rules/no-unused-props.d.ts +3 -0
  90. package/lib/rules/no-unused-props.js +213 -78
  91. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  92. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  93. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  94. package/lib/rules/parallelize-async-operations.js +375 -0
  95. package/lib/rules/prefer-batch-operations.js +199 -33
  96. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  97. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  98. package/lib/rules/prefer-document-flattening.js +239 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  100. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  101. package/lib/rules/prefer-fragment-component.js +3 -3
  102. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  103. package/lib/rules/prefer-global-router-state-key.js +230 -31
  104. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  105. package/lib/rules/prefer-next-dynamic.js +366 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  107. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  109. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  110. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  111. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  112. package/lib/rules/prefer-settings-object.js +23 -15
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  114. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  116. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  118. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  119. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  120. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  121. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  122. package/lib/rules/react-usememo-should-be-component.js +144 -78
  123. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  124. package/lib/rules/require-image-optimized.js +1 -1
  125. package/lib/rules/require-memo.js +11 -1
  126. package/lib/rules/require-usememo-object-literals.js +7 -1
  127. package/lib/rules/semantic-function-prefixes.js +38 -18
  128. package/lib/rules/sync-onwrite-name-func.js +5 -1
  129. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  130. package/lib/rules/test-file-location-enforcement.js +57 -0
  131. package/lib/rules/use-custom-link.js +6 -1
  132. package/lib/rules/use-custom-router.js +6 -1
  133. package/lib/rules/use-latest-callback.d.ts +3 -0
  134. package/lib/rules/use-latest-callback.js +270 -0
  135. package/lib/utils/ASTHelpers.js +10 -0
  136. package/lib/utils/createRule.js +1 -1
  137. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  138. package/lib/utils/ruleTester.d.ts +1 -0
  139. package/lib/utils/ruleTester.js +4 -1
  140. package/package.json +21 -14
  141. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noPassthroughGetters = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ const utils_1 = require("@typescript-eslint/utils");
6
+ const COMPLEX_EXPRESSION_TYPES = new Set([
7
+ utils_1.AST_NODE_TYPES.CallExpression,
8
+ utils_1.AST_NODE_TYPES.TemplateLiteral,
9
+ utils_1.AST_NODE_TYPES.NewExpression,
10
+ utils_1.AST_NODE_TYPES.BinaryExpression,
11
+ utils_1.AST_NODE_TYPES.ArrayExpression,
12
+ utils_1.AST_NODE_TYPES.ObjectExpression,
13
+ ]);
14
+ exports.noPassthroughGetters = (0, createRule_1.createRule)({
15
+ create(context) {
16
+ return {
17
+ // Target getter methods in classes
18
+ MethodDefinition(node) {
19
+ // Only check getter methods
20
+ if (node.kind !== 'get') {
21
+ return;
22
+ }
23
+ // Skip if the getter has decorators (like @Memoize)
24
+ if (node.decorators && node.decorators.length > 0) {
25
+ return;
26
+ }
27
+ const methodBody = node.value.body;
28
+ if (!methodBody) {
29
+ return;
30
+ }
31
+ // Check if the getter body is a simple return statement
32
+ if (methodBody.body.length === 1 &&
33
+ methodBody.body[0].type === 'ReturnStatement') {
34
+ const returnStatement = methodBody
35
+ .body[0];
36
+ // Skip if there's no return argument
37
+ if (!returnStatement.argument) {
38
+ return;
39
+ }
40
+ // Skip if the return statement uses super
41
+ if (containsSuper(returnStatement.argument)) {
42
+ return;
43
+ }
44
+ // Skip if the return statement includes type assertions or casting
45
+ if (hasTypeAssertion(returnStatement.argument)) {
46
+ return;
47
+ }
48
+ // Skip if the return statement includes null/undefined handling
49
+ if (hasNullUndefinedHandling(returnStatement.argument)) {
50
+ return;
51
+ }
52
+ // Skip if the return statement includes computed property access or function calls
53
+ if (hasComputedPropertyOrFunctionCall(returnStatement.argument)) {
54
+ return;
55
+ }
56
+ // Check if the return statement is accessing a property from a constructor parameter
57
+ if (isConstructorParameterPropertyAccess(returnStatement.argument)) {
58
+ context.report({
59
+ node,
60
+ messageId: 'noPassthroughGetter',
61
+ });
62
+ }
63
+ }
64
+ },
65
+ };
66
+ /**
67
+ * Check if the node is a simple property access from a constructor parameter
68
+ * like this.settings.property or this.settings['property'] or this.settings.nested.deep.property
69
+ */
70
+ function isConstructorParameterPropertyAccess(node) {
71
+ // Check for member expressions like this.settings.property
72
+ if (node.type === 'MemberExpression') {
73
+ return isConstructorParameterAccess(node);
74
+ }
75
+ return false;
76
+ }
77
+ /**
78
+ * Check if the node is accessing a property from a constructor parameter
79
+ * Patterns to match: this.constructorParam.property, this.constructorParam['property'], this.constructorParam.nested.deep.property
80
+ * Patterns to NOT match: this.property, SomeClass.property, this.methodCall()
81
+ */
82
+ function isConstructorParameterAccess(node) {
83
+ let current = node.object;
84
+ let depth = 0;
85
+ // Walk up member expressions until we reach a base
86
+ while (current && current.type === 'MemberExpression') {
87
+ depth += 1;
88
+ current = current.object;
89
+ }
90
+ // Require at least one nesting and a base of `this`
91
+ return depth >= 1 && current?.type === 'ThisExpression';
92
+ }
93
+ /**
94
+ * Check if the node contains a reference to super
95
+ */
96
+ function containsSuper(node) {
97
+ let current = node;
98
+ while (current && current.type === 'MemberExpression') {
99
+ if (current.object.type === 'Super') {
100
+ return true;
101
+ }
102
+ current = current.object;
103
+ }
104
+ return false;
105
+ }
106
+ /**
107
+ * Check if the node has a type assertion or casting
108
+ */
109
+ function hasTypeAssertion(node) {
110
+ if (node.type === 'TSAsExpression' || node.type === 'TSTypeAssertion') {
111
+ return true;
112
+ }
113
+ if (node.type === 'MemberExpression') {
114
+ return hasTypeAssertion(node.object);
115
+ }
116
+ return false;
117
+ }
118
+ /**
119
+ * Check if the node handles null/undefined values
120
+ */
121
+ function hasNullUndefinedHandling(node) {
122
+ // Check for logical expressions like this.settings.property || []
123
+ if (node.type === 'LogicalExpression') {
124
+ return true;
125
+ }
126
+ // Check for conditional expressions like this.settings.property ? this.settings.property : []
127
+ if (node.type === 'ConditionalExpression') {
128
+ return true;
129
+ }
130
+ // Check for optional chaining like this.settings?.property
131
+ if (node.type === 'MemberExpression' && node.optional) {
132
+ return true;
133
+ }
134
+ // The nullish coalescing check is already covered by the LogicalExpression check above
135
+ return false;
136
+ }
137
+ /**
138
+ * Check if the node includes computed property access or function calls
139
+ */
140
+ function hasComputedPropertyOrFunctionCall(node) {
141
+ if (COMPLEX_EXPRESSION_TYPES.has(node.type)) {
142
+ return true;
143
+ }
144
+ // Check for member expressions with computed properties like this.settings[key]
145
+ if (node.type === 'MemberExpression') {
146
+ // If the property is computed with a dynamic expression (not a literal), it's not a simple property access
147
+ if (node.computed && node.property.type !== 'Literal') {
148
+ return true;
149
+ }
150
+ // Recursively check the object part
151
+ return hasComputedPropertyOrFunctionCall(node.object);
152
+ }
153
+ return false;
154
+ }
155
+ },
156
+ name: 'no-passthrough-getters',
157
+ meta: {
158
+ type: 'suggestion',
159
+ docs: {
160
+ description: 'Avoid unnecessary getter methods that simply return properties from constructor parameters',
161
+ recommended: 'error',
162
+ },
163
+ schema: [],
164
+ messages: {
165
+ noPassthroughGetter: 'Avoid unnecessary getter methods that simply return properties from constructor parameters. Access the property directly instead.',
166
+ },
167
+ },
168
+ defaultOptions: [],
169
+ });
170
+ //# sourceMappingURL=no-passthrough-getters.js.map
@@ -0,0 +1,8 @@
1
+ type Options = [
2
+ {
3
+ memoizedHookNames?: string[];
4
+ assumeAllUseAreMemoized?: boolean;
5
+ }
6
+ ];
7
+ export declare const noRedundantUseCallbackWrapper: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"redundantWrapper", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
8
+ export default noRedundantUseCallbackWrapper;
@@ -0,0 +1,315 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noRedundantUseCallbackWrapper = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ function isHookLikeName(name) {
7
+ return name.startsWith('use');
8
+ }
9
+ function isKnownHookCallee(callee, knownHooks, assumeAllUseAreMemoized) {
10
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
11
+ return ((assumeAllUseAreMemoized && isHookLikeName(callee.name)) ||
12
+ knownHooks.has(callee.name));
13
+ }
14
+ // Support namespaced hook calls, e.g., Hooks.useAuthSubmit()
15
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
16
+ !callee.computed &&
17
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
18
+ const prop = callee.property.name;
19
+ return ((assumeAllUseAreMemoized && isHookLikeName(prop)) || knownHooks.has(prop));
20
+ }
21
+ return false;
22
+ }
23
+ function isPreventDefaultCall(stmt, params) {
24
+ if (stmt.type !== utils_1.AST_NODE_TYPES.ExpressionStatement)
25
+ return false;
26
+ const expr = stmt.expression;
27
+ if (expr.type !== utils_1.AST_NODE_TYPES.CallExpression)
28
+ return false;
29
+ if (expr.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
30
+ return false;
31
+ const member = expr.callee;
32
+ if (member.object.type === utils_1.AST_NODE_TYPES.Identifier &&
33
+ params.includes(member.object.name) &&
34
+ member.property.type === utils_1.AST_NODE_TYPES.Identifier &&
35
+ (member.property.name === 'preventDefault' ||
36
+ member.property.name === 'stopPropagation' ||
37
+ member.property.name === 'stopImmediatePropagation')) {
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+ function getParams(node) {
43
+ const names = new Set();
44
+ for (const p of node.params) {
45
+ if (p.type === utils_1.AST_NODE_TYPES.Identifier) {
46
+ names.add(p.name);
47
+ }
48
+ else if (p.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
49
+ for (const prop of p.properties) {
50
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
51
+ // Collect bound identifier names (aliases/defaults)
52
+ if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier) {
53
+ names.add(prop.value.name);
54
+ }
55
+ else if (prop.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
56
+ prop.value.left.type === utils_1.AST_NODE_TYPES.Identifier) {
57
+ names.add(prop.value.left.name);
58
+ }
59
+ }
60
+ else if (prop.type === utils_1.AST_NODE_TYPES.RestElement) {
61
+ if (prop.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
62
+ names.add(prop.argument.name);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ else if (p.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
68
+ for (const element of p.elements) {
69
+ if (!element)
70
+ continue;
71
+ if (element.type === utils_1.AST_NODE_TYPES.Identifier) {
72
+ names.add(element.name);
73
+ }
74
+ else if (element.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
75
+ element.left.type === utils_1.AST_NODE_TYPES.Identifier) {
76
+ names.add(element.left.name);
77
+ }
78
+ else if (element.type === utils_1.AST_NODE_TYPES.RestElement &&
79
+ element.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
80
+ names.add(element.argument.name);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ return Array.from(names);
86
+ }
87
+ function isIdentifierOrMemberOn(obj, nameSet) {
88
+ if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
89
+ return nameSet.has(obj.name);
90
+ }
91
+ if (obj.type === utils_1.AST_NODE_TYPES.MemberExpression &&
92
+ obj.object.type === utils_1.AST_NODE_TYPES.Identifier) {
93
+ return nameSet.has(obj.object.name);
94
+ }
95
+ return false;
96
+ }
97
+ function unwrapChainExpression(node) {
98
+ if (!node)
99
+ return null;
100
+ if (node.type === utils_1.AST_NODE_TYPES.ChainExpression) {
101
+ return node.expression;
102
+ }
103
+ return node;
104
+ }
105
+ // intentionally removed: helper for param-only detection not needed after simplification
106
+ // reserved for potential future options/heuristics
107
+ exports.noRedundantUseCallbackWrapper = (0, createRule_1.createRule)({
108
+ name: 'no-redundant-usecallback-wrapper',
109
+ meta: {
110
+ type: 'suggestion',
111
+ docs: {
112
+ description: 'Prevent wrapping already memoized/stable callbacks from hooks/contexts in an extra useCallback()',
113
+ recommended: 'error',
114
+ },
115
+ fixable: 'code',
116
+ schema: [
117
+ {
118
+ type: 'object',
119
+ properties: {
120
+ memoizedHookNames: {
121
+ type: 'array',
122
+ items: { type: 'string' },
123
+ default: [],
124
+ },
125
+ assumeAllUseAreMemoized: {
126
+ type: 'boolean',
127
+ default: false,
128
+ },
129
+ },
130
+ additionalProperties: false,
131
+ },
132
+ ],
133
+ messages: {
134
+ redundantWrapper: 'Redundant useCallback wrapper around an already memoized callback. Pass the memoized function directly.',
135
+ },
136
+ },
137
+ defaultOptions: [{}],
138
+ create(context) {
139
+ const option = context.options?.[0] ?? {};
140
+ const knownHooks = new Set(option.memoizedHookNames ?? []);
141
+ const assumeAllUseAreMemoized = option.assumeAllUseAreMemoized === true;
142
+ // Track identifiers coming from hook-like calls
143
+ const hookReturnObjects = new Set(); // variables assigned to a hook call result (object or function)
144
+ const hookReturnProps = new Set(); // properties destructured from a hook call result
145
+ return {
146
+ VariableDeclarator(node) {
147
+ if (!node.init)
148
+ return;
149
+ const initCall = unwrapChainExpression(node.init);
150
+ if (!initCall || initCall.type !== utils_1.AST_NODE_TYPES.CallExpression) {
151
+ return;
152
+ }
153
+ const callee = unwrapChainExpression(initCall.callee);
154
+ if (!callee)
155
+ return;
156
+ if (!isKnownHookCallee(callee, knownHooks, assumeAllUseAreMemoized))
157
+ return;
158
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
159
+ hookReturnObjects.add(node.id.name);
160
+ return;
161
+ }
162
+ if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
163
+ for (const prop of node.id.properties) {
164
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
165
+ if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier) {
166
+ hookReturnProps.add(prop.value.name);
167
+ }
168
+ else if (prop.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
169
+ prop.value.left.type === utils_1.AST_NODE_TYPES.Identifier) {
170
+ hookReturnProps.add(prop.value.left.name);
171
+ }
172
+ }
173
+ }
174
+ return;
175
+ }
176
+ },
177
+ CallExpression(node) {
178
+ // Detect useCallback wrappers (including React.useCallback)
179
+ const calleeNode = unwrapChainExpression(node.callee);
180
+ if (!calleeNode)
181
+ return;
182
+ const isUseCallback = (calleeNode.type === utils_1.AST_NODE_TYPES.Identifier &&
183
+ calleeNode.name === 'useCallback') ||
184
+ (calleeNode.type === utils_1.AST_NODE_TYPES.MemberExpression &&
185
+ !calleeNode.computed &&
186
+ calleeNode.property.type === utils_1.AST_NODE_TYPES.Identifier &&
187
+ calleeNode.property.name === 'useCallback');
188
+ if (isUseCallback && node.arguments.length >= 1) {
189
+ const arg = node.arguments[0];
190
+ const unwrappedArg = unwrapChainExpression(arg);
191
+ // Case 1: useCallback(memoizedFn, ...) or useCallback(ctx.memoized, ...)
192
+ if (unwrappedArg &&
193
+ (unwrappedArg.type === utils_1.AST_NODE_TYPES.Identifier ||
194
+ unwrappedArg.type === utils_1.AST_NODE_TYPES.MemberExpression)) {
195
+ if ((unwrappedArg.type === utils_1.AST_NODE_TYPES.Identifier &&
196
+ (hookReturnProps.has(unwrappedArg.name) ||
197
+ hookReturnObjects.has(unwrappedArg.name))) ||
198
+ (unwrappedArg.type === utils_1.AST_NODE_TYPES.MemberExpression &&
199
+ unwrappedArg.object.type === utils_1.AST_NODE_TYPES.Identifier &&
200
+ hookReturnObjects.has(unwrappedArg.object.name))) {
201
+ if (unwrappedArg.type === utils_1.AST_NODE_TYPES.Identifier) {
202
+ const replaceText = unwrappedArg.name;
203
+ context.report({
204
+ node,
205
+ messageId: 'redundantWrapper',
206
+ fix: (fixer) => fixer.replaceText(node, replaceText),
207
+ });
208
+ }
209
+ else {
210
+ // Member function — report only, no fix to avoid breaking `this`.
211
+ context.report({ node, messageId: 'redundantWrapper' });
212
+ }
213
+ }
214
+ return;
215
+ }
216
+ // Case 2: useCallback(() => memoizedFn(...), ...)
217
+ if (unwrappedArg &&
218
+ (unwrappedArg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
219
+ unwrappedArg.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
220
+ const fn = unwrappedArg;
221
+ const params = getParams(fn);
222
+ // Handle implicit return: () => memoizedFn()
223
+ if (fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
224
+ fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
225
+ const bodyExpr = unwrapChainExpression(fn.body);
226
+ if (bodyExpr && bodyExpr.type === utils_1.AST_NODE_TYPES.CallExpression) {
227
+ const callee = unwrapChainExpression(bodyExpr.callee);
228
+ if ((callee &&
229
+ isIdentifierOrMemberOn(callee, hookReturnObjects)) ||
230
+ (callee &&
231
+ callee.type === utils_1.AST_NODE_TYPES.Identifier &&
232
+ hookReturnProps.has(callee.name))) {
233
+ if (bodyExpr.arguments.length > 0) {
234
+ // Passing any arguments: treat as non-redundant (avoid false positives)
235
+ return;
236
+ }
237
+ else {
238
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
239
+ const replaceText = callee.name;
240
+ context.report({
241
+ node,
242
+ messageId: 'redundantWrapper',
243
+ fix: (fixer) => fixer.replaceText(node, replaceText),
244
+ });
245
+ }
246
+ else {
247
+ // Member function — report only, no fix to avoid breaking `this`.
248
+ context.report({ node, messageId: 'redundantWrapper' });
249
+ }
250
+ }
251
+ }
252
+ }
253
+ return;
254
+ }
255
+ // Handle block body: () => { [maybe e.preventDefault()]; return memoizedFn(); }
256
+ if (fn.body && fn.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
257
+ const stmts = fn.body.body.filter(Boolean);
258
+ if (stmts.length >= 1 && stmts.length <= 2) {
259
+ let idx = 0;
260
+ if (stmts.length === 2 &&
261
+ isPreventDefaultCall(stmts[0], params)) {
262
+ idx = 1;
263
+ }
264
+ const last = stmts[idx];
265
+ if (last.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
266
+ last.type === utils_1.AST_NODE_TYPES.ExpressionStatement) {
267
+ const expr = last.type === utils_1.AST_NODE_TYPES.ReturnStatement
268
+ ? last.argument
269
+ : last.expression;
270
+ if (expr && expr.type === utils_1.AST_NODE_TYPES.CallExpression) {
271
+ const callee = unwrapChainExpression(expr.callee);
272
+ const isHookProp = callee &&
273
+ callee.type === utils_1.AST_NODE_TYPES.Identifier &&
274
+ hookReturnProps.has(callee.name);
275
+ const isHookObjMember = callee &&
276
+ callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
277
+ callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
278
+ hookReturnObjects.has(callee.object.name);
279
+ if (isHookProp || isHookObjMember) {
280
+ if ((expr.arguments?.length ?? 0) > 0) {
281
+ // Passing any arguments: treat as non-redundant
282
+ return;
283
+ }
284
+ else {
285
+ // No args and trivial wrapper
286
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
287
+ const replaceText = callee
288
+ .name;
289
+ context.report({
290
+ node,
291
+ messageId: 'redundantWrapper',
292
+ fix: (fixer) => fixer.replaceText(node, replaceText),
293
+ });
294
+ }
295
+ else {
296
+ // Member function — report only, no fix to avoid breaking `this`.
297
+ context.report({
298
+ node,
299
+ messageId: 'redundantWrapper',
300
+ });
301
+ }
302
+ }
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ },
311
+ };
312
+ },
313
+ });
314
+ exports.default = exports.noRedundantUseCallbackWrapper;
315
+ //# sourceMappingURL=no-redundant-usecallback-wrapper.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * This rule is a wrapper around the core ESLint no-restricted-properties rule
3
+ * that adds special handling for Object.keys() and Object.values() results.
4
+ * It prevents false positives when accessing standard array properties/methods
5
+ * on the arrays returned by Object.keys() and Object.values().
6
+ */
7
+ export declare const noRestrictedPropertiesFix: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"restrictedProperty", [{
8
+ object?: string | undefined;
9
+ property?: string | undefined;
10
+ message?: string | undefined;
11
+ allowObjects?: string[] | undefined;
12
+ }[]], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noRestrictedPropertiesFix = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ const utils_1 = require("@typescript-eslint/utils");
6
+ /**
7
+ * This rule is a wrapper around the core ESLint no-restricted-properties rule
8
+ * that adds special handling for Object.keys() and Object.values() results.
9
+ * It prevents false positives when accessing standard array properties/methods
10
+ * on the arrays returned by Object.keys() and Object.values().
11
+ */
12
+ exports.noRestrictedPropertiesFix = (0, createRule_1.createRule)({
13
+ name: 'no-restricted-properties-fix',
14
+ meta: {
15
+ type: 'suggestion',
16
+ docs: {
17
+ description: 'Disallow certain properties on certain objects, with special handling for Object.keys() and Object.values()',
18
+ recommended: 'error',
19
+ },
20
+ fixable: 'code',
21
+ schema: [
22
+ {
23
+ type: 'array',
24
+ items: {
25
+ type: 'object',
26
+ properties: {
27
+ object: { type: 'string' },
28
+ property: { type: 'string' },
29
+ message: { type: 'string' },
30
+ allowObjects: {
31
+ type: 'array',
32
+ items: { type: 'string' },
33
+ },
34
+ },
35
+ additionalProperties: false,
36
+ },
37
+ },
38
+ ],
39
+ messages: {
40
+ restrictedProperty: "Disallowed object property: '{{objectName}}.{{propertyName}}'{{message}}",
41
+ },
42
+ },
43
+ defaultOptions: [[]],
44
+ create(context, [restrictedProperties]) {
45
+ if (!restrictedProperties || restrictedProperties.length === 0) {
46
+ return {};
47
+ }
48
+ const SAFE_ARRAY_PROPERTIES = new Set([
49
+ 'length',
50
+ 'sort',
51
+ 'filter',
52
+ 'map',
53
+ 'reduce',
54
+ 'forEach',
55
+ 'join',
56
+ 'slice',
57
+ 'concat',
58
+ ]);
59
+ /**
60
+ * Checks if the given node is a result of Object.keys() or Object.values()
61
+ * @param node The node to check
62
+ * @returns True if the node is a result of Object.keys() or Object.values()
63
+ */
64
+ function isObjectKeysOrValuesResult(node) {
65
+ if (node.type !== utils_1.AST_NODE_TYPES.CallExpression) {
66
+ return false;
67
+ }
68
+ const callee = node.callee;
69
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
70
+ return false;
71
+ }
72
+ if (callee.object.type !== utils_1.AST_NODE_TYPES.Identifier ||
73
+ callee.object.name !== 'Object') {
74
+ return false;
75
+ }
76
+ if (callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
77
+ (callee.property.name !== 'keys' && callee.property.name !== 'values')) {
78
+ return false;
79
+ }
80
+ return true;
81
+ }
82
+ return {
83
+ MemberExpression(node) {
84
+ // Skip if the object is a result of Object.keys() or Object.values()
85
+ if (isObjectKeysOrValuesResult(node.object)) {
86
+ if (node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
87
+ SAFE_ARRAY_PROPERTIES.has(node.property.name)) {
88
+ return;
89
+ }
90
+ }
91
+ // Apply the original rule logic
92
+ for (const restrictedProp of restrictedProperties) {
93
+ const objectMatches = restrictedProp.object &&
94
+ node.object.type === utils_1.AST_NODE_TYPES.Identifier &&
95
+ node.object.name === restrictedProp.object;
96
+ const propertyMatches = restrictedProp.property &&
97
+ ((node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
98
+ node.property.name === restrictedProp.property) ||
99
+ (node.property.type === utils_1.AST_NODE_TYPES.Literal &&
100
+ node.property.value === restrictedProp.property));
101
+ // If both object and property are restricted
102
+ if (restrictedProp.object &&
103
+ restrictedProp.property &&
104
+ objectMatches &&
105
+ propertyMatches) {
106
+ context.report({
107
+ node,
108
+ messageId: 'restrictedProperty',
109
+ data: {
110
+ objectName: restrictedProp.object,
111
+ propertyName: restrictedProp.property,
112
+ message: restrictedProp.message
113
+ ? `: ${restrictedProp.message}`
114
+ : '',
115
+ },
116
+ fix: () => null,
117
+ });
118
+ }
119
+ // If only property is restricted (for any object)
120
+ else if (!restrictedProp.object &&
121
+ restrictedProp.property &&
122
+ propertyMatches) {
123
+ // Check if the object is in the allowObjects list
124
+ if (restrictedProp.allowObjects &&
125
+ node.object.type === utils_1.AST_NODE_TYPES.Identifier &&
126
+ restrictedProp.allowObjects.includes(node.object.name)) {
127
+ continue;
128
+ }
129
+ const objectName = node.object.type === utils_1.AST_NODE_TYPES.Identifier
130
+ ? node.object.name
131
+ : 'unknown';
132
+ context.report({
133
+ node,
134
+ messageId: 'restrictedProperty',
135
+ data: {
136
+ objectName,
137
+ propertyName: restrictedProp.property,
138
+ message: restrictedProp.message
139
+ ? `: ${restrictedProp.message}`
140
+ : '',
141
+ },
142
+ fix: () => null,
143
+ });
144
+ }
145
+ // If only object is restricted (any property)
146
+ else if (restrictedProp.object &&
147
+ !restrictedProp.property &&
148
+ objectMatches) {
149
+ const propertyName = node.property.type === utils_1.AST_NODE_TYPES.Identifier
150
+ ? node.property.name
151
+ : node.property.type === utils_1.AST_NODE_TYPES.Literal
152
+ ? String(node.property.value)
153
+ : 'unknown';
154
+ context.report({
155
+ node,
156
+ messageId: 'restrictedProperty',
157
+ data: {
158
+ objectName: restrictedProp.object,
159
+ propertyName,
160
+ message: restrictedProp.message
161
+ ? `: ${restrictedProp.message}`
162
+ : '',
163
+ },
164
+ fix: () => null,
165
+ });
166
+ }
167
+ }
168
+ },
169
+ };
170
+ },
171
+ });
172
+ //# sourceMappingURL=no-restricted-properties-fix.js.map