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-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);تدعم الإضافة ESLint 9 و10 باستخدام التهيئة المسطحة. تعتمد القواعد على البنية النحوية ولا تحتاج إلى معلومات الأنواع أو parserOptions.project. يعمل أي محلل يفهم TypeScript و JSX.
التهيئات
| التهيئة | ما تفعّله |
|---|---|
textjs.configs.recommended | كل قواعد صحة الشيفرة كأخطاء؛ وقواعد وحدة التحكم والمؤقتات وتوقيت الإطارات كتحذيرات |
textjs.configs.strict | كل القواعد كأخطاء، إضافة إلى prefer-use-keymap كتحذير |
اختر القواعد منفردة بدلًا من ذلك:
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 الملف من ملفات وحدة التحكم.
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 componentsno-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: [...] }.
export function get() { … } // ✗ never calledexport function GET() { … } // ✓