@blumintinc/eslint-plugin-blumint 1.4.0 → 1.5.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/lib/index.js +19 -1
- package/lib/rules/array-methods-this-context.js +2 -2
- package/lib/rules/enforce-callback-memo.js +3 -3
- package/lib/rules/enforce-dynamic-firebase-imports.js +2 -2
- package/lib/rules/enforce-exported-function-types.js +115 -85
- package/lib/rules/enforce-firestore-doc-ref-generic.js +2 -2
- package/lib/rules/enforce-firestore-path-utils.js +2 -2
- package/lib/rules/enforce-firestore-set-merge.js +35 -4
- package/lib/rules/enforce-identifiable-firestore-type.js +2 -2
- package/lib/rules/enforce-memoize-async.js +6 -4
- package/lib/rules/enforce-mock-firestore.js +3 -3
- package/lib/rules/enforce-realtimedb-path-utils.js +1 -1
- package/lib/rules/enforce-safe-stringify.js +2 -2
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-verb-noun-naming.js +34 -12
- package/lib/rules/no-async-array-filter.js +2 -2
- package/lib/rules/no-async-foreach.js +1 -1
- package/lib/rules/no-class-instance-destructuring.d.ts +1 -0
- package/lib/rules/no-class-instance-destructuring.js +95 -0
- package/lib/rules/no-compositing-layer-props.js +1 -1
- package/lib/rules/no-conditional-literals-in-jsx.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +1 -1
- package/lib/rules/no-explicit-return-type.d.ts +1 -0
- package/lib/rules/no-explicit-return-type.js +5 -2
- package/lib/rules/no-filter-without-return.js +1 -1
- package/lib/rules/no-firestore-object-arrays.d.ts +1 -0
- package/lib/rules/no-firestore-object-arrays.js +86 -0
- package/lib/rules/no-jsx-in-hooks.d.ts +1 -0
- package/lib/rules/no-jsx-in-hooks.js +200 -0
- package/lib/rules/no-memoize-on-static.d.ts +1 -0
- package/lib/rules/no-memoize-on-static.js +54 -0
- package/lib/rules/no-misused-switch-case.js +2 -2
- package/lib/rules/no-redundant-param-types.d.ts +2 -0
- package/lib/rules/no-redundant-param-types.js +129 -0
- package/lib/rules/no-unsafe-firestore-spread.d.ts +3 -0
- package/lib/rules/no-unsafe-firestore-spread.js +125 -0
- package/lib/rules/no-unused-props.js +1 -1
- package/lib/rules/no-useless-fragment.js +1 -1
- package/lib/rules/prefer-destructuring-no-class.d.ts +8 -0
- package/lib/rules/prefer-destructuring-no-class.js +200 -0
- package/lib/rules/require-hooks-default-params.d.ts +1 -0
- package/lib/rules/require-hooks-default-params.js +195 -0
- package/lib/rules/require-usememo-object-literals.js +1 -1
- package/lib/rules/semantic-function-prefixes.js +43 -4
- package/package.json +1 -1
|
@@ -7,7 +7,10 @@ exports.enforceVerbNounNaming = void 0;
|
|
|
7
7
|
const utils_1 = require("@typescript-eslint/utils");
|
|
8
8
|
const createRule_1 = require("../utils/createRule");
|
|
9
9
|
const compromise_1 = __importDefault(require("compromise"));
|
|
10
|
-
const
|
|
10
|
+
const verbs_json_1 = __importDefault(require("../utils/verbs.json"));
|
|
11
|
+
const PREPOSITIONS = new Set(['to', 'from', 'with', 'by', 'at', 'of']);
|
|
12
|
+
// Create a Set from the verbs list for O(1) lookup
|
|
13
|
+
const VERBS_SET = new Set(verbs_json_1.default);
|
|
11
14
|
exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
|
|
12
15
|
name: 'enforce-verb-noun-naming',
|
|
13
16
|
meta: {
|
|
@@ -31,28 +34,41 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
|
|
|
31
34
|
const words = rest.split(/(?=[A-Z])/);
|
|
32
35
|
return firstChar + words[0];
|
|
33
36
|
}
|
|
34
|
-
function
|
|
37
|
+
function toPhrase(name) {
|
|
35
38
|
return name.split(/(?=[A-Z])/).join(' ');
|
|
36
39
|
}
|
|
37
|
-
function getPossibleTags(sentence) {
|
|
40
|
+
function getPossibleTags(sentence, index = 0) {
|
|
38
41
|
const doc = (0, compromise_1.default)(sentence);
|
|
39
42
|
const terms = doc.terms().json();
|
|
40
|
-
if (terms.length
|
|
43
|
+
if (terms.length < index ||
|
|
44
|
+
!terms[index]?.terms?.length ||
|
|
45
|
+
!terms[index]?.terms[0]?.tags)
|
|
41
46
|
return [];
|
|
42
|
-
const tags = terms[
|
|
47
|
+
const tags = terms[index].terms[0].tags;
|
|
43
48
|
return tags;
|
|
44
49
|
}
|
|
45
50
|
function isVerbPhrase(name) {
|
|
46
51
|
const firstWord = extractFirstWord(name);
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
const firstWordLower = firstWord.toLowerCase();
|
|
53
|
+
// Check for prepositions first
|
|
54
|
+
if (PREPOSITIONS.has(firstWordLower)) {
|
|
49
55
|
return true;
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
// Check against our comprehensive verbs list first
|
|
58
|
+
if (VERBS_SET.has(firstWordLower)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// Fall back to NLP approach if not found in verbs list
|
|
62
|
+
const withoutPrefixTags = getPossibleTags(toPhrase(name));
|
|
63
|
+
// Check if either form is recognized as a verb
|
|
64
|
+
const isVerb = withoutPrefixTags.includes('Verb');
|
|
65
|
+
const isPreposition = withoutPrefixTags.includes('Preposition');
|
|
66
|
+
const isConjunction = withoutPrefixTags.includes('Conjunction');
|
|
67
|
+
// For non-prepositions/conjunctions, require verb form
|
|
68
|
+
if (isPreposition || isConjunction) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return isVerb;
|
|
56
72
|
}
|
|
57
73
|
function isJsxReturnFunction(node) {
|
|
58
74
|
if (node.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
@@ -99,6 +115,12 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
|
|
|
99
115
|
MethodDefinition(node) {
|
|
100
116
|
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
101
117
|
return;
|
|
118
|
+
// Skip getters since they represent properties and should use noun phrases
|
|
119
|
+
if (node.kind === 'get')
|
|
120
|
+
return;
|
|
121
|
+
// Skip constructors since they are special class methods
|
|
122
|
+
if (node.kind === 'constructor')
|
|
123
|
+
return;
|
|
102
124
|
if (!isVerbPhrase(node.key.name)) {
|
|
103
125
|
context.report({
|
|
104
126
|
node: node.key,
|
|
@@ -27,12 +27,12 @@ exports.noAsyncArrayFilter = (0, createRule_1.createRule)({
|
|
|
27
27
|
meta: {
|
|
28
28
|
type: 'problem',
|
|
29
29
|
docs: {
|
|
30
|
-
description: 'Disallow async callbacks
|
|
30
|
+
description: 'Disallow async callbacks in Array.filter() as they lead to incorrect filtering. Since async functions return Promises which are always truthy, the filter will keep all elements regardless of the async check\'s result. Use Promise.all() with map() first, then filter based on the resolved results.',
|
|
31
31
|
recommended: 'error',
|
|
32
32
|
},
|
|
33
33
|
schema: [],
|
|
34
34
|
messages: {
|
|
35
|
-
unexpected: 'Async array filter is dangerous as a Promise object will always be truthy.
|
|
35
|
+
unexpected: 'Async array filter is dangerous as a Promise object will always be truthy. Instead of `array.filter(async x => await someCheck(x))`, first resolve the promises with `Promise.all()` or move the async logic elsewhere: `const results = await Promise.all(array.map(x => someCheck(x))); array.filter((_, i) => results[i])`.',
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
38
|
defaultOptions: [],
|
|
@@ -24,7 +24,7 @@ exports.noAsyncForEach = {
|
|
|
24
24
|
meta: {
|
|
25
25
|
type: 'problem',
|
|
26
26
|
docs: {
|
|
27
|
-
description: 'Disallow Array.forEach with an async callback function',
|
|
27
|
+
description: 'Disallow Array.forEach with an async callback function as it does not wait for promises to resolve. This can lead to race conditions and unexpected behavior. Use a standard for...of loop for sequential execution or Promise.all with map for concurrent execution.',
|
|
28
28
|
recommended: 'error',
|
|
29
29
|
},
|
|
30
30
|
messages: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noClassInstanceDestructuring: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noClassInstanceDestructuring", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noClassInstanceDestructuring = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-class-instance-destructuring',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow destructuring of class instances to prevent loss of `this` context',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
noClassInstanceDestructuring: 'Avoid destructuring class instances as it can lead to loss of `this` context. Use direct property access instead.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
function isClassInstance(node) {
|
|
23
|
+
// Check for new expressions
|
|
24
|
+
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Check for identifiers that might be class instances
|
|
28
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
29
|
+
const variableDef = context
|
|
30
|
+
.getScope()
|
|
31
|
+
.variables.find((variableDef) => variableDef.name === node.name);
|
|
32
|
+
if (variableDef?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
33
|
+
const init = variableDef.defs[0].node
|
|
34
|
+
.init;
|
|
35
|
+
return init?.type === utils_1.AST_NODE_TYPES.NewExpression;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
VariableDeclarator(node) {
|
|
42
|
+
if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
43
|
+
node.init &&
|
|
44
|
+
isClassInstance(node.init)) {
|
|
45
|
+
const objectPattern = node.id;
|
|
46
|
+
context.report({
|
|
47
|
+
node,
|
|
48
|
+
messageId: 'noClassInstanceDestructuring',
|
|
49
|
+
fix(fixer) {
|
|
50
|
+
const sourceCode = context.getSourceCode();
|
|
51
|
+
const properties = objectPattern.properties;
|
|
52
|
+
// Skip if there's no init expression
|
|
53
|
+
if (!node.init)
|
|
54
|
+
return null;
|
|
55
|
+
// For single property, use simple replacement
|
|
56
|
+
if (properties.length === 1) {
|
|
57
|
+
const prop = properties[0];
|
|
58
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
59
|
+
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
60
|
+
? prop.key.name
|
|
61
|
+
: sourceCode.getText(prop.key);
|
|
62
|
+
const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
|
|
63
|
+
? prop.value.name
|
|
64
|
+
: sourceCode.getText(prop.value);
|
|
65
|
+
const initText = sourceCode.getText(node.init);
|
|
66
|
+
return fixer.replaceText(node, `${value} = ${initText}.${key}`);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// For multiple properties, create multiple declarations
|
|
71
|
+
const declarations = properties
|
|
72
|
+
.filter((prop) => prop.type === utils_1.AST_NODE_TYPES.Property)
|
|
73
|
+
.map((prop) => {
|
|
74
|
+
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
75
|
+
? prop.key.name
|
|
76
|
+
: sourceCode.getText(prop.key);
|
|
77
|
+
const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
|
|
78
|
+
? prop.value.name
|
|
79
|
+
: sourceCode.getText(prop.value);
|
|
80
|
+
const initText = sourceCode.getText(node.init);
|
|
81
|
+
return `${value} = ${initText}.${key}`;
|
|
82
|
+
})
|
|
83
|
+
.join(';\nconst ');
|
|
84
|
+
// Only apply the fix if we have valid declarations
|
|
85
|
+
if (!declarations)
|
|
86
|
+
return null;
|
|
87
|
+
return fixer.replaceText(node, declarations);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=no-class-instance-destructuring.js.map
|
|
@@ -37,7 +37,7 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
|
|
|
37
37
|
meta: {
|
|
38
38
|
type: 'suggestion',
|
|
39
39
|
docs: {
|
|
40
|
-
description: 'Warn when using CSS properties that trigger compositing layers',
|
|
40
|
+
description: 'Warn when using CSS properties that trigger compositing layers, which can impact performance. Properties like transform, opacity, filter, and will-change create new GPU layers. While sometimes beneficial for animations, excessive layer creation can increase memory usage and hurt performance. Consider alternatives or explicitly document intentional layer promotion.',
|
|
41
41
|
recommended: 'error',
|
|
42
42
|
},
|
|
43
43
|
schema: [],
|
|
@@ -14,7 +14,7 @@ exports.noConditionalLiteralsInJsx = (0, createRule_1.createRule)({
|
|
|
14
14
|
},
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
unexpected: 'Conditional
|
|
17
|
+
unexpected: 'Conditional text literals must be wrapped in a container element when next to other text. Instead of `<div>text {condition && "more text"}</div>`, use `<div>text <span>{condition && "more text"}</span></div>` to prevent React hydration issues.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|
|
@@ -136,7 +136,7 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
|
|
|
136
136
|
meta: {
|
|
137
137
|
type: 'suggestion',
|
|
138
138
|
docs: {
|
|
139
|
-
description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used. Requires TypeScript and `parserOptions.project` to be configured.',
|
|
139
|
+
description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used, as this can cause unnecessary re-renders. When a hook only uses obj.name but obj is in the deps array, any change to obj.age will trigger the hook. Use individual fields (obj.name) instead of the entire object. Requires TypeScript and `parserOptions.project` to be configured.',
|
|
140
140
|
recommended: 'error',
|
|
141
141
|
requiresTypeChecking: true,
|
|
142
142
|
},
|
|
@@ -6,6 +6,7 @@ type Options = [
|
|
|
6
6
|
allowInterfaceMethodSignatures?: boolean;
|
|
7
7
|
allowAbstractMethodSignatures?: boolean;
|
|
8
8
|
allowDtsFiles?: boolean;
|
|
9
|
+
allowFirestoreFunctionFiles?: boolean;
|
|
9
10
|
}
|
|
10
11
|
];
|
|
11
12
|
export declare const noExplicitReturnType: TSESLint.RuleModule<'noExplicitReturnType', Options>;
|
|
@@ -9,6 +9,7 @@ const defaultOptions = {
|
|
|
9
9
|
allowInterfaceMethodSignatures: true,
|
|
10
10
|
allowAbstractMethodSignatures: true,
|
|
11
11
|
allowDtsFiles: true,
|
|
12
|
+
allowFirestoreFunctionFiles: true,
|
|
12
13
|
};
|
|
13
14
|
function isRecursiveFunction(node) {
|
|
14
15
|
const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
|
|
@@ -91,7 +92,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
91
92
|
meta: {
|
|
92
93
|
type: 'suggestion',
|
|
93
94
|
docs: {
|
|
94
|
-
description: 'Disallow explicit return
|
|
95
|
+
description: 'Disallow explicit return type annotations on functions when TypeScript can infer them. This reduces code verbosity and maintenance burden while leveraging TypeScript\'s powerful type inference. Exceptions are made for recursive functions, overloaded functions, interface methods, and abstract methods where explicit types improve clarity.',
|
|
95
96
|
recommended: 'error',
|
|
96
97
|
requiresTypeChecking: false,
|
|
97
98
|
extendsBaseRule: false,
|
|
@@ -106,6 +107,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
106
107
|
allowInterfaceMethodSignatures: { type: 'boolean' },
|
|
107
108
|
allowAbstractMethodSignatures: { type: 'boolean' },
|
|
108
109
|
allowDtsFiles: { type: 'boolean' },
|
|
110
|
+
allowFirestoreFunctionFiles: { type: 'boolean' },
|
|
109
111
|
},
|
|
110
112
|
additionalProperties: false,
|
|
111
113
|
},
|
|
@@ -118,7 +120,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
118
120
|
create(context, [options]) {
|
|
119
121
|
const mergedOptions = { ...defaultOptions, ...options };
|
|
120
122
|
const filename = context.getFilename();
|
|
121
|
-
if (mergedOptions.allowDtsFiles && filename.endsWith('.d.ts'))
|
|
123
|
+
if ((mergedOptions.allowDtsFiles && filename.endsWith('.d.ts')) ||
|
|
124
|
+
(mergedOptions.allowFirestoreFunctionFiles && filename.endsWith('.f.ts'))) {
|
|
122
125
|
return {};
|
|
123
126
|
}
|
|
124
127
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -33,7 +33,7 @@ exports.noFilterWithoutReturn = (0, createRule_1.createRule)({
|
|
|
33
33
|
},
|
|
34
34
|
schema: [],
|
|
35
35
|
messages: {
|
|
36
|
-
unexpected: '
|
|
36
|
+
unexpected: 'Array.filter callbacks with block statements must contain a return statement. Instead of `array.filter(x => { doSomething(x); })`, use `array.filter(x => { doSomething(x); return someCondition; })` or use implicit return `array.filter(x => someCondition)`.',
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
defaultOptions: [],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noFirestoreObjectArrays: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noObjectArrays", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noFirestoreObjectArrays = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const PRIMITIVE_TYPES = new Set([
|
|
7
|
+
'string',
|
|
8
|
+
'number',
|
|
9
|
+
'boolean',
|
|
10
|
+
'Date',
|
|
11
|
+
'Timestamp',
|
|
12
|
+
'null',
|
|
13
|
+
'undefined',
|
|
14
|
+
'GeoPoint',
|
|
15
|
+
]);
|
|
16
|
+
const isInFirestoreTypesDirectory = (filename) => {
|
|
17
|
+
return filename.includes('functions/src/types/firestore');
|
|
18
|
+
};
|
|
19
|
+
const isObjectType = (node) => {
|
|
20
|
+
switch (node.type) {
|
|
21
|
+
case utils_1.AST_NODE_TYPES.TSTypeLiteral:
|
|
22
|
+
return true;
|
|
23
|
+
case utils_1.AST_NODE_TYPES.TSTypeReference:
|
|
24
|
+
if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
25
|
+
const typeName = node.typeName.name;
|
|
26
|
+
return !PRIMITIVE_TYPES.has(typeName);
|
|
27
|
+
}
|
|
28
|
+
else if (node.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
|
|
29
|
+
// Handle namespace.Type cases
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
case utils_1.AST_NODE_TYPES.TSIntersectionType:
|
|
34
|
+
case utils_1.AST_NODE_TYPES.TSUnionType:
|
|
35
|
+
return node.types.some(isObjectType);
|
|
36
|
+
case utils_1.AST_NODE_TYPES.TSMappedType:
|
|
37
|
+
case utils_1.AST_NODE_TYPES.TSIndexedAccessType:
|
|
38
|
+
return true;
|
|
39
|
+
default:
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
exports.noFirestoreObjectArrays = (0, createRule_1.createRule)({
|
|
44
|
+
name: 'no-firestore-object-arrays',
|
|
45
|
+
meta: {
|
|
46
|
+
type: 'problem',
|
|
47
|
+
docs: {
|
|
48
|
+
description: 'Disallow arrays of objects in Firestore type definitions to optimize performance and avoid unnecessary fetches',
|
|
49
|
+
recommended: 'warn',
|
|
50
|
+
},
|
|
51
|
+
schema: [],
|
|
52
|
+
messages: {
|
|
53
|
+
noObjectArrays: 'Arrays of objects are not recommended in Firestore. Use subcollections, arrays of IDs, or structured maps (Record<string, T>) instead.',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
defaultOptions: [],
|
|
57
|
+
create(context) {
|
|
58
|
+
if (!isInFirestoreTypesDirectory(context.getFilename())) {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
TSArrayType(node) {
|
|
63
|
+
if (isObjectType(node.elementType)) {
|
|
64
|
+
context.report({
|
|
65
|
+
node,
|
|
66
|
+
messageId: 'noObjectArrays',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
TSTypeReference(node) {
|
|
71
|
+
// Handle Array<T> and ReadonlyArray<T> syntax
|
|
72
|
+
const typeName = node.typeName.name;
|
|
73
|
+
if ((typeName === 'Array' || typeName === 'ReadonlyArray') && node.typeParameters) {
|
|
74
|
+
const elementType = node.typeParameters.params[0];
|
|
75
|
+
if (isObjectType(elementType)) {
|
|
76
|
+
context.report({
|
|
77
|
+
node,
|
|
78
|
+
messageId: 'noObjectArrays',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=no-firestore-object-arrays.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noJsxInHooks: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noJsxInHooks", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noJsxInHooks = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const isJsxElement = (node) => {
|
|
7
|
+
if (!node)
|
|
8
|
+
return false;
|
|
9
|
+
if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
10
|
+
return isJsxElement(node.consequent) || isJsxElement(node.alternate);
|
|
11
|
+
}
|
|
12
|
+
return (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
13
|
+
node.type === utils_1.AST_NODE_TYPES.JSXFragment ||
|
|
14
|
+
node.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer);
|
|
15
|
+
};
|
|
16
|
+
const isJsxReturnType = (node) => {
|
|
17
|
+
if (node.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
18
|
+
const typeName = node.typeAnnotation.typeName;
|
|
19
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
20
|
+
return ['JSX', 'ReactNode', 'ReactElement'].includes(typeName.name);
|
|
21
|
+
}
|
|
22
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
|
|
23
|
+
return typeName.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
24
|
+
typeName.left.name === 'JSX' &&
|
|
25
|
+
typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
26
|
+
typeName.right.name === 'Element';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
const containsJsxInBlockStatement = (node) => {
|
|
32
|
+
const variablesWithJsx = new Set();
|
|
33
|
+
for (const statement of node.body) {
|
|
34
|
+
// Check variable declarations for JSX assignments
|
|
35
|
+
if (statement.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
36
|
+
for (const declarator of statement.declarations) {
|
|
37
|
+
if (declarator.init) {
|
|
38
|
+
if (declarator.init.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
39
|
+
containsJsxInUseMemo(declarator.init)) {
|
|
40
|
+
if (declarator.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
41
|
+
variablesWithJsx.add(declarator.id.name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Check return statements
|
|
48
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement && statement.argument) {
|
|
49
|
+
if (isJsxElement(statement.argument)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (statement.argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
53
|
+
if (containsJsxInUseMemo(statement.argument)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (statement.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
58
|
+
variablesWithJsx.has(statement.argument.name)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Check if statements
|
|
63
|
+
if (statement.type === utils_1.AST_NODE_TYPES.IfStatement) {
|
|
64
|
+
if (statement.consequent.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
65
|
+
statement.consequent.argument) {
|
|
66
|
+
if (isJsxElement(statement.consequent.argument)) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (statement.consequent.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
70
|
+
variablesWithJsx.has(statement.consequent.argument.name)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (statement.consequent.type === utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
75
|
+
containsJsxInBlockStatement(statement.consequent)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (statement.alternate) {
|
|
79
|
+
if (statement.alternate.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
80
|
+
statement.alternate.argument) {
|
|
81
|
+
if (isJsxElement(statement.alternate.argument)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (statement.alternate.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
85
|
+
variablesWithJsx.has(statement.alternate.argument.name)) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (statement.alternate.type === utils_1.AST_NODE_TYPES.BlockStatement &&
|
|
90
|
+
containsJsxInBlockStatement(statement.alternate)) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
};
|
|
98
|
+
const containsJsxInUseMemo = (node) => {
|
|
99
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
100
|
+
node.callee.name === 'useMemo' &&
|
|
101
|
+
node.arguments.length > 0) {
|
|
102
|
+
const callback = node.arguments[0];
|
|
103
|
+
if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
104
|
+
callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
105
|
+
const body = callback.body;
|
|
106
|
+
if (isJsxElement(body)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
110
|
+
return containsJsxInBlockStatement(body);
|
|
111
|
+
}
|
|
112
|
+
if (body.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
113
|
+
body.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
114
|
+
body.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
115
|
+
body.callee.property.name === 'map') {
|
|
116
|
+
const mapCallback = body.arguments[0];
|
|
117
|
+
if ((mapCallback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
118
|
+
mapCallback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
119
|
+
isJsxElement(mapCallback.body)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
};
|
|
127
|
+
exports.noJsxInHooks = (0, createRule_1.createRule)({
|
|
128
|
+
name: 'no-jsx-in-hooks',
|
|
129
|
+
meta: {
|
|
130
|
+
type: 'problem',
|
|
131
|
+
docs: {
|
|
132
|
+
description: 'Prevent hooks from returning JSX',
|
|
133
|
+
recommended: 'error',
|
|
134
|
+
},
|
|
135
|
+
schema: [],
|
|
136
|
+
messages: {
|
|
137
|
+
noJsxInHooks: 'Hooks should not return JSX. Convert this hook into a component or extract the JSX into a separate component.',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
defaultOptions: [],
|
|
141
|
+
create(context) {
|
|
142
|
+
return {
|
|
143
|
+
FunctionDeclaration(node) {
|
|
144
|
+
if (node.id && node.id.name.startsWith('use')) {
|
|
145
|
+
// Check return type annotation
|
|
146
|
+
if (node.returnType && isJsxReturnType(node.returnType)) {
|
|
147
|
+
context.report({
|
|
148
|
+
node: node.id,
|
|
149
|
+
messageId: 'noJsxInHooks',
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Check return statements
|
|
154
|
+
if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
155
|
+
if (containsJsxInBlockStatement(node.body)) {
|
|
156
|
+
context.report({
|
|
157
|
+
node: node.id,
|
|
158
|
+
messageId: 'noJsxInHooks',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
ArrowFunctionExpression(node) {
|
|
165
|
+
const parent = node.parent;
|
|
166
|
+
if (parent &&
|
|
167
|
+
parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
168
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
169
|
+
parent.id.name.startsWith('use')) {
|
|
170
|
+
// Check return type annotation
|
|
171
|
+
if (node.returnType && isJsxReturnType(node.returnType)) {
|
|
172
|
+
context.report({
|
|
173
|
+
node: parent.id,
|
|
174
|
+
messageId: 'noJsxInHooks',
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Check direct JSX return
|
|
179
|
+
if (isJsxElement(node.body)) {
|
|
180
|
+
context.report({
|
|
181
|
+
node: parent.id,
|
|
182
|
+
messageId: 'noJsxInHooks',
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Check block body returns
|
|
187
|
+
if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
188
|
+
if (containsJsxInBlockStatement(node.body)) {
|
|
189
|
+
context.report({
|
|
190
|
+
node: parent.id,
|
|
191
|
+
messageId: 'noJsxInHooks',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=no-jsx-in-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noMemoizeOnStatic: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noMemoizeOnStatic", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noMemoizeOnStatic = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noMemoizeOnStatic = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-memoize-on-static',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Prevent using @Memoize() decorator on static methods',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
noMemoizeOnStatic: '@Memoize() decorator should not be used on static methods',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create(context) {
|
|
21
|
+
// Track renamed imports of Memoize
|
|
22
|
+
const memoizeAliases = new Set(['Memoize']);
|
|
23
|
+
return {
|
|
24
|
+
ImportSpecifier(node) {
|
|
25
|
+
if (node.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
26
|
+
node.imported.name === 'Memoize' &&
|
|
27
|
+
node.local.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
28
|
+
memoizeAliases.add(node.local.name);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
'MethodDefinition[static=true]'(node) {
|
|
32
|
+
if (node.decorators) {
|
|
33
|
+
for (const decorator of node.decorators) {
|
|
34
|
+
const expr = decorator.expression;
|
|
35
|
+
if (
|
|
36
|
+
// Handle @Memoize()
|
|
37
|
+
(expr.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
38
|
+
expr.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
39
|
+
memoizeAliases.has(expr.callee.name)) ||
|
|
40
|
+
// Handle @Memoize (without parentheses)
|
|
41
|
+
(expr.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
42
|
+
memoizeAliases.has(expr.name))) {
|
|
43
|
+
context.report({
|
|
44
|
+
node: decorator,
|
|
45
|
+
messageId: 'noMemoizeOnStatic',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=no-memoize-on-static.js.map
|
|
@@ -7,12 +7,12 @@ exports.noMisusedSwitchCase = (0, createRule_1.createRule)({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: 'problem',
|
|
9
9
|
docs: {
|
|
10
|
-
description: 'Prevent misuse of logical OR in switch case statements',
|
|
10
|
+
description: 'Prevent misuse of logical OR (||) in switch case statements, which can lead to confusing and error-prone code. Instead of using OR operators in case expressions, use multiple case statements in sequence to handle multiple values. This improves code readability and follows the standard switch-case pattern.',
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
schema: [],
|
|
14
14
|
messages: {
|
|
15
|
-
noMisusedSwitchCase: 'Avoid using logical OR
|
|
15
|
+
noMisusedSwitchCase: 'Avoid using logical OR (||) in switch case statements. Instead of `case x || y:`, use cascading cases like `case x: case y:`.',
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
18
|
defaultOptions: [],
|