Kompleksowy przewodnik po TypeScript Compiler API, obejmuj膮cy drzewa sk艂adni abstrakcyjnej (AST), analiz臋 kodu, transformacj臋 i generowanie dla mi臋dzynarodowych deweloper贸w.
TypeScript Compiler API: Opis i manipulacja AST oraz transformacja kodu
TypeScript Compiler API zapewnia pot臋偶ny interfejs do analizowania, manipulowania i generowania kodu TypeScript i JavaScript. W jego sercu znajduje si臋 Abstract Syntax Tree (AST), czyli strukturalna reprezentacja kodu 藕r贸d艂owego. Zrozumienie, jak pracowa膰 z AST, odblokowuje mo偶liwo艣ci budowania zaawansowanych narz臋dzi, takich jak linters, formatery kodu, analizatory statyczne i niestandardowe generatory kodu.
Czym jest TypeScript Compiler API?
TypeScript Compiler API to zestaw interfejs贸w i funkcji TypeScript, kt贸re ujawniaj膮 wewn臋trzne dzia艂anie kompilatora TypeScript. Umo偶liwia programistom programowe interakcje z procesem kompilacji, wykraczaj膮c poza zwyk艂膮 kompilacj臋 kodu. Mo偶esz go u偶y膰, aby:
- Analizowa膰 kod: Sprawdza膰 struktur臋 kodu, identyfikowa膰 potencjalne problemy i wyodr臋bnia膰 informacje semantyczne.
- Transformowa膰 kod: Modyfikowa膰 istniej膮cy kod, dodawa膰 nowe funkcje lub refaktoryzowa膰 kod automatycznie.
- Generowa膰 kod: Tworzy膰 nowy kod od podstaw na podstawie szablon贸w lub innych danych wej艣ciowych.
To API jest niezb臋dne do budowy zaawansowanych narz臋dzi programistycznych, kt贸re poprawiaj膮 jako艣膰 kodu, automatyzuj膮 powtarzalne zadania i zwi臋kszaj膮 produktywno艣膰 programist贸w.
Zrozumienie Abstract Syntax Tree (AST)
AST to reprezentacja struktury twojego kodu w postaci drzewa. Ka偶dy w臋ze艂 w drzewie reprezentuje konstrukcj臋 sk艂adniow膮, tak膮 jak deklaracja zmiennej, wywo艂anie funkcji lub instrukcja sterowania przep艂ywem. TypeScript Compiler API udost臋pnia narz臋dzia do przechodzenia po AST, sprawdzania jego w臋z艂贸w i modyfikowania ich.
Rozwa偶my ten prosty kod TypeScript:
function greet(name: string): string {
return `Witaj, ${name}!`;
}
console.log(greet("艢wiecie"));
AST dla tego kodu b臋dzie reprezentowa膰 deklaracj臋 funkcji, instrukcj臋 return, literal szablonu, wywo艂anie console.log i inne elementy kodu. Wizualizacja AST mo偶e by膰 wyzwaniem, ale narz臋dzia takie jak AST explorer (astexplorer.net) mog膮 pom贸c. Narz臋dzia te pozwalaj膮 na wprowadzenie kodu i zobaczenie jego odpowiadaj膮cego AST w przyjaznym dla u偶ytkownika formacie. Korzystanie z AST Explorer pomo偶e Ci zrozumie膰 rodzaj struktury kodu, kt贸r膮 b臋dziesz manipulowa膰.
Kluczowe typy w臋z艂贸w AST
TypeScript Compiler API definiuje r贸偶ne typy w臋z艂贸w AST, z kt贸rych ka偶dy reprezentuje inn膮 konstrukcj臋 sk艂adniow膮. Oto kilka typowych typ贸w w臋z艂贸w:
- SourceFile: Reprezentuje ca艂y plik TypeScript.
- FunctionDeclaration: Reprezentuje definicj臋 funkcji.
- VariableDeclaration: Reprezentuje deklaracj臋 zmiennej.
- Identifier: Reprezentuje identyfikator (np. nazw臋 zmiennej, nazw臋 funkcji).
- StringLiteral: Reprezentuje literal ci膮gu znak贸w.
- CallExpression: Reprezentuje wywo艂anie funkcji.
- ReturnStatement: Reprezentuje instrukcj臋 return.
Ka偶dy typ w臋z艂a ma w艂a艣ciwo艣ci, kt贸re dostarczaj膮 informacji o odpowiadaj膮cym elemencie kodu. Na przyk艂ad w臋ze艂 `FunctionDeclaration` mo偶e mie膰 w艂a艣ciwo艣ci dla swojej nazwy, parametr贸w, typu zwracanego i tre艣ci.
Rozpocz臋cie pracy z Compiler API
Aby rozpocz膮膰 korzystanie z Compiler API, musisz zainstalowa膰 TypeScript i mie膰 podstawow膮 wiedz臋 na temat sk艂adni TypeScript. Oto prosty przyk艂ad, kt贸ry pokazuje, jak odczyta膰 plik TypeScript i wydrukowa膰 jego AST:
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);
Wyja艣nienie:
- Import modu艂贸w: Importuje modu艂 `typescript` i modu艂 `fs` do operacji na systemie plik贸w.
- Odczyt pliku 藕r贸d艂owego: Odczytuje zawarto艣膰 pliku TypeScript o nazwie `example.ts`. Musisz utworzy膰 plik `example.ts`, aby to zadzia艂a艂o.
- Create SourceFile: Tworzy obiekt `SourceFile`, kt贸ry reprezentuje korze艅 AST. Funkcja `ts.createSourceFile` analizuje kod 藕r贸d艂owy i generuje AST.
- Print AST: Definiuje rekurencyjn膮 funkcj臋 `printAST`, kt贸ra przechodzi przez AST i drukuje rodzaj ka偶dego w臋z艂a.
- Call printAST: Wywo艂uje `printAST`, aby rozpocz膮膰 drukowanie AST z korzenia w臋z艂a `SourceFile`.
Aby uruchomi膰 ten kod, zapisz go jako plik `.ts` (np. `ast-example.ts`), utw贸rz plik `example.ts` z kodem TypeScript, a nast臋pnie skompiluj i uruchom kod:
tsc ast-example.ts
node ast-example.js
Spowoduje to wydrukowanie AST pliku `example.ts` w konsoli. Dane wyj艣ciowe poka偶膮 hierarchi臋 w臋z艂贸w i ich typy. Na przyk艂ad mo偶e pokazywa膰 `FunctionDeclaration`, `Identifier`, `Block` i inne typy w臋z艂贸w.
Przechodzenie przez AST
Compiler API oferuje kilka sposob贸w na przechodzenie przez AST. Najprostszym z nich jest u偶ycie metody `forEachChild`, jak pokazano w poprzednim przyk艂adzie. Ta metoda odwiedza ka偶dy w臋ze艂 podrz臋dny danego w臋z艂a.
W przypadku bardziej z艂o偶onych scenariuszy przechodzenia mo偶na u偶y膰 wzorca `Visitor`. Wizytator to obiekt, kt贸ry definiuje metody, kt贸re maj膮 by膰 wywo艂ywane dla okre艣lonych typ贸w w臋z艂贸w. Pozwala to na dostosowanie procesu przechodzenia i wykonywanie dzia艂a艅 na podstawie typu w臋z艂a.
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(`Znaleziono identyfikator: ${node.text}`);
}
ts.forEachChild(node, n => this.visit(n));
}
}
const visitor = new IdentifierVisitor();
visitor.visit(sourceFile);
Wyja艣nienie:
- Klasa IdentifierVisitor: Definiuje klas臋 `IdentifierVisitor` z metod膮 `visit`.
- Metoda Visit: Metoda `visit` sprawdza, czy bie偶膮cy w臋ze艂 jest `Identifier`. Je艣li tak, drukuje tekst identyfikatora. Nast臋pnie rekurencyjnie wywo艂uje `ts.forEachChild`, aby odwiedzi膰 w臋z艂y podrz臋dne.
- Utw贸rz wizytatora: Tworzy instancj臋 `IdentifierVisitor`.
- Rozpocznij przechodzenie: Wywo艂uje metod臋 `visit` na `SourceFile`, aby rozpocz膮膰 przechodzenie.
Ten przyk艂ad pokazuje, jak znale藕膰 wszystkie identyfikatory w AST. Mo偶esz dostosowa膰 ten wzorzec, aby znale藕膰 inne typy w臋z艂贸w i wykonywa膰 r贸偶ne czynno艣ci.
Transformowanie AST
Prawdziwa moc Compiler API tkwi w jego zdolno艣ci do transformowania AST. Mo偶esz modyfikowa膰 AST, aby zmieni膰 struktur臋 i zachowanie swojego kodu. Jest to podstawa narz臋dzi do refaktoryzacji kodu, generator贸w kodu i innych zaawansowanych narz臋dzi.
Aby przekszta艂ci膰 AST, musisz u偶y膰 funkcji `ts.transform`. Ta funkcja przyjmuje plik `SourceFile` i list臋 funkcji `TransformerFactory`. `TransformerFactory` to funkcja, kt贸ra przyjmuje `TransformationContext` i zwraca funkcj臋 `Transformer`. Funkcja `Transformer` jest odpowiedzialna za odwiedzanie i transformowanie w臋z艂贸w w AST.
Oto prosty przyk艂ad, kt贸ry pokazuje, jak doda膰 komentarz na pocz膮tku pliku TypeScript:
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)) {
// Utw贸rz komentarz wiod膮cy
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" Ten plik zosta艂 automatycznie przekszta艂cony ",
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);
Wyja艣nienie:
- TransformerFactory: Definiuje funkcj臋 `TransformerFactory`, kt贸ra zwraca funkcj臋 `Transformer`.
- Transformer: Funkcja `Transformer` sprawdza, czy bie偶膮cy w臋ze艂 jest `SourceFile`. Je艣li tak, dodaje wiod膮cy komentarz do w臋z艂a za pomoc膮 `ts.addSyntheticLeadingComment`.
- ts.transform: Wywo艂uje `ts.transform`, aby zastosowa膰 transformacj臋 do `SourceFile`.
- Drukarka: Tworzy obiekt `Printer` do generowania kodu z przekszta艂conego AST.
- Drukuj i zapisz: Drukuje przekszta艂cony kod i zapisuje go w nowym pliku o nazwie `example.transformed.ts`.
Ten przyk艂ad pokazuje prost膮 transformacj臋, ale mo偶esz u偶y膰 tego samego wzorca do wykonywania bardziej z艂o偶onych transformacji, takich jak refaktoryzacja kodu, dodawanie instrukcji rejestrowania lub generowanie dokumentacji.
Zaawansowane techniki transformacji
Oto kilka zaawansowanych technik transformacji, kt贸rych mo偶esz u偶y膰 z Compiler API:
- Tworzenie nowych w臋z艂贸w: U偶yj funkcji `ts.createXXX`, aby utworzy膰 nowe w臋z艂y AST. Na przyk艂ad `ts.createVariableDeclaration` tworzy nowy w臋ze艂 deklaracji zmiennej.
- Zast臋powanie w臋z艂贸w: Zast膮p istniej膮ce w臋z艂y nowymi w臋z艂ami za pomoc膮 funkcji `ts.visitEachChild`.
- Dodawanie w臋z艂贸w: Dodaj nowe w臋z艂y do AST za pomoc膮 funkcji `ts.updateXXX`. Na przyk艂ad `ts.updateBlock` aktualizuje instrukcj臋 bloku nowymi instrukcjami.
- Usuwanie w臋z艂贸w: Usu艅 w臋z艂y z AST, zwracaj膮c `undefined` z funkcji transformer.
Generacja kodu
Po przekszta艂ceniu AST musisz wygenerowa膰 z niego kod. Compiler API udost臋pnia w tym celu obiekt `Printer`. `Printer` przyjmuje AST i generuje reprezentacj臋 ci膮gu znak贸w kodu.
Funkcja `ts.createPrinter` tworzy obiekt `Printer`. Mo偶esz skonfigurowa膰 drukark臋 za pomoc膮 r贸偶nych opcji, takich jak znak nowej linii do u偶ycia i czy emitowa膰 komentarze.
Metoda `printer.printFile` przyjmuje plik `SourceFile` i zwraca reprezentacj臋 ci膮gu znak贸w kodu. Nast臋pnie mo偶esz zapisa膰 ten ci膮g znak贸w do pliku.
Praktyczne zastosowania Compiler API
TypeScript Compiler API ma liczne praktyczne zastosowania w tworzeniu oprogramowania. Oto kilka przyk艂ad贸w:
- Linters: Tw贸rz niestandardowe linters, aby wymusi膰 standardy kodowania i zidentyfikowa膰 potencjalne problemy w kodzie.
- Formatery kodu: Tw贸rz formatery kodu, aby automatycznie formatowa膰 kod zgodnie z okre艣lonym stylem.
- Analizatory statyczne: Opracowuj analizatory statyczne do wykrywania b艂臋d贸w, luk w zabezpieczeniach i w膮skich garde艂 wydajno艣ci w kodzie.
- Generatory kodu: Generuj kod z szablon贸w lub innych danych wej艣ciowych, automatyzuj膮c powtarzalne zadania i redukuj膮c kod szablonowy. Na przyk艂ad generowanie klient贸w API lub schemat贸w bazy danych z pliku opisu.
- Narz臋dzia do refaktoryzacji: Tw贸rz narz臋dzia do refaktoryzacji, aby automatycznie zmienia膰 nazwy zmiennych, wyodr臋bnia膰 funkcje lub przenosi膰 kod mi臋dzy plikami.
- Internacjonalizacja (i18n) Automatyzacja: Automatycznie wyodr臋bniaj ci膮gi znak贸w do przet艂umaczenia z kodu TypeScript i generuj pliki lokalizacyjne dla r贸偶nych j臋zyk贸w. Na przyk艂ad narz臋dzie mo偶e skanowa膰 kod w poszukiwaniu ci膮g贸w przekazywanych do funkcji `translate()` i automatycznie dodawa膰 je do pliku zasob贸w t艂umaczeniowych.
Przyk艂ad: Budowanie prostego linera
Stw贸rzmy prosty linter, kt贸ry sprawdza nieu偶ywane zmienne w kodzie TypeScript. Ten linter zidentyfikuje zmienne, kt贸re s膮 zadeklarowane, ale nigdy nie u偶ywane.
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("Nieu偶ywane zmienne:");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("Nie znaleziono nieu偶ywanych zmiennych.");
}
Wyja艣nienie:
- Funkcja findUnusedVariables: Definiuje funkcj臋 `findUnusedVariables`, kt贸ra jako dane wej艣ciowe przyjmuje `SourceFile`.
- Zestaw usedVariables: Tworzy `Set`, aby przechowywa膰 nazwy u偶ywanych zmiennych.
- Funkcja visit: Definiuje rekurencyjn膮 funkcj臋 `visit`, kt贸ra przechodzi przez AST i dodaje nazwy wszystkich identyfikator贸w do zestawu `usedVariables`.
- Funkcja checkVariableDeclaration: Definiuje rekurencyjn膮 funkcj臋 `checkVariableDeclaration`, kt贸ra sprawdza, czy deklaracja zmiennej jest nieu偶ywana. Je艣li tak, dodaje nazw臋 zmiennej do tablicy `unusedVariables`.
- Zwr贸膰 unusedVariables: Zwraca tablic臋 zawieraj膮c膮 nazwy wszelkich nieu偶ywanych zmiennych.
- Dane wyj艣ciowe: Drukuje nieu偶ywane zmienne w konsoli.
Ten przyk艂ad pokazuje prosty linter. Mo偶esz go rozszerzy膰, aby sprawdza膰 inne standardy kodowania i identyfikowa膰 inne potencjalne problemy w kodzie. Na przyk艂ad mo偶esz sprawdza膰 nieu偶ywane importy, zbyt z艂o偶one funkcje lub potencjalne luki w zabezpieczeniach. Kluczem jest zrozumienie, jak przechodzi膰 przez AST i identyfikowa膰 okre艣lone typy w臋z艂贸w, kt贸re Ci臋 interesuj膮.
Najlepsze praktyki i uwagi
- Zrozumienie AST: Po艣wi臋膰 czas na zrozumienie struktury AST. U偶yj narz臋dzi takich jak AST explorer, aby zwizualizowa膰 AST swojego kodu.
- U偶ywaj stra偶nik贸w typ贸w: U偶ywaj stra偶nik贸w typ贸w (`ts.isXXX`), aby upewni膰 si臋, 偶e pracujesz z prawid艂owymi typami w臋z艂贸w.
- Rozwa偶 wydajno艣膰: Transformacje AST mog膮 by膰 kosztowne obliczeniowo. Zoptymalizuj sw贸j kod, aby zminimalizowa膰 liczb臋 odwiedzanych i transformowanych w臋z艂贸w.
- Obs艂uga b艂臋d贸w: Obs艂uguj b艂臋dy w spos贸b elegancki. Compiler API mo偶e zg艂asza膰 wyj膮tki, je艣li spr贸bujesz wykona膰 nieprawid艂owe operacje na AST.
- Testuj dok艂adnie: Testuj swoje transformacje dok艂adnie, aby upewni膰 si臋, 偶e daj膮 po偶膮dane rezultaty i nie wprowadzaj膮 nowych b艂臋d贸w.
- U偶ywaj istniej膮cych bibliotek: Rozwa偶 u偶ycie istniej膮cych bibliotek, kt贸re zapewniaj膮 bardziej zaawansowane abstrakcje nad Compiler API. Biblioteki te mog膮 upro艣ci膰 typowe zadania i zmniejszy膰 ilo艣膰 kodu, kt贸ry musisz napisa膰. Przyk艂ady obejmuj膮 `ts-morph` i `typescript-eslint`.
Wniosek
TypeScript Compiler API to pot臋偶ne narz臋dzie do budowania zaawansowanych narz臋dzi programistycznych. Zrozumienie, jak pracowa膰 z AST, pozwala na tworzenie linters, formatter贸w kodu, analizator贸w statycznych i innych narz臋dzi, kt贸re poprawiaj膮 jako艣膰 kodu, automatyzuj膮 powtarzalne zadania i zwi臋kszaj膮 produktywno艣膰 programist贸w. Chocia偶 API mo偶e by膰 z艂o偶one, korzy艣ci z jego opanowania s膮 znacz膮ce. Ten kompleksowy przewodnik stanowi podstaw臋 do eksploracji i skutecznego wykorzystania Compiler API w swoich projektach. Pami臋taj, aby wykorzystywa膰 narz臋dzia takie jak AST Explorer, dok艂adnie obs艂ugiwa膰 typy w臋z艂贸w i dok艂adnie testowa膰 swoje transformacje. Dzi臋ki praktyce i zaanga偶owaniu mo偶esz odblokowa膰 pe艂ny potencja艂 TypeScript Compiler API i budowa膰 innowacyjne rozwi膮zania dla krajobrazu tworzenia oprogramowania.
Dalsza eksploracja:
- Dokumentacja TypeScript Compiler API: [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/)
- biblioteka ts-morph: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)