@blumintinc/eslint-plugin-blumint 1.2.0 → 1.3.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.
Files changed (30) hide show
  1. package/README.md +40 -37
  2. package/lib/index.js +22 -13
  3. package/lib/rules/dynamic-https-errors.js +1 -1
  4. package/lib/rules/enforce-callback-memo.js +21 -3
  5. package/lib/rules/enforce-firestore-doc-ref-generic.d.ts +10 -0
  6. package/lib/rules/enforce-firestore-doc-ref-generic.js +134 -0
  7. package/lib/rules/enforce-firestore-set-merge.d.ts +1 -0
  8. package/lib/rules/enforce-firestore-set-merge.js +106 -0
  9. package/lib/rules/enforce-mock-firestore.d.ts +3 -0
  10. package/lib/rules/enforce-mock-firestore.js +76 -0
  11. package/lib/rules/enforce-verb-noun-naming.d.ts +1 -0
  12. package/lib/rules/enforce-verb-noun-naming.js +110 -0
  13. package/lib/rules/export-if-in-doubt.js +1 -1
  14. package/lib/rules/extract-global-constants.js +31 -2
  15. package/lib/rules/global-const-style.d.ts +2 -4
  16. package/lib/rules/global-const-style.js +48 -22
  17. package/lib/rules/no-compositing-layer-props.js +38 -4
  18. package/lib/rules/no-entire-object-hook-deps.js +68 -60
  19. package/lib/rules/no-explicit-return-type.d.ts +11 -0
  20. package/lib/rules/no-explicit-return-type.js +190 -0
  21. package/lib/rules/no-unused-props.js +33 -8
  22. package/lib/rules/no-useless-fragment.js +1 -1
  23. package/lib/rules/prefer-fragment-shorthand.js +1 -1
  24. package/lib/rules/prefer-type-over-interface.js +1 -1
  25. package/lib/rules/require-https-error.js +32 -23
  26. package/lib/rules/require-image-optimized.d.ts +7 -0
  27. package/lib/rules/require-image-optimized.js +82 -0
  28. package/lib/rules/semantic-function-prefixes.d.ts +1 -0
  29. package/lib/rules/semantic-function-prefixes.js +74 -0
  30. package/package.json +2 -1
package/README.md CHANGED
@@ -54,42 +54,45 @@ Or use the recommended config:
54
54
  💼 Configurations enabled in.\
55
55
  ⚠️ Configurations set to warn in.\
56
56
  ✅ Set in the `recommended` configuration.\
57
- 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
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-path-utils](docs/rules/enforce-firestore-path-utils.md) | Enforce usage of utility functions for Firestore paths | ✅ | | |
70
- | [enforce-identifiable-firestore-type](docs/rules/enforce-identifiable-firestore-type.md) | Enforce that Firestore type definitions extend Identifiable and match their folder name | ✅ | | |
71
- | [enforce-safe-stringify](docs/rules/enforce-safe-stringify.md) | Enforce using safe-stable-stringify instead of JSON.stringify | ✅ | | 🔧 |
72
- | [export-if-in-doubt](docs/rules/export-if-in-doubt.md) | All top-level const definitions, type definitions, and functions should be exported | | | |
73
- | [extract-global-constants](docs/rules/extract-global-constants.md) | Extract static constants and functions to the global scope when possible | | | |
74
- | [generic-starts-with-t](docs/rules/generic-starts-with-t.md) | Enforce TypeScript generic types to start with T | || |
75
- | [global-const-style](docs/rules/global-const-style.md) | Enforce UPPER_SNAKE_CASE and as const for global static constants | ✅ | | 🔧 |
76
- | [no-async-array-filter](docs/rules/no-async-array-filter.md) | Disallow async callbacks for Array.filter | ✅ | | |
77
- | [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow Array.forEach with an async callback function | ✅ | | |
78
- | [no-conditional-literals-in-jsx](docs/rules/no-conditional-literals-in-jsx.md) | Disallow use of conditional literals in JSX code | ✅ | | |
79
- | [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 | ✅ | | 🔧 |
80
- | [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) | ✅ | | |
81
- | [no-jsx-whitespace-literal](docs/rules/no-jsx-whitespace-literal.md) | Disallow the use of {" "} elements in JSX code | ✅ | | |
82
- | [no-misused-switch-case](docs/rules/no-misused-switch-case.md) | Prevent misuse of logical OR in switch case statements | ✅ | | |
83
- | [no-unpinned-dependencies](docs/rules/no-unpinned-dependencies.md) | Enforces pinned dependencies | ✅ | | 🔧 |
84
- | [no-unused-props](docs/rules/no-unused-props.md) | Detect unused props in React component type definitions | ✅ | | 🔧 |
85
- | [no-useless-fragment](docs/rules/no-useless-fragment.md) | Prevent unnecessary use of React fragments | | ✅ | 🔧 |
86
- | [prefer-fragment-shorthand](docs/rules/prefer-fragment-shorthand.md) | Prefer <> shorthand for <React.Fragment> | | ✅ | 🔧 |
87
- | [prefer-type-over-interface](docs/rules/prefer-type-over-interface.md) | Prefer using type alias over interface | | ✅ | 🔧 |
88
- | [require-dynamic-firebase-imports](docs/rules/require-dynamic-firebase-imports.md) | Enforce dynamic imports for Firebase dependencies | ✅ | | 🔧 |
89
- | [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 | ✅ | | |
90
- | [require-image-overlayed](docs/rules/require-image-overlayed.md) | Enforce using ImageOverlayed component instead of next/image or img tags | ✅ | | 🔧 |
91
- | [require-memo](docs/rules/require-memo.md) | React components must be memoized | ✅ | | 🔧 |
92
- | [require-usememo-object-literals](docs/rules/require-usememo-object-literals.md) | Enforce using useMemo for inline object literals passed as props to JSX components | ✅ | | |
93
- | [use-custom-router](docs/rules/use-custom-router.md) | Enforce using src/hooks/routing/useRouter instead of next/router | ✅ | | 🔧 |
57
+ 🔧 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 | ✅ | | 🔧 | |
94
97
 
95
98
  <!-- end auto-generated rules list -->
package/lib/index.js CHANGED
@@ -30,17 +30,20 @@ const no_jsx_whitespace_literal_1 = require("./rules/no-jsx-whitespace-literal")
30
30
  const require_dynamic_firebase_imports_1 = __importDefault(require("./rules/require-dynamic-firebase-imports"));
31
31
  const require_https_error_1 = __importDefault(require("./rules/require-https-error"));
32
32
  const use_custom_router_1 = require("./rules/use-custom-router");
33
- const require_image_overlayed_1 = __importDefault(require("./rules/require-image-overlayed"));
33
+ const require_image_optimized_1 = __importDefault(require("./rules/require-image-optimized"));
34
34
  const require_usememo_object_literals_1 = require("./rules/require-usememo-object-literals");
35
35
  const enforce_safe_stringify_1 = require("./rules/enforce-safe-stringify");
36
36
  const avoid_utils_directory_1 = require("./rules/avoid-utils-directory");
37
37
  const no_entire_object_hook_deps_1 = require("./rules/no-entire-object-hook-deps");
38
38
  const enforce_firestore_path_utils_1 = require("./rules/enforce-firestore-path-utils");
39
39
  const no_compositing_layer_props_1 = require("./rules/no-compositing-layer-props");
40
+ const enforce_firestore_doc_ref_generic_1 = require("./rules/enforce-firestore-doc-ref-generic");
41
+ const semantic_function_prefixes_1 = require("./rules/semantic-function-prefixes");
42
+ const enforce_mock_firestore_1 = require("./rules/enforce-mock-firestore");
40
43
  module.exports = {
41
44
  meta: {
42
45
  name: '@blumintinc/eslint-plugin-blumint',
43
- version: '1.2.0',
46
+ version: '1.3.0',
44
47
  },
45
48
  parseOptions: {
46
49
  ecmaVersion: 2020,
@@ -52,17 +55,17 @@ module.exports = {
52
55
  '@blumintinc/blumint/avoid-utils-directory': 'error',
53
56
  '@blumintinc/blumint/enforce-firestore-path-utils': 'error',
54
57
  '@blumintinc/blumint/no-jsx-whitespace-literal': 'error',
55
- '@blumintinc/blumint/array-methods-this-context': 'warn',
56
- '@blumintinc/blumint/class-methods-read-top-to-bottom': 'warn',
58
+ '@blumintinc/blumint/array-methods-this-context': 'error',
59
+ '@blumintinc/blumint/class-methods-read-top-to-bottom': 'error',
57
60
  '@blumintinc/blumint/consistent-callback-naming': 'error',
58
- '@blumintinc/blumint/dynamic-https-errors': 'warn',
61
+ '@blumintinc/blumint/dynamic-https-errors': 'error',
59
62
  '@blumintinc/blumint/enforce-identifiable-firestore-type': 'error',
60
63
  '@blumintinc/blumint/enforce-callback-memo': 'error',
61
64
  '@blumintinc/blumint/enforce-callable-types': 'error',
62
65
  '@blumintinc/blumint/enforce-dynamic-firebase-imports': 'error',
63
66
  // '@blumintinc/blumint/export-if-in-doubt': 'warn',
64
- '@blumintinc/blumint/extract-global-constants': 'warn',
65
- '@blumintinc/blumint/generic-starts-with-t': 'warn',
67
+ '@blumintinc/blumint/extract-global-constants': 'error',
68
+ '@blumintinc/blumint/generic-starts-with-t': 'error',
66
69
  '@blumintinc/blumint/global-const-style': 'error',
67
70
  '@blumintinc/blumint/no-async-array-filter': 'error',
68
71
  '@blumintinc/blumint/no-async-foreach': 'error',
@@ -71,18 +74,21 @@ module.exports = {
71
74
  '@blumintinc/blumint/no-misused-switch-case': 'error',
72
75
  '@blumintinc/blumint/no-unpinned-dependencies': 'error',
73
76
  '@blumintinc/blumint/no-unused-props': 'error',
74
- '@blumintinc/blumint/no-useless-fragment': 'warn',
75
- '@blumintinc/blumint/prefer-fragment-shorthand': 'warn',
76
- '@blumintinc/blumint/prefer-type-over-interface': 'warn',
77
+ //'@blumintinc/blumint/no-useless-fragment': 'error',
78
+ '@blumintinc/blumint/prefer-fragment-shorthand': 'error',
79
+ '@blumintinc/blumint/prefer-type-over-interface': 'error',
77
80
  '@blumintinc/blumint/require-memo': 'error',
78
81
  '@blumintinc/blumint/require-dynamic-firebase-imports': 'error',
79
82
  '@blumintinc/blumint/require-https-error': 'error',
80
83
  '@blumintinc/blumint/use-custom-router': 'error',
81
- '@blumintinc/blumint/require-image-overlayed': 'error',
84
+ '@blumintinc/blumint/require-image-optimized': 'error',
82
85
  '@blumintinc/blumint/require-usememo-object-literals': 'error',
83
86
  '@blumintinc/blumint/enforce-safe-stringify': 'error',
84
87
  '@blumintinc/blumint/no-entire-object-hook-deps': 'error',
85
- '@blumintinc/blumint/no-compositing-layer-props': 'warn',
88
+ '@blumintinc/blumint/no-compositing-layer-props': 'error',
89
+ '@blumintinc/blumint/enforce-firestore-doc-ref-generic': 'error',
90
+ '@blumintinc/blumint/semantic-function-prefixes': 'error',
91
+ '@blumintinc/blumint/enforce-mock-firestore': 'error',
86
92
  },
87
93
  },
88
94
  },
@@ -114,13 +120,16 @@ module.exports = {
114
120
  'require-dynamic-firebase-imports': require_dynamic_firebase_imports_1.default,
115
121
  'require-https-error': require_https_error_1.default,
116
122
  'use-custom-router': use_custom_router_1.useCustomRouter,
117
- 'require-image-overlayed': require_image_overlayed_1.default,
123
+ 'require-image-optimized': require_image_optimized_1.default,
118
124
  'require-usememo-object-literals': require_usememo_object_literals_1.requireUseMemoObjectLiterals,
119
125
  'enforce-safe-stringify': enforce_safe_stringify_1.enforceStableStringify,
120
126
  'avoid-utils-directory': avoid_utils_directory_1.avoidUtilsDirectory,
121
127
  'no-entire-object-hook-deps': no_entire_object_hook_deps_1.noEntireObjectHookDeps,
122
128
  'enforce-firestore-path-utils': enforce_firestore_path_utils_1.enforceFirestorePathUtils,
123
129
  'no-compositing-layer-props': no_compositing_layer_props_1.noCompositingLayerProps,
130
+ 'enforce-firestore-doc-ref-generic': enforce_firestore_doc_ref_generic_1.enforceFirestoreDocRefGeneric,
131
+ 'semantic-function-prefixes': semantic_function_prefixes_1.semanticFunctionPrefixes,
132
+ 'enforce-mock-firestore': enforce_mock_firestore_1.enforceFirestoreMock,
124
133
  },
125
134
  };
126
135
  //# sourceMappingURL=index.js.map
@@ -20,7 +20,7 @@ exports.dynamicHttpsErrors = (0, createRule_1.createRule)({
20
20
  type: 'suggestion',
21
21
  docs: {
22
22
  description: 'Dynamic error details should only be in the third argument of the HttpsError constructor. The second argument is hashed to produce a unique id.',
23
- recommended: 'warn',
23
+ recommended: 'error',
24
24
  },
25
25
  schema: [],
26
26
  messages: {
@@ -40,6 +40,19 @@ exports.default = (0, createRule_1.createRule)({
40
40
  }
41
41
  return false;
42
42
  }
43
+ function hasJSXWithFunction(node) {
44
+ if (node.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement) {
45
+ return node.openingElement.attributes.some((attr) => {
46
+ if (attr.type === utils_1.TSESTree.AST_NODE_TYPES.JSXAttribute && attr.value) {
47
+ if (attr.value.type === utils_1.TSESTree.AST_NODE_TYPES.JSXExpressionContainer) {
48
+ return containsFunction(attr.value.expression);
49
+ }
50
+ }
51
+ return false;
52
+ });
53
+ }
54
+ return false;
55
+ }
43
56
  function checkJSXAttribute(node) {
44
57
  if (!node.value ||
45
58
  node.value.type !== utils_1.TSESTree.AST_NODE_TYPES.JSXExpressionContainer) {
@@ -61,10 +74,15 @@ exports.default = (0, createRule_1.createRule)({
61
74
  });
62
75
  return;
63
76
  }
64
- // Check for objects/arrays containing functions
77
+ // Check for objects/arrays/JSX elements containing functions
65
78
  if ((expression.type === utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression ||
66
- expression.type === utils_1.TSESTree.AST_NODE_TYPES.ArrayExpression) &&
67
- containsFunction(expression)) {
79
+ expression.type === utils_1.TSESTree.AST_NODE_TYPES.ArrayExpression ||
80
+ expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement) &&
81
+ (containsFunction(expression) || hasJSXWithFunction(expression))) {
82
+ // Skip reporting if this is a JSX element and we already reported an inline function
83
+ if (expression.type === utils_1.TSESTree.AST_NODE_TYPES.JSXElement && hasJSXWithFunction(expression)) {
84
+ return;
85
+ }
68
86
  context.report({
69
87
  node,
70
88
  messageId: 'enforceMemo',
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview Enforce generic argument for Firestore DocumentReference
3
+ * @author BluMint
4
+ */
5
+ type MessageIds = 'missingGeneric' | 'invalidGeneric';
6
+ /**
7
+ * @type {import('eslint').Rule.RuleModule}
8
+ */
9
+ export declare const enforceFirestoreDocRefGeneric: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
10
+ export {};
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Enforce generic argument for Firestore DocumentReference
4
+ * @author BluMint
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.enforceFirestoreDocRefGeneric = void 0;
8
+ /**
9
+ * @type {import('eslint').Rule.RuleModule}
10
+ */
11
+ const utils_1 = require("@typescript-eslint/utils");
12
+ const createRule_1 = require("../utils/createRule");
13
+ /**
14
+ * @type {import('eslint').Rule.RuleModule}
15
+ */
16
+ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
17
+ name: 'enforce-firestore-doc-ref-generic',
18
+ meta: {
19
+ type: 'problem',
20
+ docs: {
21
+ description: 'Enforce generic argument for Firestore DocumentReference',
22
+ recommended: 'error',
23
+ requiresTypeChecking: true,
24
+ },
25
+ schema: [],
26
+ messages: {
27
+ missingGeneric: 'DocumentReference must specify a generic type argument',
28
+ invalidGeneric: 'DocumentReference must not use "any" or "{}" as generic type argument',
29
+ },
30
+ },
31
+ defaultOptions: [],
32
+ create(context) {
33
+ const typeCache = new Map();
34
+ function hasInvalidType(node) {
35
+ if (!node)
36
+ return false;
37
+ switch (node.type) {
38
+ case utils_1.AST_NODE_TYPES.TSAnyKeyword:
39
+ return true;
40
+ case utils_1.AST_NODE_TYPES.TSTypeLiteral:
41
+ if (!node.members || node.members.length === 0) {
42
+ return true;
43
+ }
44
+ return node.members.some((member) => {
45
+ if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
46
+ member.typeAnnotation) {
47
+ return hasInvalidType(member.typeAnnotation.typeAnnotation);
48
+ }
49
+ return false;
50
+ });
51
+ case utils_1.AST_NODE_TYPES.TSTypeReference:
52
+ if (node.typeParameters) {
53
+ return node.typeParameters.params.some(hasInvalidType);
54
+ }
55
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
56
+ const typeName = node.typeName.name;
57
+ if (typeCache.has(typeName)) {
58
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
59
+ return typeCache.get(typeName);
60
+ }
61
+ // Prevent infinite recursion
62
+ typeCache.set(typeName, false);
63
+ const program = context.getSourceCode().ast;
64
+ const interfaceDecl = program.body.find((n) => n.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration &&
65
+ n.id.name === typeName);
66
+ if (interfaceDecl) {
67
+ const result = interfaceDecl.body.body.some((member) => {
68
+ if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
69
+ member.typeAnnotation) {
70
+ return hasInvalidType(member.typeAnnotation.typeAnnotation);
71
+ }
72
+ return false;
73
+ });
74
+ typeCache.set(typeName, result);
75
+ return result;
76
+ }
77
+ }
78
+ return false;
79
+ case utils_1.AST_NODE_TYPES.TSIntersectionType:
80
+ case utils_1.AST_NODE_TYPES.TSUnionType:
81
+ return node.types.some(hasInvalidType);
82
+ case utils_1.AST_NODE_TYPES.TSTypeOperator:
83
+ if ('typeAnnotation' in node) {
84
+ return hasInvalidType(node.typeAnnotation);
85
+ }
86
+ return false;
87
+ case utils_1.AST_NODE_TYPES.TSMappedType:
88
+ if ('typeAnnotation' in node) {
89
+ return hasInvalidType(node.typeAnnotation);
90
+ }
91
+ return false;
92
+ case utils_1.AST_NODE_TYPES.TSIndexedAccessType:
93
+ return (hasInvalidType(node.objectType) || hasInvalidType(node.indexType));
94
+ case utils_1.AST_NODE_TYPES.TSConditionalType:
95
+ return (hasInvalidType(node.checkType) ||
96
+ hasInvalidType(node.extendsType) ||
97
+ hasInvalidType(node.trueType) ||
98
+ hasInvalidType(node.falseType));
99
+ case utils_1.AST_NODE_TYPES.TSArrayType:
100
+ return hasInvalidType(node.elementType);
101
+ case utils_1.AST_NODE_TYPES.TSTupleType:
102
+ return node.elementTypes.some(hasInvalidType);
103
+ case utils_1.AST_NODE_TYPES.TSTypeQuery:
104
+ return false;
105
+ default:
106
+ return false;
107
+ }
108
+ }
109
+ return {
110
+ TSTypeReference(node) {
111
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
112
+ node.typeName.name === 'DocumentReference') {
113
+ // Check if generic type argument is missing
114
+ if (!node.typeParameters || node.typeParameters.params.length === 0) {
115
+ context.report({
116
+ node,
117
+ messageId: 'missingGeneric',
118
+ });
119
+ return;
120
+ }
121
+ // Check for invalid generic type arguments (any or {}) recursively
122
+ const typeArg = node.typeParameters.params[0];
123
+ if (hasInvalidType(typeArg)) {
124
+ context.report({
125
+ node,
126
+ messageId: 'invalidGeneric',
127
+ });
128
+ }
129
+ }
130
+ },
131
+ };
132
+ },
133
+ });
134
+ //# sourceMappingURL=enforce-firestore-doc-ref-generic.js.map
@@ -0,0 +1 @@
1
+ export declare const enforceFirestoreSetMerge: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferSetMerge", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceFirestoreSetMerge = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
7
+ name: 'enforce-firestore-set-merge',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce using set() with { merge: true } instead of update() for Firestore operations',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ preferSetMerge: 'Use set() with { merge: true } instead of update() for more predictable Firestore operations',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ const updateAliases = new Set();
23
+ function isFirestoreUpdateCall(node) {
24
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
25
+ const property = node.callee.property;
26
+ return property.type === utils_1.AST_NODE_TYPES.Identifier && property.name === 'update';
27
+ }
28
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
29
+ return updateAliases.has(node.callee.name);
30
+ }
31
+ return false;
32
+ }
33
+ function convertUpdateToSetMerge(node, sourceCode) {
34
+ const args = node.arguments;
35
+ if (args.length === 0)
36
+ return '';
37
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
38
+ const object = sourceCode.getText(node.callee.object);
39
+ if (object.includes('transaction')) {
40
+ const docRef = sourceCode.getText(args[0]);
41
+ const data = sourceCode.getText(args[1]);
42
+ return `${object}.set(${docRef}, ${data}, { merge: true })`;
43
+ }
44
+ const data = sourceCode.getText(args[0]);
45
+ return `${object}.set(${data}, { merge: true })`;
46
+ }
47
+ // For updateDoc from firebase/firestore
48
+ const docRef = sourceCode.getText(args[0]);
49
+ const data = args.length > 1 ? sourceCode.getText(args[1]) : '{}';
50
+ return `setDoc(${docRef}, ${data}, { merge: true })`;
51
+ }
52
+ return {
53
+ ImportDeclaration(node) {
54
+ if (node.source.value === 'firebase/firestore' || node.source.value === 'firebase-admin') {
55
+ node.specifiers.forEach(specifier => {
56
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
57
+ if (specifier.imported.name === 'updateDoc') {
58
+ updateAliases.add(specifier.local.name);
59
+ }
60
+ }
61
+ });
62
+ }
63
+ },
64
+ ImportExpression(node) {
65
+ if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
66
+ (node.source.value === 'firebase/firestore' || node.source.value === 'firebase-admin')) {
67
+ // Dynamic imports are handled in VariableDeclarator
68
+ }
69
+ },
70
+ VariableDeclarator(node) {
71
+ if (node.init?.type === utils_1.AST_NODE_TYPES.AwaitExpression &&
72
+ node.init.argument.type === utils_1.AST_NODE_TYPES.ImportExpression) {
73
+ const importSource = node.init.argument.source;
74
+ if (importSource.type === utils_1.AST_NODE_TYPES.Literal &&
75
+ (importSource.value === 'firebase/firestore' || importSource.value === 'firebase-admin')) {
76
+ // Handle destructured imports
77
+ if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
78
+ node.id.properties.forEach(prop => {
79
+ if (prop.type === utils_1.AST_NODE_TYPES.Property &&
80
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
81
+ prop.key.name === 'updateDoc') {
82
+ if (prop.value.type === utils_1.AST_NODE_TYPES.Identifier) {
83
+ updateAliases.add(prop.value.name);
84
+ }
85
+ }
86
+ });
87
+ }
88
+ }
89
+ }
90
+ },
91
+ CallExpression(node) {
92
+ if (isFirestoreUpdateCall(node)) {
93
+ context.report({
94
+ node,
95
+ messageId: 'preferSetMerge',
96
+ fix(fixer) {
97
+ const newText = convertUpdateToSetMerge(node, context.getSourceCode());
98
+ return fixer.replaceText(node, newText);
99
+ },
100
+ });
101
+ }
102
+ },
103
+ };
104
+ },
105
+ });
106
+ //# sourceMappingURL=enforce-firestore-set-merge.js.map
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'noManualFirestoreMock' | 'noMockFirebase';
2
+ export declare const enforceFirestoreMock: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceFirestoreMock = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const FIRESTORE_PATHS = [
7
+ 'functions/src/config/firebaseAdmin',
8
+ 'firebase-admin',
9
+ 'firebase-admin/firestore',
10
+ ];
11
+ exports.enforceFirestoreMock = (0, createRule_1.createRule)({
12
+ name: 'enforce-mock-firestore',
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ description: 'Enforce using mockFirestore over manual Firestore mocking',
17
+ recommended: 'error',
18
+ },
19
+ schema: [],
20
+ messages: {
21
+ noManualFirestoreMock: 'Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead of manually mocking Firestore',
22
+ noMockFirebase: 'Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead of mockFirebase',
23
+ },
24
+ },
25
+ defaultOptions: [],
26
+ create(context) {
27
+ return {
28
+ // Detect jest.mock() calls for firebaseAdmin
29
+ CallExpression(node) {
30
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
31
+ node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
32
+ node.callee.object.name === 'jest' &&
33
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
34
+ node.callee.property.name === 'mock' &&
35
+ node.arguments.length > 0 &&
36
+ node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
37
+ typeof node.arguments[0].value === 'string' &&
38
+ FIRESTORE_PATHS.some((path) => node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
39
+ typeof node.arguments[0].value === 'string' &&
40
+ node.arguments[0].value?.includes(path))) {
41
+ // Check if the mock includes Firestore-related properties
42
+ const mockFn = node.arguments[1];
43
+ if (mockFn &&
44
+ mockFn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
45
+ const returnStmt = mockFn.body;
46
+ if (returnStmt.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
47
+ returnStmt.properties.some((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
48
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
49
+ (prop.key.name === 'db' ||
50
+ prop.key.name === 'firestore' ||
51
+ prop.key.name === 'getFirestore'))) {
52
+ context.report({
53
+ node,
54
+ messageId: 'noManualFirestoreMock',
55
+ });
56
+ }
57
+ }
58
+ }
59
+ },
60
+ // Detect imports of mockFirebase
61
+ ImportDeclaration(node) {
62
+ if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
63
+ node.source.value === 'firestore-jest-mock' &&
64
+ node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
65
+ specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
66
+ specifier.imported.name === 'mockFirebase')) {
67
+ context.report({
68
+ node,
69
+ messageId: 'noMockFirebase',
70
+ });
71
+ }
72
+ },
73
+ };
74
+ },
75
+ });
76
+ //# sourceMappingURL=enforce-mock-firestore.js.map
@@ -0,0 +1 @@
1
+ export declare const enforceVerbNounNaming: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"functionVerbPhrase", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;