@blumintinc/eslint-plugin-blumint 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -11
- package/lib/index.js +14 -12
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/consistent-callback-naming.js +42 -3
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +132 -41
- package/lib/rules/enforce-boolean-naming-prefixes.js +0 -212
- package/lib/rules/enforce-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- package/lib/rules/enforce-global-constants.js +3 -3
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-memoize-async.js +69 -15
- package/lib/rules/enforce-props-argument-name.js +42 -16
- package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
- package/lib/rules/enforce-transform-memoization.js +1 -1
- package/lib/rules/enforce-verb-noun-naming.js +3814 -4641
- package/lib/rules/global-const-style.js +16 -4
- package/lib/rules/memo-compare-deeply-complex-props.js +16 -3
- package/lib/rules/memo-nested-react-components.js +108 -103
- package/lib/rules/no-async-foreach.js +7 -2
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +13 -15
- package/lib/rules/no-console-error.js +12 -10
- package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +48 -1
- package/lib/rules/no-excessive-parent-chain.js +3 -0
- package/lib/rules/no-inline-component-prop.js +16 -7
- package/lib/rules/no-passthrough-getters.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +83 -1
- package/lib/rules/no-redundant-this-params.js +50 -1
- package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
- package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
- package/lib/rules/parallelize-async-operations.js +119 -54
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
- package/lib/rules/prefer-params-over-parent-id.js +1 -1
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
- package/lib/rules/prevent-children-clobber.js +9 -5
- package/lib/rules/react-memoize-literals.js +131 -12
- package/lib/rules/require-https-error-cause.js +30 -11
- package/lib/rules/require-memo.js +17 -9
- package/lib/utils/ASTHelpers.d.ts +34 -1
- package/lib/utils/ASTHelpers.js +346 -112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,19 +74,21 @@ Or use the recommended config:
|
|
|
74
74
|
| [enforce-centralized-mock-firestore](docs/rules/enforce-centralized-mock-firestore.md) | Enforce usage of centralized mockFirestore from predefined location | ✅ | | | 🔧 | | |
|
|
75
75
|
| [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
76
|
| [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) | Enforce that any generic type parameter named TTime is explicitly set to Date in frontend code | ✅ | | | 🔧 | | 💭 |
|
|
77
78
|
| [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 | | | | | | |
|
|
78
79
|
| [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. | ✅ | | | 🔧 | 💡 | |
|
|
79
80
|
| [enforce-dynamic-imports](docs/rules/enforce-dynamic-imports.md) | Enforce dynamic imports for specified libraries to optimize bundle size | ✅ | | | | | |
|
|
80
81
|
| [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. | ✅ | | | 🔧 | | |
|
|
81
82
|
| [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. | ✅ | | | 🔧 | | |
|
|
82
83
|
| [enforce-exported-function-types](docs/rules/enforce-exported-function-types.md) | Enforce exporting types for function props and return values | ✅ | | | | | |
|
|
84
|
+
| [enforce-f-extension-for-entry-points](docs/rules/enforce-f-extension-for-entry-points.md) | Enforce .f.ts extension for entry points | ✅ | | | | | |
|
|
83
85
|
| [enforce-fieldpath-syntax-in-docsetter](docs/rules/enforce-fieldpath-syntax-in-docsetter.md) | Enforce the use of Firestore FieldPath syntax when passing documentData into DocSetter. Instead of using nested object syntax, developers should use dot notation for deeply nested fields. | ✅ | | | 🔧 | | |
|
|
84
86
|
| [enforce-firestore-doc-ref-generic](docs/rules/enforce-firestore-doc-ref-generic.md) | Enforce generic argument for Firestore DocumentReference, CollectionReference and CollectionGroup | ✅ | | | | | 💭 |
|
|
85
87
|
| [enforce-firestore-facade](docs/rules/enforce-firestore-facade.md) | Enforce usage of Firestore facades instead of direct Firestore methods | ✅ | | | | | |
|
|
86
88
|
| [enforce-firestore-path-utils](docs/rules/enforce-firestore-path-utils.md) | Enforce usage of utility functions for Firestore paths to ensure type safety, maintainability, and consistent path construction. This prevents errors from manual string concatenation and makes path changes easier to manage. | ✅ | | | | | |
|
|
87
89
|
| [enforce-firestore-rules-get-access](docs/rules/enforce-firestore-rules-get-access.md) | Ensure Firestore security rules use .get() with a default value instead of direct field access comparisons (e.g., resource.data.fieldX.fieldY != null). | ✅ | | | 🔧 | | |
|
|
88
90
|
| [enforce-firestore-set-merge](docs/rules/enforce-firestore-set-merge.md) | Enforce using set() with { merge: true } instead of update() for Firestore operations to ensure consistent behavior. The update() method fails if the document does not exist, while set() with { merge: true } creates the document if needed and safely merges fields, making it more reliable and predictable. | ✅ | | | 🔧 | | |
|
|
89
|
-
| [enforce-global-constants](docs/rules/enforce-global-constants.md) | Enforce
|
|
91
|
+
| [enforce-global-constants](docs/rules/enforce-global-constants.md) | Enforce global static constants for React components/hooks | ✅ | | | 🔧 | | |
|
|
90
92
|
| [enforce-id-capitalization](docs/rules/enforce-id-capitalization.md) | Enforce the use of "ID" instead of "id" in user-facing text | ✅ | | | 🔧 | | |
|
|
91
93
|
| [enforce-identifiable-firestore-type](docs/rules/enforce-identifiable-firestore-type.md) | Enforce that Firestore type definitions extend Identifiable and match their folder name | ✅ | | | | | |
|
|
92
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. This improves performance by reusing previous results when the same parameters are provided, particularly useful for data fetching methods. | ✅ | | | 🔧 | | |
|
|
@@ -108,7 +110,7 @@ Or use the recommended config:
|
|
|
108
110
|
| [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. | ✅ | | | 🔧 | | |
|
|
109
111
|
| [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 | ✅ | | | | | |
|
|
110
112
|
| [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. | ✅ | | | 🔧 | | |
|
|
111
|
-
| [enforce-transform-memoization](docs/rules/enforce-transform-memoization.md) | Enforce memoization of
|
|
113
|
+
| [enforce-transform-memoization](docs/rules/enforce-transform-memoization.md) | Enforce memoization of transformValue and transformOnChange in adaptValue | ✅ | | | | | |
|
|
112
114
|
| [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. | ✅ | | | 🔧 | | |
|
|
113
115
|
| [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 | ✅ | | | 🔧 | | |
|
|
114
116
|
| [enforce-verb-noun-naming](docs/rules/enforce-verb-noun-naming.md) | Enforce verb phrases for functions and methods | ✅ | | | | | |
|
|
@@ -122,14 +124,14 @@ Or use the recommended config:
|
|
|
122
124
|
| [global-const-style](docs/rules/global-const-style.md) | Enforce UPPER_SNAKE_CASE and as const for global static constants | ✅ | | | 🔧 | | |
|
|
123
125
|
| [jsdoc-above-field](docs/rules/jsdoc-above-field.md) | Require JSDoc blocks to sit above fields instead of trailing inline so IDE hovers surface the documentation. | ✅ | | | 🔧 | | |
|
|
124
126
|
| [key-only-outermost-element](docs/rules/key-only-outermost-element.md) | Enforce that only the outermost element in list rendering has a key prop | ✅ | | | 🔧 | | |
|
|
125
|
-
| [logical-top-to-bottom-grouping](docs/rules/logical-top-to-bottom-grouping.md) |
|
|
127
|
+
| [logical-top-to-bottom-grouping](docs/rules/logical-top-to-bottom-grouping.md) | Enforce logical top-to-bottom grouping of related statements | ✅ | | | 🔧 | | |
|
|
126
128
|
| [memo-compare-deeply-complex-props](docs/rules/memo-compare-deeply-complex-props.md) | Suggest compareDeeply for memoized components that receive object/array props to avoid shallow comparison re-renders. | ✅ | | | 🔧 | | 💭 |
|
|
127
|
-
| [memo-nested-react-components](docs/rules/memo-nested-react-components.md) |
|
|
129
|
+
| [memo-nested-react-components](docs/rules/memo-nested-react-components.md) | Disallow React components defined in render bodies, hooks, or passed as props | ✅ | | | | | |
|
|
128
130
|
| [memoize-root-level-hocs](docs/rules/memoize-root-level-hocs.md) | Prevent creating Higher-Order Components at the root level of React components/hooks without wrapping them in useMemo to keep wrapped component identities stable across renders. | ✅ | | | | | |
|
|
129
131
|
| [no-always-true-false-conditions](docs/rules/no-always-true-false-conditions.md) | Detect conditions that are always truthy or always falsy | ✅ | | | | | |
|
|
130
132
|
| [no-array-length-in-deps](docs/rules/no-array-length-in-deps.md) | Detects array.length entries in React hook dependency arrays because length ignores content changes; auto-fixes by memoizing stableHash(array) with useMemo and depending on the hash instead. | ✅ | | | 🔧 | | |
|
|
131
133
|
| [no-async-array-filter](docs/rules/no-async-array-filter.md) | Disallow async callbacks in Array.filter(). Async predicates return Promises that are always truthy to the filter, so no element is ever removed. Resolve async checks first (Promise.all + map) or use a synchronous predicate to decide which items to keep. | ✅ | | | | | |
|
|
132
|
-
| [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow async callbacks
|
|
134
|
+
| [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow async callbacks to Array.forEach | ✅ | | | | | |
|
|
133
135
|
| [no-circular-references](docs/rules/no-circular-references.md) | Disallow circular references in objects | ✅ | | | | | |
|
|
134
136
|
| [no-class-instance-destructuring](docs/rules/no-class-instance-destructuring.md) | Disallow destructuring of class instances to prevent loss of `this` context | ✅ | | | 🔧 | | |
|
|
135
137
|
| [no-complex-cloud-params](docs/rules/no-complex-cloud-params.md) | Disallow passing complex objects to cloud functions | ✅ | | | | | |
|
|
@@ -195,11 +197,9 @@ Or use the recommended config:
|
|
|
195
197
|
| [prefer-fragment-shorthand](docs/rules/prefer-fragment-shorthand.md) | Prefer <> shorthand for <React.Fragment> | ✅ | | | 🔧 | | |
|
|
196
198
|
| [prefer-getter-over-parameterless-method](docs/rules/prefer-getter-over-parameterless-method.md) | Enforce getter syntax for synchronous parameterless methods that return values, improving semantic clarity and avoiding accidental method invocation without parentheses. | ✅ | | | 🔧 | | |
|
|
197
199
|
| [prefer-global-router-state-key](docs/rules/prefer-global-router-state-key.md) | Enforce using centralized router state key constants from queryKeys.ts for useRouterState key parameter | ✅ | | | 🔧 | | |
|
|
198
|
-
| [prefer-memoized-props](docs/rules/prefer-memoized-props.md) | Require memoizing reference props (objects, arrays, functions) inside React.memo components while avoiding unnecessary useMemo for pass-through values. | ✅ | | | | | |
|
|
199
200
|
| [prefer-next-dynamic](docs/rules/prefer-next-dynamic.md) | Prefer Next.js dynamic() over custom useDynamic() for component imports | ✅ | | | 🔧 | | |
|
|
200
201
|
| [prefer-nullish-coalescing-boolean-props](docs/rules/prefer-nullish-coalescing-boolean-props.md) | Prefer nullish coalescing over logical OR, but allow logical OR in boolean contexts | ✅ | | | 🔧 | | |
|
|
201
|
-
| [prefer-
|
|
202
|
-
| [prefer-params-over-parent-id](docs/rules/prefer-params-over-parent-id.md) | Prefer handler params for parent IDs instead of traversing ref.parent.id so Firebase triggers stay aligned with path templates and type-safe. | ✅ | | | 🔧 | | |
|
|
202
|
+
| [prefer-params-over-parent-id](docs/rules/prefer-params-over-parent-id.md) | Prefer event.params over ref.parent.id for type-safe Firebase trigger paths. | ✅ | | | 🔧 | | |
|
|
203
203
|
| [prefer-settings-object](docs/rules/prefer-settings-object.md) | Enforce using a settings object for functions with multiple parameters | ✅ | | | 🔧 | | |
|
|
204
204
|
| [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
205
|
| [prefer-type-over-interface](docs/rules/prefer-type-over-interface.md) | Prefer using type alias over interface | ✅ | | | 🔧 | | |
|
|
@@ -208,7 +208,7 @@ Or use the recommended config:
|
|
|
208
208
|
| [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
209
|
| [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
210
|
| [prefer-utility-function-over-private-static](docs/rules/prefer-utility-function-over-private-static.md) | Enforce abstraction of private static methods into utility functions | ✅ | | | | | |
|
|
211
|
-
| [prevent-children-clobber](docs/rules/prevent-children-clobber.md) | Prevent JSX spreads from silently discarding props.children
|
|
211
|
+
| [prevent-children-clobber](docs/rules/prevent-children-clobber.md) | Prevent JSX spreads from silently discarding props.children | ✅ | | | | | |
|
|
212
212
|
| [react-memoize-literals](docs/rules/react-memoize-literals.md) | Detect object, array, and function literals created in React components or hooks that create new references every render. Prefer memoized values (useMemo/useCallback) or module-level constants to keep referential stability. | ✅ | | | | 💡 | |
|
|
213
213
|
| [react-usememo-should-be-component](docs/rules/react-usememo-should-be-component.md) | Enforce that useMemo hooks explicitly returning JSX should be abstracted into separate React components | ✅ | | | | | |
|
|
214
214
|
| [require-dynamic-firebase-imports](docs/rules/require-dynamic-firebase-imports.md) | Enforce dynamic imports for Firebase dependencies | ✅ | | | 🔧 | | |
|
|
@@ -217,8 +217,7 @@ Or use the recommended config:
|
|
|
217
217
|
| [require-https-error-cause](docs/rules/require-https-error-cause.md) | Ensure HttpsError calls inside catch blocks pass the caught error as the fourth "cause" argument to preserve stack traces for monitoring. | ✅ | | | | | |
|
|
218
218
|
| [require-image-optimized](docs/rules/require-image-optimized.md) | Enforce using ImageOptimized component instead of next/image or img tags | ✅ | | | 🔧 | | |
|
|
219
219
|
| [require-memo](docs/rules/require-memo.md) | React components must be memoized | ✅ | | | 🔧 | | |
|
|
220
|
-
| [require-memoize-jsx-returners](docs/rules/require-memoize-jsx-returners.md) |
|
|
221
|
-
| [require-usememo-object-literals](docs/rules/require-usememo-object-literals.md) | Enforce using useMemo for inline object/array literals passed as props to JSX components to prevent unnecessary re-renders. When object/array literals are defined inline in JSX, they create new references on every render, causing child components to re-render even if the values haven't changed. Wrap them in useMemo to maintain referential equality. | ✅ | | | | | |
|
|
220
|
+
| [require-memoize-jsx-returners](docs/rules/require-memoize-jsx-returners.md) | Require @Memoize() decorator on instance members that return JSX or JSX factories | ✅ | | | 🔧 | | |
|
|
222
221
|
| [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 | ✅ | | | | | |
|
|
223
222
|
| [sync-onwrite-name-func](docs/rules/sync-onwrite-name-func.md) | Ensure that the name field matches the func field in onWrite handlers | ✅ | | | 🔧 | | |
|
|
224
223
|
| [test-file-location-enforcement](docs/rules/test-file-location-enforcement.md) | Enforce colocating *.test.ts or *.test.tsx files with the code they cover. | ✅ | | | | | |
|
package/lib/index.js
CHANGED
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const enforce_date_ttime_1 = require("./rules/enforce-date-ttime");
|
|
7
|
+
const enforce_f_extension_for_entry_points_1 = require("./rules/enforce-f-extension-for-entry-points");
|
|
6
8
|
const array_methods_this_context_1 = require("./rules/array-methods-this-context");
|
|
7
9
|
const class_methods_read_top_to_bottom_1 = require("./rules/class-methods-read-top-to-bottom");
|
|
8
10
|
const consistent_callback_naming_1 = __importDefault(require("./rules/consistent-callback-naming"));
|
|
@@ -15,7 +17,7 @@ const enforce_callback_memo_1 = __importDefault(require("./rules/enforce-callbac
|
|
|
15
17
|
const react_memoize_literals_1 = require("./rules/react-memoize-literals");
|
|
16
18
|
const enforce_callable_types_1 = require("./rules/enforce-callable-types");
|
|
17
19
|
const enforce_console_error_1 = require("./rules/enforce-console-error");
|
|
18
|
-
const enforce_dynamic_firebase_imports_1 = require("./rules/enforce-dynamic-firebase-imports");
|
|
20
|
+
const enforce_dynamic_firebase_imports_1 = __importDefault(require("./rules/enforce-dynamic-firebase-imports"));
|
|
19
21
|
const enforce_mui_rounded_icons_1 = require("./rules/enforce-mui-rounded-icons");
|
|
20
22
|
const enforce_querykey_ts_1 = require("./rules/enforce-querykey-ts");
|
|
21
23
|
const enforce_react_type_naming_1 = require("./rules/enforce-react-type-naming");
|
|
@@ -39,7 +41,6 @@ const prefer_fragment_shorthand_1 = require("./rules/prefer-fragment-shorthand")
|
|
|
39
41
|
const prefer_getter_over_parameterless_method_1 = require("./rules/prefer-getter-over-parameterless-method");
|
|
40
42
|
const prefer_type_over_interface_1 = require("./rules/prefer-type-over-interface");
|
|
41
43
|
const prefer_type_alias_over_typeof_constant_1 = require("./rules/prefer-type-alias-over-typeof-constant");
|
|
42
|
-
const prefer_memoized_props_1 = require("./rules/prefer-memoized-props");
|
|
43
44
|
const require_memo_1 = require("./rules/require-memo");
|
|
44
45
|
const require_memoize_jsx_returners_1 = require("./rules/require-memoize-jsx-returners");
|
|
45
46
|
const no_jsx_whitespace_literal_1 = require("./rules/no-jsx-whitespace-literal");
|
|
@@ -48,7 +49,6 @@ const require_https_error_1 = __importDefault(require("./rules/require-https-err
|
|
|
48
49
|
const require_https_error_cause_1 = require("./rules/require-https-error-cause");
|
|
49
50
|
const use_custom_router_1 = require("./rules/use-custom-router");
|
|
50
51
|
const require_image_optimized_1 = __importDefault(require("./rules/require-image-optimized"));
|
|
51
|
-
const require_usememo_object_literals_1 = require("./rules/require-usememo-object-literals");
|
|
52
52
|
const enforce_safe_stringify_1 = require("./rules/enforce-safe-stringify");
|
|
53
53
|
const enforce_storage_context_1 = require("./rules/enforce-storage-context");
|
|
54
54
|
const avoid_utils_directory_1 = require("./rules/avoid-utils-directory");
|
|
@@ -136,7 +136,6 @@ const prefer_block_comments_for_declarations_1 = require("./rules/prefer-block-c
|
|
|
136
136
|
const no_undefined_null_passthrough_1 = require("./rules/no-undefined-null-passthrough");
|
|
137
137
|
const firestore_transaction_reads_before_writes_1 = require("./rules/firestore-transaction-reads-before-writes");
|
|
138
138
|
const enforce_typescript_markdown_code_blocks_1 = require("./rules/enforce-typescript-markdown-code-blocks");
|
|
139
|
-
const prefer_nullish_coalescing_override_1 = require("./rules/prefer-nullish-coalescing-override");
|
|
140
139
|
const prefer_nullish_coalescing_boolean_props_1 = require("./rules/prefer-nullish-coalescing-boolean-props");
|
|
141
140
|
const no_restricted_properties_fix_1 = require("./rules/no-restricted-properties-fix");
|
|
142
141
|
const no_excessive_parent_chain_1 = require("./rules/no-excessive-parent-chain");
|
|
@@ -195,7 +194,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
|
|
|
195
194
|
module.exports = {
|
|
196
195
|
meta: {
|
|
197
196
|
name: '@blumintinc/eslint-plugin-blumint',
|
|
198
|
-
version: '1.
|
|
197
|
+
version: '1.15.0',
|
|
199
198
|
},
|
|
200
199
|
parseOptions: {
|
|
201
200
|
ecmaVersion: 2020,
|
|
@@ -204,10 +203,10 @@ module.exports = {
|
|
|
204
203
|
recommended: {
|
|
205
204
|
plugins: ['@blumintinc/blumint'],
|
|
206
205
|
rules: {
|
|
206
|
+
'@blumintinc/blumint/enforce-f-extension-for-entry-points': 'error',
|
|
207
207
|
'@blumintinc/blumint/firestore-transaction-reads-before-writes': 'error',
|
|
208
208
|
// Override the @typescript-eslint/prefer-nullish-coalescing rule
|
|
209
209
|
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
|
210
|
-
'@blumintinc/blumint/prefer-nullish-coalescing-override': 'error',
|
|
211
210
|
'@blumintinc/blumint/prefer-block-comments-for-declarations': 'error',
|
|
212
211
|
'@blumintinc/blumint/key-only-outermost-element': 'error',
|
|
213
212
|
'@blumintinc/blumint/logical-top-to-bottom-grouping': 'error',
|
|
@@ -262,7 +261,6 @@ module.exports = {
|
|
|
262
261
|
'@blumintinc/blumint/require-https-error-cause': 'error',
|
|
263
262
|
'@blumintinc/blumint/use-custom-router': 'error',
|
|
264
263
|
'@blumintinc/blumint/require-image-optimized': 'error',
|
|
265
|
-
'@blumintinc/blumint/require-usememo-object-literals': 'error',
|
|
266
264
|
'@blumintinc/blumint/memoize-root-level-hocs': 'error',
|
|
267
265
|
'@blumintinc/blumint/enforce-safe-stringify': 'error',
|
|
268
266
|
'@blumintinc/blumint/enforce-early-destructuring': 'error',
|
|
@@ -324,7 +322,6 @@ module.exports = {
|
|
|
324
322
|
'@blumintinc/blumint/enforce-props-naming-consistency': 'error',
|
|
325
323
|
'@blumintinc/blumint/prefer-global-router-state-key': 'error',
|
|
326
324
|
'@blumintinc/blumint/prefer-usememo-over-useeffect-usestate': 'error',
|
|
327
|
-
'@blumintinc/blumint/prefer-memoized-props': 'error',
|
|
328
325
|
'@blumintinc/blumint/enforce-dynamic-imports': 'error',
|
|
329
326
|
'@blumintinc/blumint/ensure-pointer-events-none': 'error',
|
|
330
327
|
'@blumintinc/blumint/no-object-values-on-strings': 'error',
|
|
@@ -378,6 +375,12 @@ module.exports = {
|
|
|
378
375
|
* requires an additional override.
|
|
379
376
|
*/
|
|
380
377
|
overrides: [
|
|
378
|
+
{
|
|
379
|
+
files: ['src/**/*.{ts,tsx}'],
|
|
380
|
+
rules: {
|
|
381
|
+
'@blumintinc/blumint/enforce-date-ttime': 'error',
|
|
382
|
+
},
|
|
383
|
+
},
|
|
381
384
|
{
|
|
382
385
|
files: ['functions/*.f.ts'],
|
|
383
386
|
rules: {
|
|
@@ -448,8 +451,9 @@ module.exports = {
|
|
|
448
451
|
},
|
|
449
452
|
},
|
|
450
453
|
rules: {
|
|
454
|
+
'enforce-date-ttime': enforce_date_ttime_1.enforceDateTTime,
|
|
455
|
+
'enforce-f-extension-for-entry-points': enforce_f_extension_for_entry_points_1.enforceFExtensionForEntryPoints,
|
|
451
456
|
'firestore-transaction-reads-before-writes': firestore_transaction_reads_before_writes_1.firestoreTransactionReadsBeforeWrites,
|
|
452
|
-
'prefer-nullish-coalescing-override': prefer_nullish_coalescing_override_1.preferNullishCoalescingOverride,
|
|
453
457
|
'no-restricted-properties-fix': no_restricted_properties_fix_1.noRestrictedPropertiesFix,
|
|
454
458
|
'no-excessive-parent-chain': no_excessive_parent_chain_1.noExcessiveParentChain,
|
|
455
459
|
'prefer-document-flattening': prefer_document_flattening_1.preferDocumentFlattening,
|
|
@@ -469,7 +473,7 @@ module.exports = {
|
|
|
469
473
|
'enforce-firestore-rules-get-access': enforce_firestore_rules_get_access_1.enforceFirestoreRulesGetAccess,
|
|
470
474
|
'enforce-callable-types': enforce_callable_types_1.enforceCallableTypes,
|
|
471
475
|
'enforce-console-error': enforce_console_error_1.enforceConsoleError,
|
|
472
|
-
'enforce-dynamic-firebase-imports': enforce_dynamic_firebase_imports_1.
|
|
476
|
+
'enforce-dynamic-firebase-imports': enforce_dynamic_firebase_imports_1.default,
|
|
473
477
|
'enforce-mui-rounded-icons': enforce_mui_rounded_icons_1.enforceMuiRoundedIcons,
|
|
474
478
|
'export-if-in-doubt': export_if_in_doubt_1.exportIfInDoubt,
|
|
475
479
|
'extract-global-constants': extract_global_constants_1.extractGlobalConstants,
|
|
@@ -501,7 +505,6 @@ module.exports = {
|
|
|
501
505
|
'require-https-error-cause': require_https_error_cause_1.requireHttpsErrorCause,
|
|
502
506
|
'use-custom-router': use_custom_router_1.useCustomRouter,
|
|
503
507
|
'require-image-optimized': require_image_optimized_1.default,
|
|
504
|
-
'require-usememo-object-literals': require_usememo_object_literals_1.requireUseMemoObjectLiterals,
|
|
505
508
|
'memoize-root-level-hocs': memoize_root_level_hocs_1.memoizeRootLevelHocs,
|
|
506
509
|
'enforce-safe-stringify': enforce_safe_stringify_1.enforceStableStringify,
|
|
507
510
|
'enforce-early-destructuring': enforce_early_destructuring_1.enforceEarlyDestructuring,
|
|
@@ -602,7 +605,6 @@ module.exports = {
|
|
|
602
605
|
'no-redundant-usecallback-wrapper': no_redundant_usecallback_wrapper_1.default,
|
|
603
606
|
'no-res-error-status-in-onrequest': no_res_error_status_in_onrequest_1.requireHttpsErrorInOnRequestHandlers,
|
|
604
607
|
'no-array-length-in-deps': no_array_length_in_deps_1.noArrayLengthInDeps,
|
|
605
|
-
'prefer-memoized-props': prefer_memoized_props_1.preferMemoizedProps,
|
|
606
608
|
'prefer-use-deep-compare-memo': prefer_use_deep_compare_memo_1.preferUseDeepCompareMemo,
|
|
607
609
|
'memo-nested-react-components': memo_nested_react_components_1.memoNestedReactComponents,
|
|
608
610
|
'memo-compare-deeply-complex-props': memo_compare_deeply_complex_props_1.memoCompareDeeplyComplexProps,
|
|
@@ -37,8 +37,12 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
37
37
|
fixable: 'code',
|
|
38
38
|
schema: [],
|
|
39
39
|
messages: {
|
|
40
|
-
callbackPropPrefix: 'Callback
|
|
41
|
-
|
|
40
|
+
callbackPropPrefix: 'Callback prop "{{propName}}" is a function but lacks the "on" prefix. ' +
|
|
41
|
+
'Consistent "on" prefixes signal event handlers to consumers and distinguish callbacks from data props. ' +
|
|
42
|
+
'Rename to "on{{eventName}}".',
|
|
43
|
+
callbackFunctionPrefix: 'Function "{{functionName}}" uses the "handle" prefix. ' +
|
|
44
|
+
'The "handle" prefix is redundant and less descriptive than action-oriented verb phrases. ' +
|
|
45
|
+
'Rename using a descriptive verb (e.g., click instead of handleClick).',
|
|
42
46
|
},
|
|
43
47
|
},
|
|
44
48
|
defaultOptions: [],
|
|
@@ -91,6 +95,28 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
91
95
|
const type = checker.getTypeAtLocation(tsNode);
|
|
92
96
|
return type.getCallSignatures().length > 0;
|
|
93
97
|
}
|
|
98
|
+
function isRenderFunction(node) {
|
|
99
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
100
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
101
|
+
const signatures = type.getCallSignatures();
|
|
102
|
+
if (signatures.length === 0)
|
|
103
|
+
return false;
|
|
104
|
+
const isReactType = (t) => {
|
|
105
|
+
const typeStr = checker.typeToString(t);
|
|
106
|
+
return (typeStr.includes('JSX.Element') ||
|
|
107
|
+
typeStr.includes('ReactElement') ||
|
|
108
|
+
typeStr.includes('ReactNode'));
|
|
109
|
+
};
|
|
110
|
+
return signatures.some((signature) => {
|
|
111
|
+
const returnType = checker.getReturnTypeOfSignature(signature);
|
|
112
|
+
if (isReactType(returnType))
|
|
113
|
+
return true;
|
|
114
|
+
if (returnType.isUnion()) {
|
|
115
|
+
return returnType.types.some((t) => isReactType(t));
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
94
120
|
return {
|
|
95
121
|
// Check JSX attributes for callback props
|
|
96
122
|
JSXAttribute(node) {
|
|
@@ -137,13 +163,19 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
137
163
|
if (isFunctionType(node.value.expression) &&
|
|
138
164
|
propName &&
|
|
139
165
|
!propName.startsWith('on') &&
|
|
166
|
+
!propName.startsWith('render') &&
|
|
167
|
+
!isRenderFunction(node.value.expression) &&
|
|
140
168
|
!isReactComponentType(node.value.expression)) {
|
|
169
|
+
const eventName = propName.charAt(0).toUpperCase() + propName.slice(1);
|
|
141
170
|
context.report({
|
|
142
171
|
node,
|
|
143
172
|
messageId: 'callbackPropPrefix',
|
|
173
|
+
data: {
|
|
174
|
+
propName,
|
|
175
|
+
eventName,
|
|
176
|
+
},
|
|
144
177
|
fix(fixer) {
|
|
145
178
|
// Convert camelCase to PascalCase for the event name
|
|
146
|
-
const eventName = propName.charAt(0).toUpperCase() + propName.slice(1);
|
|
147
179
|
return fixer.replaceText(node.name, `on${eventName}`);
|
|
148
180
|
},
|
|
149
181
|
});
|
|
@@ -159,6 +191,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
159
191
|
context.report({
|
|
160
192
|
node,
|
|
161
193
|
messageId: 'callbackFunctionPrefix',
|
|
194
|
+
data: { functionName },
|
|
162
195
|
});
|
|
163
196
|
return;
|
|
164
197
|
}
|
|
@@ -169,6 +202,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
169
202
|
context.report({
|
|
170
203
|
node,
|
|
171
204
|
messageId: 'callbackFunctionPrefix',
|
|
205
|
+
data: { functionName },
|
|
172
206
|
});
|
|
173
207
|
return;
|
|
174
208
|
}
|
|
@@ -220,6 +254,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
220
254
|
context.report({
|
|
221
255
|
node,
|
|
222
256
|
messageId: 'callbackFunctionPrefix',
|
|
257
|
+
data: { functionName },
|
|
223
258
|
fix(fixer) {
|
|
224
259
|
// Remove 'handle' prefix and convert first character to lowercase
|
|
225
260
|
const newName = functionName.slice(6).charAt(0).toLowerCase() +
|
|
@@ -248,6 +283,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
248
283
|
context.report({
|
|
249
284
|
node: node.key,
|
|
250
285
|
messageId: 'callbackFunctionPrefix',
|
|
286
|
+
data: { functionName: name },
|
|
251
287
|
});
|
|
252
288
|
return;
|
|
253
289
|
}
|
|
@@ -256,12 +292,14 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
256
292
|
context.report({
|
|
257
293
|
node: node.key,
|
|
258
294
|
messageId: 'callbackFunctionPrefix',
|
|
295
|
+
data: { functionName: name },
|
|
259
296
|
});
|
|
260
297
|
return;
|
|
261
298
|
}
|
|
262
299
|
context.report({
|
|
263
300
|
node: node.key,
|
|
264
301
|
messageId: 'callbackFunctionPrefix',
|
|
302
|
+
data: { functionName: name },
|
|
265
303
|
fix(fixer) {
|
|
266
304
|
// Remove 'handle' prefix and convert first character to lowercase
|
|
267
305
|
const newName = name.slice(6).charAt(0).toLowerCase() + name.slice(7);
|
|
@@ -277,6 +315,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
277
315
|
context.report({
|
|
278
316
|
node,
|
|
279
317
|
messageId: 'callbackFunctionPrefix',
|
|
318
|
+
data: { functionName: node.parameter.name },
|
|
280
319
|
});
|
|
281
320
|
}
|
|
282
321
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
type MessageIds = 'dynamicHttpsErrors' | 'missingThirdArgument';
|
|
2
|
+
type MessageIds = 'dynamicHttpsErrors' | 'missingThirdArgument' | 'missingDetailsProperty' | 'missingDetailsDueToSpread' | 'unexpectedExtraArgumentForObjectCall';
|
|
3
3
|
export declare const dynamicHttpsErrors: TSESLint.RuleModule<MessageIds, never[]>;
|
|
4
4
|
export {};
|
|
@@ -4,17 +4,21 @@ exports.dynamicHttpsErrors = void 0;
|
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
5
|
const utils_1 = require("@typescript-eslint/utils");
|
|
6
6
|
const isHttpsErrorCall = (callee) => {
|
|
7
|
-
if (callee.type ===
|
|
8
|
-
return (callee.object.type ===
|
|
7
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
8
|
+
return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
9
9
|
callee.object.name === 'https' &&
|
|
10
|
-
callee.property.type ===
|
|
10
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
11
11
|
callee.property.name === 'HttpsError');
|
|
12
12
|
}
|
|
13
|
-
else if (callee.type ===
|
|
13
|
+
else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
14
14
|
return callee.name === 'HttpsError';
|
|
15
15
|
}
|
|
16
16
|
return false;
|
|
17
17
|
};
|
|
18
|
+
const findPropertyByName = (properties, name) => properties.find((p) => p.type === utils_1.AST_NODE_TYPES.Property &&
|
|
19
|
+
!p.computed &&
|
|
20
|
+
((p.key.type === utils_1.AST_NODE_TYPES.Identifier && p.key.name === name) ||
|
|
21
|
+
(p.key.type === utils_1.AST_NODE_TYPES.Literal && p.key.value === name)));
|
|
18
22
|
exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
19
23
|
name: 'dynamic-https-errors',
|
|
20
24
|
meta: {
|
|
@@ -27,57 +31,144 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
|
|
|
27
31
|
messages: {
|
|
28
32
|
dynamicHttpsErrors: 'The HttpsError message (second argument) must stay static. Template expressions here change the hashed message and explode the number of error ids for the same failure. Keep this argument constant and move interpolated values into the third "details" argument so monitoring groups the error while still capturing request context.',
|
|
29
33
|
missingThirdArgument: 'HttpsError calls must include a third "details" argument. The message (second argument) is hashed into a stable identifier, so omitting details leaves errors hard to debug and encourages packing variables into the hashed message. Provide a third argument with the request-specific context (object or string) to keep identifiers stable and diagnostics useful.',
|
|
34
|
+
missingDetailsProperty: 'HttpsError calls must include a "details" property. The message is hashed into a stable identifier, so omitting details leaves errors hard to debug and encourages packing variables into the hashed message. Provide a details property with the request-specific context (object or string) to keep identifiers stable and diagnostics useful.',
|
|
35
|
+
missingDetailsDueToSpread: 'HttpsError calls must include a "details" property. This call uses an object spread, which prevents static verification that "details" is present. Ensure the spread object contains "details" or provide it explicitly to keep identifiers stable and diagnostics useful.',
|
|
36
|
+
unexpectedExtraArgumentForObjectCall: 'Object-based HttpsError calls must have exactly one argument containing code, message, and details properties. Remove extra arguments or use the positional signature (code, message, details).',
|
|
30
37
|
},
|
|
31
38
|
},
|
|
32
39
|
defaultOptions: [],
|
|
33
40
|
create(context) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
// Only string concatenation with "+" can be static; all other operators
|
|
52
|
+
// are treated as dynamic to avoid hashing non-literal message content.
|
|
53
|
+
const isDynamicBinaryExpression = (expression) => {
|
|
54
|
+
if (expression.operator !== '+')
|
|
55
|
+
return true;
|
|
56
|
+
const isStaticLiteral = (expr) => {
|
|
57
|
+
const inner = unwrapTSAssertions(expr);
|
|
58
|
+
return (inner.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
59
|
+
typeof inner.value === 'string');
|
|
60
|
+
};
|
|
61
|
+
const isSafe = (expr) => {
|
|
62
|
+
if (expr.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const inner = unwrapTSAssertions(expr);
|
|
66
|
+
if (inner.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
67
|
+
return !isDynamicBinaryExpression(inner);
|
|
68
|
+
}
|
|
69
|
+
return isStaticLiteral(inner);
|
|
70
|
+
};
|
|
71
|
+
return !(isSafe(expression.left) && isSafe(expression.right));
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Determines if a node should be validated for staticness.
|
|
75
|
+
*
|
|
76
|
+
* Pragmatic Exception: Identifier, MemberExpression, and ChainExpression nodes
|
|
77
|
+
* (e.g., `props.message`, `props?.message`, `ERROR_MSG`) are excluded from
|
|
78
|
+
* staticness validation. While they can be dynamic and may affect message
|
|
79
|
+
* stability, they are permitted to support common React/props patterns and
|
|
80
|
+
* constants, preserving developer ergonomics as an intentional trade-off.
|
|
81
|
+
*/
|
|
82
|
+
const shouldValidateForStaticness = (node) => {
|
|
83
|
+
return (node.type !== utils_1.AST_NODE_TYPES.Identifier &&
|
|
84
|
+
node.type !== utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
85
|
+
node.type !== utils_1.AST_NODE_TYPES.ChainExpression &&
|
|
86
|
+
node.type !== utils_1.AST_NODE_TYPES.SpreadElement &&
|
|
87
|
+
// Explicitly exclude patterns and other non-Expression nodes that can appear in Property.value
|
|
88
|
+
node.type !== utils_1.AST_NODE_TYPES.ArrayPattern &&
|
|
89
|
+
node.type !== utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
90
|
+
node.type !== utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
91
|
+
node.type !== utils_1.AST_NODE_TYPES.RestElement &&
|
|
92
|
+
node.type !== utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression);
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Checks if the message node is static.
|
|
96
|
+
*
|
|
97
|
+
* A message is considered static if it's a Literal (string), a TemplateLiteral with no expressions,
|
|
98
|
+
* or a BinaryExpression (string concatenation with '+') where all parts are static.
|
|
99
|
+
*
|
|
100
|
+
* This function reports `dynamicHttpsErrors` for all other expression types (CallExpression,
|
|
101
|
+
* ConditionalExpression, etc.) that reach it, except for those explicitly excluded by
|
|
102
|
+
* `shouldValidateForStaticness`.
|
|
103
|
+
*/
|
|
104
|
+
const checkMessageIsStatic = (messageNode) => {
|
|
105
|
+
const currentNode = unwrapTSAssertions(messageNode);
|
|
106
|
+
if (currentNode.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (currentNode.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
110
|
+
currentNode.expressions.length === 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (currentNode.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
114
|
+
if (isDynamicBinaryExpression(currentNode)) {
|
|
39
115
|
context.report({
|
|
40
|
-
node,
|
|
41
|
-
messageId: '
|
|
116
|
+
node: messageNode,
|
|
117
|
+
messageId: 'dynamicHttpsErrors',
|
|
42
118
|
});
|
|
43
119
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Catch-all for other dynamic forms (CallExpression, ConditionalExpression, etc.)
|
|
123
|
+
context.report({
|
|
124
|
+
node: messageNode,
|
|
125
|
+
messageId: 'dynamicHttpsErrors',
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const checkForHttpsError = (node) => {
|
|
129
|
+
const callee = node.callee;
|
|
130
|
+
if (!isHttpsErrorCall(callee))
|
|
131
|
+
return;
|
|
132
|
+
// Signature 1: Object-based constructor (HttpsErrorProps)
|
|
133
|
+
if (node.arguments.length >= 1 &&
|
|
134
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
135
|
+
if (node.arguments.length > 1) {
|
|
50
136
|
context.report({
|
|
51
|
-
node
|
|
52
|
-
messageId: '
|
|
137
|
+
node,
|
|
138
|
+
messageId: 'unexpectedExtraArgumentForObjectCall',
|
|
53
139
|
});
|
|
54
140
|
return;
|
|
55
141
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const isStaticLiteral = (expr) => expr.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
62
|
-
typeof expr.value === 'string';
|
|
63
|
-
const isSafe = (expr) => {
|
|
64
|
-
if (expr.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
if (expr.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
68
|
-
return !isDynamicBinaryExpression(expr);
|
|
69
|
-
}
|
|
70
|
-
return isStaticLiteral(expr);
|
|
71
|
-
};
|
|
72
|
-
return !(isSafe(expression.left) && isSafe(expression.right));
|
|
73
|
-
};
|
|
74
|
-
if (secondArg.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
|
|
75
|
-
isDynamicBinaryExpression(secondArg)) {
|
|
142
|
+
const props = node.arguments[0];
|
|
143
|
+
const messageProperty = findPropertyByName(props.properties, 'message');
|
|
144
|
+
const detailsProperty = findPropertyByName(props.properties, 'details');
|
|
145
|
+
if (!detailsProperty) {
|
|
146
|
+
const hasSpread = props.properties.some((p) => p.type === utils_1.AST_NODE_TYPES.SpreadElement);
|
|
76
147
|
context.report({
|
|
77
|
-
node
|
|
78
|
-
messageId:
|
|
148
|
+
node,
|
|
149
|
+
messageId: hasSpread
|
|
150
|
+
? 'missingDetailsDueToSpread'
|
|
151
|
+
: 'missingDetailsProperty',
|
|
79
152
|
});
|
|
80
153
|
}
|
|
154
|
+
if (messageProperty &&
|
|
155
|
+
shouldValidateForStaticness(messageProperty.value)) {
|
|
156
|
+
checkMessageIsStatic(messageProperty.value);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// Signature 2: Positional arguments (code, message, details)
|
|
161
|
+
// Check for missing third argument
|
|
162
|
+
if (node.arguments.length < 3) {
|
|
163
|
+
context.report({
|
|
164
|
+
node,
|
|
165
|
+
messageId: 'missingThirdArgument',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Check for dynamic content in second argument
|
|
169
|
+
const secondArg = node.arguments[1];
|
|
170
|
+
if (secondArg && shouldValidateForStaticness(secondArg)) {
|
|
171
|
+
checkMessageIsStatic(secondArg);
|
|
81
172
|
}
|
|
82
173
|
};
|
|
83
174
|
return {
|