@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.
- package/README.md +140 -99
- package/lib/index.js +111 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -10
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +250 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-exported-function-types.js +3 -3
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
- package/lib/rules/enforce-firestore-facade.js +347 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +96 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +123 -0
- package/lib/rules/enforce-microdiff.js +2 -1
- package/lib/rules/enforce-positive-naming.js +5 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-singular-type-names.js +9 -5
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
- package/lib/rules/enforce-verb-noun-naming.js +85 -10
- package/lib/rules/extract-global-constants.js +57 -45
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +308 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-complex-cloud-params.js +15 -2
- package/lib/rules/no-compositing-layer-props.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +317 -63
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-firestore-object-arrays.js +345 -30
- package/lib/rules/no-hungarian.js +13 -13
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +16 -3
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +170 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +172 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +142 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +6 -0
- package/lib/rules/no-undefined-null-passthrough.js +166 -19
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +3 -0
- package/lib/rules/no-unused-props.js +213 -78
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +234 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +375 -0
- package/lib/rules/prefer-batch-operations.js +199 -33
- package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
- package/lib/rules/prefer-fragment-component.js +3 -3
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +230 -31
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +366 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +228 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +11 -1
- package/lib/rules/require-usememo-object-literals.js +7 -1
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +270 -0
- package/lib/utils/ASTHelpers.js +10 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/package.json +21 -14
- 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
|