ESLint
eslint-plugin-textjs: JSX an toàn cho console, ký tự theo chiều rộng ô, bộ hẹn giờ theo nhịp frame và kiểm tra App Router.
eslint-plugin-textjs phát hiện lỗi Text.js trong trình soạn thảo: thẻ DOM trong component console, hộp bên trong văn bản, ký tự mà Menlo không có, lệnh gọi console.log ngoài ý muốn và các export App Router bị bỏ qua mà không có thông báo.
Thiết lập
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 hỗ trợ ESLint 9 và 10 với cấu hình phẳng. Các quy tắc kiểm tra cú pháp và không cần thông tin kiểu hay parserOptions.project. Mọi bộ phân tích cú pháp hiểu TypeScript và JSX đều dùng được.
Cấu hình
| Cấu hình | Nội dung bật |
|---|---|
textjs.configs.recommended | Mọi quy tắc kiểm tra tính đúng đắn ở mức lỗi; quy tắc về console, bộ hẹn giờ và nhịp frame ở mức cảnh báo |
textjs.configs.strict | Tất cả ở mức lỗi, thêm prefer-use-keymap ở mức cảnh báo |
Hoặc chọn từng quy tắc:
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" }], }, },];Tệp console
Hầu hết quy tắc chỉ áp dụng cho tệp render vào console: tệp import component hoặc hook (Box, Text, useInput, …) từ @textjs/app hoặc @textjs/react. Tệp import react-dom hoặc @textjs/react/dom là mã trang và không bị kiểm tra, nên một website có thể kết hợp trang DOM và ứng dụng console trong cùng dự án. Import tiện ích như tokenize hoặc parseMarkdown không biến tệp thành tệp 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" }, }, },];Các quy tắc App Router dựa vào tên tệp (page.tsx, layout.tsx, route.ts, …) trong mọi thư mục app/, kể cả src/app/. Nếu thư mục của bạn có tên khác, truyền { appDir: "routes" } cho valid-route-module, valid-metadata và route-handler-exports.
Quy tắc
| Quy tắc | Khuyến nghị | Sửa lỗi |
|---|---|---|
no-dom-elements | lỗi | gợi ý |
no-box-in-text | lỗi | |
no-unsupported-glyphs | lỗi | tự sửa, gợi ý |
no-console-in-components | cảnh báo | gợi ý |
prefer-use-interval | cảnh báo | gợi ý |
no-sub-frame-timers | cảnh báo | |
prefer-use-keymap | chỉ trong strict | |
valid-route-module | lỗi | |
valid-metadata | lỗi | |
route-handler-exports | lỗi | gợi ý |
no-dom-elements
<div>, <span>, phần tử tùy chỉnh và các thẻ DOM khác không tồn tại trong console. Bộ đối soát chỉ biết các nút host của nó và ném lỗi TX003 với mọi thứ khác.
<div className="flex-col"> // ✗<Box className="flex-col"> // ✓<strong>Deployed</strong> // ✗<Text bold>Deployed</Text> // ✓Gợi ý thay thẻ bằng component gần nhất (div → Box, span/p → Text, strong → Text bold, a → Link, input → Input, hr → Divider, …) và thêm import.
no-box-in-text
<Text> là một chuỗi span có định dạng; nó không thể chứa hộp. Lồng <Box> hoặc bất kỳ component nào có gốc là hộp (<ScrollBox>, <Input>, <Code>, <ProgressBar>, <Divider>, …) sẽ ném lỗi TX004 khi render. Quy tắc này phát hiện lỗi ngay trong trình soạn thảo, kể cả bên trong &&, biểu thức ba ngôi, fragment và .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 bố trí mọi ký tự trên lưới ô cố định. Ký tự mà Menlo, phông chữ console DevTools trên macOS, không có sẽ render bằng phông dự phòng rộng hơn một ô, khiến mọi ô phía sau bị lệch.
- Chữ nổi Braille (U+2800–U+28FF, các frame spinner terminal cổ điển): dùng ký tự khối hoặc góc phần tư (
▘▝▗▖),<Spinner variant="dots">hoặcmarker="quadrant"trên biểu đồ. Đặt{ allowBraille: true }cho ứng dụng chỉ chạy bằngtext runtrong terminal. - Ký hiệu và mũi tên khác (U+2B00–U+2BFF, như hình vuông nhỏ U+2B1D): gợi ý đưa ra ký tự tương đương trong Menlo (
·,●,←). - Hiển thị emoji cho ký hiệu một ô (
▶hoặc↩theo sau bởi U+FE0F): ký tự chọn biến thể biến chúng thành emoji hai ô trong khi lưới dành một ô. Tính năng tự sửa sẽ xóa ký tự chọn biến thể.
Emoji rộng (🚀, ✅) không có vấn đề: Text.js đo chúng là hai ô.
no-console-in-components
Mỗi frame là một console.log. Nhật ký riêng của bạn nằm giữa các frame, bị frame tiếp theo cuộn đi và khuất khỏi chế độ xem bám đáy. console.clear() xóa chính ứng dụng.
"textjs/no-console-in-components": ["warn", { allow: ["error"], logger: "log" }]Hiển thị thông báo trong giao diện bằng useToast() hoặc <ScrollBox> nhật ký, hoặc kiểm tra trạng thái trong Dev Overlay [▼] (Ctrl+`). Với logger, gợi ý viết lại console.warn(…) thành log.warn(…). Quy tắc bỏ qua tệp kiểm thử và các lần render bằng @textjs/app/testing.
prefer-use-interval
Dùng trực tiếp setInterval trong component cần effect, hàm dọn dẹp và ref để tránh trạng thái cũ. useInterval làm cả ba, và null dừng nó.
useEffect(() => { if (paused) return; const id = setInterval(tick, 1000); return () => clearInterval(id);}, [paused]);useInterval(tick, paused ? null : 1000); // the suggestionVới hoạt ảnh, useAnimationFrame theo nhịp frame của bộ render và dừng khi đầu ra tạm dừng. Xem Hook tiện ích.
no-sub-frame-timers
Text.js xuất tối đa một frame mỗi 66 ms (khoảng 15 fps). Khoảng lặp 16 ms render bốn lần cho mỗi frame xuất ra; các frame thừa gộp lại và chỉ tiêu tốn CPU. Quy tắc báo cáo độ trễ setInterval/useInterval dưới minInterval (mặc định 66) và useAnimationFrame có fps trên 15.
prefer-use-keymap
So sánh e.key thủ công vẫn hoạt động, nhưng phím tắt không hiển thị. useKeymap đưa các liên kết phím có nhãn vào <KeyHelp>, <KeyHints> (liên kết có hint: true) và bảng lệnh, xử lý chuỗi như g g và ngăn lan truyền sự kiện cho phím khớp. Quy tắc báo cáo handler useInput so sánh từ hai giá trị phím dạng literal trở lên (minKeys) và bỏ qua handler đọc e.input để nhập văn bản.
useKeymap({ "up, k": { label: "Increment", run: inc, hint: true }, "down, j": { label: "Decrement", run: dec, hint: true },});Xem Phím tắt.
valid-route-module
- Tệp
page,layout,loading,errorvànot-foundphải export mặc định một component (TX015). export const dynamicphải là"auto","force-static"hoặc"force-dynamic"(TX031). Text.js không hỗ trợ"error"của Next.js. Text.js đọc giá trị này từ trang, layout và route handler. Trong trang và layout, viết giá trị dưới dạng chuỗi literal thuần:text buildđọc nó từ mã nguồn mà không chạy mô-đun.
valid-metadata
Metadata hợp nhất từ các layout, rồi đến trang (hoặc not-found). Quy tắc báo cáo những gì router sẽ bỏ qua mà không có thông báo:
metadatahoặcgenerateMetadatatrong tệploadinghoặcerror- cả hai export trong cùng một tệp
async generateMetadata: hàm chạy đồng bộ, nên Promise không bao giờ đặt tiêu đềtitlekhông phải chuỗi hay{ default, template?, absolute? }, hoặc khóa viết saititle.templatekhông có%s, hoặc nằm trong trang, nơi nó không có tác dụngdescriptionkhông phải chuỗi
route-handler-exports
Tệp route.ts chỉ phản hồi các phương thức HTTP mà chúng export: GET, HEAD, POST, PUT, PATCH, DELETE và OPTIONS. get viết thường hoặc Post không bao giờ được gọi (yêu cầu nhận 405); gợi ý đổi tên chúng. Quy tắc báo cáo export mặc định, handler không phải hàm và các export khác, ngoại trừ cấu hình phân đoạn route dynamic và revalidate. Chuyển mã dùng chung vào lib/, hoặc liệt kê tên bổ sung bằng { allow: [...] }.
export function get() { … } // ✗ never calledexport function GET() { … } // ✓