@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
|
@@ -11,6 +11,98 @@ const defaultOptions = {
|
|
|
11
11
|
allowDtsFiles: true,
|
|
12
12
|
allowFirestoreFunctionFiles: true,
|
|
13
13
|
};
|
|
14
|
+
function getNameFromIdentifierOrLiteral(key) {
|
|
15
|
+
if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
16
|
+
return key.name;
|
|
17
|
+
}
|
|
18
|
+
if (typeof key.value === 'string') {
|
|
19
|
+
return key.value;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
function describeClassMethod(node) {
|
|
24
|
+
if (!node.computed &&
|
|
25
|
+
(node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
26
|
+
(node.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
27
|
+
typeof node.key.value === 'string'))) {
|
|
28
|
+
const name = getNameFromIdentifierOrLiteral(node.key);
|
|
29
|
+
if (name) {
|
|
30
|
+
return `class method "${name}"`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return 'class method';
|
|
34
|
+
}
|
|
35
|
+
function describeMethodSignature(node) {
|
|
36
|
+
if (!node.computed &&
|
|
37
|
+
(node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
38
|
+
(node.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
39
|
+
typeof node.key.value === 'string'))) {
|
|
40
|
+
const name = getNameFromIdentifierOrLiteral(node.key);
|
|
41
|
+
if (name) {
|
|
42
|
+
return `interface method "${name}"`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return 'interface method';
|
|
46
|
+
}
|
|
47
|
+
function describeFunctionDeclaration(node) {
|
|
48
|
+
if (node.id?.name) {
|
|
49
|
+
return `function "${node.id.name}"`;
|
|
50
|
+
}
|
|
51
|
+
return 'function';
|
|
52
|
+
}
|
|
53
|
+
function describeTSDeclareFunction(node) {
|
|
54
|
+
if (node.id?.name) {
|
|
55
|
+
return `function "${node.id.name}"`;
|
|
56
|
+
}
|
|
57
|
+
return 'function';
|
|
58
|
+
}
|
|
59
|
+
function describeFunctionExpression(node) {
|
|
60
|
+
if (node.id?.name) {
|
|
61
|
+
return `function "${node.id.name}"`;
|
|
62
|
+
}
|
|
63
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
64
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
65
|
+
return `function "${node.parent.id.name}"`;
|
|
66
|
+
}
|
|
67
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
68
|
+
!node.parent.computed &&
|
|
69
|
+
(node.parent.key.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
70
|
+
(node.parent.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
71
|
+
typeof node.parent.key.value === 'string'))) {
|
|
72
|
+
const name = getNameFromIdentifierOrLiteral(node.parent.key);
|
|
73
|
+
if (name) {
|
|
74
|
+
return `object method "${name}"`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return 'function expression';
|
|
78
|
+
}
|
|
79
|
+
function describeArrowFunction(node) {
|
|
80
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
81
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
82
|
+
return `arrow function "${node.parent.id.name}"`;
|
|
83
|
+
}
|
|
84
|
+
return 'arrow function';
|
|
85
|
+
}
|
|
86
|
+
function describeFunctionKind(node) {
|
|
87
|
+
switch (node.type) {
|
|
88
|
+
case utils_1.AST_NODE_TYPES.MethodDefinition:
|
|
89
|
+
return describeClassMethod(node);
|
|
90
|
+
case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
|
|
91
|
+
return describeClassMethod(node);
|
|
92
|
+
case utils_1.AST_NODE_TYPES.TSMethodSignature:
|
|
93
|
+
return describeMethodSignature(node);
|
|
94
|
+
case utils_1.AST_NODE_TYPES.TSDeclareFunction:
|
|
95
|
+
return describeTSDeclareFunction(node);
|
|
96
|
+
case utils_1.AST_NODE_TYPES.FunctionDeclaration:
|
|
97
|
+
return describeFunctionDeclaration(node);
|
|
98
|
+
case utils_1.AST_NODE_TYPES.FunctionExpression:
|
|
99
|
+
return describeFunctionExpression(node);
|
|
100
|
+
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
|
|
101
|
+
return describeArrowFunction(node);
|
|
102
|
+
default:
|
|
103
|
+
return 'function';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
14
106
|
function isRecursiveFunction(node) {
|
|
15
107
|
const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
|
|
16
108
|
? node.id?.name
|
|
@@ -63,16 +155,52 @@ function isOverloadedFunction(node) {
|
|
|
63
155
|
const interfaceBody = node.parent;
|
|
64
156
|
if (interfaceBody.type !== utils_1.AST_NODE_TYPES.TSInterfaceBody)
|
|
65
157
|
return false;
|
|
66
|
-
|
|
158
|
+
if (node.computed)
|
|
159
|
+
return false;
|
|
160
|
+
const methodName = node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
161
|
+
node.key.type === utils_1.AST_NODE_TYPES.Literal
|
|
162
|
+
? getNameFromIdentifierOrLiteral(node.key)
|
|
163
|
+
: undefined;
|
|
67
164
|
if (!methodName)
|
|
68
165
|
return false;
|
|
69
166
|
return (interfaceBody.body.filter((member) => member.type === utils_1.AST_NODE_TYPES.TSMethodSignature &&
|
|
70
|
-
member.
|
|
71
|
-
member.key.
|
|
167
|
+
!member.computed &&
|
|
168
|
+
(member.key.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
169
|
+
member.key.type === utils_1.AST_NODE_TYPES.Literal) &&
|
|
170
|
+
getNameFromIdentifierOrLiteral(member.key) === methodName).length > 1);
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
function isOverloadedTsDeclareFunction(node) {
|
|
175
|
+
const functionName = node.id?.name;
|
|
176
|
+
if (!functionName)
|
|
177
|
+
return false;
|
|
178
|
+
let container = node.parent;
|
|
179
|
+
while (container) {
|
|
180
|
+
if (container.type === utils_1.AST_NODE_TYPES.Program ||
|
|
181
|
+
container.type === utils_1.AST_NODE_TYPES.TSModuleBlock) {
|
|
182
|
+
const declarations = container.body
|
|
183
|
+
.map((statement) => {
|
|
184
|
+
if (statement.type === utils_1.AST_NODE_TYPES.TSDeclareFunction) {
|
|
185
|
+
return statement;
|
|
186
|
+
}
|
|
187
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration &&
|
|
188
|
+
statement.declaration?.type === utils_1.AST_NODE_TYPES.TSDeclareFunction) {
|
|
189
|
+
return statement.declaration;
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
})
|
|
193
|
+
.filter((value) => Boolean(value?.id?.name))
|
|
194
|
+
.filter((decl) => decl.id.name === functionName);
|
|
195
|
+
return declarations.length > 1;
|
|
196
|
+
}
|
|
197
|
+
container = container.parent;
|
|
72
198
|
}
|
|
73
199
|
return false;
|
|
74
200
|
}
|
|
75
201
|
function isInterfaceOrAbstractMethodSignature(node) {
|
|
202
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition)
|
|
203
|
+
return true;
|
|
76
204
|
if (node.type === utils_1.AST_NODE_TYPES.TSMethodSignature)
|
|
77
205
|
return true;
|
|
78
206
|
if (node.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
@@ -133,7 +261,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
133
261
|
},
|
|
134
262
|
],
|
|
135
263
|
messages: {
|
|
136
|
-
|
|
264
|
+
noExplicitReturnTypeInferable: "What's wrong: {{functionKind}} has an explicit return type annotation. \u2192 Why it matters: it must be updated manually and can drift from what the implementation actually returns, hiding bugs behind a stale type. \u2192 How to fix: remove the return type annotation so TypeScript infers it from the implementation.",
|
|
265
|
+
noExplicitReturnTypeNonInferable: "What's wrong: {{functionKind}} has an explicit return type annotation but no implementation body. \u2192 Why it matters: TypeScript cannot infer the return type here; removing it widens the return type to `any`. \u2192 How to fix: keep the annotation, or provide an implementation body where inference can succeed.",
|
|
137
266
|
},
|
|
138
267
|
},
|
|
139
268
|
defaultOptions: [defaultOptions],
|
|
@@ -141,58 +270,77 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
141
270
|
const mergedOptions = { ...defaultOptions, ...options };
|
|
142
271
|
const filename = context.getFilename();
|
|
143
272
|
if ((mergedOptions.allowDtsFiles && filename.endsWith('.d.ts')) ||
|
|
144
|
-
(mergedOptions.allowFirestoreFunctionFiles &&
|
|
273
|
+
(mergedOptions.allowFirestoreFunctionFiles &&
|
|
274
|
+
filename.endsWith('.f.ts'))) {
|
|
145
275
|
return {};
|
|
146
276
|
}
|
|
147
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
277
|
function fixReturnType(fixer, node) {
|
|
149
|
-
|
|
278
|
+
// Some nodes expose returnType directly while others nest it under value.
|
|
279
|
+
const returnType = 'returnType' in node
|
|
280
|
+
? node.returnType
|
|
281
|
+
: 'value' in node
|
|
282
|
+
? node.value.returnType
|
|
283
|
+
: null;
|
|
150
284
|
if (!returnType)
|
|
151
285
|
return null;
|
|
152
|
-
// Create a fix that removes the return type annotation
|
|
153
286
|
return fixer.remove(returnType);
|
|
154
287
|
}
|
|
155
288
|
return {
|
|
156
289
|
FunctionDeclaration(node) {
|
|
157
|
-
|
|
290
|
+
const returnType = node.returnType;
|
|
291
|
+
if (!returnType)
|
|
158
292
|
return;
|
|
159
293
|
if (isTypeGuardFunction(node) ||
|
|
160
294
|
(mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
|
|
161
295
|
return;
|
|
162
296
|
}
|
|
297
|
+
const isInferable = Boolean(node.body);
|
|
163
298
|
context.report({
|
|
164
|
-
node:
|
|
165
|
-
messageId:
|
|
166
|
-
|
|
299
|
+
node: returnType,
|
|
300
|
+
messageId: isInferable
|
|
301
|
+
? 'noExplicitReturnTypeInferable'
|
|
302
|
+
: 'noExplicitReturnTypeNonInferable',
|
|
303
|
+
data: { functionKind: describeFunctionKind(node) },
|
|
304
|
+
...(isInferable
|
|
305
|
+
? { fix: (fixer) => fixReturnType(fixer, node) }
|
|
306
|
+
: {}),
|
|
167
307
|
});
|
|
168
308
|
},
|
|
169
309
|
FunctionExpression(node) {
|
|
170
|
-
|
|
310
|
+
const returnType = node.returnType;
|
|
311
|
+
if (!returnType)
|
|
312
|
+
return;
|
|
313
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
171
314
|
return;
|
|
315
|
+
}
|
|
172
316
|
if (isTypeGuardFunction(node) ||
|
|
173
317
|
(mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
|
|
174
318
|
return;
|
|
175
319
|
}
|
|
176
320
|
context.report({
|
|
177
|
-
node:
|
|
178
|
-
messageId: '
|
|
321
|
+
node: returnType,
|
|
322
|
+
messageId: 'noExplicitReturnTypeInferable',
|
|
323
|
+
data: { functionKind: describeFunctionKind(node) },
|
|
179
324
|
fix: (fixer) => fixReturnType(fixer, node),
|
|
180
325
|
});
|
|
181
326
|
},
|
|
182
327
|
ArrowFunctionExpression(node) {
|
|
183
|
-
|
|
328
|
+
const returnType = node.returnType;
|
|
329
|
+
if (!returnType)
|
|
184
330
|
return;
|
|
185
331
|
if (isTypeGuardFunction(node)) {
|
|
186
332
|
return;
|
|
187
333
|
}
|
|
188
334
|
context.report({
|
|
189
|
-
node:
|
|
190
|
-
messageId: '
|
|
335
|
+
node: returnType,
|
|
336
|
+
messageId: 'noExplicitReturnTypeInferable',
|
|
337
|
+
data: { functionKind: describeFunctionKind(node) },
|
|
191
338
|
fix: (fixer) => fixReturnType(fixer, node),
|
|
192
339
|
});
|
|
193
340
|
},
|
|
194
341
|
TSMethodSignature(node) {
|
|
195
|
-
|
|
342
|
+
const returnType = node.returnType;
|
|
343
|
+
if (!returnType)
|
|
196
344
|
return;
|
|
197
345
|
if (mergedOptions.allowInterfaceMethodSignatures) {
|
|
198
346
|
return;
|
|
@@ -202,23 +350,64 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
202
350
|
return;
|
|
203
351
|
}
|
|
204
352
|
context.report({
|
|
205
|
-
node:
|
|
206
|
-
messageId: '
|
|
207
|
-
|
|
353
|
+
node: returnType,
|
|
354
|
+
messageId: 'noExplicitReturnTypeNonInferable',
|
|
355
|
+
data: { functionKind: describeFunctionKind(node) },
|
|
208
356
|
});
|
|
209
357
|
},
|
|
210
358
|
MethodDefinition(node) {
|
|
211
|
-
|
|
359
|
+
const returnType = node.value.returnType;
|
|
360
|
+
if (!returnType)
|
|
212
361
|
return;
|
|
213
362
|
if (isTypeGuardFunction(node.value) ||
|
|
214
363
|
(mergedOptions.allowAbstractMethodSignatures &&
|
|
215
364
|
isInterfaceOrAbstractMethodSignature(node))) {
|
|
216
365
|
return;
|
|
217
366
|
}
|
|
367
|
+
const isInferable = Boolean(node.value.body);
|
|
218
368
|
context.report({
|
|
219
|
-
node:
|
|
220
|
-
messageId:
|
|
221
|
-
|
|
369
|
+
node: returnType,
|
|
370
|
+
messageId: isInferable
|
|
371
|
+
? 'noExplicitReturnTypeInferable'
|
|
372
|
+
: 'noExplicitReturnTypeNonInferable',
|
|
373
|
+
data: { functionKind: describeFunctionKind(node) },
|
|
374
|
+
...(isInferable
|
|
375
|
+
? { fix: (fixer) => fixReturnType(fixer, node) }
|
|
376
|
+
: {}),
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
TSAbstractMethodDefinition(node) {
|
|
380
|
+
const returnType = node.value.returnType;
|
|
381
|
+
if (!returnType)
|
|
382
|
+
return;
|
|
383
|
+
if (isTypeGuardFunction(node.value) ||
|
|
384
|
+
(mergedOptions.allowAbstractMethodSignatures &&
|
|
385
|
+
isInterfaceOrAbstractMethodSignature(node))) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
// Abstract methods never have bodies; they are always non-inferable and
|
|
389
|
+
// intentionally have no fixer.
|
|
390
|
+
context.report({
|
|
391
|
+
node: returnType,
|
|
392
|
+
messageId: 'noExplicitReturnTypeNonInferable',
|
|
393
|
+
data: { functionKind: describeFunctionKind(node) },
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
TSDeclareFunction(node) {
|
|
397
|
+
const returnType = node.returnType;
|
|
398
|
+
if (!returnType)
|
|
399
|
+
return;
|
|
400
|
+
if (isTypeGuardFunction(node)) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (mergedOptions.allowOverloadedFunctions &&
|
|
404
|
+
isOverloadedTsDeclareFunction(node)) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
context.report({
|
|
408
|
+
node: returnType,
|
|
409
|
+
messageId: 'noExplicitReturnTypeNonInferable',
|
|
410
|
+
data: { functionKind: describeFunctionKind(node) },
|
|
222
411
|
});
|
|
223
412
|
},
|
|
224
413
|
};
|
|
@@ -15,9 +15,13 @@ exports.noFilterWithoutReturn = (0, createRule_1.createRule)({
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
if (!ASTHelpers_1.ASTHelpers.hasReturnStatement(body)) {
|
|
18
|
+
const filterCall = context.getSourceCode().getText(node.callee);
|
|
18
19
|
context.report({
|
|
19
20
|
node,
|
|
20
21
|
messageId: 'unexpected',
|
|
22
|
+
data: {
|
|
23
|
+
filterCall,
|
|
24
|
+
},
|
|
21
25
|
});
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -33,7 +37,7 @@ exports.noFilterWithoutReturn = (0, createRule_1.createRule)({
|
|
|
33
37
|
},
|
|
34
38
|
schema: [],
|
|
35
39
|
messages: {
|
|
36
|
-
unexpected: '
|
|
40
|
+
unexpected: 'Callback for {{filterCall}} uses braces but never returns a value, so filter receives undefined for every element and silently drops them all. Return the predicate result from inside the block (e.g., "return matches(item);") or use a concise arrow like {{filterCall}}((item) => matches(item)) to make the keep/remove condition explicit.',
|
|
37
41
|
},
|
|
38
42
|
},
|
|
39
43
|
defaultOptions: [],
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const noFirestoreJestMock: TSESLint.RuleModule<"noFirestoreJestMock", [], TSESLint.RuleListener>;
|
|
@@ -1,8 +1,90 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.noFirestoreJestMock = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
4
8
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
9
|
const createRule_1 = require("../utils/createRule");
|
|
10
|
+
const FIRESTORE_JEST_MOCK = 'firestore-jest-mock';
|
|
11
|
+
const MOCK_FIRESTORE_TARGET = '__test-utils__/mockFirestore';
|
|
12
|
+
const toPosixPath = (filePath) => filePath.replace(/\\/g, '/');
|
|
13
|
+
const ensureRelativeSpecifier = (specifier) => specifier.startsWith('.') ? specifier : `./${specifier}`;
|
|
14
|
+
const isWindowsDrivePath = (filePath) => /^[A-Za-z]:[\\/]/.test(filePath);
|
|
15
|
+
const isValidRelativePath = (relativePath) => !path_1.default.isAbsolute(relativePath) && !isWindowsDrivePath(relativePath);
|
|
16
|
+
const buildReplacementPath = (sourceFilePath, cwd) => {
|
|
17
|
+
const absoluteFilename = path_1.default.isAbsolute(sourceFilePath)
|
|
18
|
+
? sourceFilePath
|
|
19
|
+
: path_1.default.join(cwd, sourceFilePath);
|
|
20
|
+
const targetPath = path_1.default.join(cwd, MOCK_FIRESTORE_TARGET);
|
|
21
|
+
const relativePath = path_1.default.relative(path_1.default.dirname(absoluteFilename), targetPath);
|
|
22
|
+
if (!isValidRelativePath(relativePath)) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
return ensureRelativeSpecifier(toPosixPath(relativePath));
|
|
26
|
+
};
|
|
27
|
+
const findVariableDeclarator = (node) => {
|
|
28
|
+
const { parent } = node;
|
|
29
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
30
|
+
return parent;
|
|
31
|
+
}
|
|
32
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.AwaitExpression &&
|
|
33
|
+
parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
34
|
+
return parent.parent;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
38
|
+
const buildDestructuringFix = (fixer, pattern) => {
|
|
39
|
+
if (pattern.properties.length !== 1) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const [property] = pattern.properties;
|
|
43
|
+
if (property.type !== utils_1.AST_NODE_TYPES.Property) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (property.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (property.value.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const localName = property.value.name;
|
|
53
|
+
const replacement = localName === 'mockFirestore'
|
|
54
|
+
? '{ mockFirestore }'
|
|
55
|
+
: `{ mockFirestore: ${localName} }`;
|
|
56
|
+
return fixer.replaceText(pattern, replacement);
|
|
57
|
+
};
|
|
58
|
+
const constructImportStatement = (localName, replacementPath) => {
|
|
59
|
+
const binding = localName === 'mockFirestore'
|
|
60
|
+
? 'mockFirestore'
|
|
61
|
+
: `mockFirestore as ${localName}`;
|
|
62
|
+
return `import { ${binding} } from '${replacementPath}';`;
|
|
63
|
+
};
|
|
64
|
+
const getSingleValueImportSpecifier = (node) => {
|
|
65
|
+
const valueSpecifiers = node.specifiers.filter((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
66
|
+
specifier.importKind !== 'type');
|
|
67
|
+
const hasTypeSpecifiers = node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
68
|
+
specifier.importKind === 'type');
|
|
69
|
+
const hasUnsupportedSpecifier = node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
70
|
+
specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
71
|
+
if (valueSpecifiers.length !== 1 ||
|
|
72
|
+
hasTypeSpecifiers ||
|
|
73
|
+
hasUnsupportedSpecifier) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return valueSpecifiers[0];
|
|
77
|
+
};
|
|
78
|
+
const buildImportDeclarationFix = (node, replacementPath) => {
|
|
79
|
+
if (!replacementPath) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const specifier = getSingleValueImportSpecifier(node);
|
|
83
|
+
if (!specifier) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return constructImportStatement(specifier.local.name, replacementPath);
|
|
87
|
+
};
|
|
6
88
|
exports.noFirestoreJestMock = (0, createRule_1.createRule)({
|
|
7
89
|
name: 'no-firestore-jest-mock',
|
|
8
90
|
meta: {
|
|
@@ -14,38 +96,72 @@ exports.noFirestoreJestMock = (0, createRule_1.createRule)({
|
|
|
14
96
|
fixable: 'code',
|
|
15
97
|
schema: [],
|
|
16
98
|
messages: {
|
|
17
|
-
noFirestoreJestMock: '
|
|
99
|
+
noFirestoreJestMock: 'What\'s wrong: This test imports "{{moduleName}}" directly → Why it matters: it bypasses the centralized mockFirestore helper that mirrors production schema and keeps seeding/cleanup consistent, which leads to inconsistent data and flaky tests → How to fix: import { mockFirestore } from "{{replacementPath}}" instead.',
|
|
18
100
|
},
|
|
19
101
|
},
|
|
20
102
|
defaultOptions: [],
|
|
21
103
|
create(context) {
|
|
22
|
-
const
|
|
104
|
+
const sourceFilePath = context.getFilename();
|
|
23
105
|
// Only apply rule to test files
|
|
24
|
-
if (!
|
|
106
|
+
if (!sourceFilePath.endsWith('.test.ts')) {
|
|
25
107
|
return {};
|
|
26
108
|
}
|
|
109
|
+
const cwd = typeof context.getCwd === 'function' ? context.getCwd() : process.cwd();
|
|
110
|
+
const replacementPath = buildReplacementPath(sourceFilePath, cwd);
|
|
111
|
+
const messageReplacementPath = replacementPath ||
|
|
112
|
+
ensureRelativeSpecifier(toPosixPath(MOCK_FIRESTORE_TARGET));
|
|
113
|
+
const reportData = {
|
|
114
|
+
moduleName: FIRESTORE_JEST_MOCK,
|
|
115
|
+
replacementPath: messageReplacementPath,
|
|
116
|
+
};
|
|
27
117
|
return {
|
|
28
118
|
ImportDeclaration(node) {
|
|
29
119
|
// Skip type imports completely
|
|
30
120
|
if (node.importKind === 'type') {
|
|
31
121
|
return;
|
|
32
122
|
}
|
|
33
|
-
if (node.source.value ===
|
|
123
|
+
if (node.source.value === FIRESTORE_JEST_MOCK) {
|
|
34
124
|
context.report({
|
|
35
125
|
node,
|
|
36
126
|
messageId: 'noFirestoreJestMock',
|
|
127
|
+
data: reportData,
|
|
37
128
|
fix: (fixer) => {
|
|
38
|
-
|
|
129
|
+
const replacementImport = buildImportDeclarationFix(node, replacementPath);
|
|
130
|
+
if (!replacementImport) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return fixer.replaceText(node, replacementImport);
|
|
39
134
|
},
|
|
40
135
|
});
|
|
41
136
|
}
|
|
42
137
|
},
|
|
43
138
|
ImportExpression(node) {
|
|
44
139
|
if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
45
|
-
node.source.value ===
|
|
140
|
+
node.source.value === FIRESTORE_JEST_MOCK) {
|
|
141
|
+
const variableDeclarator = findVariableDeclarator(node);
|
|
46
142
|
context.report({
|
|
47
143
|
node,
|
|
48
144
|
messageId: 'noFirestoreJestMock',
|
|
145
|
+
data: reportData,
|
|
146
|
+
fix: (fixer) => {
|
|
147
|
+
if (!replacementPath) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (!variableDeclarator) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
if (variableDeclarator.id.type !== utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const destructuringFix = buildDestructuringFix(fixer, variableDeclarator.id);
|
|
157
|
+
if (!destructuringFix) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return [
|
|
161
|
+
fixer.replaceText(node.source, `'${replacementPath}'`),
|
|
162
|
+
destructuringFix,
|
|
163
|
+
];
|
|
164
|
+
},
|
|
49
165
|
});
|
|
50
166
|
}
|
|
51
167
|
},
|
|
@@ -58,10 +174,11 @@ exports.noFirestoreJestMock = (0, createRule_1.createRule)({
|
|
|
58
174
|
node.callee.property.name === 'mock' &&
|
|
59
175
|
node.arguments.length > 0 &&
|
|
60
176
|
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
61
|
-
node.arguments[0].value ===
|
|
177
|
+
node.arguments[0].value === FIRESTORE_JEST_MOCK) {
|
|
62
178
|
context.report({
|
|
63
179
|
node,
|
|
64
180
|
messageId: 'noFirestoreJestMock',
|
|
181
|
+
data: reportData,
|
|
65
182
|
});
|
|
66
183
|
}
|
|
67
184
|
// Check for require('firestore-jest-mock')
|
|
@@ -69,10 +186,11 @@ exports.noFirestoreJestMock = (0, createRule_1.createRule)({
|
|
|
69
186
|
node.callee.name === 'require' &&
|
|
70
187
|
node.arguments.length > 0 &&
|
|
71
188
|
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
|
|
72
|
-
node.arguments[0].value ===
|
|
189
|
+
node.arguments[0].value === FIRESTORE_JEST_MOCK) {
|
|
73
190
|
context.report({
|
|
74
191
|
node,
|
|
75
192
|
messageId: 'noFirestoreJestMock',
|
|
193
|
+
data: reportData,
|
|
76
194
|
});
|
|
77
195
|
}
|
|
78
196
|
},
|