@blumintinc/eslint-plugin-blumint 1.0.5 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +1 -0
  2. package/lib/index.js +44 -2
  3. package/lib/rules/consistent-callback-naming.d.ts +2 -0
  4. package/lib/rules/consistent-callback-naming.js +93 -0
  5. package/lib/rules/enforce-callable-types.d.ts +3 -0
  6. package/lib/rules/enforce-callable-types.js +97 -0
  7. package/lib/rules/enforce-callback-memo.d.ts +3 -0
  8. package/lib/rules/enforce-callback-memo.js +79 -0
  9. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +4 -0
  10. package/lib/rules/enforce-dynamic-firebase-imports.js +49 -0
  11. package/lib/rules/enforce-identifiable-firestore-type.d.ts +3 -0
  12. package/lib/rules/enforce-identifiable-firestore-type.js +130 -0
  13. package/lib/rules/enforce-serializable-params.d.ts +9 -0
  14. package/lib/rules/enforce-serializable-params.js +127 -0
  15. package/lib/rules/extract-global-constants.js +27 -6
  16. package/lib/rules/global-const-style.d.ts +5 -0
  17. package/lib/rules/global-const-style.js +110 -0
  18. package/lib/rules/no-jsx-whitespace-literal.d.ts +1 -0
  19. package/lib/rules/no-jsx-whitespace-literal.js +34 -0
  20. package/lib/rules/no-unused-props.d.ts +6 -0
  21. package/lib/rules/no-unused-props.js +91 -0
  22. package/lib/rules/require-dynamic-firebase-imports.d.ts +6 -0
  23. package/lib/rules/require-dynamic-firebase-imports.js +85 -0
  24. package/lib/rules/require-https-error.d.ts +6 -0
  25. package/lib/rules/require-https-error.js +74 -0
  26. package/lib/rules/require-image-overlayed.d.ts +7 -0
  27. package/lib/rules/require-image-overlayed.js +80 -0
  28. package/lib/rules/require-memo.js +3 -3
  29. package/lib/rules/require-usememo-object-literals.d.ts +4 -0
  30. package/lib/rules/require-usememo-object-literals.js +58 -0
  31. package/lib/rules/use-custom-link.d.ts +1 -0
  32. package/lib/rules/use-custom-link.js +49 -0
  33. package/lib/rules/use-custom-router.d.ts +1 -0
  34. package/lib/rules/use-custom-router.js +65 -0
  35. package/lib/utils/ASTHelpers.js +5 -0
  36. package/lib/utils/graph/ClassGraphBuilder.js +4 -0
  37. package/lib/utils/graph/ClassGraphSorterReadability.js +8 -3
  38. package/package.json +2 -2
package/README.md CHANGED
@@ -64,6 +64,7 @@ Or use the recommended config:
64
64
  | [export-if-in-doubt](docs/rules/export-if-in-doubt.md) | All top-level const definitions, type definitions, and functions should be exported | | | |
65
65
  | [extract-global-constants](docs/rules/extract-global-constants.md) | Extract constants/functions to the global scope when possible | | | |
66
66
  | [generic-starts-with-t](docs/rules/generic-starts-with-t.md) | Enforce TypeScript generic types to start with T | | ✅ | |
67
+ | [global-const-style](docs/rules/global-const-style.md) | Enforce UPPER_SNAKE_CASE and as const for global static constants | ✅ | | 🔧 |
67
68
  | [no-async-array-filter](docs/rules/no-async-array-filter.md) | Disallow async callbacks for Array.filter | ✅ | | |
68
69
  | [no-async-foreach](docs/rules/no-async-foreach.md) | Disallow Array.forEach with an async callback function | ✅ | | |
69
70
  | [no-conditional-literals-in-jsx](docs/rules/no-conditional-literals-in-jsx.md) | Disallow use of conditional literals in JSX code | ✅ | | |
package/lib/index.js CHANGED
@@ -1,25 +1,41 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const array_methods_this_context_1 = require("./rules/array-methods-this-context");
4
7
  const class_methods_read_top_to_bottom_1 = require("./rules/class-methods-read-top-to-bottom");
8
+ const consistent_callback_naming_1 = __importDefault(require("./rules/consistent-callback-naming"));
5
9
  const dynamic_https_errors_1 = require("./rules/dynamic-https-errors");
10
+ const enforce_identifiable_firestore_type_1 = require("./rules/enforce-identifiable-firestore-type");
11
+ const enforce_callback_memo_1 = __importDefault(require("./rules/enforce-callback-memo"));
12
+ const enforce_callable_types_1 = require("./rules/enforce-callable-types");
13
+ const enforce_dynamic_firebase_imports_1 = require("./rules/enforce-dynamic-firebase-imports");
6
14
  const export_if_in_doubt_1 = require("./rules/export-if-in-doubt");
7
15
  const extract_global_constants_1 = require("./rules/extract-global-constants");
8
16
  const generic_starts_with_t_1 = require("./rules/generic-starts-with-t");
17
+ const global_const_style_1 = __importDefault(require("./rules/global-const-style"));
9
18
  const no_async_array_filter_1 = require("./rules/no-async-array-filter");
10
19
  const no_async_foreach_1 = require("./rules/no-async-foreach");
11
20
  const no_conditional_literals_in_jsx_1 = require("./rules/no-conditional-literals-in-jsx");
12
21
  const no_filter_without_return_1 = require("./rules/no-filter-without-return");
13
22
  const no_misused_switch_case_1 = require("./rules/no-misused-switch-case");
14
23
  const no_unpinned_dependencies_1 = require("./rules/no-unpinned-dependencies");
24
+ const no_unused_props_1 = require("./rules/no-unused-props");
15
25
  const no_useless_fragment_1 = require("./rules/no-useless-fragment");
16
26
  const prefer_fragment_shorthand_1 = require("./rules/prefer-fragment-shorthand");
17
27
  const prefer_type_over_interface_1 = require("./rules/prefer-type-over-interface");
18
28
  const require_memo_1 = require("./rules/require-memo");
29
+ const no_jsx_whitespace_literal_1 = require("./rules/no-jsx-whitespace-literal");
30
+ const require_dynamic_firebase_imports_1 = __importDefault(require("./rules/require-dynamic-firebase-imports"));
31
+ const require_https_error_1 = __importDefault(require("./rules/require-https-error"));
32
+ const use_custom_router_1 = require("./rules/use-custom-router");
33
+ const require_image_overlayed_1 = __importDefault(require("./rules/require-image-overlayed"));
34
+ const require_usememo_object_literals_1 = require("./rules/require-usememo-object-literals");
19
35
  module.exports = {
20
36
  meta: {
21
37
  name: '@blumintinc/eslint-plugin-blumint',
22
- version: '1.0.5',
38
+ version: '1.1.1',
23
39
  },
24
40
  parseOptions: {
25
41
  ecmaVersion: 2020,
@@ -28,42 +44,68 @@ module.exports = {
28
44
  recommended: {
29
45
  plugins: ['@blumintinc/blumint'],
30
46
  rules: {
47
+ '@blumintinc/blumint/no-jsx-whitespace-literal': 'error',
31
48
  '@blumintinc/blumint/array-methods-this-context': 'warn',
32
49
  '@blumintinc/blumint/class-methods-read-top-to-bottom': 'warn',
50
+ '@blumintinc/blumint/consistent-callback-naming': 'error',
33
51
  '@blumintinc/blumint/dynamic-https-errors': 'warn',
52
+ '@blumintinc/blumint/enforce-identifiable-firestore-type': 'error',
53
+ '@blumintinc/blumint/enforce-callback-memo': 'error',
54
+ '@blumintinc/blumint/enforce-callable-types': 'error',
55
+ '@blumintinc/blumint/enforce-dynamic-firebase-imports': 'error',
34
56
  // '@blumintinc/blumint/export-if-in-doubt': 'warn',
35
- // '@blumintinc/blumint/extract-global-constants': 'warn',
57
+ '@blumintinc/blumint/extract-global-constants': 'warn',
36
58
  '@blumintinc/blumint/generic-starts-with-t': 'warn',
59
+ '@blumintinc/blumint/global-const-style': 'error',
37
60
  '@blumintinc/blumint/no-async-array-filter': 'error',
38
61
  '@blumintinc/blumint/no-async-foreach': 'error',
39
62
  '@blumintinc/blumint/no-conditional-literals-in-jsx': 'error',
40
63
  '@blumintinc/blumint/no-filter-without-return': 'error',
41
64
  '@blumintinc/blumint/no-misused-switch-case': 'error',
42
65
  '@blumintinc/blumint/no-unpinned-dependencies': 'error',
66
+ '@blumintinc/blumint/no-unused-props': 'error',
43
67
  '@blumintinc/blumint/no-useless-fragment': 'warn',
44
68
  '@blumintinc/blumint/prefer-fragment-shorthand': 'warn',
45
69
  '@blumintinc/blumint/prefer-type-over-interface': 'warn',
46
70
  '@blumintinc/blumint/require-memo': 'error',
71
+ '@blumintinc/blumint/require-dynamic-firebase-imports': 'error',
72
+ '@blumintinc/blumint/require-https-error': 'error',
73
+ '@blumintinc/blumint/use-custom-router': 'error',
74
+ '@blumintinc/blumint/require-image-overlayed': 'error',
75
+ '@blumintinc/blumint/require-usememo-object-literals': 'error',
47
76
  },
48
77
  },
49
78
  },
50
79
  rules: {
51
80
  'array-methods-this-context': array_methods_this_context_1.arrayMethodsThisContext,
52
81
  'class-methods-read-top-to-bottom': class_methods_read_top_to_bottom_1.classMethodsReadTopToBottom,
82
+ 'consistent-callback-naming': consistent_callback_naming_1.default,
53
83
  'dynamic-https-errors': dynamic_https_errors_1.dynamicHttpsErrors,
84
+ 'enforce-identifiable-firestore-type': enforce_identifiable_firestore_type_1.enforceIdentifiableFirestoreType,
85
+ 'enforce-callback-memo': enforce_callback_memo_1.default,
86
+ 'enforce-callable-types': enforce_callable_types_1.enforceCallableTypes,
87
+ 'enforce-dynamic-firebase-imports': enforce_dynamic_firebase_imports_1.enforceFirebaseImports,
54
88
  'export-if-in-doubt': export_if_in_doubt_1.exportIfInDoubt,
55
89
  'extract-global-constants': extract_global_constants_1.extractGlobalConstants,
56
90
  'generic-starts-with-t': generic_starts_with_t_1.genericStartsWithT,
91
+ 'global-const-style': global_const_style_1.default,
57
92
  'no-async-array-filter': no_async_array_filter_1.noAsyncArrayFilter,
58
93
  'no-async-foreach': no_async_foreach_1.noAsyncForEach,
59
94
  'no-conditional-literals-in-jsx': no_conditional_literals_in_jsx_1.noConditionalLiteralsInJsx,
60
95
  'no-filter-without-return': no_filter_without_return_1.noFilterWithoutReturn,
61
96
  'no-misused-switch-case': no_misused_switch_case_1.noMisusedSwitchCase,
62
97
  'no-unpinned-dependencies': no_unpinned_dependencies_1.noUnpinnedDependencies,
98
+ 'no-unused-props': no_unused_props_1.noUnusedProps,
63
99
  'no-useless-fragment': no_useless_fragment_1.noUselessFragment,
64
100
  'prefer-fragment-shorthand': prefer_fragment_shorthand_1.preferFragmentShorthand,
65
101
  'prefer-type-over-interface': prefer_type_over_interface_1.preferTypeOverInterface,
66
102
  'require-memo': require_memo_1.requireMemo,
103
+ 'no-jsx-whitespace-literal': no_jsx_whitespace_literal_1.noJsxWhitespaceLiteral,
104
+ 'require-dynamic-firebase-imports': require_dynamic_firebase_imports_1.default,
105
+ 'require-https-error': require_https_error_1.default,
106
+ 'use-custom-router': use_custom_router_1.useCustomRouter,
107
+ 'require-image-overlayed': require_image_overlayed_1.default,
108
+ 'require-usememo-object-literals': require_usememo_object_literals_1.requireUseMemoObjectLiterals,
67
109
  },
68
110
  };
69
111
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"callbackPropPrefix" | "callbackFunctionPrefix", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
2
+ export = _default;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ const createRule_1 = require("../utils/createRule");
3
+ module.exports = (0, createRule_1.createRule)({
4
+ name: 'consistent-callback-naming',
5
+ meta: {
6
+ type: 'suggestion',
7
+ docs: {
8
+ description: 'Enforce consistent naming conventions for callback props and functions',
9
+ recommended: 'error',
10
+ },
11
+ fixable: 'code',
12
+ schema: [],
13
+ messages: {
14
+ callbackPropPrefix: 'Callback props (function type props) must be prefixed with "on" (e.g., onClick, onChange)',
15
+ callbackFunctionPrefix: 'Callback functions should not use "handle" prefix, use descriptive verb phrases instead',
16
+ },
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const parserServices = context.parserServices;
21
+ // Check if we have access to TypeScript services
22
+ if (!parserServices?.program || !parserServices?.esTreeNodeToTSNodeMap) {
23
+ throw new Error('You have to enable the `project` setting in parser options to use this rule');
24
+ }
25
+ const checker = parserServices.program.getTypeChecker();
26
+ function isFunctionType(node) {
27
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
28
+ const type = checker.getTypeAtLocation(tsNode);
29
+ return type.getCallSignatures().length > 0;
30
+ }
31
+ return {
32
+ // Check JSX attributes for callback props
33
+ JSXAttribute(node) {
34
+ if (node.value?.type === 'JSXExpressionContainer' &&
35
+ node.value.expression.type === 'Identifier') {
36
+ const propName = node.name.type === 'JSXIdentifier' ? node.name.name : undefined;
37
+ // Skip React's built-in event handlers
38
+ if (propName?.match(/^on[A-Z]/)) {
39
+ return;
40
+ }
41
+ // Check if the value is a function type
42
+ if (isFunctionType(node.value.expression) &&
43
+ propName &&
44
+ !propName.startsWith('on')) {
45
+ context.report({
46
+ node,
47
+ messageId: 'callbackPropPrefix',
48
+ fix(fixer) {
49
+ // Convert camelCase to PascalCase for the event name
50
+ const eventName = propName.charAt(0).toUpperCase() + propName.slice(1);
51
+ return fixer.replaceText(node.name, `on${eventName}`);
52
+ },
53
+ });
54
+ }
55
+ }
56
+ },
57
+ // Check function declarations and variable declarations for callback functions
58
+ 'FunctionDeclaration, VariableDeclarator'(node) {
59
+ const functionName = node.id?.type === 'Identifier' ? node.id.name : undefined;
60
+ if (functionName && functionName.startsWith('handle') && node.id) {
61
+ context.report({
62
+ node,
63
+ messageId: 'callbackFunctionPrefix',
64
+ fix(fixer) {
65
+ // Remove 'handle' prefix and convert first character to lowercase
66
+ const newName = functionName.slice(6).charAt(0).toLowerCase() +
67
+ functionName.slice(7);
68
+ return fixer.replaceText(node.id, newName);
69
+ },
70
+ });
71
+ }
72
+ },
73
+ // Check class methods and object methods
74
+ 'MethodDefinition, Property'(node) {
75
+ if (node.key.type === 'Identifier' &&
76
+ node.key.name &&
77
+ node.key.name.startsWith('handle')) {
78
+ const name = node.key.name;
79
+ context.report({
80
+ node: node.key,
81
+ messageId: 'callbackFunctionPrefix',
82
+ fix(fixer) {
83
+ // Remove 'handle' prefix and convert first character to lowercase
84
+ const newName = name.slice(6).charAt(0).toLowerCase() + name.slice(7);
85
+ return fixer.replaceText(node.key, newName);
86
+ },
87
+ });
88
+ }
89
+ },
90
+ };
91
+ },
92
+ });
93
+ //# sourceMappingURL=consistent-callback-naming.js.map
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'missingParamsType' | 'missingResponseType' | 'unusedParamsType' | 'unusedResponseType';
2
+ export declare const enforceCallableTypes: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceCallableTypes = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.enforceCallableTypes = (0, createRule_1.createRule)({
7
+ name: 'enforce-callable-types',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Enforce Params and Response type exports in callable functions',
12
+ recommended: 'error',
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ missingParamsType: 'Missing Params type export in callable function file',
17
+ missingResponseType: 'Missing Response type export in callable function file',
18
+ unusedParamsType: 'Params type is exported but not used in the callable function',
19
+ unusedResponseType: 'Response type is exported but not used in the callable function',
20
+ },
21
+ },
22
+ defaultOptions: [],
23
+ create(context) {
24
+ const filename = context.getFilename();
25
+ // Only apply to .f.ts files in the callable directory
26
+ if (!filename.endsWith('.f.ts') || !filename.includes('/callable/')) {
27
+ return {};
28
+ }
29
+ let hasParamsExport = false;
30
+ let hasResponseExport = false;
31
+ let hasCallableFunction = false;
32
+ let paramsTypeUsed = false;
33
+ let responseTypeUsed = false;
34
+ return {
35
+ // Check for type exports
36
+ ExportNamedDeclaration(node) {
37
+ if (node.declaration?.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
38
+ const typeName = node.declaration.id.name;
39
+ if (typeName === 'Params') {
40
+ hasParamsExport = true;
41
+ }
42
+ else if (typeName === 'Response') {
43
+ hasResponseExport = true;
44
+ }
45
+ }
46
+ },
47
+ // Check for onCall usage
48
+ CallExpression(node) {
49
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
50
+ node.callee.name === 'onCall') {
51
+ hasCallableFunction = true;
52
+ }
53
+ },
54
+ // Check for type usage in function parameters and return types
55
+ TSTypeReference(node) {
56
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
57
+ if (node.typeName.name === 'Params') {
58
+ paramsTypeUsed = true;
59
+ }
60
+ else if (node.typeName.name === 'Response') {
61
+ responseTypeUsed = true;
62
+ }
63
+ }
64
+ },
65
+ 'Program:exit'() {
66
+ if (!hasCallableFunction) {
67
+ return;
68
+ }
69
+ if (!hasParamsExport) {
70
+ context.report({
71
+ loc: { line: 1, column: 0 },
72
+ messageId: 'missingParamsType',
73
+ });
74
+ }
75
+ else if (!paramsTypeUsed) {
76
+ context.report({
77
+ loc: { line: 1, column: 0 },
78
+ messageId: 'unusedParamsType',
79
+ });
80
+ }
81
+ if (!hasResponseExport) {
82
+ context.report({
83
+ loc: { line: 1, column: 0 },
84
+ messageId: 'missingResponseType',
85
+ });
86
+ }
87
+ else if (!responseTypeUsed) {
88
+ context.report({
89
+ loc: { line: 1, column: 0 },
90
+ messageId: 'unusedResponseType',
91
+ });
92
+ }
93
+ },
94
+ };
95
+ },
96
+ });
97
+ //# sourceMappingURL=enforce-callable-types.js.map
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'enforceCallback' | 'enforceMemo';
2
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export default _default;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const createRule_1 = require("../utils/createRule");
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ exports.default = (0, createRule_1.createRule)({
6
+ name: 'enforce-callback-memo',
7
+ meta: {
8
+ type: 'suggestion',
9
+ docs: {
10
+ description: 'Enforce useCallback or useMemo for inline functions in JSX props',
11
+ recommended: 'error',
12
+ },
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',
16
+ },
17
+ schema: [],
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ function isFunction(node) {
22
+ return (node.type === utils_1.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ||
23
+ node.type === utils_1.TSESTree.AST_NODE_TYPES.FunctionExpression);
24
+ }
25
+ function containsFunction(node) {
26
+ if (isFunction(node)) {
27
+ return true;
28
+ }
29
+ if (node.type === utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression) {
30
+ return node.properties.some((prop) => {
31
+ if (prop.type === utils_1.TSESTree.AST_NODE_TYPES.Property &&
32
+ 'value' in prop) {
33
+ return containsFunction(prop.value);
34
+ }
35
+ return false;
36
+ });
37
+ }
38
+ if (node.type === utils_1.TSESTree.AST_NODE_TYPES.ArrayExpression) {
39
+ return node.elements.some((element) => element && containsFunction(element));
40
+ }
41
+ return false;
42
+ }
43
+ function checkJSXAttribute(node) {
44
+ if (!node.value ||
45
+ node.value.type !== utils_1.TSESTree.AST_NODE_TYPES.JSXExpressionContainer) {
46
+ return;
47
+ }
48
+ const { expression } = node.value;
49
+ // Skip if the prop is already wrapped in useCallback or useMemo
50
+ if (expression.type === utils_1.TSESTree.AST_NODE_TYPES.CallExpression &&
51
+ expression.callee.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
52
+ (expression.callee.name === 'useCallback' ||
53
+ expression.callee.name === 'useMemo')) {
54
+ return;
55
+ }
56
+ // Check for direct inline functions
57
+ if (isFunction(expression)) {
58
+ context.report({
59
+ node,
60
+ messageId: 'enforceCallback',
61
+ });
62
+ return;
63
+ }
64
+ // Check for objects/arrays containing functions
65
+ if ((expression.type === utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression ||
66
+ expression.type === utils_1.TSESTree.AST_NODE_TYPES.ArrayExpression) &&
67
+ containsFunction(expression)) {
68
+ context.report({
69
+ node,
70
+ messageId: 'enforceMemo',
71
+ });
72
+ }
73
+ }
74
+ return {
75
+ JSXAttribute: checkJSXAttribute,
76
+ };
77
+ },
78
+ });
79
+ //# sourceMappingURL=enforce-callback-memo.js.map
@@ -0,0 +1,4 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ export declare const enforceFirebaseImports: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noDynamicImport", never[], {
3
+ ImportDeclaration(node: TSESTree.ImportDeclaration): void;
4
+ }>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceFirebaseImports = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ exports.enforceFirebaseImports = (0, createRule_1.createRule)({
6
+ name: 'enforce-dynamic-firebase-imports',
7
+ meta: {
8
+ type: 'problem',
9
+ docs: {
10
+ description: 'Enforce dynamic importing for modules within the firebaseCloud directory',
11
+ recommended: 'error',
12
+ },
13
+ schema: [],
14
+ messages: {
15
+ noDynamicImport: 'Static imports from firebaseCloud directory are not allowed. Use dynamic imports instead.',
16
+ },
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ return {
21
+ ImportDeclaration(node) {
22
+ // Skip type-only imports
23
+ if (node.importKind === 'type') {
24
+ return;
25
+ }
26
+ const importPath = node.source.value;
27
+ // Check if the import is from firebaseCloud directory
28
+ if (importPath.includes('firebaseCloud/')) {
29
+ context.report({
30
+ node,
31
+ messageId: 'noDynamicImport',
32
+ fix(fixer) {
33
+ const importSpecifiers = node.specifiers
34
+ .filter((spec) => spec.type === 'ImportSpecifier')
35
+ .map((spec) => spec.imported.name);
36
+ if (importSpecifiers.length === 0) {
37
+ return null;
38
+ }
39
+ const destructuredImports = `{ ${importSpecifiers.join(', ')} }`;
40
+ const dynamicImport = `const ${destructuredImports} = await import('${importPath}');`;
41
+ return fixer.replaceText(node, dynamicImport);
42
+ },
43
+ });
44
+ }
45
+ },
46
+ };
47
+ },
48
+ });
49
+ //# sourceMappingURL=enforce-dynamic-firebase-imports.js.map
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'missingType' | 'notExtendingIdentifiable';
2
+ export declare const enforceIdentifiableFirestoreType: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.enforceIdentifiableFirestoreType = void 0;
7
+ const utils_1 = require("@typescript-eslint/utils");
8
+ const createRule_1 = require("../utils/createRule");
9
+ const path_1 = __importDefault(require("path"));
10
+ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
11
+ name: 'enforce-identifiable-firestore-type',
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description: 'Enforce that Firestore type definitions extend Identifiable and match their folder name',
16
+ recommended: 'error',
17
+ },
18
+ schema: [],
19
+ messages: {
20
+ missingType: 'Expected exported type "{{ typeName }}" in index.ts under folder "{{ folderName }}"',
21
+ notExtendingIdentifiable: 'Type "{{ typeName }}" must extend "Identifiable", including an "id: string" field',
22
+ },
23
+ },
24
+ defaultOptions: [],
25
+ create(context) {
26
+ const filename = context.getFilename();
27
+ const firestoreTypesPattern = /functions\/src\/types\/firestore\/.*\/index\.ts$/;
28
+ // Only apply rule to index.ts files in the firestore types directory
29
+ if (!firestoreTypesPattern.test(filename)) {
30
+ return {};
31
+ }
32
+ // Get the expected type name from the parent folder
33
+ const folderName = path_1.default.basename(path_1.default.dirname(filename));
34
+ let hasExpectedType = false;
35
+ let typeHasIdentifiable = false;
36
+ return {
37
+ Program() {
38
+ // Reset flags for each file
39
+ hasExpectedType = false;
40
+ typeHasIdentifiable = false;
41
+ },
42
+ 'Program:exit'(node) {
43
+ if (!hasExpectedType) {
44
+ context.report({
45
+ node,
46
+ messageId: 'missingType',
47
+ data: {
48
+ typeName: folderName,
49
+ folderName,
50
+ },
51
+ });
52
+ }
53
+ else if (!typeHasIdentifiable) {
54
+ context.report({
55
+ node,
56
+ messageId: 'notExtendingIdentifiable',
57
+ data: {
58
+ typeName: folderName,
59
+ },
60
+ });
61
+ }
62
+ },
63
+ TSTypeAliasDeclaration(node) {
64
+ if (node.id.name === folderName) {
65
+ hasExpectedType = true;
66
+ // Check if type extends Identifiable
67
+ const checkIdentifiableExtension = (type) => {
68
+ // Check for direct Identifiable extension
69
+ if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
70
+ type.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
71
+ type.typeName.name === 'Identifiable') {
72
+ return true;
73
+ }
74
+ // Check intersection types
75
+ if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
76
+ return type.types.some(checkIdentifiableExtension);
77
+ }
78
+ // Check generic type parameters
79
+ if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
80
+ type.typeParameters?.params) {
81
+ return type.typeParameters.params.some(checkIdentifiableExtension);
82
+ }
83
+ return false;
84
+ };
85
+ // Check if type has id: string field
86
+ const checkIdField = (type) => {
87
+ // Check for id: string field in type literal
88
+ if (type.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
89
+ return type.members.some((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
90
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
91
+ member.key.name === 'id' &&
92
+ member.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSStringKeyword);
93
+ }
94
+ // Check intersection types
95
+ if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
96
+ return type.types.some(checkIdField);
97
+ }
98
+ return false;
99
+ };
100
+ // Check if type is wrapped in a utility type
101
+ const isUtilityType = (type) => {
102
+ return (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
103
+ type.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
104
+ type.typeName.name === 'Resolve');
105
+ };
106
+ // Recursively check the type and its parameters
107
+ const checkType = (type) => {
108
+ // Check if type extends Identifiable
109
+ if (checkIdentifiableExtension(type)) {
110
+ return true;
111
+ }
112
+ // Check if type has id: string field (only for utility types)
113
+ if (isUtilityType(type) && checkIdField(type.typeParameters.params[0])) {
114
+ return true;
115
+ }
116
+ // Check if type is wrapped in a utility type
117
+ if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
118
+ type.typeParameters?.params?.[0]) {
119
+ return checkType(type.typeParameters.params[0]);
120
+ }
121
+ // For direct type definitions, require extending Identifiable
122
+ return false;
123
+ };
124
+ typeHasIdentifiable = checkType(node.typeAnnotation);
125
+ }
126
+ },
127
+ };
128
+ },
129
+ });
130
+ //# sourceMappingURL=enforce-identifiable-firestore-type.js.map
@@ -0,0 +1,9 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"nonSerializableParam" | "nonSerializableProperty", {
3
+ additionalNonSerializableTypes: never[];
4
+ functionTypes: string[];
5
+ }[], {
6
+ TSTypeAliasDeclaration(node: TSESTree.TSTypeAliasDeclaration): void;
7
+ TSTypeReference(node: TSESTree.TSTypeReference): void;
8
+ }>;
9
+ export default _default;