Files
moeka-project/services/minecraft/src/cognitive/conscious/repl-code-normalizer.ts
T
2026-03-23 02:16:10 +08:00

92 lines
2.8 KiB
TypeScript

import ts from 'typescript'
interface TextReplacement {
start: number
end: number
value: string
}
function getNodeText(sourceFile: ts.SourceFile, node: ts.Node): string {
const start = node.getStart(sourceFile, false)
const end = node.getEnd()
return sourceFile.text.slice(start, end)
}
function getVariableKeyword(flags: ts.NodeFlags): 'const' | 'let' | 'var' {
if (flags & ts.NodeFlags.Const)
return 'const'
if (flags & ts.NodeFlags.Let)
return 'let'
return 'var'
}
function normalizeVariableStatement(sourceFile: ts.SourceFile, statement: ts.VariableStatement): string {
const keyword = getVariableKeyword(statement.declarationList.flags)
const fragments: string[] = []
for (const declaration of statement.declarationList.declarations) {
if (ts.isIdentifier(declaration.name)) {
const initializer = declaration.initializer ? getNodeText(sourceFile, declaration.initializer) : 'undefined'
fragments.push(`globalThis.${declaration.name.text} = ${initializer};`)
continue
}
const declarationText = getNodeText(sourceFile, declaration)
fragments.push(`${keyword} ${declarationText};`)
}
return fragments.join('\n')
}
function applyReplacements(code: string, replacements: TextReplacement[]): string {
if (replacements.length === 0)
return code
let output = code
const sorted = [...replacements].sort((a, b) => b.start - a.start)
for (const replacement of sorted) {
output = `${output.slice(0, replacement.start)}${replacement.value}${output.slice(replacement.end)}`
}
return output
}
export function normalizeReplScript(code: string): string {
const sourceFile = ts.createSourceFile('repl.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
const diagnostics = (sourceFile as ts.SourceFile & {
parseDiagnostics?: readonly ts.DiagnosticWithLocation[]
}).parseDiagnostics
if (diagnostics && diagnostics.length > 0)
return code
const replacements: TextReplacement[] = []
for (const statement of sourceFile.statements) {
if (!ts.isVariableStatement(statement))
continue
const start = statement.getStart(sourceFile, false)
const end = statement.getEnd()
replacements.push({
start,
end,
value: normalizeVariableStatement(sourceFile, statement),
})
}
const hasTopLevelReturn = sourceFile.statements.some(statement => ts.isReturnStatement(statement))
if (!hasTopLevelReturn && sourceFile.statements.length > 0) {
const lastStatement = sourceFile.statements.at(-1)
if (ts.isExpressionStatement(lastStatement)) {
const expressionText = getNodeText(sourceFile, lastStatement.expression)
replacements.push({
start: lastStatement.getStart(sourceFile, false),
end: lastStatement.getEnd(),
value: `return (${expressionText})`,
})
}
}
return applyReplacements(code, replacements)
}