⁦ESLint⁩

⁦eslint-plugin-textjs⁩: ⁦JSX⁩ سازگار با کنسول، گلیف‌های هم‌عرض سلول، زمان‌سنج‌های هماهنگ با فریم و بررسی‌های ⁦App Router⁩.

افزونهٔ eslint-plugin-textjs اشتباهات Text.js را در ویرایشگر شناسایی می‌کند: تگ‌های DOM در کامپوننت‌های کنسول، جعبه‌های داخل متن، نویسه‌هایی که Menlo ندارد، فراخوانی‌های نابجای console.log و خروجی‌های ⁦App Router⁩ که بی‌صدا نادیده گرفته می‌شوند.

راه‌اندازی

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

این افزونه از ⁦ESLint 9⁩ و 10 با پیکربندی تخت پشتیبانی می‌کند. قواعد نحوی هستند و به اطلاعات نوع یا parserOptions.project نیاز ندارند. هر تجزیه‌گری که TypeScript و JSX را بفهمد کار می‌کند.

پیکربندی‌ها

پیکربندیمواردی که فعال می‌کند
textjs.configs.recommendedهمهٔ قواعد درستی به‌صورت خطا؛ قواعد کنسول، زمان‌سنج و آهنگ فریم به‌صورت هشدار
textjs.configs.strictهمه‌چیز به‌صورت خطا، به‌علاوهٔ prefer-use-keymap به‌صورت هشدار

به‌جای آن، قواعد را جداگانه انتخاب کنید:

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

فایل‌های کنسول

بیشتر قواعد فقط روی فایل‌هایی اعمال می‌شوند که در کنسول رندر می‌کنند: فایل‌هایی که یک کامپوننت یا هوک (Box، Text، useInput، …) را از @textjs/app یا @textjs/react وارد می‌کنند. فایل‌هایی که react-dom یا @textjs/react/dom را وارد می‌کنند کد صفحه محسوب می‌شوند و تغییری نمی‌کنند، بنابراین یک وب‌سایت می‌تواند صفحات DOM و برنامه‌های کنسول را در یک پروژه ترکیب کند. وارد کردن یک ابزار کمکی مانند tokenize یا parseMarkdown فایل را به فایل کنسول تبدیل نمی‌کند.

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

قواعد ⁦App Router⁩ بر اساس نام فایل (page.tsx، layout.tsx، route.ts، …) در هر دایرکتوری app/، از جمله src/app/، عمل می‌کنند. اگر دایرکتوری شما نام دیگری دارد، مقدار { appDir: "routes" } را به قواعد valid-route-module، valid-metadata و route-handler-exports بدهید.

قواعد

قاعدهپیشنهادیاصلاح
no-dom-elementsخطاپیشنهاد
no-box-in-textخطا
no-unsupported-glyphsخطااصلاح خودکار، پیشنهاد
no-console-in-componentsهشدارپیشنهاد
prefer-use-intervalهشدارپیشنهاد
no-sub-frame-timersهشدار
prefer-use-keymapفقط در حالت سخت‌گیرانه
valid-route-moduleخطا
valid-metadataخطا
route-handler-exportsخطاپیشنهاد

no-dom-elements

تگ‌های <div>، <span>، عناصر سفارشی و دیگر تگ‌های DOM در کنسول وجود ندارند. تطبیق‌دهنده فقط گره‌های میزبان خودش را می‌شناسد و برای هر چیز دیگری خطای TX003 می‌دهد.

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

پیشنهاد، تگ را با نزدیک‌ترین کامپوننت جایگزین می‌کند (div → Box، span/p → Text، strong → Text bold، a → Link، input → Input، hr → Divider، …) و دستور وارد کردن آن را اضافه می‌کند.

no-box-in-text

کامپوننت <Text> دنباله‌ای از spanهای سبک‌دار است؛ نمی‌تواند جعبه‌ای در خود داشته باشد. قرار دادن <Box> یا هر کامپوننتی با ریشهٔ جعبه‌ای (<ScrollBox>، <Input>، <Code>، <ProgressBar>، <Divider>، …) درون آن، هنگام رندر خطای TX004 می‌دهد. این قاعده آن را در ویرایشگر پیدا می‌کند، از جمله درون &&، عبارت‌های سه‌تایی، فرگمنت‌ها و .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⁩ هر نویسه را در یک شبکهٔ ثابت سلولی می‌چیند. نویسه‌هایی که Menlo، قلم کنسول DevTools در macOS، ندارد با قلم جایگزینی رندر می‌شوند که از یک سلول عریض‌تر است، در نتیجه همهٔ سلول‌های بعد از آن‌ها جابه‌جا می‌شوند.

  • بریل (⁦U+2800–U+28FF⁩، فریم‌های رایج نشانگر چرخان ترمینال): از نویسه‌های بلوکی یا ربعی (▘▝▗▖)، کامپوننت <Spinner variant="dots"> یا marker="quadrant" در نمودارها استفاده کنید. برای برنامه‌هایی که فقط با دستور text run در ترمینال اجرا می‌شوند، مقدار { allowBraille: true } را تنظیم کنید.
  • نمادها و پیکان‌های متفرقه (⁦U+2B00–U+2BFF⁩، مانند مربع کوچک ⁦U+2B1D⁩): پیشنهادها معادل Menlo را ارائه می‌کنند (·، ●، ←).
  • نمایش ایموجی برای نمادهای تک‌سلولی (▶ یا ↩ با ⁦U+FE0F⁩ پس از آن): انتخابگر گونه آن‌ها را به ایموجی‌های دوسلولی تبدیل می‌کند، در حالی که شبکه یک سلول رزرو می‌کند. اصلاح خودکار آن را حذف می‌کند.

ایموجی‌های عریض (🚀، ✅) مشکلی ندارند: Text.js آن‌ها را دو سلول اندازه‌گیری می‌کند.

no-console-in-components

هر فریم یک فراخوانی console.log است. گزارش‌های خود شما بین فریم‌ها قرار می‌گیرند، با فریم بعدی از دید خارج می‌شوند و در نمای چسبیده به پایین پنهان می‌مانند. فراخوانی console.clear() خود برنامه را پاک می‌کند.

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

پیام‌ها را با useToast() یا یک <ScrollBox> برای گزارش‌ها در رابط کاربری نمایش دهید، یا وضعیت را در پوشش توسعهٔ [▼] با میان‌بر (Ctrl+`) بررسی کنید. با logger، پیشنهاد فراخوانی console.warn(…) را به log.warn(…) بازنویسی می‌کند. فایل‌های آزمون و رندرهای @textjs/app/testing نادیده گرفته می‌شوند.

prefer-use-interval

یک setInterval خام در کامپوننت به یک effect، یک cleanup و یک ref نیاز دارد تا از وضعیت کهنه جلوگیری کند. هوک useInterval هر سه کار را انجام می‌دهد و مقدار null آن را متوقف می‌کند.

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

برای پویانمایی، هوک useAnimationFrame از آهنگ فریم رندرکننده پیروی می‌کند و هنگام توقف خروجی متوقف می‌شود. به هوک‌های کمکی مراجعه کنید.

no-sub-frame-timers

⁦Text.js⁩ در هر 66 ms حداکثر یک فریم تولید می‌کند (حدود 15 fps). بازهٔ 16 ms برای هر فریم خروجی چهار بار رندر می‌کند؛ فریم‌های اضافی ادغام می‌شوند و فقط CPU مصرف می‌کنند. تأخیرهای setInterval/useInterval کمتر از minInterval (پیش‌فرض 66) و استفاده از useAnimationFrame با مقدار fps بالاتر از 15 را گزارش می‌کند.

prefer-use-keymap

مقایسهٔ دستی e.key کار می‌کند، اما میان‌برها نامرئی هستند. هوک useKeymap نگاشت‌های برچسب‌دار را در <KeyHelp>، در <KeyHints> (نگاشت‌های دارای hint: true) و در پالت فرمان قرار می‌دهد، دنباله‌هایی مانند g g را مدیریت می‌کند و انتشار رویداد را برای کلیدهای منطبق متوقف می‌کند. کنترل‌گرهای useInput را که دو یا چند مقدار لفظی کلید (minKeys) را مقایسه می‌کنند گزارش می‌کند و کنترل‌گرهایی را که برای تایپ مقدار e.input را می‌خوانند نادیده می‌گیرد.

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

به میان‌برهای صفحه‌کلید مراجعه کنید.

valid-route-module

  • فایل‌های page، layout، loading، error و not-found باید یک کامپوننت را به‌صورت پیش‌فرض صادر کنند (TX015).
  • مقدار export const dynamic باید "auto"، "force-static" یا "force-dynamic" باشد (TX031). Text.js از مقدار "error" در Next.js پشتیبانی نمی‌کند. این مقدار از صفحات، چیدمان‌ها و کنترل‌گرهای مسیر خوانده می‌شود. در صفحات و چیدمان‌ها آن را به‌صورت یک مقدار لفظی رشته‌ای ساده بنویسید: دستور text build آن را بدون اجرای ماژول از کد منبع می‌خواند.

valid-metadata

⁦Metadata⁩ ابتدا از چیدمان‌ها و سپس از صفحه (یا not-found) ادغام می‌شود. این قاعده مواردی را گزارش می‌کند که مسیریاب بی‌صدا نادیده می‌گیرد:

  • metadata یا generateMetadata در فایل‌های loading یا error
  • هر دو خروجی در یک فایل
  • async generateMetadata: به‌صورت همگام فراخوانی می‌شود، بنابراین Promise هرگز عنوان را تنظیم نمی‌کند
  • title که رشته یا { default, template?, absolute? } نیست، یا کلیدی با املای نادرست
  • title.template بدون %s، یا در صفحه که اثری ندارد
  • description غیررشته‌ای

route-handler-exports

فایل‌های route.ts فقط به متدهای HTTP که صادر می‌کنند پاسخ می‌دهند: GET، HEAD، POST، PUT، PATCH، DELETE و OPTIONS. نام‌های get یا Post با حروف کوچک هرگز فراخوانی نمی‌شوند (درخواست‌ها پاسخ 405 می‌گیرند)؛ پیشنهاد نام آن‌ها را تغییر می‌دهد. خروجی‌های پیش‌فرض، کنترل‌گرهای غیرتابعی و دیگر خروجی‌ها گزارش می‌شوند، به‌جز dynamic و revalidate در پیکربندی بخش مسیر. کد مشترک را به lib/ منتقل کنید، یا نام‌های اضافی را با { allow: [...] } فهرست کنید.

app/api/health/route.tsts
export function get() { … }   // ✗ never calledexport function GET() { … }   // ✓
ویرایش این صفحه در GitHub