TypeScript/scripts/processDiagnosticMessages.mjs

163 строки
5.8 KiB
JavaScript

import fs from "fs";
import path from "path";
/** @typedef {{
category: string;
code: number;
reportsUnnecessary?: {};
reportsDeprecated?: {};
isEarly?: boolean;
elidedInCompatabilityPyramid?: boolean;
}} DiagnosticDetails */
void 0;
/** @typedef {Map<string, DiagnosticDetails>} InputDiagnosticMessageTable */
async function main() {
if (process.argv.length < 3) {
console.log("Usage:");
console.log("\tnode processDiagnosticMessages.mjs <diagnostic-json-input-file>");
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<number, true | undefined>} */
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 = [
"// <auto-generated />",
`// generated from '${inputFilePathRel}'`,
"",
'import { DiagnosticCategory, DiagnosticMessage } from "./types.js";',
"",
"function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {",
" return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated };",
"}",
"",
"/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion*/", // type assertions are needed for isolatedDeclarations
"/** @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}) as DiagnosticMessage,`);
});
result.push("};");
return result.join("\r\n");
}
/**
* @param {InputDiagnosticMessageTable} messageTable
* @returns {string}
*/
function buildDiagnosticMessageOutput(messageTable) {
/** @type {Record<string, string>} */
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();