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;
}
|