TypeScript Derleyici API'sine kapsamlı bir rehber: Soyut Sözdizimi Ağaçları (AST), kod analizi, dönüşümü ve uluslararası geliştiriciler için kod üretimi.
TypeScript Derleyici API'si: AST Manipülasyonu ve Kod Dönüşümünde Ustalaşma
TypeScript Derleyici API'si, TypeScript ve JavaScript kodunu analiz etmek, manipüle etmek ve üretmek için güçlü bir arayüz sağlar. Merkezinde, kaynak kodunuzun yapılandırılmış bir temsili olan Soyut Sözdizimi Ağacı (AST) bulunur. AST ile nasıl çalışacağınızı anlamak, linters, kod formatlayıcıları, statik analizciler ve özel kod üreteçleri gibi gelişmiş araçlar oluşturma yeteneklerinin kilidini açar.
TypeScript Derleyici API'si Nedir?
TypeScript Derleyici API'si, TypeScript derleyicisinin iç işleyişini ortaya koyan bir dizi TypeScript arayüzü ve fonksiyonudur. Geliştiricilerin derleme süreciyle programatik olarak etkileşim kurmasını sağlayarak, sadece kodu derlemenin ötesine geçer. Şunlar için kullanabilirsiniz:
- Kodu Analiz Etme: Kod yapısını inceleyin, potansiyel sorunları belirleyin ve anlamsal bilgi çıkarın.
- Kodu Dönüştürme: Mevcut kodu değiştirin, yeni özellikler ekleyin veya kodu otomatik olarak yeniden düzenleyin.
- Kod Üretme: Şablonlara veya diğer girdilere dayalı olarak sıfırdan yeni kod oluşturun.
Bu API, kod kalitesini artıran, tekrarlayan görevleri otomatikleştiren ve geliştirici verimliliğini artıran gelişmiş geliştirme araçları oluşturmak için hayati öneme sahiptir.
Soyut Sözdizimi Ağacını (AST) Anlamak
AST, kodunuzun yapısının ağaç benzeri bir temsilidir. Ağaçtaki her düğüm, bir değişken bildirimi, bir fonksiyon çağrısı veya bir kontrol akışı ifadesi gibi bir sözdizimsel yapıyı temsil eder. TypeScript Derleyici API'si, AST'yi gezmek, düğümlerini incelemek ve değiştirmek için araçlar sağlar.
Şu basit TypeScript kodunu göz önünde bulundurun:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
Bu kodun AST'si, fonksiyon bildirimini, return ifadesini, şablon değişmezini, console.log çağrısını ve kodun diğer öğelerini temsil eder. AST'yi görselleştirmek zor olabilir, ancak AST explorer (astexplorer.net) gibi araçlar yardımcı olabilir. Bu araçlar, kod girmenizi ve karşılık gelen AST'yi kullanıcı dostu bir biçimde görmenizi sağlar. AST Explorer'ı kullanmak, manipüle edeceğiniz kod yapısının türünü anlamanıza yardımcı olacaktır.
Başlıca AST Düğüm Tipleri
TypeScript Derleyici API'si, her biri farklı bir sözdizimsel yapıyı temsil eden çeşitli AST düğüm tipleri tanımlar. İşte bazı yaygın düğüm tipleri:
- SourceFile: Bir TypeScript dosyasının tamamını temsil eder.
- FunctionDeclaration: Bir fonksiyon tanımını temsil eder.
- VariableDeclaration: Bir değişken bildirimini temsil eder.
- Identifier: Bir tanımlayıcıyı (örneğin, değişken adı, fonksiyon adı) temsil eder.
- StringLiteral: Bir dize değişmezini temsil eder.
- CallExpression: Bir fonksiyon çağrısını temsil eder.
- ReturnStatement: Bir return ifadesini temsil eder.
Her düğüm tipi, karşılık gelen kod öğesi hakkında bilgi sağlayan özelliklere sahiptir. Örneğin, bir `FunctionDeclaration` düğümü, adı, parametreleri, dönüş tipi ve gövdesi için özelliklere sahip olabilir.
Derleyici API'si ile Başlarken
Derleyici API'sini kullanmaya başlamak için TypeScript'i yüklemeniz ve TypeScript sözdizimi hakkında temel bir anlayışa sahip olmanız gerekir. İşte bir TypeScript dosyasını okuyup AST'sini yazdırmayı gösteren basit bir örnek:
import * as ts from "typescript";
import *s 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);
Açıklama:
- Modülleri İçe Aktarma: Dosya sistemi işlemleri için `typescript` modülünü ve `fs` modülünü içe aktarır.
- Kaynak Dosyayı Okuma: `example.ts` adlı bir TypeScript dosyasının içeriğini okur. Bunun çalışması için bir `example.ts` dosyası oluşturmanız gerekecektir.
- SourceFile Oluşturma: AST'nin kökünü temsil eden bir `SourceFile` nesnesi oluşturur. `ts.createSourceFile` fonksiyonu kaynak kodu ayrıştırır ve AST'yi oluşturur.
- AST'yi Yazdırma: AST'yi gezen ve her düğümün türünü yazdıran özyinelemeli bir `printAST` fonksiyonu tanımlar.
- printAST'yi Çağırma: Kök `SourceFile` düğümünden AST'yi yazdırmaya başlamak için `printAST`'i çağırır.
Bu kodu çalıştırmak için, onu bir `.ts` dosyası olarak kaydedin (örn. `ast-example.ts`), içine biraz TypeScript kodu olan bir `example.ts` dosyası oluşturun ve ardından kodu derleyip çalıştırın:
tsc ast-example.ts
node ast-example.js
Bu, `example.ts` dosyanızın AST'sini konsola yazdıracaktır. Çıktı, düğümlerin hiyerarşisini ve türlerini gösterecektir. Örneğin, `FunctionDeclaration`, `Identifier`, `Block` ve diğer düğüm türlerini gösterebilir.
AST'yi Gezme
Derleyici API'si, AST'yi gezmek için birkaç yol sunar. En basiti, önceki örnekte gösterildiği gibi `forEachChild` yöntemini kullanmaktır. Bu yöntem, belirli bir düğümün her alt düğümünü ziyaret eder.
Daha karmaşık gezinme senaryoları için bir `Visitor` deseni kullanabilirsiniz. Bir ziyaretçi, belirli düğüm tipleri için çağrılacak yöntemleri tanımlayan bir nesnedir. Bu, gezinme sürecini özelleştirmenize ve düğüm tipine göre eylemler gerçekleştirmenize olanak tanır.
import * as ts from "typescript";
import *s 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);
Açıklama:
- IdentifierVisitor Sınıfı: Bir `visit` yöntemi olan `IdentifierVisitor` adlı bir sınıf tanımlar.
- Visit Yöntemi: `visit` yöntemi, mevcut düğümün bir `Identifier` olup olmadığını kontrol eder. Eğer öyleyse, tanımlayıcının metnini yazdırır. Ardından, alt düğümleri ziyaret etmek için özyinelemeli olarak `ts.forEachChild`'ı çağırır.
- Ziyaretçi Oluşturma: `IdentifierVisitor` sınıfından bir örnek oluşturur.
- Gezinmeyi Başlatma: Gezinmeyi başlatmak için `SourceFile` üzerinde `visit` yöntemini çağırır.
Bu örnek, AST'deki tüm tanımlayıcıları nasıl bulacağınızı gösterir. Bu deseni diğer düğüm tiplerini bulmak ve farklı eylemler gerçekleştirmek için uyarlayabilirsiniz.
AST'yi Dönüştürme
Derleyici API'sinin gerçek gücü, AST'yi dönüştürme yeteneğinde yatar. Kodunuzun yapısını ve davranışını değiştirmek için AST'yi modifiye edebilirsiniz. Bu, kod yeniden düzenleme araçları, kod üreteçleri ve diğer gelişmiş araçlar için temel oluşturur.
AST'yi dönüştürmek için `ts.transform` fonksiyonunu kullanmanız gerekir. Bu fonksiyon bir `SourceFile` ve bir `TransformerFactory` fonksiyonları listesi alır. Bir `TransformerFactory`, bir `TransformationContext` alan ve bir `Transformer` fonksiyonu döndüren bir fonksiyondur. `Transformer` fonksiyonu, AST'deki düğümleri ziyaret etmekten ve dönüştürmekten sorumludur.
İşte bir TypeScript dosyasının başına nasıl yorum ekleneceğini gösteren basit bir örnek:
import * as ts from "typescript";
import *s 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);
Açıklama:
- TransformerFactory: Bir `Transformer` fonksiyonu döndüren bir `TransformerFactory` fonksiyonu tanımlar.
- Transformer: `Transformer` fonksiyonu, mevcut düğümün bir `SourceFile` olup olmadığını kontrol eder. Eğer öyleyse, `ts.addSyntheticLeadingComment` kullanarak düğüme önde gelen bir yorum ekler.
- ts.transform: Dönüşümü `SourceFile` üzerine uygulamak için `ts.transform`'u çağırır.
- Yazıcı: Dönüştürülmüş AST'den kod üretmek için bir `Printer` nesnesi oluşturur.
- Yazdırma ve Yazma: Dönüştürülmüş kodu yazdırır ve `example.transformed.ts` adlı yeni bir dosyaya yazar.
Bu örnek basit bir dönüşümü gösterir, ancak aynı deseni kod yeniden düzenleme, günlükleme ifadeleri ekleme veya dokümantasyon oluşturma gibi daha karmaşık dönüşümleri gerçekleştirmek için kullanabilirsiniz.
Gelişmiş Dönüşüm Teknikleri
İşte Derleyici API'si ile kullanabileceğiniz bazı gelişmiş dönüşüm teknikleri:
- Yeni Düğümler Oluşturma: Yeni AST düğümleri oluşturmak için `ts.createXXX` fonksiyonlarını kullanın. Örneğin, `ts.createVariableDeclaration` yeni bir değişken bildirim düğümü oluşturur.
- Düğümleri Değiştirme: Mevcut düğümleri, `ts.visitEachChild` fonksiyonunu kullanarak yeni düğümlerle değiştirin.
- Düğüm Ekleme: `ts.updateXXX` fonksiyonlarını kullanarak AST'ye yeni düğümler ekleyin. Örneğin, `ts.updateBlock` bir blok ifadesini yeni ifadelerle günceller.
- Düğümleri Kaldırma: Dönüştürücü fonksiyonundan `undefined` döndürerek düğümleri AST'den kaldırın.
Kod Üretimi
AST'yi dönüştürdükten sonra, ondan kod üretmeniz gerekir. Derleyici API'si bu amaç için bir `Printer` nesnesi sağlar. `Printer` bir AST alır ve kodun bir dize temsilini oluşturur.
`ts.createPrinter` fonksiyonu bir `Printer` nesnesi oluşturur. Kullanılacak yeni satır karakteri ve yorumları yayınlayıp yayınlamayacağı gibi çeşitli seçeneklerle yazıcıyı yapılandırabilirsiniz.
`printer.printFile` yöntemi bir `SourceFile` alır ve kodun bir dize temsilini döndürür. Daha sonra bu dizeyi bir dosyaya yazabilirsiniz.
Derleyici API'sinin Pratik Uygulamaları
TypeScript Derleyici API'sinin yazılım geliştirmede sayısız pratik uygulaması vardır. İşte birkaç örnek:
- Linters: Kodlama standartlarını uygulamak ve kodunuzdaki olası sorunları belirlemek için özel linters oluşturun.
- Kod Formatlayıcıları: Kodunuzu belirli bir stil rehberine göre otomatik olarak biçimlendirmek için kod formatlayıcıları oluşturun.
- Statik Analizciler: Kodunuzdaki hataları, güvenlik açıklarını ve performans darboğazlarını tespit etmek için statik analizciler geliştirin.
- Kod Üreteçleri: Şablonlardan veya diğer girdilerden kod üretin, tekrarlayan görevleri otomatikleştirin ve hazır kod miktarını azaltın. Örneğin, bir açıklama dosyasından API istemcileri veya veritabanı şemaları üretmek.
- Yeniden Düzenleme Araçları: Değişkenleri otomatik olarak yeniden adlandırmak, fonksiyonları çıkarmak veya kodu dosyalar arasında taşımak için yeniden düzenleme araçları oluşturun.
- Uluslararasılaşma (i18n) Otomasyonu: TypeScript kodunuzdan çevrilebilir dizeleri otomatik olarak çıkarın ve farklı diller için yerelleştirme dosyaları oluşturun. Örneğin, bir araç, `translate()` fonksiyonuna geçirilen dizeleri tarayabilir ve bunları otomatik olarak bir çeviri kaynak dosyasına ekleyebilir.
Örnek: Basit Bir Linter Oluşturma
TypeScript kodunda kullanılmayan değişkenleri kontrol eden basit bir linter oluşturalım. Bu linter, bildirilen ancak hiç kullanılmayan değişkenleri tanımlayacaktır.
import * as ts from "typescript";
import *s 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.");
}
Açıklama:
- findUnusedVariables Fonksiyonu: Giriş olarak bir `SourceFile` alan `findUnusedVariables` adında bir fonksiyon tanımlar.
- usedVariables Kümesi: Kullanılan değişkenlerin adlarını depolamak için bir `Set` oluşturur.
- visit Fonksiyonu: AST'yi gezen ve tüm tanımlayıcıların adlarını `usedVariables` kümesine ekleyen özyinelemeli bir `visit` fonksiyonu tanımlar.
- checkVariableDeclaration Fonksiyonu: Bir değişken bildiriminin kullanılmadığını kontrol eden özyinelemeli bir `checkVariableDeclaration` fonksiyonu tanımlar. Eğer kullanılmıyorsa, değişken adını `unusedVariables` dizisine ekler.
- unusedVariables Döndürme: Kullanılmayan değişkenlerin adlarını içeren bir dizi döndürür.
- Çıktı: Kullanılmayan değişkenleri konsola yazdırır.
Bu örnek basit bir linteri göstermektedir. Kodunuzdaki diğer kodlama standartlarını kontrol etmek ve diğer olası sorunları belirlemek için onu genişletebilirsiniz. Örneğin, kullanılmayan içe aktarmaları, aşırı karmaşık fonksiyonları veya potansiyel güvenlik açıklarını kontrol edebilirsiniz. Anahtar, AST'yi nasıl gezeceğinizi ve ilgilendiğiniz belirli düğüm tiplerini nasıl belirleyeceğinizi anlamaktır.
En İyi Uygulamalar ve Dikkat Edilmesi Gerekenler
- AST'yi Anlayın: AST'nin yapısını anlamak için zaman ayırın. Kodunuzun AST'sini görselleştirmek için AST explorer gibi araçları kullanın.
- Tip Korumalarını Kullanın: Doğru düğüm tipleriyle çalıştığınızdan emin olmak için tip korumalarını (`ts.isXXX`) kullanın.
- Performansı Göz Önünde Bulundurun: AST dönüşümleri hesaplama açısından maliyetli olabilir. Ziyaret ettiğiniz ve dönüştürdüğünüz düğüm sayısını en aza indirmek için kodunuzu optimize edin.
- Hataları Yönetin: Hataları zarif bir şekilde yönetin. AST üzerinde geçersiz işlemler yapmaya çalışırsanız Derleyici API'si istisnalar fırlatabilir.
- Kapsamlı Test Edin: Dönüşümlerinizin istenen sonuçları verdiğinden ve yeni hatalar ortaya çıkarmadığından emin olmak için dönüşümlerinizi kapsamlı bir şekilde test edin.
- Mevcut Kütüphaneleri Kullanın: Derleyici API'si üzerinde daha yüksek seviyeli soyutlamalar sağlayan mevcut kütüphaneleri kullanmayı düşünün. Bu kütüphaneler yaygın görevleri basitleştirebilir ve yazmanız gereken kod miktarını azaltabilir. Örnekler arasında `ts-morph` ve `typescript-eslint` bulunur.
Sonuç
TypeScript Derleyici API'si, gelişmiş geliştirme araçları oluşturmak için güçlü bir araçtır. AST ile nasıl çalışılacağını anlayarak, kod kalitesini artıran, tekrarlayan görevleri otomatikleştiren ve geliştirici üretkenliğini artıran linters, kod formatlayıcıları, statik analizciler ve diğer araçlar oluşturabilirsiniz. API karmaşık olsa da, onda ustalaşmanın faydaları oldukça önemlidir. Bu kapsamlı rehber, Derleyici API'sini projelerinizde etkili bir şekilde keşfetmek ve kullanmak için bir temel sağlar. AST Explorer gibi araçlardan yararlanmayı, düğüm tiplerini dikkatlice işlemeyi ve dönüşümlerinizi kapsamlı bir şekilde test etmeyi unutmayın. Pratik ve azimle, TypeScript Derleyici API'sinin tüm potansiyelini açığa çıkarabilir ve yazılım geliştirme ortamı için yenilikçi çözümler inşa edebilirsiniz.
Daha Fazla Keşif:
- TypeScript Derleyici API Dokümantasyonu: [https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
- AST Gezgini: [https://astexplorer.net/](https://astexplorer.net/)
- ts-morph kütüphanesi: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)