@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.
- package/CHANGELOG.md +122 -0
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/assets/prettify.css +1 -0
- package/assets/prettify.js +2 -0
- package/assets/sort-arrow-sprite.png +0 -0
- package/complexity-breakdown.js +53 -0
- package/decision-points/ast-utils.js +127 -0
- package/decision-points/decision-type.js +92 -0
- package/decision-points/function-matching.js +185 -0
- package/decision-points/in-params.js +262 -0
- package/decision-points/index.js +6 -0
- package/decision-points/node-helpers.js +89 -0
- package/decision-points/parent-map.js +62 -0
- package/decision-points/parse-main.js +101 -0
- package/decision-points/ternary-multiline.js +86 -0
- package/export-generators/helpers.js +309 -0
- package/export-generators/index.js +143 -0
- package/export-generators/md-exports.js +160 -0
- package/export-generators/txt-exports.js +262 -0
- package/function-boundaries/arrow-brace-body.js +302 -0
- package/function-boundaries/arrow-helpers.js +93 -0
- package/function-boundaries/arrow-jsx.js +73 -0
- package/function-boundaries/arrow-object-literal.js +65 -0
- package/function-boundaries/arrow-single-expr.js +72 -0
- package/function-boundaries/brace-scanning.js +151 -0
- package/function-boundaries/index.js +67 -0
- package/function-boundaries/named-helpers.js +227 -0
- package/function-boundaries/parse-utils.js +456 -0
- package/function-extraction/ast-utils.js +112 -0
- package/function-extraction/extract-callback.js +65 -0
- package/function-extraction/extract-from-eslint.js +91 -0
- package/function-extraction/extract-name-ast.js +133 -0
- package/function-extraction/extract-name-regex.js +267 -0
- package/function-extraction/index.js +6 -0
- package/function-extraction/utils.js +29 -0
- package/function-hierarchy.js +427 -0
- package/html-generators/about.js +75 -0
- package/html-generators/file-boundary-builders.js +36 -0
- package/html-generators/file-breakdown.js +412 -0
- package/html-generators/file-data.js +50 -0
- package/html-generators/file-helpers.js +100 -0
- package/html-generators/file-javascript.js +430 -0
- package/html-generators/file-line-render.js +160 -0
- package/html-generators/file.css +370 -0
- package/html-generators/file.js +207 -0
- package/html-generators/folder.js +424 -0
- package/html-generators/index.js +6 -0
- package/html-generators/main-index.js +346 -0
- package/html-generators/shared.css +471 -0
- package/html-generators/utils.js +15 -0
- package/index.js +36 -0
- package/integration/eslint/index.js +94 -0
- package/integration/threshold/index.js +45 -0
- package/package.json +64 -0
- package/report/cli.js +58 -0
- 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,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
|
+
}
|