@typescript/analyze-trace
Version:
Analyze the output of tsc --generatetrace
222 lines • 8.82 kB
JavaScript
;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRelatedTypes = exports.getEmittedImports = exports.getTypes = exports.unmangleCamelCase = exports.getPackageVersion = exports.getLineCharMapKey = exports.getNormalizedPositions = exports.buildHotPathsTree = void 0;
const fs = require("fs");
const path = require("path");
const countImportExpressions = require("./count-import-expressions");
const normalizePositions = require("./normalize-positions");
const simplify = require("./simplify-type");
const jsonstream = require("jsonstream-next");
function buildHotPathsTree(parseResult, thresholdDuration, minPercentage) {
const { minTime, maxTime, spans, unclosedStack } = parseResult;
for (let i = unclosedStack.length - 1; i >= 0; i--) {
const event = unclosedStack[i];
spans.push({ event, start: +event.ts, end: maxTime, children: [] });
}
spans.sort((a, b) => a.start - b.start);
const root = { start: minTime, end: maxTime, children: [] };
const stack = [root];
for (const span of spans) {
let i = stack.length - 1;
for (; i > 0; i--) { // No need to check root at stack[0]
const curr = stack[i];
if (curr.end > span.start) {
// Pop down to parent
stack.length = i + 1;
break;
}
}
const parent = stack[i];
const duration = span.end - span.start;
if (duration >= thresholdDuration || duration >= minPercentage * (parent.end - parent.start)) {
parent.children.push(span);
stack.push(span);
}
}
return root;
}
exports.buildHotPathsTree = buildHotPathsTree;
async function getNormalizedPositions(root, relatedTypes) {
const positionMap = new Map();
recordPositions(root, /*currentFile*/ undefined);
if (relatedTypes) {
for (const type of relatedTypes.values()) {
const location = type.location;
if (location) {
recordPosition(location.path, [location.line, location.char]);
}
}
}
const map = new Map(); // NB: can't use LineChar as map key
for (const entry of Array.from(positionMap.entries())) {
try {
const path = entry[0];
const sourceStream = fs.createReadStream(path, { encoding: "utf-8" });
const rawPositions = entry[1];
const normalizedPositions = await normalizePositions(sourceStream, rawPositions);
const pathMap = new Map();
for (let i = 0; i < rawPositions.length; i++) {
const rawPosition = rawPositions[i];
const key = typeof rawPosition === "number" ? Math.abs(rawPosition).toString() : getLineCharMapKey(...rawPosition);
pathMap.set(key, normalizedPositions[i]);
}
map.set(path, pathMap);
}
catch (_a) {
// Not finding a file is expected if this isn't the box on which the trace was recorded.
}
}
return map;
function recordPositions(span, currentFile) {
var _a, _b, _c;
if (((_a = span.event) === null || _a === void 0 ? void 0 : _a.name) === "checkSourceFile") {
currentFile = span.event.args.path;
}
else if (((_b = span.event) === null || _b === void 0 ? void 0 : _b.cat) === "check") {
const args = span.event.args;
currentFile = (_c = args === null || args === void 0 ? void 0 : args.path) !== null && _c !== void 0 ? _c : currentFile;
if (currentFile) {
if (args === null || args === void 0 ? void 0 : args.pos) {
recordPosition(currentFile, args.pos);
}
if (args === null || args === void 0 ? void 0 : args.end) {
recordPosition(currentFile, -args.end); // Negative since end should not be moved past trivia
}
}
}
for (const child of span.children) {
recordPositions(child, currentFile);
}
}
function recordPosition(path, position) {
if (!positionMap.has(path)) {
positionMap.set(path, []);
}
positionMap.get(path).push(position);
}
}
exports.getNormalizedPositions = getNormalizedPositions;
function getLineCharMapKey(line, char) {
return `${line},${char}`;
}
exports.getLineCharMapKey = getLineCharMapKey;
async function getPackageVersion(packagePath) {
try {
const jsonPath = path.join(packagePath, "package.json");
const jsonString = await fs.promises.readFile(jsonPath, { encoding: "utf-8" });
const jsonObj = JSON.parse(jsonString);
return jsonObj.version;
}
catch (_a) {
}
return undefined;
}
exports.getPackageVersion = getPackageVersion;
function unmangleCamelCase(name) {
let result = "";
for (const char of [...name]) {
if (!result.length) {
result += char.toLocaleUpperCase();
continue;
}
const lower = char.toLocaleLowerCase();
if (char !== lower) {
result += " ";
}
result += lower;
}
return result;
}
exports.unmangleCamelCase = unmangleCamelCase;
let typesCache;
async function getTypes(typesPath) {
if (!typesCache) {
return new Promise((resolve, _reject) => {
typesCache = [];
const readStream = fs.createReadStream(typesPath, { encoding: "utf-8" });
readStream.on("end", () => {
resolve(typesCache);
});
readStream.on("error", onError);
// expects types file to be {object[]}
const parser = jsonstream.parse("*");
parser.on("data", (data) => {
typesCache.push(data);
});
parser.on("error", onError);
readStream.pipe(parser);
function onError(e) {
console.error(`Error reading types file: ${e.message}`);
resolve(typesCache);
}
});
}
return typesCache;
}
exports.getTypes = getTypes;
async function getEmittedImports(dtsPath, importExpressionThreshold) {
const sourceStream = fs.createReadStream(dtsPath, { encoding: "utf-8" });
const frequency = await countImportExpressions(sourceStream);
const sorted = Array.from(frequency.entries())
.sort(([import1, count1], [import2, count2]) => count2 - count1 || import1.localeCompare(import2))
.filter(([_, count]) => count >= importExpressionThreshold)
.map(([name, count]) => ({ name, count }));
return sorted;
}
exports.getEmittedImports = getEmittedImports;
async function getRelatedTypes(root, typesPath, leafOnly) {
var _a, _b;
const relatedTypes = new Map();
const stack = [];
stack.push(root);
while (stack.length) {
const curr = stack.pop();
if (!leafOnly || curr.children.length === 0) {
if (((_a = curr.event) === null || _a === void 0 ? void 0 : _a.name) === "structuredTypeRelatedTo") {
const types = await getTypes(typesPath);
if (types.length) {
addRelatedTypes(types, curr.event.args.sourceId, relatedTypes);
addRelatedTypes(types, curr.event.args.targetId, relatedTypes);
}
}
else if (((_b = curr.event) === null || _b === void 0 ? void 0 : _b.name) === "getVariancesWorker") {
const types = await getTypes(typesPath);
if (types.length) {
addRelatedTypes(types, curr.event.args.id, relatedTypes);
}
}
}
stack.push(...curr.children); // Order doesn't matter during this traversal
}
return relatedTypes;
}
exports.getRelatedTypes = getRelatedTypes;
function addRelatedTypes(types, id, relatedTypes) {
worker(id);
function worker(id) {
if (typeof id !== "number")
return;
const type = types[id - 1];
if (!type)
return;
// If there's a cycle, suppress the children, but not the type itself
if (!relatedTypes.has(id)) {
relatedTypes.set(id, simplify(type));
for (const prop in type) {
if (prop.match(/type/i)) {
if (Array.isArray(type[prop])) {
for (const t of type[prop]) {
worker(t);
}
}
else {
worker(type[prop]);
}
}
}
}
}
}
//# sourceMappingURL=analyze-trace-utilities.js.map