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-eslint
eslint.config.jsjs
import { 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

KonfigurasiYang diaktifkan
textjs.configs.recommendedSemua aturan kebenaran sebagai kesalahan; aturan konsol, timer, dan pengaturan interval frame sebagai peringatan
textjs.configs.strictSemuanya sebagai kesalahan, ditambah prefer-use-keymap sebagai peringatan

Atau pilih aturan satu per satu:

eslint.config.jsjs
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.

eslint.config.jsjs
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

AturanRekomendasiPerbaikan
no-dom-elementskesalahansaran
no-box-in-textkesalahan
no-unsupported-glyphskesalahanperbaikan otomatis, saran
no-console-in-componentsperingatansaran
prefer-use-intervalperingatansaran
no-sub-frame-timersperingatan
prefer-use-keymaphanya strict
valid-route-modulekesalahan
valid-metadatakesalahan
route-handler-exportskesalahansaran

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 components

no-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"> atau marker="quadrant" pada grafik. Tetapkan { allowBraille: true } untuk aplikasi yang hanya berjalan dengan text run di 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 suggestion

Untuk 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, dan not-found harus mengekspor komponen sebagai ekspor default (TX015).
  • export const dynamic harus 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 build membacanya 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:

  • metadata atau generateMetadata dalam file loading atau error
  • kedua ekspor dalam satu file
  • async generateMetadata: fungsi ini dipanggil secara sinkron, sehingga Promise tidak pernah menetapkan judul
  • title yang bukan string atau { default, template?, absolute? }, atau key yang salah eja
  • title.template tanpa %s, atau dalam halaman, tempat nilai ini tidak berpengaruh
  • description yang 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: [...] }.

app/api/health/route.tsts
export function get() { … }   // ✗ never calledexport function GET() { … }   // ✓
Edit halaman ini di GitHub