Átfogó útmutató a TypeScript Compiler API-hoz, amely bemutatja az absztrakt szintaxisfákat (AST), a kódelemzést, -transzformációt és -generálást.
TypeScript Compiler API: Az AST manipuláció és a kódtranszformáció mesterfogásai
A TypeScript Compiler API egy hatékony interfészt biztosít a TypeScript és JavaScript kódok elemzéséhez, manipulálásához és generálásához. A középpontjában az Absztrakt Szintaxisfa (AST) áll, amely a forráskód strukturált reprezentációja. Az AST-vel való munka megértése lehetővé teszi fejlett eszközök, például linterek, kódformázók, statikus elemzők és egyéni kódgenerátorok készítését.
Mi az a TypeScript Compiler API?
A TypeScript Compiler API egy olyan TypeScript interfészekből és függvényekből álló készlet, amely feltárja a TypeScript fordító belső működését. Lehetővé teszi a fejlesztők számára, hogy programozottan lépjenek kapcsolatba a fordítási folyamattal, túllépve a kód egyszerű fordításán. Használhatja a következőkre:
- Kódelemzés: A kód szerkezetének vizsgálata, potenciális problémák azonosítása és szemantikai információk kinyerése.
- Kódtranszformáció: Meglévő kód módosítása, új funkciók hozzáadása vagy a kód automatikus refaktorálása.
- Kódgenerálás: Új kód létrehozása nulláról, sablonok vagy más bemenetek alapján.
Ez az API elengedhetetlen olyan kifinomult fejlesztői eszközök készítéséhez, amelyek javítják a kódminőséget, automatizálják az ismétlődő feladatokat és növelik a fejlesztői termelékenységet.
Az Absztrakt Szintaxisfa (AST) megértése
Az AST a kód szerkezetének fa-szerű ábrázolása. A fa minden csomópontja egy szintaktikai konstrukciót képvisel, például egy változódeklarációt, egy függvényhívást vagy egy vezérlési szerkezetet. A TypeScript Compiler API eszközöket biztosít az AST bejárásához, csomópontjainak vizsgálatához és módosításához.
Vegyük ezt az egyszerű TypeScript kódot:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
Ennek a kódnak az AST-je reprezentálná a függvénydeklarációt, a return utasítást, a template literált, a console.log hívást és a kód egyéb elemeit. Az AST vizualizálása kihívást jelenthet, de az olyan eszközök, mint az AST explorer (astexplorer.net) segíthetnek. Ezek az eszközök lehetővé teszik, hogy beírja a kódot, és egy felhasználóbarát formátumban lássa a hozzá tartozó AST-t. Az AST Explorer használata segít megérteni, hogy milyen típusú kódszerkezettel fog dolgozni.
Főbb AST csomóponttípusok
A TypeScript Compiler API különböző AST csomóponttípusokat definiál, amelyek mindegyike egy-egy szintaktikai konstrukciót képvisel. Íme néhány gyakori csomóponttípus:
- SourceFile: Egy teljes TypeScript fájlt reprezentál.
- FunctionDeclaration: Egy függvénydefiníciót reprezentál.
- VariableDeclaration: Egy változódeklarációt reprezentál.
- Identifier: Egy azonosítót reprezentál (pl. változónév, függvénynév).
- StringLiteral: Egy sztring literált reprezentál.
- CallExpression: Egy függvényhívást reprezentál.
- ReturnStatement: Egy return utasítást reprezentál.
Minden csomóponttípusnak vannak tulajdonságai, amelyek információt nyújtanak a megfelelő kódelemről. Például egy `FunctionDeclaration` csomópontnak lehetnek tulajdonságai a nevére, paramétereire, visszatérési típusára és törzsére vonatkozóan.
Első lépések a Compiler API-val
A Compiler API használatának megkezdéséhez telepítenie kell a TypeScriptet, és alapszinten ismernie kell a TypeScript szintaxisát. Íme egy egyszerű példa, amely bemutatja, hogyan lehet beolvasni egy TypeScript fájlt és kiírni annak AST-jét:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015, // Target ECMAScript version
true // SetParentNodes: true to retain parent references in the AST
);
function printAST(node: ts.Node, indent = 0) {
const indentStr = " ".repeat(indent);
console.log(`${indentStr}${ts.SyntaxKind[node.kind]}`);
node.forEachChild(child => printAST(child, indent + 1));
}
printAST(sourceFile);
Magyarázat:
- Modulok importálása: Importálja a `typescript` modult és a `fs` modult a fájlrendszeri műveletekhez.
- Forrásfájl beolvasása: Beolvassa egy `example.ts` nevű TypeScript fájl tartalmát. Ahhoz, hogy ez működjön, létre kell hoznia egy `example.ts` fájlt.
- SourceFile létrehozása: Létrehoz egy `SourceFile` objektumot, amely az AST gyökerét képviseli. A `ts.createSourceFile` függvény elemzi a forráskódot és generálja az AST-t.
- AST kiírása: Definiál egy rekurzív `printAST` függvényt, amely bejárja az AST-t és kiírja az egyes csomópontok típusát.
- printAST hívása: Meghívja a `printAST` függvényt, hogy elkezdje az AST kiírását a gyökér `SourceFile` csomóponttól.
A kód futtatásához mentse el `.ts` fájlként (pl. `ast-example.ts`), hozzon létre egy `example.ts` fájlt valamilyen TypeScript kóddal, majd fordítsa le és futtassa a kódot:
tsc ast-example.ts
node ast-example.js
Ez kiírja az `example.ts` fájl AST-jét a konzolra. A kimenet megmutatja a csomópontok hierarchiáját és azok típusait. Például megjelenítheti a `FunctionDeclaration`, `Identifier`, `Block` és más csomóponttípusokat.
Az AST bejárása
A Compiler API többféle módot kínál az AST bejárására. A legegyszerűbb a `forEachChild` metódus használata, ahogy az előző példában is látható. Ez a metódus egy adott csomópont minden gyermekcsomópontját meglátogatja.
Bonyolultabb bejárási forgatókönyvekhez használhat egy `Visitor` mintát. A visitor egy olyan objektum, amely metódusokat definiál, amelyeket meghatározott csomóponttípusok esetén kell meghívni. Ez lehetővé teszi a bejárási folyamat testreszabását és a csomópont típusa alapján történő műveletek végrehajtását.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
class IdentifierVisitor {
visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
console.log(`Found identifier: ${node.text}`);
}
ts.forEachChild(node, n => this.visit(n));
}
}
const visitor = new IdentifierVisitor();
visitor.visit(sourceFile);
Magyarázat:
- IdentifierVisitor osztály: Definiál egy `IdentifierVisitor` osztályt egy `visit` metódussal.
- Visit metódus: A `visit` metódus ellenőrzi, hogy az aktuális csomópont `Identifier` típusú-e. Ha igen, kiírja az azonosító szövegét. Ezután rekurzívan meghívja a `ts.forEachChild` függvényt a gyermekcsomópontok meglátogatásához.
- Visitor létrehozása: Létrehoz egy példányt az `IdentifierVisitor`-ből.
- Bejárás indítása: Meghívja a `visit` metódust a `SourceFile`-on a bejárás elindításához.
Ez a példa bemutatja, hogyan lehet megtalálni az összes azonosítót az AST-ben. Ezt a mintát adaptálhatja más csomóponttípusok megtalálásához és különböző műveletek végrehajtásához.
Az AST transzformálása
A Compiler API valódi ereje az AST transzformálásának képességében rejlik. Módosíthatja az AST-t, hogy megváltoztassa a kód szerkezetét és viselkedését. Ez az alapja a kód-refaktoráló eszközöknek, kódgenerátoroknak és más fejlett eszközöknek.
Az AST transzformálásához a `ts.transform` függvényt kell használnia. Ez a függvény egy `SourceFile`-t és egy listát vesz át `TransformerFactory` függvényekből. A `TransformerFactory` egy olyan függvény, amely egy `TransformationContext`-et kap, és egy `Transformer` függvényt ad vissza. A `Transformer` függvény felelős az AST csomópontjainak meglátogatásáért és transzformálásáért.
Íme egy egyszerű példa, amely bemutatja, hogyan lehet megjegyzést hozzáadni egy TypeScript fájl elejéhez:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
const transformerFactory: ts.TransformerFactory = context => {
return transformer => {
return node => {
if (ts.isSourceFile(node)) {
// Create a leading comment
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" This file was automatically transformed ",
true // hasTrailingNewLine
);
return node;
}
return node;
};
};
};
const { transformed } = ts.transform(sourceFile, [transformerFactory]);
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed
});
const result = printer.printFile(transformed[0]);
fs.writeFileSync("example.transformed.ts", result);
Magyarázat:
- TransformerFactory: Definiál egy `TransformerFactory` függvényt, amely egy `Transformer` függvényt ad vissza.
- Transformer: A `Transformer` függvény ellenőrzi, hogy az aktuális csomópont `SourceFile` típusú-e. Ha igen, egy bevezető megjegyzést ad a csomóponthoz a `ts.addSyntheticLeadingComment` segítségével.
- ts.transform: Meghívja a `ts.transform` függvényt a transzformáció alkalmazásához a `SourceFile`-ra.
- Printer: Létrehoz egy `Printer` objektumot, hogy kódot generáljon a transzformált AST-ből.
- Kiírás és mentés: Kiírja a transzformált kódot és elmenti egy új, `example.transformed.ts` nevű fájlba.
Ez a példa egy egyszerű transzformációt mutat be, de ugyanezt a mintát használhatja bonyolultabb transzformációk végrehajtására is, mint például a kód refaktorálása, naplózási utasítások hozzáadása vagy dokumentáció generálása.
Haladó transzformációs technikák
Íme néhány haladó transzformációs technika, amelyet a Compiler API-val használhat:
- Új csomópontok létrehozása: Használja a `ts.createXXX` függvényeket új AST csomópontok létrehozásához. Például a `ts.createVariableDeclaration` egy új változódeklarációs csomópontot hoz létre.
- Csomópontok cseréje: Cserélje le a meglévő csomópontokat újakra a `ts.visitEachChild` függvény segítségével.
- Csomópontok hozzáadása: Adjon hozzá új csomópontokat az AST-hez a `ts.updateXXX` függvényekkel. Például a `ts.updateBlock` frissít egy blokk utasítást új utasításokkal.
- Csomópontok eltávolítása: Távolítson el csomópontokat az AST-ből azáltal, hogy `undefined`-et ad vissza a transzformáló függvényből.
Kódgenerálás
Az AST transzformálása után kódot kell generálnia belőle. A Compiler API erre a célra egy `Printer` objektumot biztosít. A `Printer` egy AST-t kap bemenetként, és a kód sztring reprezentációját generálja.
A `ts.createPrinter` függvény hoz létre egy `Printer` objektumot. A printert különböző opciókkal konfigurálhatja, például a használandó újsor karakterrel és azzal, hogy a megjegyzéseket is kiírja-e.
A `printer.printFile` metódus egy `SourceFile`-t vesz át, és a kód sztring reprezentációját adja vissza. Ezt a sztringet ezután kiírhatja egy fájlba.
A Compiler API gyakorlati alkalmazásai
A TypeScript Compiler API-nak számos gyakorlati alkalmazása van a szoftverfejlesztésben. Íme néhány példa:
- Linterek: Készítsen egyéni lintereket a kódolási szabványok betartatására és a potenciális problémák azonosítására a kódban.
- Kódformázók: Hozzon létre kódformázókat, amelyek automatikusan formázzák a kódot egy adott stílus útmutató szerint.
- Statikus elemzők: Fejlesszen statikus elemzőket hibák, biztonsági rések és teljesítményproblémák felderítésére a kódban.
- Kódgenerátorok: Generáljon kódot sablonokból vagy más bemenetekből, automatizálva az ismétlődő feladatokat és csökkentve a boilerplate kódot. Például API kliensek vagy adatbázis sémák generálása egy leíró fájlból.
- Refaktoráló eszközök: Építsen refaktoráló eszközöket a változók automatikus átnevezéséhez, függvények kiemeléséhez vagy kód mozgatásához fájlok között.
- Nemzetköziesítés (i18n) automatizálása: Automatikusan kinyerheti a fordítható sztringeket a TypeScript kódból, és lokalizációs fájlokat generálhat különböző nyelvekhez. Például egy eszköz átvizsgálhatja a kódot a `translate()` függvénynek átadott sztringek után, és automatikusan hozzáadhatja azokat egy fordítási erőforrásfájlhoz.
Példa: Egy egyszerű linter készítése
Készítsünk egy egyszerű lintert, amely ellenőrzi a fel nem használt változókat a TypeScript kódban. Ez a linter azonosítja azokat a változókat, amelyeket deklaráltak, de soha nem használtak.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
function findUnusedVariables(sourceFile: ts.SourceFile) {
const usedVariables = new Set();
function visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
usedVariables.add(node.text);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
const unusedVariables: string[] = [];
function checkVariableDeclaration(node: ts.Node) {
if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
const variableName = node.name.text;
if (!usedVariables.has(variableName)) {
unusedVariables.push(variableName);
}
}
ts.forEachChild(node, checkVariableDeclaration);
}
checkVariableDeclaration(sourceFile);
return unusedVariables;
}
const unusedVariables = findUnusedVariables(sourceFile);
if (unusedVariables.length > 0) {
console.log("Unused variables:");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("No unused variables found.");
}
Magyarázat:
- findUnusedVariables függvény: Definiál egy `findUnusedVariables` függvényt, amely egy `SourceFile`-t kap bemenetként.
- usedVariables Set: Létrehoz egy `Set`-et a használt változók neveinek tárolására.
- visit függvény: Definiál egy rekurzív `visit` függvényt, amely bejárja az AST-t és hozzáadja az összes azonosító nevét a `usedVariables` halmazhoz.
- checkVariableDeclaration függvény: Definiál egy rekurzív `checkVariableDeclaration` függvényt, amely ellenőrzi, hogy egy változódeklaráció fel nem használt-e. Ha igen, hozzáadja a változó nevét az `unusedVariables` tömbhöz.
- unusedVariables visszaadása: Visszaad egy tömböt, amely a fel nem használt változók neveit tartalmazza.
- Kimenet: Kiírja a fel nem használt változókat a konzolra.
Ez a példa egy egyszerű lintert mutat be. Bővítheti, hogy más kódolási szabványokat is ellenőrizzen, és más potenciális problémákat azonosítson a kódban. Például ellenőrizhetné a fel nem használt importokat, a túlságosan bonyolult függvényeket vagy a potenciális biztonsági réseket. A kulcs az, hogy megértse, hogyan kell bejárni az AST-t, és azonosítani azokat a specifikus csomóponttípusokat, amelyek érdeklik.
Bevált gyakorlatok és megfontolások
- Értse meg az AST-t: Fektessen időt az AST szerkezetének megértésébe. Használjon olyan eszközöket, mint az AST explorer, hogy vizualizálja a kódjának AST-jét.
- Használjon típusőröket (Type Guards): Használjon típusőröket (`ts.isXXX`), hogy biztosítsa, hogy a megfelelő csomóponttípusokkal dolgozik.
- Vegye figyelembe a teljesítményt: Az AST transzformációk számításigényesek lehetnek. Optimalizálja a kódját, hogy minimalizálja a meglátogatott és transzformált csomópontok számát.
- Kezelje a hibákat: Kezelje a hibákat elegánsan. A Compiler API kivételeket dobhat, ha érvénytelen műveleteket próbál végrehajtani az AST-n.
- Teszteljen alaposan: Tesztelje alaposan a transzformációit, hogy biztosítsa, hogy a kívánt eredményt produkálják, és nem vezetnek be új hibákat.
- Használjon meglévő könyvtárakat: Fontolja meg olyan meglévő könyvtárak használatát, amelyek magasabb szintű absztrakciókat biztosítanak a Compiler API felett. Ezek a könyvtárak leegyszerűsíthetik a gyakori feladatokat és csökkenthetik a megírandó kód mennyiségét. Ilyen például a `ts-morph` és a `typescript-eslint`.
Összegzés
A TypeScript Compiler API egy hatékony eszköz a fejlett fejlesztői eszközök készítéséhez. Az AST-vel való munka megértésével lintereket, kódformázókat, statikus elemzőket és más olyan eszközöket hozhat létre, amelyek javítják a kódminőséget, automatizálják az ismétlődő feladatokat és növelik a fejlesztői termelékenységet. Bár az API összetett lehet, elsajátításának előnyei jelentősek. Ez az átfogó útmutató alapot nyújt a Compiler API hatékony felfedezéséhez és használatához a projektjeiben. Ne felejtse el kihasználni az olyan eszközöket, mint az AST Explorer, gondosan kezelni a csomóponttípusokat, és alaposan tesztelni a transzformációit. Gyakorlással és elkötelezettséggel kiaknázhatja a TypeScript Compiler API teljes potenciálját, és innovatív megoldásokat építhet a szoftverfejlesztési környezet számára.
További források:
- TypeScript Compiler API Dokumentáció: [https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
- AST Explorer: [https://astexplorer.net/](https://astexplorer.net/)
- ts-morph könyvtár: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)