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-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);

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

ConfigurazioneCosa abilita
textjs.configs.recommendedOgni regola di correttezza come errore; le regole su console, timer e cadenza dei frame come avvisi
textjs.configs.strictTutto come errore, più prefer-use-keymap come avviso

In alternativa, scegli le regole singolarmente:

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 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.

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"      },    },  },];

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

RegolaConsigliataCorrezione
no-dom-elementserroresuggerimento
no-box-in-texterrore
no-unsupported-glyphserrorecorrezione automatica, suggerimento
no-console-in-componentsavvisosuggerimento
prefer-use-intervalavvisosuggerimento
no-sub-frame-timersavviso
prefer-use-keymapsolo strict
valid-route-moduleerrore
valid-metadataerrore
route-handler-exportserroresuggerimento

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 components

no-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"> o marker="quadrant" sui grafici. Imposta { allowBraille: true } per le app eseguite solo con text run in 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 suggestion

Per 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, error e not-found devono esportare un componente come esportazione predefinita (TX015).
  • export const dynamic deve 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 build lo 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:

  • metadata o generateMetadata nei file loading o error
  • entrambe le esportazioni in un file
  • async generateMetadata: la chiamata è sincrona, quindi una Promise non imposta mai il titolo
  • title che non è una stringa o { default, template?, absolute? }, oppure una chiave scritta in modo errato
  • title.template senza %s, oppure in una pagina, dove non ha effetto
  • una description che 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: [...] }.

app/api/health/route.tsts
export function get() { … }   // ✗ never calledexport function GET() { … }   // ✓
Modifica questa pagina su GitHub