@@ -19,6 +19,22 @@ import path from 'node:path';
1919import { performance } from 'node:perf_hooks' ;
2020import { bulkNodeIdsByFile } from '../db/index.js' ;
2121import { debug } from '../infrastructure/logger.js' ;
22+ import type {
23+ AnalysisOpts ,
24+ AnalysisTiming ,
25+ ASTNodeRow ,
26+ BetterSqlite3Database ,
27+ CfgBlock ,
28+ CfgEdge ,
29+ DataflowResult ,
30+ Definition ,
31+ EngineOpts ,
32+ ExtractorOutput ,
33+ TreeSitterNode ,
34+ Visitor ,
35+ WalkOptions ,
36+ WalkResults ,
37+ } from '../types.js' ;
2238import { computeLOCMetrics , computeMaintainabilityIndex } from './metrics.js' ;
2339import {
2440 AST_TYPE_MAPS ,
@@ -35,6 +51,35 @@ import { createCfgVisitor } from './visitors/cfg-visitor.js';
3551import { createComplexityVisitor } from './visitors/complexity-visitor.js' ;
3652import { createDataflowVisitor } from './visitors/dataflow-visitor.js' ;
3753
54+ // ─── Visitor result shapes (internal, not exported) ──────────────────────
55+
56+ interface ComplexityFuncResult {
57+ funcNode : TreeSitterNode ;
58+ funcName : string | null ;
59+ metrics : {
60+ cognitive : number ;
61+ cyclomatic : number ;
62+ maxNesting : number ;
63+ halstead ?: { volume : number ; difficulty : number ; effort : number ; bugs : number } ;
64+ } ;
65+ }
66+
67+ interface CfgFuncResult {
68+ funcNode : TreeSitterNode ;
69+ blocks : CfgBlock [ ] ;
70+ edges : CfgEdge [ ] ;
71+ cyclomatic ?: number ;
72+ }
73+
74+ interface SetupResult {
75+ visitors : Visitor [ ] ;
76+ walkerOpts : WalkOptions ;
77+ astVisitor : Visitor | null ;
78+ complexityVisitor : Visitor | null ;
79+ cfgVisitor : Visitor | null ;
80+ dataflowVisitor : Visitor | null ;
81+ }
82+
3883// ─── Extension sets for quick language-support checks ────────────────────
3984
4085const CFG_EXTENSIONS = buildExtensionSet ( CFG_RULES ) ;
@@ -44,15 +89,19 @@ const WALK_EXTENSIONS = buildExtensionSet(AST_TYPE_MAPS);
4489
4590// ─── Lazy imports (heavy modules loaded only when needed) ────────────────
4691
47- let _parserModule = null ;
48- async function getParserModule ( ) {
92+ let _parserModule : Awaited < typeof import ( '../domain/parser.js' ) > | null = null ;
93+ async function getParserModule ( ) : Promise < typeof import ( '../domain/parser.js' ) > {
4994 if ( ! _parserModule ) _parserModule = await import ( '../domain/parser.js' ) ;
5095 return _parserModule ;
5196}
5297
5398// ─── WASM pre-parse ─────────────────────────────────────────────────────
5499
55- async function ensureWasmTreesIfNeeded ( fileSymbols , opts , rootDir ) {
100+ async function ensureWasmTreesIfNeeded (
101+ fileSymbols : Map < string , ExtractorOutput > ,
102+ opts : AnalysisOpts ,
103+ rootDir : string ,
104+ ) : Promise < void > {
56105 const doComplexity = opts . complexity !== false ;
57106 const doCfg = opts . cfg !== false ;
58107 const doDataflow = opts . dataflow !== false ;
@@ -91,34 +140,40 @@ async function ensureWasmTreesIfNeeded(fileSymbols, opts, rootDir) {
91140 try {
92141 const { ensureWasmTrees } = await getParserModule ( ) ;
93142 await ensureWasmTrees ( fileSymbols , rootDir ) ;
94- } catch ( err ) {
95- debug ( `ensureWasmTrees failed: ${ err . message } ` ) ;
143+ } catch ( err : unknown ) {
144+ debug ( `ensureWasmTrees failed: ${ ( err as Error ) . message } ` ) ;
96145 }
97146 }
98147}
99148
100149// ─── Per-file visitor setup ─────────────────────────────────────────────
101150
102- function setupVisitors ( db , relPath , symbols , langId , opts ) {
151+ function setupVisitors (
152+ db : BetterSqlite3Database ,
153+ relPath : string ,
154+ symbols : ExtractorOutput ,
155+ langId : string ,
156+ opts : AnalysisOpts ,
157+ ) : SetupResult {
103158 const ext = path . extname ( relPath ) . toLowerCase ( ) ;
104159 const defs = symbols . definitions || [ ] ;
105160 const doAst = opts . ast !== false ;
106161 const doComplexity = opts . complexity !== false ;
107162 const doCfg = opts . cfg !== false ;
108163 const doDataflow = opts . dataflow !== false ;
109164
110- const visitors = [ ] ;
111- const walkerOpts = {
112- functionNodeTypes : new Set ( ) ,
113- nestingNodeTypes : new Set ( ) ,
114- getFunctionName : ( _node ) => null ,
165+ const visitors : Visitor [ ] = [ ] ;
166+ const walkerOpts : WalkOptions = {
167+ functionNodeTypes : new Set < string > ( ) ,
168+ nestingNodeTypes : new Set < string > ( ) ,
169+ getFunctionName : ( _node : TreeSitterNode ) => null ,
115170 } ;
116171
117172 // AST-store visitor
118- let astVisitor = null ;
173+ let astVisitor : Visitor | null = null ;
119174 const astTypeMap = AST_TYPE_MAPS . get ( langId ) ;
120175 if ( doAst && astTypeMap && WALK_EXTENSIONS . has ( ext ) && ! symbols . astNodes ?. length ) {
121- const nodeIdMap = new Map ( ) ;
176+ const nodeIdMap = new Map < string , number > ( ) ;
122177 for ( const row of bulkNodeIdsByFile ( db , relPath ) ) {
123178 nodeIdMap . set ( `${ row . name } |${ row . kind } |${ row . line } ` , row . id ) ;
124179 }
@@ -127,7 +182,7 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
127182 }
128183
129184 // Complexity visitor (file-level mode)
130- let complexityVisitor = null ;
185+ let complexityVisitor : Visitor | null = null ;
131186 const cRules = COMPLEXITY_RULES . get ( langId ) ;
132187 const hRules = HALSTEAD_RULES . get ( langId ) ;
133188 if ( doComplexity && cRules ) {
@@ -138,20 +193,21 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
138193 complexityVisitor = createComplexityVisitor ( cRules , hRules , { fileLevelWalk : true , langId } ) ;
139194 visitors . push ( complexityVisitor ) ;
140195
141- for ( const t of cRules . nestingNodes ) walkerOpts . nestingNodeTypes . add ( t ) ;
196+ for ( const t of cRules . nestingNodes ) walkerOpts . nestingNodeTypes ? .add ( t ) ;
142197
143198 const dfRules = DATAFLOW_RULES . get ( langId ) ;
144- walkerOpts . getFunctionName = ( node ) => {
199+ walkerOpts . getFunctionName = ( node : TreeSitterNode ) : string | null => {
145200 const nameNode = node . childForFieldName ( 'name' ) ;
146201 if ( nameNode ) return nameNode . text ;
147- if ( dfRules ) return getFuncName ( node , dfRules ) ;
202+ // biome-ignore lint/suspicious/noExplicitAny: DataflowRulesConfig is structurally compatible at runtime
203+ if ( dfRules ) return getFuncName ( node , dfRules as any ) ;
148204 return null ;
149205 } ;
150206 }
151207 }
152208
153209 // CFG visitor
154- let cfgVisitor = null ;
210+ let cfgVisitor : Visitor | null = null ;
155211 const cfgRulesForLang = CFG_RULES . get ( langId ) ;
156212 if ( doCfg && cfgRulesForLang && CFG_EXTENSIONS . has ( ext ) ) {
157213 const needsWasmCfg = defs . some (
@@ -168,7 +224,7 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
168224 }
169225
170226 // Dataflow visitor
171- let dataflowVisitor = null ;
227+ let dataflowVisitor : Visitor | null = null ;
172228 const dfRules = DATAFLOW_RULES . get ( langId ) ;
173229 if ( doDataflow && dfRules && DATAFLOW_EXTENSIONS . has ( ext ) && ! symbols . dataflow ) {
174230 dataflowVisitor = createDataflowVisitor ( dfRules ) ;
@@ -180,14 +236,15 @@ function setupVisitors(db, relPath, symbols, langId, opts) {
180236
181237// ─── Result storage helpers ─────────────────────────────────────────────
182238
183- function storeComplexityResults ( results , defs , langId ) {
184- const complexityResults = results . complexity || [ ] ;
185- const resultByLine = new Map ( ) ;
239+ function storeComplexityResults ( results : WalkResults , defs : Definition [ ] , langId : string ) : void {
240+ // biome-ignore lint/complexity/useLiteralKeys: bracket notation required by noPropertyAccessFromIndexSignature
241+ const complexityResults = ( results [ 'complexity' ] || [ ] ) as ComplexityFuncResult [ ] ;
242+ const resultByLine = new Map < number , ComplexityFuncResult [ ] > ( ) ;
186243 for ( const r of complexityResults ) {
187244 if ( r . funcNode ) {
188245 const line = r . funcNode . startPosition . row + 1 ;
189246 if ( ! resultByLine . has ( line ) ) resultByLine . set ( line , [ ] ) ;
190- resultByLine . get ( line ) . push ( r ) ;
247+ resultByLine . get ( line ) ? .push ( r ) ;
191248 }
192249 }
193250 for ( const def of defs ) {
@@ -221,14 +278,15 @@ function storeComplexityResults(results, defs, langId) {
221278 }
222279}
223280
224- function storeCfgResults ( results , defs ) {
225- const cfgResults = results . cfg || [ ] ;
226- const cfgByLine = new Map ( ) ;
281+ function storeCfgResults ( results : WalkResults , defs : Definition [ ] ) : void {
282+ // biome-ignore lint/complexity/useLiteralKeys: bracket notation required by noPropertyAccessFromIndexSignature
283+ const cfgResults = ( results [ 'cfg' ] || [ ] ) as CfgFuncResult [ ] ;
284+ const cfgByLine = new Map < number , CfgFuncResult [ ] > ( ) ;
227285 for ( const r of cfgResults ) {
228286 if ( r . funcNode ) {
229287 const line = r . funcNode . startPosition . row + 1 ;
230288 if ( ! cfgByLine . has ( line ) ) cfgByLine . set ( line , [ ] ) ;
231- cfgByLine . get ( line ) . push ( r ) ;
289+ cfgByLine . get ( line ) ? .push ( r ) ;
232290 }
233291 }
234292 for ( const def of defs ) {
@@ -254,7 +312,7 @@ function storeCfgResults(results, defs) {
254312 def . complexity . cyclomatic = cfgResult . cyclomatic ;
255313 const { loc, halstead } = def . complexity ;
256314 const volume = halstead ? halstead . volume : 0 ;
257- const commentRatio = loc ? .loc > 0 ? loc . commentLines / loc . loc : 0 ;
315+ const commentRatio = loc && loc . loc > 0 ? loc . commentLines / loc . loc : 0 ;
258316 def . complexity . maintainabilityIndex = computeMaintainabilityIndex (
259317 volume ,
260318 cfgResult . cyclomatic ,
@@ -269,14 +327,22 @@ function storeCfgResults(results, defs) {
269327
270328// ─── Build delegation ───────────────────────────────────────────────────
271329
272- async function delegateToBuildFunctions ( db , fileSymbols , rootDir , opts , engineOpts , timing ) {
330+ async function delegateToBuildFunctions (
331+ db : BetterSqlite3Database ,
332+ fileSymbols : Map < string , ExtractorOutput > ,
333+ rootDir : string ,
334+ opts : AnalysisOpts ,
335+ engineOpts : EngineOpts | undefined ,
336+ timing : AnalysisTiming ,
337+ ) : Promise < void > {
273338 if ( opts . ast !== false ) {
274339 const t0 = performance . now ( ) ;
275340 try {
276341 const { buildAstNodes } = await import ( '../features/ast.js' ) ;
277- await buildAstNodes ( db , fileSymbols , rootDir , engineOpts ) ;
278- } catch ( err ) {
279- debug ( `buildAstNodes failed: ${ err . message } ` ) ;
342+ // biome-ignore lint/suspicious/noExplicitAny: ExtractorOutput is a superset of the local FileSymbols expected by buildAstNodes
343+ await buildAstNodes ( db , fileSymbols as Map < string , any > , rootDir , engineOpts ) ;
344+ } catch ( err : unknown ) {
345+ debug ( `buildAstNodes failed: ${ ( err as Error ) . message } ` ) ;
280346 }
281347 timing . astMs = performance . now ( ) - t0 ;
282348 }
@@ -285,9 +351,10 @@ async function delegateToBuildFunctions(db, fileSymbols, rootDir, opts, engineOp
285351 const t0 = performance . now ( ) ;
286352 try {
287353 const { buildComplexityMetrics } = await import ( '../features/complexity.js' ) ;
288- await buildComplexityMetrics ( db , fileSymbols , rootDir , engineOpts ) ;
289- } catch ( err ) {
290- debug ( `buildComplexityMetrics failed: ${ err . message } ` ) ;
354+ // biome-ignore lint/suspicious/noExplicitAny: ExtractorOutput is a superset of the local FileSymbols expected by buildComplexityMetrics
355+ await buildComplexityMetrics ( db , fileSymbols as Map < string , any > , rootDir , engineOpts ) ;
356+ } catch ( err : unknown ) {
357+ debug ( `buildComplexityMetrics failed: ${ ( err as Error ) . message } ` ) ;
291358 }
292359 timing . complexityMs = performance . now ( ) - t0 ;
293360 }
@@ -297,8 +364,8 @@ async function delegateToBuildFunctions(db, fileSymbols, rootDir, opts, engineOp
297364 try {
298365 const { buildCFGData } = await import ( '../features/cfg.js' ) ;
299366 await buildCFGData ( db , fileSymbols , rootDir , engineOpts ) ;
300- } catch ( err ) {
301- debug ( `buildCFGData failed: ${ err . message } ` ) ;
367+ } catch ( err : unknown ) {
368+ debug ( `buildCFGData failed: ${ ( err as Error ) . message } ` ) ;
302369 }
303370 timing . cfgMs = performance . now ( ) - t0 ;
304371 }
@@ -308,27 +375,23 @@ async function delegateToBuildFunctions(db, fileSymbols, rootDir, opts, engineOp
308375 try {
309376 const { buildDataflowEdges } = await import ( '../features/dataflow.js' ) ;
310377 await buildDataflowEdges ( db , fileSymbols , rootDir , engineOpts ) ;
311- } catch ( err ) {
312- debug ( `buildDataflowEdges failed: ${ err . message } ` ) ;
378+ } catch ( err : unknown ) {
379+ debug ( `buildDataflowEdges failed: ${ ( err as Error ) . message } ` ) ;
313380 }
314381 timing . dataflowMs = performance . now ( ) - t0 ;
315382 }
316383}
317384
318385// ─── Public API ──────────────────────────────────────────────────────────
319386
320- /**
321- * Run all enabled AST analyses in a coordinated pass.
322- *
323- * @param {object } db - open better-sqlite3 database (read-write)
324- * @param {Map<string, object> } fileSymbols - Map<relPath, { definitions, calls, _tree, _langId, ... }>
325- * @param {string } rootDir - absolute project root path
326- * @param {object } opts - build options (ast, complexity, cfg, dataflow toggles)
327- * @param {object } [engineOpts] - engine options
328- * @returns {Promise<{ astMs: number, complexityMs: number, cfgMs: number, dataflowMs: number }> }
329- */
330- export async function runAnalyses ( db , fileSymbols , rootDir , opts , engineOpts ) {
331- const timing = { astMs : 0 , complexityMs : 0 , cfgMs : 0 , dataflowMs : 0 } ;
387+ export async function runAnalyses (
388+ db : BetterSqlite3Database ,
389+ fileSymbols : Map < string , ExtractorOutput > ,
390+ rootDir : string ,
391+ opts : AnalysisOpts ,
392+ engineOpts ?: EngineOpts ,
393+ ) : Promise < AnalysisTiming > {
394+ const timing : AnalysisTiming = { astMs : 0 , complexityMs : 0 , cfgMs : 0 , dataflowMs : 0 } ;
332395
333396 const doAst = opts . ast !== false ;
334397 const doComplexity = opts . complexity !== false ;
@@ -361,13 +424,14 @@ export async function runAnalyses(db, fileSymbols, rootDir, opts, engineOpts) {
361424 const defs = symbols . definitions || [ ] ;
362425
363426 if ( astVisitor ) {
364- const astRows = results [ 'ast-store' ] || [ ] ;
427+ const astRows = ( results [ 'ast-store' ] || [ ] ) as ASTNodeRow [ ] ;
365428 if ( astRows . length > 0 ) symbols . astNodes = astRows ;
366429 }
367430
368431 if ( complexityVisitor ) storeComplexityResults ( results , defs , langId ) ;
369432 if ( cfgVisitor ) storeCfgResults ( results , defs ) ;
370- if ( dataflowVisitor ) symbols . dataflow = results . dataflow ;
433+ // biome-ignore lint/complexity/useLiteralKeys: bracket notation required by noPropertyAccessFromIndexSignature
434+ if ( dataflowVisitor ) symbols . dataflow = results [ 'dataflow' ] as DataflowResult ;
371435 }
372436
373437 timing . _unifiedWalkMs = performance . now ( ) - t0walk ;
0 commit comments