@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.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 +169 -139
- package/lib/index.js +200 -9
- package/lib/rules/array-methods-this-context.js +1 -1
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +43 -4
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +134 -44
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
- 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-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- 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-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- 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-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- 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 +4 -4
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +71 -21
- 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 +44 -18
- 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 +3818 -4644
- 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 +66 -23
- 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 +801 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +415 -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 +215 -12
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +31 -32
- 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 +527 -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 +52 -7
- package/lib/rules/no-excessive-parent-chain.js +4 -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 +465 -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.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +120 -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 +508 -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 +344 -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 +143 -59
- 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 +118 -8
- 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 +221 -172
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
- 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-usememo-over-useeffect-usestate.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 +540 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +614 -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 +155 -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 +18 -10
- 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 +47 -2
- package/lib/utils/ASTHelpers.js +370 -112
- 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
|
@@ -14,7 +14,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
14
14
|
fixable: 'code',
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
requireDefaultParams: '
|
|
17
|
+
requireDefaultParams: 'Hook "{{hookName}}" accepts an options object where every property is optional, but the parameter is not defaulted. When callers omit the argument the hook receives undefined, so destructuring or property access throws even though the fields are optional. Default the parameter to an empty object (e.g., "({ option } = {})") so the hook stays safe to call with no arguments.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|
|
@@ -81,8 +81,10 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
81
81
|
'ArrowFunctionExpression, FunctionDeclaration'(node) {
|
|
82
82
|
// Check if it's a hook function
|
|
83
83
|
let isHook = false;
|
|
84
|
+
let hookName;
|
|
84
85
|
if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
85
|
-
|
|
86
|
+
hookName = node.id?.name;
|
|
87
|
+
isHook = hookName ? isHookName(hookName) : false;
|
|
86
88
|
}
|
|
87
89
|
else {
|
|
88
90
|
const parent = node.parent;
|
|
@@ -90,12 +92,14 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
90
92
|
parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
91
93
|
parent.id &&
|
|
92
94
|
parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
95
|
+
hookName = parent.id.name;
|
|
93
96
|
isHook = isHookName(parent.id.name);
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
if (!isHook) {
|
|
97
100
|
return;
|
|
98
101
|
}
|
|
102
|
+
const messageData = { hookName: hookName ?? 'this hook' };
|
|
99
103
|
// Check if it has exactly one parameter
|
|
100
104
|
if (node.params.length !== 1) {
|
|
101
105
|
return;
|
|
@@ -121,10 +125,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
121
125
|
context.report({
|
|
122
126
|
node: param,
|
|
123
127
|
messageId: 'requireDefaultParams',
|
|
128
|
+
data: messageData,
|
|
124
129
|
fix(fixer) {
|
|
125
|
-
const paramText = context
|
|
126
|
-
.getSourceCode()
|
|
127
|
-
.getText(param);
|
|
130
|
+
const paramText = context.sourceCode.getText(param);
|
|
128
131
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
129
132
|
},
|
|
130
133
|
});
|
|
@@ -140,10 +143,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
140
143
|
context.report({
|
|
141
144
|
node: param,
|
|
142
145
|
messageId: 'requireDefaultParams',
|
|
146
|
+
data: messageData,
|
|
143
147
|
fix(fixer) {
|
|
144
|
-
const paramText = context
|
|
145
|
-
.getSourceCode()
|
|
146
|
-
.getText(param);
|
|
148
|
+
const paramText = context.sourceCode.getText(param);
|
|
147
149
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
148
150
|
},
|
|
149
151
|
});
|
|
@@ -152,7 +154,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
152
154
|
}
|
|
153
155
|
else {
|
|
154
156
|
// If we can't find the type definition, check if it's defined in the same file
|
|
155
|
-
const program = context.
|
|
157
|
+
const program = context.sourceCode.ast;
|
|
156
158
|
const typeDefinitions = program.body.filter((node) => {
|
|
157
159
|
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
|
|
158
160
|
node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
@@ -172,10 +174,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
172
174
|
context.report({
|
|
173
175
|
node: param,
|
|
174
176
|
messageId: 'requireDefaultParams',
|
|
177
|
+
data: messageData,
|
|
175
178
|
fix(fixer) {
|
|
176
|
-
const paramText = context
|
|
177
|
-
.getSourceCode()
|
|
178
|
-
.getText(param);
|
|
179
|
+
const paramText = context.sourceCode.getText(param);
|
|
179
180
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
180
181
|
},
|
|
181
182
|
});
|
|
@@ -191,10 +192,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
191
192
|
context.report({
|
|
192
193
|
node: param,
|
|
193
194
|
messageId: 'requireDefaultParams',
|
|
195
|
+
data: messageData,
|
|
194
196
|
fix(fixer) {
|
|
195
|
-
const paramText = context
|
|
196
|
-
.getSourceCode()
|
|
197
|
-
.getText(param);
|
|
197
|
+
const paramText = context.sourceCode.getText(param);
|
|
198
198
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
199
199
|
},
|
|
200
200
|
});
|
|
@@ -214,8 +214,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
|
|
|
214
214
|
context.report({
|
|
215
215
|
node: param,
|
|
216
216
|
messageId: 'requireDefaultParams',
|
|
217
|
+
data: messageData,
|
|
217
218
|
fix(fixer) {
|
|
218
|
-
const paramText = context.
|
|
219
|
+
const paramText = context.sourceCode.getText(param);
|
|
219
220
|
return fixer.replaceText(param, `${paramText} = {}`);
|
|
220
221
|
},
|
|
221
222
|
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireHttpsErrorCause = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
6
|
+
const createRule_1 = require("../utils/createRule");
|
|
7
|
+
const isHttpsErrorCallee = (callee) => {
|
|
8
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
9
|
+
return callee.name === 'HttpsError';
|
|
10
|
+
}
|
|
11
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
12
|
+
return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
13
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
14
|
+
callee.property.name === 'HttpsError');
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
};
|
|
18
|
+
const findVariableInScopeChain = (scope, name) => {
|
|
19
|
+
let currentScope = scope;
|
|
20
|
+
while (currentScope) {
|
|
21
|
+
const variable = currentScope.variables.find((item) => item.name === name);
|
|
22
|
+
if (variable) {
|
|
23
|
+
return variable;
|
|
24
|
+
}
|
|
25
|
+
currentScope = currentScope.upper;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
exports.requireHttpsErrorCause = (0, createRule_1.createRule)({
|
|
30
|
+
name: 'require-https-error-cause',
|
|
31
|
+
meta: {
|
|
32
|
+
type: 'problem',
|
|
33
|
+
docs: {
|
|
34
|
+
description: 'Ensure HttpsError calls inside catch blocks pass the caught error as the fourth "cause" argument to preserve stack traces for monitoring.',
|
|
35
|
+
recommended: 'error',
|
|
36
|
+
},
|
|
37
|
+
schema: [],
|
|
38
|
+
messages: {
|
|
39
|
+
missingCause: 'HttpsError inside a catch block must pass the caught error as the fourth "cause" argument. Without the original error, the stack is lost and monitoring fingerprints degrade. Add {{catchName}} as the cause argument after the optional details parameter.',
|
|
40
|
+
causeNotCatchBinding: 'The fourth HttpsError argument should be the catch binding {{catchName}} so the original error stack is preserved for monitoring. Pass the catch variable instead of {{actual}}.',
|
|
41
|
+
missingCatchBinding: 'HttpsError inside a catch block needs a named catch binding so the original error can be forwarded as the fourth "cause" argument. Bind the error value (e.g., `catch (error)`) and pass that variable as the cause to keep the upstream stack trace.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultOptions: [],
|
|
45
|
+
create(context) {
|
|
46
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
47
|
+
const catchStack = [];
|
|
48
|
+
const reportMissingCause = (node, catchName) => {
|
|
49
|
+
context.report({
|
|
50
|
+
node,
|
|
51
|
+
messageId: 'missingCause',
|
|
52
|
+
data: {
|
|
53
|
+
catchName,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const reportWrongCause = (node, catchName, actual) => {
|
|
58
|
+
context.report({
|
|
59
|
+
node,
|
|
60
|
+
messageId: 'causeNotCatchBinding',
|
|
61
|
+
data: {
|
|
62
|
+
catchName,
|
|
63
|
+
actual,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const isCatchBindingReference = (identifier, frame) => {
|
|
68
|
+
if (!frame.paramName || identifier.name !== frame.paramName) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const variable = findVariableInScopeChain(ASTHelpers_1.ASTHelpers.getScope(context, identifier), identifier.name);
|
|
72
|
+
return (variable?.defs.some((definition) => definition.type === 'CatchClause' && definition.node === frame.node) ?? false);
|
|
73
|
+
};
|
|
74
|
+
const validateHttpsError = (node) => {
|
|
75
|
+
if (!catchStack.length) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!isHttpsErrorCallee(node.callee)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const activeCatch = catchStack[catchStack.length - 1];
|
|
82
|
+
if (!activeCatch.paramName) {
|
|
83
|
+
context.report({
|
|
84
|
+
node,
|
|
85
|
+
messageId: 'missingCatchBinding',
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const catchName = activeCatch.paramName;
|
|
90
|
+
// HttpsError accepts both positional and object-based constructors. This signature
|
|
91
|
+
// is supported for compatibility with HttpsError overloads, ensuring the rule
|
|
92
|
+
// can validate the 'cause' property within a settings object to preserve
|
|
93
|
+
// error chaining for better diagnostics.
|
|
94
|
+
if (node.arguments.length === 1 &&
|
|
95
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
96
|
+
const settingsObj = node.arguments[0];
|
|
97
|
+
const causeProp = settingsObj.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
98
|
+
!prop.computed &&
|
|
99
|
+
((prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
100
|
+
prop.key.name === 'cause') ||
|
|
101
|
+
(prop.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
102
|
+
prop.key.value === 'cause')));
|
|
103
|
+
if (!causeProp) {
|
|
104
|
+
reportMissingCause(node, catchName);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const causeValue = causeProp.value;
|
|
108
|
+
if (causeValue.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
109
|
+
reportWrongCause(causeValue, catchName, sourceCode.getText(causeValue));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!isCatchBindingReference(causeValue, activeCatch)) {
|
|
113
|
+
reportWrongCause(causeValue, catchName, sourceCode.getText(causeValue));
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (node.arguments.length < 4) {
|
|
118
|
+
reportMissingCause(node, catchName);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const causeArg = node.arguments[3];
|
|
122
|
+
if (!causeArg || causeArg.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
123
|
+
reportWrongCause(causeArg ?? node, catchName, causeArg
|
|
124
|
+
? sourceCode.getText(causeArg)
|
|
125
|
+
: 'the missing cause argument');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!isCatchBindingReference(causeArg, activeCatch)) {
|
|
129
|
+
reportWrongCause(causeArg, catchName, sourceCode.getText(causeArg));
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
CatchClause(node) {
|
|
134
|
+
if (node.param?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
135
|
+
catchStack.push({ paramName: node.param.name, node });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Destructured catch params (e.g., `catch ({ message })`) do not bind the
|
|
139
|
+
// full error object, so they cannot be forwarded as the HttpsError cause.
|
|
140
|
+
// Users should bind the error value (e.g., `catch (error)`) before rethrowing.
|
|
141
|
+
catchStack.push({ paramName: null, node });
|
|
142
|
+
},
|
|
143
|
+
'CatchClause:exit'() {
|
|
144
|
+
catchStack.pop();
|
|
145
|
+
},
|
|
146
|
+
NewExpression(node) {
|
|
147
|
+
validateHttpsError(node);
|
|
148
|
+
},
|
|
149
|
+
CallExpression(node) {
|
|
150
|
+
validateHttpsError(node);
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=require-https-error-cause.js.map
|
|
@@ -11,8 +11,8 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
11
11
|
},
|
|
12
12
|
schema: [],
|
|
13
13
|
messages: {
|
|
14
|
-
useHttpsError: '
|
|
15
|
-
useProprietaryHttpsError: '
|
|
14
|
+
useHttpsError: 'Throwing "{{constructorName}}" in Cloud Functions returns a generic 500 and drops the structured status code clients rely on. Throw the proprietary HttpsError instead so responses include the correct status, sanitized message, and logging context.',
|
|
15
|
+
useProprietaryHttpsError: '{{reference}} comes from {{source}} and bypasses our proprietary HttpsError wrapper, so responses skip standardized status codes, logging, and client-safe payloads. Import and throw HttpsError from @our-company/errors to keep errors consistent.',
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
18
|
defaultOptions: [],
|
|
@@ -22,12 +22,20 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
22
22
|
if (!filename.includes('functions/src')) {
|
|
23
23
|
return {};
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const httpsIdentifiers = new Map();
|
|
26
|
+
const httpsErrorIdentifiers = new Map();
|
|
27
|
+
const reportProprietary = (node, data) => context.report({
|
|
28
|
+
node,
|
|
29
|
+
messageId: 'useProprietaryHttpsError',
|
|
30
|
+
data,
|
|
31
|
+
});
|
|
27
32
|
return {
|
|
28
33
|
ImportDeclaration(node) {
|
|
29
34
|
if (node.source.value === 'firebase-admin' ||
|
|
30
35
|
node.source.value === 'firebase-admin/lib/https-error') {
|
|
36
|
+
const sourceModule = String(node.source.value);
|
|
37
|
+
// Report imports immediately so the forbidden dependency is blocked even
|
|
38
|
+
// when unused; throw sites below also report to cover runtime usage.
|
|
31
39
|
// Check for direct HttpsError import
|
|
32
40
|
const httpsErrorSpecifier = node.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
33
41
|
spec.imported.name === 'HttpsError');
|
|
@@ -35,17 +43,19 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
35
43
|
const httpsSpecifier = node.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
36
44
|
spec.imported.name === 'https');
|
|
37
45
|
if (httpsErrorSpecifier && 'local' in httpsErrorSpecifier) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
const localName = httpsErrorSpecifier.local.name;
|
|
47
|
+
httpsErrorIdentifiers.set(localName, sourceModule);
|
|
48
|
+
reportProprietary(node, {
|
|
49
|
+
reference: localName,
|
|
50
|
+
source: sourceModule,
|
|
42
51
|
});
|
|
43
52
|
}
|
|
44
53
|
if (httpsSpecifier && 'local' in httpsSpecifier) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
const localName = httpsSpecifier.local.name;
|
|
55
|
+
httpsIdentifiers.set(localName, sourceModule);
|
|
56
|
+
reportProprietary(node, {
|
|
57
|
+
reference: `${localName}.HttpsError`,
|
|
58
|
+
source: sourceModule,
|
|
49
59
|
});
|
|
50
60
|
}
|
|
51
61
|
}
|
|
@@ -65,22 +75,34 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
65
75
|
context.report({
|
|
66
76
|
node,
|
|
67
77
|
messageId: 'useHttpsError',
|
|
78
|
+
data: {
|
|
79
|
+
constructorName: callee.name,
|
|
80
|
+
},
|
|
68
81
|
});
|
|
69
82
|
return;
|
|
70
83
|
}
|
|
71
84
|
// Check for firebase-admin HttpsError usage
|
|
72
|
-
|
|
85
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
73
86
|
callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
74
|
-
callee.object.name === httpsIdentifier &&
|
|
75
87
|
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
76
|
-
callee.property.name === 'HttpsError'
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
callee.property.name === 'HttpsError') {
|
|
89
|
+
const objectName = callee.object.name;
|
|
90
|
+
const source = httpsIdentifiers.get(objectName);
|
|
91
|
+
if (!source) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
reportProprietary(node, {
|
|
95
|
+
reference: `${objectName}.HttpsError`,
|
|
96
|
+
source,
|
|
83
97
|
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
101
|
+
const source = httpsErrorIdentifiers.get(callee.name);
|
|
102
|
+
if (!source) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
reportProprietary(node, { reference: callee.name, source });
|
|
84
106
|
}
|
|
85
107
|
},
|
|
86
108
|
};
|
|
@@ -32,7 +32,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
32
32
|
const options = context.options[0] || {
|
|
33
33
|
componentPath: 'src/components/image/ImageOptimized',
|
|
34
34
|
};
|
|
35
|
-
const sourceCode = context.
|
|
35
|
+
const sourceCode = context.sourceCode;
|
|
36
36
|
return {
|
|
37
37
|
// Handle JSX img elements
|
|
38
38
|
JSXElement(node) {
|
|
@@ -8,21 +8,25 @@ const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
|
8
8
|
const isComponentExplicitlyUnmemoized = (componentName) => componentName.toLowerCase().includes('unmemoized');
|
|
9
9
|
function isFunction(node) {
|
|
10
10
|
return (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
11
|
-
node.type === utils_1.AST_NODE_TYPES.FunctionExpression
|
|
11
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
12
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration);
|
|
12
13
|
}
|
|
13
|
-
function isHigherOrderFunctionReturningJSX(node) {
|
|
14
|
+
function isHigherOrderFunctionReturningJSX(node, context) {
|
|
14
15
|
if (isFunction(node)) {
|
|
15
|
-
// Check if function takes another function as an argument
|
|
16
|
-
const hasFunctionParam = 'params' in node && node.params.some(isFunction);
|
|
17
16
|
if (node.body && node.body.type === 'BlockStatement') {
|
|
18
17
|
for (const statement of node.body.body) {
|
|
19
18
|
if (statement.type === 'ReturnStatement' && statement.argument) {
|
|
20
|
-
const returnsJSX = ASTHelpers_1.ASTHelpers.returnsJSX(statement.argument);
|
|
19
|
+
const returnsJSX = ASTHelpers_1.ASTHelpers.returnsJSX(statement.argument, context);
|
|
21
20
|
const returnsFunction = isFunction(statement.argument);
|
|
22
|
-
return
|
|
21
|
+
return returnsFunction && returnsJSX;
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
}
|
|
25
|
+
else if (node.body && isFunction(node.body)) {
|
|
26
|
+
// Shorthand arrow HOC: (Comp) => (props) => <Comp {...props} />
|
|
27
|
+
// Here node.body is the inner function; check if it returns JSX.
|
|
28
|
+
return ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
|
|
29
|
+
}
|
|
26
30
|
}
|
|
27
31
|
return false;
|
|
28
32
|
}
|
|
@@ -52,14 +56,15 @@ function checkFunction(context, node) {
|
|
|
52
56
|
if (!fileName.endsWith('.tsx')) {
|
|
53
57
|
return;
|
|
54
58
|
}
|
|
55
|
-
if (isHigherOrderFunctionReturningJSX(node)) {
|
|
59
|
+
if (isHigherOrderFunctionReturningJSX(node, context)) {
|
|
56
60
|
return;
|
|
57
61
|
}
|
|
58
62
|
const parentNode = node.parent;
|
|
59
63
|
if (node.parent.type === 'CallExpression') {
|
|
60
64
|
return;
|
|
61
65
|
}
|
|
62
|
-
if (ASTHelpers_1.ASTHelpers.returnsJSX(node.body) &&
|
|
66
|
+
if (ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context) &&
|
|
67
|
+
ASTHelpers_1.ASTHelpers.hasParameters(node)) {
|
|
63
68
|
const results = [
|
|
64
69
|
isUnmemoizedArrowFunction,
|
|
65
70
|
isUnmemoizedFunctionComponent,
|
|
@@ -79,7 +84,10 @@ function checkFunction(context, node) {
|
|
|
79
84
|
},
|
|
80
85
|
fix: results[2] || results[1]
|
|
81
86
|
? function fix(fixer) {
|
|
82
|
-
|
|
87
|
+
if (node.async || node.generator) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const sourceCode = context.sourceCode;
|
|
83
91
|
let importFix = null;
|
|
84
92
|
// Search for memo import statement
|
|
85
93
|
const importDeclarations = sourceCode.ast.body.filter((node) => node.type === 'ImportDeclaration');
|
|
@@ -118,7 +126,7 @@ function checkFunction(context, node) {
|
|
|
118
126
|
const functionNameReplacement = `function ${node.id.name}Unmemoized`;
|
|
119
127
|
const fixes = [
|
|
120
128
|
fixer.replaceTextRange(functionKeywordRange, functionKeywordReplacement),
|
|
121
|
-
fixer.insertTextAfterRange([node.range[1], node.range[1]], ')'),
|
|
129
|
+
fixer.insertTextAfterRange([node.range[1], node.range[1]], ');'),
|
|
122
130
|
fixer.replaceTextRange([node.id.range[0] - 1, node.id.range[1]], functionNameReplacement),
|
|
123
131
|
];
|
|
124
132
|
if (importFix) {
|