@ui5/ts-interface-generator
Version:
Generator for TypeScript type definitions for custom UI5 controls implemented in TypeScript
166 lines • 8.69 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.initialize = initialize;
exports.getAllKnownGlobals = getAllKnownGlobals;
const typescript_1 = __importDefault(require("typescript"));
const perf_hooks_1 = require("perf_hooks");
const loglevel_1 = __importDefault(require("loglevel"));
let newProgram;
let newChangedFiles = [];
let onTSProgramUpdate;
let watch;
let options;
function initialize(configFile, onTSProgramUpdateCallback, optionsParameter = {}) {
if (onTSProgramUpdate) {
throw new Error("There may be only ONE call to either initializeForOneRun or initializeWatchMode");
}
onTSProgramUpdate = onTSProgramUpdateCallback;
options = optionsParameter;
// initialize TypeScript program
loglevel_1.default.info("Initializing TypeScript program with all source files and type definitions (this may take a few seconds)...");
if (!options.watchMode) {
// in non-watch mode monkey-patch the TypeScript system to avoid misleading console output mentioning watch mode
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalTSwrite = typescript_1.default.sys.write;
typescript_1.default.sys.write = (text) => {
text = text.replace("Starting compilation in watch mode...", "Starting compilation...");
text = text.replace("Watching for file changes.", "");
originalTSwrite(text);
};
}
const host = typescript_1.default.createWatchCompilerHost(configFile, { noEmit: true }, typescript_1.default.sys, typescript_1.default.createSemanticDiagnosticsBuilderProgram, options.watchMode ? reportDiagnostic : undefined, options.watchMode ? reportWatchStatusChanged : undefined);
// override the "after program create" function because we need access to the new "program"
// eslint-disable-next-line @typescript-eslint/unbound-method
const origPostProgramCreate = host.afterProgramCreate;
host.afterProgramCreate = (program) => {
// in run-once mode we immediately trigger generation and then close the watch
if (!options.watchMode) {
loglevel_1.default.info("Run-once mode: running generation now.");
onProgramChanged(program, []);
// TODO: shut down the watcher
setTimeout(function () {
// must be asynchronously because createWatchProgram(...) below triggers this synchronously, so the return value "watch" is not yet assigned
loglevel_1.default.info("\n\nDone. Exiting.");
watch.close();
}, 0);
}
// continue with watchMode case handling
// to avoid an endless loop triggered by the modification of the generated files, we need to check which files were changed
let diag = program.getSemanticDiagnosticsOfNextAffectedFile();
const changedFiles = [];
while (diag) {
const aff = diag.affected;
if (aff.kind) {
changedFiles.push(aff.fileName);
}
else {
loglevel_1.default.debug(`### Changed: ${aff.getRootFileNames().join(", ")}`);
throw new Error("Not a source file change, but a program change caused the watch mode to trigger. This is unexpected. What does this mean? How did it happen?");
}
diag = program.getSemanticDiagnosticsOfNextAffectedFile();
}
newChangedFiles = changedFiles;
newProgram = program;
origPostProgramCreate(program);
};
watch = typescript_1.default.createWatchProgram(host);
}
// TODO: do we need to inspect the errors? For erroneous TS files parsing and hence interface creation may fail, but does this mean the interface should be deleted or not?
// some errors will even ALWAYS occur before the interface was created, at least as soon as the constructor lines have been added: the $XYSettings type will then not exist.
function reportDiagnostic(diagnostic) {
// this is called for all TypeScript errors during compilation
// not sure yet how we can benefit
// we can filter out the errors which are caused by the interface not yet being generated
if (diagnostic.code === 2304) {
if (typeof diagnostic.messageText === "string" &&
diagnostic.messageText.match(/Cannot find name '\$.+Settings'./)) {
// caused by not-yet generated interface
return;
}
}
// the remaining errors MAY be real - some may still be caused by access to API methods when the interface is not yet generated
// but also the real errors do not need to be brought to the developer's attention - they will appear in the editor anyway.
// log.error("[reportDiagnostic] ", diagnostic.code, ":: [in ", formatHost.getCanonicalFileName(diagnostic.file.fileName), "] ", ts.flattenDiagnosticMessageText( diagnostic.messageText, formatHost.getNewLine()));
}
/**
* Once the watch reaction cycle is through, we trigger the interface generation
*/
function reportWatchStatusChanged(diagnostic) {
if (diagnostic.code === 6031 || diagnostic.code === 6032) {
// "[File change detected.] Starting compilation in watch mode..."
// initial call or before compilation on update
}
else if (diagnostic.code === 6193 || diagnostic.code === 6194) {
// 'Found X errors. Watching for file changes.'
// after compilation on update, this is what we are interested in
if (newProgram) {
// TODO: handle errorCount
newChangedFiles = newChangedFiles.filter((fileName) => !fileName.endsWith(".gen.d.ts")); // not interested in changes to generated files
if (newChangedFiles.length) {
const timer_begin = perf_hooks_1.performance.now();
!options.watchMode &&
loglevel_1.default.debug("Changed files:\n- " + newChangedFiles.join("\n- "));
onProgramChanged(newProgram, newChangedFiles);
const timer_end = perf_hooks_1.performance.now();
loglevel_1.default.debug(`Handling the file change took ${(timer_end - timer_begin).toFixed(1)} ms.`);
}
else {
// no files modified/deleted
// TODO: handle file deletion... how? On EVERY change check whether the files which are responsible for the known interfaces are still there??
// TODO: also handle class renaming
//log.debug("no changes detected")
}
}
else {
// should not happen
throw new Error("reportWatchStatusChanged: diagnostic.code === 6194, but globalProgram not available");
}
}
else {
// should not happen
throw new Error(`reportWatchStatusChanged: diagnostic.code !== 6031 or 6032 or 6193 or 6194, it is: ${diagnostic.code}`);
}
}
/**
* Called whenever there is an actual change or when run in run-once mode
*
* @param builderProgram
* @param changedFiles an empty array in non-watch mode and always an
*/
function onProgramChanged(builderProgram, changedFiles) {
const program = builderProgram.getProgram();
const typeChecker = program.getTypeChecker();
const allKnownGlobals = getAllKnownGlobals(typeChecker);
// call the callback
onTSProgramUpdate(program, typeChecker, changedFiles, allKnownGlobals);
}
function getAllKnownGlobals(typeChecker) {
const allKnownGlobals = {};
// build a map of all known modules declared in the d.ts files (and elsewhere) along with their respective exports (so we can correctly identify enums which do not live in a module on their own)
typeChecker.getAmbientModules().forEach((mod) => {
const exports = [];
mod.exports.forEach((exp) => {
// collect the exports of this module
exports.push(exp.name);
// add an entry to globals for module lookup
let globalName = mod.name
.replace(/["']/g, "")
.replace(/\/library$/, "")
.replace(/\//g, ".");
const moduleName = mod.name.replace(/["']/g, "");
const entry = {
moduleName: moduleName,
};
if (exp.name !== "default") {
globalName += "." + exp.name;
entry.exportName = exp.name;
}
allKnownGlobals[globalName] = entry;
});
});
return allKnownGlobals;
}
//# sourceMappingURL=typeScriptEnvironment.js.map