cosmos-explorer/strict-null-checks/auto-add.js

101 lines
3.4 KiB
JavaScript

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// @ts-check
const child_process = require("child_process");
const fs = require("fs");
const path = require("path");
const { collapseCompletedDirectories } = require("./collapse-completed-directories");
const config = require("./config");
const { getUncheckedLeafFiles } = require("./eligible-file-finder");
const { writeTsconfigSync } = require("./write-tsconfig");
const repoRoot = config.repoRoot;
const tscPath = path.join(repoRoot, "node_modules", "typescript", "bin", "tsc");
const tsconfigPath = path.join(repoRoot, config.targetTsconfig);
async function main() {
console.log("## Initializing tsc --watch process...");
const tscWatchProcess = child_process.spawn("node", [tscPath, "-p", tsconfigPath, "--watch"]);
await waitForBuildComplete(tscWatchProcess);
const alreadyAttempted = new Set();
for (let pass = 1; ; pass += 1) {
let successesThisPass = 0;
const uncheckedLeafFiles = await getUncheckedLeafFiles();
const candidateFiles = uncheckedLeafFiles.filter((f) => !alreadyAttempted.has(f));
const candidateCount = candidateFiles.length;
console.log(`## Starting pass ${pass} with ${candidateCount} candidate files`);
for (const file of candidateFiles) {
alreadyAttempted.add(file);
if (await tryAutoAddStrictNulls(tscWatchProcess, tsconfigPath, file)) {
successesThisPass += 1;
}
}
console.log(`### Finished pass ${pass} (added ${successesThisPass}/${candidateCount})`);
if (successesThisPass === 0) {
break;
}
}
console.log("## Stopping tsc --watch process...");
tscWatchProcess.kill();
console.log('## Collapsing fully null-checked directories into "include" patterns...');
collapseCompletedDirectories(tsconfigPath);
}
async function tryAutoAddStrictNulls(child, tsconfigPath, file) {
const relativeFilePath = path.relative(repoRoot, file).replace(/\\/g, "/");
const originalConfig = JSON.parse(fs.readFileSync(tsconfigPath).toString());
originalConfig.files = Array.from(new Set(originalConfig.files.sort()));
// Config on accept
const newConfig = Object.assign({}, originalConfig);
newConfig.files = Array.from(new Set(originalConfig.files.concat("./" + relativeFilePath).sort()));
const buildCompetePromise = waitForBuildComplete(child);
writeTsconfigSync(tsconfigPath, newConfig);
const errorCount = await buildCompetePromise;
const success = errorCount === 0;
if (success) {
console.log(`${relativeFilePath}: added`);
} else {
console.log(`${relativeFilePath}: ${errorCount} error(s), skipped`);
writeTsconfigSync(tsconfigPath, originalConfig);
}
return success;
}
const buildCompletePattern = /Found (\d+) errors?\. Watching for file changes\./gi;
async function waitForBuildComplete(tscWatchProcess) {
const match = await waitForStdoutMatching(tscWatchProcess, buildCompletePattern);
const errorCount = +match[1];
return errorCount;
}
async function waitForStdoutMatching(child, pattern) {
return new Promise((resolve) => {
const listener = (data) => {
const textOut = data.toString();
const match = pattern.exec(textOut);
if (match) {
child.stdout.removeListener("data", listener);
resolve(match);
}
};
child.stdout.on("data", listener);
});
}
main().catch((error) => {
console.error(error.stack);
process.exit(1);
});