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

Angular ve TypeScript'te ESLint: Flat Config, typescript-eslint Type-Aware Kurallar ve angular-eslint Template Kuralları

Mert Özen 14 Kas 2026 13 dk 12 görüntülenme
Angular ve TypeScript'te ESLint: Flat Config, typescript-eslint Type-Aware Kurallar ve angular-eslint Template Kuralları

Promise'leri await etmeyen kodu ancak tip bilgisi kullanan kurallar yakalar: Bookstore'un Angular istemcisinde ng add angular-eslint'in config'i bir PR'da 2 sorun buldu, strictTypeChecked ve projectService ile bu sayı 12'ye çıktı. Yazı ESLint 10'un flat config zorunluluğunu, no-floating-promises ve no-misused-promises bug'larını, accessibility template kurallarını, type-aware lint'in maliyetini, cache'in eski sonuç vermesini ve TypeScript 7 ile uyumsuzluğu gerçek çıktılarla anlatıyor.

Sürüm notu: Bu yazıdaki kodlar Angular 22.2 (CLI 22.2.7, TypeScript 6.0), ESLint 10.15, typescript-eslint 8.71 ve angular-eslint 22.6 ile Node.js 24 üzerinde denenmiştir; GitHub Actions workflow'u actionlint 1.7.12 ile doğrulanmış, GitHub'da çalıştırılmamıştır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) Angular istemcisi bookstore-web'de Emre üç küçük isteği tek PR'da (pull request; değişikliğin ana branch'e alınması için açılan inceleme isteği) topluyor. Kitap sayfasına "Buy now" butonu: sepete ekle, sepete git. Sepete "Clear cart" butonu. Ve katalogdaki kartın yalnız başlığı değil tamamı tıklanabilir olsun:

// book-detail-page.ts
protected buyNow(quantity: number): void {
  this.cartStore.add(this.isbn(), quantity);
  this.router.navigate(['/cart']);
}

// cart-page.ts
protected clearCart(): void {
  this.store.cart()?.items.forEach(async (line) => {
    await this.store.remove(line.isbn);
  });
}
<!-- catalog-page.html -->
<li class="book-card" (click)="open(book.isbn)">

Proje ng add angular-eslint ile ESLint'i yeni almış, configuration CLI'ın ürettiği hâliyle duruyor. ng lint bu PR'da kaç sorun bulur? Ve tip bilgisini kullanan kuralları açtığımızda bu sayı ne olur?

ESLint 10: Yalnız Flat Config

ESLint 10, 6 Şubat 2026'da yayımlandı ve en büyük değişikliği eski configuration sistemini tamamen kaldırması. .eslintrc.* ve .eslintignore dosyalarına artık hiç bakılmıyor, ESLint 9'daki ESLINT_USE_FLAT_CONFIG=false kaçış yolu da kalmadı; tek format eslint.config.js (ya da .mjs, .ts). Node.js gereksinimi ^20.19.0 || ^22.13.0 || >=24. ESLint 9 ise 6 Ağustos 2026'da EOL (end of life; bakım ve güvenlik güncellemesi almayan sürüm) oldu. İki ayrıntı daha önemli: config dosyası artık çalıştırılan klasörden değil lint edilen dosyanın klasöründen başlayarak aranıyor, bu da monorepo'da birden fazla config'i mümkün kılıyor; ve eslint:recommended'a yeni kurallar eklendi, yani yükseltme tek başına yeni hata getirebilir.

Angular tarafında kurulum tek komut:

$ npx ng add angular-eslint@22.6.0 --skip-confirmation
    angular-eslint v22 matches this workspace's Angular v22.
    We detected that you have a single project in your workspace and no existing linter wired up, so we are configuring ESLint for you automatically.
CREATE eslint.config.js (1004 bytes)
UPDATE package.json (934 bytes)
UPDATE angular.json (2289 bytes)
✔ Packages installed successfully.

Üretilen eslint.config.js, .ts dosyaları için eslint.configs.recommended, typescript-eslint'in recommended ve stylistic setlerini ve angular-eslint'in tsRecommended'ını; .html template'leri için templateRecommended ve templateAccessibility'yi açıyor. Flat config bir array: her element files ile hangi dosyalara uygulanacağını seçer, sonraki element öncekini ezer. angular.json'a da ng lint'i çalıştıran bir lint target'ı eklendi. Tahminin ilk cevabı:

$ npx ng lint
src/app/catalog/catalog-page.html
  22:7  error  click must be accompanied by either keyup, keydown or keypress event for accessibility  @angular-eslint/template/click-events-have-key-events
  22:7  error  Elements with interaction handlers must be focusable                                    @angular-eslint/template/interactive-supports-focus

✖ 2 problems (2 errors, 0 warnings)

İki hata, ikisi de template'teki tıklanabilir <li> için. TypeScript tarafındaki iki method hiç uyarı almadı. tseslint.configs.recommended yalnız kodun syntax ağacına bakar; bir çağrının promise döndürüp döndürmediğini bilmesi için tip bilgisine ihtiyacı var.

typescript-eslint: Tip Bilgisiyle Lint

typescript-eslint'in kurallarının bir kısmı TypeScript derleyicisinin tip bilgisini kullanır: bir ifadenin Promise olup olmadığını, any taşıyıp taşımadığını, bir API'nin @deprecated işaretli olup olmadığını. Bunları açmak için setlerin TypeChecked sürümleri ve parser'a bir TypeScript projesi vermek gerekiyor. projectService, her dosya için hangi tsconfig'in geçerli olduğunu editörlerin kullandığı mekanizmayla kendisi bulur; Angular'ın tsconfig.app.json ve tsconfig.spec.json ayrımında elle project listesi yazmana gerek kalmaz:

// @ts-check
const eslint = require('@eslint/js');
const { defineConfig } = require('eslint/config');
const tseslint = require('typescript-eslint');
const angular = require('angular-eslint');

module.exports = defineConfig([
  {
    files: ['**/*.ts'],
    extends: [
      eslint.configs.recommended,
      tseslint.configs.strictTypeChecked,
      tseslint.configs.stylisticTypeChecked,
      angular.configs.tsRecommended,
    ],
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: __dirname,
      },
    },
    processor: angular.processInlineTemplates,
    rules: {
      '@angular-eslint/directive-selector': [
        'error',
        {
          type: 'attribute',
          prefix: 'app',
          style: 'camelCase',
        },
      ],
      '@angular-eslint/component-selector': [
        'error',
        {
          type: 'element',
          prefix: 'app',
          style: 'kebab-case',
        },
      ],
    },
  },
  {
    files: ['**/*.html'],
    extends: [angular.configs.templateRecommended, angular.configs.templateAccessibility],
    rules: {},
  },
]);

recommendedTypeChecked yerine strictTypeChecked'ı seçtik; typescript-eslint dokümanı strict setleri semantic versioning açısından "stabil" saymıyor, yani major sürüm beklemeden kural değişebilir; ve bu setleri geliştiricilerinin önemli bir kısmı TypeScript'e iyi hâkim olan ekiplere öneriyor. Bookstore gibi küçük ve bakımlı bir codebase'te bu kabul edilebilir; legacy bir projede recommendedTypeChecked ile başla. Aynı PR şimdi:

$ npx ng lint
src/app/app.config.ts
  9:23  error  `withFetch` is deprecated. `withFetch` is not required anymore. `FetchBackend` is the default `HttpBackend`  @typescript-eslint/no-deprecated

src/app/cart/cart-page.ts
  31:13  error  Unsafe assignment of an `any` value                                     @typescript-eslint/no-unsafe-assignment
  32:29  error  Unsafe argument of type `any` assigned to a parameter of type `string`  @typescript-eslint/no-unsafe-argument
  32:38  error  Unsafe member access .detail on an `any` value                          @typescript-eslint/no-unsafe-member-access
  32:57  error  Unsafe member access .title on an `any` value                           @typescript-eslint/no-unsafe-member-access
  37:38  error  Promise returned in function argument where a void return was expected  @typescript-eslint/no-misused-promises

src/app/catalog/book-detail-page.ts
  29:5  error  Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises
  30:5  error  Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises

src/app/catalog/catalog-page.html
  22:7  error  click must be accompanied by either keyup, keydown or keypress event for accessibility  @angular-eslint/template/click-events-have-key-events
  22:7  error  Elements with interaction handlers must be focusable                                    @angular-eslint/template/interactive-supports-focus

src/main.ts
  6:11  error  Prefer the safe `: unknown` for a `catch` callback variable                                                         @typescript-eslint/use-unknown-in-catch-callback-variable
  6:19  error  Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function  @typescript-eslint/no-confusing-void-expression

✖ 12 problems (12 errors, 0 warnings)

On iki hata. Üçü PR'daki yeni kodda, yedisi PR'dan önce de var olan kodda; type-aware kuralları açmak, ilk yazıda .NET tarafında gördüğümüz gibi bir baseline sorusunu da beraberinde getiriyor.

buyNow'daki iki no-floating-promises gerçek bir bug. cartStore.add API'ye bir istek atıp cevabı bekleyen bir async method; await edilmediği için router.navigate hemen çalışır ve sepet sayfası çoğu zaman eski sepeti gösterir. İstek hata verirse, örneğin stok yetmezse, hata hiçbir yerde yakalanmaz. clearCart'taki no-misused-promises daha sinsi: forEach, callback'in döndürdüğü promise'i beklemez. Silme istekleri paralel çıkar, her cevap bütün sepet state'ini değiştirir ve son gelen cevap kazanır; sepette bir kitap kalmış gibi görünebilir.

Diğer bulgular PR'dan bağımsız ama değerli. no-deprecated, Angular 22'de withFetch'in artık gerekmediğini söylüyor: FetchBackend varsayılan. Dört no-unsafe-* hatası tek bir satırdan geliyor: HttpErrorResponse.error'ın tipi any ve problem?.detail zinciri derleyicinin hiç kontrol etmediği bir yol.

Bulguları Düzeltmek

protected async buyNow(quantity: number): Promise<void> {
  await this.cartStore.add(this.isbn(), quantity);
  await this.router.navigate(['/cart']);
}
// Sayfanın gösterdiği RFC 9457 problem cevabı alanları.
interface ProblemDetails {
  title?: string;
  detail?: string;
}

// ...
const problem =
  error instanceof HttpErrorResponse ? (error.error as ProblemDetails | null) : null;

protected async clearCart(): Promise<void> {
  // Her seferinde tek istek: her cevap bütün sepet state'inin yerini alıyor.
  for (const line of this.store.cart()?.items ?? []) {
    await this.store.remove(line.isbn);
  }
}

as ProblemDetails bir tip iddiası; API'nin gerçekten bu şekli döndürdüğünü TypeScript doğrulamaz. Ama artık iddia açıkça yazılı ve problem.detail'in tipi string | undefined. API'nin şeklini runtime'da doğrulamak istersen bir schema kütüphanesi gerekir; lint'in işi seni bu kararı vermeye zorlamak. main.ts'teki catch callback'i (err: unknown) => { console.error(err); } oldu, provideHttpClient(withFetch()) da provideHttpClient(). Bu değişikliklerden sonra ng lint "All files pass linting." diyor, ng build ve Vitest testleri de geçiyor.

angular-eslint ve Template Kuralları

angular-eslint iki parça: TypeScript tarafındaki decorator ve class kuralları (tsRecommended; prefer-inject, prefer-standalone, no-output-native gibi 13 kural) ve HTML template'ler için ayrı bir parser ile çalışan template kuralları. templateRecommended şu an dört kural içeriyor: banana-in-box, eqeqeq, no-negated-async ve eski *ngIf/*ngFor yerine @if/@for isteyen prefer-control-flow. templateAccessibility ise 11 accessibility (erişilebilirlik) kuralı; Bookstore'un kartını yakalayanlar bu setten.

Kartın tamamını tıklanabilir yapmanın doğru yolu <li>'ye (click) vermek değil. Klavyeyle gezinen bir kullanıcı o elemana Tab ile gelemez, ekran okuyucu onu bir link olarak duyurmaz. Zaten var olan başlık linkini CSS ile kartın tamamına yaydık:

<li class="book-card">
  <a class="card-link" [routerLink]="['/books', book.isbn]">{{ book.title }}</a>
/* Başlık linki kartın tamamını kaplar; kart hem tıklanabilir hem klavyeyle erişilebilir. */
.book-card .card-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.book-card button {
  position: relative;
  z-index: 1;
}

Önerilen setlerin dışında bakmaya değer kurallar da var. @angular-eslint/no-uncalled-signals, bir signal'ı çağırmadan koşulda kullanmayı yakalar: if (this.added) her zaman true'dur. @angular-eslint/no-implicit-take-until-destroyed, injection context dışında argümansız takeUntilDestroyed() çağrısını raporlar. İkisi de varsayılan olarak kapalı; RxJS'i yoğun kullanan bir ekip için açmaya değer.

Şimdi bir sınır. Bookstore'un kataloğunda şu satır var ve lint ondan hiç şikâyet etmiyor:

<button type="button" (click)="cartStore.add(book.isbn)">Add to cart</button>

cartStore.add bir promise döndürüyor ve template'te kimse onu beklemiyor; buyNow'daki sorunun aynısı. Ama typescript-eslint'in tip bilgili kuralları yalnız .ts dosyalarında çalışır, template'teki event binding'ler onun gördüğü kodun içinde değil. Hata olursa Angular'ın provideBrowserGlobalErrorListeners'ı yakalayıp konsola yazar, ama kullanıcı hiçbir şey görmez. Bookstore'un kuralı: template'ten çağrılan async işler component'te bir protected async method'a sarılır ve hata durumunu kendisi yönetir; bu, review checklist'inde bir satır, lint kuralı değil.

Maliyet: Süre, Cache ve Nerede Çalıştırılır?

Tip bilgisinin bedeli, ESLint'in önce TypeScript programını kurması. Bookstore'un küçük istemcisinde npx eslint src'nin duvar saati süresi üç ölçümde CLI'ın ürettiği config ile 0,51–0,52 saniye, strictTypeChecked ile 0,93–0,95 saniye. Hangi kuralın ne kadar sürdüğünü TIMING environment variable'ı gösteriyor:

$ TIMING=1 npx eslint src
Rule                                                  | Time (ms) | Relative
:-----------------------------------------------------|----------:|--------:
@typescript-eslint/no-unsafe-enum-assignment          |    35.279 |    25.1%
@typescript-eslint/no-misused-promises                |    20.041 |    14.2%
@typescript-eslint/no-unsafe-assignment               |    17.792 |    12.6%
@typescript-eslint/no-unused-vars                     |    14.070 |    10.0%
@typescript-eslint/no-floating-promises               |    11.417 |     8.1%
@angular-eslint/template/click-events-have-key-events |     8.602 |     6.1%

Listenin başındaki kuralın kendisi pahalı değil; tip bilgisi isteyen ilk kural olduğu için TypeScript programının kurulma süresi büyük ihtimalle onun hanesine yazılıyor. Büyük bir projede asıl maliyet bu kurulum; typescript-eslint dokümanı da küçük projelerde bu sürenin ihmal edilebilir, büyüklerde daha uzun olduğunu söylüyor ve editör eklentilerinin cache sayesinde bu maliyeti hissettirmediğini ekliyor.

ESLint'in kendi cache'i (--cache) sonuçları dosya bazında saklar. Bookstore'da ilk koşu 1,11 saniye, değişiklik olmadan ikinci ve üçüncü koşu 0,53 ve 0,50 saniye, tek dosyaya dokunduktan sonra 0,74 saniye sürdü. Ama ESLint dokümanı açıkça uyarıyor: cache dosyalar arasındaki bağımlılıkları izlemez, bağımlı olduğu bir dosya değiştiğinde bir dosyanın eski sonucu yeniden kullanılır ve tip bilgili kurallar bu yüzden eski sonuç raporlayabilir. Bunu denedik. CartStore.add'in void döndürdüğü bir hâlde buyNow'u await'siz yazıp cache'li lint aldık, sonra yalnız cart-store.ts'te add'i yeniden Promise<void> yaptık:

$ npx eslint --cache src
src/app/catalog/book-detail-page.ts
  24:5   error  Unexpected `await` of a non-Promise (non-"Thenable") value                                              @typescript-eslint/await-thenable
  24:11  error  Placing a void expression inside another expression is forbidden. Move it to its own statement instead  @typescript-eslint/no-confusing-void-expression

✖ 2 problems (2 errors, 0 warnings)
$ npx eslint src
src/app/catalog/book-detail-page.ts
  29:5  error  Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises

✖ 1 problem (1 error, 0 warnings)

Cache'li koşu, artık geçerli olan bir await'i hâlâ hata sayıyor ve gerçek floating promise'i görmüyor; book-detail-page.ts değişmediği için eski sonucu döndürdü. Cache'siz koşu doğru sonucu verdi. Bookstore'un kararı:

  • Editörde: ESLint eklentisi, kaydetmeden yazarken. Tip bilgili kurallar burada da çalışır, maliyeti eklentinin cache'i taşır.
  • Commit öncesi: yalnız staged dosyalar, Coding Conventions serisinin "Kuralları Otomatikleştirmek" yazısındaki lint-staged kurulumuyla. Burada --cache kabul edilebilir, çünkü son söz CI'da.
  • CI'da: cache'siz, bütün proje, --max-warnings 0.

Type-aware lint'i her kaydetmede komut satırından bütün projeye çalıştırmak ise yapmaman gereken şey: editör eklentisi aynı bulguyu zaten gösteriyor, komut satırı her seferinde TypeScript programını sıfırdan kuruyor.

CI ve Prettier ile Sınır

package.json'a bir CI script'i ve workflow:

"lint": "ng lint",
"lint:ci": "ng lint --max-warnings 0"
name: web-lint

on:
  pull_request:
    paths: ['web/bookstore-web/**']
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  lint:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: web/bookstore-web
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
          cache-dependency-path: web/bookstore-web/package-lock.json
      - run: npm ci
      # Cache'siz tam lint: tip bilgili sonuçlar başka dosyalara bağlı.
      - run: npm run lint:ci

--max-warnings 0, ikinci yazıdaki .NET tarafındaki hedefli hataların tersi bir tercih: ESLint'te warning seviyesindeki kurallar sayısı arttığında görünmez olur, bu yüzden Bookstore'un web tarafında warning bütçesi sıfır. Bir kuralı warning olarak tutmanın tek anlamı, "editörde göster ama CI'da kırma" olmamalı; o zaman kuralı kapatmak daha dürüst. GitHub Actions serisinin "GitHub Actions ile Angular CI" yazısındaki pipeline'a bu adımı ayrı bir job olarak ekleyebilirsin.

Prettier ile sınır net: biçim Prettier'ın, kalite ve convention ESLint'in. tseslint.configs.stylisticTypeChecked adına rağmen bir formatting seti değil, yazım tercihleriyle ilgili kurallar içeriyor ve Prettier ile çakışmıyor. Bir biçim kuralı eklemek zorunda kalırsan eslint-config-prettier'ı array'in sonuna koy; ayrıntısı Coding Conventions serisinin "Angular ve TypeScript'te Convention: Style Guide, Prettier ve ESLint'in Sorumluluk Paylaşımı" yazısında.

TypeScript 7 Notu

Sürüm notunda TypeScript 6.0 yazmasının sebebi peer dependency'ler. Kurulu paketlere bakınca:

$ node -e "console.log(require('@angular/compiler-cli/package.json').peerDependencies.typescript)"
>=6.0 <6.1
$ npm view typescript-eslint@8.72.2 peerDependencies
{
  eslint: '^8.57.0 || ^9.0.0 || ^10.0.0',
  typescript: '>=4.8.4 <6.1.0'
}

Angular 22.2'nin derleyicisi TypeScript 6.0 istiyor; typescript-eslint'in yazım anındaki son sürümü (8.72.2, 11 Kasım 2026) de 6.1'in altını. Go ile yeniden yazılan TypeScript 7.0 npm'de latest etiketiyle yayında, ama 7.0 bir programatik API sunmuyor ve typescript-eslint'in tip bilgili kuralları tam olarak bu API'ye dayanıyor. typescript-eslint'in GitHub'daki ilgili issue'sunda bakımcıların cevabı kısa: TS 7 bir API sağlayana kadar yapabilecekleri bir şey yok. TypeScript 7.1 beta'sı (Ekim 2026) yeni bir API ile geliyor; typescript-eslint'in onu ne zaman destekleyeceği yazım anında belli değil.

TypeScript 6.0'ın bir değişikliği de bu yazıyı ilgilendiriyor: strict artık varsayılan olarak açık. Bookstore'un tsconfig.json'ında "strict": true satırı yok; tsconfig'siz bir dosyada x.length denediğimizde derleyici yine error TS18047: 'x' is possibly 'null'. verdi. Type checker, spektrumun en ucuz katmanı olarak yerinde.

Üretimde Dikkat

Strict seti yükseltmeleri dikkatle oku. typescript-eslint'in strict setleri minor sürümlerde yeni kural alabiliyor; package.json'da caret aralığı varsa bir npm install CI'ı kırabilir. ng add'in typescript-eslint'i tam sürümle (8.71.0) yazması bu yüzden iyi bir varsayılan; yükseltmeyi .NET tarafındaki AnalysisLevel gibi ayrı bir PR'da yap.

Legacy bir Angular projesinde type-aware lint'i bir kerede açma. Bookstore'da PR dışındaki kodda 7 hata çıktı; yüz bin satırlık bir projede bu sayı binlerle ifade edilir. Önce no-floating-promises ve no-misused-promises gibi gerçek bug yakalayan birkaç kuralı tek tek aç, no-unsafe-* ailesini any temizliği ilerledikçe ekle.

Lint'i tek kalite kapısı sanma. Template'teki promise'ler gibi araçların görmediği alanlar var; bunlar Angular derleyicisinin extended diagnostic'lerinde, testlerde ve review'da yakalanır.

Sık Düşülen Tuzaklar

projectService olmadan type-aware set açmak. Parser'a bir TypeScript projesi verilmezse tip bilgisi isteyen kurallar çalışamaz. Bookstore'da iki satırı silince ESLint ilk tip bilgili kuralda durdu: Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires type information, but don't have parserOptions set to generate type information for this file. tsconfigRootDir'ı da unutma; yoksa lint'i başka bir klasörden çalıştırdığında yanlış tsconfig bulunabilir.

Eski .eslintrc.json örneklerini ESLint 10'a taşımak. İnternetteki Angular ESLint örneklerinin çoğu hâlâ .eslintrc.json biçiminde; ESLint 10 bu dosyayı okumuyor. Taşıma ve TSLint'ten gelen eski projeler altıncı yazının konusu.

void ile susturmak. no-floating-promises'ı geçmenin en kolay yolu void this.router.navigate(...) yazmak. Bazen doğrudur, Bookstore'un App component'indeki void this.cartStore.load() gibi; ama buyNow'da bug'ı saklardı. void, "bu promise'in sonucunu ve hatasını bilerek önemsemiyorum" demek; code review'da bu cümleyi sorgula.

Cache dosyasını CI cache'ine koymak. .eslintcache'i CI'da saklarsan, yukarıdaki eski sonuç sorunu bu kez herkesin PR'ında yaşanır.

Küçük Bir Deneme

★☆☆ Kendi Angular projende tseslint.configs.recommended'ı recommendedTypeChecked ile değiştir ve projectService'i aç. Kaç yeni hata çıktı? Bunlardan kaçı no-floating-promises ya da no-misused-promises?

★★☆ Kendi projende npx eslint --cache ile npx eslint'i art arda çalıştır ve süreleri karşılaştır. Sonra tipleri başka bir dosyada tanımlanan bir fonksiyonun dönüş tipini değiştirip iki komutu yeniden çalıştır. Cache'li sonuç ne zaman yanıltıyor?

★★★ @angular-eslint/no-uncalled-signals ve no-implicit-take-until-destroyed kurallarını aç ve projende bir RxJS subscription'ını takeUntilDestroyed() ile constructor dışında bağla. Kural ne diyor? Aynı hatayı Angular'ın runtime'da nasıl raporladığıyla karşılaştır.

Artık hem .NET hem Angular tarafında kurallar build ve lint sırasında çalışıyor. Ama ikisinin sonuçlarını tek bir yerde, zaman içinde ve yalnız yeni koda odaklanarak görmek başka bir araç istiyor. Sıradaki yazıda SonarQube'u Bookstore'un yanında Docker ile ayağa kaldırıp quality gate kuracağız.