@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.
- package/README.md +1 -0
- package/lib/index.js +44 -2
- package/lib/rules/consistent-callback-naming.d.ts +2 -0
- package/lib/rules/consistent-callback-naming.js +93 -0
- 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 +130 -0
- package/lib/rules/enforce-serializable-params.d.ts +9 -0
- package/lib/rules/enforce-serializable-params.js +127 -0
- package/lib/rules/extract-global-constants.js +27 -6
- package/lib/rules/global-const-style.d.ts +5 -0
- package/lib/rules/global-const-style.js +110 -0
- 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-unused-props.d.ts +6 -0
- package/lib/rules/no-unused-props.js +91 -0
- package/lib/rules/require-dynamic-firebase-imports.d.ts +6 -0
- package/lib/rules/require-dynamic-firebase-imports.js +85 -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 +3 -3
- package/lib/rules/require-usememo-object-literals.d.ts +4 -0
- package/lib/rules/require-usememo-object-literals.js +58 -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 +5 -0
- package/lib/utils/graph/ClassGraphBuilder.js +4 -0
- package/lib/utils/graph/ClassGraphSorterReadability.js +8 -3
- package/package.json +2 -2
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const NON_SERIALIZABLE_TYPES = new Set([
|
|
6
|
+
'Date',
|
|
7
|
+
'DocumentReference',
|
|
8
|
+
'Timestamp',
|
|
9
|
+
'Map',
|
|
10
|
+
'Set',
|
|
11
|
+
'Symbol',
|
|
12
|
+
'Function',
|
|
13
|
+
'undefined',
|
|
14
|
+
]);
|
|
15
|
+
exports.default = (0, createRule_1.createRule)({
|
|
16
|
+
name: 'enforce-serializable-params',
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'problem',
|
|
19
|
+
docs: {
|
|
20
|
+
description: 'Enforce serializable parameters in Firebase Callable/HTTPS Cloud Functions',
|
|
21
|
+
recommended: 'error',
|
|
22
|
+
},
|
|
23
|
+
schema: [
|
|
24
|
+
{
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
additionalNonSerializableTypes: {
|
|
28
|
+
type: 'array',
|
|
29
|
+
items: { type: 'string' },
|
|
30
|
+
},
|
|
31
|
+
functionTypes: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
items: { type: 'string' },
|
|
34
|
+
default: ['CallableRequest'],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
messages: {
|
|
41
|
+
nonSerializableParam: 'Parameter type "{{ type }}" is not serializable',
|
|
42
|
+
nonSerializableProperty: 'Property "{{ prop }}" has non-serializable type "{{ type }}"',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [
|
|
46
|
+
{
|
|
47
|
+
additionalNonSerializableTypes: [],
|
|
48
|
+
functionTypes: ['CallableRequest'],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
create(context, [options]) {
|
|
52
|
+
const allNonSerializableTypes = new Set([
|
|
53
|
+
...NON_SERIALIZABLE_TYPES,
|
|
54
|
+
...(options.additionalNonSerializableTypes || []),
|
|
55
|
+
]);
|
|
56
|
+
const typeAliasMap = new Map();
|
|
57
|
+
function isNonSerializableType(typeName) {
|
|
58
|
+
return allNonSerializableTypes.has(typeName);
|
|
59
|
+
}
|
|
60
|
+
function checkTypeNode(node, propName) {
|
|
61
|
+
if (!node)
|
|
62
|
+
return;
|
|
63
|
+
switch (node.type) {
|
|
64
|
+
case utils_1.AST_NODE_TYPES.TSTypeReference: {
|
|
65
|
+
const typeName = node.typeName.name;
|
|
66
|
+
if (isNonSerializableType(typeName)) {
|
|
67
|
+
context.report({
|
|
68
|
+
node,
|
|
69
|
+
messageId: propName
|
|
70
|
+
? 'nonSerializableProperty'
|
|
71
|
+
: 'nonSerializableParam',
|
|
72
|
+
data: {
|
|
73
|
+
type: typeName,
|
|
74
|
+
prop: propName,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// Check type parameters of generic types (like Array<T>)
|
|
79
|
+
if (node.typeParameters) {
|
|
80
|
+
node.typeParameters.params.forEach((param) => checkTypeNode(param, propName));
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case utils_1.AST_NODE_TYPES.TSArrayType:
|
|
85
|
+
checkTypeNode(node.elementType, propName);
|
|
86
|
+
break;
|
|
87
|
+
case utils_1.AST_NODE_TYPES.TSTypeAnnotation:
|
|
88
|
+
checkTypeNode(node.typeAnnotation, propName);
|
|
89
|
+
break;
|
|
90
|
+
case utils_1.AST_NODE_TYPES.TSTypeLiteral:
|
|
91
|
+
node.members.forEach((member) => {
|
|
92
|
+
if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature) {
|
|
93
|
+
const propertyName = member.key.name;
|
|
94
|
+
checkTypeNode(member.typeAnnotation, propertyName);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
case utils_1.AST_NODE_TYPES.TSUnionType:
|
|
99
|
+
node.types.forEach((type) => checkTypeNode(type, propName));
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
TSTypeAliasDeclaration(node) {
|
|
105
|
+
typeAliasMap.set(node.id.name, node);
|
|
106
|
+
},
|
|
107
|
+
TSTypeReference(node) {
|
|
108
|
+
const typeName = node.typeName.name;
|
|
109
|
+
if (options.functionTypes.includes(typeName) &&
|
|
110
|
+
node.typeParameters?.params[0]) {
|
|
111
|
+
const typeParam = node.typeParameters.params[0];
|
|
112
|
+
if (typeParam.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
113
|
+
const referencedTypeName = typeParam.typeName.name;
|
|
114
|
+
const typeAlias = typeAliasMap.get(referencedTypeName);
|
|
115
|
+
if (typeAlias) {
|
|
116
|
+
checkTypeNode(typeAlias.typeAnnotation);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
checkTypeNode(typeParam);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=enforce-serializable-params.js.map
|
|
@@ -3,6 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.extractGlobalConstants = void 0;
|
|
4
4
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
function isInsideFunction(node) {
|
|
7
|
+
let current = node;
|
|
8
|
+
while (current) {
|
|
9
|
+
if (current.type === 'FunctionDeclaration' ||
|
|
10
|
+
current.type === 'FunctionExpression' ||
|
|
11
|
+
current.type === 'ArrowFunctionExpression') {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
current = current.parent;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
function isFunctionDefinition(node) {
|
|
19
|
+
return (node?.type === 'FunctionExpression' ||
|
|
20
|
+
node?.type === 'ArrowFunctionExpression');
|
|
21
|
+
}
|
|
6
22
|
exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
7
23
|
create(context) {
|
|
8
24
|
return {
|
|
@@ -10,13 +26,19 @@ exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
|
10
26
|
if (node.kind !== 'const') {
|
|
11
27
|
return;
|
|
12
28
|
}
|
|
29
|
+
// Skip if any of the declarations are function definitions
|
|
30
|
+
if (node.declarations.some((d) => isFunctionDefinition(d.init))) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
13
33
|
const scope = context.getScope();
|
|
14
34
|
const hasDependencies = node.declarations.some((declaration) => declaration.init &&
|
|
15
35
|
ASTHelpers_1.ASTHelpers.declarationIncludesIdentifier(declaration.init));
|
|
36
|
+
// Only check function/block scoped constants without dependencies
|
|
16
37
|
if (!hasDependencies &&
|
|
17
|
-
(scope.type === 'function' || scope.type === 'block')
|
|
18
|
-
|
|
19
|
-
const constName = node.declarations[0].id
|
|
38
|
+
(scope.type === 'function' || scope.type === 'block') &&
|
|
39
|
+
isInsideFunction(node)) {
|
|
40
|
+
const constName = node.declarations[0].id
|
|
41
|
+
.name;
|
|
20
42
|
context.report({
|
|
21
43
|
node,
|
|
22
44
|
messageId: 'extractGlobalConstants',
|
|
@@ -34,8 +56,7 @@ exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
|
34
56
|
const scope = context.getScope();
|
|
35
57
|
const hasDependencies = ASTHelpers_1.ASTHelpers.blockIncludesIdentifier(node.body);
|
|
36
58
|
if (!hasDependencies && scope.type === 'function') {
|
|
37
|
-
|
|
38
|
-
const funcName = node.id.name;
|
|
59
|
+
const funcName = node.id?.name;
|
|
39
60
|
context.report({
|
|
40
61
|
node,
|
|
41
62
|
messageId: 'extractGlobalConstants',
|
|
@@ -52,7 +73,7 @@ exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
|
52
73
|
meta: {
|
|
53
74
|
type: 'suggestion',
|
|
54
75
|
docs: {
|
|
55
|
-
description: 'Extract constants
|
|
76
|
+
description: 'Extract static constants and functions to the global scope when possible',
|
|
56
77
|
recommended: 'error',
|
|
57
78
|
},
|
|
58
79
|
schema: [],
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"upperSnakeCase" | "asConst", never[], {
|
|
3
|
+
VariableDeclaration(node: TSESTree.VariableDeclaration): void;
|
|
4
|
+
}>;
|
|
5
|
+
export default _default;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const isUpperSnakeCase = (str) => /^[A-Z][A-Z0-9_]*$/.test(str);
|
|
6
|
+
exports.default = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'global-const-style',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce UPPER_SNAKE_CASE and as const for global static constants',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
upperSnakeCase: 'Global constants should be in UPPER_SNAKE_CASE',
|
|
18
|
+
asConst: 'Global constants should use "as const"',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
return {
|
|
24
|
+
VariableDeclaration(node) {
|
|
25
|
+
// Only check top-level const declarations
|
|
26
|
+
if (node.kind !== 'const') {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Skip if not at program level
|
|
30
|
+
if (node.parent?.type !== utils_1.AST_NODE_TYPES.Program) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Skip if any declaration is a function component or arrow function
|
|
34
|
+
const shouldSkip = node.declarations.some(declaration => {
|
|
35
|
+
if (declaration.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const name = declaration.id.name;
|
|
39
|
+
const init = declaration.init;
|
|
40
|
+
return (
|
|
41
|
+
// Skip function components (uppercase name + arrow function)
|
|
42
|
+
(/^[A-Z]/.test(name) && init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) ||
|
|
43
|
+
// Skip any arrow function
|
|
44
|
+
init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
|
|
45
|
+
});
|
|
46
|
+
if (shouldSkip) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
node.declarations.forEach((declaration) => {
|
|
50
|
+
// Skip destructuring patterns
|
|
51
|
+
if (declaration.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const { name } = declaration.id;
|
|
55
|
+
const init = declaration.init;
|
|
56
|
+
// Skip if no initializer or if it's a dynamic value
|
|
57
|
+
if (!init ||
|
|
58
|
+
init.type === utils_1.AST_NODE_TYPES.CallExpression ||
|
|
59
|
+
init.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Check for UPPER_SNAKE_CASE
|
|
63
|
+
if (!isUpperSnakeCase(name)) {
|
|
64
|
+
context.report({
|
|
65
|
+
node: declaration.id,
|
|
66
|
+
messageId: 'upperSnakeCase',
|
|
67
|
+
fix(fixer) {
|
|
68
|
+
const newName = name
|
|
69
|
+
.replace(/([A-Z])/g, '_$1')
|
|
70
|
+
.toUpperCase()
|
|
71
|
+
.replace(/^_/, '');
|
|
72
|
+
return fixer.replaceText(declaration.id, newName);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Check for as const
|
|
77
|
+
const isAsConstExpression = (node) => {
|
|
78
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
79
|
+
return (node.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
80
|
+
node.typeAnnotation?.typeName?.name === 'const');
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
const shouldHaveAsConst = (node) => {
|
|
85
|
+
// Skip if it's already an as const expression
|
|
86
|
+
if (isAsConstExpression(node)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
// Check if it's a literal, array, or object that should have as const
|
|
90
|
+
return (node.type === utils_1.AST_NODE_TYPES.Literal ||
|
|
91
|
+
node.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
|
|
92
|
+
node.type === utils_1.AST_NODE_TYPES.ObjectExpression);
|
|
93
|
+
};
|
|
94
|
+
if (shouldHaveAsConst(init)) {
|
|
95
|
+
context.report({
|
|
96
|
+
node: init,
|
|
97
|
+
messageId: 'asConst',
|
|
98
|
+
fix(fixer) {
|
|
99
|
+
const sourceCode = context.getSourceCode();
|
|
100
|
+
const initText = sourceCode.getText(init);
|
|
101
|
+
return fixer.replaceText(init, `${initText} as const`);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=global-const-style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noJsxWhitespaceLiteral: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noWhitespaceLiteral", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noJsxWhitespaceLiteral = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
exports.noJsxWhitespaceLiteral = (0, createRule_1.createRule)({
|
|
6
|
+
name: 'no-jsx-whitespace-literal',
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Disallow the use of {" "} elements in JSX code',
|
|
11
|
+
recommended: 'error',
|
|
12
|
+
},
|
|
13
|
+
schema: [],
|
|
14
|
+
messages: {
|
|
15
|
+
noWhitespaceLiteral: 'Avoid using {" "} for spacing in JSX. Use proper text nodes or CSS spacing instead.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
JSXExpressionContainer(node) {
|
|
22
|
+
if (node.expression.type === 'Literal' &&
|
|
23
|
+
typeof node.expression.value === 'string' &&
|
|
24
|
+
node.expression.value.trim() === '') {
|
|
25
|
+
context.report({
|
|
26
|
+
node,
|
|
27
|
+
messageId: 'noWhitespaceLiteral',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=no-jsx-whitespace-literal.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const noUnusedProps: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"unusedProp", never[], {
|
|
3
|
+
TSTypeAliasDeclaration(node: TSESTree.TSTypeAliasDeclaration): void;
|
|
4
|
+
VariableDeclaration(node: TSESTree.VariableDeclaration): void;
|
|
5
|
+
'VariableDeclaration:exit'(node: never): void;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noUnusedProps = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-unused-props',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Detect unused props in React component type definitions',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
unusedProp: 'Prop "{{propName}}" is defined in type but not used in component',
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const propsTypes = new Map();
|
|
23
|
+
const usedProps = new Map();
|
|
24
|
+
let currentComponent = null;
|
|
25
|
+
return {
|
|
26
|
+
TSTypeAliasDeclaration(node) {
|
|
27
|
+
if (node.id.name.endsWith('Props')) {
|
|
28
|
+
if (node.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
|
|
29
|
+
const props = {};
|
|
30
|
+
node.typeAnnotation.members.forEach((member) => {
|
|
31
|
+
if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
|
|
32
|
+
member.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
33
|
+
props[member.key.name] = member.key;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
propsTypes.set(node.id.name, props);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
VariableDeclaration(node) {
|
|
41
|
+
if (node.declarations.length === 1) {
|
|
42
|
+
const declaration = node.declarations[0];
|
|
43
|
+
if (declaration.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
44
|
+
const param = declaration.init.params[0];
|
|
45
|
+
if (param?.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
46
|
+
param.typeAnnotation?.typeAnnotation.type ===
|
|
47
|
+
utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
48
|
+
param.typeAnnotation.typeAnnotation.typeName.type ===
|
|
49
|
+
utils_1.AST_NODE_TYPES.Identifier) {
|
|
50
|
+
const typeName = param.typeAnnotation.typeAnnotation.typeName.name;
|
|
51
|
+
if (typeName.endsWith('Props')) {
|
|
52
|
+
currentComponent = { node, typeName };
|
|
53
|
+
const used = new Set();
|
|
54
|
+
param.properties.forEach((prop) => {
|
|
55
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
56
|
+
prop.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
57
|
+
used.add(prop.key.name);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
usedProps.set(typeName, used);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
'VariableDeclaration:exit'(node) {
|
|
67
|
+
if (currentComponent?.node === node) {
|
|
68
|
+
const { typeName } = currentComponent;
|
|
69
|
+
const propsType = propsTypes.get(typeName);
|
|
70
|
+
const used = usedProps.get(typeName);
|
|
71
|
+
if (propsType && used) {
|
|
72
|
+
Object.keys(propsType).forEach((prop) => {
|
|
73
|
+
if (!used.has(prop)) {
|
|
74
|
+
context.report({
|
|
75
|
+
node: propsType[prop],
|
|
76
|
+
messageId: 'unusedProp',
|
|
77
|
+
data: { propName: prop },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Reset state for this component
|
|
83
|
+
propsTypes.delete(typeName);
|
|
84
|
+
usedProps.delete(typeName);
|
|
85
|
+
currentComponent = null;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=no-unused-props.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const RULE_NAME = "require-dynamic-firebase-imports";
|
|
3
|
+
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"requireDynamicImport", never[], {
|
|
4
|
+
ImportDeclaration(node: TSESTree.ImportDeclaration): void;
|
|
5
|
+
}>;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
exports.RULE_NAME = 'require-dynamic-firebase-imports';
|
|
6
|
+
exports.default = (0, createRule_1.createRule)({
|
|
7
|
+
name: exports.RULE_NAME,
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce dynamic imports for Firebase dependencies',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
requireDynamicImport: 'Firebase dependencies must be imported dynamically to reduce bundle size',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const isFirebaseImport = (source) => {
|
|
23
|
+
return (source.startsWith('firebase/') ||
|
|
24
|
+
source.includes('config/firebase-client'));
|
|
25
|
+
};
|
|
26
|
+
const createDynamicImport = (node) => {
|
|
27
|
+
const importSource = node.source.value;
|
|
28
|
+
const importSpecifiers = node.specifiers;
|
|
29
|
+
if (importSpecifiers.length === 0) {
|
|
30
|
+
// For side-effect imports like 'firebase/auth'
|
|
31
|
+
return `await import('${importSource}');`;
|
|
32
|
+
}
|
|
33
|
+
if (importSpecifiers.length === 1) {
|
|
34
|
+
const spec = importSpecifiers[0];
|
|
35
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
36
|
+
// For default imports
|
|
37
|
+
return `const ${spec.local.name} = (await import('${importSource}')).default;`;
|
|
38
|
+
}
|
|
39
|
+
if (spec.type === 'ImportSpecifier') {
|
|
40
|
+
// For single named import
|
|
41
|
+
const importedName = spec.imported.name;
|
|
42
|
+
const localName = spec.local.name;
|
|
43
|
+
if (importedName === localName) {
|
|
44
|
+
return `const { ${localName} } = await import('${importSource}');`;
|
|
45
|
+
}
|
|
46
|
+
return `const { ${importedName}: ${localName} } = await import('${importSource}');`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// For multiple named imports
|
|
50
|
+
const importedModule = `await import('${importSource}')`;
|
|
51
|
+
const namedImports = importSpecifiers
|
|
52
|
+
.map((spec) => {
|
|
53
|
+
if (spec.type === 'ImportSpecifier') {
|
|
54
|
+
const importedName = spec.imported.name;
|
|
55
|
+
const localName = spec.local.name;
|
|
56
|
+
return importedName === localName
|
|
57
|
+
? localName
|
|
58
|
+
: `${importedName}: ${localName}`;
|
|
59
|
+
}
|
|
60
|
+
return '';
|
|
61
|
+
})
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
.join(', ');
|
|
64
|
+
return `const { ${namedImports} } = ${importedModule};`;
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
ImportDeclaration(node) {
|
|
68
|
+
const importSource = node.source.value;
|
|
69
|
+
if (typeof importSource === 'string' &&
|
|
70
|
+
isFirebaseImport(importSource) &&
|
|
71
|
+
!node.importKind?.includes('type')) {
|
|
72
|
+
context.report({
|
|
73
|
+
node,
|
|
74
|
+
messageId: 'requireDynamicImport',
|
|
75
|
+
fix(fixer) {
|
|
76
|
+
const dynamicImport = createDynamicImport(node);
|
|
77
|
+
return fixer.replaceText(node, dynamicImport);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=require-dynamic-firebase-imports.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useHttpsError" | "useProprietaryHttpsError", never[], {} | {
|
|
3
|
+
ImportDeclaration(node: TSESTree.ImportDeclaration): void;
|
|
4
|
+
ThrowStatement(node: TSESTree.ThrowStatement): void;
|
|
5
|
+
}>;
|
|
6
|
+
export = _default;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
3
|
+
const createRule_1 = require("../utils/createRule");
|
|
4
|
+
module.exports = (0, createRule_1.createRule)({
|
|
5
|
+
name: 'require-https-error',
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'problem',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Enforce using proprietary HttpsError instead of throw new Error or firebase-admin HttpsError in functions/src',
|
|
10
|
+
recommended: 'error',
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
useHttpsError: 'Use HttpsError instead of throw new Error in functions/src directory',
|
|
15
|
+
useProprietaryHttpsError: 'Use our proprietary HttpsError instead of firebase-admin HttpsError',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create(context) {
|
|
20
|
+
const filename = context.getFilename();
|
|
21
|
+
// Only apply rule to files in functions/src directory
|
|
22
|
+
if (!filename.includes('functions/src')) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
let hasFirebaseAdminImport = false;
|
|
26
|
+
let httpsIdentifier = null;
|
|
27
|
+
return {
|
|
28
|
+
ImportDeclaration(node) {
|
|
29
|
+
if (node.source.value === 'firebase-admin' ||
|
|
30
|
+
node.source.value === 'firebase-admin/lib/https-error') {
|
|
31
|
+
hasFirebaseAdminImport = true;
|
|
32
|
+
// Track the local name of the https import
|
|
33
|
+
const httpsSpecifier = node.specifiers.find((spec) => spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
34
|
+
spec.imported.name === 'https');
|
|
35
|
+
if (httpsSpecifier && 'local' in httpsSpecifier) {
|
|
36
|
+
httpsIdentifier = httpsSpecifier.local.name;
|
|
37
|
+
}
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: 'useProprietaryHttpsError',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
ThrowStatement(node) {
|
|
45
|
+
const argument = node.argument;
|
|
46
|
+
if (argument &&
|
|
47
|
+
argument.type === utils_1.AST_NODE_TYPES.NewExpression &&
|
|
48
|
+
argument.callee) {
|
|
49
|
+
if (argument.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
50
|
+
argument.callee.name === 'Error') {
|
|
51
|
+
context.report({
|
|
52
|
+
node,
|
|
53
|
+
messageId: 'useHttpsError',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else if (hasFirebaseAdminImport &&
|
|
57
|
+
((argument.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
58
|
+
argument.callee.name === 'HttpsError') ||
|
|
59
|
+
(argument.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
60
|
+
argument.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
61
|
+
argument.callee.object.name === httpsIdentifier &&
|
|
62
|
+
argument.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
63
|
+
argument.callee.property.name === 'HttpsError'))) {
|
|
64
|
+
context.report({
|
|
65
|
+
node,
|
|
66
|
+
messageId: 'useProprietaryHttpsError',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=require-https-error.js.map
|