@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
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireHttpsErrorInOnRequestHandlers = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
|
+
const ON_REQUEST_MODULE = 'functions/src/v2/https/onRequest';
|
|
8
|
+
const HTTPS_ERROR_IMPORT_PATH = 'functions/src/util/errors/HttpsError';
|
|
9
|
+
const isFunctionLike = (node) => !!node &&
|
|
10
|
+
(node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
11
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
12
|
+
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
|
|
13
|
+
const isIdentifierWithName = (node, name) => !!node && node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === name;
|
|
14
|
+
const unwrapTypedExpression = (expression) => {
|
|
15
|
+
let current = expression;
|
|
16
|
+
while (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
17
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
18
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
19
|
+
current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
20
|
+
current.type === utils_1.AST_NODE_TYPES.TSInstantiationExpression) {
|
|
21
|
+
current = current.expression;
|
|
22
|
+
}
|
|
23
|
+
return current;
|
|
24
|
+
};
|
|
25
|
+
const getResponseParamNames = (node) => {
|
|
26
|
+
const RESPONSE_PARAM_KEYS = new Set(['res', 'response', 'resp']);
|
|
27
|
+
const extractName = (param) => {
|
|
28
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
29
|
+
return { name: param.name, isExplicitResponseBinding: false };
|
|
30
|
+
}
|
|
31
|
+
if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
32
|
+
param.left.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
33
|
+
return { name: param.left.name, isExplicitResponseBinding: false };
|
|
34
|
+
}
|
|
35
|
+
if (param.type === utils_1.AST_NODE_TYPES.RestElement &&
|
|
36
|
+
param.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
37
|
+
return { name: param.argument.name, isExplicitResponseBinding: false };
|
|
38
|
+
}
|
|
39
|
+
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
40
|
+
for (const property of param.properties) {
|
|
41
|
+
if (property.type !== utils_1.AST_NODE_TYPES.Property || property.computed) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const keyName = property.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
45
|
+
? property.key.name
|
|
46
|
+
: property.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
47
|
+
typeof property.key.value === 'string'
|
|
48
|
+
? property.key.value
|
|
49
|
+
: null;
|
|
50
|
+
if (!keyName || !RESPONSE_PARAM_KEYS.has(keyName.toLowerCase())) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (property.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
54
|
+
return { name: property.value.name, isExplicitResponseBinding: true };
|
|
55
|
+
}
|
|
56
|
+
if (property.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
57
|
+
property.value.left.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
58
|
+
return {
|
|
59
|
+
name: property.value.left.name,
|
|
60
|
+
isExplicitResponseBinding: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (param.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
67
|
+
for (const element of param.elements) {
|
|
68
|
+
if (!element) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const name = element.type === utils_1.AST_NODE_TYPES.Identifier
|
|
72
|
+
? element.name
|
|
73
|
+
: element.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
74
|
+
element.left.type === utils_1.AST_NODE_TYPES.Identifier
|
|
75
|
+
? element.left.name
|
|
76
|
+
: element.type === utils_1.AST_NODE_TYPES.RestElement &&
|
|
77
|
+
element.argument.type === utils_1.AST_NODE_TYPES.Identifier
|
|
78
|
+
? element.argument.name
|
|
79
|
+
: null;
|
|
80
|
+
if (name && RESPONSE_PARAM_KEYS.has(name.toLowerCase())) {
|
|
81
|
+
return { name, isExplicitResponseBinding: false };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
return node.params.reduce((acc, param, index) => {
|
|
89
|
+
const extracted = extractName(param);
|
|
90
|
+
if (!extracted)
|
|
91
|
+
return acc;
|
|
92
|
+
const lowered = extracted.name.toLowerCase();
|
|
93
|
+
const likelyResponse = extracted.isExplicitResponseBinding || RESPONSE_PARAM_KEYS.has(lowered);
|
|
94
|
+
/**
|
|
95
|
+
* In typical Express handlers, the response param is the second argument (req, res),
|
|
96
|
+
* so we treat the second parameter as a likely response name even if it doesn't match
|
|
97
|
+
* 'res', 'response', or 'resp'.
|
|
98
|
+
*/
|
|
99
|
+
if (likelyResponse || index === 1) {
|
|
100
|
+
acc.push(extracted.name);
|
|
101
|
+
}
|
|
102
|
+
return acc;
|
|
103
|
+
}, []);
|
|
104
|
+
};
|
|
105
|
+
const getStatusCodeFromArg = (arg) => {
|
|
106
|
+
if (!arg || arg.type === utils_1.AST_NODE_TYPES.SpreadElement) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (arg.type === utils_1.AST_NODE_TYPES.Literal && typeof arg.value === 'number') {
|
|
110
|
+
return arg.value;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
};
|
|
114
|
+
const mapStatusToHttpsCode = (status) => {
|
|
115
|
+
if (status === 400)
|
|
116
|
+
return 'invalid-argument';
|
|
117
|
+
if (status === 401)
|
|
118
|
+
return 'unauthenticated';
|
|
119
|
+
if (status === 403)
|
|
120
|
+
return 'permission-denied';
|
|
121
|
+
if (status === 404)
|
|
122
|
+
return 'not-found';
|
|
123
|
+
if (status === 409)
|
|
124
|
+
return 'already-exists';
|
|
125
|
+
if (status === 412 || status === 422)
|
|
126
|
+
return 'failed-precondition';
|
|
127
|
+
if (status === 429)
|
|
128
|
+
return 'resource-exhausted';
|
|
129
|
+
if (status === 499 || status === 504)
|
|
130
|
+
return 'deadline-exceeded';
|
|
131
|
+
if (status === 500)
|
|
132
|
+
return 'internal';
|
|
133
|
+
if (status === 502 || status === 503)
|
|
134
|
+
return 'unavailable';
|
|
135
|
+
return 'unknown';
|
|
136
|
+
};
|
|
137
|
+
const findNearestFunction = (ancestors) => {
|
|
138
|
+
for (let idx = ancestors.length - 1; idx >= 0; idx -= 1) {
|
|
139
|
+
const ancestor = ancestors[idx];
|
|
140
|
+
if (isFunctionLike(ancestor)) {
|
|
141
|
+
return ancestor;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
};
|
|
146
|
+
const getMessageExample = (call) => {
|
|
147
|
+
const firstArg = call.arguments[0];
|
|
148
|
+
if (firstArg &&
|
|
149
|
+
firstArg.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
150
|
+
typeof firstArg.value === 'string') {
|
|
151
|
+
return firstArg.value;
|
|
152
|
+
}
|
|
153
|
+
if (firstArg &&
|
|
154
|
+
firstArg.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
155
|
+
firstArg.expressions.length === 0) {
|
|
156
|
+
return firstArg.quasis.map((part) => part.value.raw).join('');
|
|
157
|
+
}
|
|
158
|
+
return 'provide a descriptive message';
|
|
159
|
+
};
|
|
160
|
+
const getObjectNameFromExpression = (node) => {
|
|
161
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
162
|
+
return node.name;
|
|
163
|
+
}
|
|
164
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
165
|
+
!node.computed &&
|
|
166
|
+
node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
167
|
+
node.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
168
|
+
return `${node.object.name}.${node.property.name}`;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
};
|
|
172
|
+
const getCalleeDisplayName = (callee) => {
|
|
173
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
174
|
+
return callee.name;
|
|
175
|
+
}
|
|
176
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
177
|
+
!callee.computed &&
|
|
178
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
179
|
+
const objectName = getObjectNameFromExpression(callee.object);
|
|
180
|
+
if (objectName) {
|
|
181
|
+
return `${objectName}.${callee.property.name}`;
|
|
182
|
+
}
|
|
183
|
+
return callee.property.name;
|
|
184
|
+
}
|
|
185
|
+
return 'helper';
|
|
186
|
+
};
|
|
187
|
+
const getIdentifierNameFromExpression = (expression) => {
|
|
188
|
+
if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
189
|
+
return expression.name;
|
|
190
|
+
}
|
|
191
|
+
if (expression.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const unwrapped = unwrapTypedExpression(expression);
|
|
195
|
+
return unwrapped.type === utils_1.AST_NODE_TYPES.Identifier ? unwrapped.name : null;
|
|
196
|
+
};
|
|
197
|
+
const isStatusMember = (member, propertyName) => {
|
|
198
|
+
if (member.computed ||
|
|
199
|
+
member.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
200
|
+
member.property.name !== propertyName) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const responseName = getIdentifierNameFromExpression(member.object);
|
|
204
|
+
return responseName ? { responseName } : null;
|
|
205
|
+
};
|
|
206
|
+
const getStatusInfoFromStatusCall = (call) => {
|
|
207
|
+
if (call.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
208
|
+
const statusMember = isStatusMember(call.callee, 'status');
|
|
209
|
+
if (!statusMember) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
responseName: statusMember.responseName,
|
|
214
|
+
statusCode: getStatusCodeFromArg(call.arguments[0]),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
const findStatusCall = (node) => {
|
|
220
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
221
|
+
const sendStatusMember = isStatusMember(node.callee, 'sendStatus');
|
|
222
|
+
if (sendStatusMember) {
|
|
223
|
+
return {
|
|
224
|
+
responseName: sendStatusMember.responseName,
|
|
225
|
+
statusCode: getStatusCodeFromArg(node.arguments[0]),
|
|
226
|
+
method: 'sendStatus',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
231
|
+
const statusMember = isStatusMember(node.callee, 'status');
|
|
232
|
+
if (statusMember) {
|
|
233
|
+
const parent = node.parent;
|
|
234
|
+
if (parent &&
|
|
235
|
+
parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
236
|
+
parent.object === node &&
|
|
237
|
+
parent.parent &&
|
|
238
|
+
parent.parent.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
responseName: statusMember.responseName,
|
|
243
|
+
statusCode: getStatusCodeFromArg(node.arguments[0]),
|
|
244
|
+
method: 'status',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
249
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
250
|
+
const statusInfo = getStatusInfoFromStatusCall(node.callee.object);
|
|
251
|
+
if (!statusInfo) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const method = node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier
|
|
255
|
+
? node.callee.property.name
|
|
256
|
+
: 'status';
|
|
257
|
+
return {
|
|
258
|
+
...statusInfo,
|
|
259
|
+
method,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
};
|
|
264
|
+
const isErrorStatus = (statusCode) => statusCode === null || statusCode >= 400;
|
|
265
|
+
exports.requireHttpsErrorInOnRequestHandlers = (0, createRule_1.createRule)({
|
|
266
|
+
name: 'no-res-error-status-in-onrequest',
|
|
267
|
+
meta: {
|
|
268
|
+
type: 'problem',
|
|
269
|
+
docs: {
|
|
270
|
+
description: 'Forbid sending 4xx/5xx Express responses inside onRequest handlers; throw structured HttpsError instances instead so the wrapper can format, log, and map errors consistently.',
|
|
271
|
+
recommended: 'error',
|
|
272
|
+
},
|
|
273
|
+
schema: [],
|
|
274
|
+
messages: {
|
|
275
|
+
useHttpsErrorForStatus: "HTTP status {{statusCode}} sent via `{{responseName}}.{{method}}` inside an onRequest handler bypasses the centralized HttpsError pipeline → This creates observability gaps (errors won't appear in structured logs), inconsistent client error shapes (breaking API contracts), and missing error context (details object unavailable for debugging). → Throw new HttpsError('{{httpsCode}}', '{{messageExample}}', details) instead and import HttpsError from {{httpsErrorImport}} so the wrapper captures context, logs structured data, and returns consistent error responses.",
|
|
276
|
+
useHttpsErrorForComputedStatus: "Computed HTTP status sent via `{{responseName}}.{{method}}` inside an onRequest handler cannot be mapped to the expected HttpsError codes → This bypasses the HttpsError pipeline, leading to inconsistent error logging and unstandardized response shapes. → Choose an explicit HttpsError code (e.g., 'invalid-argument', 'permission-denied') and throw new HttpsError('{{httpsCode}}', '{{messageExample}}', details) after importing from {{httpsErrorImport}} so the wrapper can standardize logging, capture error context, and maintain a consistent API contract.",
|
|
277
|
+
useHttpsErrorForWrapper: "`{{calleeName}}` receives the Express response object `{{responseName}}` inside an onRequest handler, which writes HTTP errors directly and skips the HttpsError wrapper → This results in missing structured logs, inconsistent error responses for clients, and makes debugging difficult due to lost error context. → Refactor this helper to throw new HttpsError('{{httpsCode}}', '{{messageExample}}', details) (import from {{httpsErrorImport}}) so the onRequest wrapper can handle formatting, status mapping, and centralized logging.",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
defaultOptions: [],
|
|
281
|
+
create(context) {
|
|
282
|
+
const onRequestIdentifiers = new Set();
|
|
283
|
+
const onRequestNamespaces = new Set();
|
|
284
|
+
const handlerFunctions = new Set();
|
|
285
|
+
const responseNamesByFunction = new Map();
|
|
286
|
+
const pendingViolations = [];
|
|
287
|
+
const recordFunction = (node) => {
|
|
288
|
+
responseNamesByFunction.set(node, getResponseParamNames(node));
|
|
289
|
+
};
|
|
290
|
+
const extractFunctionFromDefinition = (def) => {
|
|
291
|
+
const defNode = def.node;
|
|
292
|
+
if (isFunctionLike(defNode)) {
|
|
293
|
+
return defNode;
|
|
294
|
+
}
|
|
295
|
+
if (defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator && defNode.init) {
|
|
296
|
+
const initializer = unwrapTypedExpression(defNode.init);
|
|
297
|
+
if (isFunctionLike(initializer)) {
|
|
298
|
+
return initializer;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
};
|
|
303
|
+
const resolveFunctionFromIdentifier = (identifier) => {
|
|
304
|
+
let scope = context.getScope();
|
|
305
|
+
while (scope) {
|
|
306
|
+
const variable = scope.set.get(identifier.name) ??
|
|
307
|
+
scope.variables.find((candidate) => candidate.name === identifier.name);
|
|
308
|
+
if (variable) {
|
|
309
|
+
for (const def of variable.defs) {
|
|
310
|
+
const fn = extractFunctionFromDefinition(def);
|
|
311
|
+
if (fn) {
|
|
312
|
+
return fn;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
scope = scope.upper;
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
};
|
|
320
|
+
const isDirectOnRequestHandler = (fn) => {
|
|
321
|
+
return handlerFunctions.has(fn);
|
|
322
|
+
};
|
|
323
|
+
function findInFunctionChain(startNode, callback) {
|
|
324
|
+
let current = startNode;
|
|
325
|
+
while (current) {
|
|
326
|
+
if (isFunctionLike(current)) {
|
|
327
|
+
const result = callback(current);
|
|
328
|
+
if (result) {
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
current = current.parent;
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
const isWithinOnRequest = (fn) => !!findInFunctionChain(fn, (current) => isDirectOnRequestHandler(current));
|
|
337
|
+
const hasResponseBinding = (fn, responseName) => !!findInFunctionChain(fn, (current) => {
|
|
338
|
+
const params = responseNamesByFunction.get(current) ?? [];
|
|
339
|
+
return params.includes(responseName);
|
|
340
|
+
});
|
|
341
|
+
const findResponseIdentifierInArgs = (fn, call) => findInFunctionChain(fn, (current) => {
|
|
342
|
+
const params = responseNamesByFunction.get(current) ?? [];
|
|
343
|
+
return params.find((name) => call.arguments.some((arg) => {
|
|
344
|
+
const unwrapped = unwrapTypedExpression(arg);
|
|
345
|
+
return isIdentifierWithName(unwrapped, name);
|
|
346
|
+
}));
|
|
347
|
+
});
|
|
348
|
+
const handleOnRequestTracking = (node) => {
|
|
349
|
+
const isOnRequestCallee = (callee) => {
|
|
350
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
351
|
+
return onRequestIdentifiers.has(callee.name);
|
|
352
|
+
}
|
|
353
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
354
|
+
!callee.computed &&
|
|
355
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
356
|
+
callee.property.name === 'onRequest' &&
|
|
357
|
+
callee.object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
358
|
+
return onRequestNamespaces.has(callee.object.name);
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
};
|
|
362
|
+
if (isOnRequestCallee(node.callee)) {
|
|
363
|
+
const handlerArg = [...node.arguments]
|
|
364
|
+
.reverse()
|
|
365
|
+
.find((arg) => arg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
366
|
+
arg.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
367
|
+
arg.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
368
|
+
arg.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
369
|
+
arg.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
370
|
+
arg.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
371
|
+
arg.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
372
|
+
arg.type === utils_1.AST_NODE_TYPES.TSInstantiationExpression);
|
|
373
|
+
if (handlerArg) {
|
|
374
|
+
const expression = unwrapTypedExpression(handlerArg);
|
|
375
|
+
if (isFunctionLike(expression)) {
|
|
376
|
+
handlerFunctions.add(expression);
|
|
377
|
+
}
|
|
378
|
+
else if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
379
|
+
const resolvedHandler = resolveFunctionFromIdentifier(expression);
|
|
380
|
+
if (resolvedHandler) {
|
|
381
|
+
handlerFunctions.add(resolvedHandler);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
const handleStatusViolation = (node) => {
|
|
388
|
+
const statusInfo = findStatusCall(node);
|
|
389
|
+
if (!statusInfo) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
const { responseName, statusCode, method } = statusInfo;
|
|
393
|
+
if (!isErrorStatus(statusCode)) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
const functionNode = findNearestFunction(ASTHelpers_1.ASTHelpers.getAncestors(context, node));
|
|
397
|
+
if (!functionNode) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
if (!hasResponseBinding(functionNode, responseName)) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
pendingViolations.push({
|
|
404
|
+
node,
|
|
405
|
+
functionNode,
|
|
406
|
+
responseName,
|
|
407
|
+
kind: statusCode === null ? 'computedStatus' : 'status',
|
|
408
|
+
statusCode,
|
|
409
|
+
method,
|
|
410
|
+
messageExample: getMessageExample(node),
|
|
411
|
+
});
|
|
412
|
+
return true;
|
|
413
|
+
};
|
|
414
|
+
const handleWrapperViolation = (node) => {
|
|
415
|
+
const functionNode = findNearestFunction(ASTHelpers_1.ASTHelpers.getAncestors(context, node));
|
|
416
|
+
if (!functionNode) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const responseName = findResponseIdentifierInArgs(functionNode, node);
|
|
420
|
+
/**
|
|
421
|
+
* Skip direct method calls on the response object (e.g., res.send(res)) so the
|
|
422
|
+
* wrapper detection only reports helpers that receive the response object as
|
|
423
|
+
* an argument.
|
|
424
|
+
*/
|
|
425
|
+
const calleeUsesResponseDirectly = node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
426
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
427
|
+
responseName === node.callee.object.name;
|
|
428
|
+
if (!responseName || calleeUsesResponseDirectly) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
pendingViolations.push({
|
|
432
|
+
node,
|
|
433
|
+
functionNode,
|
|
434
|
+
responseName,
|
|
435
|
+
kind: 'wrapper',
|
|
436
|
+
statusCode: null,
|
|
437
|
+
method: 'wrapper',
|
|
438
|
+
messageExample: getMessageExample(node),
|
|
439
|
+
calleeName: getCalleeDisplayName(node.callee),
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
return {
|
|
443
|
+
ImportDeclaration(node) {
|
|
444
|
+
if (node.source.value !== ON_REQUEST_MODULE) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
node.specifiers.forEach((specifier) => {
|
|
448
|
+
if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
449
|
+
onRequestIdentifiers.add(specifier.local.name);
|
|
450
|
+
}
|
|
451
|
+
else if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
452
|
+
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
453
|
+
specifier.imported.name === 'onRequest') {
|
|
454
|
+
onRequestIdentifiers.add(specifier.local.name);
|
|
455
|
+
}
|
|
456
|
+
else if (specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
457
|
+
onRequestNamespaces.add(specifier.local.name);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
},
|
|
461
|
+
FunctionDeclaration: recordFunction,
|
|
462
|
+
FunctionExpression: recordFunction,
|
|
463
|
+
ArrowFunctionExpression: recordFunction,
|
|
464
|
+
CallExpression(node) {
|
|
465
|
+
handleOnRequestTracking(node);
|
|
466
|
+
if (handleStatusViolation(node)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
handleWrapperViolation(node);
|
|
470
|
+
},
|
|
471
|
+
'Program:exit'() {
|
|
472
|
+
pendingViolations.forEach((violation) => {
|
|
473
|
+
if (!isWithinOnRequest(violation.functionNode)) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const httpsCode = mapStatusToHttpsCode(violation.statusCode);
|
|
477
|
+
const commonData = {
|
|
478
|
+
responseName: violation.responseName,
|
|
479
|
+
messageExample: violation.messageExample,
|
|
480
|
+
httpsCode,
|
|
481
|
+
httpsErrorImport: HTTPS_ERROR_IMPORT_PATH,
|
|
482
|
+
};
|
|
483
|
+
if (violation.kind === 'wrapper') {
|
|
484
|
+
context.report({
|
|
485
|
+
node: violation.node,
|
|
486
|
+
messageId: 'useHttpsErrorForWrapper',
|
|
487
|
+
data: {
|
|
488
|
+
...commonData,
|
|
489
|
+
calleeName: violation.calleeName ?? 'helper',
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (violation.kind === 'computedStatus') {
|
|
495
|
+
context.report({
|
|
496
|
+
node: violation.node,
|
|
497
|
+
messageId: 'useHttpsErrorForComputedStatus',
|
|
498
|
+
data: {
|
|
499
|
+
...commonData,
|
|
500
|
+
method: violation.method,
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
context.report({
|
|
506
|
+
node: violation.node,
|
|
507
|
+
messageId: 'useHttpsErrorForStatus',
|
|
508
|
+
data: {
|
|
509
|
+
...commonData,
|
|
510
|
+
method: violation.method,
|
|
511
|
+
statusCode: violation.statusCode !== null
|
|
512
|
+
? violation.statusCode.toString()
|
|
513
|
+
: 'computed status',
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
//# sourceMappingURL=no-res-error-status-in-onrequest.js.map
|
|
@@ -37,7 +37,7 @@ exports.noRestrictedPropertiesFix = (0, createRule_1.createRule)({
|
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
39
|
messages: {
|
|
40
|
-
restrictedProperty:
|
|
40
|
+
restrictedProperty: 'Access to "{{objectName}}.{{propertyName}}" is restricted. {{restrictionReason}}Restricted properties often bypass safer APIs, hide side effects, or encourage patterns this codebase forbids. Use the allowed alternative from your rule configuration or remove this property access.',
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
43
|
defaultOptions: [[]],
|
|
@@ -56,6 +56,13 @@ exports.noRestrictedPropertiesFix = (0, createRule_1.createRule)({
|
|
|
56
56
|
'slice',
|
|
57
57
|
'concat',
|
|
58
58
|
]);
|
|
59
|
+
/**
|
|
60
|
+
* Keeps the templated message readable by only adding a trailing space
|
|
61
|
+
* when a restriction reason is provided.
|
|
62
|
+
*/
|
|
63
|
+
function formatRestrictionReason(message) {
|
|
64
|
+
return message ? `${message} ` : '';
|
|
65
|
+
}
|
|
59
66
|
/**
|
|
60
67
|
* Checks if the given node is a result of Object.keys() or Object.values()
|
|
61
68
|
* @param node The node to check
|
|
@@ -109,9 +116,7 @@ exports.noRestrictedPropertiesFix = (0, createRule_1.createRule)({
|
|
|
109
116
|
data: {
|
|
110
117
|
objectName: restrictedProp.object,
|
|
111
118
|
propertyName: restrictedProp.property,
|
|
112
|
-
|
|
113
|
-
? `: ${restrictedProp.message}`
|
|
114
|
-
: '',
|
|
119
|
+
restrictionReason: formatRestrictionReason(restrictedProp.message),
|
|
115
120
|
},
|
|
116
121
|
fix: () => null,
|
|
117
122
|
});
|
|
@@ -135,9 +140,7 @@ exports.noRestrictedPropertiesFix = (0, createRule_1.createRule)({
|
|
|
135
140
|
data: {
|
|
136
141
|
objectName,
|
|
137
142
|
propertyName: restrictedProp.property,
|
|
138
|
-
|
|
139
|
-
? `: ${restrictedProp.message}`
|
|
140
|
-
: '',
|
|
143
|
+
restrictionReason: formatRestrictionReason(restrictedProp.message),
|
|
141
144
|
},
|
|
142
145
|
fix: () => null,
|
|
143
146
|
});
|
|
@@ -157,9 +160,7 @@ exports.noRestrictedPropertiesFix = (0, createRule_1.createRule)({
|
|
|
157
160
|
data: {
|
|
158
161
|
objectName: restrictedProp.object,
|
|
159
162
|
propertyName,
|
|
160
|
-
|
|
161
|
-
? `: ${restrictedProp.message}`
|
|
162
|
-
: '',
|
|
163
|
+
restrictionReason: formatRestrictionReason(restrictedProp.message),
|
|
163
164
|
},
|
|
164
165
|
fix: () => null,
|
|
165
166
|
});
|
|
@@ -29,7 +29,7 @@ exports.noSeparateLoadingState = (0, createRule_1.createRule)({
|
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
messages: {
|
|
32
|
-
separateLoadingState: '
|
|
32
|
+
separateLoadingState: 'Loading flag "{{stateName}}" splits the source of truth for data fetching. Boolean toggles drift from the actual data and add extra renders. Encode the loading phase inside the primary state instead (use a "loading" sentinel or discriminated union) so components read a single authoritative value.',
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
defaultOptions: [{}],
|
|
@@ -63,16 +63,6 @@ exports.noSeparateLoadingState = (0, createRule_1.createRule)({
|
|
|
63
63
|
}
|
|
64
64
|
return false;
|
|
65
65
|
}
|
|
66
|
-
function getSetterName(declarator) {
|
|
67
|
-
if (declarator.id.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
|
|
68
|
-
declarator.id.elements.length >= 2) {
|
|
69
|
-
const setterElement = declarator.id.elements[1];
|
|
70
|
-
if (setterElement?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
71
|
-
return setterElement.name;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
66
|
return {
|
|
77
67
|
VariableDeclarator(node) {
|
|
78
68
|
// Check for useState destructuring patterns
|
|
@@ -95,12 +85,13 @@ exports.noSeparateLoadingState = (0, createRule_1.createRule)({
|
|
|
95
85
|
declarator: node,
|
|
96
86
|
setterVar,
|
|
97
87
|
usage: { truthy: false, falsy: false },
|
|
88
|
+
stateName: stateElement.name,
|
|
98
89
|
});
|
|
99
90
|
}
|
|
100
91
|
}
|
|
101
92
|
}
|
|
102
93
|
},
|
|
103
|
-
CallExpression(
|
|
94
|
+
CallExpression() {
|
|
104
95
|
// Setter usage is resolved via scope references in Program:exit
|
|
105
96
|
return;
|
|
106
97
|
},
|
|
@@ -123,15 +114,15 @@ exports.noSeparateLoadingState = (0, createRule_1.createRule)({
|
|
|
123
114
|
}
|
|
124
115
|
}
|
|
125
116
|
}
|
|
126
|
-
const { declarator, usage } = tracker;
|
|
127
|
-
const setterName = getSetterName(declarator);
|
|
128
|
-
if (!setterName)
|
|
129
|
-
continue;
|
|
117
|
+
const { declarator, usage, stateName } = tracker;
|
|
130
118
|
// If we have both truthy and falsy setter calls, it's likely a loading state pattern
|
|
131
119
|
if (usage.truthy && usage.falsy) {
|
|
132
120
|
context.report({
|
|
133
121
|
node: declarator,
|
|
134
122
|
messageId: 'separateLoadingState',
|
|
123
|
+
data: {
|
|
124
|
+
stateName,
|
|
125
|
+
},
|
|
135
126
|
});
|
|
136
127
|
}
|
|
137
128
|
}
|
|
@@ -22,7 +22,7 @@ exports.noStaleStateAcrossAwait = (0, createRule_1.createRule)({
|
|
|
22
22
|
return {
|
|
23
23
|
// Check functions for violations
|
|
24
24
|
'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression'(node) {
|
|
25
|
-
const sourceCode = context.
|
|
25
|
+
const sourceCode = context.sourceCode;
|
|
26
26
|
const scopeManager = sourceCode.scopeManager;
|
|
27
27
|
if (!scopeManager)
|
|
28
28
|
return;
|