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