@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.
Files changed (45) hide show
  1. package/lib/index.js +19 -1
  2. package/lib/rules/array-methods-this-context.js +2 -2
  3. package/lib/rules/enforce-callback-memo.js +3 -3
  4. package/lib/rules/enforce-dynamic-firebase-imports.js +2 -2
  5. package/lib/rules/enforce-exported-function-types.js +115 -85
  6. package/lib/rules/enforce-firestore-doc-ref-generic.js +2 -2
  7. package/lib/rules/enforce-firestore-path-utils.js +2 -2
  8. package/lib/rules/enforce-firestore-set-merge.js +35 -4
  9. package/lib/rules/enforce-identifiable-firestore-type.js +2 -2
  10. package/lib/rules/enforce-memoize-async.js +6 -4
  11. package/lib/rules/enforce-mock-firestore.js +3 -3
  12. package/lib/rules/enforce-realtimedb-path-utils.js +1 -1
  13. package/lib/rules/enforce-safe-stringify.js +2 -2
  14. package/lib/rules/enforce-serializable-params.js +3 -3
  15. package/lib/rules/enforce-verb-noun-naming.js +34 -12
  16. package/lib/rules/no-async-array-filter.js +2 -2
  17. package/lib/rules/no-async-foreach.js +1 -1
  18. package/lib/rules/no-class-instance-destructuring.d.ts +1 -0
  19. package/lib/rules/no-class-instance-destructuring.js +95 -0
  20. package/lib/rules/no-compositing-layer-props.js +1 -1
  21. package/lib/rules/no-conditional-literals-in-jsx.js +1 -1
  22. package/lib/rules/no-entire-object-hook-deps.js +1 -1
  23. package/lib/rules/no-explicit-return-type.d.ts +1 -0
  24. package/lib/rules/no-explicit-return-type.js +5 -2
  25. package/lib/rules/no-filter-without-return.js +1 -1
  26. package/lib/rules/no-firestore-object-arrays.d.ts +1 -0
  27. package/lib/rules/no-firestore-object-arrays.js +86 -0
  28. package/lib/rules/no-jsx-in-hooks.d.ts +1 -0
  29. package/lib/rules/no-jsx-in-hooks.js +200 -0
  30. package/lib/rules/no-memoize-on-static.d.ts +1 -0
  31. package/lib/rules/no-memoize-on-static.js +54 -0
  32. package/lib/rules/no-misused-switch-case.js +2 -2
  33. package/lib/rules/no-redundant-param-types.d.ts +2 -0
  34. package/lib/rules/no-redundant-param-types.js +129 -0
  35. package/lib/rules/no-unsafe-firestore-spread.d.ts +3 -0
  36. package/lib/rules/no-unsafe-firestore-spread.js +125 -0
  37. package/lib/rules/no-unused-props.js +1 -1
  38. package/lib/rules/no-useless-fragment.js +1 -1
  39. package/lib/rules/prefer-destructuring-no-class.d.ts +8 -0
  40. package/lib/rules/prefer-destructuring-no-class.js +200 -0
  41. package/lib/rules/require-hooks-default-params.d.ts +1 -0
  42. package/lib/rules/require-hooks-default-params.js +195 -0
  43. package/lib/rules/require-usememo-object-literals.js +1 -1
  44. package/lib/rules/semantic-function-prefixes.js +43 -4
  45. 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 PREPOSITIONS = ['to', 'from', 'with', 'by', 'at', 'of'];
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 toSentence(name) {
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 === 0 || !terms[0].terms || !terms[0].terms[0].tags)
43
+ if (terms.length < index ||
44
+ !terms[index]?.terms?.length ||
45
+ !terms[index]?.terms[0]?.tags)
41
46
  return [];
42
- const tags = terms[0].terms[0].tags;
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
- if (PREPOSITIONS.includes(firstWord.toLowerCase())) {
52
+ const firstWordLower = firstWord.toLowerCase();
53
+ // Check for prepositions first
54
+ if (PREPOSITIONS.has(firstWordLower)) {
49
55
  return true;
50
56
  }
51
- const tags = getPossibleTags(toSentence(name));
52
- const isVerb = tags.includes('Verb');
53
- const isPreposition = tags.includes('Preposition');
54
- const isConjunction = tags.includes('Conjunction');
55
- return isVerb || isPreposition || isConjunction;
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 for Array.filter',
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. You should move the asynchronous logic elsewhere.',
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 expression is a sibling of raw text and must be wrapped in <div> or <span>',
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 types on functions',
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: 'An array filter callback with a block statement must contain a return statement',
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 in switch case. Use cascading cases instead.',
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: [],