@blumintinc/eslint-plugin-blumint 1.2.0 → 1.3.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 (30) hide show
  1. package/README.md +40 -37
  2. package/lib/index.js +22 -13
  3. package/lib/rules/dynamic-https-errors.js +1 -1
  4. package/lib/rules/enforce-callback-memo.js +21 -3
  5. package/lib/rules/enforce-firestore-doc-ref-generic.d.ts +10 -0
  6. package/lib/rules/enforce-firestore-doc-ref-generic.js +134 -0
  7. package/lib/rules/enforce-firestore-set-merge.d.ts +1 -0
  8. package/lib/rules/enforce-firestore-set-merge.js +106 -0
  9. package/lib/rules/enforce-mock-firestore.d.ts +3 -0
  10. package/lib/rules/enforce-mock-firestore.js +76 -0
  11. package/lib/rules/enforce-verb-noun-naming.d.ts +1 -0
  12. package/lib/rules/enforce-verb-noun-naming.js +110 -0
  13. package/lib/rules/export-if-in-doubt.js +1 -1
  14. package/lib/rules/extract-global-constants.js +31 -2
  15. package/lib/rules/global-const-style.d.ts +2 -4
  16. package/lib/rules/global-const-style.js +48 -22
  17. package/lib/rules/no-compositing-layer-props.js +38 -4
  18. package/lib/rules/no-entire-object-hook-deps.js +68 -60
  19. package/lib/rules/no-explicit-return-type.d.ts +11 -0
  20. package/lib/rules/no-explicit-return-type.js +190 -0
  21. package/lib/rules/no-unused-props.js +33 -8
  22. package/lib/rules/no-useless-fragment.js +1 -1
  23. package/lib/rules/prefer-fragment-shorthand.js +1 -1
  24. package/lib/rules/prefer-type-over-interface.js +1 -1
  25. package/lib/rules/require-https-error.js +32 -23
  26. package/lib/rules/require-image-optimized.d.ts +7 -0
  27. package/lib/rules/require-image-optimized.js +82 -0
  28. package/lib/rules/semantic-function-prefixes.d.ts +1 -0
  29. package/lib/rules/semantic-function-prefixes.js +74 -0
  30. package/package.json +2 -1
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.enforceVerbNounNaming = void 0;
7
+ const utils_1 = require("@typescript-eslint/utils");
8
+ const createRule_1 = require("../utils/createRule");
9
+ const compromise_1 = __importDefault(require("compromise"));
10
+ const PREPOSITIONS = ['to', 'from', 'with', 'by', 'at', 'of'];
11
+ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
12
+ name: 'enforce-verb-noun-naming',
13
+ meta: {
14
+ type: 'suggestion',
15
+ docs: {
16
+ description: 'Enforce verb phrases for functions and methods',
17
+ recommended: 'error',
18
+ },
19
+ schema: [],
20
+ messages: {
21
+ functionVerbPhrase: 'Function names should start with a verb phrase (e.g., fetchData, processRequest)',
22
+ },
23
+ },
24
+ defaultOptions: [],
25
+ create(context) {
26
+ function extractFirstWord(name) {
27
+ const firstChar = name.charAt(0);
28
+ const rest = name.slice(1);
29
+ const words = rest.split(/(?=[A-Z])/);
30
+ return firstChar + words[0];
31
+ }
32
+ function toSentence(name) {
33
+ return name.split(/(?=[A-Z])/).join(' ');
34
+ }
35
+ function getPossibleTags(sentence) {
36
+ const doc = (0, compromise_1.default)(sentence);
37
+ const terms = doc.terms().json();
38
+ if (terms.length === 0 || !terms[0].terms || !terms[0].terms[0].tags)
39
+ return [];
40
+ const tags = terms[0].terms[0].tags;
41
+ return tags;
42
+ }
43
+ function isVerbPhrase(name) {
44
+ const firstWord = extractFirstWord(name);
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ if (PREPOSITIONS.includes(firstWord.toLowerCase())) {
47
+ return true;
48
+ }
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;
54
+ }
55
+ function isJsxReturnFunction(node) {
56
+ if (node.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration &&
57
+ node.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
58
+ node.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
59
+ return false;
60
+ }
61
+ // Check if function returns JSX
62
+ const sourceCode = context.getSourceCode();
63
+ const text = sourceCode.getText(node);
64
+ return text.includes('return <') || text.includes('=> <');
65
+ }
66
+ return {
67
+ FunctionDeclaration(node) {
68
+ if (!node.id)
69
+ return;
70
+ if (isJsxReturnFunction(node)) {
71
+ return;
72
+ }
73
+ if (!isVerbPhrase(node.id.name)) {
74
+ context.report({
75
+ node: node.id,
76
+ messageId: 'functionVerbPhrase',
77
+ });
78
+ }
79
+ },
80
+ VariableDeclarator(node) {
81
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
82
+ return;
83
+ // Only check if it's a function
84
+ if (node.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
85
+ node.init?.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
86
+ if (isJsxReturnFunction(node.init)) {
87
+ return;
88
+ }
89
+ if (!isVerbPhrase(node.id.name)) {
90
+ context.report({
91
+ node: node.id,
92
+ messageId: 'functionVerbPhrase',
93
+ });
94
+ }
95
+ }
96
+ },
97
+ MethodDefinition(node) {
98
+ if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
99
+ return;
100
+ if (!isVerbPhrase(node.key.name)) {
101
+ context.report({
102
+ node: node.key,
103
+ messageId: 'functionVerbPhrase',
104
+ });
105
+ }
106
+ },
107
+ };
108
+ },
109
+ });
110
+ //# sourceMappingURL=enforce-verb-noun-naming.js.map
@@ -9,7 +9,7 @@ exports.exportIfInDoubt = (0, createRule_1.createRule)({
9
9
  type: 'suggestion',
10
10
  docs: {
11
11
  description: 'All top-level const definitions, type definitions, and functions should be exported',
12
- recommended: 'warn',
12
+ recommended: 'error',
13
13
  },
14
14
  schema: [],
15
15
  messages: {
@@ -19,13 +19,42 @@ function isFunctionDefinition(node) {
19
19
  return (node?.type === 'FunctionExpression' ||
20
20
  node?.type === 'ArrowFunctionExpression');
21
21
  }
22
+ function isImmutableValue(node) {
23
+ if (!node)
24
+ return false;
25
+ switch (node.type) {
26
+ case 'Literal':
27
+ return true;
28
+ case 'TemplateLiteral':
29
+ return node.expressions.length === 0;
30
+ case 'UnaryExpression':
31
+ return isImmutableValue(node.argument);
32
+ case 'BinaryExpression':
33
+ if (node.left.type === 'PrivateIdentifier')
34
+ return false;
35
+ return isImmutableValue(node.left) && isImmutableValue(node.right);
36
+ default:
37
+ return false;
38
+ }
39
+ }
22
40
  function isMutableValue(node) {
23
41
  if (!node)
24
42
  return false;
25
- // Check for array literals and object expressions
26
- if (node.type === 'ArrayExpression' || node.type === 'ObjectExpression') {
43
+ // Check for object expressions (always mutable)
44
+ if (node.type === 'ObjectExpression') {
27
45
  return true;
28
46
  }
47
+ // Check array literals - mutable if empty or if they contain mutable values
48
+ if (node.type === 'ArrayExpression') {
49
+ // Empty arrays are mutable since they can be modified later
50
+ if (node.elements.length === 0)
51
+ return true;
52
+ // Arrays with spread elements are mutable
53
+ if (node.elements.some(element => !element || element.type === 'SpreadElement'))
54
+ return true;
55
+ // Arrays with non-immutable values are mutable
56
+ return node.elements.some(element => !isImmutableValue(element));
57
+ }
29
58
  // Check for new expressions (e.g., new Map(), new Set())
30
59
  if (node.type === 'NewExpression') {
31
60
  return true;
@@ -1,5 +1,3 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
- declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"upperSnakeCase" | "asConst", never[], {
3
- VariableDeclaration(node: TSESTree.VariableDeclaration): void;
4
- }>;
1
+ type MessageIds = 'upperSnakeCase' | 'asConst';
2
+ declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
5
3
  export default _default;
@@ -21,7 +21,8 @@ exports.default = (0, createRule_1.createRule)({
21
21
  defaultOptions: [],
22
22
  create(context) {
23
23
  // Check if the file is a TypeScript file
24
- const isTypeScript = context.getFilename().endsWith('.ts') || context.getFilename().endsWith('.tsx');
24
+ const isTypeScript = context.getFilename().endsWith('.ts') ||
25
+ context.getFilename().endsWith('.tsx');
25
26
  return {
26
27
  VariableDeclaration(node) {
27
28
  // Only check top-level const declarations
@@ -33,7 +34,7 @@ exports.default = (0, createRule_1.createRule)({
33
34
  return;
34
35
  }
35
36
  // Skip if any declaration is a function component, arrow function, forwardRef, or memo
36
- const shouldSkip = node.declarations.some(declaration => {
37
+ const shouldSkip = node.declarations.some((declaration) => {
37
38
  if (declaration.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
38
39
  return false;
39
40
  }
@@ -44,7 +45,8 @@ exports.default = (0, createRule_1.createRule)({
44
45
  return false;
45
46
  }
46
47
  // Skip function components (uppercase name + arrow function)
47
- if (/^[A-Z]/.test(name) && init.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
48
+ if (/^[A-Z]/.test(name) &&
49
+ init.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
48
50
  return true;
49
51
  }
50
52
  // Skip any arrow function
@@ -84,26 +86,20 @@ exports.default = (0, createRule_1.createRule)({
84
86
  init.type === utils_1.AST_NODE_TYPES.NewExpression) {
85
87
  return;
86
88
  }
87
- // Check for UPPER_SNAKE_CASE
88
- if (!isUpperSnakeCase(name)) {
89
- context.report({
90
- node: declaration.id,
91
- messageId: 'upperSnakeCase',
92
- fix(fixer) {
93
- const newName = name
94
- .replace(/([A-Z])/g, '_$1')
95
- .toUpperCase()
96
- .replace(/^_/, '');
97
- return fixer.replaceText(declaration.id, newName);
98
- },
99
- });
100
- }
89
+ const sourceCode = context.getSourceCode();
90
+ const initText = sourceCode.getText(init);
91
+ const typeAnnotation = declaration.id.typeAnnotation;
92
+ const typeText = typeAnnotation
93
+ ? sourceCode.getText(typeAnnotation)
94
+ : '';
101
95
  // Only check for as const in TypeScript files
102
96
  if (isTypeScript) {
103
97
  const isAsConstExpression = (node) => {
104
98
  if (node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
105
- return (node.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
106
- node.typeAnnotation?.typeName?.name === 'const');
99
+ return (node.typeAnnotation?.type ===
100
+ utils_1.AST_NODE_TYPES.TSTypeReference &&
101
+ node.typeAnnotation?.typeName
102
+ ?.name === 'const');
107
103
  }
108
104
  return false;
109
105
  };
@@ -112,23 +108,53 @@ exports.default = (0, createRule_1.createRule)({
112
108
  if (isAsConstExpression(node)) {
113
109
  return false;
114
110
  }
111
+ // Handle type assertions
112
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
113
+ node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
114
+ return shouldHaveAsConst(node.expression);
115
+ }
116
+ // Skip if there's an explicit type annotation
117
+ if (declaration.id.typeAnnotation) {
118
+ return false;
119
+ }
115
120
  // Check if it's a literal, array, or object that should have as const
121
+ // Skip regular expressions as they are already immutable
122
+ if (node.type === utils_1.AST_NODE_TYPES.Literal && 'regex' in node) {
123
+ return false;
124
+ }
116
125
  return (node.type === utils_1.AST_NODE_TYPES.Literal ||
117
126
  node.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
118
127
  node.type === utils_1.AST_NODE_TYPES.ObjectExpression);
119
128
  };
120
129
  if (shouldHaveAsConst(init)) {
121
130
  context.report({
122
- node: init,
131
+ node: declaration,
123
132
  messageId: 'asConst',
124
133
  fix(fixer) {
125
- const sourceCode = context.getSourceCode();
126
- const initText = sourceCode.getText(init);
127
134
  return fixer.replaceText(init, `${initText} as const`);
128
135
  },
129
136
  });
130
137
  }
131
138
  }
139
+ // Check for UPPER_SNAKE_CASE
140
+ if (!isUpperSnakeCase(name)) {
141
+ const newName = name
142
+ .replace(/([A-Z])/g, '_$1')
143
+ .toUpperCase()
144
+ .replace(/^_/, '');
145
+ context.report({
146
+ node: declaration,
147
+ messageId: 'upperSnakeCase',
148
+ fix(fixer) {
149
+ if (typeAnnotation) {
150
+ return fixer.replaceText(declaration, `${newName}${typeText} = ${initText}`);
151
+ }
152
+ else {
153
+ return fixer.replaceText(declaration.id, newName);
154
+ }
155
+ },
156
+ });
157
+ }
132
158
  });
133
159
  },
134
160
  };
@@ -5,7 +5,7 @@ const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
6
  // Convert camelCase to kebab-case
7
7
  function toKebabCase(str) {
8
- return str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
8
+ return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
9
9
  }
10
10
  // Normalize property name to kebab-case for consistent lookup
11
11
  function normalizePropertyName(name) {
@@ -38,7 +38,7 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
38
38
  type: 'suggestion',
39
39
  docs: {
40
40
  description: 'Warn when using CSS properties that trigger compositing layers',
41
- recommended: 'warn',
41
+ recommended: 'error',
42
42
  },
43
43
  schema: [],
44
44
  messages: {
@@ -49,10 +49,10 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
49
49
  create(context) {
50
50
  const seenNodes = new WeakSet();
51
51
  function checkPropertyValue(value) {
52
- return COMPOSITING_VALUES.has(value) ||
52
+ return (COMPOSITING_VALUES.has(value) ||
53
53
  value.includes('translate3d') ||
54
54
  value.includes('scale3d') ||
55
- value.includes('translateZ');
55
+ value.includes('translateZ'));
56
56
  }
57
57
  function checkProperty(propertyName, propertyValue) {
58
58
  const normalizedName = normalizePropertyName(propertyName);
@@ -73,11 +73,45 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
73
73
  }
74
74
  return false;
75
75
  }
76
+ function isStyleContext(node) {
77
+ let current = node;
78
+ while (current?.parent) {
79
+ // Check for JSX style attribute
80
+ if (current.parent.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
81
+ current.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
82
+ current.parent.name.name === 'style') {
83
+ return true;
84
+ }
85
+ // Check for style-related variable names or properties
86
+ if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
87
+ current.id.type === utils_1.AST_NODE_TYPES.Identifier &&
88
+ /style/i.test(current.id.name)) {
89
+ return true;
90
+ }
91
+ // Check for style-related object property assignments
92
+ if (current.parent.type === utils_1.AST_NODE_TYPES.Property &&
93
+ current.parent.key.type === utils_1.AST_NODE_TYPES.Identifier &&
94
+ /style/i.test(current.parent.key.name)) {
95
+ return true;
96
+ }
97
+ // Skip if we're in a TypeScript type definition
98
+ if (current.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
99
+ current.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
100
+ current.type === utils_1.AST_NODE_TYPES.TSPropertySignature) {
101
+ return false;
102
+ }
103
+ current = current.parent;
104
+ }
105
+ return false;
106
+ }
76
107
  function checkNode(node) {
77
108
  // Skip if we've already processed this node
78
109
  if (seenNodes.has(node))
79
110
  return;
80
111
  seenNodes.add(node);
112
+ // Skip if not in a style context
113
+ if (!isStyleContext(node))
114
+ return;
81
115
  let propertyName = '';
82
116
  let propertyValue = '';
83
117
  // Get property name
@@ -3,11 +3,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noEntireObjectHookDeps = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const typescript_1 = require("typescript");
6
7
  const HOOK_NAMES = new Set(['useEffect', 'useCallback', 'useMemo']);
7
8
  function isHookCall(node) {
8
9
  const callee = node.callee;
9
- return (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
10
- HOOK_NAMES.has(callee.name));
10
+ return (callee.type === utils_1.AST_NODE_TYPES.Identifier && HOOK_NAMES.has(callee.name));
11
+ }
12
+ function isArrayOrPrimitive(checker, esTreeNode, nodeMap) {
13
+ const tsNode = nodeMap.get(esTreeNode);
14
+ if (!tsNode)
15
+ return false;
16
+ const type = checker.getTypeAtLocation(tsNode);
17
+ // Check if it's a primitive type
18
+ if (type.flags &
19
+ (typescript_1.TypeFlags.String |
20
+ typescript_1.TypeFlags.Number |
21
+ typescript_1.TypeFlags.Boolean |
22
+ typescript_1.TypeFlags.Null |
23
+ typescript_1.TypeFlags.Undefined |
24
+ typescript_1.TypeFlags.Void |
25
+ typescript_1.TypeFlags.Never |
26
+ typescript_1.TypeFlags.Any |
27
+ typescript_1.TypeFlags.Unknown |
28
+ typescript_1.TypeFlags.BigInt |
29
+ typescript_1.TypeFlags.ESSymbol)) {
30
+ return true;
31
+ }
32
+ // Check if it's an array type
33
+ const typeNode = checker.typeToTypeNode(type, undefined, undefined);
34
+ if (type.symbol?.name === 'Array' ||
35
+ type.symbol?.escapedName === 'Array' ||
36
+ (typeNode && ((0, typescript_1.isArrayTypeNode)(typeNode) || (0, typescript_1.isTupleTypeNode)(typeNode)))) {
37
+ return true;
38
+ }
39
+ // If it's not a primitive or array, and has properties, it's an object
40
+ return false;
11
41
  }
12
42
  function getObjectUsagesInHook(hookBody, objectName) {
13
43
  const usages = new Set();
@@ -25,75 +55,44 @@ function getObjectUsagesInHook(hookBody, objectName) {
25
55
  parts.unshift(current.property.name);
26
56
  current = current.object;
27
57
  }
28
- if (current.type === utils_1.AST_NODE_TYPES.Identifier && current.name === objectName) {
29
- parts.unshift(objectName);
58
+ if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
59
+ current.name === objectName) {
30
60
  return parts.join('.');
31
61
  }
32
62
  return null;
33
63
  }
34
64
  function visit(node) {
35
- if (visited.has(node)) {
65
+ if (visited.has(node))
36
66
  return;
37
- }
38
67
  visited.add(node);
39
- if (node.type === utils_1.AST_NODE_TYPES.MemberExpression && !node.computed) {
40
- const accessPath = buildAccessPath(node);
41
- if (accessPath) {
42
- usages.add(accessPath);
68
+ if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
69
+ const path = buildAccessPath(node);
70
+ if (path) {
71
+ usages.add(`${objectName}.${path}`);
43
72
  }
44
73
  }
45
- // Visit children
46
- switch (node.type) {
47
- case utils_1.AST_NODE_TYPES.BlockStatement:
48
- case utils_1.AST_NODE_TYPES.Program:
49
- node.body.forEach(child => visit(child));
50
- break;
51
- case utils_1.AST_NODE_TYPES.ExpressionStatement:
52
- visit(node.expression);
53
- break;
54
- case utils_1.AST_NODE_TYPES.CallExpression:
55
- visit(node.callee);
56
- node.arguments.forEach(arg => visit(arg));
57
- break;
58
- case utils_1.AST_NODE_TYPES.MemberExpression:
59
- visit(node.object);
60
- if (!node.computed) {
61
- visit(node.property);
62
- }
63
- break;
64
- case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
65
- case utils_1.AST_NODE_TYPES.FunctionExpression:
66
- if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
67
- visit(node.body);
68
- }
69
- else {
70
- visit(node.body);
74
+ // Visit all child nodes
75
+ for (const key in node) {
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ const child = node[key];
78
+ if (child && typeof child === 'object') {
79
+ if (Array.isArray(child)) {
80
+ child.forEach((item) => {
81
+ if (item && typeof item === 'object') {
82
+ visit(item);
83
+ }
84
+ });
71
85
  }
72
- break;
73
- case utils_1.AST_NODE_TYPES.ReturnStatement:
74
- if (node.argument) {
75
- visit(node.argument);
86
+ else if ('type' in child) {
87
+ visit(child);
76
88
  }
77
- break;
78
- case utils_1.AST_NODE_TYPES.ConditionalExpression:
79
- visit(node.test);
80
- visit(node.consequent);
81
- visit(node.alternate);
82
- break;
83
- case utils_1.AST_NODE_TYPES.LogicalExpression:
84
- case utils_1.AST_NODE_TYPES.BinaryExpression:
85
- visit(node.left);
86
- visit(node.right);
87
- break;
88
- case utils_1.AST_NODE_TYPES.TemplateLiteral:
89
- node.expressions.forEach(expr => visit(expr));
90
- break;
89
+ }
91
90
  }
92
91
  }
93
92
  visit(hookBody);
94
93
  // Filter out intermediate paths
95
94
  const paths = Array.from(usages);
96
- const filteredPaths = paths.filter(path => !paths.some(otherPath => otherPath !== path && otherPath.startsWith(path + '.')));
95
+ const filteredPaths = paths.filter((path) => !paths.some((otherPath) => otherPath !== path && otherPath.startsWith(path + '.')));
97
96
  return new Set(filteredPaths);
98
97
  }
99
98
  exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
@@ -112,6 +111,13 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
112
111
  },
113
112
  defaultOptions: [],
114
113
  create(context) {
114
+ const parserServices = context.parserServices;
115
+ // Check if we have access to TypeScript services
116
+ if (!parserServices?.program || !parserServices?.esTreeNodeToTSNodeMap) {
117
+ throw new Error('You have to enable the `project` setting in parser options to use this rule');
118
+ }
119
+ const checker = parserServices.program.getTypeChecker();
120
+ const nodeMap = parserServices.esTreeNodeToTSNodeMap;
115
121
  return {
116
122
  CallExpression(node) {
117
123
  if (!isHookCall(node)) {
@@ -119,8 +125,7 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
119
125
  }
120
126
  // Get the dependency array argument
121
127
  const depsArg = node.arguments[node.arguments.length - 1];
122
- if (!depsArg ||
123
- depsArg.type !== utils_1.AST_NODE_TYPES.ArrayExpression) {
128
+ if (!depsArg || depsArg.type !== utils_1.AST_NODE_TYPES.ArrayExpression) {
124
129
  return;
125
130
  }
126
131
  // Get the hook callback function
@@ -131,10 +136,13 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
131
136
  return;
132
137
  }
133
138
  // Check each dependency in the array
134
- depsArg.elements.forEach(element => {
135
- if (element &&
136
- element.type === utils_1.AST_NODE_TYPES.Identifier) {
139
+ depsArg.elements.forEach((element) => {
140
+ if (element && element.type === utils_1.AST_NODE_TYPES.Identifier) {
137
141
  const objectName = element.name;
142
+ // Skip if the dependency is an array or primitive type
143
+ if (isArrayOrPrimitive(checker, element, nodeMap)) {
144
+ return;
145
+ }
138
146
  const usages = getObjectUsagesInHook(callbackArg.body, objectName);
139
147
  // If we found specific field usages and the entire object is in deps
140
148
  if (usages.size > 0) {
@@ -0,0 +1,11 @@
1
+ type Options = [
2
+ {
3
+ allowRecursiveFunctions?: boolean;
4
+ allowOverloadedFunctions?: boolean;
5
+ allowInterfaceMethodSignatures?: boolean;
6
+ allowAbstractMethodSignatures?: boolean;
7
+ allowDtsFiles?: boolean;
8
+ }
9
+ ];
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>;
11
+ export {};