Statik Analizde Neler Değişti: .NET 5–11 Analyzer'ları, TSLint'ten ESLint 10'a, TypeScript 7 ve Yeni Nesil Linter'lar
Analyzer'lar .NET 5'ten beri SDK'nın parçası ve her sürüm yeni kural getiriyor: aynı kod .NET 10 SDK'sıyla sıfır, .NET 11 SDK'sıyla dört uyarı verdi (CA2026, CA2027, CA1877, CA2028). Yazı .NET 6–11 kural tarihini, CA2028'in source-generated regex'i görmemesini, .eslintrc'den flat config'e otomatik taşımanın angular-eslint 22'de neden yetmediğini, TypeScript 7'nin typescript-eslint'i beklettiğini, Oxlint ve Biome'un Bookstore'daki sonuçlarını ve Sonar'ın yeni adlarını anlatıyor.
Sürüm notu: Bu yazıdaki kodlar .NET 10 (SDK 10.0.402) ve .NET 11 (SDK 11.0.100) ile derlenip denenmiştir; JavaScript tarafında ESLint 10.15, @eslint/migrate-config 3.0.4, Oxlint 1.93 (oxlint-tsgolint 7.0.2007) ve Biome 2.6.7 Node.js 24 üzerinde çalıştırılmıştır. Sonar ile ilgili bilgiler beşinci yazıdaki SonarQube Community Build 26.11 kurulumundan ve dokümandan alınmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'da (bir online kitapçı) tedarikçilerden gelen katalog dosyalarını okuyan eski bir yardımcı class var. Yıllar önce yazılmış, bugünkü .NET'te de sorunsuz derleniyor:
using System.Text.Json;
using System.Text.RegularExpressions;
namespace Bookstore.Catalog;
// Tedarikçinin katalog dosyasını okur; eski .NET kodunun sık görülen hâliyle yazılmış.
internal static partial class CatalogImport
{
private static readonly Regex IsbnField = new(@"^isbn:(97[89]\d{10})$", RegexOptions.Compiled);
[GeneratedRegex(@"^isbn:(97[89]\d{10})$")]
private static partial Regex IsbnFieldGenerated { get; }
public static string? IsbnFromField(string field)
{
if (IsbnField.IsMatch(field))
{
return IsbnField.Match(field).Groups[1].Value;
}
return null;
}
public static string? IsbnFromFieldGenerated(string field)
{
if (IsbnFieldGenerated.IsMatch(field))
{
return IsbnFieldGenerated.Match(field).Groups[1].Value;
}
return null;
}
public static string TitleFromJson(string json) =>
JsonDocument.Parse(json).RootElement.GetProperty("title").GetString() ?? "";
public static string CoverPath(string root, string isbn) =>
Path.Combine(Path.Combine(root, "covers"), isbn + ".jpg");
public static async Task<bool> DownloadWithTimeoutAsync(Task download)
{
Task finished = await Task.WhenAny(download, Task.Delay(TimeSpan.FromSeconds(5)));
return finished == download;
}
}
Proje net10.0 hedefliyor ve <AnalysisLevel>latest-recommended</AnalysisLevel> kullanıyor. .NET 10 SDK'sıyla derlediğinde kaç uyarı alırsın? Aynı projeyi, kodda tek harf değiştirmeden .NET 11 SDK'sıyla derlediğinde kaç? Ve iki IsbnFrom... method'u aynı hatayı yaptığı hâlde ikisi de mi yakalanır?
.NET 5: Analyzer'lar SDK'ya Giriyor
Bugünkü düzeni anlamak için biraz geriye gitmek gerekiyor. .NET Framework döneminde kod analizi iki ayrı dünyadan oluşuyordu. Eski FxCop, derlenmiş assembly'yi build'den sonra analiz eden bir araçtı; kuralları .ruleset XML dosyalarıyla yönetilirdi. Roslyn'le birlikte bu kurallar kaynak kod üzerinde çalışan analyzer'lara taşındı ve bir süre Microsoft.CodeAnalysis.FxCopAnalyzers NuGet paketiyle dağıtıldı.
.NET 5 (Kasım 2020) bu paketi SDK'nın içine aldı. net5.0 ve sonrasını hedefleyen her projede Microsoft.CodeAnalysis.NetAnalyzers ek kurulum olmadan çalışır hâle geldi ve AnalysisLevel property'si doğdu. Ruleset'in yerini önce .editorconfig, sonra .NET 6 ile önceliği global_level ile belirlenebilen .globalconfig aldı. SDK'nın her seviye için getirdiği config dosyalarının uzantısı da bu değişimi taşıyor: Microsoft'un dokümanına göre .NET 7 ve öncesinin seviye dosyaları .editorconfig, sonrasınınkiler .globalconfig. İkinci yazıda gördüğümüz .ruleset taşıması bu tarihin kalıntısı.
.NET 6–10: Her Sürümde Ne Geldi?
Hangi kuralın hangi sürümde eklendiğinin birincil kaynağı, dotnet/sdk repo'sundaki AnalyzerReleases.Shipped.md dosyası. Her sürümün "Recommended" modda neyi warning'e çevirdiğini ise SDK'nın kendi analysislevel_<version>_recommended.globalconfig dosyalarını karşılaştırarak çıkardık. Bookstore gibi bir web uygulaması için dikkat çekenler:
| Sürüm | Öne çıkan yeni kurallar | Bookstore için anlamı |
|---|---|---|
| .NET 6 | CA1848, CA2253, CA2254 (logging template'leri), CA2017 (parametre sayısı uyuşmazlığı, varsayılan warning) | Structured logging kuralları; ilk yazıdaki interpolated log satırı buradan |
| .NET 7 | CA1849 (async yerine sync çağrı), CA1851 (çoklu enumeration), CA1852, CA1854 | Program.cs'teki app.Run() bulgusu |
| .NET 8 | CA1510–CA1513 (throw helper'lar), CA1859–CA1870 (performans), CA2021 (varsayılan warning) | .NET 6 ile birlikte Recommended moda en çok kural ekleyen sürüm: 17 kural |
| .NET 9 | CA1515 (type'ları internal yap), CA2022 (Stream.Read), CA2263–CA2265 | İlk yazıdaki All modda 39 kez çıkan CA1515 |
| .NET 10 | CA1873 (pahalı log argümanı), CA1874/CA1875 (Regex), CA2023, CA2024, CA2025 | İlk yazıdaki log satırında CA1873 |
| .NET 11 | CA1517, CA1876, CA1877, CA2026, CA2027, CA2028, CA2267 | Aşağıda |
Tabloyu okurken bir ayrıntıyı unutma: bir kuralın SDK'ya eklendiği sürüm ile varsayılan olarak açıldığı mod farklı şeyler. Örneğin CA1515 .NET 9'da geldi ama varsayılan severity'si "Disabled"; yalnız All modda çalışıyor. Her yeni kuralın build'ini kırıp kırmayacağı, ikinci yazıda sabitlediğin AnalysisLevel'a ve mode'a bağlı.
.NET 11'in Kuralları
Tahminin cevabı:
$ dotnet --version
10.0.402
$ dotnet build
0 Warning(s)
$ dotnet --version
11.0.100
$ dotnet build
CatalogImport.cs(16,13): warning CA2028: Redundant call to 'Regex.IsMatch' before 'Regex.Match'. Use 'Regex.Match' and check 'Match.Success' instead.
CatalogImport.cs(33,9): warning CA2026: Use 'JsonElement.Parse' instead of 'JsonDocument.Parse(...).RootElement' to avoid resource leaks
CatalogImport.cs(36,9): warning CA1877: Multiple consecutive Path.Combine operations can be collapsed into a single operation
CatalogImport.cs(40,54): warning CA2027: Using Task.WhenAny with Task.Delay may result in a timer continuing to run after the operation completes, wasting resources
4 Warning(s)
.NET 10'da sıfır, .NET 11'de dört warning. Dördü de gerçek sorunlar. CA2026: JsonDocument havuzdan bellek kiralar ve dispose edilmesi gerekir; RootElement'i alıp document'ı bırakmak bu belleği geri vermez. CA2027: işlem bittiğinde Task.Delay'in timer'ı beş saniye daha çalışmaya devam eder; yoğun bir endpoint'te bu, birikmiş timer demek. CA1877'nin önerisi tek bir Path.Combine(root, "covers", isbn + ".jpg"). Microsoft Learn'de bu kuralların bir kısmının sayfası yazım anında henüz yok; ID'leri ve açıklamaları SDK 11.0.100'ün config dosyalarından ve dotnet/sdk repo'sundaki AnalyzerReleases.Unshipped.md'den doğruladık.
Üçüncü sorunun cevabı da çıktıda: CA2028 yalnız 16. satırda, static field üzerinden çağrılan regex'te çıktı. Aynı hatayı [GeneratedRegex] property'si üzerinden yapan IsbnFromFieldGenerated sessiz kaldı. İkinci yazıda, Bookstore'un Isbn class'ını yazarken de aynısını görmüştük: kural, regex'i method ya da property olarak veren source-generated versiyonu tanımıyor. Yeni bir kuralın ilk sürümünde böyle false negative'ler olağan; bu yüzden "analyzer sessizse kod temizdir" çıkarımı yanlış.
Bookstore'un hâlâ .NET 10 SDK'sında ve 10.0-recommended seviyesinde olduğunu hatırla. .NET 11'e geçiş PR'ını (pull request; değişikliğin ana branch'e alınması için açılan inceleme isteği) hazırlamak için önce kodu düzelttik; Isbn.Normalize artık tek bir Match çağırıyor:
// "ISBN 978-0-441-17271-9" değerini "9780441172719"a çevirir; geri kalan her şey için null döner.
public static string? Normalize(string input)
{
Match match = Isbn13Pattern.Match(input.Trim());
if (!match.Success)
{
return null;
}
string digits = string.Concat(match.Groups.Values.Skip(1).Select(g => g.Value));
return digits.Length == 13 ? digits : null;
}
Bu hâliyle Bookstore'u .NET 11 SDK'sıyla -p:AnalysisLevel=11.0-recommended vererek derlediğimizde sıfır warning, sıfır error çıktı. Geçiş PR'ı artık yalnız global.json ve AnalysisLevel satırını değiştirecek.
TSLint'ten ESLint 10'a
Angular tarafındaki tarih daha karışık. Angular CLI uzun yıllar TSLint ile geldi; TSLint 2019'da deprecated ilan edildi ve Angular CLI 12'den itibaren yeni projelerde lint yapılandırılmadı. O dönemde açılmış ve bugüne kadar yükseltilmiş projelerde hâlâ bir tslint.json durabilir; hiçbir araç onu okumuyorsa ölü bir dosyadır. Geçiş angular-eslint'in schematic'leriyle yapıldı; 22.6'nın schematic listesinde convert-tslint-to-eslint hâlâ duruyor.
Sonra ESLint'in kendi geçişi geldi. ESLint 9 (Nisan 2024) flat config'i varsayılan yaptı ama eski .eslintrc'yi bir environment variable ile hâlâ destekliyordu; ESLint 10 (Şubat 2026) onu tamamen kaldırdı. angular-eslint 22.0'ın sürüm notları da ESLint 8 ve eslintrc formatı desteğini bıraktığını yazıyor. Angular 17 döneminden kalma tipik bir .eslintrc.json ile ESLint 10:
$ npx eslint src
Oops! Something went wrong! :(
ESLint: 10.15.0
ESLint couldn't find an eslint.config.* file.
ESLint ekibinin taşıma aracı dosyayı çeviriyor:
$ npx @eslint/migrate-config .eslintrc.json
Migrating .eslintrc.json
Wrote new config to ./eslint.config.mjs
You will need to install the following packages to use the new config:
- @eslint/js
- @eslint/eslintrc
Üretilen dosya eski extends girdilerini FlatCompat ile sarıyor: compat.extends("plugin:@angular-eslint/recommended", ...). Paketleri kurup çalıştırınca:
$ npx eslint src
Oops! Something went wrong! :(
ESLint: 10.15.0
ESLint couldn't find the config "plugin:@angular-eslint/recommended" to extend from. Please check that the name of the config is correct.
FlatCompat, eski formatta yazılmış config'leri yeni formatta kullanmayı sağlayan bir köprü; ama angular-eslint 22 artık eski formatta config yayımlamıyor, köprünün karşı tarafı yok. Doğru taşıma, otomatik çıktıyı başlangıç noktası alıp eski adları paketlerin flat config karşılıklarıyla değiştirmek: plugin:@angular-eslint/recommended yerine angular.configs.tsRecommended, plugin:@angular-eslint/template/recommended yerine angular.configs.templateRecommended, plugin:@typescript-eslint/recommended yerine tseslint.configs.recommended. Sonuç, dördüncü yazıdaki eslint.config.js'in ta kendisi. overrides blokları files alanlı array element'lerine, ignorePatterns de globalIgnores'a dönüşüyor; bu kısmı araç doğru yaptı.
TypeScript 6.0 ve TypeScript 7
TypeScript 6.0 (Mart 2026), JavaScript ile yazılmış son TypeScript sürümü olarak çıktı ve varsayılanları değiştirdi; bizi en çok ilgilendiren strict'in artık varsayılan olarak açık olması. Dördüncü yazıda tsconfig'siz bir dosyada derleyicinin null kontrolü yaptığını gördük. Statik analiz spektrumunun en ucuz katmanı, type checker, böylece kimse açmasa da devrede.
TypeScript 7.0 (Temmuz 2026) ise derleyicinin Go ile yeniden yazılmış hâli. Hızlı, ama 7.0'da programatik bir API yok. typescript-eslint'in tip bilgili kuralları bu API'ye dayandığı için, dördüncü yazıda gördüğümüz gibi typescript-eslint'in son sürümü bile TypeScript'i <6.1.0 ile sınırlıyor; Angular 22.2 de >=6.0 <6.1 istiyor. TypeScript 7.1'in beta'sı (Ekim 2026) yeni bir API ile geliyor; bu API stabil olup typescript-eslint ona geçene kadar Angular projeleri TypeScript 6.0'da kalıyor. Pratik sonuç: bir Angular projesinde "TypeScript 7'ye geçelim" kararı bugün derleyiciden değil, lint ve framework araç zincirinden geçiyor.
Yeni Nesil Linter'lar: Oxlint ve Biome
Bu boşluğu dolduran araçların başında Oxlint var. Rust ile yazılmış, 1.0 sürümü Haziran 2025'te yayımlandı. Tip bilgili kuralları için ayrı bir motor kullanıyor: oxlint-tsgolint, TypeScript 7'nin native derleyicisi üzerine kurulu. Oxc ekibinin 22 Temmuz 2026 tarihli yazısına göre type-aware linting bu tarihte stabil oldu, typescript-eslint'in 61 tip bilgili kuralının 59'unu destekliyor ve dört büyük codebase'te ESLint ile typescript-eslint'ten 12 ila 18 kat hızlı ölçülmüş. İlginç olan, aracın kendi TypeScript'ini getirmesi: Bookstore'un TypeScript 6.0 projesinde çalıştı.
Dördüncü yazıdaki hatalı buyNow ve clearCart'ı geri koyup denedik. Varsayılan kurallarla Oxlint hiçbir şey raporlamadı; type-aware kipte varsayılan set iki floating promise'i buldu:
$ npx oxlint --type-aware src
src/app/catalog/book-detail-page.ts:29:5: warning typescript(no-floating-promises): Promises must be awaited, add void operator to ignore.
src/app/catalog/book-detail-page.ts:30:5: warning typescript(no-floating-promises): Promises must be awaited, add void operator to ignore.
ESLint'in bulduğu diğer kuralları açıkça istediğimizde onları da yakaladı:
$ npx oxlint --type-aware -A all -D typescript/no-floating-promises -D typescript/no-misused-promises -D typescript/no-unsafe-assignment src
src/app/catalog/book-detail-page.ts:29:5: error typescript(no-floating-promises): Promises must be awaited, add void operator to ignore.
src/app/cart/cart-page.ts:31:13: error typescript(no-unsafe-assignment): Unsafe assignment of an any value.
src/app/catalog/book-detail-page.ts:30:5: error typescript(no-floating-promises): Promises must be awaited, add void operator to ignore.
src/app/cart/cart-page.ts:37:51: error typescript(no-misused-promises): Promise returned in function argument where a void return was expected.
Ama Oxlint'te angular-eslint'in karşılığı yok: Angular template'lerini lint etmiyor, prefer-control-flow ya da accessibility template kuralları burada yok. Bookstore'un küçük istemcisinde süre farkı da anlamlı değil; type-aware Oxlint 0,51 saniye, ESLint 0,94 saniye sürdü. Fark büyük codebase'lerde ortaya çıkıyor.
Biome ise linter ve formatter'ı tek araçta birleştiriyor. 2.6.7 ile Bookstore'un istemcisinde biome lint src, 7 warning (useImportType; yalnız tip olarak kullanılan import'lar için import type önerisi) ve 9 error verdi. 9 error'un hepsi aynı kural:
src/app/catalog/catalog-page.html:23:9 lint/a11y/useValidAnchor ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Provide a href attribute for the a element.
> 23 │ <a class="card-link" [routerLink]="['/books', book.isbn]">{{ book.title }}</a>
Angular'ın routerLink directive'i href'i runtime'da kendisi ekliyor; Biome bunu bilmediği için Bookstore'daki dokuz linkin hepsi false positive. Floating promise'leri ise varsayılan ayarlarla görmedi. Biome'un Angular template'leri için bugün ESLint'in yerini tutmadığını gösteren somut bir örnek.
Peki yerine mi, yanına mı? Bookstore'un kararı yanına. ESLint ve angular-eslint, template kuralları ve olgun ekosistemiyle CI'daki asıl kontrol olarak kalıyor. Oxlint, büyük bir monorepo'da editör ve pre-commit gibi saniyelerin önemli olduğu yerlerde hızlı ilk filtre olarak düşünülebilir; iki aracın aynı kuralı iki kez raporlamaması için Oxlint'in yayımladığı eslint-plugin-oxlint, Oxlint'in kapsadığı kuralları ESLint tarafında kapatıyor. Biome, formatter'ı da değiştirmeyi göze alan ve Angular template'lerine ağırlık vermeyen projeler için bir seçenek.
Sonar: Yeni İsimler ve Sürüm Şeması
SonarSource 29 Ekim 2024'te ürün adlarını değiştirdiğini duyurdu ve yeni adlar Aralık'ta ürünlere geldi: SonarQube → SonarQube Server, SonarCloud → SonarQube Cloud, SonarLint → SonarQube for IDE, SonarQube Community Edition → SonarQube Community Build. Aynı dönemde Server yıl bazlı sürüm şemasına geçti; ilk LTA (Long-Term Active; uzun süre bakım alan sürüm) 2025.1, bugünkü LTA 2026.5. Community Build ise aylık çıkıyor ve sürüm numarası yıl.ay biçiminde; beşinci yazıda 26.11'i kullandık.
Community Build 26.10, yerleşik quality profile'ları da değiştirdi: "Sonar way" profili "Sonar way comprehensive" adını aldı ve varsayılan profil artık daha dar olan "Sonar way core". Bu değişiklikten önce kurulmuş projeler eski profillerini koruyor, ama yeni bir kurulum yaptığında varsayılan kural sayısı belirgin şekilde az; beşinci yazıda C# için 121'e karşı 321 kural gördük. Dil desteği de sürümlerle güncelleniyor: kurulumumuzdaki C# analyzer'ında csharp14 etiketli üç kural var. Üçü de C# 14'ün değiştirdiği extension, partial ve scoped kelimeleriyle aynı adı taşıyan identifier'ların anlamının yeni derleyicide değişmesine karşı (S8368, S8380, S8381).
Üretimde Dikkat
Yükseltmeleri ayrı PR'larda ve ölçerek yap. SDK, AnalysisLevel, typescript-eslint'in strict seti, SonarQube sürümü ve profili: her biri kod değişmeden yeni bulgu getirebilir. Her biri için "önce ve sonra" bulgu sayısını kural ID'sine göre karşılaştıran bir PR, ekibin araca güvenini korur.
Yeni bir kuralın ilk sürümüne körü körüne güvenme. CA2028'in source-generated regex'i görmemesi gibi boşluklar ilk sürümlerde sık. Kural önemliyse kendi kodunda tetiklemeyi dene; tetiklemiyorsa ya kendi analyzer'ını yaz ya da review checklist'ine ekle.
Araç değiştirirken bulgu setini karşılaştır. ESLint'ten Oxlint'e ya da Biome'a geçmeden önce iki aracı aynı commit'te çalıştır ve farkı oku; Biome'un dokuz routerLink false positive'i bu karşılaştırma olmadan ekibin ilk gününü yerdi.
Sık Düşülen Tuzaklar
Otomatik taşıma çıktısını bitmiş saymak. @eslint/migrate-config dosyanın yapısını doğru çevirdi ama çalışan bir config üretmedi; plugin'lerin flat config karşılıklarını elle yazmak gerekti.
TypeScript 7'yi Angular projesine erken almak. npm'de latest etiketi 7.0'ı gösteriyor; npm install typescript@latest bir Angular 22 projesinde peer dependency uyarılarıyla birlikte Angular derleyicisinin ve typescript-eslint'in desteklediği aralığın dışına çıkar. Bir standalone TypeScript kütüphanesinde 7.0 bugün kullanılabilir, Angular projesinde değil.
Eski tslint.json'u silmemek. Hiçbir aracın okumadığı bir lint config'i, ekibe olmayan bir kontrolün var olduğu izlenimini verir.
Sonar'ın yeni varsayılan profilini eski profil sanmak. Kurulumdan sonra "Sonar way" adını aramak boşuna; complexity ya da kullanılmayan parametre kuralları istiyorsan profili bilerek seç.
Küçük Bir Deneme
★☆☆ Kendi .NET projeni .NET 11 SDK'sıyla -p:AnalysisLevel=11.0-recommended vererek derle. Yeni uyarıların kaçı CA2026, CA2027 ya da CA2028? Her birini düzeltmenin maliyetini not al.
★★☆ Eski bir Angular projesinin .eslintrc.json'unu @eslint/migrate-config ile çevir, sonra FlatCompat satırlarını angular-eslint ve typescript-eslint'in flat config'leriyle değiştirerek çalışır hâle getir. Taşımadan önce ve sonra aynı commit'te bulgu sayılarını karşılaştır.
★★★ Projende Oxlint'i --type-aware ile ESLint'in yanına koy ve eslint-plugin-oxlint ile çakışan kuralları ESLint'te kapat. Pre-commit'te yalnız Oxlint, CI'da ikisi birden çalışsın. Büyük bir projede süreler nasıl değişti, hangi bulgular yalnız birinde çıktı?
Seri burada bitiyor. Statik analizin neyi bulup neyi bulamadığını, .NET'te analyzer'ları seviyesiyle ve severity'siyle yönetmeyi, kendi kuralını yazmayı, Angular'da tip bilgili lint'i ve yeni koda odaklı bir quality gate'i Bookstore üzerinde gördük. Bu araçların hiçbiri test ve review'un yerini tutmuyor; ama ikisinin de dikkatini mekanik hatalardan kurtarıp asıl soruya, kodun doğru şeyi yapıp yapmadığına yöneltiyor.