ESLint
eslint-plugin-textjs: JSX compatibile con la console, glifi larghi una cella, timer sincronizzati ai frame e controlli App Router.
eslint-plugin-textjs rileva gli errori Text.js nell’editor: tag DOM nei componenti della console, box all’interno del testo, glifi assenti in Menlo, chiamate console.log fuori posto ed esportazioni App Router ignorate senza segnalazioni.
Installazione
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);Il plugin supporta ESLint 9 e 10 con la configurazione flat. Le regole sono sintattiche e non richiedono informazioni sui tipi né parserOptions.project. Funziona qualsiasi parser che comprenda TypeScript e JSX.
Configurazioni
| Configurazione | Cosa abilita |
|---|---|
textjs.configs.recommended | Ogni regola di correttezza come errore; le regole su console, timer e cadenza dei frame come avvisi |
textjs.configs.strict | Tutto come errore, più prefer-use-keymap come avviso |
In alternativa, scegli le regole singolarmente:
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 della console
La maggior parte delle regole si applica solo ai file che renderizzano nella console: file che importano un componente o un hook (Box, Text, useInput, …) da @textjs/app o @textjs/react. I file che importano react-dom o @textjs/react/dom sono codice della pagina e non vengono controllati, quindi un sito può combinare pagine DOM e app per la console nello stesso progetto. Importare un’utilità come tokenize o parseMarkdown non rende un file un file della console.
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" }, }, },];Le regole App Router si basano sul nome del file (page.tsx, layout.tsx, route.ts, …) all’interno di qualsiasi directory app/, inclusa src/app/. Se la tua ha un altro nome, passa { appDir: "routes" } a valid-route-module, valid-metadata e route-handler-exports.
Regole
| Regola | Consigliata | Correzione |
|---|---|---|
no-dom-elements | errore | suggerimento |
no-box-in-text | errore | |
no-unsupported-glyphs | errore | correzione automatica, suggerimento |
no-console-in-components | avviso | suggerimento |
prefer-use-interval | avviso | suggerimento |
no-sub-frame-timers | avviso | |
prefer-use-keymap | solo strict | |
valid-route-module | errore | |
valid-metadata | errore | |
route-handler-exports | errore | suggerimento |
no-dom-elements
<div>, <span>, elementi personalizzati e altri tag DOM non esistono nella console. Il reconciler conosce solo i propri nodi host e genera TX003 per qualsiasi altro elemento.
<div className="flex-col"> // ✗<Box className="flex-col"> // ✓<strong>Deployed</strong> // ✗<Text bold>Deployed</Text> // ✓Il suggerimento sostituisce il tag con il componente più vicino (div → Box, span/p → Text, strong → Text bold, a → Link, input → Input, hr → Divider, …) e aggiunge l’importazione.
no-box-in-text
<Text> è una sequenza di span con stili; non può contenere un box. Annidare <Box> o qualsiasi componente con un box come radice (<ScrollBox>, <Input>, <Code>, <ProgressBar>, <Divider>, …) genera TX004 durante il rendering. Questa regola lo rileva invece nell’editor, anche all’interno di &&, operatori ternari, frammenti e .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 dispone ogni carattere in una griglia di celle fisse. I glifi assenti in Menlo, il font della console DevTools su macOS, vengono renderizzati con un font di ripiego più largo di una cella, quindi tutte le celle successive si disallineano.
- Braille (U+2800–U+28FF, i classici frame degli indicatori di caricamento del terminale): usa caratteri a blocchi o quadranti (
▘▝▗▖),<Spinner variant="dots">omarker="quadrant"sui grafici. Imposta{ allowBraille: true }per le app eseguite solo context runin un terminale. - Simboli e frecce vari (U+2B00–U+2BFF, come il quadratino U+2B1D): i suggerimenti propongono l’equivalente in Menlo (
·,●,←). - Presentazione emoji dei simboli da una cella (
▶o↩seguiti da U+FE0F): il selettore di variazione li trasforma in emoji da due celle mentre la griglia ne riserva una. La correzione automatica lo rimuove.
Le emoji larghe (🚀, ✅) vanno bene: Text.js le misura come due celle.
no-console-in-components
Ogni frame è un console.log. I tuoi log finiscono tra i frame, il successivo li fa scorrere fuori dalla vista e la visualizzazione ancorata al fondo li nasconde. console.clear() cancella l’app stessa.
"textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }]Mostra i messaggi nell’interfaccia con useToast() o uno <ScrollBox> per i log, oppure ispeziona lo stato nel Dev Overlay [▼] (Ctrl+`). Con logger, il suggerimento riscrive console.warn(…) come log.warn(…). I file di test e i rendering di @textjs/app/testing vengono esclusi.
prefer-use-interval
Un setInterval usato direttamente in un componente richiede un effetto, una funzione di pulizia e un ref per evitare uno stato obsoleto. useInterval gestisce tutti e tre e null lo arresta.
useEffect(() => { if (paused) return; const id = setInterval(tick, 1000); return () => clearInterval(id);}, [paused]);useInterval(tick, paused ? null : 1000); // the suggestionPer le animazioni, useAnimationFrame segue la cadenza dei frame del renderer e si arresta quando l’output è in pausa. Consulta Hook di utilità.
no-sub-frame-timers
Text.js emette al massimo un frame ogni 66 ms (circa 15 fps). Un intervallo di 16 ms esegue il rendering quattro volte per ogni frame emesso; i frame aggiuntivi vengono accorpati e consumano solo CPU. Segnala i ritardi di setInterval/useInterval inferiori a minInterval (valore predefinito 66) e useAnimationFrame con fps superiore a 15.
prefer-use-keymap
Confrontare e.key manualmente funziona, ma le scorciatoie sono invisibili. useKeymap inserisce le associazioni con etichetta in <KeyHelp>, <KeyHints> (associazioni con hint: true) e nella palette dei comandi, gestisce sequenze come g g e interrompe la propagazione per i tasti corrispondenti. Segnala gli handler di useInput che confrontano due o più valori letterali di tasti (minKeys) ed esclude gli handler che leggono e.input per la digitazione.
useKeymap({ "up, k": { label: "Increment", run: inc, hint: true }, "down, j": { label: "Decrement", run: dec, hint: true },});Consulta Scorciatoie da tastiera.
valid-route-module
- I file
page,layout,loading,errorenot-founddevono esportare un componente come esportazione predefinita (TX015). export const dynamicdeve essere"auto","force-static"o"force-dynamic"(TX031). Text.js non supporta"error"di Next.js. Il valore viene letto da pagine, layout e route handler. Nelle pagine e nei layout, scrivilo come stringa letterale:text buildlo legge dal sorgente senza eseguire il modulo.
valid-metadata
Metadata viene unito a partire dai layout, poi dalla pagina (o da not-found). La regola segnala ciò che il router ignorerebbe senza segnalazioni:
metadataogenerateMetadatanei fileloadingoerror- entrambe le esportazioni in un file
async generateMetadata: la chiamata è sincrona, quindi una Promise non imposta mai il titolotitleche non è una stringa o{ default, template?, absolute? }, oppure una chiave scritta in modo erratotitle.templatesenza%s, oppure in una pagina, dove non ha effetto- una
descriptionche non è una stringa
route-handler-exports
I file route.ts rispondono solo ai metodi HTTP che esportano: GET, HEAD, POST, PUT, PATCH, DELETE e OPTIONS. get in minuscolo o Post non vengono mai chiamati (le richieste ricevono 405); il suggerimento li rinomina. Le esportazioni predefinite, gli handler che non sono funzioni e le altre esportazioni vengono segnalati, tranne le configurazioni del segmento di route dynamic e revalidate. Sposta il codice condiviso in lib/, oppure elenca i nomi aggiuntivi con { allow: [...] }.
export function get() { … } // ✗ never calledexport function GET() { … } // ✓