En omfattande guide till TypeScript Compiler API, som tÀcker Abstract Syntax Trees (AST), kodanalys, transformation och generering.
TypeScript Compiler API: BemÀstra AST-manipulation och kodtransformation
TypeScript Compiler API erbjuder ett kraftfullt grÀnssnitt för att analysera, manipulera och generera TypeScript- och JavaScript-kod. KÀrnan i detta Àr Abstract Syntax Tree (AST), en strukturerad representation av din kÀllkod. Att förstÄ hur man arbetar med AST lÄser upp möjligheter för att bygga avancerade verktyg, sÄsom linters, kodformaterare, statiska analysverktyg och anpassade kodgeneratorer.
Vad Àr TypeScript Compiler API?
TypeScript Compiler API Àr en uppsÀttning TypeScript-grÀnssnitt och funktioner som exponerar den interna funktionen hos TypeScript-kompilatorn. Det lÄter utvecklare programmatiskt interagera med kompileringsprocessen, bortom att bara kompilera kod. Du kan anvÀnda det för att:
- Analysera kod: Inspektera kodstruktur, identifiera potentiella problem och extrahera semantisk information.
- Transformera kod: Modifiera befintlig kod, lÀgga till nya funktioner eller refaktorera kod automatiskt.
- Generera kod: Skapa ny kod frÄn grunden baserat pÄ mallar eller annan input.
Detta API Àr avgörande för att bygga sofistikerade utvecklingsverktyg som förbÀttrar kodkvaliteten, automatiserar repetitiva uppgifter och ökar utvecklarnas produktivitet.
FörstÄ Abstract Syntax Tree (AST)
AST Àr en trÀdliknande representation av din kods struktur. Varje nod i trÀdet representerar ett syntaktiskt konstrukt, sÄsom en variabeldeklaration, ett funktionsanrop eller ett kontrollflödesuttryck. TypeScript Compiler API tillhandahÄller verktyg för att traversera AST, inspektera dess noder och modifiera dem.
Betrakta denna enkla TypeScript-kod:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
AST för denna kod skulle representera funktionsdeklarationen, retur-satsen, mall-strÀngen, console.log-anropet och andra delar av koden. Att visualisera AST kan vara utmanande, men verktyg som AST explorer (astexplorer.net) kan hjÀlpa. Dessa verktyg lÄter dig mata in kod och se dess motsvarande AST i ett anvÀndarvÀnligt format. Att anvÀnda AST Explorer hjÀlper dig att förstÄ vilken typ av kodstruktur du kommer att manipulera.
Viktiga AST-nodtyper
TypeScript Compiler API definierar olika AST-nodtyper, var och en representerar ett annat syntaktiskt konstrukt. HÀr Àr nÄgra vanliga nodtyper:
- SourceFile: Representerar en hel TypeScript-fil.
- FunctionDeclaration: Representerar en funktionsdefinition.
- VariableDeclaration: Representerar en variabeldeklaration.
- Identifier: Representerar en identifierare (t.ex. variabelnamn, funktionsnamn).
- StringLiteral: Representerar en strÀngliteral.
- CallExpression: Representerar ett funktionsanrop.
- ReturnStatement: Representerar en retursats.
Varje nodtyp har egenskaper som ger information om motsvarande kodkomponent. Till exempel kan en `FunctionDeclaration`-nod ha egenskaper för sitt namn, parametrar, returtyp och kropp.
Komma igÄng med Compiler API
För att börja anvÀnda Compiler API behöver du installera TypeScript och ha en grundlÀggande förstÄelse för TypeScript-syntax. HÀr Àr ett enkelt exempel som visar hur man lÀser en TypeScript-fil och skriver ut dess 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, // MÄl-ECMAScript-version
true // SetParentNodes: true för att behÄlla förÀldreferenser i 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);
Förklaring:
- Importera moduler: Importerar `typescript`-modulen och `fs`-modulen för filsystemoperationer.
- LÀs kÀllfil: LÀser innehÄllet i en TypeScript-fil med namnet `example.ts`. Du mÄste skapa en `example.ts`-fil för att detta ska fungera.
- Skapa SourceFile: Skapar ett `SourceFile`-objekt, som representerar roten till AST. `ts.createSourceFile`-funktionen parsar kÀllkoden och genererar AST.
- Skriv ut AST: Definierar en rekursiv funktion `printAST` som traverserar AST och skriver ut typen av varje nod.
- Anropa printAST: Anropar `printAST` för att börja skriva ut AST frÄn rot-noden `SourceFile`.
För att köra denna kod, spara den som en `.ts`-fil (t.ex. `ast-example.ts`), skapa en `example.ts`-fil med lite TypeScript-kod, och kompilera och kör sedan koden:
tsc ast-example.ts
node ast-example.js
Detta kommer att skriva ut AST för din `example.ts`-fil till konsolen. Utmatningen visar hierarkin av noder och deras typer. Till exempel kan det visa `FunctionDeclaration`, `Identifier`, `Block` och andra nodtyper.
Traversera AST
Compiler API tillhandahÄller flera sÀtt att traversera AST. Det enklaste Àr att anvÀnda `forEachChild`-metoden, som visades i det föregÄende exemplet. Denna metod besöker varje barnnod till en given nod.
För mer komplexa traverseringsscenarier kan du anvÀnda ett `Visitor`-mönster. En visitor Àr ett objekt som definierar metoder som ska anropas för specifika nodtyper. Detta gör att du kan anpassa traverseringsprocessen och utföra ÄtgÀrder baserat pÄ nodtypen.
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);
Förklaring:
- IdentifierVisitor-klass: Definierar en klass `IdentifierVisitor` med en `visit`-metod.
- Besök-metod: `visit`-metoden kontrollerar om den aktuella noden Àr en `Identifier`. Om sÄ Àr fallet skriver den ut identifierarens text. Den anropar sedan rekursivt `ts.forEachChild` för att besöka barnnoderna.
- Skapa visitor: Skapar en instans av `IdentifierVisitor`.
- Starta traversering: Anropar `visit`-metoden pÄ `SourceFile` för att starta traverseringen.
Detta exempel visar hur man hittar alla identifierare i AST. Du kan anpassa detta mönster för att hitta andra nodtyper och utföra olika ÄtgÀrder.
Transformera AST
Den verkliga kraften i Compiler API ligger i dess förmÄga att transformera AST. Du kan modifiera AST för att Àndra strukturen och beteendet hos din kod. Detta Àr grunden för kodrefaktoreringsverktyg, kodgeneratorer och andra avancerade verktyg.
För att transformera AST behöver du anvÀnda funktionen `ts.transform`. Denna funktion tar en `SourceFile` och en lista med `TransformerFactory`-funktioner. En `TransformerFactory` Àr en funktion som tar ett `TransformationContext` och returnerar en `Transformer`-funktion. `Transformer`-funktionen ansvarar för att besöka och transformera noder i AST.
HÀr Àr ett enkelt exempel som visar hur man lÀgger till en kommentar i början av en TypeScript-fil:
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)) {
// Skapa en ledande kommentar
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" This file was automatically transformed ",
true // har slutradstecken
);
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);
Förklaring:
- TransformerFactory: Definierar en `TransformerFactory`-funktion som returnerar en `Transformer`-funktion.
- Transformer: `Transformer`-funktionen kontrollerar om den aktuella noden Àr en `SourceFile`. Om sÄ Àr fallet lÀgger den till en ledande kommentar till noden med hjÀlp av `ts.addSyntheticLeadingComment`.
- ts.transform: Anropar `ts.transform` för att tillÀmpa transformationen pÄ `SourceFile`.
- Printer: Skapar ett `Printer`-objekt för att generera kod frÄn den transformerade AST.
- Skriv ut och spara: Skriver ut den transformerade koden och sparar den i en ny fil med namnet `example.transformed.ts`.
Detta exempel visar en enkel transformation, men du kan anvÀnda samma mönster för att utföra mer komplexa transformationer, sÄsom refaktorering av kod, lÀggning till loggningssatser eller generering av dokumentation.
Avancerade transformationstekniker
HÀr Àr nÄgra avancerade transformationstekniker du kan anvÀnda med Compiler API:
- Skapa nya noder: AnvÀnd `ts.createXXX`-funktionerna för att skapa nya AST-noder. Till exempel skapar `ts.createVariableDeclaration` en ny variabeldeklarationsnod.
- Byta ut noder: Byt ut befintliga noder mot nya noder med hjÀlp av `ts.visitEachChild`-funktionen.
- LÀgga till noder: LÀgg till nya noder i AST med hjÀlp av `ts.updateXXX`-funktionerna. Till exempel uppdaterar `ts.updateBlock` en block-sats med nya satser.
- Ta bort noder: Ta bort noder frÄn AST genom att returnera `undefined` frÄn transformer-funktionen.
Kodgenerering
Efter att ha transformerat AST mÄste du generera kod frÄn den. Compiler API tillhandahÄller ett `Printer`-objekt för detta ÀndamÄl. `Printer` tar en AST och genererar en strÀngrepresentation av koden.
Funktionen `ts.createPrinter` skapar ett `Printer`-objekt. Du kan konfigurera skrivaren med olika alternativ, sÄsom radbrytningstecknet som ska anvÀndas och om kommentarer ska inkluderas.
Metoden `printer.printFile` tar en `SourceFile` och returnerar en strÀngrepresentation av koden. Du kan sedan skriva denna strÀng till en fil.
Praktiska tillÀmpningar av Compiler API
TypeScript Compiler API har mÄnga praktiska tillÀmpningar inom mjukvaruutveckling. HÀr Àr nÄgra exempel:
- Linters: Bygg anpassade linters för att upprÀtthÄlla kodstandarder och identifiera potentiella problem i din kod.
- Kodformaterare: Skapa kodformaterare för att automatiskt formatera din kod enligt en specifik stilguide.
- Statiska analysverktyg: Utveckla statiska analysverktyg för att upptÀcka buggar, sÀkerhetsbrister och prestandaflaskhalsar i din kod.
- Kodgeneratorer: Generera kod frÄn mallar eller annan input, automatisera repetitiva uppgifter och minska boilerplate-kod. Till exempel kan en generator skapa API-klienter eller databasscheman frÄn en beskrivningsfil.
- Refaktoreringsverktyg: Bygg refaktoreringsverktyg för att automatiskt byta namn pÄ variabler, extrahera funktioner eller flytta kod mellan filer.
- Automatisering av internationalisering (i18n): Extrahera automatiskt översÀttningsbara strÀngar frÄn din TypeScript-kod och generera lokaliseringsfiler för olika sprÄk. Till exempel kan ett verktyg skanna kod för strÀngar som skickas till en `translate()`-funktion och automatiskt lÀgga till dem i en översÀttningsresursfil.
Exempel: Bygga en enkel Linter
LÄt oss skapa en enkel linter som kontrollerar oanvÀnda variabler i TypeScript-kod. Denna linter kommer att identifiera variabler som deklareras men aldrig anvÀnds.
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("OanvÀnda variabler:");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("Inga oanvÀnda variabler hittades.");
}
Förklaring:
- findUnusedVariables-funktion: Definierar en funktion `findUnusedVariables` som tar en `SourceFile` som input.
- usedVariables-mÀngd: Skapar en `Set` för att lagra namnen pÄ anvÀnda variabler.
- visit-funktion: Definierar en rekursiv funktion `visit` som traverserar AST och lÀgger till namnen pÄ alla identifierare i `usedVariables`-mÀngden.
- checkVariableDeclaration-funktion: Definierar en rekursiv funktion `checkVariableDeclaration` som kontrollerar om en variabeldeklaration Àr oanvÀnd. Om sÄ Àr fallet lÀggs variabelnamnet till i arrayen `unusedVariables`.
- Returnera unusedVariables: Returnerar en array som innehÄller namnen pÄ eventuella oanvÀnda variabler.
- Utmatning: Skriver ut de oanvÀnda variablerna till konsolen.
Detta exempel visar en enkel linter. Du kan utöka den för att kontrollera andra kodstandarder och identifiera andra potentiella problem i din kod. Till exempel kan du kontrollera oanvÀnda importer, alltför komplexa funktioner eller potentiella sÀkerhetsproblem. Nyckeln Àr att förstÄ hur man traverserar AST och identifierar de specifika nodtyper du Àr intresserad av.
BÀsta metoder och övervÀganden
- FörstÄ AST: LÀgg tid pÄ att förstÄ AST:s struktur. AnvÀnd verktyg som AST explorer för att visualisera din kods AST.
- AnvÀnd typkontroller: AnvÀnd typkontroller (`ts.isXXX`) för att sÀkerstÀlla att du arbetar med rÀtt nodtyper.
- ĂvervĂ€g prestanda: AST-transformationer kan vara berĂ€kningsmĂ€ssigt dyra. Optimera din kod för att minimera antalet noder du besöker och transformerar.
- Hantera fel: Hantera fel pÄ ett smidigt sÀtt. Compiler API kan kasta undantag om du försöker utföra ogiltiga operationer pÄ AST.
- Testa noggrant: Testa dina transformationer noggrant för att sÀkerstÀlla att de ger önskade resultat och inte introducerar nya buggar.
- AnvĂ€nd befintliga bibliotek: ĂvervĂ€g att anvĂ€nda befintliga bibliotek som tillhandahĂ„ller abstraktioner pĂ„ högre nivĂ„ över Compiler API. Dessa bibliotek kan förenkla vanliga uppgifter och minska mĂ€ngden kod du behöver skriva. Exempel inkluderar `ts-morph` och `typescript-eslint`.
Slutsats
TypeScript Compiler API Ă€r ett kraftfullt verktyg för att bygga avancerade utvecklingsverktyg. Genom att förstĂ„ hur man arbetar med AST kan du skapa linters, kodformaterare, statiska analysverktyg och andra verktyg som förbĂ€ttrar kodkvaliteten, automatiserar repetitiva uppgifter och ökar utvecklarnas produktivitet. Ăven om API:et kan vara komplext, Ă€r fördelarna med att bemĂ€stra det betydande. Denna omfattande guide ger en grund för att utforska och effektivt utnyttja Compiler API i dina projekt. Kom ihĂ„g att anvĂ€nda verktyg som AST Explorer, hantera nodtyper noggrant och testa dina transformationer grundligt. Med övning och engagemang kan du lĂ„sa upp den fulla potentialen i TypeScript Compiler API och bygga innovativa lösningar för mjukvaruutvecklingslandskapet.
Vidare utforskning:
- TypeScript Compiler API-dokumentation: [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-biblioteket: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)