Mert Özen Her Satırda Daha İleri
Test ve Kalite Statik Analiz — #05

SonarQube ile Quality Gate: Community Build mi Server mı, Quality Profile, Yeni Kod Odaklı Gate ve CI Entegrasyonu

Mert Özen 14 Kas 2026 13 dk 11 görüntülenme
SonarQube ile Quality Gate: Community Build mi Server mı, Quality Profile, Yeni Kod Odaklı Gate ve CI Entegrasyonu

SonarQube'un varsayılan gate'i yalnız yeni koda bakar: yeni issue sıfır, yeni kodda coverage en az %80. Bookstore'da build'i ve testleri temiz olan 'benzer kitaplar' endpoint'i gate'i %10,3 yeni kod coverage'ıyla kırdı, iki test sonrası geçti. Yazı Community Build'i Docker'da PostgreSQL ile kurmayı, SonarScanner for .NET'i, Sonar way core profilini, Roslyn ile çift raporlamayı, eski coverage raporu tuzağını ve Community Build'in PR analizi olmayışını gerçek çıktılarla anlatıyor.

Sürüm notu: Bu yazıdaki kodlar .NET 10 (SDK 10.0.402) ile derlenip denenmiştir; analizler Docker'da SonarQube Community Build 26.11 ve PostgreSQL 17 üzerinde, SonarScanner for .NET 12.1 ve Microsoft.Testing.Extensions.CodeCoverage 18.12 ile çalıştırılmış, testler SQL Server 2025'e karşı koşmuştur. SonarQube Server, SonarQube Cloud, pull request analizi ve SonarQube for IDE'nin connected mode'u dokümandan anlatılmış, çalıştırılmamıştır; GitHub Actions workflow'u actionlint 1.7.12 ile doğrulanmıştır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'a (bir online kitapçı) kitap sayfası için "benzer kitaplar" endpoint'i ekleniyor: aynı yazarın ya da aynı kategorinin stokta olan en fazla dört kitabı. Ayşe'nin kodu:

// Aynı yazarın ya da aynı kategorinin stokta olan en fazla dört kitabı.
private static async Task<Results<Ok<List<BookSummary>>, NotFound>> GetSimilar(
    string isbn, BookstoreContext db, IInventoryService inventory, CancellationToken ct)
{
    var book = await db.Books.AsNoTracking()
        .Where(b => b.Isbn == isbn)
        .Select(b => new { b.Id, b.AuthorId, b.CategoryId })
        .FirstOrDefaultAsync(ct);
    if (book is null)
    {
        return TypedResults.NotFound();
    }

    List<Book> candidates = await db.Books.AsNoTracking()
        .Include(b => b.Author)
        .Include(b => b.Category)
        .Where(b => b.Id != book.Id && (b.AuthorId == book.AuthorId || b.CategoryId == book.CategoryId))
        .OrderBy(b => b.AuthorId == book.AuthorId ? 0 : 1)
        .ThenBy(b => b.Title)
        .ToListAsync(ct);

    List<BookSummary> similar = candidates
        .Where(b => inventory.GetAvailableAsync(b.Id, ct).Result > 0)
        .Take(4)
        .Select(b => new BookSummary(b.Isbn, b.Title, b.Author.Name, b.Category.Name, b.Price, b.PublishedYear))
        .ToList();
    return TypedResults.Ok(similar);
}

İkinci ve üçüncü yazıdaki analyzer'larla build temiz: sıfır warning, sıfır error. Var olan on integration test geçiyor. Bu kod SonarQube'un varsayılan quality gate'inden geçer mi? Geçmezse hangi koşul yüzünden?

Hangi SonarQube?

İlk yazıda dashboard katmanından söz etmiştik: tek bir PR'a (pull request; değişikliğin ana branch'e alınması için açılan inceleme isteği) değil, zamana bakan, .NET ve Angular tarafının sonuçlarını tek yerde toplayan bir sunucu. SonarQube bugün üç ürün olarak geliyor ve aralarındaki fark yalnız fiyat değil:

ÜrünYayın şekliBranch ve PR analiziBu yazıda
SonarQube Community BuildAylık, yıl.ay numarasıyla (26.11, Kasım 2026)Yok, tek branchDocker'da çalıştırıldı
SonarQube Server (Developer, Enterprise, Data Center)Yıllık sürüm şeması; son LTA (Long-Term Active; uzun süre bakım alan sürüm) 2026.5, Eylül 2026Developer Edition'dan itibaren varDokümandan
SonarQube CloudSürekli güncellenen SaaS (yazılımı hizmet olarak sunan bulut ürünü)VarHesap açılmadı, dokümandan

Community Build'in sınırını denedik: analiz parametrelerine bir branch adı verdiğimizde scanner analizi reddetti:

ERROR: Validation of project failed:
  o To use the property "sonar.branch.name" and analyze branches, Developer Edition or above is required.

Bunun pratik anlamı büyük. Community Build'de PR'ı analiz edip sonucu PR'a yazmak (PR decoration) yok; analiz yalnız ana branch'te, merge'den sonra çalışır. Gate'i PR'da kırmızı görmek istiyorsan ya Developer Edition ve üstü ya da SonarQube Cloud gerekir. Server ZIP (sıkıştırılmış arşiv dosyası) olarak kurulacaksa Java 21 ya da 25 istiyor; Docker image'ı Java'yı içinde getiriyor.

Yerel Kurulum: Docker Compose ile SonarQube ve PostgreSQL

SonarQube'un gömülü H2 (Java ile yazılmış küçük bir veritabanı motoru) veritabanı yalnız deneme içindir; kalıcı bir kurulum ayrı bir veritabanı ister. Bookstore'un repo'suna eklediğimiz compose dosyası:

# Bookstore için yerel SonarQube Community Build.
# Başlat: docker compose -f tools/sonarqube/compose.yaml up -d   (sonra http://localhost:9000)
# Gerekli: SONAR_DB_PASSWORD environment'ta ya da tools/sonarqube/.env'de (commit'lenmez).
services:
  sonarqube:
    image: sonarqube:26.11.0.134175-community
    depends_on:
      db:
        condition: service_healthy
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: ${SONAR_DB_PASSWORD:?set SONAR_DB_PASSWORD}
    ports:
      - "127.0.0.1:9000:9000"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: ${SONAR_DB_PASSWORD:?set SONAR_DB_PASSWORD}
      POSTGRES_DB: sonar
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sonar -d sonar"]
      interval: 5s
      timeout: 5s
      retries: 10
    volumes:
      - postgresql:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:

Denemede host portunu çakışma olmasın diye 19000 yaptık, bu yüzden aşağıdaki bazı çıktılarda o adres görünüyor. Image etiketi tam sürümle yazılı; community etiketi her ay ilerler ve bir sabah veritabanı migration'ıyla karşılaşırsın. Port yalnız 127.0.0.1'e açık. SonarQube'un içindeki Elasticsearch, host'ta vm.max_map_count değerinin en az 524288 olmasını istiyor; doküman bu ayarın container'da değil host'ta yapılması gerektiğini söylüyor. Windows'ta Docker Desktop bir Linux VM'i içinde çalıştığı için ayar o VM'e aittir. Sunucu hazır olduğunda:

$ curl http://127.0.0.1:19000/api/system/status
{"id":"243B8A4D-AaHZ5i6CAftXmZhh9lVq","version":"26.11.0.134175","status":"UP"}

İlk iş, varsayılan admin/admin şifresini değiştirmek ve analiz için bir token üretmek. İkisini de API ile yaptık (api/users/change_password, api/user_tokens/generate ve type=GLOBAL_ANALYSIS_TOKEN); token yalnız analiz yapabilir, yönetim yetkisi yoktur. Şifre ve token repo'ya değil, CI'ın secret store'una gider.

SonarScanner for .NET: begin, build, test, end

.NET projeleri, kaynak kodu dışarıdan okuyan genel scanner ile değil, MSBuild'e bağlanan SonarScanner for .NET ile analiz edilir. Scanner'ı Bookstore'un local tool manifest'ine ekledik (dotnet tool install dotnet-sonarscanner --version 12.1.0); global tool kurulmadı. Akış dört adım:

dotnet sonarscanner begin /k:bookstore /n:Bookstore /v:1.0 /d:sonar.host.url=http://localhost:9000 /d:sonar.token=<token> "/d:sonar.cs.cobertura.reportsPaths=TestResults/*.cobertura.xml"
dotnet build Bookstore.slnx --no-incremental
dotnet test --project tests/Bookstore.Api.Tests --coverage --coverage-output-format cobertura --coverage-output Bookstore.Api.Tests.cobertura.xml
dotnet test --project tests/Bookstore.Analyzers.Tests --coverage --coverage-output-format cobertura --coverage-output Bookstore.Analyzers.Tests.cobertura.xml
dotnet sonarscanner end /d:sonar.token=<token>

begin sunucudan quality profile'ı indirir ve build'e SonarAnalyzer'ın Roslyn analyzer'larını ekleyen MSBuild target'larını yerleştirir. Bu yüzden build'in kendisi analiz adımıdır; Sonar'ın kuralları build log'unda warning olarak da görünür:

src/Bookstore.Api/Catalog/Isbn.cs(7,51): warning S6444: Pass a timeout to limit the execution time.
src/Bookstore.Api/Program.cs(48,1): warning S6966: Await RunAsync instead.

Makinede Java olmadığı hâlde analiz çalıştı: scanner, SonarQube'dan bir JRE (Java Runtime Environment; Java programlarını çalıştıran ortam) indirdi ve log'a, Java zaten kuruluysa /d:sonar.scanner.skipJreProvisioning=true ya da /d:sonar.scanner.javaExePath=<PATH> verilebileceğini yazdı. Coverage için Test Stratejisi serisinin "Code Coverage Ne Söyler, Ne Söylemez" yazısında kullandığımız Microsoft.Testing.Extensions.CodeCoverage paketini her iki test projesine ekledik. Sonar'ın .NET dokümanı Cobertura formatını sonar.cs.cobertura.reportsPaths ile destekliyor. Her test projesine ayrı dosya adı verdik: ikisini tek dotnet test --solution komutuyla aynı ada yazdırdığımızda ikinci rapor ilkinin üzerine yazdı.

Bir sürpriz de olumlu: end adımı yalnız C# dosyalarını değil, repo'daki web/bookstore-web'i de analiz etti. Log'da "7 languages detected" yazıyor ve dil dağılımı cs=1354;css=74;js=45;ts=243;web=120 satır. TypeScript analizi için scanner kendi Node.js runtime'ını kullandı ("Using embedded Node.js runtime."); ayrı bir frontend projesi kurmaya gerek kalmadı.

İlk Analiz: Ne Gördük?

Benzer kitaplar eklenmeden önceki Bookstore'u 1.0 sürümü olarak analiz ettik. Sunucunun API'sinden (api/measures/component ve api/issues/search cevaplarının özeti):

coverage 79.3
duplicated_lines_density 1.6
cognitive_complexity 72
ncloc 1836
violations 6
alert_status OK
csharpsquid:S6444 src/Bookstore.Api/Catalog/Isbn.cs 7 Pass a timeout to limit the execution time.
external_roslyn:SYSLIB1045 src/Bookstore.Api/Catalog/Isbn.cs 7 Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile
csharpsquid:S6966 src/Bookstore.Api/Program.cs 48 Await RunAsync instead.
Web:InputWithoutLabelCheck web/bookstore-web/src/app/cart/cart-page.html 19 Add an "id" attribute to this input field and associate it with a label.
Web:InputWithoutLabelCheck web/bookstore-web/src/app/cart/cart-page.html 32 Add an "id" attribute to this input field and associate it with a label.
Web:InputWithoutLabelCheck web/bookstore-web/src/app/cart/cart-page.html 33 Add an "id" attribute to this input field and associate it with a label.

Altı issue, üç kaynaktan. İkisi Sonar'ın kendi C# kuralları ve üçüncü yazıda SonarAnalyzer paketini denerken gördüklerimizle aynı. Üçü Sepet sayfasındaki label'sız input'lar: dördüncü yazıda angular-eslint'in accessibility setinden temiz geçen template'te Sonar'ın HTML analizi bir eksik buldu. Araçların kural setleri örtüşür ama aynı değildir. Biri external_roslyn önekli: Sonar'ın kendi kuralı değil, build sırasında SDK'nın analyzer'ının ürettiği bir diagnostic'i dış issue olarak içeri almış. Bu ikinci yazıdaki .globalconfig'te warning olarak bile görünmeyen, IDE'de suggestion seviyesindeki SYSLIB1045. İstemezsen /d:sonar.cs.roslyn.ignoreIssues=true ile kapanıyor; denedik, open issue sayısı 6'dan 5'e indi.

Metriklere bakarken dikkat: cognitive_complexity 72 bütün projenin toplamı, tek başına bir anlam taşımaz. Değerli olan, method bazındaki dağılım ve zaman içindeki değişim. Duplication için de aynısı: %1,6 düşük görünür, ama aynı 30 satırlık doğrulama mantığı iki module'de kopyalanmışsa bu, yüzdeden çok daha önemli bir bilgidir.

Quality Profile ve Quality Gate

Quality profile, bir dil için hangi kuralların açık olduğunu belirler. Community Build 26.10 ile yerleşik profiller üçe ayrıldı. Dokümana göre "Sonar way core" varsayılan ve güvenlik kurallarını, Major ve üzeri reliability ile Blocker maintainability kurallarını içeriyor; "Sonar way extended" reliability ve maintainability tarafında daha sıkı; "Sonar way comprehensive" önerilen bütün kuralları açıyor ve eski "Sonar way" profilinin yerini alıyor. Bookstore sunucusunda C# için sayılar 121, 173 ve 321 kural. Varsayılan profilin dar olması bilinçli bir tercih, ama bir sonucu var: cognitive complexity kuralı S3776 ya da kullanılmayan parametre kuralı S1172 core profilde kapalı. "SonarQube kurduk, karmaşıklık kontrol ediliyor" cümlesi varsayılan ayarlarla doğru değil.

Quality gate ise "bu analiz kabul edilebilir mi" sorusunun cevabı. Varsayılan "Sonar way" gate'inin koşulları sunucudan (api/qualitygates/show, id alanları çıkarıldı):

[
  {"metric": "new_violations", "op": "GT", "error": "0", "isCaycCondition": true},
  {"metric": "new_coverage", "op": "LT", "error": "80", "isCaycCondition": true},
  {"metric": "new_duplicated_lines_density", "op": "GT", "error": "3", "isCaycCondition": true},
  {"metric": "new_security_hotspots_reviewed", "op": "LT", "error": "100", "isCaycCondition": true}
]

Dört koşulun hepsi new_ ile başlıyor: gate bütün koda değil, yalnız yeni koda bakıyor. isCaycCondition, Sonar'ın "Clean as You Code" yaklaşımının koşulu olduğunu söylüyor. İlk yazıdaki ratchet fikrinin Sonar'daki karşılığı bu: eski kodda 6 issue ve %79,3 coverage olabilir, ama yeni kodda sıfır yeni issue ve %80 coverage istenir. "Yeni kod" tanımı proje ayarı; Bookstore'da varsayılan PREVIOUS_VERSION, yani /v: ile verilen sürüm değiştiğinde önceki sürümden bu yana değişen satırlar.

Yeni Kodda Gate: Benzer Kitaplar

Tahminin cevabı. Benzer kitaplar endpoint'iyle, testleri yeniden koşup /v:1.1 ile analiz ettiğimizde gate'in durumu (api/qualitygates/project_status cevabının özeti):

ERROR {'mode': 'PREVIOUS_VERSION', 'date': '2026-11-14T11:54:09+0000', 'parameter': '1.0'}
ERROR new_coverage 10.3 LT 80
OK new_duplicated_lines_density 0.0 GT 3
OK new_violations 0 GT 0

Gate kırmızı, ama sebep issue değil: yeni kodda 23 satır test edilmesi gereken kod var ve 22'si hiçbir testten geçmiyor. Sonar ne .Result'ı ne de her aday kitap için ayrı bir stok sorgusu atılmasını (N+1) bir issue olarak gördü. Varsayılan profilde thread havuzunu bloke eden bu çağrıyı yakalayan bir kural yok; üçüncü yazıdaki SDK ve üçüncü parti analyzer'lar da sessizdi. Bunu bir reviewer yakalar. Coverage koşulu ise başka bir şeyi zorladı: endpoint için iki test yazmayı. Testleri yazarken .Result'ı da await'li bir döngüye çevirdik:

List<BookSummary> similar = [];
foreach (Book candidate in candidates)
{
    if (similar.Count == 4)
    {
        break;
    }
    if (await inventory.GetAvailableAsync(candidate.Id, ct) > 0)
    {
        similar.Add(new BookSummary(candidate.Isbn, candidate.Title, candidate.Author.Name,
            candidate.Category.Name, candidate.Price, candidate.PublishedYear));
    }
}
return TypedResults.Ok(similar);
[Fact]
public async Task Similar_books_share_the_author_or_the_category()
{
    // Emma (Jane Austen, Classics): Pride and Prejudice yazar yüzünden ilk sırada.
    List<BookSummary>? similar = await client.GetFromJsonAsync<List<BookSummary>>(
        "/api/books/9780141439587/similar", TestContext.Current.CancellationToken);

    Assert.NotNull(similar);
    Assert.InRange(similar.Count, 1, 4);
    Assert.Equal("Pride and Prejudice", similar[0].Title);
    Assert.All(similar, b => Assert.True(b.Author == "Jane Austen" || b.Category == "Classics"));
}

Dosyada bilinmeyen ISBN (kitapların uluslararası standart numarası) için 404 dönen ikinci bir test de var. Aynı sürümle tekrar analiz:

OK {'mode': 'PREVIOUS_VERSION', 'date': '2026-11-14T11:54:09+0000', 'parameter': '1.0'}
OK new_coverage 100.0 LT 80
OK new_duplicated_lines_density 0.0 GT 3
OK new_violations 0 GT 0

Proje geçmişi de bu hikâyeyi gösteriyor; sürüm değişmediği için "yeni kod" hep 1.0'dan bu yana değişen satırlar:

2026-11-14T11:58:47+0000 1.1 ['Passed', '1.1']
2026-11-14T11:57:55+0000 1.1 ['Failed']
2026-11-14T11:54:09+0000 1.0 ['26.11.0.134175', '1.0']

Coverage eşiği hakkında bir uyarı. %80 bir hedef değil, bir alarm eşiği. Ekip ona oyun oynamaya başladığında, örneğin assertion'sız ya da yalnız happy path'i çağıran testlerle, metrik yükselir ama güven artmaz. Yukarıdaki test sıralamayı ve filtreyi gerçekten doğruluyor; coverage'ı bu tür testler yükseltmeli. Coverage'ın neyi ölçüp neyi ölçmediğini Test Stratejisi serisinin aynı yazısında mutation testing ile ayrıntılı gördük.

CI Entegrasyonu ve IDE

Gate'in bir anlamı olması için CI'ın sonucunu beklemesi gerekir. /d:sonar.qualitygate.wait=true ile end adımı sunucunun analizi işlemesini bekler ve gate kırmızıysa sıfırdan farklı bir kodla çıkar. Bookstore'da gate'i kıran sürümle:

INFO: ------------- Check Quality Gate status
ERROR: QUALITY GATE STATUS: FAILED - View details on http://127.0.0.1:19000/dashboard?id=bookstore
The scanner engine did not complete successfully
Post-processing failed. Exit code: 1

Community Build tek branch analiz ettiği için workflow yalnız main'e push'ta çalışıyor; testlerin ihtiyaç duyduğu SQL Server bir service container:

name: sonarqube

# Community Build tek branch analiz eder; bu yüzden yalnız main'de çalışır.
on:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  analyze:
    runs-on: ubuntu-latest
    services:
      sql:
        image: mcr.microsoft.com/mssql/server:2025-latest
        env:
          ACCEPT_EULA: "Y"
          MSSQL_SA_PASSWORD: ${{ secrets.CI_SQL_PASSWORD }}
        ports:
          - 1433:1433
    env:
      ConnectionStrings__BookstoreDb: Server=localhost,1433;Database=BookstoreDb;User Id=sa;Password=${{ secrets.CI_SQL_PASSWORD }};TrustServerCertificate=True
    steps:
      - uses: actions/checkout@v7
        with:
          # Tam geçmiş: SonarQube yeni satırları eskilerden blame ile ayırır.
          fetch-depth: 0
      - uses: actions/setup-dotnet@v6
        with:
          global-json-file: global.json
      - run: dotnet tool restore
      - name: Begin analysis
        run: >
          dotnet sonarscanner begin /k:bookstore /n:Bookstore
          /d:sonar.host.url=${{ vars.SONAR_HOST_URL }}
          /d:sonar.token=${{ secrets.SONAR_TOKEN }}
          "/d:sonar.cs.cobertura.reportsPaths=TestResults/*.cobertura.xml"
          /d:sonar.qualitygate.wait=true
      - run: dotnet build Bookstore.slnx
      - run: dotnet test --project tests/Bookstore.Api.Tests --no-build --coverage --coverage-output-format cobertura --coverage-output Bookstore.Api.Tests.cobertura.xml
      - run: dotnet test --project tests/Bookstore.Analyzers.Tests --no-build --coverage --coverage-output-format cobertura --coverage-output Bookstore.Analyzers.Tests.cobertura.xml
      - name: End analysis and wait for the quality gate
        run: dotnet sonarscanner end /d:sonar.token=${{ secrets.SONAR_TOKEN }}

Workflow'da /v: yok; her push'u ayrı bir sürüm saymak yerine, sürekli main'e akan bir projede proje ayarlarından yeni kodu "son 30 gün" gibi bir gün sayısıyla tanımlamak daha anlamlı. Sürüm etiketiyle release yapan bir ekip için PREVIOUS_VERSION doğal seçim. fetch-depth: 0 da önemli: SonarQube bir satırın ne zaman değiştiğini Git blame bilgisinden çıkarır, sığ bir clone'da bütün satırlar yeni görünebilir.

Geliştiricinin makinesinde SonarLint'in yeni adı SonarQube for IDE var; Visual Studio, Visual Studio Code ve Rider eklentisi olarak geliyor. Connected mode'da eklenti sunucudaki quality profile'ı alır ve aynı kuralları editörde çalıştırır; dokümana göre Community Build de bu moda bağlanabiliyor. Bu bölümü bir IDE'de denemedik. Bookstore'da editördeki asıl geri bildirim yine üçüncü yazıdaki Roslyn analyzer'ları; eklenti, Sonar'ın kendi kurallarını PR'dan önce görmek isteyenler için.

Üretimde Dikkat

Sunucunun kendisini yönet. SonarQube bir veritabanı, bir Elasticsearch ve düzenli güncelleme isteyen bir uygulama. Community Build her ay yeni sürüm çıkarıyor ve eski sürümler bakım almıyor; Server'ın LTA sürümleri ise edition'a göre 12 ya da 18 ay yama alıyor. Kimin güncelleyeceği, veritabanını kimin yedekleyeceği kararı kurulumdan önce verilmeli.

Gate'i bütün koda koyma. "Overall code" üzerine violations > 0 gibi bir koşul eklemek, legacy bir projede ilk gün kırmızıya döner ve ekip ya gate'i kapatır ya da toplu bastırmaya başlar. Yeni koda odaklı varsayılan gate'i koru; eski koddaki issue sayısını dashboard'da trend olarak izle.

Token'ı en az yetkiyle üret. GLOBAL_ANALYSIS_TOKEN ya da proje bazlı analiz token'ı yeterli; admin token'ını CI'a koymak, CI'ı ele geçiren birine SonarQube'un yönetimini verir. Kurulumda ürettiğimiz admin şifresi ve token deneme bittikten sonra container'larla birlikte silindi.

Sık Düşülen Tuzaklar

Eski coverage raporuyla analiz. Benzer kitaplar kodunu ekleyip testleri yeniden koşmadan, önceki koşudan kalan coverage dosyasıyla analiz ettiğimizde gate new_coverage 100.0 ile geçti: eski rapordaki satır numaraları değişen dosyada başka satırlara denk geldi. Coverage raporu her analizde aynı commit'ten, aynı pipeline'da üretilmeli.

Sonar ve Roslyn'in aynı sorunu iki kez raporlaması. Isbn.cs'in 7. satırı için hem Sonar'ın S6444'ü hem içeri alınan SYSLIB1045 göründü; üçüncü yazıda aynı satır için Meziantou'nun MA0009'unu da görmüştük. Bir sorunun sahibi tek bir araç olmalı: Roslyn'de error yaptığın bir kuralın Sonar karşılığını profilden kapat ya da dış issue'ları sonar.cs.roslyn.ignoreIssues ile al.

Scanner'ın kullanıcı profiline yazdıklarını bilmemek. Yerel bir denemede begin adımı, indirdiği JRE'yi kullanıcı klasöründeki .sonar/cache'e, bir MSBuild target dosyasını da (SonarQube.Integration.ImportBefore.targets) kullanıcı profilindeki Microsoft/MSBuild klasörüne koydu. Target dosyası end'den sonra da yerinde kaldı. CI'da önemsiz, ama geliştirici makinesinde analiz denediysen bunları bil; JRE'nin yeri için /d:sonar.userHome=... parametresi var.

PR analizi beklemek. Community Build'de PR'da gate yok. Bunu bilmeden kurulan bir sistemde ekip, merge'den sonra kırmızıya dönen ana branch'le karşılaşır. Ya PR'da başka kontroller (ikinci ve dördüncü yazıdaki build ve lint) gate olsun, ya da Developer Edition veya SonarQube Cloud değerlendirilsin.

Küçük Bir Deneme

★☆☆ Compose dosyasıyla SonarQube'u kendi makinende ayağa kaldır, kendi projeni /v:1.0 ile analiz et ve dil dağılımına bak. Scanner beklemediğin bir klasörü (üretilmiş kod, wwwroot/lib gibi) analiz ediyor mu? sonar.exclusions ile dışarıda bırak.

★★☆ Projeyi "Sonar way comprehensive" profiline al ve analizi tekrarla. Kaç yeni issue çıktı, hangileri ikinci yazıdaki .globalconfig'te zaten bir CA (SDK'nın code analysis kuralları) kuralıyla karşılanıyor? Çakışanları profilden kapatarak kendi profilini türet.

★★★ Yeni kod tanımını PREVIOUS_VERSION'dan "30 gün"e çevir, bir haftalık değişiklikleri birkaç analizle simüle et ve gate'in davranışını karşılaştır. Release'leri etiketle yapan bir ekip için hangisi daha doğru?

Böylece serinin kemik yapısı tamam: neyi bulup bulamadığını bildiğimiz analyzer'lar, kendi kuralımız, Angular'da type-aware lint ve yeni koda odaklı bir gate. Son yazıda geriye bakıyoruz: .NET 5'ten 11'e analyzer'lar, TSLint'ten ESLint 10'a geçiş, TypeScript 7 ve Oxlint gibi yeni nesil linter'lar ile Sonar'ın sürüm şemasındaki değişiklikler.