@blumintinc/eslint-plugin-blumint 1.3.2 → 1.5.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.
Files changed (36) hide show
  1. package/README.md +41 -40
  2. package/lib/index.js +37 -1
  3. package/lib/rules/array-methods-this-context.js +2 -2
  4. package/lib/rules/enforce-callback-memo.js +3 -3
  5. package/lib/rules/enforce-dynamic-firebase-imports.js +2 -2
  6. package/lib/rules/enforce-exported-function-types.js +88 -85
  7. package/lib/rules/enforce-firestore-doc-ref-generic.js +7 -2
  8. package/lib/rules/enforce-firestore-path-utils.js +2 -2
  9. package/lib/rules/enforce-firestore-set-merge.js +45 -8
  10. package/lib/rules/enforce-identifiable-firestore-type.js +2 -2
  11. package/lib/rules/enforce-memoize-async.js +2 -2
  12. package/lib/rules/enforce-mock-firestore.js +3 -3
  13. package/lib/rules/enforce-realtimedb-path-utils.js +1 -1
  14. package/lib/rules/enforce-safe-stringify.js +2 -2
  15. package/lib/rules/enforce-serializable-params.js +3 -3
  16. package/lib/rules/enforce-verb-noun-naming.js +41 -7
  17. package/lib/rules/no-async-array-filter.js +2 -2
  18. package/lib/rules/no-async-foreach.js +1 -1
  19. package/lib/rules/no-class-instance-destructuring.d.ts +1 -0
  20. package/lib/rules/no-class-instance-destructuring.js +95 -0
  21. package/lib/rules/no-compositing-layer-props.js +1 -1
  22. package/lib/rules/no-conditional-literals-in-jsx.js +1 -1
  23. package/lib/rules/no-entire-object-hook-deps.js +1 -1
  24. package/lib/rules/no-explicit-return-type.d.ts +3 -1
  25. package/lib/rules/no-explicit-return-type.js +30 -17
  26. package/lib/rules/no-filter-without-return.js +1 -1
  27. package/lib/rules/no-misused-switch-case.js +2 -2
  28. package/lib/rules/no-redundant-param-types.d.ts +2 -0
  29. package/lib/rules/no-redundant-param-types.js +129 -0
  30. package/lib/rules/no-unused-props.js +1 -1
  31. package/lib/rules/no-useless-fragment.js +1 -1
  32. package/lib/rules/prefer-destructuring-no-class.d.ts +8 -0
  33. package/lib/rules/prefer-destructuring-no-class.js +200 -0
  34. package/lib/rules/require-usememo-object-literals.js +1 -1
  35. package/lib/rules/semantic-function-prefixes.js +43 -4
  36. package/package.json +2 -2
@@ -8,24 +8,58 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
8
8
  meta: {
9
9
  type: 'suggestion',
10
10
  docs: {
11
- description: 'Enforce using set() with { merge: true } instead of update() for Firestore operations',
11
+ description: 'Enforce using set() with { merge: true } instead of update() for Firestore operations to ensure consistent behavior. The update() method fails if the document does not exist, while set() with { merge: true } creates the document if needed and safely merges fields, making it more reliable and predictable.',
12
12
  recommended: 'error',
13
+ requiresTypeChecking: false,
14
+ extendsBaseRule: false,
13
15
  },
14
16
  fixable: 'code',
15
17
  schema: [],
16
18
  messages: {
17
- preferSetMerge: 'Use set() with { merge: true } instead of update() for more predictable Firestore operations',
19
+ preferSetMerge: 'Use set() with { merge: true } instead of update() for more predictable Firestore operations. Instead of `docRef.update({ field: value })`, use `docRef.set({ field: value }, { merge: true })`. This ensures consistent behavior when the document does not exist.',
18
20
  },
19
21
  },
20
22
  defaultOptions: [],
21
23
  create(context) {
22
24
  const updateAliases = new Set();
23
25
  function isFirestoreUpdateCall(node) {
26
+ // Check if it's a set() call with merge: true
24
27
  if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
25
28
  const property = node.callee.property;
26
- return property.type === utils_1.AST_NODE_TYPES.Identifier && property.name === 'update';
29
+ if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
30
+ // If it's a set() call, check if it has merge: true
31
+ if (property.name === 'set') {
32
+ const lastArg = node.arguments[node.arguments.length - 1];
33
+ if (lastArg?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
34
+ const hasMergeTrue = lastArg.properties.some((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
35
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
36
+ prop.key.name === 'merge' &&
37
+ prop.value.type === utils_1.AST_NODE_TYPES.Literal &&
38
+ prop.value.value === true);
39
+ if (hasMergeTrue) {
40
+ return false; // Already using set with merge: true
41
+ }
42
+ }
43
+ }
44
+ // Only flag update() calls
45
+ return property.name === 'update';
46
+ }
27
47
  }
28
48
  if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
49
+ // Check if it's a setDoc() call with merge: true
50
+ if (node.callee.name === 'setDoc') {
51
+ const lastArg = node.arguments[node.arguments.length - 1];
52
+ if (lastArg?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
53
+ const hasMergeTrue = lastArg.properties.some((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
54
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
55
+ prop.key.name === 'merge' &&
56
+ prop.value.type === utils_1.AST_NODE_TYPES.Literal &&
57
+ prop.value.value === true);
58
+ if (hasMergeTrue) {
59
+ return false; // Already using setDoc with merge: true
60
+ }
61
+ }
62
+ }
29
63
  return updateAliases.has(node.callee.name);
30
64
  }
31
65
  return false;
@@ -51,8 +85,9 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
51
85
  }
52
86
  return {
53
87
  ImportDeclaration(node) {
54
- if (node.source.value === 'firebase/firestore' || node.source.value === 'firebase-admin') {
55
- node.specifiers.forEach(specifier => {
88
+ if (node.source.value === 'firebase/firestore' ||
89
+ node.source.value === 'firebase-admin') {
90
+ node.specifiers.forEach((specifier) => {
56
91
  if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
57
92
  if (specifier.imported.name === 'updateDoc') {
58
93
  updateAliases.add(specifier.local.name);
@@ -63,7 +98,8 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
63
98
  },
64
99
  ImportExpression(node) {
65
100
  if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
66
- (node.source.value === 'firebase/firestore' || node.source.value === 'firebase-admin')) {
101
+ (node.source.value === 'firebase/firestore' ||
102
+ node.source.value === 'firebase-admin')) {
67
103
  // Dynamic imports are handled in VariableDeclarator
68
104
  }
69
105
  },
@@ -72,10 +108,11 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
72
108
  node.init.argument.type === utils_1.AST_NODE_TYPES.ImportExpression) {
73
109
  const importSource = node.init.argument.source;
74
110
  if (importSource.type === utils_1.AST_NODE_TYPES.Literal &&
75
- (importSource.value === 'firebase/firestore' || importSource.value === 'firebase-admin')) {
111
+ (importSource.value === 'firebase/firestore' ||
112
+ importSource.value === 'firebase-admin')) {
76
113
  // Handle destructured imports
77
114
  if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
78
- node.id.properties.forEach(prop => {
115
+ node.id.properties.forEach((prop) => {
79
116
  if (prop.type === utils_1.AST_NODE_TYPES.Property &&
80
117
  prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
81
118
  prop.key.name === 'updateDoc') {
@@ -17,8 +17,8 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
17
17
  },
18
18
  schema: [],
19
19
  messages: {
20
- missingType: 'Expected exported type "{{ typeName }}" in index.ts under folder "{{ folderName }}"',
21
- notExtendingIdentifiable: 'Type "{{ typeName }}" must extend "Identifiable", including an "id: string" field',
20
+ missingType: 'Expected exported type "{{ typeName }}" in index.ts under folder "{{ folderName }}". Create a type that matches the folder name: `export type {{ typeName }} = { /* fields */ }`.',
21
+ notExtendingIdentifiable: 'Type "{{ typeName }}" must extend "Identifiable" to ensure all Firestore documents have an ID field. Add `extends Identifiable` or include `id: string`: `export type {{ typeName }} = { id: string; /* other fields */ }`.',
22
22
  },
23
23
  },
24
24
  defaultOptions: [],
@@ -8,13 +8,13 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
8
8
  meta: {
9
9
  type: 'suggestion',
10
10
  docs: {
11
- description: 'Enforce @Memoize() decorator on async methods with 0-1 parameters',
11
+ description: 'Enforce @Memoize() decorator on async methods with 0-1 parameters to cache results and prevent redundant API calls or expensive computations. This improves performance by reusing previous results when the same parameters are provided, particularly useful for data fetching methods.',
12
12
  recommended: 'error',
13
13
  },
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- requireMemoize: 'Async methods with 0-1 parameters should be decorated with @Memoize()',
17
+ requireMemoize: 'Async methods with 0-1 parameters should be decorated with @Memoize() to cache results and improve performance. Instead of `async getData(id?: string)`, use `@Memoize()\nasync getData(id?: string)`. Import Memoize from "typescript-memoize".',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -13,13 +13,13 @@ exports.enforceFirestoreMock = (0, createRule_1.createRule)({
13
13
  meta: {
14
14
  type: 'problem',
15
15
  docs: {
16
- description: 'Enforce using mockFirestore over manual Firestore mocking',
16
+ description: 'Enforce using the standardized mockFirestore utility instead of manual Firestore mocking or third-party mocks. This ensures consistent test behavior across the codebase, reduces boilerplate, and provides type-safe mocking of Firestore operations.',
17
17
  recommended: 'error',
18
18
  },
19
19
  schema: [],
20
20
  messages: {
21
- noManualFirestoreMock: 'Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead of manually mocking Firestore',
22
- noMockFirebase: 'Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead of mockFirebase',
21
+ noManualFirestoreMock: 'Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead of manually mocking Firestore. Replace `jest.mock("firebase-admin", () => ({ firestore: () => ({ /* mock */ }) }))` with `import { mockFirestore } from "__mocks__/functions/src/config/mockFirestore"; jest.mock("firebase-admin", () => mockFirestore)`.',
22
+ noMockFirebase: 'Use mockFirestore from __mocks__/functions/src/config/mockFirestore instead of mockFirebase. Replace `import { mockFirebase } from "firestore-jest-mock"` with `import { mockFirestore } from "__mocks__/functions/src/config/mockFirestore"`.',
23
23
  },
24
24
  },
25
25
  defaultOptions: [],
@@ -14,7 +14,7 @@ exports.enforceRealtimedbPathUtils = (0, createRule_1.createRule)({
14
14
  },
15
15
  schema: [],
16
16
  messages: {
17
- requirePathUtil: 'Use a utility function (e.g., toUserPath, toItemPath) for Realtime Database paths instead of string literals',
17
+ requirePathUtil: 'Use a utility function for Realtime Database paths to ensure type safety and maintainability. Instead of `ref("users/" + userId)`, create and use a utility function: `const toUserPath = (id: string) => `users/${id}`; ref(toUserPath(userId))`.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -8,13 +8,13 @@ exports.enforceStableStringify = (0, createRule_1.createRule)({
8
8
  meta: {
9
9
  type: 'problem',
10
10
  docs: {
11
- description: 'Enforce using safe-stable-stringify instead of JSON.stringify',
11
+ description: 'Enforce using safe-stable-stringify instead of JSON.stringify to handle circular references and ensure deterministic output. JSON.stringify can throw errors on circular references and produce inconsistent output for objects with the same properties in different orders. safe-stable-stringify handles these cases safely.',
12
12
  recommended: 'error',
13
13
  },
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- useStableStringify: 'Use safe-stable-stringify instead of JSON.stringify for safer serialization',
17
+ useStableStringify: 'Use safe-stable-stringify instead of JSON.stringify for safer serialization. Replace `JSON.stringify(obj)` with `stringify(obj)`. First import it: `import stringify from "safe-stable-stringify"`. This handles circular references and provides deterministic output.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -17,7 +17,7 @@ exports.default = (0, createRule_1.createRule)({
17
17
  meta: {
18
18
  type: 'problem',
19
19
  docs: {
20
- description: 'Enforce serializable parameters in Firebase Callable/HTTPS Cloud Functions',
20
+ description: 'Enforce serializable parameters in Firebase Callable/HTTPS Cloud Functions to prevent runtime errors. Firebase Functions can only pass JSON-serializable data, so using non-serializable types like Date, DocumentReference, or Map will cause failures. Use primitive types, plain objects, and arrays instead, converting complex types to their serializable representations (e.g., Date to ISO string).',
21
21
  recommended: 'error',
22
22
  },
23
23
  schema: [
@@ -38,8 +38,8 @@ exports.default = (0, createRule_1.createRule)({
38
38
  },
39
39
  ],
40
40
  messages: {
41
- nonSerializableParam: 'Parameter type "{{ type }}" is not serializable',
42
- nonSerializableProperty: 'Property "{{ prop }}" has non-serializable type "{{ type }}"',
41
+ nonSerializableParam: 'Parameter type "{{ type }}" is not serializable in Firebase Cloud Functions. Use JSON-serializable types like string, number, boolean, arrays, or plain objects. Instead of `Date`, use ISO strings: `new Date().toISOString()`.',
42
+ nonSerializableProperty: 'Property "{{ prop }}" has non-serializable type "{{ type }}". Use JSON-serializable types. For example, instead of `{ timestamp: Date }`, use `{ timestamp: string }` with ISO format.',
43
43
  },
44
44
  },
45
45
  defaultOptions: [
@@ -8,6 +8,22 @@ const utils_1 = require("@typescript-eslint/utils");
8
8
  const createRule_1 = require("../utils/createRule");
9
9
  const compromise_1 = __importDefault(require("compromise"));
10
10
  const PREPOSITIONS = ['to', 'from', 'with', 'by', 'at', 'of'];
11
+ // Common short verbs that should be allowed
12
+ const COMMON_VERBS = new Set([
13
+ 'sync',
14
+ 'fix',
15
+ 'set',
16
+ 'log',
17
+ 'get',
18
+ 'put',
19
+ 'add',
20
+ 'map',
21
+ 'run',
22
+ 'use',
23
+ 'has',
24
+ 'is',
25
+ 'do',
26
+ ]);
11
27
  exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
12
28
  name: 'enforce-verb-noun-naming',
13
29
  meta: {
@@ -15,6 +31,8 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
15
31
  docs: {
16
32
  description: 'Enforce verb phrases for functions and methods',
17
33
  recommended: 'error',
34
+ requiresTypeChecking: false,
35
+ extendsBaseRule: false,
18
36
  },
19
37
  schema: [],
20
38
  messages: {
@@ -30,7 +48,8 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
30
48
  return firstChar + words[0];
31
49
  }
32
50
  function toSentence(name) {
33
- return name.split(/(?=[A-Z])/).join(' ');
51
+ // Add "I" prefix to create a proper sentence for better verb detection
52
+ return 'I ' + name.split(/(?=[A-Z])/).join(' ');
34
53
  }
35
54
  function getPossibleTags(sentence) {
36
55
  const doc = (0, compromise_1.default)(sentence);
@@ -43,14 +62,23 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
43
62
  function isVerbPhrase(name) {
44
63
  const firstWord = extractFirstWord(name);
45
64
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
- if (PREPOSITIONS.includes(firstWord.toLowerCase())) {
65
+ const firstWordLower = firstWord.toLowerCase();
66
+ // Check for prepositions and common verbs first
67
+ if (PREPOSITIONS.includes(firstWordLower) || COMMON_VERBS.has(firstWordLower)) {
47
68
  return true;
48
69
  }
49
- const tags = getPossibleTags(toSentence(name));
50
- const isVerb = tags.includes('Verb');
51
- const isPreposition = tags.includes('Preposition');
52
- const isConjunction = tags.includes('Conjunction');
53
- return isVerb || isPreposition || isConjunction;
70
+ // Try both with and without "I" prefix to catch more verb forms
71
+ const withPrefixTags = getPossibleTags(toSentence(name));
72
+ const withoutPrefixTags = getPossibleTags(firstWord);
73
+ // Check if either form is recognized as a verb
74
+ const isVerb = withPrefixTags.includes('Verb') || withoutPrefixTags.includes('Verb');
75
+ const isPreposition = withPrefixTags.includes('Preposition');
76
+ const isConjunction = withPrefixTags.includes('Conjunction');
77
+ // For non-prepositions/conjunctions, require verb form
78
+ if (isPreposition || isConjunction) {
79
+ return true;
80
+ }
81
+ return isVerb;
54
82
  }
55
83
  function isJsxReturnFunction(node) {
56
84
  if (node.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
@@ -97,6 +125,12 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
97
125
  MethodDefinition(node) {
98
126
  if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
99
127
  return;
128
+ // Skip getters since they represent properties and should use noun phrases
129
+ if (node.kind === 'get')
130
+ return;
131
+ // Skip constructors since they are special class methods
132
+ if (node.kind === 'constructor')
133
+ return;
100
134
  if (!isVerbPhrase(node.key.name)) {
101
135
  context.report({
102
136
  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
  },
@@ -1,3 +1,4 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
1
2
  type Options = [
2
3
  {
3
4
  allowRecursiveFunctions?: boolean;
@@ -5,7 +6,8 @@ type Options = [
5
6
  allowInterfaceMethodSignatures?: boolean;
6
7
  allowAbstractMethodSignatures?: boolean;
7
8
  allowDtsFiles?: boolean;
9
+ allowFirestoreFunctionFiles?: boolean;
8
10
  }
9
11
  ];
10
- export declare const noExplicitReturnType: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noExplicitReturnType", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
12
+ export declare const noExplicitReturnType: TSESLint.RuleModule<'noExplicitReturnType', Options>;
11
13
  export {};
@@ -9,11 +9,14 @@ 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
- const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ? node.id?.name :
15
- node.type === utils_1.AST_NODE_TYPES.FunctionExpression && node.id ? node.id.name :
16
- undefined;
15
+ const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
16
+ ? node.id?.name
17
+ : node.type === utils_1.AST_NODE_TYPES.FunctionExpression && node.id
18
+ ? node.id.name
19
+ : undefined;
17
20
  if (!functionName || !node.body)
18
21
  return false;
19
22
  let hasRecursiveCall = false;
@@ -63,9 +66,9 @@ function isOverloadedFunction(node) {
63
66
  const methodName = node.key.type === utils_1.AST_NODE_TYPES.Identifier ? node.key.name : undefined;
64
67
  if (!methodName)
65
68
  return false;
66
- return interfaceBody.body.filter(member => member.type === utils_1.AST_NODE_TYPES.TSMethodSignature &&
69
+ return (interfaceBody.body.filter((member) => member.type === utils_1.AST_NODE_TYPES.TSMethodSignature &&
67
70
  member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
68
- member.key.name === methodName).length > 1;
71
+ member.key.name === methodName).length > 1);
69
72
  }
70
73
  return false;
71
74
  }
@@ -75,7 +78,8 @@ function isInterfaceOrAbstractMethodSignature(node) {
75
78
  if (node.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
76
79
  let current = node;
77
80
  while (current) {
78
- if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration && current.abstract) {
81
+ if (current.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
82
+ current.abstract) {
79
83
  return true;
80
84
  }
81
85
  current = current.parent;
@@ -88,8 +92,10 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
88
92
  meta: {
89
93
  type: 'suggestion',
90
94
  docs: {
91
- 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.',
92
96
  recommended: 'error',
97
+ requiresTypeChecking: false,
98
+ extendsBaseRule: false,
93
99
  },
94
100
  fixable: 'code',
95
101
  schema: [
@@ -101,6 +107,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
101
107
  allowInterfaceMethodSignatures: { type: 'boolean' },
102
108
  allowAbstractMethodSignatures: { type: 'boolean' },
103
109
  allowDtsFiles: { type: 'boolean' },
110
+ allowFirestoreFunctionFiles: { type: 'boolean' },
104
111
  },
105
112
  additionalProperties: false,
106
113
  },
@@ -113,9 +120,11 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
113
120
  create(context, [options]) {
114
121
  const mergedOptions = { ...defaultOptions, ...options };
115
122
  const filename = context.getFilename();
116
- if (mergedOptions.allowDtsFiles && filename.endsWith('.d.ts')) {
123
+ if ((mergedOptions.allowDtsFiles && filename.endsWith('.d.ts')) ||
124
+ (mergedOptions.allowFirestoreFunctionFiles && filename.endsWith('.f.ts'))) {
117
125
  return {};
118
126
  }
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
128
  function fixReturnType(fixer, node) {
120
129
  const returnType = node.returnType || node.value?.returnType;
121
130
  if (!returnType)
@@ -127,25 +136,27 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
127
136
  FunctionDeclaration(node) {
128
137
  if (!node.returnType)
129
138
  return;
130
- if (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node)) {
139
+ if (mergedOptions.allowRecursiveFunctions &&
140
+ isRecursiveFunction(node)) {
131
141
  return;
132
142
  }
133
143
  context.report({
134
144
  node: node.returnType,
135
145
  messageId: 'noExplicitReturnType',
136
- fix: fixer => fixReturnType(fixer, node),
146
+ fix: (fixer) => fixReturnType(fixer, node),
137
147
  });
138
148
  },
139
149
  FunctionExpression(node) {
140
150
  if (!node.returnType)
141
151
  return;
142
- if (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node)) {
152
+ if (mergedOptions.allowRecursiveFunctions &&
153
+ isRecursiveFunction(node)) {
143
154
  return;
144
155
  }
145
156
  context.report({
146
157
  node: node.returnType,
147
158
  messageId: 'noExplicitReturnType',
148
- fix: fixer => fixReturnType(fixer, node),
159
+ fix: (fixer) => fixReturnType(fixer, node),
149
160
  });
150
161
  },
151
162
  ArrowFunctionExpression(node) {
@@ -154,7 +165,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
154
165
  context.report({
155
166
  node: node.returnType,
156
167
  messageId: 'noExplicitReturnType',
157
- fix: fixer => fixReturnType(fixer, node),
168
+ fix: (fixer) => fixReturnType(fixer, node),
158
169
  });
159
170
  },
160
171
  TSMethodSignature(node) {
@@ -163,25 +174,27 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
163
174
  if (mergedOptions.allowInterfaceMethodSignatures) {
164
175
  return;
165
176
  }
166
- if (mergedOptions.allowOverloadedFunctions && isOverloadedFunction(node)) {
177
+ if (mergedOptions.allowOverloadedFunctions &&
178
+ isOverloadedFunction(node)) {
167
179
  return;
168
180
  }
169
181
  context.report({
170
182
  node: node.returnType,
171
183
  messageId: 'noExplicitReturnType',
172
- fix: fixer => fixReturnType(fixer, node),
184
+ fix: (fixer) => fixReturnType(fixer, node),
173
185
  });
174
186
  },
175
187
  MethodDefinition(node) {
176
188
  if (!node.value.returnType)
177
189
  return;
178
- if (mergedOptions.allowAbstractMethodSignatures && isInterfaceOrAbstractMethodSignature(node)) {
190
+ if (mergedOptions.allowAbstractMethodSignatures &&
191
+ isInterfaceOrAbstractMethodSignature(node)) {
179
192
  return;
180
193
  }
181
194
  context.report({
182
195
  node: node.value.returnType,
183
196
  messageId: 'noExplicitReturnType',
184
- fix: fixer => fixReturnType(fixer, node),
197
+ fix: (fixer) => fixReturnType(fixer, node),
185
198
  });
186
199
  },
187
200
  };
@@ -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: [],
@@ -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: [],
@@ -0,0 +1,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noRedundantParamTypes: TSESLint.RuleModule<"redundantParamType", [], TSESLint.RuleListener>;