UNPKG

@ui5/ts-interface-generator

Version:

Generator for TypeScript type definitions for custom UI5 controls implemented in TypeScript

166 lines 8.69 kB
"use strict"; 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