ESLint
eslint-plugin-textjs: JSX aman untuk konsol, glif selebar sel, timer sesuai laju frame, dan pemeriksaan App Router.
eslint-plugin-textjs mendeteksi kesalahan Text.js di editor: tag DOM dalam komponen konsol, kotak di dalam teks, glif yang tidak tersedia di Menlo, pemanggilan console.log yang terselip, dan ekspor App Router yang diabaikan tanpa pemberitahuan.
Penyiapan
bun add -d eslint eslint-plugin-textjs typescript-eslintimport { defineConfig } from "eslint/config";import tseslint from "typescript-eslint";import textjs from "eslint-plugin-textjs";export default defineConfig(tseslint.configs.recommended, textjs.configs.recommended);Plugin ini mendukung ESLint 9 dan 10 dengan konfigurasi flat. Aturannya bersifat sintaktis dan tidak memerlukan informasi tipe atau parserOptions.project. Parser apa pun yang memahami TypeScript dan JSX dapat digunakan.
Konfigurasi
| Konfigurasi | Yang diaktifkan |
|---|---|
textjs.configs.recommended | Semua aturan kebenaran sebagai kesalahan; aturan konsol, timer, dan pengaturan interval frame sebagai peringatan |
textjs.configs.strict | Semuanya sebagai kesalahan, ditambah prefer-use-keymap sebagai peringatan |
Atau pilih aturan satu per satu:
import textjs from "eslint-plugin-textjs";export default [ { plugins: { textjs }, rules: { "textjs/no-box-in-text": "error", "textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }], }, },];File konsol
Sebagian besar aturan hanya berlaku untuk file yang me-render ke konsol: file yang mengimpor komponen atau hook (Box, Text, useInput, …) dari @textjs/app atau @textjs/react. File yang mengimpor react-dom atau @textjs/react/dom adalah kode halaman dan tidak diperiksa, sehingga situs web dapat menggabungkan halaman DOM dan aplikasi konsol dalam satu proyek. Mengimpor utilitas seperti tokenize atau parseMarkdown tidak menjadikan suatu file sebagai file konsol.
import textjs from "eslint-plugin-textjs";export default [ textjs.configs.recommended, { settings: { textjs: { packages: ["@acme/console-ui"], // also re-exports Text.js components consoleFiles: "imports", // or "all" }, }, },];Aturan App Router mengikuti nama file (page.tsx, layout.tsx, route.ts, …) di dalam direktori app/ mana pun, termasuk src/app/. Jika direktori Anda memiliki nama lain, teruskan { appDir: "routes" } ke valid-route-module, valid-metadata, dan route-handler-exports.
Aturan
| Aturan | Rekomendasi | Perbaikan |
|---|---|---|
no-dom-elements | kesalahan | saran |
no-box-in-text | kesalahan | |
no-unsupported-glyphs | kesalahan | perbaikan otomatis, saran |
no-console-in-components | peringatan | saran |
prefer-use-interval | peringatan | saran |
no-sub-frame-timers | peringatan | |
prefer-use-keymap | hanya strict | |
valid-route-module | kesalahan | |
valid-metadata | kesalahan | |
route-handler-exports | kesalahan | saran |
no-dom-elements
<div>, <span>, elemen kustom, dan tag DOM lainnya tidak tersedia di konsol. Reconciler hanya mengenali node host miliknya sendiri dan melempar TX003 untuk yang lainnya.
<div className="flex-col"> // ✗<Box className="flex-col"> // ✓<strong>Deployed</strong> // ✗<Text bold>Deployed</Text> // ✓Saran ini mengganti tag dengan komponen terdekat (div → Box, span/p → Text, strong → Text bold, a → Link, input → Input, hr → Divider, …) dan menambahkan impor.
no-box-in-text
<Text> adalah rangkaian span bergaya; komponen ini tidak dapat berisi kotak. Menyarangkan <Box> atau komponen apa pun yang berakar pada kotak (<ScrollBox>, <Input>, <Code>, <ProgressBar>, <Divider>, …) akan melempar TX004 saat rendering. Aturan ini mendeteksinya di editor, termasuk di dalam &&, ekspresi ternary, fragmen, dan .map().
<Text>Status <Box /></Text> // ✗<Box className="flex-row gap-1"><Text>Status</Text><Box /></Box> // ✓<Text>Press <Kbd>⌘K</Kbd> <Spinner /></Text> // ✓ inline componentsno-unsupported-glyphs
Text.js menata setiap karakter dalam kisi sel tetap. Glif yang tidak tersedia di Menlo, font konsol DevTools di macOS, di-render menggunakan font cadangan yang lebih lebar dari satu sel, sehingga setiap sel setelahnya bergeser.
- Braille (U+2800–U+28FF, frame spinner terminal klasik): gunakan karakter blok atau kuadran (
▘▝▗▖),<Spinner variant="dots">ataumarker="quadrant"pada grafik. Tetapkan{ allowBraille: true }untuk aplikasi yang hanya berjalan dengantext rundi terminal. - Simbol dan panah lainnya (U+2B00–U+2BFF, seperti persegi kecil U+2B1D): saran menawarkan padanan di Menlo (
·,●,←). - Tampilan emoji pada simbol satu sel (
▶atau↩diikuti U+FE0F): pemilih variasi mengubahnya menjadi emoji dua sel sementara kisi menyediakan satu sel. Perbaikan otomatis menghapus pemilih tersebut.
Emoji lebar (🚀, ✅) tidak bermasalah: Text.js mengukurnya sebagai dua sel.
no-console-in-components
Setiap frame adalah satu console.log. Log Anda sendiri muncul di antara frame, tergulir keluar oleh frame berikutnya, dan tersembunyi dalam tampilan yang selalu mengikuti bagian terbawah. console.clear() menghapus aplikasi itu sendiri.
"textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }]Tampilkan pesan di UI dengan useToast() atau <ScrollBox> log, atau periksa state di Dev Overlay [▼] (Ctrl+`). Dengan logger, saran mengubah console.warn(…) menjadi log.warn(…). File pengujian dan rendering @textjs/app/testing dilewati.
prefer-use-interval
setInterval yang digunakan langsung dalam komponen memerlukan effect, cleanup, dan ref untuk menghindari state kedaluwarsa. useInterval menangani ketiganya, dan null menghentikannya.
useEffect(() => { if (paused) return; const id = setInterval(tick, 1000); return () => clearInterval(id);}, [paused]);useInterval(tick, paused ? null : 1000); // the suggestionUntuk animasi, useAnimationFrame mengikuti pengaturan interval frame renderer dan berhenti saat output dijeda. Lihat Hook utilitas.
no-sub-frame-timers
Text.js mengeluarkan paling banyak satu frame setiap 66 ms (sekitar 15 fps). Interval 16 ms me-render empat kali per frame yang dikeluarkan; frame tambahan digabungkan dan hanya menghabiskan CPU. Aturan ini melaporkan jeda setInterval/useInterval di bawah minInterval (default 66) dan useAnimationFrame dengan fps di atas 15.
prefer-use-keymap
Membandingkan e.key secara manual berfungsi, tetapi pintasannya tidak terlihat. useKeymap menempatkan binding berlabel di <KeyHelp>, <KeyHints> (binding dengan hint: true), dan palet perintah, menangani urutan seperti g g, serta menghentikan propagasi untuk tombol yang cocok. Aturan ini melaporkan handler useInput yang membandingkan dua atau lebih literal tombol (minKeys) dan melewati handler yang membaca e.input untuk pengetikan.
useKeymap({ "up, k": { label: "Increment", run: inc, hint: true }, "down, j": { label: "Decrement", run: dec, hint: true },});Lihat Pintasan keyboard.
valid-route-module
- File
page,layout,loading,error, dannot-foundharus mengekspor komponen sebagai ekspor default (TX015). export const dynamicharus bernilai"auto","force-static", atau"force-dynamic"(TX031). Text.js tidak mendukung"error"milik Next.js. Nilai ini dibaca dari halaman, layout, dan route handler. Dalam halaman dan layout, tulis sebagai literal string biasa:text buildmembacanya dari kode sumber tanpa menjalankan modul.
valid-metadata
Metadata digabungkan dari layout, lalu halaman (atau not-found). Aturan ini melaporkan hal yang akan diabaikan router tanpa pemberitahuan:
metadataataugenerateMetadatadalam fileloadingatauerror- kedua ekspor dalam satu file
async generateMetadata: fungsi ini dipanggil secara sinkron, sehingga Promise tidak pernah menetapkan judultitleyang bukan string atau{ default, template?, absolute? }, atau key yang salah ejatitle.templatetanpa%s, atau dalam halaman, tempat nilai ini tidak berpengaruhdescriptionyang bukan string
route-handler-exports
File route.ts hanya menjawab metode HTTP yang diekspornya: GET, HEAD, POST, PUT, PATCH, DELETE, dan OPTIONS. get atau Post dengan huruf kecil tidak pernah dipanggil (permintaan mendapat 405); saran mengganti namanya. Ekspor default, handler yang bukan fungsi, dan ekspor lainnya dilaporkan, kecuali konfigurasi segmen route dynamic dan revalidate. Pindahkan kode bersama ke lib/, atau cantumkan nama tambahan dengan { allow: [...] }.
export function get() { … } // ✗ never calledexport function GET() { … } // ✓