1import path from "path"; 2import fs from "fs"; 3 4/** @typedef {{ 5 category: string; 6 code: number; 7 reportsUnnecessary?: {}; 8 reportsDeprecated?: {}; 9 isEarly?: boolean; 10 elidedInCompatabilityPyramid?: boolean; 11}} DiagnosticDetails */ 12 13/** @typedef {Map<string, DiagnosticDetails>} InputDiagnosticMessageTable */ 14 15function main() { 16 if (process.argv.length < 3) { 17 console.log("Usage:"); 18 console.log("\tnode processDiagnosticMessages.mjs <diagnostic-json-input-file>"); 19 return; 20 } 21 22 /** 23 * @param {string} fileName 24 * @param {string} contents 25 */ 26 function writeFile(fileName, contents) { 27 fs.writeFile(path.join(path.dirname(inputFilePath), fileName), contents, { encoding: "utf-8" }, err => { 28 if (err) throw err; 29 }); 30 } 31 32 const inputFilePath = process.argv[2].replace(/\\/g, "/"); 33 console.log(`Reading diagnostics from ${inputFilePath}`); 34 const inputStr = fs.readFileSync(inputFilePath, { encoding: "utf-8" }); 35 36 /** @type {{ [key: string]: DiagnosticDetails }} */ 37 const diagnosticMessagesJson = JSON.parse(inputStr); 38 39 /** @type {InputDiagnosticMessageTable} */ 40 const diagnosticMessages = new Map(); 41 for (const key in diagnosticMessagesJson) { 42 if (Object.hasOwnProperty.call(diagnosticMessagesJson, key)) { 43 diagnosticMessages.set(key, diagnosticMessagesJson[key]); 44 } 45 } 46 47 const outputFilesDir = path.dirname(inputFilePath); 48 const thisFilePathRel = path.relative(process.cwd(), outputFilesDir); 49 50 const infoFileOutput = buildInfoFileOutput(diagnosticMessages, `./${path.basename(inputFilePath)}`, thisFilePathRel); 51 checkForUniqueCodes(diagnosticMessages); 52 writeFile("diagnosticInformationMap.generated.ts", infoFileOutput); 53 54 const messageOutput = buildDiagnosticMessageOutput(diagnosticMessages); 55 writeFile("diagnosticMessages.generated.json", messageOutput); 56} 57 58/** 59 * @param {InputDiagnosticMessageTable} diagnosticTable 60 */ 61function checkForUniqueCodes(diagnosticTable) { 62 /** @type {Record<number, true | undefined>} */ 63 const allCodes = []; 64 diagnosticTable.forEach(({ code }) => { 65 if (allCodes[code]) { 66 throw new Error(`Diagnostic code ${code} appears more than once.`); 67 } 68 allCodes[code] = true; 69 }); 70} 71 72/** 73 * @param {InputDiagnosticMessageTable} messageTable 74 * @param {string} inputFilePathRel 75 * @param {string} thisFilePathRel 76 * @returns {string} 77 */ 78function buildInfoFileOutput(messageTable, inputFilePathRel, thisFilePathRel) { 79 let result = 80 "// <auto-generated />\r\n" + 81 "// generated from '" + inputFilePathRel + "' in '" + thisFilePathRel.replace(/\\/g, "/") + "'\r\n" + 82 "/* @internal */\r\n" + 83 "namespace ts {\r\n" + 84 " function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {\r\n" + 85 " return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated };\r\n" + 86 " }\r\n" + 87 " export const Diagnostics = {\r\n"; 88 messageTable.forEach(({ code, category, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated }, name) => { 89 const propName = convertPropertyName(name); 90 const argReportsUnnecessary = reportsUnnecessary ? `, /*reportsUnnecessary*/ ${reportsUnnecessary}` : ""; 91 const argElidedInCompatabilityPyramid = elidedInCompatabilityPyramid ? `${!reportsUnnecessary ? ", /*reportsUnnecessary*/ undefined" : ""}, /*elidedInCompatabilityPyramid*/ ${elidedInCompatabilityPyramid}` : ""; 92 const argReportsDeprecated = reportsDeprecated ? `${!argElidedInCompatabilityPyramid ? ", /*reportsUnnecessary*/ undefined, /*elidedInCompatabilityPyramid*/ undefined" : ""}, /*reportsDeprecated*/ ${reportsDeprecated}` : ""; 93 94 result += ` ${propName}: diag(${code}, DiagnosticCategory.${category}, "${createKey(propName, code)}", ${JSON.stringify(name)}${argReportsUnnecessary}${argElidedInCompatabilityPyramid}${argReportsDeprecated}),\r\n`; 95 }); 96 97 result += " };\r\n}"; 98 99 return result; 100} 101 102/** 103 * @param {InputDiagnosticMessageTable} messageTable 104 * @returns {string} 105 */ 106function buildDiagnosticMessageOutput(messageTable) { 107 /** @type {Record<string, string>} */ 108 const result = {}; 109 110 messageTable.forEach(({ code }, name) => { 111 const propName = convertPropertyName(name); 112 result[createKey(propName, code)] = name; 113 }); 114 115 return JSON.stringify(result, undefined, 2).replace(/\r?\n/g, "\r\n"); 116} 117 118/** 119 * 120 * @param {string} name 121 * @param {number} code 122 * @returns {string} 123 */ 124function createKey(name, code) { 125 return name.slice(0, 100) + "_" + code; 126} 127 128/** 129 * @param {string} origName 130 * @returns {string} 131 */ 132function convertPropertyName(origName) { 133 let result = origName.split("").map(char => { 134 if (char === "*") return "_Asterisk"; 135 if (char === "/") return "_Slash"; 136 if (char === ":") return "_Colon"; 137 return /\w/.test(char) ? char : "_"; 138 }).join(""); 139 140 // get rid of all multi-underscores 141 result = result.replace(/_+/g, "_"); 142 143 // remove any leading underscore, unless it is followed by a number. 144 result = result.replace(/^_([^\d])/, "$1"); 145 146 // get rid of all trailing underscores. 147 result = result.replace(/_$/, ""); 148 149 return result; 150} 151 152main(); 153