ESLint

eslint-plugin-textjs: JSX bezpieczny dla konsoli, glify o szerokości komórki, timery w rytmie klatek i kontrole App Router.

eslint-plugin-textjs wykrywa błędy Text.js w edytorze: tagi DOM w komponentach konsoli, kontenery wewnątrz tekstu, glify niedostępne w Menlo, przypadkowe wywołania console.log i eksporty App Router, które są ignorowane bez ostrzeżenia.

Konfiguracja

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

Wtyczka obsługuje ESLint 9 i 10 z płaską konfiguracją. Reguły analizują składnię i nie potrzebują informacji o typach ani parserOptions.project. Działa każdy parser obsługujący TypeScript i JSX.

Zestawy konfiguracji

KonfiguracjaCo włącza
textjs.configs.recommendedWszystkie reguły poprawności jako błędy; reguły konsoli, timerów i tempa klatek jako ostrzeżenia
textjs.configs.strictWszystko jako błędy, dodatkowo prefer-use-keymap jako ostrzeżenie

Zamiast tego wybierz reguły pojedynczo:

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

Pliki konsoli

Większość reguł dotyczy tylko plików renderujących w konsoli: plików importujących komponent lub hook (Box, Text, useInput, …) z @textjs/app lub @textjs/react. Pliki importujące react-dom lub @textjs/react/dom są kodem strony i pozostają bez zmian, więc witryna może łączyć strony DOM i aplikacje konsolowe w jednym projekcie. Import funkcji pomocniczej takiej jak tokenize lub parseMarkdown nie czyni pliku plikiem konsoli.

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

Reguły App Router kierują się nazwą pliku (page.tsx, layout.tsx, route.ts, …) w dowolnym katalogu app/, w tym src/app/. Jeśli twój katalog ma inną nazwę, przekaż { appDir: "routes" } do valid-route-module, valid-metadata i route-handler-exports.

Reguły

RegułaZalecany poziomPoprawka
no-dom-elementsbłądsugestia
no-box-in-textbłąd
no-unsupported-glyphsbłądautomatyczna poprawka, sugestia
no-console-in-componentsostrzeżeniesugestia
prefer-use-intervalostrzeżeniesugestia
no-sub-frame-timersostrzeżenie
prefer-use-keymaptylko strict
valid-route-modulebłąd
valid-metadatabłąd
route-handler-exportsbłądsugestia

no-dom-elements

<div>, <span>, elementy niestandardowe i inne tagi DOM nie istnieją w konsoli. Reconciler rozpoznaje tylko własne węzły hosta i zgłasza TX003 dla wszystkich pozostałych.

<div className="flex-col">       // ✗<Box className="flex-col">       // ✓<strong>Deployed</strong>        // ✗<Text bold>Deployed</Text>       // ✓

Sugestia zastępuje tag najbliższym komponentem (div → Box, span/p → Text, strong → Text bold, a → Link, input → Input, hr → Divider, …) i dodaje import.

no-box-in-text

<Text> to ciąg stylowanych spanów; nie może zawierać kontenera. Zagnieżdżenie <Box> lub dowolnego komponentu z kontenerem w korzeniu (<ScrollBox>, <Input>, <Code>, <ProgressBar>, <Divider>, …) powoduje błąd TX004 podczas renderowania. Ta reguła wykrywa go już w edytorze, również wewnątrz &&, operatorów trójargumentowych, fragmentów i .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 rozmieszcza każdy znak w stałej siatce komórek. Glify niedostępne w Menlo, czcionce konsoli DevTools w systemie macOS, są renderowane czcionką zastępczą szerszą od komórki, więc każda kolejna komórka się przesuwa.

  • Brajl (U+2800–U+28FF, klasyczne klatki spinnera terminalowego): użyj znaków blokowych lub ćwiartkowych (▘▝▗▖), <Spinner variant="dots"> lub marker="quadrant" na wykresach. Ustaw { allowBraille: true } dla aplikacji uruchamianych wyłącznie przez text run w terminalu.
  • Różne symbole i strzałki (U+2B00–U+2BFF, na przykład mały kwadrat U+2B1D): sugestie proponują odpowiednik z Menlo (·, ●, ←).
  • Prezentacja emoji dla symboli jednokomórkowych (▶ lub ↩ z następującym U+FE0F): selektor wariantu zamienia je w emoji zajmujące dwie komórki, podczas gdy siatka rezerwuje jedną. Automatyczna poprawka go usuwa.

Szerokie emoji (🚀, ✅) działają poprawnie: Text.js mierzy je jako dwie komórki.

no-console-in-components

Każda klatka to jedno wywołanie console.log. Twoje logi trafiają między klatki, są przewijane przez następną i pozostają ukryte w widoku przyklejonym do dołu. console.clear() usuwa samą aplikację.

"textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }]

Wyświetlaj komunikaty w interfejsie za pomocą useToast() lub komponentu <ScrollBox> z logami albo sprawdzaj stan w nakładce deweloperskiej [▼] (Ctrl+`). Przy ustawionym logger sugestia zamienia console.warn(…) na log.warn(…). Pliki testowe i renderowania @textjs/app/testing są pomijane.

prefer-use-interval

Bezpośrednie użycie setInterval w komponencie wymaga efektu, funkcji sprzątającej i referencji, aby uniknąć nieaktualnego stanu. useInterval zapewnia wszystkie trzy, a null go zatrzymuje.

useEffect(() => {  if (paused) return;  const id = setInterval(tick, 1000);  return () => clearInterval(id);}, [paused]);useInterval(tick, paused ? null : 1000); // the suggestion

W animacjach useAnimationFrame dostosowuje się do tempa klatek renderera i zatrzymuje się, gdy wyjście jest wstrzymane. Zobacz Hooki pomocnicze.

no-sub-frame-timers

Text.js emituje najwyżej jedną klatkę co 66 ms (około 15 klatek/s). Interwał 16 ms renderuje cztery razy na każdą emitowaną klatkę; dodatkowe klatki są scalane i tylko zużywają czas procesora. Reguła zgłasza opóźnienia setInterval/useInterval poniżej minInterval (domyślnie 66) oraz useAnimationFrame z fps powyżej 15.

prefer-use-keymap

Ręczne porównywanie e.key działa, ale skróty klawiszowe są niewidoczne. useKeymap umieszcza powiązania z etykietami w <KeyHelp>, <KeyHints> (powiązania z hint: true) i palecie poleceń, obsługuje sekwencje takie jak g g i zatrzymuje propagację dla dopasowanych klawiszy. Reguła zgłasza handlery useInput, które porównują co najmniej dwa literały klawiszy (minKeys), i pomija handlery odczytujące e.input do wpisywania tekstu.

useKeymap({  "up, k": { label: "Increment", run: inc, hint: true },  "down, j": { label: "Decrement", run: dec, hint: true },});

Zobacz Skróty klawiszowe.

valid-route-module

  • Pliki page, layout, loading, error i not-found muszą eksportować komponent jako eksport domyślny (TX015).
  • export const dynamic musi mieć wartość "auto", "force-static" lub "force-dynamic" (TX031). Text.js nie obsługuje wartości "error" z Next.js. Wartość jest odczytywana ze stron, layoutów i handlerów tras. W stronach i layoutach zapisz ją jako zwykły literał tekstowy: text build odczytuje ją z kodu źródłowego bez uruchamiania modułu.

valid-metadata

Metadata są scalane z layoutów, a następnie ze strony (lub not-found). Reguła zgłasza to, co router zignorowałby bez ostrzeżenia:

  • metadata lub generateMetadata w plikach loading lub error
  • oba eksporty w jednym pliku
  • async generateMetadata: funkcja jest wywoływana synchronicznie, więc Promise nigdy nie ustawia tytułu
  • title, który nie jest ciągiem znaków ani { default, template?, absolute? }, lub błędnie zapisany klucz
  • title.template bez %s lub na stronie, gdzie nie ma żadnego efektu
  • description, który nie jest ciągiem znaków

route-handler-exports

Pliki route.ts odpowiadają tylko na eksportowane metody HTTP: GET, HEAD, POST, PUT, PATCH, DELETE i OPTIONS. Zapisane małymi literami get lub Post nigdy nie są wywoływane (żądania otrzymują 405); sugestia zmienia ich nazwę. Reguła zgłasza eksporty domyślne, handlery niebędące funkcjami i inne eksporty, z wyjątkiem dynamic i revalidate z konfiguracji segmentu trasy. Przenieś wspólny kod do lib/ lub wymień dodatkowe nazwy za pomocą { allow: [...] }.

app/api/health/route.tsts
export function get() { … }   // ✗ never calledexport function GET() { … }   // ✓
Edytuj tę stronę w serwisie GitHub