@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
|
@@ -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
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.exportIfInDoubt = void 0;
|
|
4
|
-
/* eslint-disable @blumintinc/blumint/extract-global-constants */
|
|
5
4
|
// import { ASTHelpers } from '../utils/ASTHelpers';
|
|
6
5
|
const createRule_1 = require("../utils/createRule");
|
|
7
6
|
exports.exportIfInDoubt = (0, createRule_1.createRule)({
|
|
@@ -15,6 +15,7 @@ exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
|
15
15
|
ASTHelpers_1.ASTHelpers.declarationIncludesIdentifier(declaration.init));
|
|
16
16
|
if (!hasDependencies &&
|
|
17
17
|
(scope.type === 'function' || scope.type === 'block')) {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
19
|
const constName = node.declarations[0].id.name;
|
|
19
20
|
context.report({
|
|
20
21
|
node,
|
|
@@ -33,6 +34,7 @@ exports.extractGlobalConstants = (0, createRule_1.createRule)({
|
|
|
33
34
|
const scope = context.getScope();
|
|
34
35
|
const hasDependencies = ASTHelpers_1.ASTHelpers.blockIncludesIdentifier(node.body);
|
|
35
36
|
if (!hasDependencies && scope.type === 'function') {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
38
|
const funcName = node.id.name;
|
|
37
39
|
context.report({
|
|
38
40
|
node,
|
|
@@ -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,77 @@
|
|
|
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
|
+
node.parent?.type !== utils_1.AST_NODE_TYPES.Program) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
node.declarations.forEach((declaration) => {
|
|
31
|
+
// Skip destructuring patterns
|
|
32
|
+
if (declaration.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const { name } = declaration.id;
|
|
36
|
+
const init = declaration.init;
|
|
37
|
+
// Skip if no initializer or if it's a dynamic value
|
|
38
|
+
if (!init ||
|
|
39
|
+
init.type === utils_1.AST_NODE_TYPES.CallExpression ||
|
|
40
|
+
init.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Check for UPPER_SNAKE_CASE
|
|
44
|
+
if (!isUpperSnakeCase(name)) {
|
|
45
|
+
context.report({
|
|
46
|
+
node: declaration.id,
|
|
47
|
+
messageId: 'upperSnakeCase',
|
|
48
|
+
fix(fixer) {
|
|
49
|
+
const newName = name
|
|
50
|
+
.replace(/([A-Z])/g, '_$1')
|
|
51
|
+
.toUpperCase()
|
|
52
|
+
.replace(/^_/, '');
|
|
53
|
+
return fixer.replaceText(declaration.id, newName);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Check for as const
|
|
58
|
+
if (init.type !== utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
59
|
+
init.typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypeReference ||
|
|
60
|
+
init.typeAnnotation.typeName.name !==
|
|
61
|
+
'const') {
|
|
62
|
+
context.report({
|
|
63
|
+
node: init,
|
|
64
|
+
messageId: 'asConst',
|
|
65
|
+
fix(fixer) {
|
|
66
|
+
const sourceCode = context.getSourceCode();
|
|
67
|
+
const initText = sourceCode.getText(init);
|
|
68
|
+
return fixer.replaceText(init, `${initText} as const`);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=global-const-style.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noConditionalLiteralsInJsx = void 0;
|
|
4
|
-
/* eslint-disable @blumintinc/blumint/extract-global-constants */
|
|
5
4
|
// import { ASTHelpers } from '../utils/ASTHelpers';
|
|
6
5
|
const createRule_1 = require("../utils/createRule");
|
|
7
6
|
const utils_1 = require("@typescript-eslint/utils");
|
|
@@ -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
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noUnpinnedDependencies = void 0;
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
6
|
exports.noUnpinnedDependencies = (0, createRule_1.createRule)({
|
|
6
7
|
meta: {
|
|
7
8
|
type: 'problem',
|
|
@@ -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
|
|
@@ -11,7 +11,9 @@ exports.noUselessFragment = {
|
|
|
11
11
|
messageId: 'noUselessFragment',
|
|
12
12
|
fix(fixer) {
|
|
13
13
|
const sourceCode = context.getSourceCode();
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
14
15
|
const openingFragment = sourceCode.getFirstToken(node);
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
15
17
|
const closingFragment = sourceCode.getLastToken(node);
|
|
16
18
|
return [
|
|
17
19
|
fixer.removeRange([
|
|
@@ -16,6 +16,7 @@ exports.preferFragmentShorthand = {
|
|
|
16
16
|
messageId: 'preferShorthand',
|
|
17
17
|
fix: (fixer) => [
|
|
18
18
|
fixer.replaceTextRange(openingElement.range, '<>'),
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
19
20
|
fixer.replaceTextRange(node.closingElement.range, '</>'),
|
|
20
21
|
],
|
|
21
22
|
});
|
|
@@ -33,7 +33,9 @@ exports.preferTypeOverInterface = (0, createRule_1.createRule)({
|
|
|
33
33
|
];
|
|
34
34
|
if (node.extends && node.extends.length > 0 && openingBrace) {
|
|
35
35
|
const extendsKeyword = sourceCode.getFirstTokenBetween(node.id, openingBrace, { filter: (token) => token.value === 'extends' });
|
|
36
|
-
fixes.push(
|
|
36
|
+
fixes.push(
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
38
|
+
fixer.remove(extendsKeyword), fixer.insertTextBefore(openingBrace, '& '));
|
|
37
39
|
}
|
|
38
40
|
return fixes;
|
|
39
41
|
},
|
|
@@ -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,84 @@
|
|
|
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
|
+
context.report({
|
|
72
|
+
node,
|
|
73
|
+
messageId: 'requireDynamicImport',
|
|
74
|
+
fix(fixer) {
|
|
75
|
+
const dynamicImport = createDynamicImport(node);
|
|
76
|
+
return fixer.replaceText(node, dynamicImport);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
//# 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
|