@blumintinc/eslint-plugin-blumint 1.0.2 → 1.0.3

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 (71) hide show
  1. package/README.md +62 -281
  2. package/lib/index.d.ts +1 -0
  3. package/lib/index.js +69 -0
  4. package/lib/rules/array-methods-this-context.d.ts +3 -0
  5. package/lib/rules/array-methods-this-context.js +64 -0
  6. package/lib/rules/class-methods-read-top-to-bottom.d.ts +2 -0
  7. package/lib/rules/class-methods-read-top-to-bottom.js +72 -0
  8. package/lib/rules/dynamic-https-errors.d.ts +2 -0
  9. package/lib/rules/dynamic-https-errors.js +57 -0
  10. package/lib/rules/export-if-in-doubt.d.ts +2 -0
  11. package/lib/rules/export-if-in-doubt.js +69 -0
  12. package/lib/rules/extract-global-constants.d.ts +2 -0
  13. package/lib/rules/extract-global-constants.js +63 -0
  14. package/lib/rules/generic-starts-with-t.d.ts +2 -0
  15. package/lib/rules/generic-starts-with-t.js +35 -0
  16. package/lib/rules/no-async-array-filter.d.ts +2 -0
  17. package/lib/rules/no-async-array-filter.js +40 -0
  18. package/lib/rules/no-async-foreach.d.ts +2 -0
  19. package/lib/rules/no-async-foreach.js +37 -0
  20. package/lib/rules/no-conditional-literals-in-jsx.d.ts +2 -0
  21. package/lib/rules/no-conditional-literals-in-jsx.js +61 -0
  22. package/lib/rules/no-filter-without-return.d.ts +2 -0
  23. package/lib/rules/no-filter-without-return.js +41 -0
  24. package/lib/rules/no-misused-switch-case.d.ts +2 -0
  25. package/lib/rules/no-misused-switch-case.js +35 -0
  26. package/lib/rules/no-unpinned-dependencies.d.ts +2 -0
  27. package/lib/rules/no-unpinned-dependencies.js +65 -0
  28. package/lib/rules/no-useless-fragment.d.ts +2 -0
  29. package/lib/rules/no-useless-fragment.js +46 -0
  30. package/lib/rules/prefer-fragment-shorthand.d.ts +3 -0
  31. package/lib/rules/prefer-fragment-shorthand.js +40 -0
  32. package/lib/rules/prefer-type-over-interface.d.ts +2 -0
  33. package/lib/rules/prefer-type-over-interface.js +45 -0
  34. package/lib/rules/require-memo.d.ts +6 -0
  35. package/lib/rules/require-memo.js +166 -0
  36. package/lib/utils/ASTHelpers.d.ts +12 -0
  37. package/lib/utils/ASTHelpers.js +336 -0
  38. package/lib/utils/createRule.d.ts +2 -0
  39. package/lib/utils/createRule.js +6 -0
  40. package/lib/utils/graph/ClassGraphBuilder.d.ts +29 -0
  41. package/lib/utils/graph/ClassGraphBuilder.js +80 -0
  42. package/lib/utils/graph/ClassGraphSorter.d.ts +7 -0
  43. package/lib/utils/graph/ClassGraphSorter.js +10 -0
  44. package/lib/utils/graph/ClassGraphSorterReadability.d.ts +16 -0
  45. package/lib/utils/graph/ClassGraphSorterReadability.js +94 -0
  46. package/lib/utils/ruleTester.d.ts +5 -0
  47. package/lib/utils/ruleTester.js +23 -0
  48. package/package.json +33 -13
  49. package/CHANGELOG.md +0 -63
  50. package/assets/logo.svg +0 -26
  51. package/jest.config.js +0 -13
  52. package/plugin/README.md +0 -80
  53. package/plugin/docs/rules/array-methods-this-context.md +0 -30
  54. package/plugin/docs/rules/class-methods-read-top-to-bottom.md +0 -46
  55. package/plugin/docs/rules/dynamic-https-errors.md +0 -23
  56. package/plugin/docs/rules/export-if-in-doubt.md +0 -26
  57. package/plugin/docs/rules/extract-global-constants.md +0 -33
  58. package/plugin/docs/rules/generic-starts-with-t.md +0 -33
  59. package/plugin/docs/rules/no-async-array-filter.md +0 -32
  60. package/plugin/docs/rules/no-async-foreach.md +0 -41
  61. package/plugin/docs/rules/no-conditional-literals-in-jsx.md +0 -27
  62. package/plugin/docs/rules/no-filter-without-return.md +0 -43
  63. package/plugin/docs/rules/no-misused-switch-case.md +0 -38
  64. package/plugin/docs/rules/no-unpinned-dependencies.md +0 -34
  65. package/plugin/docs/rules/no-useless-fragment.md +0 -25
  66. package/plugin/docs/rules/prefer-fragment-shorthand.md +0 -25
  67. package/plugin/docs/rules/prefer-type-over-interface.md +0 -25
  68. package/plugin/docs/rules/require-memo.md +0 -36
  69. package/plugin/package-lock.json +0 -7662
  70. package/plugin/package.json +0 -45
  71. package/tsconfig.json +0 -79
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireMemo = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
6
+ const isComponentExplicitlyUnmemoized = (componentName) => componentName.toLowerCase().includes('unmemoized');
7
+ function isFunction(node) {
8
+ return (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
9
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression);
10
+ }
11
+ function isHigherOrderFunctionReturningJSX(node) {
12
+ if (isFunction(node)) {
13
+ // Check if function takes another function as an argument
14
+ const hasFunctionParam = 'params' in node && node.params.some(isFunction);
15
+ if (node.body && node.body.type === 'BlockStatement') {
16
+ for (const statement of node.body.body) {
17
+ if (statement.type === 'ReturnStatement' && statement.argument) {
18
+ const returnsJSX = ASTHelpers_1.ASTHelpers.returnsJSX(statement.argument);
19
+ const returnsFunction = isFunction(statement.argument);
20
+ return (hasFunctionParam || returnsFunction) && returnsJSX;
21
+ }
22
+ }
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+ const isUnmemoizedArrowFunction = (parentNode) => {
28
+ return (parentNode.type === 'VariableDeclarator' &&
29
+ parentNode.id.type === 'Identifier' &&
30
+ !isComponentExplicitlyUnmemoized(parentNode.id.name));
31
+ };
32
+ const isUnmemoizedFunctionComponent = (parentNode, node) => {
33
+ return (node.type === 'FunctionDeclaration' &&
34
+ parentNode.type === 'Program' &&
35
+ node.id &&
36
+ !isComponentExplicitlyUnmemoized(node.id.name));
37
+ };
38
+ const isUnmemoizedExportedFunctionComponent = (parentNode, node) => {
39
+ return (node.type === 'FunctionDeclaration' &&
40
+ parentNode.type === 'ExportNamedDeclaration' &&
41
+ node.id &&
42
+ !isComponentExplicitlyUnmemoized(node.id.name));
43
+ };
44
+ function isMemoImport(importPath) {
45
+ // Match both absolute and relative paths ending with util/memo
46
+ return /(?:^|\/)util\/memo$/.test(importPath);
47
+ }
48
+ function checkFunction(context, node) {
49
+ const fileName = context.getFilename();
50
+ if (!fileName.endsWith('.tsx')) {
51
+ return;
52
+ }
53
+ if (isHigherOrderFunctionReturningJSX(node)) {
54
+ return;
55
+ }
56
+ const parentNode = node.parent;
57
+ if (node.parent.type === 'CallExpression') {
58
+ return;
59
+ }
60
+ if (ASTHelpers_1.ASTHelpers.returnsJSX(node.body) && ASTHelpers_1.ASTHelpers.hasParameters(node)) {
61
+ const results = [
62
+ isUnmemoizedArrowFunction,
63
+ isUnmemoizedFunctionComponent,
64
+ isUnmemoizedExportedFunctionComponent,
65
+ ].map((fn) => fn(parentNode, node));
66
+ if (results.some((result) => !!result)) {
67
+ context.report({
68
+ node,
69
+ messageId: 'requireMemo',
70
+ fix: results[2] || results[1]
71
+ ? function fix(fixer) {
72
+ const sourceCode = context.getSourceCode();
73
+ let importFix = null;
74
+ // Search for memo import statement
75
+ const importDeclarations = sourceCode.ast.body.filter((node) => node.type === 'ImportDeclaration');
76
+ const memoImport = importDeclarations.find((importDeclaration) => isMemoImport(importDeclaration.source.value));
77
+ if (memoImport) {
78
+ // Check if memo is already imported
79
+ if (!memoImport.specifiers.some((specifier) => specifier.local.name === 'memo')) {
80
+ // Add memo to existing import statement
81
+ const lastSpecifier = memoImport.specifiers[memoImport.specifiers.length - 1];
82
+ importFix = fixer.insertTextAfter(lastSpecifier, ', memo');
83
+ }
84
+ }
85
+ else {
86
+ // Calculate relative path based on current file location
87
+ const currentFilePath = context.getFilename();
88
+ const importPath = calculateImportPath(currentFilePath);
89
+ // Find the first import statement to insert after
90
+ const firstImport = importDeclarations[0];
91
+ // Add new import statement for memo
92
+ const importStatement = `import { memo } from '${importPath}';\n`;
93
+ if (firstImport) {
94
+ // Insert after the first import with a single newline
95
+ importFix = fixer.insertTextAfter(firstImport, '\n' + importStatement.trim());
96
+ }
97
+ else {
98
+ // Insert at the start of the file
99
+ importFix = fixer.insertTextBeforeRange([sourceCode.ast.range[0], sourceCode.ast.range[0]], importStatement.trim() + '\n');
100
+ }
101
+ }
102
+ const functionKeywordRange = [
103
+ node.range[0],
104
+ node.range[0] + 'function'.length,
105
+ ];
106
+ const functionKeywordReplacement = `const ${node.id.name} = memo(`;
107
+ // Step 3: Rename function
108
+ const functionNameReplacement = `function ${node.id.name}Unmemoized`;
109
+ const fixes = [
110
+ fixer.replaceTextRange(functionKeywordRange, functionKeywordReplacement),
111
+ fixer.insertTextAfterRange([node.range[1], node.range[1]], ')'),
112
+ fixer.replaceTextRange([node.id.range[0] - 1, node.id.range[1]], functionNameReplacement),
113
+ ];
114
+ if (importFix) {
115
+ fixes.push(importFix);
116
+ }
117
+ return fixes;
118
+ }
119
+ : undefined,
120
+ });
121
+ }
122
+ }
123
+ }
124
+ function calculateImportPath(currentFilePath) {
125
+ // Default to absolute path if we can't calculate relative path
126
+ if (!currentFilePath)
127
+ return 'src/util/memo';
128
+ // Split the current file path into parts and normalize
129
+ const parts = currentFilePath.split(/[\\/]/); // Handle both Unix and Windows paths
130
+ const srcIndex = parts.indexOf('src');
131
+ if (srcIndex === -1) {
132
+ // If we're not in a src directory, use absolute path
133
+ return 'src/util/memo';
134
+ }
135
+ // Calculate relative path based on current file depth from src
136
+ // Subtract 1 from depth to exclude the filename itself
137
+ const depth = parts.length - (srcIndex + 1) - 1;
138
+ return '../'.repeat(depth) + 'util/memo';
139
+ }
140
+ exports.requireMemo = {
141
+ create: (context) => ({
142
+ ArrowFunctionExpression(node) {
143
+ checkFunction(context, node);
144
+ },
145
+ FunctionDeclaration(node) {
146
+ checkFunction(context, node);
147
+ },
148
+ FunctionExpression(node) {
149
+ checkFunction(context, node);
150
+ },
151
+ }),
152
+ meta: {
153
+ type: 'problem',
154
+ docs: {
155
+ description: 'React components must be memoized',
156
+ recommended: 'error',
157
+ },
158
+ messages: {
159
+ requireMemo: 'Component definition not wrapped in React.memo()',
160
+ },
161
+ schema: [],
162
+ fixable: 'code',
163
+ },
164
+ defaultOptions: [],
165
+ };
166
+ //# sourceMappingURL=require-memo.js.map
@@ -0,0 +1,12 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ import { Graph } from './graph/ClassGraphBuilder';
3
+ export declare class ASTHelpers {
4
+ static blockIncludesIdentifier(block: TSESTree.BlockStatement): boolean;
5
+ static declarationIncludesIdentifier(node: TSESTree.Node | null): boolean;
6
+ static classMethodDependenciesOf(node: TSESTree.Node | null, graph: Graph, className: string): string[];
7
+ static isNode(value: unknown): value is TSESTree.Node;
8
+ static hasReturnStatement(node: TSESTree.Node): boolean;
9
+ static isNodeExported(node: TSESTree.Node): boolean;
10
+ static returnsJSX(node: TSESTree.Node): boolean;
11
+ static hasParameters(node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | TSESTree.FunctionDeclaration): boolean;
12
+ }
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ASTHelpers = void 0;
4
+ class ASTHelpers {
5
+ static blockIncludesIdentifier(block) {
6
+ for (const statement of block.body) {
7
+ if (ASTHelpers.declarationIncludesIdentifier(statement)) {
8
+ return true;
9
+ }
10
+ }
11
+ return false;
12
+ }
13
+ static declarationIncludesIdentifier(node) {
14
+ if (!node) {
15
+ return false;
16
+ }
17
+ switch (node.type) {
18
+ case 'TSNonNullExpression':
19
+ return this.declarationIncludesIdentifier(node.expression);
20
+ case 'ArrayPattern':
21
+ return node.elements.some((element) => ASTHelpers.declarationIncludesIdentifier(element));
22
+ case 'ObjectPattern':
23
+ return node.properties.some((property) => this.declarationIncludesIdentifier(property.value || null));
24
+ case 'AssignmentPattern':
25
+ return this.declarationIncludesIdentifier(node.left);
26
+ case 'RestElement':
27
+ return this.declarationIncludesIdentifier(node.argument);
28
+ case 'AwaitExpression':
29
+ return this.declarationIncludesIdentifier(node.argument);
30
+ case 'AssignmentExpression':
31
+ return (this.declarationIncludesIdentifier(node.left) ||
32
+ this.declarationIncludesIdentifier(node.right));
33
+ case 'BlockStatement':
34
+ return node.body.some((statement) => statement.type === 'BlockStatement' &&
35
+ ASTHelpers.blockIncludesIdentifier(statement));
36
+ case 'IfStatement':
37
+ return (this.declarationIncludesIdentifier(node.test) ||
38
+ this.declarationIncludesIdentifier(node.consequent) ||
39
+ this.declarationIncludesIdentifier(node.alternate));
40
+ case 'TSTypeAssertion':
41
+ return this.declarationIncludesIdentifier(node.expression);
42
+ case 'Identifier':
43
+ return true;
44
+ case 'SpreadElement':
45
+ return ASTHelpers.declarationIncludesIdentifier(node.argument);
46
+ case 'ChainExpression':
47
+ return ASTHelpers.declarationIncludesIdentifier(node.expression);
48
+ case 'ArrayExpression':
49
+ return node.elements.some((element) => element &&
50
+ (element.type === 'SpreadElement'
51
+ ? ASTHelpers.declarationIncludesIdentifier(element.argument)
52
+ : ASTHelpers.declarationIncludesIdentifier(element)));
53
+ case 'ObjectExpression':
54
+ return node.properties.some((property) => {
55
+ if (property.type === 'Property') {
56
+ return ASTHelpers.declarationIncludesIdentifier(property.value);
57
+ }
58
+ else if (property.type === 'SpreadElement') {
59
+ return ASTHelpers.declarationIncludesIdentifier(property.argument);
60
+ }
61
+ return false;
62
+ });
63
+ case 'Property':
64
+ return this.declarationIncludesIdentifier(node.value);
65
+ case 'BinaryExpression':
66
+ case 'LogicalExpression':
67
+ return (this.declarationIncludesIdentifier(node.left) ||
68
+ this.declarationIncludesIdentifier(node.right));
69
+ case 'UnaryExpression':
70
+ case 'UpdateExpression':
71
+ return this.declarationIncludesIdentifier(node.argument);
72
+ case 'MemberExpression':
73
+ if (node.object.type === 'ThisExpression') {
74
+ return true;
75
+ }
76
+ return (this.declarationIncludesIdentifier(node.object) ||
77
+ this.declarationIncludesIdentifier(node.property));
78
+ case 'CallExpression':
79
+ case 'NewExpression':
80
+ // For function and constructor calls, we care about both the callee and the arguments.
81
+ return (this.declarationIncludesIdentifier(node.callee) ||
82
+ node.arguments.some((arg) => this.declarationIncludesIdentifier(arg)));
83
+ case 'ConditionalExpression':
84
+ return (this.declarationIncludesIdentifier(node.test) ||
85
+ this.declarationIncludesIdentifier(node.consequent) ||
86
+ this.declarationIncludesIdentifier(node.alternate));
87
+ case 'TSAsExpression':
88
+ return this.declarationIncludesIdentifier(node.expression);
89
+ default:
90
+ return false;
91
+ }
92
+ }
93
+ static classMethodDependenciesOf(node, graph, className) {
94
+ const dependencies = [];
95
+ if (!node) {
96
+ return dependencies;
97
+ }
98
+ switch (node.type) {
99
+ case 'MethodDefinition':
100
+ const functionBody = node.value.body;
101
+ return (functionBody?.body || [])
102
+ .map((statement) => ASTHelpers.classMethodDependenciesOf(statement, graph, className))
103
+ .flat();
104
+ case 'Identifier':
105
+ dependencies.push(node.name);
106
+ break;
107
+ case 'ExpressionStatement':
108
+ return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
109
+ case 'MemberExpression':
110
+ if ((node.object.type === 'ThisExpression' &&
111
+ node.property.type === 'Identifier') ||
112
+ (node.object.type === 'Identifier' &&
113
+ node.object.name === className &&
114
+ node.property.type === 'Identifier')) {
115
+ dependencies.push(node.property.name);
116
+ }
117
+ else {
118
+ return [
119
+ ...ASTHelpers.classMethodDependenciesOf(node.object, graph, className),
120
+ ...ASTHelpers.classMethodDependenciesOf(node.property, graph, className),
121
+ ];
122
+ }
123
+ break;
124
+ case 'TSNonNullExpression':
125
+ return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
126
+ case 'ArrayPattern':
127
+ return node.elements
128
+ .map((element) => ASTHelpers.classMethodDependenciesOf(element, graph, className))
129
+ .flat();
130
+ case 'ObjectPattern':
131
+ return node.properties
132
+ .map((property) => ASTHelpers.classMethodDependenciesOf(property.value || null, graph, className))
133
+ .flat();
134
+ case 'AssignmentPattern':
135
+ return ASTHelpers.classMethodDependenciesOf(node.left, graph, className);
136
+ case 'RestElement':
137
+ return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
138
+ case 'AwaitExpression':
139
+ return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
140
+ case 'AssignmentExpression':
141
+ return [
142
+ ...ASTHelpers.classMethodDependenciesOf(node.left, graph, className),
143
+ ...ASTHelpers.classMethodDependenciesOf(node.right, graph, className),
144
+ ];
145
+ case 'BlockStatement':
146
+ return node.body
147
+ .map((statement) => ASTHelpers.classMethodDependenciesOf(statement, graph, className))
148
+ .flat()
149
+ .filter(Boolean);
150
+ case 'IfStatement':
151
+ return [
152
+ ...ASTHelpers.classMethodDependenciesOf(node.test, graph, className),
153
+ ...ASTHelpers.classMethodDependenciesOf(node.consequent, graph, className),
154
+ ...ASTHelpers.classMethodDependenciesOf(node.alternate, graph, className),
155
+ ];
156
+ case 'TSTypeAssertion':
157
+ return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
158
+ case 'Identifier':
159
+ return dependencies;
160
+ case 'SpreadElement':
161
+ return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
162
+ case 'ChainExpression':
163
+ return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
164
+ case 'ArrayExpression':
165
+ return node.elements
166
+ .map((element) => element &&
167
+ (element.type === 'SpreadElement'
168
+ ? ASTHelpers.classMethodDependenciesOf(element.argument, graph, className)
169
+ : ASTHelpers.classMethodDependenciesOf(element, graph, className)))
170
+ .flat()
171
+ .filter(Boolean);
172
+ case 'ObjectExpression':
173
+ return node.properties
174
+ .map((property) => {
175
+ if (property.type === 'Property') {
176
+ return ASTHelpers.classMethodDependenciesOf(property.value, graph, className);
177
+ }
178
+ else if (property.type === 'SpreadElement') {
179
+ return ASTHelpers.classMethodDependenciesOf(property.argument, graph, className);
180
+ }
181
+ return false;
182
+ })
183
+ .flat()
184
+ .filter(Boolean);
185
+ case 'Property':
186
+ return ASTHelpers.classMethodDependenciesOf(node.value, graph, className);
187
+ case 'BinaryExpression':
188
+ case 'LogicalExpression':
189
+ return [
190
+ ...ASTHelpers.classMethodDependenciesOf(node.left, graph, className),
191
+ ...ASTHelpers.classMethodDependenciesOf(node.right, graph, className),
192
+ ];
193
+ case 'UnaryExpression':
194
+ case 'UpdateExpression':
195
+ return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
196
+ case 'CallExpression':
197
+ case 'NewExpression':
198
+ // For function and constructor calls, we care about both the callee and the arguments.
199
+ return [
200
+ ...ASTHelpers.classMethodDependenciesOf(node.callee, graph, className),
201
+ ...node.arguments
202
+ .map((arg) => ASTHelpers.classMethodDependenciesOf(arg, graph, className))
203
+ .flat(),
204
+ ];
205
+ case 'ConditionalExpression':
206
+ return [
207
+ ...ASTHelpers.classMethodDependenciesOf(node.test, graph, className),
208
+ ...ASTHelpers.classMethodDependenciesOf(node.consequent, graph, className),
209
+ ...ASTHelpers.classMethodDependenciesOf(node.alternate, graph, className),
210
+ ];
211
+ case 'TSAsExpression':
212
+ return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
213
+ case 'VariableDeclaration':
214
+ return node.declarations
215
+ .map((declaration) => ASTHelpers.classMethodDependenciesOf(declaration, graph, className))
216
+ .flat()
217
+ .filter(Boolean);
218
+ case 'VariableDeclarator':
219
+ return ASTHelpers.classMethodDependenciesOf(node.init, graph, className);
220
+ case 'ForOfStatement':
221
+ return [
222
+ ...ASTHelpers.classMethodDependenciesOf(node.left, graph, className),
223
+ ...ASTHelpers.classMethodDependenciesOf(node.body, graph, className),
224
+ ...ASTHelpers.classMethodDependenciesOf(node.right, graph, className),
225
+ ];
226
+ case 'ForStatement':
227
+ return [node.body, node.init, node.test, node.update]
228
+ .map((node) => ASTHelpers.classMethodDependenciesOf(node, graph, className))
229
+ .flat();
230
+ case 'ThrowStatement':
231
+ return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
232
+ case 'TemplateLiteral':
233
+ return node.expressions
234
+ .map((expression) => ASTHelpers.classMethodDependenciesOf(expression, graph, className))
235
+ .flat();
236
+ case 'ReturnStatement':
237
+ return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
238
+ case 'ArrowFunctionExpression':
239
+ return [
240
+ ...node.params.flatMap((param) => ASTHelpers.classMethodDependenciesOf(param, graph, className)),
241
+ ...ASTHelpers.classMethodDependenciesOf(node.body, graph, className),
242
+ ];
243
+ default:
244
+ break;
245
+ }
246
+ // Removing duplicates
247
+ return [
248
+ ...new Set(dependencies.filter((dep) => graph?.[dep]?.type !== 'property')),
249
+ ];
250
+ }
251
+ static isNode(value) {
252
+ return typeof value === 'object' && value !== null && 'type' in value;
253
+ }
254
+ static hasReturnStatement(node) {
255
+ if (node.type === 'ReturnStatement') {
256
+ return true;
257
+ }
258
+ if (node.type === 'IfStatement') {
259
+ const consequentHasReturn = ASTHelpers.hasReturnStatement(node.consequent);
260
+ const alternateHasReturn = !!node.alternate && ASTHelpers.hasReturnStatement(node.alternate);
261
+ return consequentHasReturn && alternateHasReturn;
262
+ }
263
+ if (node.type === 'BlockStatement') {
264
+ for (const statement of node.body) {
265
+ if (ASTHelpers.hasReturnStatement(statement)) {
266
+ return true;
267
+ }
268
+ }
269
+ }
270
+ for (const key in node) {
271
+ if (key === 'parent') {
272
+ continue; // Ignore the parent property
273
+ }
274
+ const value = node[key];
275
+ if (ASTHelpers.isNode(value)) {
276
+ if (ASTHelpers.hasReturnStatement(value)) {
277
+ return true;
278
+ }
279
+ }
280
+ }
281
+ return false;
282
+ }
283
+ static isNodeExported(node) {
284
+ // Checking if the node is exported as a named export.
285
+ if (node.parent && node.parent.type === 'ExportNamedDeclaration') {
286
+ return true;
287
+ }
288
+ // Checking if the node is exported as default.
289
+ if (node.parent &&
290
+ node.parent.parent &&
291
+ node.parent.parent.type === 'ExportDefaultDeclaration') {
292
+ return true;
293
+ }
294
+ // Checking if the node is exported in a list of exports.
295
+ if (node.parent &&
296
+ node.parent.parent &&
297
+ node.parent.parent.type === 'ExportSpecifier' &&
298
+ node.parent.parent.exported.name === node.name) {
299
+ return true;
300
+ }
301
+ return false;
302
+ }
303
+ static returnsJSX(node) {
304
+ if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
305
+ return true;
306
+ }
307
+ if (node.type === 'BlockStatement') {
308
+ for (const statement of node.body) {
309
+ if (statement.type === 'ReturnStatement' &&
310
+ (statement.argument?.type === 'JSXElement' ||
311
+ statement.argument?.type === 'JSXFragment')) {
312
+ return true;
313
+ }
314
+ // Handle conditional returns
315
+ if (statement.type === 'ReturnStatement' &&
316
+ statement.argument?.type === 'ConditionalExpression') {
317
+ const conditionalExpr = statement.argument;
318
+ if (ASTHelpers.returnsJSX(conditionalExpr.consequent) ||
319
+ ASTHelpers.returnsJSX(conditionalExpr.alternate)) {
320
+ return true;
321
+ }
322
+ }
323
+ }
324
+ }
325
+ if (node.type === 'ConditionalExpression') {
326
+ return (ASTHelpers.returnsJSX(node.consequent) ||
327
+ ASTHelpers.returnsJSX(node.alternate));
328
+ }
329
+ return false;
330
+ }
331
+ static hasParameters(node) {
332
+ return node.params && node.params.length > 0;
333
+ }
334
+ }
335
+ exports.ASTHelpers = ASTHelpers;
336
+ //# sourceMappingURL=ASTHelpers.js.map
@@ -0,0 +1,2 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const createRule: <TOptions extends readonly unknown[], TMessageIds extends string, TRuleListener extends import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener = import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>({ name, meta, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<TOptions, TMessageIds, TRuleListener>>) => import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<TMessageIds, TOptions, TRuleListener>;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRule = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ exports.createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/BluMintInc/eslint-custom-rules/plugin/docs/rules/${name}.md`);
6
+ //# sourceMappingURL=createRule.js.map
@@ -0,0 +1,29 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ export type GraphNode = {
3
+ name: string;
4
+ type: 'method' | 'property' | 'constructor';
5
+ accessibility?: TSESTree.Accessibility;
6
+ isStatic: boolean;
7
+ dependencies: string[];
8
+ };
9
+ export type Graph = Record<string, GraphNode>;
10
+ /**
11
+ * Builds a graph of class methods and properties with their dependencies from a class declaration.
12
+ * A dependency in this case is the name of another class method.
13
+ */
14
+ export declare class ClassGraphBuilder {
15
+ private className;
16
+ private classBody;
17
+ graph: Graph;
18
+ private sorter;
19
+ constructor(className: string, classBody: TSESTree.ClassBody);
20
+ private buildGraph;
21
+ private static isClassMember;
22
+ private addMemberToGraph;
23
+ private static nodeTypeOf;
24
+ private static createGraphNode;
25
+ private static isNamedClassMethod;
26
+ private addDependencies;
27
+ get graphSorted(): GraphNode[];
28
+ get memberNamesSorted(): string[];
29
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClassGraphBuilder = void 0;
4
+ /* eslint-disable security/detect-object-injection */
5
+ const utils_1 = require("@typescript-eslint/utils");
6
+ const ClassGraphSorterReadability_1 = require("./ClassGraphSorterReadability");
7
+ const ASTHelpers_1 = require("../ASTHelpers");
8
+ /**
9
+ * Builds a graph of class methods and properties with their dependencies from a class declaration.
10
+ * A dependency in this case is the name of another class method.
11
+ */
12
+ class ClassGraphBuilder {
13
+ constructor(className, classBody) {
14
+ this.className = className;
15
+ this.classBody = classBody;
16
+ this.graph = {};
17
+ this.buildGraph();
18
+ // Note: extension requires injection of other sorters
19
+ this.sorter = new ClassGraphSorterReadability_1.ClassGraphSorterReadability(this.graph);
20
+ }
21
+ buildGraph() {
22
+ // NOTE: these need to be run sequentially for each member,
23
+ // since we need to know the class members before we can search
24
+ // methods for dependencies
25
+ this.classBody.body.forEach((member) => {
26
+ if (ClassGraphBuilder.isClassMember(member)) {
27
+ this.addMemberToGraph(member);
28
+ }
29
+ });
30
+ this.classBody.body.forEach((member) => {
31
+ if (ClassGraphBuilder.isNamedClassMethod(member)) {
32
+ const { key } = member;
33
+ if (utils_1.ASTUtils.isIdentifier(key)) {
34
+ this.addDependencies(member, key.name);
35
+ }
36
+ }
37
+ });
38
+ }
39
+ static isClassMember(node) {
40
+ return (node.type === 'MethodDefinition' || node.type === 'PropertyDefinition');
41
+ }
42
+ addMemberToGraph(member) {
43
+ const name = member.key.name;
44
+ const type = ClassGraphBuilder.nodeTypeOf(member);
45
+ const node = ClassGraphBuilder.createGraphNode(name, type, member.accessibility, member.static);
46
+ this.graph[name] = node;
47
+ }
48
+ static nodeTypeOf(member) {
49
+ if (member.type === 'MethodDefinition') {
50
+ return member.kind === 'constructor' ? 'constructor' : 'method';
51
+ }
52
+ return 'property';
53
+ }
54
+ static createGraphNode(name, type, accessibility, isStatic = false) {
55
+ return {
56
+ name,
57
+ type,
58
+ accessibility,
59
+ isStatic,
60
+ dependencies: [],
61
+ };
62
+ }
63
+ static isNamedClassMethod(node) {
64
+ return node.type === 'MethodDefinition';
65
+ }
66
+ addDependencies(node, methodName) {
67
+ const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) => !!this.graph[name] && name !== methodName);
68
+ if (this.graph[methodName]) {
69
+ this.graph[methodName].dependencies.push(...newDependencies);
70
+ }
71
+ }
72
+ get graphSorted() {
73
+ return this.sorter.nodesSorted;
74
+ }
75
+ get memberNamesSorted() {
76
+ return this.sorter.nodeNamesSorted;
77
+ }
78
+ }
79
+ exports.ClassGraphBuilder = ClassGraphBuilder;
80
+ //# sourceMappingURL=ClassGraphBuilder.js.map
@@ -0,0 +1,7 @@
1
+ import { Graph, GraphNode } from './ClassGraphBuilder';
2
+ export declare abstract class ClassGraphSorter {
3
+ graph: Graph;
4
+ constructor(graph: Graph);
5
+ abstract nodesSorted: GraphNode[];
6
+ abstract nodeNamesSorted: GraphNode['name'][];
7
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClassGraphSorter = void 0;
4
+ class ClassGraphSorter {
5
+ constructor(graph) {
6
+ this.graph = graph;
7
+ }
8
+ }
9
+ exports.ClassGraphSorter = ClassGraphSorter;
10
+ //# sourceMappingURL=ClassGraphSorter.js.map
@@ -0,0 +1,16 @@
1
+ import { Graph, GraphNode } from './ClassGraphBuilder';
2
+ import { ClassGraphSorter } from './ClassGraphSorter';
3
+ type GraphNodeModifier = 'isStatic' | 'accessibility';
4
+ export declare class ClassGraphSorterReadability extends ClassGraphSorter {
5
+ graph: Graph;
6
+ static readonly MODIFIER_PRIORITY_MAP: Record<GraphNodeModifier, any[]>;
7
+ private static readonly SEARCH_NODE_PRIORITY_FUNCTIONS;
8
+ constructor(graph: Graph);
9
+ get nodeNamesSorted(): string[];
10
+ get nodesSorted(): GraphNode[];
11
+ sortNodes(): GraphNode[];
12
+ private groupNodesByType;
13
+ private static sortMembersForReadability;
14
+ private dependencyDfs;
15
+ }
16
+ export {};