abstrakt syntakstre - Hvordan slå sammen to Typescript AST-er
Jeg prøver å slå sammen to TypeScript AST-er laget fra strenger.
Jeg lager begge med denne metoden
const sourceFile = ts.createSourceFile(
"file.ts", // filePath
"function myFunction() {}", // fileText
ts.ScriptTarget.Latest, // scriptTarget
true // setParentNodes -- sets the `parent` property
);
Men dette vil opprette en node av typen ts.SourceFile, og hvis jeg prøver å slå sammen disse to AST-ene får jeg en feil når jeg prøver å skrive ut den endelige sourceFile: "Ubehandlet SyntaxKind: SourceFile."
Hvordan bør jeg riktig slå sammen to AST-er?
Takk
REDIGER: Her er et fullstendig fungerende kodeeksempel. Når jeg kjører dette, kaster det meg direkte inn i feilsøker (i nettleseren) med nevnte feil.
Jeg prøvde også bevisst å trekke ut ArrowFunction-noden (i ast2 er den pakket inn i SourceFile og ExpressionStatement), deretter kjører programmet uten feil, men endrer helt den indre template-literal til ugyldig kode.
const createAST = (str: string) =>
ts.createSourceFile(
"file.ts", // filePath
str, // fileText
ts.ScriptTarget.Latest, // scriptTarget
true // setParentNodes -- sets the `parent` property
)
const template1 = `const arr = fields.map(() => {})`
const template2 = `(field) => <Toolbar id={\`\${field.id}.\${field.name}\`} />`
const ast1 = createAST(template1)
const ast2 = createAST(template2)
const replaceTransformer = <T extends ts.Node>(
newNode: T
): ts.TransformerFactory<T> => {
return context => {
const visit: ts.Visitor = node => {
if (ts.isArrowFunction(node)) return newNode
return ts.visitEachChild(node, child => visit(child), context)
}
return node => ts.visitNode(node, visit)
}
}
if (!ast1 || !ast2) return console.log("something went wrong")
const result = ts.transform(ast1, [replaceTransformer(ast2)])
const res = result.transformed[0]
const printer = ts.createPrinter()
const string = printer.printFile(res)
console.log(string)
Svar #1
Det første problemet er at koden har JSX i en .ts-fil (ta en titt på resultatet av ast2). For å fikse dette, endre filnavnutvidelsen til .tsx slik at den vil bli tolket som JSX.
Det andre problemet er at det å sette en kildefil innenfor en kildefil vil føre til den feilen. I stedet er det best å erstatte et uttrykk med et annet uttrykk, så i stedet for å gi kildefilen—ast2—gi (ast2.statements[0] as ts.ExpressionStatement).expression.
Det tredje problemet er at TypeScript ikke er designet for å ta nodene fra én kildefil og bruke dem i en annen kildefil. For eksempel, selv med korrigeringene ovenfor, vil du fortsatt få følgende utdata:
const arr = fields.map((field) => <Toolbar id={) =field.idfield.name}/>);
...årsakene til dette er fordi skriveren bruker posisjonene fra ast2 for å indeksere inn i kildefilteksten til ast1, så det spyr ut søppel. Det jeg har anbefalt å gjøre tidligere er å sette pos og end for alle nodene i uttrykket fra ast2 til -1 (standardverdien som brukes for fabrikkskapte noder). Det er dog en litt hackete løsning.
Fullt eksempel
const createAST = (str: string) =>
ts.createSourceFile(
"file.tsx", // filePath
str, // fileText
ts.ScriptTarget.Latest, // scriptTarget
true, // setParentNodes -- sets the `parent` property
);
const template1 = `const arr = fields.map(() => {})`;
const template2 = "(field) => <Toolbar id={`${field.id}.${field.name}`} />";
const ast1 = createAST(template1);
const ast2 = createAST(template2);
const replaceTransformer = (
newNode: ts.Node,
): ts.TransformerFactory<ts.SourceFile> => {
return (context) => {
const visit: ts.Visitor = (node) => {
if (ts.isArrowFunction(node)) {
stripRanges(newNode);
return newNode;
}
return ts.visitEachChild(node, (child) => visit(child), context);
};
return (node) => ts.visitNode(node, visit);
};
};
const newNode = (ast2.statements[0] as ts.ExpressionStatement).expression;
stripRanges(newNode);
const result = ts.transform(ast1, [
replaceTransformer(newNode),
]);
const res = result.transformed[0];
const printer = ts.createPrinter();
console.log(printer.printFile(res));
function stripRanges(node: ts.Node) {
(node as any).pos = -1;
(node as any).end = -1;
ts.forEachChild(node, stripRanges);
}
Alternativt i stedet for å fjerne områdene, ser det ut til å fungere bare å ikke sette foreldrenodene (ved å gi false for det argumentet), siden skriveren ikke ser ut til å konsultere tekstfilen i det tilfellet… igjen, en annen midlertidig løsning gitt den nåværende implementeringen av skriveren.
