@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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.2] - 2026-02-08
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Package renamed to `@pythonidaer/complexity-report`** — The unscoped name `complexity-report` is already taken on npm by another package. Install with: `npm install --save-dev @pythonidaer/complexity-report`. The CLI command remains `complexity-report`.
|
|
12
|
+
- README and badges updated for scoped package name.
|
|
13
|
+
|
|
14
|
+
## [1.0.1] - 2026-02-08
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Repository URL normalized for npm (`git+https://` prefix)
|
|
18
|
+
|
|
19
|
+
## [1.0.0] - 2026-02-08
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Initial release as standalone npm package
|
|
23
|
+
- CLI tool via `npx complexity-report`
|
|
24
|
+
- Programmatic API with `generateComplexityReport()`
|
|
25
|
+
- AST-based decision point analysis (100% accuracy)
|
|
26
|
+
- Interactive HTML reports with syntax highlighting
|
|
27
|
+
- Folder and file-level complexity breakdowns
|
|
28
|
+
- Hierarchical function display for nested callbacks
|
|
29
|
+
- TXT/MD export formats
|
|
30
|
+
- Support for JavaScript and TypeScript
|
|
31
|
+
- ESLint 9+ flat config integration
|
|
32
|
+
- Configurable complexity thresholds
|
|
33
|
+
- Multiple CLI flags for customization
|
|
34
|
+
- Clean public API with utility functions
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
- **100% Accurate** - Uses `@typescript-eslint/typescript-estree` for perfect ESLint alignment
|
|
38
|
+
- **Interactive Reports** - Sortable tables, filterable views, code annotations
|
|
39
|
+
- **Decision Point Breakdown** - See exactly which lines add complexity
|
|
40
|
+
- **Multiple Export Formats** - TXT and Markdown for documentation
|
|
41
|
+
- **Fast Analysis** - Processes hundreds of files in seconds
|
|
42
|
+
- **Comprehensive Testing** - 287 tests with 90%+ coverage
|
|
43
|
+
|
|
44
|
+
### Migration
|
|
45
|
+
- Extracted from `new-years-project` scripts directory
|
|
46
|
+
- Now available as standalone package
|
|
47
|
+
- Maintains backward compatibility with previous script-based usage
|
|
48
|
+
|
|
49
|
+
### Requirements
|
|
50
|
+
- Node.js >=18
|
|
51
|
+
- ESLint >=9.0.0 with flat config
|
|
52
|
+
|
|
53
|
+
## [Unreleased]
|
|
54
|
+
|
|
55
|
+
### Planned
|
|
56
|
+
- VS Code extension integration
|
|
57
|
+
- GitHub Action for CI/CD
|
|
58
|
+
- Trend analysis over time
|
|
59
|
+
- Comparison reports between branches
|
|
60
|
+
- Additional export formats (JSON, CSV)
|
|
61
|
+
- Performance optimizations for very large codebases
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Migration from Scripts
|
|
66
|
+
|
|
67
|
+
If you were using the complexity report from `new-years-project/scripts/`, follow these steps:
|
|
68
|
+
|
|
69
|
+
### 1. Install the Package
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install --save-dev @pythonidaer/complexity-report
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Update Scripts in package.json
|
|
76
|
+
|
|
77
|
+
Replace:
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"scripts": {
|
|
81
|
+
"lint:complexity": "node scripts/report/index.js"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
With:
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"scripts": {
|
|
90
|
+
"lint:complexity": "complexity-report"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
(The CLI command is still `complexity-report`; only the package name is scoped.)
|
|
95
|
+
|
|
96
|
+
### 3. Keep Your Configuration
|
|
97
|
+
|
|
98
|
+
If you have `complexityReport.exportDir` in your `package.json`, keep it - the package reads this configuration.
|
|
99
|
+
|
|
100
|
+
### 4. Remove Old Scripts (Optional)
|
|
101
|
+
|
|
102
|
+
Once verified working, you can remove the `scripts/` directory from your project.
|
|
103
|
+
|
|
104
|
+
### What Changed?
|
|
105
|
+
|
|
106
|
+
- **Installation**: Now via npm instead of local scripts
|
|
107
|
+
- **Usage**: `complexity-report` instead of `node scripts/report/index.js`
|
|
108
|
+
- **Output**: Same location (`complexity/` by default)
|
|
109
|
+
- **Configuration**: Same (`eslint.config.js` and `package.json`)
|
|
110
|
+
- **Features**: All features preserved, plus new programmatic API
|
|
111
|
+
|
|
112
|
+
### What Stayed the Same?
|
|
113
|
+
|
|
114
|
+
- HTML report format and features
|
|
115
|
+
- Decision point analysis accuracy
|
|
116
|
+
- Export formats and locations
|
|
117
|
+
- ESLint configuration integration
|
|
118
|
+
- Complexity threshold reading
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
For more details, see [Migration Guide](./docs/MIGRATION.md)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonathan M. Hammond
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @pythonidaer/complexity-report
|
|
2
|
+
|
|
3
|
+
> AST-based cyclomatic complexity analyzer with interactive HTML reports and detailed function breakdowns
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@pythonidaer/complexity-report)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🎯 **100% Accurate** - Uses ESLint's AST parser for perfect complexity calculations
|
|
11
|
+
- 📊 **Interactive HTML Reports** - Beautiful, sortable tables with file-by-file breakdowns
|
|
12
|
+
- 🔍 **Decision Point Analysis** - See exactly which lines contribute to complexity
|
|
13
|
+
- 📈 **Hierarchical Function Display** - Understand nested callback complexity
|
|
14
|
+
- 📝 **Multiple Export Formats** - TXT and Markdown exports for documentation
|
|
15
|
+
- ⚡ **Fast** - Analyzes hundreds of files in seconds
|
|
16
|
+
- 🎨 **Syntax Highlighting** - Code annotations with prettify.js
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
\`\`\`bash
|
|
21
|
+
npm install --save-dev @pythonidaer/complexity-report
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- **Node.js**: >=18
|
|
27
|
+
- **ESLint**: >=9.0.0 with flat config (\`eslint.config.js\`)
|
|
28
|
+
|
|
29
|
+
Your project must have an ESLint flat config file. The tool will use your project's ESLint configuration to analyze complexity.
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### CLI Usage
|
|
34
|
+
|
|
35
|
+
Run from your project root:
|
|
36
|
+
|
|
37
|
+
\`\`\`bash
|
|
38
|
+
npx complexity-report
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
This generates an interactive HTML report at \`complexity/index.html\`.
|
|
42
|
+
|
|
43
|
+
### With npm Scripts
|
|
44
|
+
|
|
45
|
+
Add to your \`package.json\`:
|
|
46
|
+
|
|
47
|
+
\`\`\`json
|
|
48
|
+
{
|
|
49
|
+
"scripts": {
|
|
50
|
+
"complexity": "complexity-report",
|
|
51
|
+
"complexity:export": "complexity-report --export"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then run:
|
|
57
|
+
|
|
58
|
+
\`\`\`bash
|
|
59
|
+
npm run complexity
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
## CLI Options
|
|
63
|
+
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
complexity-report [options]
|
|
66
|
+
|
|
67
|
+
Options:
|
|
68
|
+
--cwd <path> Project root directory (default: process.cwd())
|
|
69
|
+
--output-dir <path> Output directory (default: complexity)
|
|
70
|
+
--show-all Show all functions initially (not just over threshold)
|
|
71
|
+
--show-all-columns Show all breakdown columns initially
|
|
72
|
+
--hide-table Hide breakdown table initially
|
|
73
|
+
--no-lines Hide line numbers initially
|
|
74
|
+
--no-highlights Hide code highlights initially
|
|
75
|
+
--export Generate TXT/MD exports
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
## Programmatic API
|
|
79
|
+
|
|
80
|
+
\`\`\`javascript
|
|
81
|
+
import { generateComplexityReport } from '@pythonidaer/complexity-report';
|
|
82
|
+
|
|
83
|
+
const result = await generateComplexityReport({
|
|
84
|
+
cwd: '/path/to/project',
|
|
85
|
+
outputDir: 'reports/complexity',
|
|
86
|
+
showAllInitially: true,
|
|
87
|
+
shouldExport: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(\`Generated report in: \${result.complexityDir}\`);
|
|
91
|
+
console.log(\`Total functions: \${result.stats.allFunctionsCount}\`);
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
## Documentation
|
|
95
|
+
|
|
96
|
+
- [Full API Documentation](./docs/API.md)
|
|
97
|
+
- [Developer Guide](./docs/DEVELOPER.md)
|
|
98
|
+
- [Migration Guide](./docs/MIGRATION.md)
|
|
99
|
+
- [Changelog](./CHANGELOG.md)
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT © Johnny Hammond
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.ignoreCase){ac=true}else{if(/[a-z]/i.test(ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,""))){S=true;ac=false;break}}}var Y={b:8,t:9,n:10,v:11,f:12,r:13};function ab(ah){var ag=ah.charCodeAt(0);if(ag!==92){return ag}var af=ah.charAt(1);ag=Y[af];if(ag){return ag}else{if("0"<=af&&af<="7"){return parseInt(ah.substring(1),8)}else{if(af==="u"||af==="x"){return parseInt(ah.substring(2),16)}else{return ah.charCodeAt(1)}}}}function T(af){if(af<32){return(af<16?"\\x0":"\\x")+af.toString(16)}var ag=String.fromCharCode(af);if(ag==="\\"||ag==="-"||ag==="["||ag==="]"){ag="\\"+ag}return ag}function X(am){var aq=am.substring(1,am.length-1).match(new RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));var ak=[];var af=[];var ao=aq[0]==="^";for(var ar=ao?1:0,aj=aq.length;ar<aj;++ar){var ah=aq[ar];if(/\\[bdsw]/i.test(ah)){ak.push(ah)}else{var ag=ab(ah);var al;if(ar+2<aj&&"-"===aq[ar+1]){al=ab(aq[ar+2]);ar+=2}else{al=ag}af.push([ag,al]);if(!(al<65||ag>122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;ar<af.length;++ar){var at=af[ar];if(at[0]<=ap[1]+1){ap[1]=Math.max(ap[1],at[1])}else{ai.push(ap=at)}}var an=["["];if(ao){an.push("^")}an.push.apply(an,ak);for(var ar=0;ar<ai.length;++ar){var at=ai[ar];an.push(T(at[0]));if(at[1]>at[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){an[af]=-1}}}}for(var ak=1;ak<an.length;++ak){if(-1===an[ak]){an[ak]=++ad}}for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag==="("){++am;if(an[am]===undefined){aj[ak]="(?:"}}else{if("\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){aj[ak]="\\"+an[am]}}}}for(var ak=0,am=0;ak<ah;++ak){if("^"===aj[ak]&&"^"!==aj[ak+1]){aj[ak]=""}}if(al.ignoreCase&&S){for(var ak=0;ak<ah;++ak){var ag=aj[ak];var ai=ag.charAt(0);if(ag.length>=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.global||ae.multiline){throw new Error(""+ae)}aa.push("(?:"+W(ae)+")")}return new RegExp(aa.join("|"),ac?"gi":"g")}function a(V){var U=/(?:^|\s)nocode(?:\s|$)/;var X=[];var T=0;var Z=[];var W=0;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=document.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Y=S&&"pre"===S.substring(0,3);function aa(ab){switch(ab.nodeType){case 1:if(U.test(ab.className)){return}for(var ae=ab.firstChild;ae;ae=ae.nextSibling){aa(ae)}var ad=ab.nodeName;if("BR"===ad||"LI"===ad){X[W]="\n";Z[W<<1]=T++;Z[(W++<<1)|1]=ab}break;case 3:case 4:var ac=ab.nodeValue;if(ac.length){if(!Y){ac=ac.replace(/[ \t\r\n]+/g," ")}else{ac=ac.replace(/\r\n?/g,"\n")}X[W]=ac;Z[W<<1]=T;T+=ac.length;Z[(W++<<1)|1]=ab}break}}aa(V);return{sourceCode:X.join("").replace(/\n$/,""),spans:Z}}function B(S,U,W,T){if(!U){return}var V={sourceCode:U,basePos:S};W(V);T.push.apply(T,V.decorations)}var v=/\S/;function o(S){var V=undefined;for(var U=S.firstChild;U;U=U.nextSibling){var T=U.nodeType;V=(T===1)?(V?S:U):(T===3)?(v.test(U.nodeValue)?S:V):V}return V===S?undefined:V}function g(U,T){var S={};var V;(function(){var ad=U.concat(T);var ah=[];var ag={};for(var ab=0,Z=ad.length;ab<Z;++ab){var Y=ad[ab];var ac=Y[3];if(ac){for(var ae=ac.length;--ae>=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae<aq;++ae){var ag=an[ae];var ap=aj[ag];var ai=void 0;var am;if(typeof ap==="string"){am=false}else{var aa=S[ag.charAt(0)];if(aa){ai=ag.match(aa[1]);ap=aa[0]}else{for(var ao=0;ao<X;++ao){aa=T[ao];ai=ag.match(aa[1]);if(ai){ap=aa[0];break}}if(!ai){ap=F}}am=ap.length>=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y<W.length;++Y){ae(W[Y])}if(ag===(ag|0)){W[0].setAttribute("value",ag)}var aa=ac.createElement("OL");aa.className="linenums";var X=Math.max(0,((ag-1))|0)||0;for(var Y=0,T=W.length;Y<T;++Y){af=W[Y];af.className="L"+((Y+X)%10);if(!af.firstChild){af.appendChild(ac.createTextNode("\xA0"))}aa.appendChild(af)}V.appendChild(aa)}function D(ac){var aj=/\bMSIE\b/.test(navigator.userAgent);var am=/\n/g;var al=ac.sourceCode;var an=al.length;var V=0;var aa=ac.spans;var T=aa.length;var ah=0;var X=ac.decorations;var Y=X.length;var Z=0;X[Y]=an;var ar,aq;for(aq=ar=0;aq<Y;){if(X[aq]!==X[aq+2]){X[ar++]=X[aq++];X[ar++]=X[aq++]}else{aq+=2}}Y=ar;for(aq=ar=0;aq<Y;){var at=X[aq];var ab=X[aq+1];var W=aq+2;while(W+2<=Y&&X[W+1]===ab){W+=2}X[ar++]=at;X[ar++]=ab;aq=W}Y=X.length=ar;var ae=null;while(ah<T){var af=aa[ah];var S=aa[ah+2]||an;var ag=X[Z];var ap=X[Z+2]||an;var W=Math.min(S,ap);var ak=aa[ah+1];var U;if(ak.nodeType!==1&&(U=al.substring(V,W))){if(aj){U=U.replace(am,"\r")}ak.nodeValue=U;var ai=ak.ownerDocument;var ao=ai.createElement("SPAN");ao.className=X[Z+1];var ad=ak.parentNode;ad.replaceChild(ao,ak);ao.appendChild(ak);if(V<S){aa[ah+1]=ak=ai.createTextNode(al.substring(W,S));ad.insertBefore(ak,ao.nextSibling)}}V=W;if(V>=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*</.test(S)?"default-markup":"default-code"}return t[T]}c(K,["default-code"]);c(g([],[[F,/^[^<?]+/],[E,/^<!\w[^>]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa<ac.length;++aa){for(var Z=0,V=ac[aa].length;Z<V;++Z){T.push(ac[aa][Z])}}ac=null;var W=Date;if(!W.now){W={now:function(){return +(new Date)}}}var X=0;var S;var ab=/\blang(?:uage)?-([\w.]+)(?!\S)/;var ae=/\bprettyprint\b/;function U(){var ag=(window.PR_SHOULD_USE_CONTINUATION?W.now()+250:Infinity);for(;X<T.length&&W.now()<ag;X++){var aj=T[X];var ai=aj.className;if(ai.indexOf("prettyprint")>=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X<T.length){setTimeout(U,250)}else{if(ad){ad()}}}U()}window.prettyPrintOne=y;window.prettyPrint=b;window.PR={createSimpleLexer:g,registerLangHandler:c,sourceDecorator:i,PR_ATTRIB_NAME:P,PR_ATTRIB_VALUE:n,PR_COMMENT:j,PR_DECLARATION:E,PR_KEYWORD:z,PR_LITERAL:G,PR_NOCODE:N,PR_PLAIN:F,PR_PUNCTUATION:L,PR_SOURCE:J,PR_STRING:C,PR_TAG:m,PR_TYPE:O}})();PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_DECLARATION,/^<!\w[^>]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^<script\b[^>]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:<!--|-->)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]);
|
|
Binary file
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates complexity breakdown for a specific function
|
|
3
|
+
* @param {number} functionLine - Line number of the function
|
|
4
|
+
* @param {Array} decisionPoints - All decision points
|
|
5
|
+
* @param {number} baseComplexity - Base complexity (should be 1)
|
|
6
|
+
* @returns {Object} Breakdown, total, and decision points
|
|
7
|
+
*/
|
|
8
|
+
export function calculateComplexityBreakdown(
|
|
9
|
+
functionLine,
|
|
10
|
+
decisionPoints,
|
|
11
|
+
baseComplexity
|
|
12
|
+
) {
|
|
13
|
+
const functionDecisionPoints = decisionPoints.filter(
|
|
14
|
+
(dp) => dp.functionLine === functionLine
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const breakdown = {
|
|
18
|
+
base: baseComplexity || 1,
|
|
19
|
+
'if': 0,
|
|
20
|
+
'else if': 0,
|
|
21
|
+
'for': 0,
|
|
22
|
+
'for...of': 0,
|
|
23
|
+
'for...in': 0,
|
|
24
|
+
'while': 0,
|
|
25
|
+
'do...while': 0,
|
|
26
|
+
'switch': 0,
|
|
27
|
+
'case': 0,
|
|
28
|
+
'catch': 0,
|
|
29
|
+
'ternary': 0,
|
|
30
|
+
'&&': 0,
|
|
31
|
+
'||': 0,
|
|
32
|
+
'??': 0,
|
|
33
|
+
'?.': 0,
|
|
34
|
+
'default parameter': 0,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
functionDecisionPoints.forEach((dp) => {
|
|
38
|
+
if (Object.hasOwn(breakdown, dp.type)) {
|
|
39
|
+
breakdown[dp.type] += 1;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const calculatedTotal = Object.values(breakdown).reduce(
|
|
44
|
+
(sum, count) => sum + count,
|
|
45
|
+
0
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
breakdown,
|
|
50
|
+
calculatedTotal,
|
|
51
|
+
decisionPoints: functionDecisionPoints,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST traversal and parsing utilities for decision-points.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parse } from '@typescript-eslint/typescript-estree';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parses source code into an AST
|
|
9
|
+
* @param {string} sourceCode - Source code to parse
|
|
10
|
+
* @param {string} filePath - Path to the file (for parser context)
|
|
11
|
+
* @returns {Object} Parsed AST
|
|
12
|
+
*/
|
|
13
|
+
export function parseAST(sourceCode, filePath) {
|
|
14
|
+
try {
|
|
15
|
+
const isTSX = filePath.endsWith('.tsx') || filePath.endsWith('.jsx');
|
|
16
|
+
return parse(sourceCode, {
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
ecmaVersion: 2020,
|
|
19
|
+
jsx: isTSX,
|
|
20
|
+
filePath: filePath,
|
|
21
|
+
comment: true,
|
|
22
|
+
loc: true,
|
|
23
|
+
range: true,
|
|
24
|
+
});
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`Error parsing AST for ${filePath}:`, error.message);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a key should be skipped during AST traversal
|
|
33
|
+
* @param {string} key - Key to check
|
|
34
|
+
* @returns {boolean} True if key should be skipped
|
|
35
|
+
*/
|
|
36
|
+
export function shouldSkipKey(key) {
|
|
37
|
+
return key === 'parent' || key === 'range' || key === 'loc' ||
|
|
38
|
+
key === 'leadingComments' || key === 'trailingComments';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Processes an array of child nodes
|
|
43
|
+
* @param {Array} array - Array of child nodes
|
|
44
|
+
* @param {string} nodeType - Type of nodes to collect
|
|
45
|
+
* @param {Array} results - Array to collect results
|
|
46
|
+
* @param {Function} collectNodesByType - Collector to call
|
|
47
|
+
*/
|
|
48
|
+
export function processArrayChildren(array, nodeType, results, collectNodesByType) {
|
|
49
|
+
array.forEach(item => {
|
|
50
|
+
if (item && typeof item === 'object' && item.type) {
|
|
51
|
+
collectNodesByType(item, nodeType, results);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Processes a single child node
|
|
58
|
+
* @param {Object} child - Child node
|
|
59
|
+
* @param {string} nodeType - Type of nodes to collect
|
|
60
|
+
* @param {Array} results - Array to collect results
|
|
61
|
+
* @param {Function} collectNodesByType - Collector to call
|
|
62
|
+
*/
|
|
63
|
+
export function processChildNode(child, nodeType, results, collectNodesByType) {
|
|
64
|
+
if (child && typeof child === 'object' && child.type) {
|
|
65
|
+
collectNodesByType(child, nodeType, results);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Traverses AST and collects all nodes of a specific type
|
|
71
|
+
* @param {Object} node - AST node to traverse
|
|
72
|
+
* @param {string} nodeType - Type of nodes to collect
|
|
73
|
+
* @param {Array} results - Array to collect results
|
|
74
|
+
*/
|
|
75
|
+
export function collectNodesByType(node, nodeType, results) {
|
|
76
|
+
if (!node || typeof node !== 'object') return;
|
|
77
|
+
|
|
78
|
+
if (node.type === nodeType) {
|
|
79
|
+
results.push(node);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const key in node) {
|
|
83
|
+
if (shouldSkipKey(key)) continue;
|
|
84
|
+
const child = node[key];
|
|
85
|
+
if (Array.isArray(child)) {
|
|
86
|
+
processArrayChildren(child, nodeType, results, collectNodesByType);
|
|
87
|
+
} else {
|
|
88
|
+
processChildNode(child, nodeType, results, collectNodesByType);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Finds all function nodes in the AST
|
|
95
|
+
* @param {Object} ast - ESLint AST
|
|
96
|
+
* @returns {Array} Array of function nodes
|
|
97
|
+
*/
|
|
98
|
+
export function findAllFunctions(ast) {
|
|
99
|
+
const functions = [];
|
|
100
|
+
const functionTypes = [
|
|
101
|
+
'FunctionDeclaration',
|
|
102
|
+
'FunctionExpression',
|
|
103
|
+
'ArrowFunctionExpression',
|
|
104
|
+
'MethodDefinition',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
functionTypes.forEach(type => {
|
|
108
|
+
collectNodesByType(ast, type, functions);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return functions;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Gets the line number for an AST node
|
|
116
|
+
* @param {Object} node - AST node
|
|
117
|
+
* @returns {number} Line number (1-based)
|
|
118
|
+
*/
|
|
119
|
+
export function getNodeLine(node) {
|
|
120
|
+
if (node.loc && node.loc.start) {
|
|
121
|
+
return node.loc.start.line;
|
|
122
|
+
}
|
|
123
|
+
if (node.range && node.range[0] !== undefined) {
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision point type mapping (control flow, expressions, default params).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isInFunctionParameters } from './in-params.js';
|
|
6
|
+
|
|
7
|
+
/** Control flow node types that map directly to a decision type (no variant). */
|
|
8
|
+
const CONTROL_FLOW_TYPE_MAP = {
|
|
9
|
+
IfStatement: 'if',
|
|
10
|
+
ForStatement: 'for',
|
|
11
|
+
ForInStatement: 'for',
|
|
12
|
+
ForOfStatement: 'for',
|
|
13
|
+
WhileStatement: 'while',
|
|
14
|
+
DoWhileStatement: 'while',
|
|
15
|
+
SwitchStatement: 'switch',
|
|
16
|
+
CatchClause: 'catch',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets decision point type for control flow statements
|
|
21
|
+
* @param {string} nodeType - Node type
|
|
22
|
+
* @param {string} variant - 'classic' or 'modified'
|
|
23
|
+
* @returns {string|null} Decision point type or null
|
|
24
|
+
*/
|
|
25
|
+
export function getControlFlowDecisionType(nodeType, variant) {
|
|
26
|
+
if (nodeType === 'SwitchCase') {
|
|
27
|
+
return variant === 'modified' ? null : 'case';
|
|
28
|
+
}
|
|
29
|
+
return CONTROL_FLOW_TYPE_MAP[nodeType] ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Gets decision point type for logical expressions
|
|
34
|
+
* @param {Object} node - AST node
|
|
35
|
+
* @returns {string|null} Decision point type or null
|
|
36
|
+
*/
|
|
37
|
+
export function getLogicalExpressionType(node) {
|
|
38
|
+
if (node.operator === '&&') return '&&';
|
|
39
|
+
if (node.operator === '||') return '||';
|
|
40
|
+
if (node.operator === '??') return '??';
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets decision point type for expressions
|
|
46
|
+
* @param {Object} node - AST node
|
|
47
|
+
* @param {string} nodeType - Node type
|
|
48
|
+
* @returns {string|null} Decision point type or null
|
|
49
|
+
*/
|
|
50
|
+
export function getExpressionDecisionType(node, nodeType) {
|
|
51
|
+
if (nodeType === 'ConditionalExpression') {
|
|
52
|
+
return 'ternary';
|
|
53
|
+
}
|
|
54
|
+
if (nodeType === 'LogicalExpression') {
|
|
55
|
+
return getLogicalExpressionType(node);
|
|
56
|
+
}
|
|
57
|
+
if (nodeType === 'MemberExpression' && node.optional) {
|
|
58
|
+
return '?.';
|
|
59
|
+
}
|
|
60
|
+
if (nodeType === 'BinaryExpression' && node.operator === '??') {
|
|
61
|
+
return '??';
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks if a node represents a decision point
|
|
68
|
+
* @param {Object} node - AST node
|
|
69
|
+
* @param {Map} parentMap - Parent map for checking context
|
|
70
|
+
* @param {Object} ast - Root AST node (for checking function context)
|
|
71
|
+
* @param {string} variant - 'classic' or 'modified'
|
|
72
|
+
* @returns {string|null} Decision point type or null
|
|
73
|
+
*/
|
|
74
|
+
export function getDecisionPointType(node, parentMap, ast, variant) {
|
|
75
|
+
if (!node || !node.type) return null;
|
|
76
|
+
|
|
77
|
+
const nodeType = node.type;
|
|
78
|
+
|
|
79
|
+
const controlFlowType = getControlFlowDecisionType(nodeType, variant);
|
|
80
|
+
if (controlFlowType) return controlFlowType;
|
|
81
|
+
|
|
82
|
+
const expressionType = getExpressionDecisionType(node, nodeType);
|
|
83
|
+
if (expressionType) return expressionType;
|
|
84
|
+
|
|
85
|
+
if (nodeType === 'AssignmentPattern' && parentMap) {
|
|
86
|
+
if (isInFunctionParameters(node, parentMap, ast)) {
|
|
87
|
+
return 'default parameter';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|