@blumintinc/eslint-plugin-blumint 1.0.4 → 1.1.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 +1 -0
- package/lib/index.js +44 -2
- package/lib/rules/class-methods-read-top-to-bottom.js +1 -0
- package/lib/rules/consistent-callback-naming.d.ts +2 -0
- package/lib/rules/consistent-callback-naming.js +84 -0
- package/lib/rules/dynamic-https-errors.js +0 -1
- package/lib/rules/enforce-callable-types.d.ts +3 -0
- package/lib/rules/enforce-callable-types.js +97 -0
- package/lib/rules/enforce-callback-memo.d.ts +3 -0
- package/lib/rules/enforce-callback-memo.js +79 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +4 -0
- package/lib/rules/enforce-dynamic-firebase-imports.js +49 -0
- package/lib/rules/enforce-identifiable-firestore-type.d.ts +3 -0
- package/lib/rules/enforce-identifiable-firestore-type.js +89 -0
- package/lib/rules/enforce-serializable-params.d.ts +9 -0
- package/lib/rules/enforce-serializable-params.js +127 -0
- package/lib/rules/export-if-in-doubt.js +0 -1
- package/lib/rules/extract-global-constants.js +2 -0
- package/lib/rules/global-const-style.d.ts +5 -0
- package/lib/rules/global-const-style.js +77 -0
- package/lib/rules/no-conditional-literals-in-jsx.js +0 -1
- package/lib/rules/no-jsx-whitespace-literal.d.ts +1 -0
- package/lib/rules/no-jsx-whitespace-literal.js +34 -0
- package/lib/rules/no-unpinned-dependencies.js +1 -0
- package/lib/rules/no-unused-props.d.ts +6 -0
- package/lib/rules/no-unused-props.js +91 -0
- package/lib/rules/no-useless-fragment.js +2 -0
- package/lib/rules/prefer-fragment-shorthand.js +1 -0
- package/lib/rules/prefer-type-over-interface.js +3 -1
- package/lib/rules/require-dynamic-firebase-imports.d.ts +6 -0
- package/lib/rules/require-dynamic-firebase-imports.js +84 -0
- package/lib/rules/require-https-error.d.ts +6 -0
- package/lib/rules/require-https-error.js +74 -0
- package/lib/rules/require-image-overlayed.d.ts +7 -0
- package/lib/rules/require-image-overlayed.js +80 -0
- package/lib/rules/require-memo.js +5 -3
- package/lib/rules/require-usememo-object-literals.d.ts +4 -0
- package/lib/rules/require-usememo-object-literals.js +53 -0
- package/lib/rules/use-custom-link.d.ts +1 -0
- package/lib/rules/use-custom-link.js +49 -0
- package/lib/rules/use-custom-router.d.ts +1 -0
- package/lib/rules/use-custom-router.js +65 -0
- package/lib/utils/ASTHelpers.js +2 -0
- package/lib/utils/graph/ClassGraphBuilder.js +4 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +8 -3
- package/package.json +3 -4
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
|
|
38
|
+
version: '1.1.0',
|
|
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
|
-
|
|
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
|
|
@@ -46,6 +46,7 @@ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
|
|
|
46
46
|
const newClassBody = sortedOrder
|
|
47
47
|
.map((n) => {
|
|
48
48
|
// Fetch the actual AST node corresponding to the name
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
49
50
|
const memberNode = node.body.find((member) => getMemberName(member) === n);
|
|
50
51
|
const comments = sourceCode.getCommentsBefore(memberNode);
|
|
51
52
|
memberNode.range = [
|
|
@@ -0,0 +1,84 @@
|
|
|
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 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
|
+
return {
|
|
21
|
+
// Check JSX attributes for callback props
|
|
22
|
+
JSXAttribute(node) {
|
|
23
|
+
if (node.value &&
|
|
24
|
+
node.value.type === 'JSXExpressionContainer' &&
|
|
25
|
+
node.value.expression.type === 'Identifier') {
|
|
26
|
+
const propName = node.name.name;
|
|
27
|
+
const valueName = node.value.expression.name;
|
|
28
|
+
// Skip React's built-in event handlers
|
|
29
|
+
if (propName.match(/^on[A-Z]/)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Check if it's a function prop but doesn't follow the 'on' prefix convention
|
|
33
|
+
if (valueName &&
|
|
34
|
+
typeof valueName === 'string' &&
|
|
35
|
+
(valueName.startsWith('handle') || valueName.match(/^[a-z]+[A-Z]/))) {
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
messageId: 'callbackPropPrefix',
|
|
39
|
+
fix(fixer) {
|
|
40
|
+
// Convert camelCase to PascalCase for the event name
|
|
41
|
+
const eventName = propName.charAt(0).toUpperCase() + propName.slice(1);
|
|
42
|
+
return fixer.replaceText(node.name, `on${eventName}`);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
// Check function declarations and variable declarations for callback functions
|
|
49
|
+
'FunctionDeclaration, VariableDeclarator'(node) {
|
|
50
|
+
const functionName = node.id?.name;
|
|
51
|
+
if (functionName && functionName.startsWith('handle')) {
|
|
52
|
+
context.report({
|
|
53
|
+
node,
|
|
54
|
+
messageId: 'callbackFunctionPrefix',
|
|
55
|
+
fix(fixer) {
|
|
56
|
+
// Remove 'handle' prefix and convert first character to lowercase
|
|
57
|
+
const newName = functionName.slice(6).charAt(0).toLowerCase() +
|
|
58
|
+
functionName.slice(7);
|
|
59
|
+
return fixer.replaceText(node.id, newName);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
// Check object property methods
|
|
65
|
+
Property(node) {
|
|
66
|
+
if (node.method &&
|
|
67
|
+
node.key.name &&
|
|
68
|
+
node.key.name.startsWith('handle')) {
|
|
69
|
+
context.report({
|
|
70
|
+
node: node.key,
|
|
71
|
+
messageId: 'callbackFunctionPrefix',
|
|
72
|
+
fix(fixer) {
|
|
73
|
+
// Remove 'handle' prefix and convert first character to lowercase
|
|
74
|
+
const newName = node.key.name.slice(6).charAt(0).toLowerCase() +
|
|
75
|
+
node.key.name.slice(7);
|
|
76
|
+
return fixer.replaceText(node.key, newName);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=consistent-callback-naming.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dynamicHttpsErrors = void 0;
|
|
4
|
-
/* eslint-disable @blumintinc/blumint/extract-global-constants */
|
|
5
4
|
const createRule_1 = require("../utils/createRule");
|
|
6
5
|
const isHttpsErrorCall = (callee) => {
|
|
7
6
|
if (callee.type === 'MemberExpression') {
|
|
@@ -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,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,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,89 @@
|
|
|
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
|
+
// Check if type extends Identifiable directly or through a generic type
|
|
68
|
+
const checkIdentifiable = (type) => {
|
|
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
|
+
if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
|
|
75
|
+
return type.types.some(checkIdentifiable);
|
|
76
|
+
}
|
|
77
|
+
if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
78
|
+
type.typeParameters?.params) {
|
|
79
|
+
return type.typeParameters.params.some(checkIdentifiable);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
};
|
|
83
|
+
typeHasIdentifiable = checkIdentifiable(node.typeAnnotation);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
//# 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;
|