⁦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 ברכיב דורש אפקט, ניקוי ו-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. הערך נקרא מדפים, מקובצי layout וממטפלי נתיב. בדפים ובקובצי layout יש לכתוב אותו כליטרל מחרוזת רגיל: text build קורא אותו מקוד המקור בלי להריץ את המודול.

valid-metadata

⁦Metadata⁩ ממוזג מקובצי layout, ולאחר מכן מהדף (או מ-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