مواد پر جائیں

کوڈ کو ساختی طور پر ری فیکٹر کریں

یہ گائیڈ دکھاتی ہے کہ کسی ایجنٹ سے وسیع میکانیکی تبدیلی کیسے کرائی جائے — کوئی کودم، کوئی بلک 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() کال کرتے ہیں جس کا ابھی 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. اسے چھوٹا کریں۔ if (a) { foo($X); } سے پہلے 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")

اسے ساختی طور پر کرنے کا مقصد یہی ہے کہ یہ وسیع ہوتا ہے۔ وسیع تبدیلیاں بالکل وہیں ہوتی ہیں جہاں کوئی صحیح دکھنے والا ری رائٹ کوئی ٹائپ توڑ دیتا ہے۔ تجویز طے ہو جانے کے بعد تشخیص طلب کریں:

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