@blumintinc/eslint-plugin-blumint 1.12.6 → 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 +171 -99
- package/lib/index.js +300 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- 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.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -11
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +52 -96
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +820 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +5 -2
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +278 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- 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.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- 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 +4 -4
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
- package/lib/rules/enforce-firestore-facade.js +349 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +92 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +147 -0
- package/lib/rules/enforce-microdiff.js +3 -2
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +9 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +10 -6
- 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.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
- 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 +90 -14
- 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 +92 -53
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- 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/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -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.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +314 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- 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 +22 -5
- package/lib/rules/no-compositing-layer-props.js +6 -4
- 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.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +320 -68
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-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 +398 -28
- 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 +14 -14
- 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.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +22 -9
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
- 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.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +206 -0
- 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.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
- 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.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +173 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +133 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +49 -49
- package/lib/rules/no-undefined-null-passthrough.js +217 -40
- 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 -3
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +5 -2
- package/lib/rules/no-unused-props.js +451 -112
- 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.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +236 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +394 -0
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +228 -35
- package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
- 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-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +251 -0
- package/lib/rules/prefer-fragment-component.js +4 -4
- 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.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +305 -31
- 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.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +357 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +277 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- 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.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-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 +2 -2
- package/lib/rules/require-memo.js +12 -2
- 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 +9 -4
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +271 -0
- 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 +34 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +23 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -3,6 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.reactUseMemoShouldBeComponent = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const ARRAY_RETURNING_METHODS = new Set([
|
|
7
|
+
'map',
|
|
8
|
+
'flatMap',
|
|
9
|
+
'filter',
|
|
10
|
+
'concat',
|
|
11
|
+
'slice',
|
|
12
|
+
]);
|
|
6
13
|
/**
|
|
7
14
|
* Checks if a node is a JSX element or fragment
|
|
8
15
|
*/
|
|
@@ -24,6 +31,99 @@ const isJsxElement = (node) => {
|
|
|
24
31
|
return (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
25
32
|
node.type === utils_1.AST_NODE_TYPES.JSXFragment);
|
|
26
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* True if the resolved variable is referenced inside a JSX prop (attribute or spread).
|
|
36
|
+
*/
|
|
37
|
+
const isUsedAsComponentProp = (context, variableName, node) => {
|
|
38
|
+
// Find containing function for proper scope acquisition
|
|
39
|
+
let current = node;
|
|
40
|
+
let fn;
|
|
41
|
+
while (current?.parent) {
|
|
42
|
+
current = current.parent;
|
|
43
|
+
if (current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
44
|
+
current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
45
|
+
current.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
46
|
+
fn = current;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const sourceCode = context.sourceCode;
|
|
51
|
+
const scopeManager = sourceCode.scopeManager;
|
|
52
|
+
const scope = scopeManager &&
|
|
53
|
+
((fn && scopeManager.acquire(fn)) || scopeManager.acquire(node));
|
|
54
|
+
if (!scopeManager || !scope) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const variable = scope.variables.find((v) => v.name === variableName) ??
|
|
58
|
+
scope.upper?.variables.find((v) => v.name === variableName) ??
|
|
59
|
+
null;
|
|
60
|
+
if (!variable) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
for (const ref of variable.references) {
|
|
64
|
+
let p = ref.identifier.parent ?? null;
|
|
65
|
+
while (p) {
|
|
66
|
+
if (p.type === utils_1.AST_NODE_TYPES.JSXAttribute ||
|
|
67
|
+
p.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
p = p.parent;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
75
|
+
const isUsedAsJsxChild = (context, variableName, node) => {
|
|
76
|
+
// Traverse the nearest function (or program) to see if the identifier appears
|
|
77
|
+
// as a JSX child expression.
|
|
78
|
+
const sourceCode = context.sourceCode;
|
|
79
|
+
let container = sourceCode.ast;
|
|
80
|
+
let current = node;
|
|
81
|
+
while (current?.parent) {
|
|
82
|
+
current = current.parent;
|
|
83
|
+
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
84
|
+
current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
85
|
+
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
86
|
+
container = current;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let found = false;
|
|
91
|
+
const visited = new Set();
|
|
92
|
+
const visit = (n) => {
|
|
93
|
+
if (found || visited.has(n))
|
|
94
|
+
return;
|
|
95
|
+
visited.add(n);
|
|
96
|
+
if (n.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
|
|
97
|
+
n.expression.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
98
|
+
n.expression.name === variableName &&
|
|
99
|
+
n.parent &&
|
|
100
|
+
(n.parent.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
101
|
+
n.parent.type === utils_1.AST_NODE_TYPES.JSXFragment) &&
|
|
102
|
+
n.parent.children.includes(n)) {
|
|
103
|
+
found = true;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const key of Object.keys(n)) {
|
|
107
|
+
if (key === 'parent')
|
|
108
|
+
continue;
|
|
109
|
+
const value = n[key];
|
|
110
|
+
if (!value)
|
|
111
|
+
continue;
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
for (const child of value) {
|
|
114
|
+
if (child && typeof child.type === 'string') {
|
|
115
|
+
visit(child);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (value && typeof value.type === 'string') {
|
|
120
|
+
visit(value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
visit(container);
|
|
125
|
+
return found;
|
|
126
|
+
};
|
|
27
127
|
/**
|
|
28
128
|
* Checks if a variable is used multiple times in a component
|
|
29
129
|
*/
|
|
@@ -149,28 +249,13 @@ const containsJsxInFunction = (node) => {
|
|
|
149
249
|
if (body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
150
250
|
return containsJsxInBlockStatement(body);
|
|
151
251
|
}
|
|
152
|
-
// Check for array methods returning JSX
|
|
252
|
+
// Check for array methods returning arrays (do not flag arrays of JSX)
|
|
153
253
|
if (body.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
154
254
|
if (body.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
155
255
|
body.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
'filter',
|
|
160
|
-
'find',
|
|
161
|
-
'findIndex',
|
|
162
|
-
'some',
|
|
163
|
-
'every',
|
|
164
|
-
'reduce',
|
|
165
|
-
];
|
|
166
|
-
if (arrayMethods.includes(body.callee.property.name) &&
|
|
167
|
-
body.arguments.length > 0) {
|
|
168
|
-
const callback = body.arguments[0];
|
|
169
|
-
if ((callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
170
|
-
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
171
|
-
containsJsxInFunction(callback)) {
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
256
|
+
if (ARRAY_RETURNING_METHODS.has(body.callee.property.name)) {
|
|
257
|
+
// Returning an array (even if its elements are JSX) should not be flagged
|
|
258
|
+
return false;
|
|
174
259
|
}
|
|
175
260
|
}
|
|
176
261
|
// Check for IIFE
|
|
@@ -239,70 +324,27 @@ const containsJsxInExpression = (node) => {
|
|
|
239
324
|
}
|
|
240
325
|
return containsJsxInObject(node);
|
|
241
326
|
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
242
|
-
//
|
|
327
|
+
// Do not flag arrays of JSX: common array-producing patterns
|
|
243
328
|
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
244
|
-
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier
|
|
245
|
-
node.callee.property.name
|
|
246
|
-
|
|
247
|
-
const callback = node.arguments[0];
|
|
248
|
-
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
249
|
-
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
250
|
-
// Check if the callback returns an object with both JSX and non-JSX properties
|
|
251
|
-
if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
252
|
-
callback.body.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
253
|
-
let hasNonJsxProperties = false;
|
|
254
|
-
let hasJsxProperties = false;
|
|
255
|
-
for (const property of callback.body.properties) {
|
|
256
|
-
if (property.type === utils_1.AST_NODE_TYPES.Property && property.value) {
|
|
257
|
-
if (isJsxElement(property.value)) {
|
|
258
|
-
hasJsxProperties = true;
|
|
259
|
-
}
|
|
260
|
-
else if (property.value.type !== utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
261
|
-
hasNonJsxProperties = true;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// If the object has both JSX and non-JSX properties, it's likely a data object
|
|
266
|
-
if (hasNonJsxProperties && hasJsxProperties) {
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
// For other cases, check if the function returns JSX directly
|
|
271
|
-
return containsJsxInFunction(callback);
|
|
329
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
330
|
+
if (ARRAY_RETURNING_METHODS.has(node.callee.property.name)) {
|
|
331
|
+
return false;
|
|
272
332
|
}
|
|
273
333
|
}
|
|
334
|
+
// Array.from(...) returns an array as well
|
|
335
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
336
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
337
|
+
node.callee.object.name === 'Array' &&
|
|
338
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
339
|
+
node.callee.property.name === 'from') {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
274
342
|
// Check if it's an IIFE
|
|
275
343
|
if (node.callee.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
276
344
|
node.callee.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
277
345
|
return containsJsxInFunction(node.callee);
|
|
278
346
|
}
|
|
279
|
-
//
|
|
280
|
-
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
281
|
-
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
282
|
-
const arrayMethods = [
|
|
283
|
-
'filter',
|
|
284
|
-
'find',
|
|
285
|
-
'findIndex',
|
|
286
|
-
'some',
|
|
287
|
-
'every',
|
|
288
|
-
'reduce',
|
|
289
|
-
];
|
|
290
|
-
if (arrayMethods.includes(node.callee.property.name) &&
|
|
291
|
-
node.arguments.length > 0) {
|
|
292
|
-
const callback = node.arguments[0];
|
|
293
|
-
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
294
|
-
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
295
|
-
return containsJsxInFunction(callback);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// Check arguments for JSX
|
|
300
|
-
for (const arg of node.arguments) {
|
|
301
|
-
if (arg.type !== utils_1.AST_NODE_TYPES.SpreadElement &&
|
|
302
|
-
containsJsxInExpression(arg)) {
|
|
303
|
-
return true;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
347
|
+
// Do not flag based on JSX in arguments of non-IIFE calls
|
|
306
348
|
return false;
|
|
307
349
|
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
|
|
308
350
|
case utils_1.AST_NODE_TYPES.FunctionExpression:
|
|
@@ -371,8 +413,15 @@ const containsJsxInBlockStatement = (node) => {
|
|
|
371
413
|
// Check variable declarations for JSX
|
|
372
414
|
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
373
415
|
for (const declarator of statement.declarations) {
|
|
374
|
-
if (declarator.init
|
|
375
|
-
return
|
|
416
|
+
if (declarator.init) {
|
|
417
|
+
// Ignore nested function expressions (helpers/callbacks) even if they return JSX
|
|
418
|
+
if (declarator.init.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
419
|
+
declarator.init.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (containsJsxInExpression(declarator.init)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
376
425
|
}
|
|
377
426
|
}
|
|
378
427
|
}
|
|
@@ -441,7 +490,7 @@ exports.reactUseMemoShouldBeComponent = (0, createRule_1.createRule)({
|
|
|
441
490
|
},
|
|
442
491
|
schema: [],
|
|
443
492
|
messages: {
|
|
444
|
-
useMemoShouldBeComponent: 'useMemo
|
|
493
|
+
useMemoShouldBeComponent: 'useMemo result "{{memoName}}" returns JSX, which hides a component inside a memoized value. JSX needs component identity so React can manage props, state, and dev tooling; wrapping it in useMemo bypasses that boundary and makes reuse/debugging harder. Extract this JSX into its own component and memoize it with React.memo if stability is required.',
|
|
445
494
|
},
|
|
446
495
|
},
|
|
447
496
|
defaultOptions: [],
|
|
@@ -454,15 +503,32 @@ exports.reactUseMemoShouldBeComponent = (0, createRule_1.createRule)({
|
|
|
454
503
|
node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
455
504
|
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
456
505
|
const variableName = node.parent.id.name;
|
|
506
|
+
// Only report when the memoized value is used as a JSX child
|
|
507
|
+
if (!isUsedAsJsxChild(context, variableName, node)) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
457
510
|
// Check if the variable is used multiple times in the component
|
|
458
511
|
if (isUsedMultipleTimes(variableName, node)) {
|
|
459
512
|
// If the variable is used multiple times, allow it
|
|
460
513
|
return;
|
|
461
514
|
}
|
|
515
|
+
// Check if the variable is used as a prop value in a JSX element
|
|
516
|
+
if (isUsedAsComponentProp(context, variableName, node)) {
|
|
517
|
+
// If the variable is used as a prop value, allow it
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
462
520
|
}
|
|
521
|
+
const memoName = node.parent &&
|
|
522
|
+
node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
523
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier
|
|
524
|
+
? node.parent.id.name
|
|
525
|
+
: 'useMemo return value';
|
|
463
526
|
context.report({
|
|
464
527
|
node,
|
|
465
528
|
messageId: 'useMemoShouldBeComponent',
|
|
529
|
+
data: {
|
|
530
|
+
memoName,
|
|
531
|
+
},
|
|
466
532
|
});
|
|
467
533
|
}
|
|
468
534
|
},
|
|
@@ -14,7 +14,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
14
14
|
fixable: 'code',
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
requireDynamicImport: 'Firebase
|
|
17
|
+
requireDynamicImport: 'Static Firebase import from "{{importSource}}" keeps the Firebase SDK in the initial bundle and blocks route-level code splitting. Use an `await import(\'{{importSource}}\')` dynamic import so Firebase loads only on the code path that needs it; type-only imports remain allowed.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|
|
@@ -72,6 +72,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
72
72
|
context.report({
|
|
73
73
|
node,
|
|
74
74
|
messageId: 'requireDynamicImport',
|
|
75
|
+
data: { importSource },
|
|
75
76
|
fix(fixer) {
|
|
76
77
|
const dynamicImport = createDynamicImport(node);
|
|
77
78
|
return fixer.replaceText(node, dynamicImport);
|
|
@@ -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,136 @@
|
|
|
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 createRule_1 = require("../utils/createRule");
|
|
6
|
+
const isHttpsErrorCallee = (callee) => {
|
|
7
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
8
|
+
return callee.name === 'HttpsError';
|
|
9
|
+
}
|
|
10
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
11
|
+
return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
12
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
13
|
+
callee.property.name === 'HttpsError');
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
const findVariableInScopeChain = (scope, name) => {
|
|
18
|
+
let currentScope = scope;
|
|
19
|
+
while (currentScope) {
|
|
20
|
+
const variable = currentScope.variables.find((item) => item.name === name);
|
|
21
|
+
if (variable) {
|
|
22
|
+
return variable;
|
|
23
|
+
}
|
|
24
|
+
currentScope = currentScope.upper;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
exports.requireHttpsErrorCause = (0, createRule_1.createRule)({
|
|
29
|
+
name: 'require-https-error-cause',
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'problem',
|
|
32
|
+
docs: {
|
|
33
|
+
description: 'Ensure HttpsError calls inside catch blocks pass the caught error as the fourth "cause" argument to preserve stack traces for monitoring.',
|
|
34
|
+
recommended: 'error',
|
|
35
|
+
},
|
|
36
|
+
schema: [],
|
|
37
|
+
messages: {
|
|
38
|
+
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.',
|
|
39
|
+
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}}.',
|
|
40
|
+
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.',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
defaultOptions: [],
|
|
44
|
+
create(context) {
|
|
45
|
+
const sourceCode = context.getSourceCode();
|
|
46
|
+
const catchStack = [];
|
|
47
|
+
// ESLint 9 moves getScope onto sourceCode; ESLint 8 exposes context.getScope().
|
|
48
|
+
// This shim keeps the rule compatible until the codebase drops ESLint 8 support.
|
|
49
|
+
const getScopeForNode = (node) => {
|
|
50
|
+
const sourceCodeWithScope = sourceCode;
|
|
51
|
+
if (typeof sourceCodeWithScope.getScope === 'function') {
|
|
52
|
+
return sourceCodeWithScope.getScope(node);
|
|
53
|
+
}
|
|
54
|
+
return context.getScope();
|
|
55
|
+
};
|
|
56
|
+
const reportMissingCause = (node, catchName) => {
|
|
57
|
+
context.report({
|
|
58
|
+
node,
|
|
59
|
+
messageId: 'missingCause',
|
|
60
|
+
data: {
|
|
61
|
+
catchName,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const reportWrongCause = (node, catchName, actual) => {
|
|
66
|
+
context.report({
|
|
67
|
+
node,
|
|
68
|
+
messageId: 'causeNotCatchBinding',
|
|
69
|
+
data: {
|
|
70
|
+
catchName,
|
|
71
|
+
actual,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const isCatchBindingReference = (identifier, frame) => {
|
|
76
|
+
if (!frame.paramName || identifier.name !== frame.paramName) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const variable = findVariableInScopeChain(getScopeForNode(identifier), identifier.name);
|
|
80
|
+
return (variable?.defs.some((definition) => definition.type === 'CatchClause' && definition.node === frame.node) ?? false);
|
|
81
|
+
};
|
|
82
|
+
const validateHttpsError = (node) => {
|
|
83
|
+
if (!catchStack.length) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!isHttpsErrorCallee(node.callee)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const activeCatch = catchStack[catchStack.length - 1];
|
|
90
|
+
if (!activeCatch.paramName) {
|
|
91
|
+
context.report({
|
|
92
|
+
node,
|
|
93
|
+
messageId: 'missingCatchBinding',
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const catchName = activeCatch.paramName;
|
|
98
|
+
if (node.arguments.length < 4) {
|
|
99
|
+
reportMissingCause(node, catchName);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const causeArg = node.arguments[3];
|
|
103
|
+
if (!causeArg || causeArg.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
104
|
+
reportWrongCause(causeArg ?? node, catchName, causeArg
|
|
105
|
+
? sourceCode.getText(causeArg)
|
|
106
|
+
: 'the missing cause argument');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!isCatchBindingReference(causeArg, activeCatch)) {
|
|
110
|
+
reportWrongCause(causeArg, catchName, sourceCode.getText(causeArg));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
CatchClause(node) {
|
|
115
|
+
if (node.param?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
116
|
+
catchStack.push({ paramName: node.param.name, node });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Destructured catch params (e.g., `catch ({ message })`) do not bind the
|
|
120
|
+
// full error object, so they cannot be forwarded as the HttpsError cause.
|
|
121
|
+
// Users should bind the error value (e.g., `catch (error)`) before rethrowing.
|
|
122
|
+
catchStack.push({ paramName: null, node });
|
|
123
|
+
},
|
|
124
|
+
'CatchClause:exit'() {
|
|
125
|
+
catchStack.pop();
|
|
126
|
+
},
|
|
127
|
+
NewExpression(node) {
|
|
128
|
+
validateHttpsError(node);
|
|
129
|
+
},
|
|
130
|
+
CallExpression(node) {
|
|
131
|
+
validateHttpsError(node);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
//# 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
|
};
|