import path from "path"; import fs from "fs"; /** @typedef {{ category: string; code: number; reportsUnnecessary?: {}; reportsDeprecated?: {}; isEarly?: boolean; elidedInCompatabilityPyramid?: boolean; }} DiagnosticDetails */ /** @typedef {Map} InputDiagnosticMessageTable */ async function main() { if (process.argv.length < 3) { console.log("Usage:"); console.log("\tnode processDiagnosticMessages.mjs "); return; } /** * @param {string} fileName * @param {string} contents */ async function writeFile(fileName, contents) { const filePath = path.join(path.dirname(inputFilePath), fileName); try { const existingContents = await fs.promises.readFile(filePath, "utf-8"); if (existingContents === contents) { return; } } catch { // Just write the file. } await fs.promises.writeFile(filePath, contents, { encoding: "utf-8" }); } const inputFilePath = process.argv[2].replace(/\\/g, "/"); console.log(`Reading diagnostics from ${inputFilePath}`); const inputStr = await fs.promises.readFile(inputFilePath, { encoding: "utf-8" }); /** @type {{ [key: string]: DiagnosticDetails }} */ const diagnosticMessagesJson = JSON.parse(inputStr); /** @type {InputDiagnosticMessageTable} */ const diagnosticMessages = new Map(); for (const key in diagnosticMessagesJson) { if (Object.hasOwnProperty.call(diagnosticMessagesJson, key)) { diagnosticMessages.set(key, diagnosticMessagesJson[key]); } } const infoFileOutput = buildInfoFileOutput(diagnosticMessages, inputFilePath); checkForUniqueCodes(diagnosticMessages); await writeFile("diagnosticInformationMap.generated.ts", infoFileOutput); const messageOutput = buildDiagnosticMessageOutput(diagnosticMessages); await writeFile("diagnosticMessages.generated.json", messageOutput); } /** * @param {InputDiagnosticMessageTable} diagnosticTable */ function checkForUniqueCodes(diagnosticTable) { /** @type {Record} */ const allCodes = []; diagnosticTable.forEach(({ code }) => { if (allCodes[code]) { throw new Error(`Diagnostic code ${code} appears more than once.`); } allCodes[code] = true; }); } /** * @param {InputDiagnosticMessageTable} messageTable * @param {string} inputFilePathRel * @returns {string} */ function buildInfoFileOutput(messageTable, inputFilePathRel) { const result = [ "// ", `// generated from '${inputFilePathRel}'`, "", "import { DiagnosticCategory, DiagnosticMessage } from \"./types\";", "", "function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {", " return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated };", "}", "", "/** @internal */", "export const Diagnostics = {", ]; messageTable.forEach(({ code, category, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated }, name) => { const propName = convertPropertyName(name); const argReportsUnnecessary = reportsUnnecessary ? `, /*reportsUnnecessary*/ ${reportsUnnecessary}` : ""; const argElidedInCompatabilityPyramid = elidedInCompatabilityPyramid ? `${!reportsUnnecessary ? ", /*reportsUnnecessary*/ undefined" : ""}, /*elidedInCompatabilityPyramid*/ ${elidedInCompatabilityPyramid}` : ""; const argReportsDeprecated = reportsDeprecated ? `${!argElidedInCompatabilityPyramid ? ", /*reportsUnnecessary*/ undefined, /*elidedInCompatabilityPyramid*/ undefined" : ""}, /*reportsDeprecated*/ ${reportsDeprecated}` : ""; result.push(` ${propName}: diag(${code}, DiagnosticCategory.${category}, "${createKey(propName, code)}", ${JSON.stringify(name)}${argReportsUnnecessary}${argElidedInCompatabilityPyramid}${argReportsDeprecated}),`); }); result.push("};"); return result.join("\r\n"); } /** * @param {InputDiagnosticMessageTable} messageTable * @returns {string} */ function buildDiagnosticMessageOutput(messageTable) { /** @type {Record} */ const result = {}; messageTable.forEach(({ code }, name) => { const propName = convertPropertyName(name); result[createKey(propName, code)] = name; }); return JSON.stringify(result, undefined, 2).replace(/\r?\n/g, "\r\n"); } /** * * @param {string} name * @param {number} code * @returns {string} */ function createKey(name, code) { return name.slice(0, 100) + "_" + code; } /** * @param {string} origName * @returns {string} */ function convertPropertyName(origName) { let result = origName.split("").map(char => { if (char === "*") return "_Asterisk"; if (char === "/") return "_Slash"; if (char === ":") return "_Colon"; return /\w/.test(char) ? char : "_"; }).join(""); // get rid of all multi-underscores result = result.replace(/_+/g, "_"); // remove any leading underscore, unless it is followed by a number. result = result.replace(/^_([^\d])/, "$1"); // get rid of all trailing underscores. result = result.replace(/_$/, ""); return result; } main();