• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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