TypeScript/scripts/processDiagnosticMessages.mjs
Jake Bailey d12116d8da Fix all internal JSDoc comments
If these are regular comments, then they won't appear in our d.ts files.
But, now we are relying on an external d.ts bundler to produce our final
merged, so they need to be present in the "input" d.ts files, meaning
they have to be JSDoc comments.

These comments only work today because all of our builds load their TS
files from scratch, so they see the actual source files and their
non-JSDoc comments.

The comments also need to be attached to a declaration, not floating,
otherwise they won't be used by api-extractor, so move them if needed.
2022-11-07 13:34:44 -08:00

154 lines
5.3 KiB
JavaScript

import path from "path";
import fs from "fs";
/** @typedef {{
category: string;
code: number;
reportsUnnecessary?: {};
reportsDeprecated?: {};
isEarly?: boolean;
elidedInCompatabilityPyramid?: boolean;
}} DiagnosticDetails */
void 0;
/** @typedef {Map<string, DiagnosticDetails>} InputDiagnosticMessageTable */
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
*/
function writeFile(fileName, contents) {
fs.writeFile(path.join(path.dirname(inputFilePath), fileName), contents, { encoding: "utf-8" }, err => {
if (err) throw err;
});
}
const inputFilePath = process.argv[2].replace(/\\/g, "/");
console.log(`Reading diagnostics from ${inputFilePath}`);
const inputStr = fs.readFileSync(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);
writeFile("diagnosticInformationMap.generated.ts", infoFileOutput);
const messageOutput = buildDiagnosticMessageOutput(diagnosticMessages);
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\";",
"",
"function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {",
" return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated };",
"}",
"",
"/** @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}),`);
});
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();