رفتن به محتوا

بازآرایی ساختاری کد

این راهنما نشان می‌دهد چگونه با جست‌وجو و بازنویسی ساختاری، یک عامل را به انجام یک تغییر مکانیکی گسترده وادارید — یک codemod، یک مهاجرت انبوه API، یا الگویی که می‌خواهید از میان برود — و تغییر پیش از نوشته‌شدن هر چیزی برای بازبینی آماده شود.

ast_grep کد را بر پایهٔ شکل مطابقت می‌دهد. ast_edit آن را بازنویسی می‌کند. resolve کامیت یا دور می‌اندازد.

ابزارهای ساختاری زبان‌هایی را پوشش می‌دهند که گراف کد تجزیه می‌کند: Dart، JavaScript، TypeScript، TSX، PHP، Python، Rust، Zig، C، C++، Go، Java، Ruby، C#، Swift، Kotlin، R، Assembly، MATLAB و Ada. این ابزارها به آماده‌بودن گرامرهای tree-sitter نیاز دارند، که در یک بیلد عرضه‌شده همیشه آماده‌اند — گرامر گم‌شده سرور را متوقف می‌کند، نه اینکه جست‌وجو را تنزل دهد.

هرگز چیزی را که ندیده‌اید بازنویسی نکنید. از عامل بخواهید جست‌وجو کند:

هر جایی را پیدا کن که dispose() را روی یک nullable که تازه null-بودنش را بررسی کرده‌ایم فراخوانی می‌کنیم.

ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")

خروجی برای هر مورد، file:line به‌همراه کد مطابقت‌شده است.

نحو الگو چه چیزی نصیب‌تان می‌کند

بخش با عنوان “نحو الگو چه چیزی نصیب‌تان می‌کند”
نشانه مطابقت
$NAME هر گرهٔ تکی، ثبت‌شده
$$$NAME هر توالی از گره‌های خواهر — آرگومان‌ها، دستورها
$_ هر چیز، بی‌آنکه زیر نامی ثبت شود

آن که اهمیت دارد: یک $NAME تکراری باید همان متن را بگیرد.

if ($X != null) $X.dispose()

این گاردی را پیدا می‌کند که همان چیزی را که آزموده آزاد می‌کند. if (a != null) b.dispose() را پیدا نمی‌کند — که معمولاً همان باگی است که دنبالش بودید. هیچ جست‌وجوی متنی نمی‌تواند این را بیان کند.

دو کار که به ترتیب امتحان کنید:

  1. کوچک‌ترش کنید. foo($X) پیش از if (a) { foo($X); }. الگویی که به‌خودی‌خود کد معتبری نیست، اغلب همان ساختاری نیست که در ذهن داشتید.
  2. زبان را نام ببرید. language: تنها زمانی استنباط می‌شود که path یک فایل تکی را نام ببرد.

الگویی که هیچ زمینهٔ تجزیه‌ای آن را نمی‌پذیرد، به‌عنوان تجزیه‌ناپذیر گزارش می‌شود — ابزار همان را می‌گوید، نه اینکه برای چیزی که هرگز آن‌گونه که به نظر می‌رسد خوانده نمی‌شد «هیچ موردی» گزارش کند.

بازنویسی کنید و پیش از اعمال نگاه کنید

بخش با عنوان “بازنویسی کنید و پیش از اعمال نگاه کنید”
ast_edit(
pattern: "if ($X != null) $X.dispose()",
rewrite: "$X?.dispose()",
language: "dart",
)

نتیجه یک پیشنهاد است:

(proposed) ast_edit: if ($X != null) $X.dispose() → $X?.dispose()
12 replacements in 4 files:
lib/features/a/x.dart (3)
lib/features/b/y.dart (5)
…
Nothing has been written. Call resolve with edit_id "edit_1" and action
"accept" to apply it, or "discard" to drop it.

هنوز چیزی روی دیسک نیست. فهرست را بخوانید، چند مورد از مواردی که جست‌وجو گزارش کرده را بررسی کنید، سپس:

resolve(edit_id: "edit_1", action: "accept")

یا

resolve(edit_id: "edit_1", action: "discard")

کارتان را با کامپایلر بررسی کنید

بخش با عنوان “کارتان را با کامپایلر بررسی کنید”

نکتهٔ انجام این کار به‌صورت ساختاری این است که گسترده است. تغییرهای گسترده دقیقاً همان جایی‌اند که یک بازنویسی ظاهراً معقول، یک نوع را می‌شکند. پس از resolve کردن، درخواست تشخیص کنید:

lsp(action: "diagnostics", file: "*")

file: "*" به‌جای پرس‌وجوی تک‌فایل، بررسی‌کنندهٔ خود پروژه (dart analyze، tsc --noEmit، cargo check، go build) را اجرا می‌کند — شکل درست پرسش پس از تغییری که به چهل فایل دست زده است.

فرض کنید log(msg, level) در حال تبدیل‌شدن به log(level: level, message: msg) است.

ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")

به موارد مطابقت نگاه کنید. اگر برخی از آن‌ها یک log دیگر با دو آرگومان باشند، با محدودکردن path به پوشه‌ای که مالک log موردنظر شماست الگو را باریک‌تر کنید، نه اینکه بخواهید تفاوت را در خود الگو بیان کنید.

ast_edit(
pattern: "log($MSG, $LEVEL)",
rewrite: "log(level: $LEVEL, message: $MSG)",
path: "lib/features/telemetry",
language: "dart",
)

سپس resolve، و سپس lsp(action: "diagnostics", file: "*").

چه زمانی ابزار دیگری به‌کار ببریم

بخش با عنوان “چه زمانی ابزار دیگری به‌کار ببریم”
وضعیت ابزار بهتر
تغییر نام یک نماد lsp_rename — مراجع را دنبال می‌کند، نه شکل‌ها را
یافتن یک رشته، یک کامنت یا یک نام فایل search (محتوا) / search_files (نام‌ها)
«اگر این را تغییر دهم چه چیزی می‌شکند؟» گراف کد
زبانی که گرامرها پوشش نمی‌دهند search به‌همراه edit