ESLint v9'a Geçiş: Sadece Bir Güncelleme Değil, Bir Paradigma Değişikliği

Yıllardır Frontend ekosisteminde, kod kalitesini garanti altına almak için linter'lara güvenen bir geliştirici olarak, ESLint'in evrimini yakından takip ettim. Eski dostumuz ESLint v8, uzun süre projelerimizin kod kalitesi için vazgeçilmez bir temel taşı oldu. Ancak dürüst olmak gerekirse, yapılandırması ciddi bir zorluktu. .eslintrc dosyaları büyüdükçe, farklı eklentilerin (plugin) ve paylaşılan yapılandırmaların (config) birbiriyle nasıl etkileşime girdiğini anlamak karmaşıklaşıyordu. Hangi kuralın nereden geldiğini, neden birbiriyle çakıştığını veya eklenti bağımlılıklarını çözmeye çalışmak, çoğu zaman kafa karıştırıcı ve zaman alıcı bir hata ayıklama sürecine dönüşüyordu.

ESLint ekibi 2024 yılının Nisan ayında v9.0.0 sürümünü yayınladığında, bu sadece bir sürüm numarası artışı değildi; bu, linting dünyasında bir paradigma değişikliğiydi. Bu major sürüm, ESLint'i daha esnek, daha hızlı ve JavaScript dışındaki dilleri (language-agnostic) destekleyebilen modern bir araca dönüştürme vizyonunun sonucuydu.

Eğer hala v8'deyseniz, geçişin zorluklarından çekindiğinizi biliyorum. ESLint v9 ilk çıktığında toplulukta "hazır değil" veya "ekosistemi bozdu" gibi olumsuz tepkiler olsa da, ekip bu geri bildirimlere hızla cevap verdi, araçlar geliştirdi ve artık geçiş süreci her zamankinden çok daha sorunsuz.

Şimdi, bu dönüşümün projeniz için neden bu kadar kritik olduğunu ve Flat Config ile başlayan, çoklu iş parçacığı (multithread) desteğiyle gelen performans devrimine ve dil bağımsızlığı (language-agnostic) vizyonuna kadar uzanan bu yeni paradigmanın teknik derinliklerini inceleyelim


I. Eski Dostumuz V8 ve Neden Değişim Gerekti?

ESLint v8, .eslintrc.* yapılandırma sistemi üzerine kuruluydu. Bu sistem, yıllar içinde biriken teknik borçlar nedeniyle sürdürülemez hale gelmişti. ESLint v9'un getirdiği büyük mimari değişiklikler, temelde iki ana amaca hizmet ediyordu:

  1. Dil Bağımsızlığı (Language Agnosticism): ESLint'in sadece JavaScript değil, projedeki her türlü dosya (JSON, Markdown, CSS, HTML vb.) için tek bir linter haline gelmesi hedeflendi. Bu vizyon için çekirdek yapının JavaScript'e özgü kısımlardan arındırılması gerekiyordu.
  2. API Modernizasyonu ve Performans: Eski yapı, hem performans hem de yetenek açısından ciddi mimari kısıtlamalara sahipti:
    1. Eşzamansız (Asynchronous) İşlem Yetersizliği: v8'in çekirdeği tamamen eşzamanlı (synchronous) çalışmak zorundaydı. Bu, modern ve güçlü kuralların önünde bir engeldi. Teknik bir örnek: Koddaki <a> etiketlerinde veya metinlerde bulunan URL'lerin geçerli olup olmadığını bir API'ye istek atarak (asenkron) kontrol eden bir kural yazamazdınız. Benzer şekilde, özel bir ayrıştırıcının (parser), bir GraphQL şeması gibi harici bir kaynağı asenkron olarak yüklemesi imkansızdı. Bu kısıtlama, v9'un getirdiği çoklu iş parçacığı (multithread) gibi temel performans iyileştirmelerinin de önünü tıkıyordu.
    2. Karmaşık Eklenti Yazma (Rule API): Yeni bir kural oluşturmak, kodun teknik yapısı olan "Soyut Sözdizimi Ağacı" (AST) üzerinde "Visitor" desenlerini kullanarak manuel ve karmaşık gezinme (traversal) gerektiriyordu. Bu, eklenti ekosisteminin gelişimini yavaşlatıyordu.
    3. Kaotik Konfigürasyon Yükleme ve Çözünürlük (Config): "Labirent" benzetmesinin asıl kaynağı buydu. ESLint v8, bir .js dosyası için hangi kuralların geçerli olacağına karar vermek için, o dosyanın klasöründen başlayıp bilgisayarın kök dizinine (/) kadar tüm üst klasörleri tarar ve bulduğu .eslintrc.* dosyalarını birleştirirdi (merging). Bu "katmanlı" (cascading) yapı, hangi kuralın eslint-config-airbnb'den, hangisinin eslint-plugin-import'tan geldiğini veya bir kuralın neden başka bir kuralı ezdiğini (override) hata ayıklamayı imkansız hale getiriyordu.

Bu hedeflere ulaşmak için, v9 sürümü birçok radikal kırılma değişikliğini (breaking changes) tek bir pakette topladı. Geriye dönüp baktığımızda, bu durumun geçiş sürecini zorlaştıran en büyük hata olduğu kabul edildi.


II. Karşımızdaki Yeni Kahraman: Flat Config

Flat Config, yani ESLint v9 ile varsayılan hale gelen yeni yapılandırma sistemi, eslint.config.js adlı bir JavaScript dosyası kullanır. Bu, artık JSON veya YAML tabanlı eski konfigürasyon dosyalarından kurtulduğumuz anlamına geliyor.

A. Flat Config'in Paradigması: Diziler ve Eksiksiz Denetim

Eski hiyerarşik yapılandırma (.eslintrc.*), hangi kuralın hangi dosyaya uygulanacağını belirlemek için dosya sisteminde "yukarı doğru" (cascading) bir arama yapar ve bulduğu tüm yapılandırmaları birleştirirdi. Bu, "labirent" benzetmesine yol açan kaotik bir sistemdi.

Flat Config (eslint.config.js), bu sistemi tamamen terk eder. Artık tek bir giriş noktası vardır: projenizin kök dizinindeki eslint.config.js. Bu dosya, bir yapılandırma nesneleri dizisi (array) döndürür. ESLint bir dosyayı lint edeceği zaman, bu dizideki nesneleri sırayla gezer. Bir nesnenin files özelliği (örn: files: ["**/*.ts"]) o dosyayla eşleşirse, o nesnedeki kurallar uygulanır.

İşte bu "dizi" yaklaşımının neden bir paradigma değişikliği olduğu:

1. Dinamik Konfigürasyon (JavaScript Gücü): STATİK JSON'dan DİNAMİK JS'e

Eski (v8): .eslintrc.json veya .eslintrc.yml statik dosyalardı. İçlerinde if/else gibi koşullu mantıklar kullanamazdınız. En fazla, package.json'dan bir değer okumak gibi basit şeyler için .eslintrc.js kullanabilirdiniz, ancak bu bile hantal bir yapıdaydı.

Yeni (v9): eslint.config.js gerçek bir JavaScript (veya ESM) modülüdür. Bu, yapılandırmanızı kodla oluşturabileceğiniz anlamına gelir:

  • Programatik Oluşturma: Bir monorepo'nuz varsa, packages klasöründeki her alt proje için fs (File System) modülünü kullanarak otomatik olarak yapılandırma nesneleri oluşturabilirsiniz.

Koşullu Kurallar: Ortam değişkenlerine (environment variables) göre kuralları değiştirebilirsiniz.

Örnek: Sadece CI (Continuous Integration) sunucusunda çalışırken no-console kuralını "hata" (error) olarak ayarlayabilir, yerel makinenizde (local) ise "uyarı" (warn) olarak bırakabilirsiniz:JavaScript

2. Açıklık ve Netlik (Explicit Configuration): "ÖRTÜLÜ"den "AÇIK"a

Eski (v8): En büyük sorun buydu. src/components/Button.js dosyasını lint ederken, ESLint sırayla src/components/.eslintrc, src/.eslintrc, /.eslintrc dosyalarını arar ve hepsini birleştirirdi. Bir kuralın neden aktif olduğunu veya neden çalışmadığını anlamak için bu "miras zincirini" takip etmeniz gerekirdi.

Yeni (v9): Bu "katmanlı arama" (cascading) tamamen kaldırıldı. Bir kuralın bir dosyaya uygulanmasının tek bir yolu vardır: O dosyanın, eslint.config.js dizisindeki bir nesnenin files glob deseniyle açıkça (explicitly) eşleşmesi gerekir.

Örnek:

Artık "Neden benim Button.js dosyamda 'react/rules-of-hooks' kuralı çalışmıyor?" diye sormazsınız.

Cevap basittir: eslint.config.js dosyanızı açın. files: ["**/*.js", "**/*.jsx"] (veya benzeri) ile eşleşen ve plugins: { react } içeren bir nesne var mı? Yoksa çalışmaz. Varsa çalışır. Tüm sihir (magic) bitti, sadece net kurallar kaldı.

3. Dil Bağımsızlığı için Zemin: "JS ÖNCELİKLİ"den "EŞİT VATANDAŞ"a

Eski (v8): ESLint, temelde bir JavaScript linter'ı olarak doğdu. Başka bir şeyi (HTML, CSS, Markdown) lint etmek için overrides içinde karmaşık parser ve plugin ayarları yapmanız gerekirdi. Bu diller her zaman "ikinci sınıf vatandaş" gibi hissettirirdi.

Yeni (v9): Flat Config'in bir dizi olması, bu sorunu kökten çözer. Dizideki her nesne, kendi başına bağımsız bir yapılandırma evrenidir. Bu, farklı dilleri eşit vatandaşlar olarak ele almamızı sağlar.

Örnek: Makalenizdeki kod bloğunun neden devrimsel olduğunu şöyle açıklayabiliriz:JavaScript
import html from "@html-eslint/eslint-plugin";
import css from "@eslint/css";

export default [
    {
        files: ["**/*.html"],
        plugins: { html },
        language: "html/html", // Yeni dil tanımlaması
        rules: {
            "html/no-duplicate-class": "error",
        },
    },

    {
        files: ["**/*.css"],
        plugins: { css },
        language: "css/css", // Yeni dil tanımlaması
        rules: {
            "css/no-duplicate-imports": "error",
        }
    }
];

B. Yeni Konfigürasyon Yardımcıları: Geliştirici Deneyimi (DX) İyileştirmeleri

Geçiş sürecinde en çok şikayet edilen konulardan biri, harici konfigürasyonları (özellikle popüler eklentilerden gelenleri) dahil etmenin karmaşıklığıydı. ESLint ekibi, geliştirici deneyimini (DX) iyileştirmek için iki önemli yardımcı işlev sundu: defineConfig() ve globalIgnores().

1. defineConfig() ve extends

Artık konfigürasyon dizinizi oluştururken defineConfig() kullanmalısınız.

  • Tip Güvenliği (Type Safety): Özellikle TypeScript kullanıyorsanız, defineConfig() işlevi konfigürasyonunuz için tip güvenliği sağlar, böylece hatalı veya yanlış yapılandırılmış anahtarları erkenden yakalarsınız.
  • Otomatik Düzleştirme ve Yayılım (Flattening): Artık karmaşık yayılım operatörlerini (...) kullanmak zorunda değilsiniz. defineConfig(), iç içe geçmiş nesneleri ve dizileri otomatik olarak düzleştirir.
  • extends Geri Geldi: En önemlisi, herhangi bir konfigürasyon nesnesinin içinde extends dizisini kullanabilirsiniz. Bu, harici konfigürasyonları (örneğin js.configs.recommended gibi) tutarlı ve anlaşılır bir şekilde dahil etmenizi sağlar:
import { defineConfig } from "eslint/config";
import js from "@eslint/js";
import reactPlugin from "eslint-plugin-react";

export default defineConfig({
    files: ["**/*.js", "**/*.ts", "**/*.tsx"],
    extends: [
        "js/recommended",
        reactPlugin.configs.flat.recommended,
    ],
});

2. globalIgnores()

ignores özelliğinin ne zaman global ignore (tüm proje için dosya yok sayma) ne zaman yerel exclude (konfigürasyon kapsamındaki dosyalardan çıkarma) olarak çalıştığı karışıklığa neden oluyordu. globalIgnores() yardımcı işlevi, global olarak yok sayılacak kalıpları (pattern) açıkça tanımlamanızı sağlar:

import { defineConfig, globalIgnores } from "eslint/config";

export default defineConfig([
    globalIgnores(["dist", "build", "node_modules"])
]);

III. V9'un Getirdiği Somut Avantajlar (Neden Hemen Geçmelisiniz?)

Geçişin maliyeti yüksekti, ancak ESLint v9'un getirdiği yenilikler uzun vadede ekip verimliliğinizi önemli ölçüde artıracaktır.

1. Performansta Devrim: Çoklu İş Parçacığı (Multithread) Desteği

Büyük projelerde linting süresinin CPU kısıtlı bir işlem olması nedeniyle tek iş parçacığı (single-thread) ile sınırlı kalması on yıldır süren bir sorundu.

  • Multithread Linting: ESLint v9.34.0 ile gelen çoklu iş parçacığı desteği, artık birden fazla dosyayı aynı anda işlemcinizin tüm çekirdeklerini kullanarak analiz edebiliyor.
  • Nasıl Kullanılır? CLI'da --concurrency bayrağını kullanın. En kolay yolu, ESLint'in CPU'nuz ve dosya sayınıza göre en uygun iş parçacığı sayısına karar vermesini sağlamaktır:
npx eslint --concurrency=auto

Erken testler, bu özellikle büyük projelerde 1.30x ila 3.01x hızlanma raporladı.

2. TypeScript/Frontend Desteğinde Atılımlar

Sizler TypeScript'e aşina orta ve üst düzey geliştiriciler olduğunuz için, bu konuya özel bir vurgu yapmalıyım.

  • Doğrudan TypeScript Konfigürasyon Desteği: ESLint v9.18.0 itibarıyla, konfigürasyon dosyalarınızı doğrudan eslint.config.ts, .mts veya .cts olarak kullanabilirsiniz. Bu, defineConfig() ile birlikte çalıştığında konfigürasyonunuzda eşsiz bir tip güvenliği sağlar.
  • Çekirdek Kurallarda TS Sözdizimi Desteği: ESLint v9.23.0 sürümüyle başlayarak, çekirdek kurallar (class-methods-use-this, default-param-last, no-useless-constructor gibi) TypeScript sözdizimini (syntax) desteklemeye başladı. Bu, zaman içinde no-shadow, no-magic-numbers ve no-use-before-define gibi kurallara da genişletildi. Bu entegrasyon, TypeScript projelerinde linting ayarlarını basitleştirir.

3. Otomatik Hata Düzeltmede (Autofix) Gelişmeler

  • Çatışan Autofix Tespiti: v9.23.0 sürümünden itibaren, iki kuralın aynı kod parçası için birbiriyle çelişen otomatik düzeltmeler (autofix) önermesi durumunda ESLint bir uyarı (Warning) yayınlar (ESLintCircularFixesWarning). Bu, konfigürasyon hatalarını (özellikle stil kurallarında) saptamayı kolaylaştırır.
  • Kullanılmayan Yönergeler: Kullanılmayan /* eslint */ disable yönergeleri artık varsayılan olarak uyarı verir. Bu, projenizdeki ölü kodları ve gereksiz istisnaları temizlemenize yardımcı olur.

IV. Geçiş Süreci: Adım Adım Rehber

Geçiş sancılı bir süreç olsa da, elimizde artık bu süreci yumuşatacak araçlar var.

1. Hazırlık ve Kurulum

Öncelikle, ESLint v9'un Node.js gereksinimlerini kontrol edin. v9.0.0, Node.js v18.18.0 veya v20.9.0 ve üzerini gerektirir.

# ESLint v9'a yükseltme
npm install eslint@latest --save-dev

# Flat Config için gerekli temel eklentiyi yükleyin
npm install @eslint/js --save-dev

# Eğer TypeScript kullanıyorsanız, typescript-eslint paketlerini güncelleyin
# (Bu paketler v9 ile uyumlu olmalıdır)
npm install @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev

2. Flat Config Dosyasını Oluşturma (eslint.config.js)

npx eslint --init komutu (v9 sonrası için) size temel bir yapı sunsa da, elle oluşturmak ve defineConfig kullanmak en temiz yoldur.

Yeni bir eslint.config.js dosyası oluşturalım:

import { defineConfig } from "eslint/config";
import js from "@eslint/js";
import tseslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";

export default defineConfig([
    globalIgnores(["dist/", "node_modules/", "coverage/"]),
    {
        files: ["**/*.js", "**/*.mjs"],
        extends: [js.configs.recommended],
        languageOptions: {
            ecmaVersion: "latest",
            sourceType: "module",
        },
        rules: {
            "no-console": "warn",
        },
    },
    {
        files: ["**/*.ts", "**/*.tsx"],
        plugins: {
            "@typescript-eslint": tseslint,
        },
        languageOptions: {
            parser: tsParser,
            parserOptions: {
                project: ["./tsconfig.json"],
            },
        },
        extends: [
            tseslint.configs.strictTypeChecked,
        ],
        rules: {
            "no-undef": "off",
            "@typescript-eslint/explicit-function-return-type": "warn",
        }
    }
]);

3. TypeScript Geçişi İçin Kritik Not

TypeScript kullanan ekipler için, typescript-eslint ekibi bu geçişi büyük ölçüde kolaylaştıran harika dokümantasyonlar ve yapılandırma çözümleri hazırladı. Mutlaka resmi typescript-eslint belgelerine başvurun. Zira v9 ile artık bir araya gelen ESLint ve TypeScript, birlikte en güçlü hallerine ulaşıyorlar. (Örneğin, no-unused-vars kuralının TypeScript versiyonu, daha granüler yapılandırma sunar ve değişken isimlerine göre yok sayma gibi özellikler sağlar).

4. Geçişi Görselleştirme ve Hata Ayıklama

Yeni yapılandırma dosyalarınızın beklendiği gibi çalıştığından emin olmak için güçlü araçlara ihtiyacınız olacak.

  • Config Inspector: ESLint v9, yapılandırma dosyanızın nasıl çözümlendiğini görselleştirmek için harika bir araç olan @eslint/config-inspector aracını kullanır. Bu aracı CLI üzerinden --inspect-config bayrağı ile başlatabilirsiniz:
npx eslint --inspect-config

Bu araç, hangi dosyanın hangi kuralları aldığını anlamanızda devrim yaratacaktır.


V. Sonuç: Yeni Mimarinin Sağladığı Huzur

ESLint v9'a geçiş, kuşkusuz bir mühendislik yatırımı gerektiriyor. Ancak bu geçiş, projenizi 2024 ve sonrası için modern, ölçeklenebilir ve dil-agnostik bir linting mimarisine taşıyacaktır.

Unutmayın: v9, performansı artıran çoklu iş parçacığı desteği, daha net ve tip güvenli yapılandırma (defineConfig ile) ve gelecekteki dil entegrasyonlarına (HTML, CSS) kapı açan bir temel sunar. Bu, geçici bir düzeltme değil, on yıl daha sürecek mimari bir yenilenmedir.

Subscribe to Faru Nuri Sönmez

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe