Files
ask262/engine262/scripts/generate_error_message_hint.mts
T

104 lines
3.5 KiB
TypeScript

/* eslint-disable no-console */
import { opendir, readFile, writeFile } from 'fs/promises';
import { join } from 'path';
import {
createSourceFile, isCallExpression, isIdentifier, isPropertyAccessExpression, isStringLiteral, ScriptTarget,
} from 'typescript';
async function* readdir(dir: string): AsyncGenerator<string> {
for await (const dirent of await opendir(dir)) {
const p = join(dir, dirent.name);
if (dirent.isDirectory()) {
yield* readdir(p);
} else {
yield p;
}
}
}
const list = ['EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'Error', 'AggregateError'];
const messages = new Set<string>();
const promises: Promise<void>[] = [];
for await (const filePath of readdir(join(import.meta.dirname, '../src/'))) {
if (!filePath.endsWith('.mts')) {
continue;
}
promises.push(readFile(filePath, 'utf8').then((content) => {
const sourceFile = createSourceFile(filePath, content, {
languageVersion: ScriptTarget.ESNext,
});
sourceFile.forEachChild(function visitor(node) {
if (
isCallExpression(node)
&& isPropertyAccessExpression(node.expression)
&& isIdentifier(node.expression.expression)
&& node.expression.expression.escapedText === 'Throw'
&& isIdentifier(node.expression.name)
&& list.includes(node.expression.name.escapedText as string)
&& node.arguments.length >= 1
) {
if (!isStringLiteral(node.arguments[0])) {
console.warn(`Non-literal error message in ${filePath}`);
} else {
messages.add(node.arguments[0].text);
}
}
node.forEachChild(visitor);
});
}));
}
await Promise.all(promises);
const sortedMessages = Array.from(messages).sort();
const old = await readFile(join(import.meta.dirname, '../src/host-defined/error-messages.mts'), 'utf8');
const autoGenStart = '// auto-generate start';
const autoGenEnd = '// auto-generate end';
const beforeAutoGen = old.slice(0, old.indexOf(autoGenStart) + autoGenStart.length);
const afterAutoGen = old.slice(old.indexOf(autoGenEnd));
const messagesByParameterCount: string[][] = [];
sortedMessages.forEach((m) => {
// const paramCount = (m.match(/\$\d+/g) || []).length;
// const params = Array.from({ length: paramCount }, (_, i) => `$${i + 1}: Formattable`).join(', ');
// return ` (m: '${m}'${params ? `, ${params}` : ''}): ThrowCompletion;`;
if (m.includes('$3')) {
messagesByParameterCount[3] ??= [];
messagesByParameterCount[3].push(m);
} else if (m.includes('$2')) {
messagesByParameterCount[2] ??= [];
messagesByParameterCount[2].push(m);
} else if (m.includes('$1')) {
messagesByParameterCount[1] ??= [];
messagesByParameterCount[1].push(m);
} else {
messagesByParameterCount[0] ??= [];
messagesByParameterCount[0].push(m);
}
});
const generatedLines: string[] = [];
messagesByParameterCount.forEach((group, index) => {
const args: string[] = [group.sort().map((m) => (m.includes("'") ? `"${m}"` : `'${m}'`)).join('\n | '), ...Array(index).fill('Formattable').map((t, i) => `$${i + 1}: ${t}`)];
args[0] += '\n ';
generatedLines.push(` (m:\n${args.join(', ')}): ThrowCompletion;`);
});
const generated = generatedLines.join('\n');
const newFileContent = `${beforeAutoGen}
${generated}
${afterAutoGen}`;
if (newFileContent !== old) {
console.log('Updating error-messages.mts');
await writeFile(
join(import.meta.dirname, '../src/host-defined/error-messages.mts'),
newFileContent,
);
} else {
console.log('error-messages.mts is up to date');
}