Alat lint (linter) memeriksa kode terhadap sekumpulan aturan penulisan yang lebih luas daripada pemeriksaan tipe compiler. Topik ini membahas posisi ESLint terhadap tsc, pemasangan typescript-eslint dengan flat config, serta perbedaan aturan dasar dan aturan type-aware yang memanfaatkan informasi tipe. Sebuah proyek catatan kecil memperlihatkan bug berupa pemanggilan fungsi async tanpa await yang lolos dari tsc, lalu tertangkap aturan type-aware tepat pada baris penyebabnya. Setelah membaca topik ini, pemasangan ESLint pada proyek TypeScript dapat dilakukan dengan konfigurasi yang tepat dan batasannya menjadi jelas.
Pembagian tugas antara tsc dan ESLint#
tsc menangkap pelanggaran tipe dan berhenti dengan pesan seperti TS2339 atau TS2345, sedangkan ESLint menangkap pola penulisan yang sah secara tipe tetapi berisiko saat program berjalan. Keduanya membaca kode yang sama sebagai pohon sintaks, tetapi tujuannya berbeda, sehingga temuan keduanya saling melengkapi alih-alih saling menggantikan. Aturan ESLint menyala satu per satu sesuai daftar pilihan, sedangkan pemeriksaan tipe berlaku menyeluruh atas seluruh program. Untuk proyek TypeScript, integrasinya disediakan oleh paket typescript-eslint yang berisi parser, plugin, dan kumpulan aturan siap pakai dalam satu pemasangan.
| Temuan | tsc | ESLint |
|---|---|---|
Argumen number diteruskan ke parameter string | ditolak | tidak diperiksa |
| Properti yang tidak ada pada tipe objek | ditolak | tidak diperiksa |
Fungsi async dipanggil tanpa await | lolos | ditandai no-floating-promises |
Fungsi async diteruskan ke forEach | lolos | ditandai no-misused-promises |
| Variabel yang dideklarasikan dan tidak dipakai | lolos kecuali noUnusedLocals menyala | ditandai no-unused-vars |
Tabel di atas menunjukkan pola pembagiannya secara umum. Aturan dasar seperti no-unused-vars cukup membaca sintaks dan berjalan cepat, sedangkan aturan type-aware seperti no-floating-promises memerlukan program tipe dari compiler, yaitu hasil pembacaan seluruh berkas beserta tipenya, untuk mengetahui bahwa suatu ekspresi bertipe Promise. Karena itu pemeriksaan tipe dan lint dijalankan berdampingan sebagai dua gerbang terpisah, baik pada editor maupun CI.
Instalasi dan konfigurasi flat config#
Konfigurasi ESLint modern ditulis dalam satu berkas eslint.config.js di akar proyek, dan bentuknya disebut flat config karena berisi larik konfigurasi yang digabung secara berurutan. Pemasangan dilakukan lewat npm bersama paket pendukungnya.
# tidak-dijalankan
npm install --save-dev eslint @eslint/js typescript-eslintBerkas konfigurasi berikut menggabungkan aturan dasar JavaScript dengan himpunan aturan type-aware milik typescript-eslint.
// tidak-dijalankan
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
ignores: ["dist/"],
},
);Tiap bagian pada berkas itu punya peran tetap. eslint.configs.recommended menyalakan aturan dasar JavaScript, sedangkan ...tseslint.configs.recommendedTypeChecked menambahkan himpunan aturan type-aware yang hanya berjalan bila informasi tipe tersedia. Bagian languageOptions.parserOptions menghubungkan lint dengan proyek TypeScript melalui projectService, dan ignores membuang folder hasil build dari pemeriksaan. Opsi projectService membaca tsconfig.json yang ditemukan di proyek, sehingga konfigurasi seperti berikut ikut menentukan hasil lint.
{
"compilerOptions": {
"strict": true,
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"target": "es2022"
}
}Dengan projectService, berkas-berkas yang berbagi satu tsconfig.json dimuat melalui project service milik compiler tanpa membangun program tipe berulang-ulang. Pengaturan strict pada tsconfig.json tersebut juga dibaca oleh aturan type-aware, sehingga kesimpulan tipenya sama persis dengan kesimpulan tsc.
Karena flat config berupa kode biasa, isi berkasnya dapat disusun seperti larik JavaScript lainnya. Entri tambahan dapat menyaring berkas tertentu melalui kunci files, menurunkan tingkat keparahan aturan untuk berkas pengujian, atau menonaktifkan aturan pada folder tertentu. Fungsi tseslint.config() menyusun entri-entri itu menjadi satu larik konfigurasi sekaligus memberi tipe pada hasilnya.
Lupa await yang lolos dari tsc#
Bug yang paling sering ditangkap oleh aturan type-aware adalah pemanggilan fungsi async tanpa await, yang menghasilkan promise yang tidak pernah ditunggu. Berkas laporan-salah.ts berikut memanggil simpan untuk mengarsipkan satu catatan, lalu langsung memakai hasilnya. Berkas ini dijalankan dengan node, dan keluarannya ditempel di bawahnya.
interface Catatan {
isi: string;
}
const arsip: Catatan[] = [];
async function simpan(isi: string): Promise<Catatan> {
await Promise.resolve();
const catatan: Catatan = { isi };
arsip.push(catatan);
return catatan;
}
const catatanBaru = simpan("belajar linting");
console.log("arsip:", arsip.length, "isi:", catatanBaru.isi);arsip: 0 isi: undefinedProgram berhenti tanpa error yang mencolok, tetapi hasilnya keliru tanpa pesan apa pun. Arsip masih kosong karena console.log berjalan sebelum isi fungsi simpan selesai dieksekusi, dan catatanBaru.isi bernilai undefined karena yang disimpan dalam catatanBaru adalah objek Promise, bukan catatannya. tsc ikut melaporkan kasus ini, tetapi baru pada baris pemakaian nilainya.
# memeriksa berkas yang lupa await dengan tsc
tsc --noEmit --pretty false laporan-salah.tslaporan-salah.ts(15,57): error TS2339: Property 'isi' does not exist on type 'Promise<Catatan>'.Pesan TS2339 menunjuk baris 15, yaitu baris pemakaian catatanBaru.isi, bukan baris pemanggilan simpan pada baris 14 yang menjadi penyebabnya. tsc menangkap akibat lupa await saat nilai promise dipakai seolah-olah nilai biasa, dan penyebabnya benar-benar lolos bila nilai promise itu tidak pernah dipakai sama sekali.
Aturan type-aware no-floating-promises#
Aturan no-floating-promises menandai setiap ekspresi bertipe Promise yang tidak ditunggu dan menunjuk baris pemanggilannya secara langsung. Perintah lint dijalankan dari akar proyek untuk memeriksa seluruh berkas yang tercakup oleh tsconfig.json.
# tidak-dijalankan
npx eslint .ESLint tidak terpasang pada lingkungan kerja topik ini, sehingga perintahnya tidak dieksekusi di sini, tetapi bentuk keluarannya dapat dijelaskan dari strukturnya. Setiap temuan menempati satu baris yang berisi berkas, posisi baris dan kolom, tingkat keparahan, pesan aturan, lalu nama aturannya pada kolom terakhir. Untuk laporan-salah.ts, temuan dari aturan @typescript-eslint/no-floating-promises menunjuk baris 14 tempat simpan dipanggil, bukan baris 15 tempat akibatnya muncul. Tingkat keparahan error membuat eslint berhenti dengan kode keluar bukan nol, sehingga gerbang CI dapat mengandalkannya.
Perbaikannya adalah menunggu promise dengan await, atau menambahkan operator void di depannya bila pemanggilan memang sengaja tidak ditunggu. Berkas laporan.ts berikut menerapkan keduanya dan menghasilkan keluaran yang benar.
interface Catatan {
isi: string;
}
const arsip: Catatan[] = [];
async function simpan(isi: string): Promise<Catatan> {
await Promise.resolve();
const catatan: Catatan = { isi };
arsip.push(catatan);
return catatan;
}
async function utama(): Promise<void> {
const catatanBaru = await simpan("belajar linting");
console.log("arsip:", arsip.length, "isi:", catatanBaru.isi);
}
void utama();arsip: 1 isi: belajar linting# memeriksa versi yang benar dengan tsc
tsc --noEmit --pretty false laporan.tsOperator void pada pemanggilan utama() bukan sekadar tempelan, melainkan penanda eksplisit yang dikenali aturan no-floating-promises bahwa promise itu sengaja dibiarkan tanpa ditunggu. Pemeriksaan tsc terhadap berkas ini berakhir tanpa satu pun pesan. Aturan saudaranya, no-misused-promises, menangkap pemakaian promise pada posisi yang bentuknya tidak cocok, misalnya fungsi async yang diteruskan ke forEach, karena tsc menerima kembalian apa pun untuk callback yang tipe kembaliannya void.
Tradeoff kecepatan linting type-aware#
Aturan dasar cukup membaca pohon sintaks sehingga hampir tidak menambah waktu lint, sedangkan aturan type-aware harus memuat program tipe untuk setiap berkas yang diperiksa. Biaya itu naik mengikuti jumlah berkas dan besarnya tsconfig.json yang tercakup, sehingga proyek besar terasa lebih lambat saat aturan type-aware menyala dibandingkan saat hanya aturan dasar yang aktif.
Opsi projectService mempersingkat jalur tersebut dengan memakai satu project service untuk seluruh berkas yang berbagi tsconfig.json, alih-alih membangun program tipe terpisah per berkas seperti cara lama dengan opsi project. Untuk proyek dengan satu konfigurasi, hasilnya sama, tetapi pada proyek dengan beberapa tsconfig.json atau berkas di luar cakupan, projectService lebih mudah dirawat dan lebih hemat waktu.
Kesalahan umum#
Kesalahan berikut sering muncul saat menata lint untuk proyek TypeScript.
- Mengharapkan ESLint menolak argumen dengan tipe yang salah. Pemeriksaan tipe tetap tugas tsc, sehingga keduanya dijalankan berdampingan sebagai dua gerbang terpisah.
- Memasang himpunan
recommendedsaja lalu mengharapkanno-floating-promisesikut menyala. Aturan type-aware hanya terdapat pada himpunanrecommendedTypeCheckeddan menuntutprojectServiceyang tertata padaparserOptions. - Menyalakan aturan type-aware pada berkas di luar cakupan
tsconfig.json. ESLint melaporkan berkas itu tidak tercakup proyek mana pun, sehingga berkas tersebut perlu didaftarkan atau diabaikan melaluiignores. - Membiarkan folder hasil build seperti
dist/ikut diperiksa. Isinya kode generasi yang memperlambat lint dan menimbulkan temuan yang tidak dapat diperbaiki di berkas sumbernya. - Mengawali setiap pemanggilan async dengan
voidagar aturan diam, tanpa memeriksa apakah promise-nya memang sengaja dibiarkan. Penanda itu hanya untuk pemanggilan yang benar-benar tidak ditunggu, sedangkan pemanggilan yang hasilnya diperlukan tetap wajib memakaiawait.
Ringkasan#
- tsc menangkap pelanggaran tipe seperti TS2339, sedangkan ESLint menangkap pola seperti variabel tak terpakai dan pemanggilan async tanpa
awaityang lolos dari pemeriksaan tipe. - Paket
typescript-eslintmenyediakan parser, plugin, dan himpunan aturan yang digabung dalam flat config padaeslint.config.js, denganrecommendedTypeCheckedsebagai himpunan aturan type-aware. - Opsi
projectServicemenghubungkan lint dengantsconfig.jsonproyek, sehingga aturan type-aware memakai informasi tipe yang sama dengan compiler. - Aturan type-aware menunjuk penyebab bug langsung pada baris pemanggilannya, dengan biaya waktu lint yang naik mengikuti ukuran proyek dan dapat diredam lewat
--cachesertaignores.
Referensi#
- Getting Started — typescript-eslint — Langkah pemasangan ESLint dan typescript-eslint beserta contoh flat config-nya.
- Typed Linting — typescript-eslint
— Panduan linting type-aware, termasuk opsi
projectServicedan tradeoff kinerjanya. - no-floating-promises — typescript-eslint — Aturan yang menandai promise yang tidak ditunggu beserta perbaikannya.