2011’in sonlarına doğru Microsoft, Rosyln’nin ilk versiyonu çıkardığında anlamak konusunda biraz zorlanmıştım açıkcası. Neden böyle bir şeye ihtiyaç olabileceği, ya da .NET compiler’ın servis olarak açılmasının neler getireceğini o zamanlar bayaa anlamak için çalışmıştım. Preview ve CTP versiyonları olmasının getirdiği net olmayan kavramların da olması işleri pek kolaylaştırmıyordu açıkcası.

Hala son halini tam olarak almamış olmasına rağmen oldukça olgunlaşmış olduğunu söyleyebilirim. Hatta bu senenin içinde RTM haberini de duyarız gibime geliyor.

Rosyln kısaca, .NET için olan bir compiler platformu. Hatta resmi adı da zaten .NET Compiler Platform. .NET Runtime’ında çalışan kodları derleyen, compiler’ın servis olarak, API’lar üzerinden dışarı açılması olarak tanımlayabiliriz. Bu API’lar, lexical analiz, semantic analiz gibi compiler’ların olmazsa olmaz bileşenlerine müdahele etmeyi, genişletmeyi sağlayan parçalar olarak karşımıza çıkıyor. Ve tabi ki derleme aşamasına da aynı şekilde müdahale edebiliyoruz.

Yani?

Biraz daha içerlere girersek, her programlama dilinin bir grammer’i vardır; normal konuşma dillerinde olduğu gibi. Bu grammer, o dilin kurallarını oluşturur. if’den sonra () içerisinde true/false bir ifade kontrolü ile { } içerisinde ki kodların çalıştırılması gibi. Buradaki keyword ya da diğer ifadeler de dilin syntax’ını yani alfabesini oluşturur. Yazdığımız ifadeler, syntax’ın parse edilip, dilin kuralları ile yorumlanması ve derlenmesi ile de çalışır hale gelirler. Basitçe anlatmaya çalıştığım bütün bu adımlara, Rosyln ile artık çeşitli noktalarda müdahele etme şansımız ortaya çıkıyor.

Eee Yani?

Yani Rosyln sayesinde, kendi keyword’lerinizi tanımlayıp, kodların derlendiğinde farklı ifadeler üretmesi, lexical analiz ve syntax analiz adımlarında kendi kurallarınızı uygulamanız artık mümkün olabilecek. Biraz daha gerçek ve anlaşılır olması adına, mesela kendi scripting dilinizi Rosyln sayesinde geliştirebileceksiniz.

İlerleyen zamanlarda biraz daha derinlere girerek farklı şeyleri paylaşmayı umuyorum. Ama şimdilik biraz daha başlamayı teşvik edebilecek kolay bir örnek ile ısınma hareketleri kıvamında bir konuya değinmeye çalışacağım.

Not: .NET Compiler Platform’u ile sağlıklı çalışabilmek için, Visual Studio 2015 Preview ve Visual Studio 2015 Preview SDK’yı yüklemeniz gerekli olacaktır. Daha sonra Nuget’den Rosyln ile ilgili paketi almanız gerekecektir.
 Install-Package Microsoft.CodeAnalysis –Pre
Yapmaya çalışacağım örnek, VSIX proje şablonları üzerinden Visual Studio’ya extension yazmak olacak. O yüzden bu konulara biraz hakim olmak gerekiyor.

Kodumuzu teşhis edelim…

“Diagnostic” yani teşhiş olarak adlandırabileceğimiz kodu analiz eden ve yazdığımız kuralları kodda uygulayabileceğimiz çözümleyiciler(analyzer), .NET Compiler Platform’un sunduğu en önemli özelliklerden biri. FxCop’u hatırlayanlar için oldukça tanıdık gelecektir. .NET Compiler Platform ile beraber gelen, DiagnosticAnalyzer sınıfını kullanarak kendi statik kod analizimizi yapacağız.

Biraz daha anlaşılır olması için, basitçe kendi tanımladığımız build uyarısını oluşturacağız. Visual Studio, bazen yazdığımız kodların altını mavimsi bir renk ile çizer ve çıktı ekranında bir uyarı verir. İşte bizim de yapacağımız kendi tanımladığımız bir kurala göre Visual Studio’nun bu kurala uyulmadığı noktalarda uyarı vermesini sağlamak.

Örnek olması adına, basit bir kuralımız olsun. Mesela yazdığımız metodların parametre sayısı 5’den büyükse Visual Studio bize uyarı(Warning) versin.

Bunun için, Visual Studio 2015 ile, bir tane “Class Library” projesi yaratıp, NuGet’den Microsoft.CodeAnalysis paketini indirmemiz gerekecek. Artık kod yazmaya başlayabiliriz. MethodChecker isminde DiagnosticAnalyzer sınıfından türeyen bir sınıf yaratıyoruz.

 

    public class MethodChecker : DiagnosticAnalyzer
    {
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override void Initialize(AnalysisContext context)
        {
            throw new NotImplementedException();
        }
    }

DiagnosticAnalyzer’dan gelen abstact SupportedDiagnostics özelliğini ve Initialize() metodunu yeni yarattığımız sınıfta tanımlamak durumdayız. SupportedDiagnostics, kendi kural(lar)ımızı tanımladığımız bir özellik. Burada kuralların tanımları olacak.
Initialize metodunda ise, adından da çıkarılabileceği üzere analiz yapılacak içeriklere uygulanacak aksiyonları tanımladığımız metod olacak.

Öncelikle sınıfımıza küçük bir attribute eklememiz gerekmekte.

    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class MethodChecker : DiagnosticAnalyzer
    {

DiagnosticAnalyzer’ımızın hangi dil için geçerli olacağını bu attribute ile belirtebiliyoruz. Çok bir alternatifimiz yok zaten. Ya CSharp ya da VisualBasic.
Bundan sonra artık kuralımızı tanımlayabiliriz. Bunun için DiagnosticDescriptor sınıfından kuralımızı bir nesne olacak şekilde oluşturuyoruz.

        internal static DiagnosticDescriptor _firstRule = 
            new DiagnosticDescriptor("MethodCheck",
                                    "Method parameters' count should not be more than 5.",
                                    "Method '{0}' has more than 5 parameters.",
                                    "Design",
                                    DiagnosticSeverity.Warning,
                                    isEnabledByDefault: true);

Burada önemli olan, DiagnosticAnalyzer’ımıza unique olacak bir ID veriyor olmamız. Bu örnek için için MethodCheck şeklinde bir ID veriyoruz. Diğer parametreler, ön yüz tarafında, uyarı verdiğimiz zaman ekranda görünecek değerler ile ilgili. Bunlara kendi ihtiyacınıza göre uygun ifadeleri yazabilirsiniz. Önemli ikinci nokta, bu kuralımızın ağırlığı. Uyarı(Warning) mı, yoksa hata(Error) mu olacağını da bu tanım sınıfı ile belirtiyoruz.
Daha sonra bu yarattığımız tanımı, SupportedDiagnostics özelliği tarafından dönecek şekilde kodumuzu yazıyoruz.

    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class MethodChecker : DiagnosticAnalyzer
    {

        internal static DiagnosticDescriptor _firstRule =
            new DiagnosticDescriptor("MethodCheck",
                                    "Method parameters' count should not be more than 5.",
                                    "Method '{0}' has more than 5 parameters.",
                                    "Design",
                                    DiagnosticSeverity.Warning,
                                    isEnabledByDefault: true);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        {
            get
            {
                return ImmutableArray.Create(_firstRule);
            }
        }

        public override void Initialize(AnalysisContext context)
        {

        }
    }

Kodumuzun son hali yukarıdaki gibi olmuş oluyor. Şimdi yapmamız gereken kuralımızı tanımlamak. Bunun için de aşağıdaki kodu yazmamız gerekecek.


        //SymbolAnalyisContext tipinde bir parametre alan metodumuzu oluşturuyoruz.
        //Bu tip, analiz edilecek yapıyı temsil ediyor. Bu örnekte bu tip
        //yazacağımız metodlar olacaktır.
        private static void MethodParameterCheck(SymbolAnalysisContext context)
        {

            //Metodlar üzerinde kural yazdığımızdan dolayı, parametre olarak
            //gelen Symbol'ü IMethodSymbol arayüzüne cast etmemiz gerekmekte.
            //Bu sayede metodumuzun özelliklerine ulaşabileceğiz.
            var methodSymbol = (IMethodSymbol)context.Symbol;

            //IMethodSymbol arayüzünden sağlanan Parameters özelliği ile
            //yazdığımız metodların parametrelerine dolayısıyla sayılarına
            //ulaşabiliyoruz.
            if (methodSymbol.Parameters.Count() > 5)
            {
                //Kuralamız gereği, eğer parametre sayısı 5'den büyük ise
                //Bir tane Diagnostic nesnesi yaratıyoruz. Metodumuzun özellikleri
                //ile yarattığımız bu teşhis nesnesi Visual Studio'nun vereceği uyarı oluyor
                var diagnostic = Diagnostic.Create(_firstRule, methodSymbol.Locations[0], methodSymbol.Name);

                //Son olarak bu oluşturduğumuz nesneyi Visual Studion'nun göstermesini
                //sağlıyoruz. Visual Studio'nun Error List penceresinden baktığınızda
                //ilgili uyarıyı görüyor olacaksınız.
                context.ReportDiagnostic(diagnostic);
            }

        }

Kodumuzun sonuna gelirken, bu yazdığımız MethodParameterCheck metodunu da Initialize() metodumuz içerisinde tanımlamamız gerekiyor. Ve kodumuz son halini almış oluyor.


    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class MethodChecker : DiagnosticAnalyzer
    {

        internal static DiagnosticDescriptor _firstRule = 
            new DiagnosticDescriptor("MethodCheck",
                                    "Method parameters' count should not be more than 5.",
                                    "Method '{0}' has more than 5 parameters.",
                                    "Design",
                                    DiagnosticSeverity.Warning,
                                    isEnabledByDefault: true);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        {
            get
            {
                return ImmutableArray.Create(_firstRule);
            }
        }

        public override void Initialize(AnalysisContext context)
        {
            //Analiz edilecez içeriğin RegisterSymbolAction metodu ile
            //kendi kuralımızı tanımlıyoruz.
            //Burada önemli olan ikici parametre olan SymbolKind'lar.
            //SymbolKind'lar yazdığımız kuralların nerelerde geçerli
            //olacağını belirtmiş oluyoruz.
            //Bu örnekte biz Method'u seçiyoruz.
            context.RegisterSymbolAction(MethodParameterCheck, SymbolKind.Method);
        }

        //SymbolAnalyisContext tipinde bir parametre alan metodumuzu oluşturuyoruz.
        //Bu tip, analiz edilecek yapıyı temsil ediyor. Bu örnekte bu tip
        //yazacağımız metodlar olacaktır.
        private static void MethodParameterCheck(SymbolAnalysisContext context)
        {

            //Metodlar üzerinde kural yazdığımızdan dolayı, parametre olarak
            //gelen Symbol'ü IMethodSymbol arayüzüne cast etmemiz gerekmekte.
            //Bu sayede metodumuzun özelliklerine ulaşabileceğiz.
            var methodSymbol = (IMethodSymbol)context.Symbol;

            //IMethodSymbol arayüzünden sağlanan Parameters özelliği ile
            //yazdığımız metodların parametrelerine dolayısıyla sayılarına
            //ulaşabiliyoruz.
            if (methodSymbol.Parameters.Count() > 5)
            {
                //Kuralamız gereği, eğer parametre sayısı 5'den büyük ise
                //Bir tane Diagnostic nesnesi yaratıyoruz. Metodumuzun özellikleri
                //ile yarattığımız bu teşhis nesnesi Visual Studio'nun vereceği uyarı oluyor
                var diagnostic = Diagnostic.Create(_firstRule, methodSymbol.Locations[0], methodSymbol.Name);

                //Son olarak bu oluşturduğumuz nesneyi Visual Studion'nun göstermesini
                //sağlıyoruz. Visual Studio'nun Error List penceresinden baktığınızda
                //ilgili uyarıyı görüyor olacaksınız.
                context.ReportDiagnostic(diagnostic);
            }

        }
    }

Evet… .NET Compiler Platform’u ile ilk çözümleyicimizi yazmış oluyoruz. Bundan sonrası bu yazdığımız kodu, VSIX projesi ile eşleştirip, Visual Studio için extension olarak yayınlamak. Bunun çok .NET Compiler Platform ile alakası olmadığından açıkcası fazla ayrıntısına girmek istemiyorum. Zaten yazının sonunda komple burada anlattığım kodların çalışan halini bulabileceksiniz.

Şimdi bitirmeden önce isterseniz çıktılarımıza bir göz atalım. Bu extension’nın yüklü olduğu yeni bir Visual Studio açtığımızda ve yeni bir projeye başladığımızda, yazacağımız metodlar için artık bu yazmış olduğumuz kurallar geçerli olacaktır.

MethodCheck

 

Umarım biraz olsun faydalı olmuş ve biraz da olsun .NET Compiler Platform için merak uyandırmıştır. Dediğim gibi ilerleyen günlerde daha fazla ve farklı şey paylaşmaya çalışacağım. Ben paylaşana kadar ama tabi ki benimle iletişime geçerek sorularınızı paylaşabilirsiniz.