All files collect-performance.ts

97.14% Statements 34/35
96% Branches 24/25
100% Functions 0/0
97.05% Lines 33/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83                4x               122x 122x 122x 122x 122x     7x   23x 3x 20x 16x             3x 3x       73x   6x 6x     4x 4x     3x 3x         193x   9x 9x 9x 9x 9x                 2015x   122x   122x 122x 122x 122x 122x      
import * as ts from 'typescript';
 
import { isFunctionLike } from './ast-helpers.js';
import { blockContainsCall, findParentBlock } from './collect-effects.js';
import { getLineAndCharacter } from './utils.js';
 
import type { CodeLocation, FileEntry, TimerCall } from './types.js';
 
const SYNC_IO_METHODS = new Set([
  'readFileSync', 'writeFileSync', 'existsSync', 'mkdirSync', 'readdirSync',
  'statSync', 'lstatSync', 'unlinkSync', 'rmdirSync', 'renameSync', 'copyFileSync',
  'accessSync', 'appendFileSync', 'chmodSync', 'chownSync', 'openSync', 'closeSync',
  'execSync', 'execFileSync', 'spawnSync',
]);
 
export function collectPerformanceData(sourceFile: ts.SourceFile, fileRelative: string, fileEntry: FileEntry): void {
  const awaitInLoopLocations: CodeLocation[] = [];
  const syncIoCalls: Array<{ name: string; lineStart: number; lineEnd: number }> = [];
  const timerCalls: TimerCall[] = [];
  const listenerRegistrations: CodeLocation[] = [];
  const listenerRemovals: CodeLocation[] = [];
 
  const isInsideLoop = (node: ts.Node): boolean => {
    let current = node.parent;
    while (current) {
      if (ts.isForStatement(current) || ts.isWhileStatement(current) || ts.isDoStatement(current)
          || ts.isForOfStatement(current) || ts.isForInStatement(current)) return true;
      if (isFunctionLike(current)) return false;
      current = current.parent;
    }
    return false;
  };
 
  const visit = (node: ts.Node): void => {
    if (ts.isAwaitExpression(node) && isInsideLoop(node)) {
      const loc = getLineAndCharacter(sourceFile, node);
      awaitInLoopLocations.push({ file: fileRelative, lineStart: loc.lineStart, lineEnd: loc.lineEnd });
    }
 
    if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
      const methodName = node.expression.name.getText(sourceFile);
      if (SYNC_IO_METHODS.has(methodName)) {
        const loc = getLineAndCharacter(sourceFile, node);
        syncIoCalls.push({ name: methodName, lineStart: loc.lineStart, lineEnd: loc.lineEnd });
      }
      if (methodName === 'addEventListener' || methodName === 'on' || methodName === 'addListener') {
        const loc = getLineAndCharacter(sourceFile, node);
        listenerRegistrations.push({ file: fileRelative, lineStart: loc.lineStart, lineEnd: loc.lineEnd });
      }
      if (methodName === 'removeEventListener' || methodName === 'off' || methodName === 'removeListener') {
        const loc = getLineAndCharacter(sourceFile, node);
        listenerRemovals.push({ file: fileRelative, lineStart: loc.lineStart, lineEnd: loc.lineEnd });
      }
    }
 
    if (ts.isCallExpression(node)) {
      const text = node.expression.getText(sourceFile);
      if (text === 'setInterval' || text === 'setTimeout') {
        const loc = getLineAndCharacter(sourceFile, node);
        const clearName = text === 'setInterval' ? 'clearInterval' : 'clearTimeout';
        const parentBlock = findParentBlock(node);
        const hasCleanup = parentBlock ? blockContainsCall(parentBlock, sourceFile, clearName) : false;
        timerCalls.push({
          kind: text as 'setInterval' | 'setTimeout',
          lineStart: loc.lineStart,
          lineEnd: loc.lineEnd,
          hasCleanup,
        });
      }
    }
 
    ts.forEachChild(node, visit);
  };
  ts.forEachChild(sourceFile, visit);
 
  fileEntry.awaitInLoopLocations = awaitInLoopLocations;
  fileEntry.syncIoCalls = syncIoCalls;
  fileEntry.timerCalls = timerCalls;
  fileEntry.listenerRegistrations = listenerRegistrations;
  fileEntry.listenerRemovals = listenerRemovals;
}