@blumintinc/eslint-plugin-blumint 0.1.22 → 0.1.23

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.
@@ -0,0 +1,44 @@
1
+ # Try to keep classes reading top to bottom (`@blumintinc/blumint/class-methods-read-top-to-bottom`)
2
+
3
+ ⚠️ This rule _warns_ in the ✅ `recommended` config.
4
+
5
+ <!-- end auto-generated rule header -->
6
+
7
+ This rule enforces an ordering of class methods according to BluMint's code style.
8
+
9
+ ## Rule Details
10
+
11
+ This rule warns for classes that don't follow the 'top to bottom' ordering - such that properties appear at the top of the class, the constructor (if present) follows, and other methods follow in a top-to-bottom order.
12
+
13
+ ### Examples of incorrect code for this rule:
14
+
15
+ ```typescript
16
+ class IncorrectlyOrdered {
17
+ field1: string;
18
+ field2: number;
19
+ methodA() {
20
+ this.methodB();
21
+ }
22
+ constructor() {
23
+ this.methodA();
24
+ this.methodC();
25
+ }
26
+ methodB() {}
27
+ methodC() {}
28
+ }
29
+ ```
30
+
31
+ ### Examples of correct code for this rule:
32
+ ```typescript
33
+ class CorrectlyOrdered {
34
+ field1: string;
35
+ field2: number;
36
+ constructor() {
37
+ this.methodA();
38
+ }
39
+ methodA() {
40
+ this.methodB();
41
+ }
42
+ methodB() {}
43
+ }
44
+ ```
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const array_methods_this_context_1 = require("./rules/array-methods-this-context");
4
+ const class_methods_read_top_to_bottom_1 = require("./rules/class-methods-read-top-to-bottom");
4
5
  const dynamic_https_errors_1 = require("./rules/dynamic-https-errors");
5
6
  const export_if_in_doubt_1 = require("./rules/export-if-in-doubt");
6
7
  const extract_global_constants_1 = require("./rules/extract-global-constants");
@@ -18,7 +19,7 @@ const require_memo_1 = require("./rules/require-memo");
18
19
  module.exports = {
19
20
  meta: {
20
21
  name: '@blumintinc/eslint-plugin-blumint',
21
- version: '0.1.22',
22
+ version: '0.1.23',
22
23
  },
23
24
  parseOptions: {
24
25
  ecmaVersion: 2020,
@@ -28,6 +29,7 @@ module.exports = {
28
29
  plugins: ['@blumintinc/blumint'],
29
30
  rules: {
30
31
  '@blumintinc/blumint/array-methods-this-context': 'warn',
32
+ '@blumintinc/blumint/class-methods-read-top-to-bottom': 'warn',
31
33
  '@blumintinc/blumint/dynamic-https-errors': 'warn',
32
34
  // '@blumintinc/blumint/export-if-in-doubt': 'warn',
33
35
  // '@blumintinc/blumint/extract-global-constants': 'warn',
@@ -47,6 +49,7 @@ module.exports = {
47
49
  },
48
50
  rules: {
49
51
  'array-methods-this-context': array_methods_this_context_1.arrayMethodsThisContext,
52
+ 'class-methods-read-top-to-bottom': class_methods_read_top_to_bottom_1.classMethodsReadTopToBottom,
50
53
  'dynamic-https-errors': dynamic_https_errors_1.dynamicHttpsErrors,
51
54
  'export-if-in-doubt': export_if_in_doubt_1.exportIfInDoubt,
52
55
  'extract-global-constants': extract_global_constants_1.extractGlobalConstants,
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classMethodsReadTopToBottom = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ const ClassGraphBuilder_1 = require("../utils/graph/ClassGraphBuilder");
6
+ function getMemberName(member) {
7
+ if (member.type === 'MethodDefinition' ||
8
+ member.type === 'PropertyDefinition') {
9
+ return member.key.type === 'Identifier' ? member.key.name : null;
10
+ }
11
+ return null;
12
+ }
13
+ exports.classMethodsReadTopToBottom = (0, createRule_1.createRule)({
14
+ name: 'class-methods-read-top-to-bottom',
15
+ meta: {
16
+ type: 'suggestion',
17
+ docs: {
18
+ description: 'Ensures classes read linearly from top to bottom.',
19
+ recommended: 'warn',
20
+ },
21
+ schema: [],
22
+ messages: {
23
+ classMethodsReadTopToBottom: 'Methods should be ordered for top-down readability.',
24
+ },
25
+ fixable: 'code', // To allow ESLint to autofix issues.
26
+ },
27
+ defaultOptions: [],
28
+ create(context) {
29
+ let className;
30
+ return {
31
+ ClassDeclaration(node) {
32
+ className = node.id?.name || '';
33
+ },
34
+ 'ClassBody:exit'(node) {
35
+ const graphBuilder = new ClassGraphBuilder_1.ClassGraphBuilder(className, node);
36
+ const sortedOrder = graphBuilder.memberNamesSorted;
37
+ const actualOrder = node.body
38
+ .map((member) => member.type === 'MethodDefinition' ||
39
+ member.type === 'PropertyDefinition'
40
+ ? member.key.name
41
+ : null)
42
+ .filter(Boolean);
43
+ for (let i = 0; i < actualOrder.length; i++) {
44
+ if (actualOrder[i] !== sortedOrder[i]) {
45
+ const sourceCode = context.getSourceCode();
46
+ const newClassBody = sortedOrder
47
+ .map((n) => {
48
+ // Fetch the actual AST node corresponding to the name
49
+ const memberNode = node.body.find((member) => getMemberName(member) === n);
50
+ const comments = sourceCode.getCommentsBefore(memberNode);
51
+ memberNode.range = [
52
+ Math.min(memberNode.range[0], Math.min(...comments.map((comment) => comment.range[0]))),
53
+ Math.max(memberNode.range[1], Math.max(...comments.map((comment) => comment.range[1]))),
54
+ ];
55
+ return sourceCode.getText(memberNode);
56
+ })
57
+ .join('\n');
58
+ return context.report({
59
+ node,
60
+ messageId: 'classMethodsReadTopToBottom',
61
+ fix(fixer) {
62
+ return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], // Exclude the curly braces
63
+ newClassBody);
64
+ },
65
+ });
66
+ }
67
+ }
68
+ },
69
+ };
70
+ },
71
+ });
72
+ //# sourceMappingURL=class-methods-read-top-to-bottom.js.map
@@ -90,6 +90,164 @@ class ASTHelpers {
90
90
  return false;
91
91
  }
92
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
+ }
93
251
  static isNode(value) {
94
252
  return typeof value === 'object' && value !== null && 'type' in value;
95
253
  }
@@ -165,7 +323,8 @@ class ASTHelpers {
165
323
  }
166
324
  }
167
325
  if (node.type === 'ConditionalExpression') {
168
- return (this.returnsJSX(node.consequent) || this.returnsJSX(node.alternate));
326
+ return (ASTHelpers.returnsJSX(node.consequent) ||
327
+ ASTHelpers.returnsJSX(node.alternate));
169
328
  }
170
329
  return false;
171
330
  }
@@ -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 ClassGraphSorter_1 = require("./ClassGraphSorter");
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 ClassGraphSorter_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,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClassGraphSorterReadability = exports.ClassGraphSorter = void 0;
4
+ class ClassGraphSorter {
5
+ constructor(graph) {
6
+ this.graph = graph;
7
+ }
8
+ }
9
+ exports.ClassGraphSorter = ClassGraphSorter;
10
+ class ClassGraphSorterReadability extends ClassGraphSorter {
11
+ constructor(graph) {
12
+ super(graph);
13
+ this.graph = graph;
14
+ }
15
+ get nodeNamesSorted() {
16
+ return this.nodesSorted.map((node) => node.name);
17
+ }
18
+ get nodesSorted() {
19
+ return this.sortNodes();
20
+ }
21
+ sortNodes() {
22
+ const { methods, properties, classConstructor } = this.groupNodesByType();
23
+ const [propertiesSorted, methodsSorted] = [properties, methods].map((nodeGroup) => nodeGroup.sort(ClassGraphSorterReadability.sortMembersForReadability));
24
+ const searchNodeCandidates = [classConstructor, ...methodsSorted].filter((node) => !!node);
25
+ const searchNodeCandidatesValidated = searchNodeCandidates.filter((method, _index, arr) => ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS.some((fn) => !!fn(method, arr)));
26
+ const searchNodesSorted = searchNodeCandidatesValidated.sort((a, b) => {
27
+ const getPriority = (method) => {
28
+ const priority = ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS.findIndex((fn) => !!fn(method, methodsSorted));
29
+ return priority === -1
30
+ ? ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS.length
31
+ : priority;
32
+ };
33
+ return getPriority(a) - getPriority(b);
34
+ });
35
+ const dfsSortedNodes = this.dependencyDfs(searchNodesSorted);
36
+ return [...propertiesSorted, ...dfsSortedNodes];
37
+ }
38
+ groupNodesByType() {
39
+ const nodes = Object.values(this.graph);
40
+ return nodes.reduce((acc, node) => {
41
+ switch (node.type) {
42
+ case 'property':
43
+ acc.properties.push(node);
44
+ break;
45
+ case 'constructor':
46
+ acc.classConstructor = node;
47
+ break;
48
+ case 'method':
49
+ acc.methods.push(node);
50
+ break;
51
+ }
52
+ return acc;
53
+ }, {
54
+ properties: [],
55
+ methods: [],
56
+ classConstructor: null,
57
+ });
58
+ }
59
+ static sortMembersForReadability(a, b) {
60
+ // NOTE: the ordering from Object.entries is safe here since it is readonly
61
+ for (const [key, priorities] of Object.entries(ClassGraphSorterReadability.MODIFIER_PRIORITY_MAP)) {
62
+ const indexA = priorities.indexOf(a[key]);
63
+ const indexB = priorities.indexOf(b[key]);
64
+ if (indexA !== indexB) {
65
+ return indexA - indexB;
66
+ }
67
+ }
68
+ return 0;
69
+ }
70
+ dependencyDfs(searchNodes) {
71
+ const visited = new Set();
72
+ const dfsSortedNodes = [];
73
+ const dfs = (node) => {
74
+ if (visited.has(node.name) || !node) {
75
+ return;
76
+ }
77
+ visited.add(node.name);
78
+ dfsSortedNodes.push(node);
79
+ for (const dep of node.dependencies) {
80
+ dfs(this.graph[String(dep)]);
81
+ }
82
+ };
83
+ searchNodes.forEach((node) => dfs(node));
84
+ return dfsSortedNodes;
85
+ }
86
+ }
87
+ exports.ClassGraphSorterReadability = ClassGraphSorterReadability;
88
+ ClassGraphSorterReadability.MODIFIER_PRIORITY_MAP = {
89
+ isStatic: [true, false],
90
+ accessibility: ['public', undefined, 'private'],
91
+ };
92
+ ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS = [
93
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
94
+ (method, _methods) => method.type === 'constructor',
95
+ (method, _methods) => !_methods.some((node) => node.dependencies.includes(method.name)),
96
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
97
+ (method, _methods) => method.dependencies.length === 0,
98
+ ];
99
+ //# sourceMappingURL=ClassGraphSorter.js.map
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createRule = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
- exports.createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/BluMintInc/custom-eslint-rules/plugin/docs/rules/${name}.md`);
5
+ exports.createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/BluMintInc/eslint-custom-rules/plugin/docs/rules/${name}.md`);
6
6
  //# sourceMappingURL=createRule.js.map
@@ -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,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,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClassGraphSorterReadability = void 0;
4
+ const ClassGraphSorter_1 = require("./ClassGraphSorter");
5
+ class ClassGraphSorterReadability extends ClassGraphSorter_1.ClassGraphSorter {
6
+ constructor(graph) {
7
+ super(graph);
8
+ this.graph = graph;
9
+ }
10
+ get nodeNamesSorted() {
11
+ return this.nodesSorted.map((node) => node.name);
12
+ }
13
+ get nodesSorted() {
14
+ return this.sortNodes();
15
+ }
16
+ sortNodes() {
17
+ const { methods, properties, classConstructor } = this.groupNodesByType();
18
+ const [propertiesSorted, methodsSorted] = [properties, methods].map((nodeGroup) => nodeGroup.sort(ClassGraphSorterReadability.sortMembersForReadability));
19
+ const searchNodeCandidates = [classConstructor, ...methodsSorted].filter((node) => !!node);
20
+ const searchNodeCandidatesValidated = searchNodeCandidates.filter((method, _index, arr) => ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS.some((fn) => !!fn(method, arr)));
21
+ const searchNodesSorted = searchNodeCandidatesValidated.sort((a, b) => {
22
+ const getPriority = (method) => {
23
+ const priority = ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS.findIndex((fn) => !!fn(method, methodsSorted));
24
+ return priority === -1
25
+ ? ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS.length
26
+ : priority;
27
+ };
28
+ return getPriority(a) - getPriority(b);
29
+ });
30
+ const dfsSortedNodes = this.dependencyDfs(searchNodesSorted);
31
+ return [...propertiesSorted, ...dfsSortedNodes];
32
+ }
33
+ groupNodesByType() {
34
+ const nodes = Object.values(this.graph);
35
+ return nodes.reduce((acc, node) => {
36
+ switch (node.type) {
37
+ case 'property':
38
+ acc.properties.push(node);
39
+ break;
40
+ case 'constructor':
41
+ acc.classConstructor = node;
42
+ break;
43
+ case 'method':
44
+ acc.methods.push(node);
45
+ break;
46
+ }
47
+ return acc;
48
+ }, {
49
+ properties: [],
50
+ methods: [],
51
+ classConstructor: null,
52
+ });
53
+ }
54
+ static sortMembersForReadability(a, b) {
55
+ // NOTE: the ordering from Object.entries is safe here since it is readonly
56
+ for (const [key, priorities] of Object.entries(ClassGraphSorterReadability.MODIFIER_PRIORITY_MAP)) {
57
+ const indexA = priorities.indexOf(a[key]);
58
+ const indexB = priorities.indexOf(b[key]);
59
+ if (indexA !== indexB) {
60
+ return indexA - indexB;
61
+ }
62
+ }
63
+ return 0;
64
+ }
65
+ dependencyDfs(searchNodes) {
66
+ const visited = new Set();
67
+ const dfsSortedNodes = [];
68
+ const dfs = (node) => {
69
+ if (visited.has(node.name) || !node) {
70
+ return;
71
+ }
72
+ visited.add(node.name);
73
+ dfsSortedNodes.push(node);
74
+ for (const dep of node.dependencies) {
75
+ dfs(this.graph[String(dep)]);
76
+ }
77
+ };
78
+ searchNodes.forEach((node) => dfs(node));
79
+ return dfsSortedNodes;
80
+ }
81
+ }
82
+ exports.ClassGraphSorterReadability = ClassGraphSorterReadability;
83
+ ClassGraphSorterReadability.MODIFIER_PRIORITY_MAP = {
84
+ isStatic: [true, false],
85
+ accessibility: ['public', undefined, 'private'],
86
+ };
87
+ ClassGraphSorterReadability.SEARCH_NODE_PRIORITY_FUNCTIONS = [
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89
+ (method, _methods) => method.type === 'constructor',
90
+ (method, _methods) => !_methods.some((node) => node.dependencies.includes(method.name)),
91
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
92
+ (method, _methods) => method.dependencies.length === 0,
93
+ ];
94
+ //# sourceMappingURL=ClassGraphSorterReadability.js.map
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classNodesDfs = exports.ClassGraphSorter = void 0;
4
+ class ClassGraphSorter {
5
+ constructor(graph, sortFunction) {
6
+ this.graph = graph;
7
+ this.sortFunction = sortFunction;
8
+ }
9
+ sort() {
10
+ const nodesGrouped = this.sortFunction(Object.values(this.graph));
11
+ // Additional logic for DFS and sorting will be implemented here
12
+ // Placeholder for the sorted result
13
+ const sortedResult = [];
14
+ // Logic to populate sortedResult
15
+ return sortedResult;
16
+ }
17
+ defaultEnhancedSort(nodes) {
18
+ // Implementation of the default enhanced sort logic
19
+ // This will include sorting by type, static status, and accessibility
20
+ // Placeholder for return value
21
+ return { properties: [], notProperties: [] };
22
+ }
23
+ // Additional private helper methods will be implemented here
24
+ // Placeholder for depth-first search (DFS) logic
25
+ dfs(node, visited, sortedNodes) {
26
+ // Implementation of the DFS logic
27
+ }
28
+ }
29
+ exports.ClassGraphSorter = ClassGraphSorter;
30
+ function classNodesDfs(graph) {
31
+ const { properties, notProperties } = sortClassMembersForReadability(Object.values(graph));
32
+ //now we have nodes in properties -> constructor -> methods order
33
+ //we need to sort constructor & methods such that
34
+ //if a node has dependencies, then the dependencies are moved after that node in a dfs manner
35
+ const visited = new Set();
36
+ const dfsSortedNodes = [];
37
+ const dfs = (node) => {
38
+ if (visited.has(node.name)) {
39
+ return;
40
+ }
41
+ visited.add(node.name);
42
+ dfsSortedNodes.push(node);
43
+ for (const dep of node.dependencies) {
44
+ dfs(graph[dep]);
45
+ }
46
+ };
47
+ const searchNodes = notProperties
48
+ .filter((method) => method.type === 'constructor' ||
49
+ !notProperties.some((node) => node.dependencies.includes(method.name)) ||
50
+ method.dependencies.length === 0)
51
+ .sort((a, b) => {
52
+ const getPriority = (method) => {
53
+ if (method.type === 'constructor')
54
+ return 1;
55
+ if (!notProperties.some((node) => node.dependencies.includes(method.name)))
56
+ return 2;
57
+ if (method.dependencies.length === 0)
58
+ return 3;
59
+ return 4; // In case none of the conditions match
60
+ };
61
+ return getPriority(a) - getPriority(b);
62
+ });
63
+ for (const searchNode of searchNodes) {
64
+ dfs(searchNode);
65
+ }
66
+ return [...properties, ...dfsSortedNodes].map((node) => node.name);
67
+ }
68
+ exports.classNodesDfs = classNodesDfs;
69
+ function sortClassMembersForReadability(nodes) {
70
+ // 1. Group nodes by type
71
+ const properties = [];
72
+ const methods = [];
73
+ let constructor = null;
74
+ for (const node of nodes) {
75
+ switch (node.type) {
76
+ case 'property':
77
+ properties.push(node);
78
+ break;
79
+ case 'constructor':
80
+ constructor = node;
81
+ break;
82
+ case 'method':
83
+ methods.push(node);
84
+ break;
85
+ }
86
+ }
87
+ // 2. Fine-tune ordering within each group
88
+ // For properties and methods
89
+ const comparator = (a, b) => {
90
+ // Static methods/properties come first
91
+ if (a.isStatic && !b.isStatic)
92
+ return -1;
93
+ if (!a.isStatic && b.isStatic)
94
+ return 1;
95
+ const accessibilityA = a.accessibility ?? 'public';
96
+ const accessibilityB = b.accessibility ?? 'public';
97
+ if (accessibilityA === 'public' && accessibilityB === 'private')
98
+ return -1;
99
+ if (accessibilityA === 'private' && accessibilityB === 'public')
100
+ return 1;
101
+ // Maintain original order otherwise
102
+ return 0;
103
+ };
104
+ properties.sort(comparator);
105
+ methods.sort(comparator);
106
+ // Return the nodes in the desired order
107
+ return { properties, notProperties: [constructor, ...methods] };
108
+ }
109
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ASTHelpers = void 0;
4
+ class ASTHelpers {
5
+ constructor() {
6
+ ASTHelpers.returnsJSX;
7
+ }
8
+ static returnsJSX(node) {
9
+ if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
10
+ return true;
11
+ }
12
+ this.bar();
13
+ if (node.type === 'BlockStatement') {
14
+ for (const statement of node.body) {
15
+ if (statement.type === 'ReturnStatement' &&
16
+ (statement.argument?.type === 'JSXElement' ||
17
+ statement.argument?.type === 'JSXFragment')) {
18
+ return true;
19
+ }
20
+ // Handle conditional returns
21
+ if (statement.type === 'ReturnStatement' &&
22
+ statement.argument?.type === 'ConditionalExpression') {
23
+ const conditionalExpr = statement.argument;
24
+ if (ASTHelpers.returnsJSX(conditionalExpr.consequent) ||
25
+ ASTHelpers.returnsJSX(conditionalExpr.alternate)) {
26
+ return true;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ if (node.type === 'ConditionalExpression') {
32
+ return (ASTHelpers.returnsJSX(node.consequent) ||
33
+ ASTHelpers.returnsJSX(node.alternate));
34
+ }
35
+ return false;
36
+ }
37
+ bar() {
38
+ this.foo();
39
+ }
40
+ foo() {
41
+ return ASTHelpers.returnsJSX(node);
42
+ }
43
+ }
44
+ exports.ASTHelpers = ASTHelpers;
45
+ //# sourceMappingURL=testClass.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Custom eslint rules for use at BluMint",
5
5
  "keywords": [
6
6
  "eslint",