/* 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 { 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(); const promises: Promise[] = []; 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'); }