@pythonidaer/complexity-report 1.0.2

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 (57) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -0
  4. package/assets/prettify.css +1 -0
  5. package/assets/prettify.js +2 -0
  6. package/assets/sort-arrow-sprite.png +0 -0
  7. package/complexity-breakdown.js +53 -0
  8. package/decision-points/ast-utils.js +127 -0
  9. package/decision-points/decision-type.js +92 -0
  10. package/decision-points/function-matching.js +185 -0
  11. package/decision-points/in-params.js +262 -0
  12. package/decision-points/index.js +6 -0
  13. package/decision-points/node-helpers.js +89 -0
  14. package/decision-points/parent-map.js +62 -0
  15. package/decision-points/parse-main.js +101 -0
  16. package/decision-points/ternary-multiline.js +86 -0
  17. package/export-generators/helpers.js +309 -0
  18. package/export-generators/index.js +143 -0
  19. package/export-generators/md-exports.js +160 -0
  20. package/export-generators/txt-exports.js +262 -0
  21. package/function-boundaries/arrow-brace-body.js +302 -0
  22. package/function-boundaries/arrow-helpers.js +93 -0
  23. package/function-boundaries/arrow-jsx.js +73 -0
  24. package/function-boundaries/arrow-object-literal.js +65 -0
  25. package/function-boundaries/arrow-single-expr.js +72 -0
  26. package/function-boundaries/brace-scanning.js +151 -0
  27. package/function-boundaries/index.js +67 -0
  28. package/function-boundaries/named-helpers.js +227 -0
  29. package/function-boundaries/parse-utils.js +456 -0
  30. package/function-extraction/ast-utils.js +112 -0
  31. package/function-extraction/extract-callback.js +65 -0
  32. package/function-extraction/extract-from-eslint.js +91 -0
  33. package/function-extraction/extract-name-ast.js +133 -0
  34. package/function-extraction/extract-name-regex.js +267 -0
  35. package/function-extraction/index.js +6 -0
  36. package/function-extraction/utils.js +29 -0
  37. package/function-hierarchy.js +427 -0
  38. package/html-generators/about.js +75 -0
  39. package/html-generators/file-boundary-builders.js +36 -0
  40. package/html-generators/file-breakdown.js +412 -0
  41. package/html-generators/file-data.js +50 -0
  42. package/html-generators/file-helpers.js +100 -0
  43. package/html-generators/file-javascript.js +430 -0
  44. package/html-generators/file-line-render.js +160 -0
  45. package/html-generators/file.css +370 -0
  46. package/html-generators/file.js +207 -0
  47. package/html-generators/folder.js +424 -0
  48. package/html-generators/index.js +6 -0
  49. package/html-generators/main-index.js +346 -0
  50. package/html-generators/shared.css +471 -0
  51. package/html-generators/utils.js +15 -0
  52. package/index.js +36 -0
  53. package/integration/eslint/index.js +94 -0
  54. package/integration/threshold/index.js +45 -0
  55. package/package.json +64 -0
  56. package/report/cli.js +58 -0
  57. package/report/index.js +559 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Matching AST function nodes to ESLint function results.
3
+ */
4
+
5
+ import { getNodeLine } from './ast-utils.js';
6
+
7
+ /**
8
+ * Finds the line number where => appears in an arrow function
9
+ * @param {Object} astFunc - AST arrow function node
10
+ * @param {string} sourceCode - Full source code
11
+ * @returns {number|null} Line number where => appears, or null if not found
12
+ */
13
+ export function findArrowFunctionLine(astFunc, sourceCode) {
14
+ if (astFunc.type !== 'ArrowFunctionExpression') return null;
15
+ if (!astFunc.range) return null;
16
+
17
+ const funcCode = sourceCode.substring(astFunc.range[0], astFunc.range[1]);
18
+ const arrowIndex = funcCode.indexOf('=>');
19
+ if (arrowIndex === -1) return null;
20
+
21
+ const linesBeforeArrow = sourceCode.substring(0, astFunc.range[0] + arrowIndex).split('\n');
22
+ return linesBeforeArrow.length;
23
+ }
24
+
25
+ /**
26
+ * Gets the match line for an AST function (handles arrow functions)
27
+ * @param {Object} astFunc - AST function node
28
+ * @param {string} sourceCode - Full source code
29
+ * @returns {number} Line number to match against
30
+ */
31
+ export function getMatchLineForASTFunction(astFunc, sourceCode) {
32
+ const astLine = getNodeLine(astFunc);
33
+ if (astFunc.type === 'ArrowFunctionExpression') {
34
+ const arrowLine = findArrowFunctionLine(astFunc, sourceCode);
35
+ if (arrowLine !== null) {
36
+ return arrowLine;
37
+ }
38
+ }
39
+ return astLine;
40
+ }
41
+
42
+ /**
43
+ * Checks if node types match between ESLint and AST
44
+ * @param {string} eslintNodeType - ESLint node type
45
+ * @param {string} astNodeType - AST node type
46
+ * @returns {boolean} True if types match
47
+ */
48
+ export function doNodeTypesMatch(eslintNodeType, astNodeType) {
49
+ return (eslintNodeType === 'FunctionDeclaration' && astNodeType === 'FunctionDeclaration') ||
50
+ (eslintNodeType === 'ArrowFunctionExpression' && astNodeType === 'ArrowFunctionExpression') ||
51
+ (eslintNodeType === 'FunctionExpression' && astNodeType === 'FunctionExpression');
52
+ }
53
+
54
+ /**
55
+ * Matches AST function to ESLint functions by node type
56
+ * @param {Object} astFunc - AST function node
57
+ * @param {Array} matchingEslintFuncs - Array of matching ESLint functions
58
+ * @param {Map} astToEslintMap - Map to update
59
+ * @returns {boolean} True if matched
60
+ */
61
+ export function matchByNodeType(astFunc, matchingEslintFuncs, astToEslintMap) {
62
+ for (const eslintFunc of matchingEslintFuncs) {
63
+ const nodeTypeMatches = doNodeTypesMatch(eslintFunc.nodeType, astFunc.type);
64
+ if (nodeTypeMatches || matchingEslintFuncs.length === 1) {
65
+ astToEslintMap.set(astFunc, eslintFunc.line);
66
+ return true;
67
+ }
68
+ }
69
+ if (matchingEslintFuncs.length > 0) {
70
+ astToEslintMap.set(astFunc, matchingEslintFuncs[0].line);
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Checks if ESLint line is within AST function range
78
+ * @param {Object} eslintFunc - ESLint function
79
+ * @param {Object} astFunc - AST function
80
+ * @param {string} sourceCode - Source code
81
+ * @returns {boolean} True if within range
82
+ */
83
+ export function isESLintLineInASTRange(eslintFunc, astFunc, sourceCode) {
84
+ if (!astFunc.range) return false;
85
+ const lines = sourceCode.split('\n');
86
+ const eslintLineStart = lines.slice(0, eslintFunc.line - 1).join('\n').length;
87
+ const eslintLineEnd = eslintLineStart + lines[eslintFunc.line - 1].length;
88
+ return eslintLineStart >= astFunc.range[0] && eslintLineEnd <= astFunc.range[1];
89
+ }
90
+
91
+ /**
92
+ * Tries to match AST function to ESLint functions by range
93
+ * @param {Object} astFunc - AST function node
94
+ * @param {Array} eslintFunctions - All ESLint functions
95
+ * @param {string} sourceCode - Source code
96
+ * @param {Map} astToEslintMap - Map to update
97
+ * @returns {boolean} True if matched
98
+ */
99
+ export function tryMatchByRange(astFunc, eslintFunctions, sourceCode, astToEslintMap) {
100
+ for (const eslintFunc of eslintFunctions) {
101
+ if (eslintFunc.nodeType === astFunc.type) {
102
+ if (isESLintLineInASTRange(eslintFunc, astFunc, sourceCode)) {
103
+ astToEslintMap.set(astFunc, eslintFunc.line);
104
+ return true;
105
+ }
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+
111
+ /**
112
+ * Matches AST function nodes to ESLint function results by line number
113
+ * and creates a map
114
+ * @param {Array} astFunctions - Function nodes from AST
115
+ * @param {Array} eslintFunctions - Function objects from ESLint results
116
+ * @param {string} sourceCode - Full source code (for finding => in arrow)
117
+ * @returns {Map} Map of AST function node to ESLint function line
118
+ */
119
+ export function matchFunctionsToAST(astFunctions, eslintFunctions, sourceCode) {
120
+ const astToEslintMap = new Map();
121
+
122
+ const eslintFunctionMap = new Map();
123
+ eslintFunctions.forEach(func => {
124
+ if (!eslintFunctionMap.has(func.line)) {
125
+ eslintFunctionMap.set(func.line, []);
126
+ }
127
+ eslintFunctionMap.get(func.line).push(func);
128
+ });
129
+
130
+ astFunctions.forEach(astFunc => {
131
+ const matchLine = getMatchLineForASTFunction(astFunc, sourceCode);
132
+ const matchingEslintFuncs = eslintFunctionMap.get(matchLine);
133
+
134
+ if (matchingEslintFuncs && matchingEslintFuncs.length > 0) {
135
+ matchByNodeType(astFunc, matchingEslintFuncs, astToEslintMap);
136
+ } else {
137
+ tryMatchByRange(astFunc, eslintFunctions, sourceCode, astToEslintMap);
138
+ }
139
+ });
140
+
141
+ return astToEslintMap;
142
+ }
143
+
144
+ /**
145
+ * Finds the innermost AST function that contains a decision point node
146
+ * @param {Object} node - Decision point node
147
+ * @param {Array} astFunctions - All AST function nodes
148
+ * @returns {Object|null} Innermost AST function node or null
149
+ */
150
+ export function findInnermostASTFunction(node, astFunctions) {
151
+ if (!node.range) return null;
152
+
153
+ let innermost = null;
154
+ let innermostSize = Infinity;
155
+
156
+ for (const astFunc of astFunctions) {
157
+ if (astFunc.range &&
158
+ node.range[0] >= astFunc.range[0] &&
159
+ node.range[1] <= astFunc.range[1]) {
160
+ const size = astFunc.range[1] - astFunc.range[0];
161
+ if (size < innermostSize) {
162
+ innermost = astFunc;
163
+ innermostSize = size;
164
+ }
165
+ }
166
+ }
167
+
168
+ return innermost;
169
+ }
170
+
171
+ /**
172
+ * Finds the ESLint function line that contains a decision point
173
+ * @param {Object} node - Decision point node
174
+ * @param {Array} astFunctions - All AST function nodes
175
+ * @param {Map} astToEslintMap - Map of AST function node to ESLint function line
176
+ * @returns {number|null} ESLint function line number or null
177
+ */
178
+ export function findFunctionForDecisionPoint(node, astFunctions, astToEslintMap) {
179
+ if (!node.range) return null;
180
+
181
+ const innermostASTFunc = findInnermostASTFunction(node, astFunctions);
182
+ if (!innermostASTFunc) return null;
183
+
184
+ return astToEslintMap.get(innermostASTFunc) || null;
185
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Checks for nodes in function parameters (default params, destructuring).
3
+ */
4
+
5
+ /**
6
+ * Checks if a node type is a function type
7
+ * @param {string} type - Node type
8
+ * @returns {boolean} True if function type
9
+ */
10
+ export function isFunctionType(type) {
11
+ return type === 'FunctionDeclaration' ||
12
+ type === 'FunctionExpression' ||
13
+ type === 'ArrowFunctionExpression';
14
+ }
15
+
16
+ /**
17
+ * Checks if a node is in a function's parameter list
18
+ * @param {Object} node - Node to check
19
+ * @param {Object} funcParent - Function parent node
20
+ * @returns {boolean} True if node is in function params
21
+ */
22
+ export function isNodeInFunctionParams(node, funcParent) {
23
+ if (!funcParent.params || !Array.isArray(funcParent.params)) {
24
+ return false;
25
+ }
26
+
27
+ if (funcParent.params.includes(node)) {
28
+ return true;
29
+ }
30
+
31
+ for (const param of funcParent.params) {
32
+ if (checkNestedPattern(param, node)) {
33
+ return true;
34
+ }
35
+ }
36
+
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Checks if a parent node is a pattern container
42
+ * @param {Object} parent - Parent node
43
+ * @returns {boolean} True if pattern container
44
+ */
45
+ export function isPatternContainer(parent) {
46
+ return parent.type === 'ArrayPattern' ||
47
+ parent.type === 'ObjectPattern' ||
48
+ parent.type === 'Property' ||
49
+ parent.type === 'RestElement';
50
+ }
51
+
52
+ /**
53
+ * Checks if AssignmentPattern is in function parameters by traversing up
54
+ * @param {Object} node - AssignmentPattern node
55
+ * @param {Map} parentMap - Parent map
56
+ * @returns {boolean} True if in function parameters
57
+ */
58
+ export function checkInFunctionParams(node, parentMap) {
59
+ let current = node;
60
+ let depth = 0;
61
+ const maxDepth = 15;
62
+
63
+ while (current && depth < maxDepth) {
64
+ const parent = parentMap.get(current);
65
+ if (!parent) break;
66
+
67
+ if (isFunctionType(parent.type)) {
68
+ if (isNodeInFunctionParams(node, parent)) {
69
+ return true;
70
+ }
71
+ }
72
+
73
+ if (isPatternContainer(parent)) {
74
+ current = parent;
75
+ depth += 1;
76
+ continue;
77
+ }
78
+
79
+ break;
80
+ }
81
+
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * Checks if VariableDeclaration is a direct child of function body
87
+ * @param {Object} varParent - VariableDeclaration node
88
+ * @param {Object} funcParent - Function parent node
89
+ * @returns {boolean} True if direct child of function body
90
+ */
91
+ export function isVariableDeclarationInFunctionBody(varParent, funcParent) {
92
+ if (!funcParent.body) return false;
93
+
94
+ const bodyType = funcParent.body.type;
95
+ if (bodyType !== 'BlockStatement' && bodyType !== 'Program') {
96
+ return false;
97
+ }
98
+
99
+ const bodyStatements = funcParent.body.body || funcParent.body.statements || [];
100
+ return bodyStatements.includes(varParent);
101
+ }
102
+
103
+ /**
104
+ * Checks if VariableDeclarator is in a top-level destructuring in function body
105
+ * @param {Object} varDeclarator - VariableDeclarator node
106
+ * @param {Object} node - Original AssignmentPattern node
107
+ * @param {Map} parentMap - Parent map
108
+ * @returns {boolean} True if in top-level destructuring in function body
109
+ */
110
+ export function checkInTopLevelDestructuring(varDeclarator, node, parentMap) {
111
+ let varParent = parentMap.get(varDeclarator);
112
+ let depth = 0;
113
+ const maxDepth = 15;
114
+
115
+ while (varParent && depth < maxDepth) {
116
+ if (varParent.type === 'VariableDeclaration') {
117
+ let funcParent = parentMap.get(varParent);
118
+ let funcDepth = 0;
119
+
120
+ while (funcParent && funcDepth < 10) {
121
+ if (isFunctionType(funcParent.type)) {
122
+ if (isVariableDeclarationInFunctionBody(varParent, funcParent)) {
123
+ return true;
124
+ }
125
+ break;
126
+ }
127
+ funcParent = parentMap.get(funcParent);
128
+ funcDepth += 1;
129
+ }
130
+ }
131
+ varParent = parentMap.get(varParent);
132
+ depth += 1;
133
+ }
134
+
135
+ return false;
136
+ }
137
+
138
+ /**
139
+ * Checks if AssignmentPattern is in a top-level destructuring
140
+ * assignment in function body
141
+ * @param {Object} node - AssignmentPattern node
142
+ * @param {Map} parentMap - Parent map
143
+ * @returns {boolean} True if in top-level destructuring
144
+ */
145
+ export function checkInFunctionBodyDestructuring(node, parentMap) {
146
+ let current = node;
147
+ let depth = 0;
148
+ const maxDepth = 15;
149
+
150
+ while (current && depth < maxDepth) {
151
+ const parent = parentMap.get(current);
152
+ if (!parent) break;
153
+
154
+ if (parent.type === 'VariableDeclarator' && parent.id) {
155
+ if (checkNestedPattern(parent.id, node)) {
156
+ if (checkInTopLevelDestructuring(parent, node, parentMap)) {
157
+ return true;
158
+ }
159
+ }
160
+ }
161
+
162
+ if (isPatternContainer(parent)) {
163
+ current = parent;
164
+ depth += 1;
165
+ continue;
166
+ }
167
+
168
+ break;
169
+ }
170
+
171
+ return false;
172
+ }
173
+
174
+ /**
175
+ * Checks if AssignmentPattern is in function parameters or destructuring
176
+ * at the top level of a function body (ESLint counts these as decision)
177
+ * @param {Object} node - AssignmentPattern node
178
+ * @param {Map} parentMap - Parent map
179
+ * @param {Object} _ast - Root AST node (for finding containing function)
180
+ * @returns {boolean} True if in function params or top-level destructuring
181
+ */
182
+ export function isInFunctionParameters(node, parentMap, _ast) {
183
+ if (checkInFunctionParams(node, parentMap)) {
184
+ return true;
185
+ }
186
+ return checkInFunctionBodyDestructuring(node, parentMap);
187
+ }
188
+
189
+ /**
190
+ * Checks if target is nested in ArrayPattern elements
191
+ * @param {Array} elements - Array of elements
192
+ * @param {Object} target - Target node to find
193
+ * @returns {boolean} True if target is found
194
+ */
195
+ function checkArrayPatternElements(elements, target) {
196
+ return elements.some(el => {
197
+ if (el === target) return true;
198
+ if (el && typeof el === 'object' && el.type) {
199
+ return checkNestedPattern(el, target);
200
+ }
201
+ return false;
202
+ });
203
+ }
204
+
205
+ /**
206
+ * Checks if target is nested in ObjectPattern properties
207
+ * @param {Array} properties - Array of properties
208
+ * @param {Object} target - Target node to find
209
+ * @returns {boolean} True if target is found
210
+ */
211
+ function checkObjectPatternProperties(properties, target) {
212
+ return properties.some(prop => {
213
+ if (prop.value === target) return true;
214
+ if (prop.value && typeof prop.value === 'object' && prop.value.type) {
215
+ return checkNestedPattern(prop.value, target);
216
+ }
217
+ if (prop.key && typeof prop.key === 'object' && prop.key.type) {
218
+ return checkNestedPattern(prop.key, target);
219
+ }
220
+ return false;
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Checks if target is nested in RestElement
226
+ * @param {Object} restElement - RestElement node
227
+ * @param {Object} target - Target node to find
228
+ * @returns {boolean} True if target is found
229
+ */
230
+ function checkRestElement(restElement, target) {
231
+ if (!restElement.argument) return false;
232
+ if (restElement.argument === target) return true;
233
+ if (restElement.argument && typeof restElement.argument === 'object' && restElement.argument.type) {
234
+ return checkNestedPattern(restElement.argument, target);
235
+ }
236
+ return false;
237
+ }
238
+
239
+ /**
240
+ * Checks if a node is nested in a pattern (recursively)
241
+ * @param {Object} pattern - Pattern node (ArrayPattern, ObjectPattern, Property, etc.)
242
+ * @param {Object} target - Target node to find
243
+ * @returns {boolean} True if target is nested in pattern
244
+ */
245
+ export function checkNestedPattern(pattern, target) {
246
+ if (!pattern || typeof pattern !== 'object') return false;
247
+ if (pattern === target) return true;
248
+
249
+ if (pattern.elements && Array.isArray(pattern.elements)) {
250
+ return checkArrayPatternElements(pattern.elements, target);
251
+ }
252
+
253
+ if (pattern.properties && Array.isArray(pattern.properties)) {
254
+ return checkObjectPatternProperties(pattern.properties, target);
255
+ }
256
+
257
+ if (pattern.type === 'RestElement') {
258
+ return checkRestElement(pattern, target);
259
+ }
260
+
261
+ return false;
262
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Decision points module — AST-based decision point parsing.
3
+ * Main export: parseDecisionPointsAST.
4
+ */
5
+
6
+ export { parseDecisionPointsAST } from './parse-main.js';
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Node line/column helpers for decision-points.
3
+ */
4
+
5
+ import { getNodeLine } from './ast-utils.js';
6
+
7
+ /**
8
+ * For LogicalExpression (||, &&, ??), returns the line where the operator
9
+ * token lives.
10
+ * @param {Object} node - LogicalExpression AST node
11
+ * @returns {number|null} 1-based line of the operator, or null
12
+ */
13
+ export function getLogicalExpressionOperatorLine(node) {
14
+ if (node.type !== 'LogicalExpression' || !node.left || !node.right) return null;
15
+ const leftEnd = node.left.loc && node.left.loc.end;
16
+ if (!leftEnd) return null;
17
+ return leftEnd.line;
18
+ }
19
+
20
+ /**
21
+ * For LogicalExpression, returns the column range of the operator.
22
+ * @param {Object} node - LogicalExpression AST node
23
+ * @returns {{ column: number, endColumn: number }|null} 0-based column range, or null
24
+ */
25
+ export function getLogicalExpressionOperatorRange(node) {
26
+ if (node.type !== 'LogicalExpression' || !node.left || !node.right) return null;
27
+ const leftEnd = node.left.loc && node.left.loc.end;
28
+ const rightStart = node.right.loc && node.right.loc.start;
29
+ if (!leftEnd || !rightStart || leftEnd.line !== rightStart.line) return null;
30
+ return {
31
+ column: leftEnd.column,
32
+ endColumn: rightStart.column,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Gets the column range for an AST node (for within-line highlighting).
38
+ * For multi-line nodes, returns only the range on the start line.
39
+ * @param {Object} node - AST node
40
+ * @returns {{ column: number, endColumn: number }|null} 0-based column
41
+ */
42
+ export function getNodeColumnRange(node) {
43
+ if (node.type === 'LogicalExpression') {
44
+ const opRange = getLogicalExpressionOperatorRange(node);
45
+ if (opRange) return opRange;
46
+ }
47
+ if (node.loc && node.loc.start && node.loc.end) {
48
+ const startLine = node.loc.start.line;
49
+ const endLine = node.loc.end.line;
50
+
51
+ // For single-line nodes, use exact AST range
52
+ if (startLine === endLine) {
53
+ return {
54
+ column: node.loc.start.column,
55
+ endColumn: node.loc.end.column,
56
+ };
57
+ }
58
+
59
+ // For multi-line nodes, return null (let multi-line logic handle it)
60
+ // This prevents invalid endColumn < column situations
61
+ return null;
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Returns the report line for a decision point node.
68
+ * LogicalExpression uses operator line.
69
+ * @param {Object} node - AST node
70
+ * @returns {number} 1-based line
71
+ */
72
+ export function getDecisionPointLineForNode(node) {
73
+ if (node.type === 'LogicalExpression') {
74
+ return getLogicalExpressionOperatorLine(node) ?? getNodeLine(node);
75
+ }
76
+ return getNodeLine(node);
77
+ }
78
+
79
+ /**
80
+ * Gets start/end line for a node's loc
81
+ * @param {Object} node - AST node
82
+ * @returns {{ startLine: number, endLine: number }|null} Line range or null
83
+ */
84
+ export function getNodeLineRange(node) {
85
+ const startLine = node.loc && node.loc.start ? node.loc.start.line : null;
86
+ const endLine = node.loc && node.loc.end ? node.loc.end.line : null;
87
+ if (startLine == null || endLine == null) return null;
88
+ return { startLine, endLine };
89
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Parent map building for AST nodes.
3
+ */
4
+
5
+ import { shouldSkipKey } from './ast-utils.js';
6
+
7
+ /**
8
+ * Processes an array of child nodes for parent map building
9
+ * @param {Array} array - Array of child nodes
10
+ * @param {Object} parent - Parent node
11
+ * @param {Map} parentMap - Parent map to update
12
+ * @param {Function} traverse - Traverse function to call recursively
13
+ */
14
+ function processArrayChildrenForParentMap(array, parent, parentMap, traverse) {
15
+ array.forEach(item => {
16
+ if (item && typeof item === 'object' && item.type) {
17
+ traverse(item, parent);
18
+ }
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Processes a single child node for parent map building
24
+ * @param {Object} child - Child node
25
+ * @param {Object} parent - Parent node
26
+ * @param {Function} traverse - Traverse function to call recursively
27
+ */
28
+ function processChildNodeForParentMap(child, parent, traverse) {
29
+ if (child && typeof child === 'object' && child.type) {
30
+ traverse(child, parent);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Builds a parent map for AST nodes
36
+ * @param {Object} ast - Root AST node
37
+ * @returns {Map} Map of node to parent node
38
+ */
39
+ export function buildParentMap(ast) {
40
+ const parentMap = new Map();
41
+
42
+ function traverse(node, parent = null) {
43
+ if (!node || typeof node !== 'object') return;
44
+
45
+ if (node.type) {
46
+ parentMap.set(node, parent);
47
+ }
48
+
49
+ for (const key in node) {
50
+ if (shouldSkipKey(key)) continue;
51
+ const child = node[key];
52
+ if (Array.isArray(child)) {
53
+ processArrayChildrenForParentMap(child, node, parentMap, traverse);
54
+ } else {
55
+ processChildNodeForParentMap(child, node, traverse);
56
+ }
57
+ }
58
+ }
59
+
60
+ traverse(ast);
61
+ return parentMap;
62
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Core parse logic: orchestrates AST parsing and decision point extraction.
3
+ */
4
+
5
+ import { parseAST, collectNodesByType, findAllFunctions } from './ast-utils.js';
6
+ import { buildParentMap } from './parent-map.js';
7
+ import { getDecisionPointType } from './decision-type.js';
8
+ import { findFunctionForDecisionPoint } from './function-matching.js';
9
+ import { matchFunctionsToAST } from './function-matching.js';
10
+ import { getDecisionPointLineForNode, getNodeColumnRange } from './node-helpers.js';
11
+ import { buildDecisionPointEntries } from './ternary-multiline.js';
12
+
13
+ /**
14
+ * Parses decision points from source code using ESLint's AST
15
+ * @param {string} sourceCode - Full source code
16
+ * @param {Map} _functionBoundaries - Map (not used in AST approach)
17
+ * @param {Array} functions - Function objects with line numbers
18
+ * @param {string} filePath - Path to the file
19
+ * @param {string} _projectRoot - Root directory of the project
20
+ * @param {{ variant?: 'classic' | 'modified' }} [options] - Options.
21
+ * variant: classic = each switch case +1; modified = whole switch +1.
22
+ * Defaults to 'classic'.
23
+ * @returns {Promise<Array>} Array of decision point objects
24
+ */
25
+ export async function parseDecisionPointsAST(
26
+ sourceCode,
27
+ _functionBoundaries,
28
+ functions,
29
+ filePath,
30
+ _projectRoot,
31
+ options = {}
32
+ ) {
33
+ const variant = options.variant === 'modified' ? 'modified' : 'classic';
34
+
35
+ try {
36
+ const ast = parseAST(sourceCode, filePath);
37
+ if (!ast) {
38
+ return [];
39
+ }
40
+
41
+ const astFunctions = findAllFunctions(ast);
42
+ const astToEslintMap = matchFunctionsToAST(astFunctions, functions, sourceCode);
43
+
44
+ if (astToEslintMap.size === 0) {
45
+ return [];
46
+ }
47
+
48
+ const parentMap = buildParentMap(ast);
49
+
50
+ const decisionPointNodeTypes = [
51
+ 'IfStatement',
52
+ 'ForStatement',
53
+ 'ForInStatement',
54
+ 'ForOfStatement',
55
+ 'WhileStatement',
56
+ 'DoWhileStatement',
57
+ ...(variant === 'modified' ? ['SwitchStatement'] : ['SwitchCase']),
58
+ 'CatchClause',
59
+ 'ConditionalExpression',
60
+ 'LogicalExpression',
61
+ 'ChainExpression',
62
+ 'MemberExpression',
63
+ 'BinaryExpression',
64
+ 'AssignmentPattern',
65
+ ];
66
+
67
+ const allNodes = [];
68
+ decisionPointNodeTypes.forEach(type => {
69
+ collectNodesByType(ast, type, allNodes);
70
+ });
71
+
72
+ const decisionPoints = [];
73
+ const lines = sourceCode.split('\n');
74
+ allNodes.forEach(node => {
75
+ const decisionType = getDecisionPointType(node, parentMap, ast, variant);
76
+ if (!decisionType) return;
77
+ const functionLine = findFunctionForDecisionPoint(
78
+ node,
79
+ astFunctions,
80
+ astToEslintMap
81
+ );
82
+ if (functionLine === null) return;
83
+ const line = getDecisionPointLineForNode(node);
84
+ const columnRange = getNodeColumnRange(node);
85
+ const entries = buildDecisionPointEntries(
86
+ node,
87
+ decisionType,
88
+ functionLine,
89
+ line,
90
+ columnRange,
91
+ lines
92
+ );
93
+ decisionPoints.push(...entries);
94
+ });
95
+
96
+ return decisionPoints;
97
+ } catch (error) {
98
+ console.error(`Error parsing AST for ${filePath}:`, error.message);
99
+ return [];
100
+ }
101
+ }