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