⁦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" في المخططات. اضبط { allowBraille: true } للتطبيقات التي تعمل فقط باستخدام text run في الطرفية.
  • رموز وأسهم متنوعة (⁦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 مباشرة في مكوّن إلى تأثير وتنظيف ومرجع لتجنب الحالة القديمة. يتولى 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 إلى العرض أربع مرات لكل إطار مُخرج؛ وتُدمج الإطارات الإضافية ولا تفعل سوى استهلاك المعالج. تُبلغ القاعدة عن تأخيرات 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