@blumintinc/eslint-plugin-blumint 1.3.1 β†’ 1.4.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 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-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 | βœ… | | πŸ”§ | |
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-overlayed](docs/rules/require-image-overlayed.md) | Enforce using ImageOverlayed 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
- | [use-custom-router](docs/rules/use-custom-router.md) | Enforce using src/hooks/routing/useRouter instead of next/router | βœ… | | πŸ”§ | |
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,20 @@ 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");
43
53
  module.exports = {
44
54
  meta: {
45
55
  name: '@blumintinc/eslint-plugin-blumint',
46
- version: '1.3.1',
56
+ version: '1.4.0',
47
57
  },
48
58
  parseOptions: {
49
59
  ecmaVersion: 2020,
@@ -89,6 +99,16 @@ module.exports = {
89
99
  '@blumintinc/blumint/enforce-firestore-doc-ref-generic': 'error',
90
100
  '@blumintinc/blumint/semantic-function-prefixes': 'error',
91
101
  '@blumintinc/blumint/enforce-mock-firestore': 'error',
102
+ '@blumintinc/blumint/prefer-settings-object': 'error',
103
+ '@blumintinc/blumint/enforce-firestore-set-merge': 'error',
104
+ '@blumintinc/blumint/enforce-verb-noun-naming': 'error',
105
+ '@blumintinc/blumint/no-explicit-return-type': 'error',
106
+ '@blumintinc/blumint/use-custom-memo': 'error',
107
+ '@blumintinc/blumint/use-custom-link': 'error',
108
+ '@blumintinc/blumint/enforce-serializable-params': 'error',
109
+ '@blumintinc/blumint/enforce-realtimedb-path-utils': 'error',
110
+ '@blumintinc/blumint/enforce-memoize-async': 'error',
111
+ '@blumintinc/blumint/enforce-exported-function-types': 'error',
92
112
  },
93
113
  },
94
114
  },
@@ -130,6 +150,16 @@ module.exports = {
130
150
  'enforce-firestore-doc-ref-generic': enforce_firestore_doc_ref_generic_1.enforceFirestoreDocRefGeneric,
131
151
  'semantic-function-prefixes': semantic_function_prefixes_1.semanticFunctionPrefixes,
132
152
  'enforce-mock-firestore': enforce_mock_firestore_1.enforceFirestoreMock,
153
+ 'prefer-settings-object': prefer_settings_object_1.preferSettingsObject,
154
+ 'enforce-firestore-set-merge': enforce_firestore_set_merge_1.enforceFirestoreSetMerge,
155
+ 'enforce-verb-noun-naming': enforce_verb_noun_naming_1.enforceVerbNounNaming,
156
+ 'no-explicit-return-type': no_explicit_return_type_1.noExplicitReturnType,
157
+ 'use-custom-memo': use_custom_memo_1.useCustomMemo,
158
+ 'use-custom-link': use_custom_link_1.useCustomLink,
159
+ 'enforce-serializable-params': enforce_serializable_params_1.default,
160
+ 'enforce-realtimedb-path-utils': enforce_realtimedb_path_utils_1.enforceRealtimedbPathUtils,
161
+ 'enforce-memoize-async': enforce_memoize_async_1.enforceMemoizeAsync,
162
+ 'enforce-exported-function-types': enforce_exported_function_types_1.enforceExportedFunctionTypes,
133
163
  },
134
164
  };
135
165
  //# sourceMappingURL=index.js.map
@@ -31,6 +31,7 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
31
31
  defaultOptions: [],
32
32
  create(context) {
33
33
  const typeCache = new Map();
34
+ const nodeCache = new WeakMap();
34
35
  function hasInvalidType(node) {
35
36
  if (!node)
36
37
  return false;
@@ -106,6 +107,89 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
106
107
  return false;
107
108
  }
108
109
  }
110
+ function hasTypeAnnotation(node) {
111
+ if (nodeCache.has(node)) {
112
+ return nodeCache.get(node);
113
+ }
114
+ let current = node;
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
+ }
121
+ // Variable declarations with type annotations
122
+ if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator && current.id.typeAnnotation) {
123
+ nodeCache.set(node, true);
124
+ return true;
125
+ }
126
+ // Class property definitions with type annotations
127
+ if (current.type === utils_1.AST_NODE_TYPES.PropertyDefinition && current.typeAnnotation) {
128
+ nodeCache.set(node, true);
129
+ return true;
130
+ }
131
+ // Return statements in functions with return type annotations
132
+ if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
133
+ const func = current.parent?.parent;
134
+ if (func?.type === utils_1.AST_NODE_TYPES.FunctionDeclaration && func.returnType) {
135
+ nodeCache.set(node, true);
136
+ return true;
137
+ }
138
+ }
139
+ // Assignment expressions to class properties
140
+ if (current.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
141
+ const left = current.left;
142
+ if (left.type === utils_1.AST_NODE_TYPES.MemberExpression) {
143
+ const obj = left.object;
144
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression) {
145
+ const classNode = findParentClass(current);
146
+ if (classNode) {
147
+ const property = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
148
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
149
+ member.key.name === left.property.name);
150
+ if (property?.typeAnnotation) {
151
+ nodeCache.set(node, true);
152
+ return true;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ current = current.parent;
159
+ }
160
+ nodeCache.set(node, false);
161
+ return false;
162
+ }
163
+ function findParentClass(node) {
164
+ let current = node;
165
+ while (current) {
166
+ if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration) {
167
+ return current;
168
+ }
169
+ current = current.parent;
170
+ }
171
+ return undefined;
172
+ }
173
+ function isPartOfMethodChain(node) {
174
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
175
+ return false;
176
+ }
177
+ // Check if this node is part of a method chain as the object
178
+ const obj = node.callee.object;
179
+ if (obj.type === utils_1.AST_NODE_TYPES.CallExpression) {
180
+ return true;
181
+ }
182
+ // Check if this node is part of a method chain as the callee
183
+ let current = node;
184
+ while (current) {
185
+ if (current.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
186
+ current.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
187
+ return true;
188
+ }
189
+ current = current.parent;
190
+ }
191
+ return false;
192
+ }
109
193
  return {
110
194
  TSTypeReference(node) {
111
195
  if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -133,6 +217,73 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
133
217
  }
134
218
  }
135
219
  },
220
+ CallExpression(node) {
221
+ // Only check method calls if there's no type annotation
222
+ if (hasTypeAnnotation(node)) {
223
+ return;
224
+ }
225
+ // Check for .doc() calls
226
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
227
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
228
+ node.callee.property.name === 'doc') {
229
+ const typeAnnotation = node.typeParameters;
230
+ if (!typeAnnotation) {
231
+ context.report({
232
+ node,
233
+ messageId: 'missingGeneric',
234
+ data: { type: 'DocumentReference' }
235
+ });
236
+ }
237
+ else if (hasInvalidType(typeAnnotation.params[0])) {
238
+ context.report({
239
+ node,
240
+ messageId: 'invalidGeneric',
241
+ data: { type: 'DocumentReference' }
242
+ });
243
+ }
244
+ }
245
+ // Check for .collection() calls
246
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
247
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
248
+ node.callee.property.name === 'collection' &&
249
+ !isPartOfMethodChain(node)) {
250
+ const typeAnnotation = node.typeParameters;
251
+ if (!typeAnnotation) {
252
+ context.report({
253
+ node,
254
+ messageId: 'missingGeneric',
255
+ data: { type: 'CollectionReference' }
256
+ });
257
+ }
258
+ else if (hasInvalidType(typeAnnotation.params[0])) {
259
+ context.report({
260
+ node,
261
+ messageId: 'invalidGeneric',
262
+ data: { type: 'CollectionReference' }
263
+ });
264
+ }
265
+ }
266
+ // Check for .collectionGroup() calls
267
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
268
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
269
+ node.callee.property.name === 'collectionGroup') {
270
+ const typeAnnotation = node.typeParameters;
271
+ if (!typeAnnotation) {
272
+ context.report({
273
+ node,
274
+ messageId: 'missingGeneric',
275
+ data: { type: 'CollectionGroup' }
276
+ });
277
+ }
278
+ else if (hasInvalidType(typeAnnotation.params[0])) {
279
+ context.report({
280
+ node,
281
+ messageId: 'invalidGeneric',
282
+ data: { type: 'CollectionGroup' }
283
+ });
284
+ }
285
+ }
286
+ },
136
287
  };
137
288
  },
138
289
  });
@@ -10,6 +10,8 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
10
10
  docs: {
11
11
  description: 'Enforce using set() with { merge: true } instead of update() for Firestore operations',
12
12
  recommended: 'error',
13
+ requiresTypeChecking: false,
14
+ extendsBaseRule: false,
13
15
  },
14
16
  fixable: 'code',
15
17
  schema: [],
@@ -23,7 +25,8 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
23
25
  function isFirestoreUpdateCall(node) {
24
26
  if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
25
27
  const property = node.callee.property;
26
- return property.type === utils_1.AST_NODE_TYPES.Identifier && property.name === 'update';
28
+ return (property.type === utils_1.AST_NODE_TYPES.Identifier &&
29
+ property.name === 'update');
27
30
  }
28
31
  if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
29
32
  return updateAliases.has(node.callee.name);
@@ -51,8 +54,9 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
51
54
  }
52
55
  return {
53
56
  ImportDeclaration(node) {
54
- if (node.source.value === 'firebase/firestore' || node.source.value === 'firebase-admin') {
55
- node.specifiers.forEach(specifier => {
57
+ if (node.source.value === 'firebase/firestore' ||
58
+ node.source.value === 'firebase-admin') {
59
+ node.specifiers.forEach((specifier) => {
56
60
  if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
57
61
  if (specifier.imported.name === 'updateDoc') {
58
62
  updateAliases.add(specifier.local.name);
@@ -63,7 +67,8 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
63
67
  },
64
68
  ImportExpression(node) {
65
69
  if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
66
- (node.source.value === 'firebase/firestore' || node.source.value === 'firebase-admin')) {
70
+ (node.source.value === 'firebase/firestore' ||
71
+ node.source.value === 'firebase-admin')) {
67
72
  // Dynamic imports are handled in VariableDeclarator
68
73
  }
69
74
  },
@@ -72,10 +77,11 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
72
77
  node.init.argument.type === utils_1.AST_NODE_TYPES.ImportExpression) {
73
78
  const importSource = node.init.argument.source;
74
79
  if (importSource.type === utils_1.AST_NODE_TYPES.Literal &&
75
- (importSource.value === 'firebase/firestore' || importSource.value === 'firebase-admin')) {
80
+ (importSource.value === 'firebase/firestore' ||
81
+ importSource.value === 'firebase-admin')) {
76
82
  // Handle destructured imports
77
83
  if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
78
- node.id.properties.forEach(prop => {
84
+ node.id.properties.forEach((prop) => {
79
85
  if (prop.type === utils_1.AST_NODE_TYPES.Property &&
80
86
  prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
81
87
  prop.key.name === 'updateDoc') {
@@ -15,6 +15,8 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
15
15
  docs: {
16
16
  description: 'Enforce verb phrases for functions and methods',
17
17
  recommended: 'error',
18
+ requiresTypeChecking: false,
19
+ extendsBaseRule: false,
18
20
  },
19
21
  schema: [],
20
22
  messages: {
@@ -17,6 +17,8 @@ function isArrayOrPrimitive(checker, esTreeNode, nodeMap) {
17
17
  // Check if it's a primitive type
18
18
  if (type.flags &
19
19
  (typescript_1.TypeFlags.String |
20
+ typescript_1.TypeFlags.StringLike |
21
+ typescript_1.TypeFlags.StringLiteral |
20
22
  typescript_1.TypeFlags.Number |
21
23
  typescript_1.TypeFlags.Boolean |
22
24
  typescript_1.TypeFlags.Null |
@@ -36,6 +38,10 @@ function isArrayOrPrimitive(checker, esTreeNode, nodeMap) {
36
38
  (typeNode && ((0, typescript_1.isArrayTypeNode)(typeNode) || (0, typescript_1.isTupleTypeNode)(typeNode)))) {
37
39
  return true;
38
40
  }
41
+ // Check if it's a string type with methods (like String object)
42
+ if (type.symbol?.name === 'String' || type.symbol?.escapedName === 'String') {
43
+ return true;
44
+ }
39
45
  // If it's not a primitive or array, and has properties, it's an object
40
46
  return false;
41
47
  }
@@ -130,8 +136,9 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
130
136
  meta: {
131
137
  type: 'suggestion',
132
138
  docs: {
133
- description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used',
139
+ description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used. Requires TypeScript and `parserOptions.project` to be configured.',
134
140
  recommended: 'error',
141
+ requiresTypeChecking: true,
135
142
  },
136
143
  fixable: 'code',
137
144
  schema: [],
@@ -1,3 +1,4 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
1
2
  type Options = [
2
3
  {
3
4
  allowRecursiveFunctions?: boolean;
@@ -7,5 +8,5 @@ type Options = [
7
8
  allowDtsFiles?: boolean;
8
9
  }
9
10
  ];
10
- export declare const noExplicitReturnType: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noExplicitReturnType", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
11
+ export declare const noExplicitReturnType: TSESLint.RuleModule<'noExplicitReturnType', Options>;
11
12
  export {};
@@ -11,9 +11,11 @@ const defaultOptions = {
11
11
  allowDtsFiles: true,
12
12
  };
13
13
  function isRecursiveFunction(node) {
14
- const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ? node.id?.name :
15
- node.type === utils_1.AST_NODE_TYPES.FunctionExpression && node.id ? node.id.name :
16
- undefined;
14
+ const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
15
+ ? node.id?.name
16
+ : node.type === utils_1.AST_NODE_TYPES.FunctionExpression && node.id
17
+ ? node.id.name
18
+ : undefined;
17
19
  if (!functionName || !node.body)
18
20
  return false;
19
21
  let hasRecursiveCall = false;
@@ -63,9 +65,9 @@ function isOverloadedFunction(node) {
63
65
  const methodName = node.key.type === utils_1.AST_NODE_TYPES.Identifier ? node.key.name : undefined;
64
66
  if (!methodName)
65
67
  return false;
66
- return interfaceBody.body.filter(member => member.type === utils_1.AST_NODE_TYPES.TSMethodSignature &&
68
+ return (interfaceBody.body.filter((member) => member.type === utils_1.AST_NODE_TYPES.TSMethodSignature &&
67
69
  member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
68
- member.key.name === methodName).length > 1;
70
+ member.key.name === methodName).length > 1);
69
71
  }
70
72
  return false;
71
73
  }
@@ -75,7 +77,8 @@ function isInterfaceOrAbstractMethodSignature(node) {
75
77
  if (node.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
76
78
  let current = node;
77
79
  while (current) {
78
- if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration && current.abstract) {
80
+ if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
81
+ current.abstract) {
79
82
  return true;
80
83
  }
81
84
  current = current.parent;
@@ -90,6 +93,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
90
93
  docs: {
91
94
  description: 'Disallow explicit return types on functions',
92
95
  recommended: 'error',
96
+ requiresTypeChecking: false,
97
+ extendsBaseRule: false,
93
98
  },
94
99
  fixable: 'code',
95
100
  schema: [
@@ -116,6 +121,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
116
121
  if (mergedOptions.allowDtsFiles && filename.endsWith('.d.ts')) {
117
122
  return {};
118
123
  }
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
125
  function fixReturnType(fixer, node) {
120
126
  const returnType = node.returnType || node.value?.returnType;
121
127
  if (!returnType)
@@ -127,25 +133,27 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
127
133
  FunctionDeclaration(node) {
128
134
  if (!node.returnType)
129
135
  return;
130
- if (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node)) {
136
+ if (mergedOptions.allowRecursiveFunctions &&
137
+ isRecursiveFunction(node)) {
131
138
  return;
132
139
  }
133
140
  context.report({
134
141
  node: node.returnType,
135
142
  messageId: 'noExplicitReturnType',
136
- fix: fixer => fixReturnType(fixer, node),
143
+ fix: (fixer) => fixReturnType(fixer, node),
137
144
  });
138
145
  },
139
146
  FunctionExpression(node) {
140
147
  if (!node.returnType)
141
148
  return;
142
- if (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node)) {
149
+ if (mergedOptions.allowRecursiveFunctions &&
150
+ isRecursiveFunction(node)) {
143
151
  return;
144
152
  }
145
153
  context.report({
146
154
  node: node.returnType,
147
155
  messageId: 'noExplicitReturnType',
148
- fix: fixer => fixReturnType(fixer, node),
156
+ fix: (fixer) => fixReturnType(fixer, node),
149
157
  });
150
158
  },
151
159
  ArrowFunctionExpression(node) {
@@ -154,7 +162,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
154
162
  context.report({
155
163
  node: node.returnType,
156
164
  messageId: 'noExplicitReturnType',
157
- fix: fixer => fixReturnType(fixer, node),
165
+ fix: (fixer) => fixReturnType(fixer, node),
158
166
  });
159
167
  },
160
168
  TSMethodSignature(node) {
@@ -163,25 +171,27 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
163
171
  if (mergedOptions.allowInterfaceMethodSignatures) {
164
172
  return;
165
173
  }
166
- if (mergedOptions.allowOverloadedFunctions && isOverloadedFunction(node)) {
174
+ if (mergedOptions.allowOverloadedFunctions &&
175
+ isOverloadedFunction(node)) {
167
176
  return;
168
177
  }
169
178
  context.report({
170
179
  node: node.returnType,
171
180
  messageId: 'noExplicitReturnType',
172
- fix: fixer => fixReturnType(fixer, node),
181
+ fix: (fixer) => fixReturnType(fixer, node),
173
182
  });
174
183
  },
175
184
  MethodDefinition(node) {
176
185
  if (!node.value.returnType)
177
186
  return;
178
- if (mergedOptions.allowAbstractMethodSignatures && isInterfaceOrAbstractMethodSignature(node)) {
187
+ if (mergedOptions.allowAbstractMethodSignatures &&
188
+ isInterfaceOrAbstractMethodSignature(node)) {
179
189
  return;
180
190
  }
181
191
  context.report({
182
192
  node: node.value.returnType,
183
193
  messageId: 'noExplicitReturnType',
184
- fix: fixer => fixReturnType(fixer, node),
194
+ fix: (fixer) => fixReturnType(fixer, node),
185
195
  });
186
196
  },
187
197
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -71,7 +71,7 @@
71
71
  "dotenv-cli": "5.0.0",
72
72
  "eslint": "8.57.0",
73
73
  "eslint-config-prettier": "8.5.0",
74
- "eslint-doc-generator": "1.0.0",
74
+ "eslint-doc-generator": "1.7.1",
75
75
  "eslint-import-resolver-typescript": "3.5.5",
76
76
  "eslint-plugin-eslint-plugin": "5.0.0",
77
77
  "eslint-plugin-import": "2.26.0",