
# إعادة كتابة AST

إعادة كتابة AST هي بحث AST مع قالب استبدال. يشغّل Greph أداة المطابقة نفسها المستخدمة في [بحث AST](https://greph.inline0.dev/ar/docs/modes/ast)، ثم يدرج القالب المعاد كتابته في المصدر عند نطاقات البايت المطابقة. وتُحفَظ المسافات المحيطة والمسافات البادئة والتعليقات؛ أما الشيفرة المطابقة نفسها فيُعاد تنسيقها للطباعة (راجع [داخل المطابقة](#inside-a-match)).

## أمثلة سريعة

```bash
# Convert array() literals to []
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --dry-run src
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' src

# isset ternary -> null coalesce
./vendor/bin/greph -p 'isset($X) ? $X : $Y' -r '$X ?? $Y' --dry-run src

# Rename a method call
./vendor/bin/greph -p '$obj->oldName($$$ARGS)' -r '$obj->newName($$$ARGS)' src

# Confirm each file before writing
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --interactive src
```

## قوالب الاستبدال

يستخدم قالب الاستبدال صياغة المتغيرات الشكلية نفسها المستخدمة في نمط البحث. وتُدرج العقد الملتقطة مجددًا حسب الاسم:

| في النمط | في القالب | النتيجة |
|---|---|---|
| `$X` | `$X` | إدراج العقدة الملتقطة |
| `$$$ARGS` | `$$$ARGS` | إدراج تسلسل العقد الملتقط |
| `$NAME` (معرّف) | `$NAME` | إدراج المعرّف الملتقط |

تُحذف من الشيفرة المعاد كتابتها المتغيرات الشكلية الموجودة في النمط وغير الموجودة في القالب. أما المتغير الشكلي الموجود في القالب وغير الموجود في النمط، فلا يُرفض حاليًا بل يُطبع حرفيًا، لذا يكتب `-r '$o->$m($$$A, $EXTRA)'` متغيرًا `$EXTRA` في الشيفرة ([#4](https://github.com/inline0/greph/issues/4)). تحقّق من القوالب باستخدام `--dry-run` أولًا.

## الأوضاع

| العلم | السلوك |
|---|---|
| (لا شيء) | تطبيق إعادة الكتابة على كل ملف مطابق |
| `--dry-run` | طباعة محتوى الملف المعاد كتابته إلى stdout دون كتابته |
| `--interactive` | مطالبة `Rewrite <file>? [y/N]` قبل كتابة كل ملف |

الإعداد الافتراضي هو «كتابة كل ما يتغير». استخدم `--dry-run` أولًا عند تعديل النمط، ثم أزل العلم بعد أن تصبح راضيًا عن الفرق.

## الحفاظ على التنسيق

لا يعيد Greph تنسيق الملف. تسجل أداة المطابقة نطاق البايت لكل تطابق في المصدر الأصلي، وتدرج أداة إعادة الكتابة القالب المنسق مباشرةً في تلك النطاقات. وتبقى الشيفرة التي لم تُمس كما هي تمامًا، بما في ذلك:

- المسافات البادئة والمسافات اللاحقة
- التعليقات المضمنة والمستقلة
- تنسيق الأسطر الفارغة
- أسلوب علامات الاقتباس في النصوص وضمّها
- أسلوب وسم الفتح/الإغلاق في PHP

هذا تنازل مقصود عن إعادة التنسيق الكاملة: لن «يرتب» Greph الشيفرة التي لم يطابقها، لكنه لن يُحدث أيضًا فروقًا مزعجة في الأسطر غير المرتبطة.

### داخل المطابقة

يُستبدل النطاق المطابق نفسه بالقالب بعد تنسيقه عبر أداة الطباعة الجميلة في PHP-Parser، حيث تُعاد طباعة العقد الملتقطة بدلًا من نسخها كنص مصدر. وينتج عن ذلك حاليًا ([#2](https://github.com/inline0/greph/issues/2)):

- يُحذف التعليق غير المرتبط بعقدة تليه داخل المطابقة؛ مثل تعليق لاحق لآخر عنصر في مصفوفة أو تعليق في مصفوفة فارغة.
- تُحفظ التعليقات المرتبطة بالعقد الملتقطة، لكنها تنتقل إلى سطر مستقل، وقد يحوّل ذلك تعبيرًا من سطر واحد إلى عدة أسطر.
- لا تُحفظ فواصل الأسطر داخل المطابقة: يتحول `array(...)` متعدد الأسطر الذي لا يحتوي على تلك التعليقات إلى `[...]` في سطر واحد.
- تُوحّد صياغة القيم الحرفية: يصبح `0x1F` `0x1f`، ويصبح `1_000` `1000`، وتُكتب تسلسلات الهروب مثل `"\x41"` على هيئة الحرف (`"A"`)، وتفقد صيغة heredoc المرنة المسافات البادئة.

```php
// before
$d = array(
    "a" => 1,  // trailing comment
);

// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
```

راجع ناتج `--dry-run` للمطابقات التي تحتوي على تعليقات أو قيم حرفية منسقة يدويًا.

## المطابقات متعددة الأسطر والمتداخلة

تعمل الأنماط متعددة الأسطر بالطريقة نفسها التي تعمل بها الأنماط أحادية السطر. وتُنسق أداة الطباعة الجميلة الاستبدال بعد إحلال المتغيرات الشكلية فيه، لذا يأتي تنسيق الشيفرة المستبدلة من أداة الطباعة لا من المطابقة الأصلية.

يعيد Greph كتابة كل ملف في مرور واحد، ولا يطبّق مطابقتين متداخلتين. يجتاز المطابقات بدءًا من نهاية الملف إلى بدايته، ويتخطى أي مطابقة تتداخل مع مطابقة أعاد كتابتها بالفعل. في المطابقات المتداخلة ذات إزاحات بداية مختلفة، تُعاد كتابة المطابقة الداخلية وتبقى الخارجية كما كانت:

```php
// before
$b = array(1, array(2));

// after one run of -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$b = array(1, [2]);

// after a second run
$b = [1, [2]];
```

عندما تبدأ مطابقتان عند الإزاحة نفسها (مثل سلسلة أساليب طابقها `$obj->$method($$$ARGS)`)، تُعاد كتابة المطابقة الخارجية. وعندما يكون النمط قابلًا للتداخل، أعد تشغيل إعادة الكتابة حتى لا يطبع `--dry-run` أي شيء ([#3](https://github.com/inline0/greph/issues/3)). يعيد ast-grep كتابة المطابقة الخارجية من بين مطابقتين متداخلتين، لذا تنتج `vendor/bin/sg` و`sg` في المنبع نتائج مختلفة هنا.

## الاستخدام البرمجي

```php
use Greph\Greph;
use Greph\Ast\AstSearchOptions;

$rewrites = Greph::rewriteAst(
    'array($$$ITEMS)',
    '[$$$ITEMS]',
    'src',
    new AstSearchOptions(jobs: 4),
);

foreach ($rewrites as $result) {
    if (!$result->changed()) {
        continue;
    }

    echo "{$result->file}: {$result->replacementCount} replacement(s)\n";
    file_put_contents($result->file, $result->rewrittenContents);
}
```

لا يكتب `Greph::rewriteAst()` الملفات بنفسه. تتولى CLI تثبيت التغييرات (أو تخطيها في حالتي `--dry-run` والرفض `--interactive`). وهذا يجعل استدعاء الواجهة آمنًا من الوكلاء الآليين الذين يريدون فحص الفرق قبل تطبيقه.

`RewriteResult` يوفّر:

- `file`: المسار المطلق
- `originalContents`: محتويات الملف قبل إعادة الكتابة
- `rewrittenContents`: محتويات الملف بعد إعادة الكتابة
- `replacementCount`: عدد الاستبدالات البنيوية
- `changed()`: دالة مساعدة تُرجع `true` عندما يكون `originalContents !== rewrittenContents`

## متى تستخدم Rewrite

يكون Rewrite الخيار المناسب عندما:

- يكون التحويل محليًا وبنيويًا (وليس إعادة هيكلة للملف بأكمله).
- تريد إعادة هيكلة حتمية وقابلة للتكرار، ويمكنك إعادة تشغيلها حتى لا يتغير شيء.
- تريد معاينة التغيير قبل تطبيقه.
- تريد دمج إعادة الهيكلة في سكربت أو مهمة CI دون تشغيل ملف تنفيذي خارجي.

يكون Rewrite غير مناسب عندما:

- يعتمد التحويل على معلومات الأنواع التي لا يستطيع PHP-Parser حلها من ملف واحد.
- يحتاج التحويل إلى إضافة عمليات استيراد أو إزالتها عبر حدود الملفات.
- تريد منسقًا للتنسيق بدلًا من أداة لإعادة الهيكلة (استخدم PHP-CS-Fixer).
