92 lines
2.8 KiB
TypeScript
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)
|
|
}
|