@blumintinc/eslint-plugin-blumint 1.15.0 → 1.16.1
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 +28 -5
- package/lib/index.js +7 -1
- package/lib/rules/consistent-callback-naming.js +26 -0
- package/lib/rules/dynamic-https-errors.js +5 -26
- package/lib/rules/enforce-assert-safe-object-key.js +29 -0
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +86 -119
- package/lib/rules/enforce-dynamic-imports.d.ts +5 -1
- package/lib/rules/enforce-dynamic-imports.js +90 -19
- package/lib/rules/enforce-memoize-async.js +1 -4
- package/lib/rules/enforce-mui-rounded-icons.js +42 -1
- package/lib/rules/enforce-verb-noun-naming.js +3 -0
- package/lib/rules/global-const-style.js +9 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
- package/lib/rules/memo-compare-deeply-complex-props.js +167 -3
- package/lib/rules/memo-nested-react-components.js +143 -8
- package/lib/rules/no-array-length-in-deps.js +74 -3
- package/lib/rules/no-circular-references.js +145 -482
- package/lib/rules/no-compositing-layer-props.js +31 -0
- package/lib/rules/no-entire-object-hook-deps.js +132 -97
- package/lib/rules/no-explicit-return-type.js +6 -0
- package/lib/rules/no-hungarian.js +119 -24
- package/lib/rules/no-margin-properties.js +7 -38
- package/lib/rules/no-unnecessary-verb-suffix.js +79 -0
- package/lib/rules/no-unused-props.js +215 -37
- package/lib/rules/no-useless-fragment.js +10 -2
- package/lib/rules/parallelize-async-operations.js +1 -3
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +73 -11
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
- package/lib/rules/react-memoize-literals.js +87 -1
- package/lib/rules/require-memo.js +8 -0
- package/lib/rules/require-migration-script-metadata.d.ts +9 -0
- package/lib/rules/require-migration-script-metadata.js +206 -0
- package/lib/rules/warn-https-error-message-user-friendly.d.ts +1 -0
- package/lib/rules/warn-https-error-message-user-friendly.js +239 -0
- package/lib/utils/ASTHelpers.d.ts +15 -0
- package/lib/utils/ASTHelpers.js +48 -0
- package/package.json +7 -6
- package/release-manifest.json +196 -0
- package/lib/rules/prefer-memoized-props.d.ts +0 -3
- package/lib/rules/prefer-memoized-props.js +0 -445
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +0 -7
- package/lib/rules/prefer-nullish-coalescing-override.js +0 -188
- package/lib/rules/require-usememo-object-literals.d.ts +0 -4
- package/lib/rules/require-usememo-object-literals.js +0 -64
package/README.md
CHANGED
|
@@ -47,6 +47,27 @@ Or use the recommended config:
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
## Autonomous maintenance
|
|
51
|
+
|
|
52
|
+
This repo is maintained by an autonomous loop, not a human cutting releases on
|
|
53
|
+
demand. See [.claude/skills/repo-maintenance/SKILL.md](.claude/skills/repo-maintenance/SKILL.md)
|
|
54
|
+
for the operating model. Two contracts make it safe and self-healing:
|
|
55
|
+
|
|
56
|
+
- **The maintainer** (`/maintainer`, backed by `scripts/maintainer.ts`) is the
|
|
57
|
+
sole operator: it drains the open-issue queue (bugs before features, oldest
|
|
58
|
+
first), self-merges fixes to `develop`, and on an empty queue promotes
|
|
59
|
+
`develop → main` (firing the release) then fast-forwards `develop`.
|
|
60
|
+
- **`release-manifest.json`** is a strict, published `version → fixed-rules` map
|
|
61
|
+
(`scripts/generate-release-manifest.js`, a `@semantic-release/exec` prepareCmd)
|
|
62
|
+
so consumers re-enable disabled rules by exact rule name — never by parsing the
|
|
63
|
+
changelog. It's only reliable because **every `fix`/`feat` commit's scope must
|
|
64
|
+
be a real rule name**: enforced by the `blumint-rule-scope` commitlint rule and
|
|
65
|
+
the `validate-commit-scopes` CI gate. One rule per fix/feat commit.
|
|
66
|
+
|
|
67
|
+
On publish, `scripts/dispatch-agora-release.js` fires a `repository_dispatch` to
|
|
68
|
+
agora, which bumps its pin and re-enables the now-fixed rules (FP-audited). The
|
|
69
|
+
full closed loop is documented in agora's `.claude/skills/eslint-autonomy/SKILL.md`.
|
|
70
|
+
|
|
50
71
|
## Rules
|
|
51
72
|
|
|
52
73
|
<!-- begin auto-generated rules list -->
|
|
@@ -74,10 +95,10 @@ Or use the recommended config:
|
|
|
74
95
|
| [enforce-centralized-mock-firestore](docs/rules/enforce-centralized-mock-firestore.md) | Enforce usage of centralized mockFirestore from predefined location | ✅ | | | 🔧 | | |
|
|
75
96
|
| [enforce-console-error](docs/rules/enforce-console-error.md) | Enforce proper logging for useAlertDialog based on severity. When severity is "error", console.error must be included. When severity is "warning", console.warn must be included. This ensures all user-facing errors and warnings are properly logged to observability systems. | ✅ | | | | | |
|
|
76
97
|
| [enforce-css-media-queries](docs/rules/enforce-css-media-queries.md) | Enforce CSS media queries over JS breakpoints | ✅ | | | | | |
|
|
77
|
-
| [enforce-date-ttime](docs/rules/enforce-date-ttime.md)
|
|
98
|
+
| [enforce-date-ttime](docs/rules/enforce-date-ttime.md) | Enforce that any generic type parameter named TTime is explicitly set to Date in frontend code | ✅ | | | 🔧 | | |
|
|
78
99
|
| [enforce-dynamic-file-naming](docs/rules/enforce-dynamic-file-naming.md) | Enforce .dynamic.ts(x) file naming when @blumintinc/blumint/enforce-dynamic-imports or @blumintinc/blumint/require-dynamic-firebase-imports rule is disabled | | | | | | |
|
|
79
100
|
| [enforce-dynamic-firebase-imports](docs/rules/enforce-dynamic-firebase-imports.md) | Require firebaseCloud modules to be loaded via dynamic import so Firebase code stays out of the initial bundle and only loads when needed. | ✅ | | | 🔧 | 💡 | |
|
|
80
|
-
| [enforce-dynamic-imports](docs/rules/enforce-dynamic-imports.md) | Enforce dynamic imports for
|
|
101
|
+
| [enforce-dynamic-imports](docs/rules/enforce-dynamic-imports.md) | Enforce dynamic imports for external libraries by default to optimize bundle size, unless explicitly ignored | ✅ | | | | | |
|
|
81
102
|
| [enforce-early-destructuring](docs/rules/enforce-early-destructuring.md) | Hoist object destructuring out of React hooks so dependency arrays track the fields in use instead of the entire object. | ✅ | | | 🔧 | | |
|
|
82
103
|
| [enforce-empty-object-check](docs/rules/enforce-empty-object-check.md) | Ensure object existence checks also guard against empty objects so that empty payloads are treated like missing data. | ✅ | | | 🔧 | | |
|
|
83
104
|
| [enforce-exported-function-types](docs/rules/enforce-exported-function-types.md) | Enforce exporting types for function props and return values | ✅ | | | | | |
|
|
@@ -91,7 +112,7 @@ Or use the recommended config:
|
|
|
91
112
|
| [enforce-global-constants](docs/rules/enforce-global-constants.md) | Enforce global static constants for React components/hooks | ✅ | | | 🔧 | | |
|
|
92
113
|
| [enforce-id-capitalization](docs/rules/enforce-id-capitalization.md) | Enforce the use of "ID" instead of "id" in user-facing text | ✅ | | | 🔧 | | |
|
|
93
114
|
| [enforce-identifiable-firestore-type](docs/rules/enforce-identifiable-firestore-type.md) | Enforce that Firestore type definitions extend Identifiable and match their folder name | ✅ | | | | | |
|
|
94
|
-
| [enforce-memoize-async](docs/rules/enforce-memoize-async.md) | Enforce @Memoize() decorator on async methods with 0-1 parameters to cache results and prevent redundant API calls or expensive computations.
|
|
115
|
+
| [enforce-memoize-async](docs/rules/enforce-memoize-async.md) | Enforce @Memoize() decorator on async methods with 0-1 parameters to cache results and prevent redundant API calls or expensive computations. Without memoization, repeated calls trigger redundant requests or expensive computations, increasing latency. @Memoize() caches results by parameter, ensuring subsequent calls with identical inputs return immediately. | ✅ | | | 🔧 | | |
|
|
95
116
|
| [enforce-memoize-getters](docs/rules/enforce-memoize-getters.md) | Enforce @Memoize() decorator on private class getters to avoid re-instantiation and preserve state across accesses. | ✅ | | | 🔧 | | |
|
|
96
117
|
| [enforce-microdiff](docs/rules/enforce-microdiff.md) | Enforce using microdiff for object and array comparison operations | ✅ | | | 🔧 | | |
|
|
97
118
|
| [enforce-mock-firestore](docs/rules/enforce-mock-firestore.md) | Enforce using the standardized mockFirestore utility instead of manual Firestore mocking or third-party mocks. This ensures consistent test behavior across the codebase, reduces boilerplate, and provides type-safe mocking of Firestore operations. | ✅ | | | | | |
|
|
@@ -110,7 +131,7 @@ Or use the recommended config:
|
|
|
110
131
|
| [enforce-stable-hash-spread-props](docs/rules/enforce-stable-hash-spread-props.md) | Require stableHash wrapping when spread props rest objects are used in React hook dependency arrays to avoid re-renders triggered by new object references on every render. | ✅ | | | 🔧 | | |
|
|
111
132
|
| [enforce-storage-context](docs/rules/enforce-storage-context.md) | Require storage access to go through the LocalStorage and SessionStorage context providers instead of direct browser APIs | ✅ | | | | | |
|
|
112
133
|
| [enforce-timestamp-now](docs/rules/enforce-timestamp-now.md) | Enforce the use of Timestamp.now() for getting the current timestamp in backend code. This rule prevents using alternatives like Timestamp.fromDate(new Date()) or other date creation patterns that could lead to inconsistency. | ✅ | | | 🔧 | | |
|
|
113
|
-
| [enforce-transform-memoization](docs/rules/enforce-transform-memoization.md) | Enforce memoization of transformValue and transformOnChange in adaptValue
|
|
134
|
+
| [enforce-transform-memoization](docs/rules/enforce-transform-memoization.md) | Enforce memoization of transformValue and transformOnChange in adaptValue | ✅ | | | | | |
|
|
114
135
|
| [enforce-typescript-markdown-code-blocks](docs/rules/enforce-typescript-markdown-code-blocks.md) | Ensure Markdown fenced code blocks without a language specifier default to typescript for consistent highlighting. | ✅ | | | 🔧 | | |
|
|
115
136
|
| [enforce-unique-cursor-headers](docs/rules/enforce-unique-cursor-headers.md) | Ensure files have exactly one cursor header containing required tags (e.g., @fileoverview) before any code | ✅ | | | 🔧 | | |
|
|
116
137
|
| [enforce-verb-noun-naming](docs/rules/enforce-verb-noun-naming.md) | Enforce verb phrases for functions and methods | ✅ | | | | | |
|
|
@@ -204,7 +225,7 @@ Or use the recommended config:
|
|
|
204
225
|
| [prefer-type-alias-over-typeof-constant](docs/rules/prefer-type-alias-over-typeof-constant.md) | Prefer named type aliases over `typeof` on same-file global constants; ensure types are declared before constants. | ✅ | | | | | |
|
|
205
226
|
| [prefer-type-over-interface](docs/rules/prefer-type-over-interface.md) | Prefer using type alias over interface | ✅ | | | 🔧 | | |
|
|
206
227
|
| [prefer-url-tostring-over-tojson](docs/rules/prefer-url-tostring-over-tojson.md) | Enforce the use of toString() over toJSON() on URL objects. | ✅ | | | 🔧 | | |
|
|
207
|
-
| [prefer-use-deep-compare-memo](docs/rules/prefer-use-deep-compare-memo.md) | Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays
|
|
228
|
+
| [prefer-use-deep-compare-memo](docs/rules/prefer-use-deep-compare-memo.md) | Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays) that are not already memoized. This prevents unnecessary re-renders due to reference changes. | ✅ | | | 🔧 | | |
|
|
208
229
|
| [prefer-usecallback-over-usememo-for-functions](docs/rules/prefer-usecallback-over-usememo-for-functions.md) | Enforce using useCallback instead of useMemo for memoizing functions | ✅ | | | 🔧 | | |
|
|
209
230
|
| [prefer-usememo-over-useeffect-usestate](docs/rules/prefer-usememo-over-useeffect-usestate.md) | Prefer useMemo over useEffect + useState for pure computations to avoid extra render cycles and stale derived state. | ✅ | | | | | |
|
|
210
231
|
| [prefer-utility-function-over-private-static](docs/rules/prefer-utility-function-over-private-static.md) | Enforce abstraction of private static methods into utility functions | ✅ | | | | | |
|
|
@@ -218,6 +239,7 @@ Or use the recommended config:
|
|
|
218
239
|
| [require-image-optimized](docs/rules/require-image-optimized.md) | Enforce using ImageOptimized component instead of next/image or img tags | ✅ | | | 🔧 | | |
|
|
219
240
|
| [require-memo](docs/rules/require-memo.md) | React components must be memoized | ✅ | | | 🔧 | | |
|
|
220
241
|
| [require-memoize-jsx-returners](docs/rules/require-memoize-jsx-returners.md) | Require @Memoize() decorator on instance members that return JSX or JSX factories | ✅ | | | 🔧 | | |
|
|
242
|
+
| [require-migration-script-metadata](docs/rules/require-migration-script-metadata.md) | Enforce JSDoc migration metadata in callable scripts | ✅ | | | | | |
|
|
221
243
|
| [semantic-function-prefixes](docs/rules/semantic-function-prefixes.md) | Require semantic function prefixes instead of generic verbs so callers know whether a function fetches data, transforms input, or mutates state | ✅ | | | | | |
|
|
222
244
|
| [sync-onwrite-name-func](docs/rules/sync-onwrite-name-func.md) | Ensure that the name field matches the func field in onWrite handlers | ✅ | | | 🔧 | | |
|
|
223
245
|
| [test-file-location-enforcement](docs/rules/test-file-location-enforcement.md) | Enforce colocating *.test.ts or *.test.tsx files with the code they cover. | ✅ | | | | | |
|
|
@@ -226,6 +248,7 @@ Or use the recommended config:
|
|
|
226
248
|
| [use-custom-router](docs/rules/use-custom-router.md) | Enforce using src/hooks/routing/useRouter instead of next/router | ✅ | | | 🔧 | | |
|
|
227
249
|
| [use-latest-callback](docs/rules/use-latest-callback.md) | Enforce using useLatestCallback from use-latest-callback instead of React useCallback | ✅ | | | 🔧 | | |
|
|
228
250
|
| [vertically-group-related-functions](docs/rules/vertically-group-related-functions.md) | Keep top-level functions grouped vertically so callers, exports, and helpers read top-down. | ✅ | | | 🔧 | | |
|
|
251
|
+
| [warn-https-error-message-user-friendly](docs/rules/warn-https-error-message-user-friendly.md) | Warn when messageUserFriendly is used in HttpsError or toHttpsError to ensure it is truly a user-caused error. | | ✅ | | | | |
|
|
229
252
|
|
|
230
253
|
<!-- end auto-generated rules list -->
|
|
231
254
|
|
package/lib/index.js
CHANGED
|
@@ -168,6 +168,8 @@ const vertically_group_related_functions_1 = require("./rules/vertically-group-r
|
|
|
168
168
|
const no_static_constants_in_dynamic_files_1 = __importDefault(require("./rules/no-static-constants-in-dynamic-files"));
|
|
169
169
|
const test_file_location_enforcement_1 = require("./rules/test-file-location-enforcement");
|
|
170
170
|
const prevent_children_clobber_1 = require("./rules/prevent-children-clobber");
|
|
171
|
+
const require_migration_script_metadata_1 = require("./rules/require-migration-script-metadata");
|
|
172
|
+
const warn_https_error_message_user_friendly_1 = require("./rules/warn-https-error-message-user-friendly");
|
|
171
173
|
const NO_FRONTEND_IMPORTS_FROM_FUNCTIONS_MESSAGE = 'Backend Cloud Functions (.f.ts under functions/) must not import frontend modules from the repo root src/**. Frontend code can depend on browser-only APIs and bundling it into Cloud Functions breaks server execution; move shared logic into functions/src or a shared package.';
|
|
172
174
|
function noFrontendImportsFromFunctionsPatterns(pattern) {
|
|
173
175
|
return [
|
|
@@ -194,7 +196,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
|
|
|
194
196
|
module.exports = {
|
|
195
197
|
meta: {
|
|
196
198
|
name: '@blumintinc/eslint-plugin-blumint',
|
|
197
|
-
version: '1.
|
|
199
|
+
version: '1.16.1',
|
|
198
200
|
},
|
|
199
201
|
parseOptions: {
|
|
200
202
|
ecmaVersion: 2020,
|
|
@@ -368,6 +370,8 @@ module.exports = {
|
|
|
368
370
|
'@blumintinc/blumint/vertically-group-related-functions': 'error',
|
|
369
371
|
'@blumintinc/blumint/no-static-constants-in-dynamic-files': 'error',
|
|
370
372
|
'@blumintinc/blumint/test-file-location-enforcement': 'error',
|
|
373
|
+
'@blumintinc/blumint/require-migration-script-metadata': 'error',
|
|
374
|
+
'@blumintinc/blumint/warn-https-error-message-user-friendly': 'warn',
|
|
371
375
|
},
|
|
372
376
|
/**
|
|
373
377
|
* Depth-specific overrides block only import strings that traverse to the
|
|
@@ -616,6 +620,8 @@ module.exports = {
|
|
|
616
620
|
'vertically-group-related-functions': vertically_group_related_functions_1.verticallyGroupRelatedFunctions,
|
|
617
621
|
'no-static-constants-in-dynamic-files': no_static_constants_in_dynamic_files_1.default,
|
|
618
622
|
'test-file-location-enforcement': test_file_location_enforcement_1.testFileLocationEnforcement,
|
|
623
|
+
'require-migration-script-metadata': require_migration_script_metadata_1.requireMigrationScriptMetadata,
|
|
624
|
+
'warn-https-error-message-user-friendly': warn_https_error_message_user_friendly_1.warnHttpsErrorMessageUserFriendly,
|
|
619
625
|
},
|
|
620
626
|
};
|
|
621
627
|
//# sourceMappingURL=index.js.map
|
|
@@ -95,6 +95,31 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
95
95
|
const type = checker.getTypeAtLocation(tsNode);
|
|
96
96
|
return type.getCallSignatures().length > 0;
|
|
97
97
|
}
|
|
98
|
+
// A union counts as "mixed" when it pairs a callable member with a
|
|
99
|
+
// non-callable one (e.g. `Validate<T> | readonly T[]`). `undefined`/`null`
|
|
100
|
+
// members are ignored so plain optional callbacks (`(() => void) | undefined`)
|
|
101
|
+
// remain pure functions.
|
|
102
|
+
function isMixedFunctionUnion(type) {
|
|
103
|
+
if (!type || !type.isUnion()) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const members = type.types.filter((member) => !(member.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null)));
|
|
107
|
+
const hasFunctionMember = members.some((member) => member.getCallSignatures().length > 0);
|
|
108
|
+
const hasNonFunctionMember = members.some((member) => member.getCallSignatures().length === 0);
|
|
109
|
+
return hasFunctionMember && hasNonFunctionMember;
|
|
110
|
+
}
|
|
111
|
+
// The `on` prefix is only meaningful for props that are *exclusively*
|
|
112
|
+
// callbacks. A prop typed `Validate<T> | readonly T[]` is a configuration
|
|
113
|
+
// prop that merely accepts a function as one option, so it must be skipped.
|
|
114
|
+
// The passed value may be a plain function even when the prop legitimately
|
|
115
|
+
// accepts non-function values, so the prop's contextual (declared) type is
|
|
116
|
+
// inspected alongside the value's own type.
|
|
117
|
+
function acceptsNonFunctionValue(node) {
|
|
118
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
119
|
+
const valueType = checker.getTypeAtLocation(tsNode);
|
|
120
|
+
const contextualType = checker.getContextualType(tsNode);
|
|
121
|
+
return (isMixedFunctionUnion(valueType) || isMixedFunctionUnion(contextualType));
|
|
122
|
+
}
|
|
98
123
|
function isRenderFunction(node) {
|
|
99
124
|
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
100
125
|
const type = checker.getTypeAtLocation(tsNode);
|
|
@@ -164,6 +189,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
164
189
|
propName &&
|
|
165
190
|
!propName.startsWith('on') &&
|
|
166
191
|
!propName.startsWith('render') &&
|
|
192
|
+
!acceptsNonFunctionValue(node.value.expression) &&
|
|
167
193
|
!isRenderFunction(node.value.expression) &&
|
|
168
194
|
!isReactComponentType(node.value.expression)) {
|
|
169
195
|
const eventName = propName.charAt(0).toUpperCase() + propName.slice(1);
|
|
@@ -3,18 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.dynamicHttpsErrors = void 0;
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
5
|
const utils_1 = require("@typescript-eslint/utils");
|
|
6
|
-
const
|
|
7
|
-
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
8
|
-
return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
9
|
-
callee.object.name === 'https' &&
|
|
10
|
-
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
11
|
-
callee.property.name === 'HttpsError');
|
|
12
|
-
}
|
|
13
|
-
else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
14
|
-
return callee.name === 'HttpsError';
|
|
15
|
-
}
|
|
16
|
-
return false;
|
|
17
|
-
};
|
|
6
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
18
7
|
const findPropertyByName = (properties, name) => properties.find((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
19
8
|
!p.computed &&
|
|
20
9
|
((p.key.type === utils_1.AST_NODE_TYPES.Identifier && p.key.name === name) ||
|
|
@@ -38,23 +27,13 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
|
38
27
|
},
|
|
39
28
|
defaultOptions: [],
|
|
40
29
|
create(context) {
|
|
41
|
-
const unwrapTSAssertions = (node) => {
|
|
42
|
-
let inner = node;
|
|
43
|
-
while (inner.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
44
|
-
inner.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
45
|
-
inner.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
46
|
-
inner.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
|
|
47
|
-
inner = inner.expression;
|
|
48
|
-
}
|
|
49
|
-
return inner;
|
|
50
|
-
};
|
|
51
30
|
// Only string concatenation with "+" can be static; all other operators
|
|
52
31
|
// are treated as dynamic to avoid hashing non-literal message content.
|
|
53
32
|
const isDynamicBinaryExpression = (expression) => {
|
|
54
33
|
if (expression.operator !== '+')
|
|
55
34
|
return true;
|
|
56
35
|
const isStaticLiteral = (expr) => {
|
|
57
|
-
const inner = unwrapTSAssertions(expr);
|
|
36
|
+
const inner = ASTHelpers_1.ASTHelpers.unwrapTSAssertions(expr);
|
|
58
37
|
return (inner.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
59
38
|
typeof inner.value === 'string');
|
|
60
39
|
};
|
|
@@ -62,7 +41,7 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
|
62
41
|
if (expr.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
63
42
|
return false;
|
|
64
43
|
}
|
|
65
|
-
const inner = unwrapTSAssertions(expr);
|
|
44
|
+
const inner = ASTHelpers_1.ASTHelpers.unwrapTSAssertions(expr);
|
|
66
45
|
if (inner.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
67
46
|
return !isDynamicBinaryExpression(inner);
|
|
68
47
|
}
|
|
@@ -102,7 +81,7 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
|
102
81
|
* `shouldValidateForStaticness`.
|
|
103
82
|
*/
|
|
104
83
|
const checkMessageIsStatic = (messageNode) => {
|
|
105
|
-
const currentNode = unwrapTSAssertions(messageNode);
|
|
84
|
+
const currentNode = ASTHelpers_1.ASTHelpers.unwrapTSAssertions(messageNode);
|
|
106
85
|
if (currentNode.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
107
86
|
return;
|
|
108
87
|
}
|
|
@@ -127,7 +106,7 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
|
127
106
|
};
|
|
128
107
|
const checkForHttpsError = (node) => {
|
|
129
108
|
const callee = node.callee;
|
|
130
|
-
if (!isHttpsErrorCall(callee))
|
|
109
|
+
if (!ASTHelpers_1.ASTHelpers.isHttpsErrorCall(callee))
|
|
131
110
|
return;
|
|
132
111
|
// Signature 1: Object-based constructor (HttpsErrorProps)
|
|
133
112
|
if (node.arguments.length >= 1 &&
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.enforceAssertSafeObjectKey = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
6
7
|
const DEFAULT_IMPORT_PATH = 'functions/src/util/assertSafe';
|
|
7
8
|
exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
8
9
|
name: 'enforce-assert-safe-object-key',
|
|
@@ -66,6 +67,29 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
66
67
|
return createFixes(fixer, node, expressionText);
|
|
67
68
|
},
|
|
68
69
|
});
|
|
70
|
+
/**
|
|
71
|
+
* Returns true when the identifier was initialized directly from an
|
|
72
|
+
* assertSafe(...) call, e.g. `const safeKey = assertSafe(rawKey)`.
|
|
73
|
+
* Only direct, single-step initializers count — transitive aliases
|
|
74
|
+
* (const b = a) are not followed so they continue to be flagged.
|
|
75
|
+
* findVariableInScope returns the nearest binding, so an inner variable
|
|
76
|
+
* that shadows an outer assertSafe-initialized one is correctly not exempt.
|
|
77
|
+
*/
|
|
78
|
+
const isAssertSafeValidatedIdentifier = (node) => {
|
|
79
|
+
const scope = ASTHelpers_1.ASTHelpers.getScope(context, node);
|
|
80
|
+
const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(scope, node.name);
|
|
81
|
+
if (!variable)
|
|
82
|
+
return false;
|
|
83
|
+
return variable.defs.some((def) => {
|
|
84
|
+
const init = def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator
|
|
85
|
+
? def.node.init
|
|
86
|
+
: null;
|
|
87
|
+
return (!!init &&
|
|
88
|
+
init.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
89
|
+
init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
90
|
+
init.callee.name === 'assertSafe');
|
|
91
|
+
});
|
|
92
|
+
};
|
|
69
93
|
return {
|
|
70
94
|
ImportDeclaration(node) {
|
|
71
95
|
// Check if assertSafe is already imported
|
|
@@ -193,6 +217,11 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
193
217
|
if (isLikelyArray) {
|
|
194
218
|
return;
|
|
195
219
|
}
|
|
220
|
+
// Variables initialized directly from assertSafe(...) are already
|
|
221
|
+
// validated — no need to double-wrap them.
|
|
222
|
+
if (isAssertSafeValidatedIdentifier(property)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
196
225
|
const propText = context.sourceCode.getText(property);
|
|
197
226
|
reportUseAssertSafe(property, propText);
|
|
198
227
|
return;
|