@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.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 +170 -139
- package/lib/index.js +190 -1
- package/lib/rules/array-methods-this-context.js +1 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +1 -1
- package/lib/rules/dynamic-https-errors.js +3 -4
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
- package/lib/rules/enforce-callback-memo.js +1 -1
- package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
- package/lib/rules/enforce-console-error.js +35 -7
- package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
- package/lib/rules/enforce-dynamic-file-naming.js +53 -28
- package/lib/rules/enforce-early-destructuring.d.ts +2 -0
- package/lib/rules/enforce-early-destructuring.js +980 -0
- package/lib/rules/enforce-empty-object-check.d.ts +11 -0
- package/lib/rules/enforce-empty-object-check.js +502 -0
- package/lib/rules/enforce-exported-function-types.js +1 -1
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
- package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
- package/lib/rules/enforce-firestore-facade.js +4 -2
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +2 -6
- package/lib/rules/enforce-memoize-getters.js +39 -15
- package/lib/rules/enforce-microdiff.js +1 -1
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +4 -0
- package/lib/rules/enforce-props-argument-name.js +2 -2
- package/lib/rules/enforce-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +1 -1
- package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
- package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
- package/lib/rules/enforce-storage-context.d.ts +9 -0
- package/lib/rules/enforce-storage-context.js +646 -0
- package/lib/rules/enforce-timestamp-now.js +23 -7
- package/lib/rules/enforce-transform-memoization.d.ts +4 -0
- package/lib/rules/enforce-transform-memoization.js +416 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
- package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
- package/lib/rules/enforce-unique-cursor-headers.js +365 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -4
- package/lib/rules/ensure-pointer-events-none.js +28 -2
- package/lib/rules/export-if-in-doubt.js +27 -4
- package/lib/rules/extract-global-constants.js +40 -13
- package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
- package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/global-const-style.js +50 -19
- package/lib/rules/jsdoc-above-field.d.ts +11 -0
- package/lib/rules/jsdoc-above-field.js +208 -0
- package/lib/rules/key-only-outermost-element.js +21 -3
- package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
- package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
- package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +410 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
- package/lib/rules/no-always-true-false-conditions.js +68 -8
- package/lib/rules/no-array-length-in-deps.js +9 -3
- package/lib/rules/no-async-foreach.js +210 -12
- package/lib/rules/no-circular-references.js +18 -17
- package/lib/rules/no-class-instance-destructuring.js +55 -12
- package/lib/rules/no-complex-cloud-params.js +7 -3
- package/lib/rules/no-compositing-layer-props.js +2 -2
- package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
- package/lib/rules/no-console-error.d.ts +9 -0
- package/lib/rules/no-console-error.js +525 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
- package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
- package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
- package/lib/rules/no-entire-object-hook-deps.js +4 -6
- package/lib/rules/no-excessive-parent-chain.js +1 -1
- package/lib/rules/no-explicit-return-type.d.ts +2 -1
- package/lib/rules/no-explicit-return-type.js +215 -26
- package/lib/rules/no-filter-without-return.js +5 -1
- package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
- package/lib/rules/no-firestore-jest-mock.js +126 -8
- package/lib/rules/no-firestore-object-arrays.js +67 -12
- package/lib/rules/no-handler-suffix.d.ts +12 -0
- package/lib/rules/no-handler-suffix.js +305 -0
- package/lib/rules/no-hungarian.js +1 -1
- package/lib/rules/no-inline-component-prop.d.ts +10 -0
- package/lib/rules/no-inline-component-prop.js +456 -0
- package/lib/rules/no-jsx-in-hooks.js +6 -1
- package/lib/rules/no-jsx-whitespace-literal.js +8 -2
- package/lib/rules/no-margin-properties.js +6 -6
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
- package/lib/rules/no-misused-switch-case.js +22 -1
- package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
- package/lib/rules/no-mixed-firestore-transactions.js +296 -34
- package/lib/rules/no-mock-firebase-admin.js +5 -2
- package/lib/rules/no-object-values-on-strings.js +10 -2
- package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
- package/lib/rules/no-passthrough-getters.js +38 -2
- package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
- package/lib/rules/no-redundant-annotation-assertion.js +402 -0
- package/lib/rules/no-redundant-param-types.js +16 -6
- package/lib/rules/no-redundant-this-params.d.ts +3 -0
- package/lib/rules/no-redundant-this-params.js +459 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
- package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
- package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
- package/lib/rules/no-restricted-properties-fix.js +11 -10
- package/lib/rules/no-separate-loading-state.js +7 -16
- package/lib/rules/no-stale-state-across-await.js +1 -1
- package/lib/rules/no-type-assertion-returns.js +43 -49
- package/lib/rules/no-undefined-null-passthrough.js +61 -31
- package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
- package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
- package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
- package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
- package/lib/rules/no-unnecessary-destructuring.js +14 -4
- package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unused-props.d.ts +2 -2
- package/lib/rules/no-unused-props.js +295 -91
- package/lib/rules/no-unused-usestate.js +6 -2
- package/lib/rules/no-useless-fragment.js +28 -2
- package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
- package/lib/rules/no-useless-usememo-primitives.js +393 -0
- package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
- package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
- package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
- package/lib/rules/omit-index-html.d.ts +2 -1
- package/lib/rules/omit-index-html.js +62 -7
- package/lib/rules/optimize-object-boolean-conditions.js +6 -4
- package/lib/rules/parallelize-async-operations.js +24 -5
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +32 -5
- package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
- package/lib/rules/prefer-clone-deep.js +3 -3
- package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
- package/lib/rules/prefer-destructuring-no-class.js +244 -59
- package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
- package/lib/rules/prefer-docsetter-setall.js +243 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
- package/lib/rules/prefer-fragment-component.js +1 -1
- package/lib/rules/prefer-fragment-shorthand.js +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
- package/lib/rules/prefer-global-router-state-key.js +88 -13
- package/lib/rules/prefer-memoized-props.d.ts +3 -0
- package/lib/rules/prefer-memoized-props.js +445 -0
- package/lib/rules/prefer-next-dynamic.js +60 -69
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
- package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
- package/lib/rules/prefer-params-over-parent-id.js +220 -171
- package/lib/rules/prefer-settings-object.js +2 -2
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
- package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
- package/lib/rules/prevent-children-clobber.d.ts +2 -0
- package/lib/rules/prevent-children-clobber.js +536 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +495 -0
- package/lib/rules/react-usememo-should-be-component.js +2 -2
- package/lib/rules/require-hooks-default-params.js +17 -16
- package/lib/rules/require-https-error-cause.d.ts +4 -0
- package/lib/rules/require-https-error-cause.js +136 -0
- package/lib/rules/require-https-error.js +43 -21
- package/lib/rules/require-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +1 -1
- package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
- package/lib/rules/require-memoize-jsx-returners.js +485 -0
- package/lib/rules/require-usememo-object-literals.js +2 -3
- package/lib/rules/test-file-location-enforcement.js +1 -1
- package/lib/rules/use-latest-callback.js +6 -5
- package/lib/rules/vertically-group-related-functions.d.ts +16 -0
- package/lib/rules/vertically-group-related-functions.js +480 -0
- package/lib/utils/ASTHelpers.d.ts +13 -1
- package/lib/utils/ASTHelpers.js +24 -0
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +3 -1
|
@@ -13,6 +13,190 @@ const HANDLER_TYPES = new Set([
|
|
|
13
13
|
'RealtimeDbChangeHandler',
|
|
14
14
|
'RealtimeDbChangeHandlerTransaction',
|
|
15
15
|
]);
|
|
16
|
+
const getQualifiedNameIdentifier = (typeName) => {
|
|
17
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
18
|
+
return typeName.name;
|
|
19
|
+
}
|
|
20
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName &&
|
|
21
|
+
typeName.right.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
22
|
+
return typeName.right.name;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
const checkTypeAnnotationForHandler = (typeNode) => {
|
|
27
|
+
switch (typeNode.type) {
|
|
28
|
+
case utils_1.AST_NODE_TYPES.TSTypeReference: {
|
|
29
|
+
const typeIdentifier = getQualifiedNameIdentifier(typeNode.typeName);
|
|
30
|
+
return typeIdentifier ? HANDLER_TYPES.has(typeIdentifier) : false;
|
|
31
|
+
}
|
|
32
|
+
case utils_1.AST_NODE_TYPES.TSUnionType:
|
|
33
|
+
return typeNode.types.some(checkTypeAnnotationForHandler);
|
|
34
|
+
case utils_1.AST_NODE_TYPES.TSIntersectionType:
|
|
35
|
+
return typeNode.types.some(checkTypeAnnotationForHandler);
|
|
36
|
+
default:
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const findTypeAnnotationInContext = (node) => {
|
|
41
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
42
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
43
|
+
node.parent.id.typeAnnotation) {
|
|
44
|
+
return node.parent.id.typeAnnotation;
|
|
45
|
+
}
|
|
46
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
47
|
+
node.parent.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
48
|
+
node.parent.left.typeAnnotation) {
|
|
49
|
+
return node.parent.left.typeAnnotation;
|
|
50
|
+
}
|
|
51
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
52
|
+
node.parent.value === node) {
|
|
53
|
+
let current = node.parent.parent;
|
|
54
|
+
while (current) {
|
|
55
|
+
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
56
|
+
current.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
57
|
+
current.id.typeAnnotation) {
|
|
58
|
+
return current.id.typeAnnotation;
|
|
59
|
+
}
|
|
60
|
+
current = current.parent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
};
|
|
65
|
+
const isFirebaseChangeHandler = (node) => {
|
|
66
|
+
if (node.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
67
|
+
node.type !== utils_1.AST_NODE_TYPES.FunctionExpression &&
|
|
68
|
+
node.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const typeAnnotation = findTypeAnnotationInContext(node);
|
|
72
|
+
if (!typeAnnotation) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return checkTypeAnnotationForHandler(typeAnnotation.typeAnnotation);
|
|
76
|
+
};
|
|
77
|
+
const isParentIdAccess = (node) => {
|
|
78
|
+
if (node.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
79
|
+
node.property.name !== 'id') {
|
|
80
|
+
return { isMatch: false, depth: 0 };
|
|
81
|
+
}
|
|
82
|
+
const chain = [];
|
|
83
|
+
let current = node.object;
|
|
84
|
+
while (current && current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
85
|
+
if (current.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
86
|
+
return { isMatch: false, depth: 0 };
|
|
87
|
+
}
|
|
88
|
+
chain.unshift(current.property.name);
|
|
89
|
+
current = current.object;
|
|
90
|
+
}
|
|
91
|
+
if (chain.length < 2) {
|
|
92
|
+
return { isMatch: false, depth: 0 };
|
|
93
|
+
}
|
|
94
|
+
const refIndex = chain.lastIndexOf('ref');
|
|
95
|
+
if (refIndex === -1) {
|
|
96
|
+
return { isMatch: false, depth: 0 };
|
|
97
|
+
}
|
|
98
|
+
const parentSegment = chain.slice(refIndex + 1);
|
|
99
|
+
if (parentSegment.length === 0) {
|
|
100
|
+
return { isMatch: false, depth: 0 };
|
|
101
|
+
}
|
|
102
|
+
const invalidParent = parentSegment.some((segment) => segment !== 'parent');
|
|
103
|
+
if (invalidParent) {
|
|
104
|
+
return { isMatch: false, depth: 0 };
|
|
105
|
+
}
|
|
106
|
+
const depth = parentSegment.length;
|
|
107
|
+
return { isMatch: depth > 0, depth };
|
|
108
|
+
};
|
|
109
|
+
const getParentParamName = (depth) => {
|
|
110
|
+
if (depth === 1) {
|
|
111
|
+
return 'userId';
|
|
112
|
+
}
|
|
113
|
+
if (depth === 2) {
|
|
114
|
+
return 'parentId';
|
|
115
|
+
}
|
|
116
|
+
return `parent${depth}Id`;
|
|
117
|
+
};
|
|
118
|
+
const findHandlerFunction = (node, handlerNodes) => {
|
|
119
|
+
let current = node;
|
|
120
|
+
while (current) {
|
|
121
|
+
if (handlerNodes.has(current)) {
|
|
122
|
+
return current;
|
|
123
|
+
}
|
|
124
|
+
current = current.parent;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
};
|
|
128
|
+
const hasOptionalChaining = (node) => {
|
|
129
|
+
let current = node;
|
|
130
|
+
while (current && current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
131
|
+
if (current.optional) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
current = current.object;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
};
|
|
138
|
+
const findParamsInPattern = (pattern) => {
|
|
139
|
+
for (const prop of pattern.properties) {
|
|
140
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
141
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
142
|
+
prop.key.name === 'params') {
|
|
143
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
144
|
+
return { identifier: prop.value.name };
|
|
145
|
+
}
|
|
146
|
+
if (prop.value.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
147
|
+
const properties = new Map();
|
|
148
|
+
for (const p of prop.value.properties) {
|
|
149
|
+
if (p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
150
|
+
p.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
151
|
+
p.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
152
|
+
properties.set(p.key.name, p.value.name);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { properties };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
};
|
|
161
|
+
const getParamsInScope = (handlerNode) => {
|
|
162
|
+
if (handlerNode.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
163
|
+
handlerNode.type !== utils_1.AST_NODE_TYPES.FunctionExpression &&
|
|
164
|
+
handlerNode.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const firstParam = handlerNode.params[0];
|
|
168
|
+
if (!firstParam) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (firstParam.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
172
|
+
const paramsInScope = findParamsInPattern(firstParam);
|
|
173
|
+
if (paramsInScope) {
|
|
174
|
+
return paramsInScope;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (handlerNode.body &&
|
|
178
|
+
handlerNode.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
179
|
+
const eventParamName = firstParam.type === utils_1.AST_NODE_TYPES.Identifier ? firstParam.name : 'event';
|
|
180
|
+
for (const statement of handlerNode.body.body) {
|
|
181
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
for (const declarator of statement.declarations) {
|
|
185
|
+
if (declarator.id.type !== utils_1.AST_NODE_TYPES.ObjectPattern ||
|
|
186
|
+
!declarator.init ||
|
|
187
|
+
declarator.init.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
188
|
+
declarator.init.name !== eventParamName) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const paramsInScope = findParamsInPattern(declarator.id);
|
|
192
|
+
if (paramsInScope) {
|
|
193
|
+
return paramsInScope;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
};
|
|
16
200
|
exports.preferParamsOverParentId = (0, createRule_1.createRule)({
|
|
17
201
|
name: 'prefer-params-over-parent-id',
|
|
18
202
|
meta: {
|
|
@@ -24,199 +208,64 @@ exports.preferParamsOverParentId = (0, createRule_1.createRule)({
|
|
|
24
208
|
fixable: 'code',
|
|
25
209
|
schema: [],
|
|
26
210
|
messages: {
|
|
27
|
-
preferParams:
|
|
211
|
+
preferParams: [
|
|
212
|
+
"What's wrong: This code reads an ID via `ref.parent...id` instead of using the trigger's params.",
|
|
213
|
+
'',
|
|
214
|
+
'Why it matters: Walking `ref.parent` ties the handler to the current path depth; when collections change, it can yield the wrong ID (or a collection name) and bypasses the typed params the trigger provides.',
|
|
215
|
+
'',
|
|
216
|
+
'How to fix: Read the ID from `params.{{paramName}}` (or destructure `const { params } = event` and then access `params.{{paramName}}`).',
|
|
217
|
+
].join('\n'),
|
|
28
218
|
},
|
|
29
219
|
},
|
|
30
220
|
defaultOptions: [],
|
|
31
221
|
create(context) {
|
|
32
|
-
|
|
33
|
-
const handlerFunctions = new Set();
|
|
34
|
-
function findTypeAnnotationInContext(node) {
|
|
35
|
-
// Check variable declarator type annotation
|
|
36
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
37
|
-
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
38
|
-
node.parent.id.typeAnnotation) {
|
|
39
|
-
return node.parent.id.typeAnnotation;
|
|
40
|
-
}
|
|
41
|
-
// Check assignment expression with type annotation
|
|
42
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
43
|
-
node.parent.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
44
|
-
node.parent.left.typeAnnotation) {
|
|
45
|
-
return node.parent.left.typeAnnotation;
|
|
46
|
-
}
|
|
47
|
-
// Check property definition type annotation
|
|
48
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
49
|
-
node.parent.value === node) {
|
|
50
|
-
// Look up the tree for type annotation
|
|
51
|
-
let current = node.parent.parent;
|
|
52
|
-
while (current) {
|
|
53
|
-
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
54
|
-
current.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
55
|
-
current.id.typeAnnotation) {
|
|
56
|
-
return current.id.typeAnnotation;
|
|
57
|
-
}
|
|
58
|
-
current = current.parent;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
function isFirebaseChangeHandler(node) {
|
|
64
|
-
if (node.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
65
|
-
node.type !== utils_1.AST_NODE_TYPES.FunctionExpression &&
|
|
66
|
-
node.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
const typeAnnotation = findTypeAnnotationInContext(node);
|
|
70
|
-
if (!typeAnnotation) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return checkTypeAnnotationForHandler(typeAnnotation.typeAnnotation);
|
|
74
|
-
}
|
|
75
|
-
function checkTypeAnnotationForHandler(typeNode) {
|
|
76
|
-
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
77
|
-
typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
78
|
-
return HANDLER_TYPES.has(typeNode.typeName.name);
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
function isParentIdAccess(node) {
|
|
83
|
-
// Check if this is a .ref.parent[.parent...].id pattern
|
|
84
|
-
if (node.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
85
|
-
node.property.name !== 'id') {
|
|
86
|
-
return { isMatch: false, depth: 0 };
|
|
87
|
-
}
|
|
88
|
-
const chain = [];
|
|
89
|
-
let current = node.object;
|
|
90
|
-
while (current && current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
91
|
-
if (current.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
92
|
-
return { isMatch: false, depth: 0 };
|
|
93
|
-
}
|
|
94
|
-
chain.unshift(current.property.name);
|
|
95
|
-
current = current.object;
|
|
96
|
-
}
|
|
97
|
-
if (chain.length < 2) {
|
|
98
|
-
return { isMatch: false, depth: 0 };
|
|
99
|
-
}
|
|
100
|
-
const refIndex = chain.indexOf('ref');
|
|
101
|
-
if (refIndex === -1) {
|
|
102
|
-
return { isMatch: false, depth: 0 };
|
|
103
|
-
}
|
|
104
|
-
const parentSegment = chain.slice(refIndex + 1);
|
|
105
|
-
if (parentSegment.length === 0) {
|
|
106
|
-
return { isMatch: false, depth: 0 };
|
|
107
|
-
}
|
|
108
|
-
const invalidParent = parentSegment.some((segment) => segment !== 'parent');
|
|
109
|
-
if (invalidParent) {
|
|
110
|
-
return { isMatch: false, depth: 0 };
|
|
111
|
-
}
|
|
112
|
-
const depth = parentSegment.length;
|
|
113
|
-
return { isMatch: depth > 0, depth };
|
|
114
|
-
}
|
|
115
|
-
function findHandlerFunction(node) {
|
|
116
|
-
let current = node;
|
|
117
|
-
while (current) {
|
|
118
|
-
if (handlerFunctions.has(current)) {
|
|
119
|
-
return current;
|
|
120
|
-
}
|
|
121
|
-
current = current.parent;
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
function hasOptionalChaining(node) {
|
|
126
|
-
let current = node;
|
|
127
|
-
while (current && current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
128
|
-
if (current.optional) {
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
current = current.object;
|
|
132
|
-
}
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
function isParamsInScope(handlerNode) {
|
|
136
|
-
// Check if params is destructured from the event parameter
|
|
137
|
-
if (handlerNode.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
138
|
-
handlerNode.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
139
|
-
handlerNode.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
140
|
-
const firstParam = handlerNode.params[0];
|
|
141
|
-
if (!firstParam)
|
|
142
|
-
return false;
|
|
143
|
-
// Check for destructuring pattern: ({ params }) or ({ data, params })
|
|
144
|
-
if (firstParam.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
145
|
-
return firstParam.properties.some((prop) => {
|
|
146
|
-
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
147
|
-
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
148
|
-
prop.key.name === 'params') {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
return false;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
// Check for variable declarations inside the function that destructure params
|
|
155
|
-
if (handlerNode.body &&
|
|
156
|
-
handlerNode.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
157
|
-
for (const statement of handlerNode.body.body) {
|
|
158
|
-
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
159
|
-
for (const declarator of statement.declarations) {
|
|
160
|
-
if (declarator.id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
161
|
-
declarator.init &&
|
|
162
|
-
declarator.init.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
163
|
-
// Check if destructuring from event parameter
|
|
164
|
-
const eventParamName = firstParam.type === utils_1.AST_NODE_TYPES.Identifier
|
|
165
|
-
? firstParam.name
|
|
166
|
-
: 'event';
|
|
167
|
-
if (declarator.init.name === eventParamName) {
|
|
168
|
-
return declarator.id.properties.some((prop) => {
|
|
169
|
-
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
170
|
-
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
171
|
-
prop.key.name === 'params') {
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
return false;
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
222
|
+
const handlerNodes = new Set();
|
|
185
223
|
return {
|
|
186
224
|
// Track Firebase change handler functions
|
|
187
225
|
'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression'(node) {
|
|
188
226
|
if (isFirebaseChangeHandler(node)) {
|
|
189
|
-
|
|
227
|
+
handlerNodes.add(node);
|
|
190
228
|
}
|
|
191
229
|
},
|
|
192
230
|
// Detect .ref.parent.id patterns
|
|
193
231
|
MemberExpression(node) {
|
|
194
232
|
const parentAccess = isParentIdAccess(node);
|
|
195
233
|
if (parentAccess.isMatch) {
|
|
196
|
-
const handlerNode = findHandlerFunction(node);
|
|
234
|
+
const handlerNode = findHandlerFunction(node, handlerNodes);
|
|
197
235
|
if (handlerNode) {
|
|
198
236
|
const hasOptional = hasOptionalChaining(node);
|
|
199
|
-
const paramsInScope =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
237
|
+
const paramsInScope = getParamsInScope(handlerNode);
|
|
238
|
+
if (!paramsInScope) {
|
|
239
|
+
context.report({
|
|
240
|
+
node,
|
|
241
|
+
messageId: 'preferParams',
|
|
242
|
+
data: {
|
|
243
|
+
paramName: getParentParamName(parentAccess.depth),
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const paramSuggestion = getParentParamName(parentAccess.depth);
|
|
249
|
+
let replacement = null;
|
|
250
|
+
if (paramsInScope.identifier) {
|
|
251
|
+
replacement = hasOptional
|
|
252
|
+
? `${paramsInScope.identifier}?.${paramSuggestion}`
|
|
253
|
+
: `${paramsInScope.identifier}.${paramSuggestion}`;
|
|
254
|
+
}
|
|
255
|
+
else if (paramsInScope.properties) {
|
|
256
|
+
const localName = paramsInScope.properties.get(paramSuggestion);
|
|
257
|
+
if (localName) {
|
|
258
|
+
replacement = localName;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
207
261
|
context.report({
|
|
208
262
|
node,
|
|
209
263
|
messageId: 'preferParams',
|
|
210
264
|
data: {
|
|
211
265
|
paramName: paramSuggestion,
|
|
212
266
|
},
|
|
213
|
-
fix:
|
|
214
|
-
? (fixer) =>
|
|
215
|
-
const replacement = hasOptional
|
|
216
|
-
? `params?.${paramSuggestion}`
|
|
217
|
-
: `params.${paramSuggestion}`;
|
|
218
|
-
return fixer.replaceText(node, replacement);
|
|
219
|
-
}
|
|
267
|
+
fix: replacement
|
|
268
|
+
? (fixer) => fixer.replaceText(node, replacement)
|
|
220
269
|
: undefined,
|
|
221
270
|
});
|
|
222
271
|
}
|
|
@@ -62,7 +62,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
62
62
|
// If there are type parameters, include them in the type signature
|
|
63
63
|
if (typeNode.typeParameters &&
|
|
64
64
|
typeNode.typeParameters.params.length > 0) {
|
|
65
|
-
const sourceCode = context.
|
|
65
|
+
const sourceCode = context.sourceCode;
|
|
66
66
|
const typeParams = typeNode.typeParameters.params
|
|
67
67
|
.map((param) => sourceCode.getText(param))
|
|
68
68
|
.join(', ');
|
|
@@ -82,7 +82,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
82
82
|
// If there are type parameters, include them in the type signature
|
|
83
83
|
if (typeNode.typeParameters &&
|
|
84
84
|
typeNode.typeParameters.params.length > 0) {
|
|
85
|
-
const sourceCode = context.
|
|
85
|
+
const sourceCode = context.sourceCode;
|
|
86
86
|
const typeParams = typeNode.typeParameters.params
|
|
87
87
|
.map((param) => sourceCode.getText(param))
|
|
88
88
|
.join(', ');
|
|
@@ -12,7 +12,9 @@ exports.preferTypeOverInterface = (0, createRule_1.createRule)({
|
|
|
12
12
|
},
|
|
13
13
|
schema: [],
|
|
14
14
|
messages: {
|
|
15
|
-
preferType: '
|
|
15
|
+
preferType: 'Interface "{{interfaceName}}" should be declared as a type alias. ' +
|
|
16
|
+
'Interfaces can merge across declarations and extend in chains, which fragments the resulting shape across files and makes composition harder to predict and trace. ' +
|
|
17
|
+
'Replace `interface` with `type` and use intersections (for example, `type {{interfaceName}} = Base & { field: string }`) to keep the contract closed and predictable.',
|
|
16
18
|
},
|
|
17
19
|
fixable: 'code',
|
|
18
20
|
},
|
|
@@ -23,8 +25,11 @@ exports.preferTypeOverInterface = (0, createRule_1.createRule)({
|
|
|
23
25
|
context.report({
|
|
24
26
|
node,
|
|
25
27
|
messageId: 'preferType',
|
|
28
|
+
data: {
|
|
29
|
+
interfaceName: node.id.name,
|
|
30
|
+
},
|
|
26
31
|
fix(fixer) {
|
|
27
|
-
const sourceCode = context.
|
|
32
|
+
const sourceCode = context.sourceCode;
|
|
28
33
|
const openingBrace = sourceCode.getTokenAfter(node.id, {
|
|
29
34
|
filter: (token) => token.value === '{',
|
|
30
35
|
});
|
|
@@ -50,7 +50,7 @@ function isOptionalMemberExpression(expr) {
|
|
|
50
50
|
}
|
|
51
51
|
exports.preferUrlToStringOverToJson = (0, createRule_1.createRule)({
|
|
52
52
|
create(context) {
|
|
53
|
-
const sourceCode = context.
|
|
53
|
+
const sourceCode = context.sourceCode;
|
|
54
54
|
const parserServices = sourceCode.parserServices;
|
|
55
55
|
const urlIdentifierNames = new Set();
|
|
56
56
|
let checker = null;
|
|
@@ -132,6 +132,9 @@ exports.preferUrlToStringOverToJson = (0, createRule_1.createRule)({
|
|
|
132
132
|
context.report({
|
|
133
133
|
node,
|
|
134
134
|
messageId: 'preferToString',
|
|
135
|
+
data: {
|
|
136
|
+
urlText: sourceCode.getText(objectExpr),
|
|
137
|
+
},
|
|
135
138
|
fix(fixer) {
|
|
136
139
|
// If inside JSON.stringify and not optional chain, replace the entire call with just the object
|
|
137
140
|
if (insideJSONStringify &&
|
|
@@ -151,14 +154,14 @@ exports.preferUrlToStringOverToJson = (0, createRule_1.createRule)({
|
|
|
151
154
|
meta: {
|
|
152
155
|
type: 'suggestion',
|
|
153
156
|
docs: {
|
|
154
|
-
description: 'Enforce the use of toString() over toJSON() on URL objects.
|
|
157
|
+
description: 'Enforce the use of toString() over toJSON() on URL objects.',
|
|
155
158
|
recommended: 'error',
|
|
156
159
|
requiresTypeChecking: false,
|
|
157
160
|
},
|
|
158
161
|
fixable: 'code',
|
|
159
162
|
schema: [],
|
|
160
163
|
messages: {
|
|
161
|
-
preferToString: '
|
|
164
|
+
preferToString: 'URL value {{urlText}} calls toJSON() explicitly. URL.toJSON() delegates directly to toString(), so the call is redundant. Inside JSON.stringify it is unnecessary because JSON.stringify already invokes toJSON automatically; elsewhere it hides intent because readers must remember the delegation to see it produces the same string as toString. Use toString() when you need an explicit string, or pass the URL directly to JSON.stringify to rely on its built-in serialization.',
|
|
162
165
|
},
|
|
163
166
|
},
|
|
164
167
|
defaultOptions: [],
|
|
@@ -106,7 +106,7 @@ function isNonPrimitiveWithTypes(context, expr) {
|
|
|
106
106
|
}
|
|
107
107
|
function collectMemoizedIdentifiers(context) {
|
|
108
108
|
const memoized = new Set();
|
|
109
|
-
const sourceCode = context.
|
|
109
|
+
const sourceCode = context.sourceCode;
|
|
110
110
|
const program = sourceCode.ast;
|
|
111
111
|
function visit(node) {
|
|
112
112
|
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
@@ -156,7 +156,7 @@ function collectMemoizedIdentifiers(context) {
|
|
|
156
156
|
}
|
|
157
157
|
function ensureDeepCompareImportFixes(context, fixer) {
|
|
158
158
|
const fixes = [];
|
|
159
|
-
const sourceCode = context.
|
|
159
|
+
const sourceCode = context.sourceCode;
|
|
160
160
|
const program = sourceCode.ast;
|
|
161
161
|
// If already imported anywhere, skip adding
|
|
162
162
|
const hasImport = program.body.some((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
@@ -227,7 +227,7 @@ function removeImportSpecifierFixes(sourceCode, fixer, importDecl, specifier) {
|
|
|
227
227
|
return fixes;
|
|
228
228
|
}
|
|
229
229
|
function isImportedIdentifier(context, name) {
|
|
230
|
-
const sourceCode = context.
|
|
230
|
+
const sourceCode = context.sourceCode;
|
|
231
231
|
const program = sourceCode.ast;
|
|
232
232
|
for (const node of program.body) {
|
|
233
233
|
if (node.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
|
|
@@ -319,7 +319,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
319
319
|
fixable: 'code',
|
|
320
320
|
schema: [],
|
|
321
321
|
messages: {
|
|
322
|
-
preferUseDeepCompareMemo: 'Use useDeepCompareMemo
|
|
322
|
+
preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays/functions that change identity each render, so React treats them as changed and reruns the memoized computation, triggering avoidable renders. Use useDeepCompareMemo (or memoize those dependencies first) so comparisons use deep equality and the memo stays stable.',
|
|
323
323
|
},
|
|
324
324
|
},
|
|
325
325
|
defaultOptions: [],
|
|
@@ -386,6 +386,9 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
386
386
|
context.report({
|
|
387
387
|
node,
|
|
388
388
|
messageId: 'preferUseDeepCompareMemo',
|
|
389
|
+
data: {
|
|
390
|
+
hook: 'useMemo',
|
|
391
|
+
},
|
|
389
392
|
fix(fixer) {
|
|
390
393
|
const fixes = [];
|
|
391
394
|
// Replace callee
|
|
@@ -398,7 +401,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
398
401
|
// Ensure import exists
|
|
399
402
|
fixes.push(...ensureDeepCompareImportFixes(context, fixer));
|
|
400
403
|
// Clean up now-unused React/useMemo imports if safe
|
|
401
|
-
const sourceCode = context.
|
|
404
|
+
const sourceCode = context.sourceCode;
|
|
402
405
|
const program = sourceCode.ast;
|
|
403
406
|
const reactImport = program.body.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
404
407
|
n.source.value === 'react');
|
|
@@ -166,7 +166,7 @@ function getMemoizedFunctionDescription(node) {
|
|
|
166
166
|
return 'this callback value';
|
|
167
167
|
}
|
|
168
168
|
function reportAndFix(node, context) {
|
|
169
|
-
const sourceCode = context.
|
|
169
|
+
const sourceCode = context.sourceCode;
|
|
170
170
|
const useMemoCallback = node.arguments[0];
|
|
171
171
|
const dependencyArray = node.arguments[1]
|
|
172
172
|
? sourceCode.getText(node.arguments[1])
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.preferUtilityFunctionOverPrivateStatic = void 0;
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const getMethodName_1 = require("../utils/getMethodName");
|
|
5
6
|
exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
6
7
|
name: 'prefer-utility-function-over-private-static',
|
|
7
8
|
meta: {
|
|
@@ -12,7 +13,7 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
|
12
13
|
},
|
|
13
14
|
schema: [],
|
|
14
15
|
messages: {
|
|
15
|
-
preferUtilityFunctionOverPrivateStatic: 'Private static
|
|
16
|
+
preferUtilityFunctionOverPrivateStatic: 'Private static method "{{methodName}}" in class "{{className}}" does not use class state. Keeping class-agnostic helpers as private statics hides reusable logic and signals coupling that is not present. Extract this logic into a standalone utility function (module-level or shared) and import it where needed so it can be reused and unit tested independently.',
|
|
16
17
|
},
|
|
17
18
|
},
|
|
18
19
|
defaultOptions: [],
|
|
@@ -47,9 +48,32 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
|
47
48
|
}
|
|
48
49
|
return false;
|
|
49
50
|
};
|
|
51
|
+
const getClassName = (method) => {
|
|
52
|
+
const classNode = method.parent?.parent;
|
|
53
|
+
if (classNode &&
|
|
54
|
+
(classNode.type === 'ClassDeclaration' ||
|
|
55
|
+
classNode.type === 'ClassExpression')) {
|
|
56
|
+
if (classNode.id && classNode.id.type === 'Identifier') {
|
|
57
|
+
return classNode.id.name;
|
|
58
|
+
}
|
|
59
|
+
if (classNode.type === 'ClassExpression' &&
|
|
60
|
+
classNode.parent &&
|
|
61
|
+
classNode.parent.type === 'VariableDeclarator' &&
|
|
62
|
+
classNode.parent.id.type === 'Identifier') {
|
|
63
|
+
return classNode.parent.id.name;
|
|
64
|
+
}
|
|
65
|
+
if (classNode.type === 'ClassExpression' &&
|
|
66
|
+
classNode.parent &&
|
|
67
|
+
classNode.parent.type === 'AssignmentExpression' &&
|
|
68
|
+
classNode.parent.left.type === 'Identifier') {
|
|
69
|
+
return classNode.parent.left.name;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return 'this class';
|
|
73
|
+
};
|
|
50
74
|
return {
|
|
51
75
|
'MethodDefinition[static=true][accessibility="private"]'(node) {
|
|
52
|
-
const sourceCode = context.
|
|
76
|
+
const sourceCode = context.sourceCode;
|
|
53
77
|
const methodBody = node.value.body;
|
|
54
78
|
if (!methodBody) {
|
|
55
79
|
return;
|
|
@@ -65,9 +89,17 @@ exports.preferUtilityFunctionOverPrivateStatic = (0, createRule_1.createRule)({
|
|
|
65
89
|
const usesThis = hasThisReference(methodBody);
|
|
66
90
|
// If the method doesn't use 'this', it's a good candidate for extraction
|
|
67
91
|
if (!usesThis) {
|
|
92
|
+
const methodName = (0, getMethodName_1.getMethodName)(node, sourceCode, {
|
|
93
|
+
computedFallbackToText: false,
|
|
94
|
+
}) || '<unknown>';
|
|
95
|
+
const className = getClassName(node);
|
|
68
96
|
context.report({
|
|
69
97
|
node,
|
|
70
98
|
messageId: 'preferUtilityFunctionOverPrivateStatic',
|
|
99
|
+
data: {
|
|
100
|
+
methodName,
|
|
101
|
+
className,
|
|
102
|
+
},
|
|
71
103
|
});
|
|
72
104
|
}
|
|
73
105
|
},
|