@blumintinc/eslint-plugin-blumint 1.3.2 → 1.5.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 +41 -40
- package/lib/index.js +37 -1
- package/lib/rules/array-methods-this-context.js +2 -2
- package/lib/rules/enforce-callback-memo.js +3 -3
- package/lib/rules/enforce-dynamic-firebase-imports.js +2 -2
- package/lib/rules/enforce-exported-function-types.js +88 -85
- package/lib/rules/enforce-firestore-doc-ref-generic.js +7 -2
- package/lib/rules/enforce-firestore-path-utils.js +2 -2
- package/lib/rules/enforce-firestore-set-merge.js +45 -8
- package/lib/rules/enforce-identifiable-firestore-type.js +2 -2
- package/lib/rules/enforce-memoize-async.js +2 -2
- package/lib/rules/enforce-mock-firestore.js +3 -3
- package/lib/rules/enforce-realtimedb-path-utils.js +1 -1
- package/lib/rules/enforce-safe-stringify.js +2 -2
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-verb-noun-naming.js +41 -7
- package/lib/rules/no-async-array-filter.js +2 -2
- package/lib/rules/no-async-foreach.js +1 -1
- package/lib/rules/no-class-instance-destructuring.d.ts +1 -0
- package/lib/rules/no-class-instance-destructuring.js +95 -0
- package/lib/rules/no-compositing-layer-props.js +1 -1
- package/lib/rules/no-conditional-literals-in-jsx.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +1 -1
- package/lib/rules/no-explicit-return-type.d.ts +3 -1
- package/lib/rules/no-explicit-return-type.js +30 -17
- package/lib/rules/no-filter-without-return.js +1 -1
- package/lib/rules/no-misused-switch-case.js +2 -2
- package/lib/rules/no-redundant-param-types.d.ts +2 -0
- package/lib/rules/no-redundant-param-types.js +129 -0
- package/lib/rules/no-unused-props.js +1 -1
- package/lib/rules/no-useless-fragment.js +1 -1
- package/lib/rules/prefer-destructuring-no-class.d.ts +8 -0
- package/lib/rules/prefer-destructuring-no-class.js +200 -0
- package/lib/rules/require-usememo-object-literals.js +1 -1
- package/lib/rules/semantic-function-prefixes.js +43 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -52,47 +52,48 @@ Or use the recommended config:
|
|
|
52
52
|
<!-- begin auto-generated rules list -->
|
|
53
53
|
|
|
54
54
|
💼 Configurations enabled in.\
|
|
55
|
-
⚠️ Configurations set to warn in.\
|
|
56
55
|
✅ Set in the `recommended` configuration.\
|
|
57
56
|
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
|
58
|
-
💭 Requires type information.
|
|
59
|
-
|
|
60
|
-
| Name | Description
|
|
61
|
-
| :--------------------------------------------------------------------------------------- |
|
|
62
|
-
| [array-methods-this-context](docs/rules/array-methods-this-context.md) | Prevent misuse of Array methods in OOP
|
|
63
|
-
| [avoid-utils-directory](docs/rules/avoid-utils-directory.md) | Enforce using util/ instead of utils/ directory
|
|
64
|
-
| [class-methods-read-top-to-bottom](docs/rules/class-methods-read-top-to-bottom.md) | Ensures classes read linearly from top to bottom.
|
|
65
|
-
| [consistent-callback-naming](docs/rules/consistent-callback-naming.md) | Enforce consistent naming conventions for callback props and functions
|
|
66
|
-
| [dynamic-https-errors](docs/rules/dynamic-https-errors.md) | Dynamic error details should only be in the third argument of the HttpsError constructor. The second argument is hashed to produce a unique id.
|
|
67
|
-
| [enforce-callable-types](docs/rules/enforce-callable-types.md) | Enforce Params and Response type exports in callable functions
|
|
68
|
-
| [enforce-callback-memo](docs/rules/enforce-callback-memo.md) | Enforce useCallback or useMemo for inline functions in JSX props
|
|
69
|
-
| [enforce-dynamic-firebase-imports](docs/rules/enforce-dynamic-firebase-imports.md) | Enforce dynamic importing for modules within the firebaseCloud directory
|
|
70
|
-
| [enforce-firestore-doc-ref-generic](docs/rules/enforce-firestore-doc-ref-generic.md) | Enforce generic argument for Firestore DocumentReference
|
|
71
|
-
| [enforce-firestore-path-utils](docs/rules/enforce-firestore-path-utils.md) | Enforce usage of utility functions for Firestore paths
|
|
72
|
-
| [enforce-identifiable-firestore-type](docs/rules/enforce-identifiable-firestore-type.md) | Enforce that Firestore type definitions extend Identifiable and match their folder name
|
|
73
|
-
| [enforce-
|
|
74
|
-
| [
|
|
75
|
-
| [
|
|
76
|
-
| [
|
|
77
|
-
| [
|
|
78
|
-
| [
|
|
79
|
-
| [no-async-
|
|
80
|
-
| [no-
|
|
81
|
-
| [no-
|
|
82
|
-
| [no-
|
|
83
|
-
| [no-
|
|
84
|
-
| [no-
|
|
85
|
-
| [no-
|
|
86
|
-
| [no-
|
|
87
|
-
| [no-
|
|
88
|
-
| [no-
|
|
89
|
-
| [
|
|
90
|
-
| [prefer-
|
|
91
|
-
| [
|
|
92
|
-
| [require-
|
|
93
|
-
| [require-
|
|
94
|
-
| [require-
|
|
95
|
-
| [require-
|
|
96
|
-
| [
|
|
57
|
+
💭 Requires [type information](https://typescript-eslint.io/linting/typed-linting).
|
|
58
|
+
|
|
59
|
+
| Name | Description | 💼 | 🔧 | 💭 |
|
|
60
|
+
| :--------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
|
|
61
|
+
| [array-methods-this-context](docs/rules/array-methods-this-context.md) | Prevent misuse of Array methods in OOP | ✅ | | |
|
|
62
|
+
| [avoid-utils-directory](docs/rules/avoid-utils-directory.md) | Enforce using util/ instead of utils/ directory | ✅ | 🔧 | |
|
|
63
|
+
| [class-methods-read-top-to-bottom](docs/rules/class-methods-read-top-to-bottom.md) | Ensures classes read linearly from top to bottom. | ✅ | 🔧 | |
|
|
64
|
+
| [consistent-callback-naming](docs/rules/consistent-callback-naming.md) | Enforce consistent naming conventions for callback props and functions | ✅ | 🔧 | |
|
|
65
|
+
| [dynamic-https-errors](docs/rules/dynamic-https-errors.md) | Dynamic error details should only be in the third argument of the HttpsError constructor. The second argument is hashed to produce a unique id. | ✅ | | |
|
|
66
|
+
| [enforce-callable-types](docs/rules/enforce-callable-types.md) | Enforce Params and Response type exports in callable functions | ✅ | | |
|
|
67
|
+
| [enforce-callback-memo](docs/rules/enforce-callback-memo.md) | Enforce useCallback or useMemo for inline functions in JSX props | ✅ | | |
|
|
68
|
+
| [enforce-dynamic-firebase-imports](docs/rules/enforce-dynamic-firebase-imports.md) | Enforce dynamic importing for modules within the firebaseCloud directory | ✅ | | |
|
|
69
|
+
| [enforce-firestore-doc-ref-generic](docs/rules/enforce-firestore-doc-ref-generic.md) | Enforce generic argument for Firestore DocumentReference, CollectionReference and CollectionGroup | ✅ | | 💭 |
|
|
70
|
+
| [enforce-firestore-path-utils](docs/rules/enforce-firestore-path-utils.md) | Enforce usage of utility functions for Firestore paths | ✅ | | |
|
|
71
|
+
| [enforce-identifiable-firestore-type](docs/rules/enforce-identifiable-firestore-type.md) | Enforce that Firestore type definitions extend Identifiable and match their folder name | ✅ | | |
|
|
72
|
+
| [enforce-mock-firestore](docs/rules/enforce-mock-firestore.md) | Enforce using mockFirestore over manual Firestore mocking | ✅ | | |
|
|
73
|
+
| [enforce-safe-stringify](docs/rules/enforce-safe-stringify.md) | Enforce using safe-stable-stringify instead of JSON.stringify | ✅ | 🔧 | |
|
|
74
|
+
| [export-if-in-doubt](docs/rules/export-if-in-doubt.md) | All top-level const definitions, type definitions, and functions should be exported | | | |
|
|
75
|
+
| [extract-global-constants](docs/rules/extract-global-constants.md) | Extract static constants and functions to the global scope when possible | ✅ | | |
|
|
76
|
+
| [generic-starts-with-t](docs/rules/generic-starts-with-t.md) | Enforce TypeScript generic types to start with T | ✅ | | |
|
|
77
|
+
| [global-const-style](docs/rules/global-const-style.md) | Enforce UPPER_SNAKE_CASE and as const for global static constants | ✅ | 🔧 | |
|
|
78
|
+
| [no-async-array-filter](docs/rules/no-async-array-filter.md) | Disallow async callbacks for Array.filter | ✅ | | |
|
|
79
|
+
| [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow Array.forEach with an async callback function | ✅ | | |
|
|
80
|
+
| [no-compositing-layer-props](docs/rules/no-compositing-layer-props.md) | Warn when using CSS properties that trigger compositing layers | ✅ | | |
|
|
81
|
+
| [no-conditional-literals-in-jsx](docs/rules/no-conditional-literals-in-jsx.md) | Disallow use of conditional literals in JSX code | ✅ | | |
|
|
82
|
+
| [no-entire-object-hook-deps](docs/rules/no-entire-object-hook-deps.md) | Avoid using entire objects in React hook dependency arrays when only specific fields are used. Requires TypeScript and `parserOptions.project` to be configured. | ✅ | 🔧 | 💭 |
|
|
83
|
+
| [no-filter-without-return](docs/rules/no-filter-without-return.md) | Disallow Array.filter callbacks without an explicit return (if part of a block statement) | ✅ | | |
|
|
84
|
+
| [no-jsx-whitespace-literal](docs/rules/no-jsx-whitespace-literal.md) | Disallow the use of {" "} elements in JSX code | ✅ | | |
|
|
85
|
+
| [no-misused-switch-case](docs/rules/no-misused-switch-case.md) | Prevent misuse of logical OR in switch case statements | ✅ | | |
|
|
86
|
+
| [no-unpinned-dependencies](docs/rules/no-unpinned-dependencies.md) | Enforces pinned dependencies | ✅ | 🔧 | |
|
|
87
|
+
| [no-unused-props](docs/rules/no-unused-props.md) | Detect unused props in React component type definitions | ✅ | 🔧 | |
|
|
88
|
+
| [no-useless-fragment](docs/rules/no-useless-fragment.md) | Prevent unnecessary use of React fragments | | 🔧 | |
|
|
89
|
+
| [prefer-fragment-shorthand](docs/rules/prefer-fragment-shorthand.md) | Prefer <> shorthand for <React.Fragment> | ✅ | 🔧 | |
|
|
90
|
+
| [prefer-type-over-interface](docs/rules/prefer-type-over-interface.md) | Prefer using type alias over interface | ✅ | 🔧 | |
|
|
91
|
+
| [require-dynamic-firebase-imports](docs/rules/require-dynamic-firebase-imports.md) | Enforce dynamic imports for Firebase dependencies | ✅ | 🔧 | |
|
|
92
|
+
| [require-https-error](docs/rules/require-https-error.md) | Enforce using proprietary HttpsError instead of throw new Error or firebase-admin HttpsError in functions/src | ✅ | | |
|
|
93
|
+
| [require-image-optimized](docs/rules/require-image-optimized.md) | Enforce using ImageOptimized component instead of next/image or img tags | ✅ | 🔧 | |
|
|
94
|
+
| [require-memo](docs/rules/require-memo.md) | React components must be memoized | ✅ | 🔧 | |
|
|
95
|
+
| [require-usememo-object-literals](docs/rules/require-usememo-object-literals.md) | Enforce using useMemo for inline object literals passed as props to JSX components | ✅ | | |
|
|
96
|
+
| [semantic-function-prefixes](docs/rules/semantic-function-prefixes.md) | Enforce semantic function prefixes over generic ones like "get" and "update" | ✅ | | |
|
|
97
|
+
| [use-custom-router](docs/rules/use-custom-router.md) | Enforce using src/hooks/routing/useRouter instead of next/router | ✅ | 🔧 | |
|
|
97
98
|
|
|
98
99
|
<!-- end auto-generated rules list -->
|
package/lib/index.js
CHANGED
|
@@ -40,10 +40,22 @@ const no_compositing_layer_props_1 = require("./rules/no-compositing-layer-props
|
|
|
40
40
|
const enforce_firestore_doc_ref_generic_1 = require("./rules/enforce-firestore-doc-ref-generic");
|
|
41
41
|
const semantic_function_prefixes_1 = require("./rules/semantic-function-prefixes");
|
|
42
42
|
const enforce_mock_firestore_1 = require("./rules/enforce-mock-firestore");
|
|
43
|
+
const prefer_settings_object_1 = require("./rules/prefer-settings-object");
|
|
44
|
+
const enforce_firestore_set_merge_1 = require("./rules/enforce-firestore-set-merge");
|
|
45
|
+
const enforce_verb_noun_naming_1 = require("./rules/enforce-verb-noun-naming");
|
|
46
|
+
const no_explicit_return_type_1 = require("./rules/no-explicit-return-type");
|
|
47
|
+
const use_custom_memo_1 = require("./rules/use-custom-memo");
|
|
48
|
+
const use_custom_link_1 = require("./rules/use-custom-link");
|
|
49
|
+
const enforce_serializable_params_1 = __importDefault(require("./rules/enforce-serializable-params"));
|
|
50
|
+
const enforce_realtimedb_path_utils_1 = require("./rules/enforce-realtimedb-path-utils");
|
|
51
|
+
const enforce_memoize_async_1 = require("./rules/enforce-memoize-async");
|
|
52
|
+
const enforce_exported_function_types_1 = require("./rules/enforce-exported-function-types");
|
|
53
|
+
const no_redundant_param_types_1 = require("./rules/no-redundant-param-types");
|
|
54
|
+
const no_class_instance_destructuring_1 = require("./rules/no-class-instance-destructuring");
|
|
43
55
|
module.exports = {
|
|
44
56
|
meta: {
|
|
45
57
|
name: '@blumintinc/eslint-plugin-blumint',
|
|
46
|
-
version: '1.
|
|
58
|
+
version: '1.5.0',
|
|
47
59
|
},
|
|
48
60
|
parseOptions: {
|
|
49
61
|
ecmaVersion: 2020,
|
|
@@ -89,6 +101,18 @@ module.exports = {
|
|
|
89
101
|
'@blumintinc/blumint/enforce-firestore-doc-ref-generic': 'error',
|
|
90
102
|
'@blumintinc/blumint/semantic-function-prefixes': 'error',
|
|
91
103
|
'@blumintinc/blumint/enforce-mock-firestore': 'error',
|
|
104
|
+
'@blumintinc/blumint/prefer-settings-object': 'error',
|
|
105
|
+
'@blumintinc/blumint/enforce-firestore-set-merge': 'error',
|
|
106
|
+
'@blumintinc/blumint/enforce-verb-noun-naming': 'error',
|
|
107
|
+
'@blumintinc/blumint/no-explicit-return-type': 'error',
|
|
108
|
+
'@blumintinc/blumint/use-custom-memo': 'error',
|
|
109
|
+
'@blumintinc/blumint/use-custom-link': 'error',
|
|
110
|
+
'@blumintinc/blumint/enforce-serializable-params': 'error',
|
|
111
|
+
'@blumintinc/blumint/enforce-realtimedb-path-utils': 'error',
|
|
112
|
+
'@blumintinc/blumint/enforce-memoize-async': 'error',
|
|
113
|
+
'@blumintinc/blumint/enforce-exported-function-types': 'error',
|
|
114
|
+
'@blumintinc/blumint/no-redundant-param-types': 'error',
|
|
115
|
+
'@blumintinc/blumint/no-class-instance-destructuring': 'error',
|
|
92
116
|
},
|
|
93
117
|
},
|
|
94
118
|
},
|
|
@@ -130,6 +154,18 @@ module.exports = {
|
|
|
130
154
|
'enforce-firestore-doc-ref-generic': enforce_firestore_doc_ref_generic_1.enforceFirestoreDocRefGeneric,
|
|
131
155
|
'semantic-function-prefixes': semantic_function_prefixes_1.semanticFunctionPrefixes,
|
|
132
156
|
'enforce-mock-firestore': enforce_mock_firestore_1.enforceFirestoreMock,
|
|
157
|
+
'prefer-settings-object': prefer_settings_object_1.preferSettingsObject,
|
|
158
|
+
'enforce-firestore-set-merge': enforce_firestore_set_merge_1.enforceFirestoreSetMerge,
|
|
159
|
+
'enforce-verb-noun-naming': enforce_verb_noun_naming_1.enforceVerbNounNaming,
|
|
160
|
+
'no-explicit-return-type': no_explicit_return_type_1.noExplicitReturnType,
|
|
161
|
+
'use-custom-memo': use_custom_memo_1.useCustomMemo,
|
|
162
|
+
'use-custom-link': use_custom_link_1.useCustomLink,
|
|
163
|
+
'enforce-serializable-params': enforce_serializable_params_1.default,
|
|
164
|
+
'enforce-realtimedb-path-utils': enforce_realtimedb_path_utils_1.enforceRealtimedbPathUtils,
|
|
165
|
+
'enforce-memoize-async': enforce_memoize_async_1.enforceMemoizeAsync,
|
|
166
|
+
'enforce-exported-function-types': enforce_exported_function_types_1.enforceExportedFunctionTypes,
|
|
167
|
+
'no-redundant-param-types': no_redundant_param_types_1.noRedundantParamTypes,
|
|
168
|
+
'no-class-instance-destructuring': no_class_instance_destructuring_1.noClassInstanceDestructuring,
|
|
133
169
|
},
|
|
134
170
|
};
|
|
135
171
|
//# sourceMappingURL=index.js.map
|
|
@@ -55,8 +55,8 @@ exports.arrayMethodsThisContext = (0, createRule_1.createRule)({
|
|
|
55
55
|
},
|
|
56
56
|
schema: [],
|
|
57
57
|
messages: {
|
|
58
|
-
unexpected: 'Use an arrow function to preserve "this" context.',
|
|
59
|
-
preferArrow: 'Use an arrow function instead of binding this.',
|
|
58
|
+
unexpected: 'Use an arrow function to preserve "this" context in array methods. Instead of `array.map(this.method)`, use `array.map((x) => this.method(x))`.',
|
|
59
|
+
preferArrow: 'Use an arrow function instead of binding "this". Instead of `array.map(function(x) {}.bind(this))`, use `array.map((x) => {...})`.',
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
defaultOptions: [],
|
|
@@ -7,12 +7,12 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: 'suggestion',
|
|
9
9
|
docs: {
|
|
10
|
-
description: 'Enforce useCallback
|
|
10
|
+
description: 'Enforce useCallback for inline functions and useMemo for objects/arrays containing functions in JSX props to prevent unnecessary re-renders. This improves React component performance by ensuring stable function references across renders and memoizing complex objects.',
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
14
|
-
enforceCallback: 'Inline functions in JSX props should be wrapped with useCallback',
|
|
15
|
-
enforceMemo: 'Objects/arrays containing functions in JSX props should be wrapped with useMemo',
|
|
14
|
+
enforceCallback: 'Inline functions in JSX props should be wrapped with useCallback to prevent unnecessary re-renders. Instead of `<Button onClick={() => handleClick(id)} />`, use `<Button onClick={useCallback(() => handleClick(id), [id])} />`.',
|
|
15
|
+
enforceMemo: 'Objects/arrays containing functions in JSX props should be wrapped with useMemo to prevent unnecessary re-renders. Instead of `<Component config={{ onSubmit: () => {...} }} />`, use `<Component config={useMemo(() => ({ onSubmit: () => {...} }), [])} />`.',
|
|
16
16
|
},
|
|
17
17
|
schema: [],
|
|
18
18
|
},
|
|
@@ -7,12 +7,12 @@ exports.enforceFirebaseImports = (0, createRule_1.createRule)({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: 'problem',
|
|
9
9
|
docs: {
|
|
10
|
-
description: 'Enforce dynamic importing for modules within the firebaseCloud directory',
|
|
10
|
+
description: 'Enforce dynamic importing for modules within the firebaseCloud directory to optimize initial bundle size. This ensures Firebase-related code is only loaded when needed, improving application startup time and reducing the main bundle size.',
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
schema: [],
|
|
14
14
|
messages: {
|
|
15
|
-
noDynamicImport: 'Static imports from firebaseCloud directory are not allowed.
|
|
15
|
+
noDynamicImport: 'Static imports from firebaseCloud directory are not allowed to reduce initial bundle size. Instead of `import { func } from "./firebaseCloud/module"`, use dynamic import: `const { func } = await import("./firebaseCloud/module")`.',
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
18
|
defaultOptions: [],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.enforceExportedFunctionTypes = void 0;
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
4
5
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
6
|
const createRule_1 = require("../utils/createRule");
|
|
6
7
|
exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
@@ -13,9 +14,9 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
13
14
|
},
|
|
14
15
|
schema: [],
|
|
15
16
|
messages: {
|
|
16
|
-
missingExportedType: 'Type {{typeName}} should be exported since it is used in an exported function',
|
|
17
|
-
missingExportedReturnType: 'Return type {{typeName}} should be exported since it is used in an exported function',
|
|
18
|
-
missingExportedPropsType: 'Props type {{typeName}} should be exported since it is used in an exported React component',
|
|
17
|
+
missingExportedType: 'Type {{typeName}} should be exported since it is used in an exported function. Add `export` before the type definition: `export type {{typeName}} = ...`',
|
|
18
|
+
missingExportedReturnType: 'Return type {{typeName}} should be exported since it is used in an exported function. Add `export` before the type definition: `export type {{typeName}} = ...`',
|
|
19
|
+
missingExportedPropsType: 'Props type {{typeName}} should be exported since it is used in an exported React component. Add `export` before the type definition: `export type {{typeName}} = ...`',
|
|
19
20
|
},
|
|
20
21
|
},
|
|
21
22
|
defaultOptions: [],
|
|
@@ -33,30 +34,95 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
33
34
|
return false;
|
|
34
35
|
if (parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
|
|
35
36
|
parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration ||
|
|
36
|
-
parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
37
|
+
(parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
38
|
+
isExported(parent.parent))) {
|
|
37
39
|
return true;
|
|
38
40
|
}
|
|
39
41
|
return false;
|
|
40
42
|
}
|
|
41
|
-
function
|
|
43
|
+
function getTypeNames(node) {
|
|
42
44
|
if (!node)
|
|
43
|
-
return
|
|
45
|
+
return [];
|
|
44
46
|
switch (node.type) {
|
|
45
47
|
case utils_1.AST_NODE_TYPES.TSTypeReference:
|
|
46
48
|
if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
47
|
-
|
|
49
|
+
const names = [node.typeName.name];
|
|
50
|
+
// For generic types like AuthenticatedRequest<Params>, check both the base type and type parameters
|
|
51
|
+
if ('typeParameters' in node && node.typeParameters) {
|
|
52
|
+
node.typeParameters.params.forEach((param) => {
|
|
53
|
+
names.push(...getTypeNames(param));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return names;
|
|
48
57
|
}
|
|
49
58
|
break;
|
|
50
59
|
case utils_1.AST_NODE_TYPES.TSTypeLiteral:
|
|
51
|
-
return 'AnonymousType';
|
|
60
|
+
return ['AnonymousType'];
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
function isBuiltInType(typeName) {
|
|
65
|
+
const builtInTypes = new Set([
|
|
66
|
+
'string',
|
|
67
|
+
'number',
|
|
68
|
+
'boolean',
|
|
69
|
+
'null',
|
|
70
|
+
'undefined',
|
|
71
|
+
'void',
|
|
72
|
+
'any',
|
|
73
|
+
'never',
|
|
74
|
+
'unknown',
|
|
75
|
+
'object',
|
|
76
|
+
'Date',
|
|
77
|
+
'RegExp',
|
|
78
|
+
'Error',
|
|
79
|
+
'Promise',
|
|
80
|
+
'Array',
|
|
81
|
+
'Function',
|
|
82
|
+
'Symbol',
|
|
83
|
+
'BigInt',
|
|
84
|
+
'Map',
|
|
85
|
+
'Set',
|
|
86
|
+
'WeakMap',
|
|
87
|
+
'WeakSet',
|
|
88
|
+
]);
|
|
89
|
+
return builtInTypes.has(typeName);
|
|
90
|
+
}
|
|
91
|
+
function checkAndReportType(node, parentNode, messageId) {
|
|
92
|
+
const typeNames = getTypeNames(node);
|
|
93
|
+
for (const typeName of typeNames) {
|
|
94
|
+
if (typeName !== 'AnonymousType' &&
|
|
95
|
+
!isBuiltInType(typeName) &&
|
|
96
|
+
!isTypeExported(typeName)) {
|
|
97
|
+
// Check if we've already reported this type
|
|
98
|
+
const key = `${typeName}-${parentNode.loc?.start.line}-${parentNode.loc?.start.column}`;
|
|
99
|
+
if (!reportedTypes.has(key)) {
|
|
100
|
+
reportedTypes.add(key);
|
|
101
|
+
context.report({
|
|
102
|
+
node: parentNode,
|
|
103
|
+
messageId,
|
|
104
|
+
data: { typeName },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
52
108
|
}
|
|
53
|
-
return undefined;
|
|
54
109
|
}
|
|
55
110
|
function isTypeExported(typeName) {
|
|
56
111
|
const sourceCode = context.getSourceCode();
|
|
57
112
|
const program = sourceCode.ast;
|
|
113
|
+
// Check for imported types
|
|
114
|
+
const importedTypes = program.body.filter((node) => {
|
|
115
|
+
if (node.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
|
|
116
|
+
return node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
117
|
+
specifier.local.name === typeName);
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
});
|
|
121
|
+
if (importedTypes.length > 0) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
58
124
|
// Check for exported type declarations
|
|
59
|
-
const exportedTypes = program.body.filter(node => {
|
|
125
|
+
const exportedTypes = program.body.filter((node) => {
|
|
60
126
|
if (node.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
61
127
|
if (node.declaration?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
62
128
|
return node.declaration.id.name === typeName;
|
|
@@ -72,7 +138,7 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
72
138
|
}
|
|
73
139
|
// Check for type aliases in the current scope
|
|
74
140
|
const scope = context.getScope();
|
|
75
|
-
const variable = scope.variables.find(v => v.name === typeName);
|
|
141
|
+
const variable = scope.variables.find((v) => v.name === typeName);
|
|
76
142
|
if (!variable)
|
|
77
143
|
return false;
|
|
78
144
|
const def = variable.defs[0];
|
|
@@ -112,36 +178,13 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
112
178
|
return;
|
|
113
179
|
// Check return type
|
|
114
180
|
if (node.returnType?.typeAnnotation) {
|
|
115
|
-
|
|
116
|
-
if (typeName && !isTypeExported(typeName)) {
|
|
117
|
-
// Check if we've already reported this type
|
|
118
|
-
const key = `${typeName}-${node.loc?.start.line}-${node.loc?.start.column}`;
|
|
119
|
-
if (!reportedTypes.has(key)) {
|
|
120
|
-
reportedTypes.add(key);
|
|
121
|
-
context.report({
|
|
122
|
-
node: node.returnType,
|
|
123
|
-
messageId: 'missingExportedReturnType',
|
|
124
|
-
data: { typeName },
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
181
|
+
checkAndReportType(node.returnType.typeAnnotation, node.returnType, 'missingExportedReturnType');
|
|
128
182
|
}
|
|
129
183
|
// Check parameter types
|
|
130
|
-
node.params.forEach(param => {
|
|
131
|
-
if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Check if we've already reported this type
|
|
135
|
-
const key = `${typeName}-${param.loc?.start.line}-${param.loc?.start.column}`;
|
|
136
|
-
if (!reportedTypes.has(key)) {
|
|
137
|
-
reportedTypes.add(key);
|
|
138
|
-
context.report({
|
|
139
|
-
node: param.typeAnnotation,
|
|
140
|
-
messageId: 'missingExportedType',
|
|
141
|
-
data: { typeName },
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
184
|
+
node.params.forEach((param) => {
|
|
185
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
186
|
+
param.typeAnnotation) {
|
|
187
|
+
checkAndReportType(param.typeAnnotation.typeAnnotation, param.typeAnnotation, 'missingExportedType');
|
|
145
188
|
}
|
|
146
189
|
});
|
|
147
190
|
},
|
|
@@ -155,36 +198,13 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
155
198
|
return;
|
|
156
199
|
// Check return type
|
|
157
200
|
if (node.returnType?.typeAnnotation) {
|
|
158
|
-
|
|
159
|
-
if (typeName && !isTypeExported(typeName)) {
|
|
160
|
-
// Check if we've already reported this type
|
|
161
|
-
const key = `${typeName}-${node.loc?.start.line}-${node.loc?.start.column}`;
|
|
162
|
-
if (!reportedTypes.has(key)) {
|
|
163
|
-
reportedTypes.add(key);
|
|
164
|
-
context.report({
|
|
165
|
-
node: node.returnType,
|
|
166
|
-
messageId: 'missingExportedReturnType',
|
|
167
|
-
data: { typeName },
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
201
|
+
checkAndReportType(node.returnType.typeAnnotation, node.returnType, 'missingExportedReturnType');
|
|
171
202
|
}
|
|
172
203
|
// Check parameter types
|
|
173
|
-
node.params.forEach(param => {
|
|
174
|
-
if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// Check if we've already reported this type
|
|
178
|
-
const key = `${typeName}-${param.loc?.start.line}-${param.loc?.start.column}`;
|
|
179
|
-
if (!reportedTypes.has(key)) {
|
|
180
|
-
reportedTypes.add(key);
|
|
181
|
-
context.report({
|
|
182
|
-
node: param.typeAnnotation,
|
|
183
|
-
messageId: 'missingExportedType',
|
|
184
|
-
data: { typeName },
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
204
|
+
node.params.forEach((param) => {
|
|
205
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
206
|
+
param.typeAnnotation) {
|
|
207
|
+
checkAndReportType(param.typeAnnotation.typeAnnotation, param.typeAnnotation, 'missingExportedType');
|
|
188
208
|
}
|
|
189
209
|
});
|
|
190
210
|
},
|
|
@@ -194,20 +214,7 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
194
214
|
return;
|
|
195
215
|
// Check props parameter
|
|
196
216
|
if (node.typeAnnotation) {
|
|
197
|
-
|
|
198
|
-
if (typeName && !isTypeExported(typeName)) {
|
|
199
|
-
// Check if we've already reported this type
|
|
200
|
-
const key = `${typeName}-${node.loc?.start.line}-${node.loc?.start.column}`;
|
|
201
|
-
if (!reportedTypes.has(key)) {
|
|
202
|
-
reportedTypes.add(key);
|
|
203
|
-
context.report({
|
|
204
|
-
node: node.typeAnnotation,
|
|
205
|
-
messageId: 'missingExportedType',
|
|
206
|
-
data: { typeName },
|
|
207
|
-
});
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
217
|
+
checkAndReportType(node.typeAnnotation.typeAnnotation, node.typeAnnotation, 'missingExportedPropsType');
|
|
211
218
|
}
|
|
212
219
|
},
|
|
213
220
|
// Skip type checking for React components since we handle them separately
|
|
@@ -239,10 +246,6 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
239
246
|
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
240
247
|
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
241
248
|
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
242
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
243
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
244
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
245
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
246
249
|
};
|
|
247
250
|
},
|
|
248
251
|
});
|
|
@@ -24,8 +24,8 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
24
24
|
},
|
|
25
25
|
schema: [],
|
|
26
26
|
messages: {
|
|
27
|
-
missingGeneric: '{{ type }} must specify a generic type argument',
|
|
28
|
-
invalidGeneric: '{{ type }} must not use "any" or "{}" as generic type argument',
|
|
27
|
+
missingGeneric: '{{ type }} must specify a generic type argument for type safety. Instead of `const docRef = doc(collection)`, use `const docRef = doc<YourType>(collection)`.',
|
|
28
|
+
invalidGeneric: '{{ type }} must not use "any" or "{}" as generic type argument. Define a proper interface/type for your document: `interface UserDoc { name: string; age: number; }` and use it: `const docRef = doc<UserDoc>(collection)`.',
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
31
|
defaultOptions: [],
|
|
@@ -113,6 +113,11 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
|
|
|
113
113
|
}
|
|
114
114
|
let current = node;
|
|
115
115
|
while (current) {
|
|
116
|
+
// Type assertions using 'as' keyword
|
|
117
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
118
|
+
nodeCache.set(node, true);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
116
121
|
// Variable declarations with type annotations
|
|
117
122
|
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator && current.id.typeAnnotation) {
|
|
118
123
|
nodeCache.set(node, true);
|
|
@@ -9,12 +9,12 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
|
|
|
9
9
|
meta: {
|
|
10
10
|
type: 'suggestion',
|
|
11
11
|
docs: {
|
|
12
|
-
description: 'Enforce usage of utility functions for Firestore paths',
|
|
12
|
+
description: '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.',
|
|
13
13
|
recommended: 'error',
|
|
14
14
|
},
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
requirePathUtil: 'Use a utility function
|
|
17
|
+
requirePathUtil: 'Use a utility function for Firestore paths to ensure type safety and maintainability. Instead of `doc("users/" + userId)`, create and use a utility function: `const toUserPath = (id: string) => `users/${id}`; doc(toUserPath(userId))`.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|