
# بحث AST

يبحث وضع AST في Greph عن مصدر PHP وفق بنيته لا نصه. تُكتب الأنماط على هيئة شيفرة PHP عادية باستخدام المتغيرات الشكلية `$VARIABLE` و`$$$VARIADIC`. يحلل [nikic/php-parser](https://github.com/nikic/PHP-Parser) النمط إلى بنية AST نفسها التي للمصدر الجاري البحث فيه، ثم يطابق العقدة تلو الأخرى.

هذا هو النموذج نفسه الذي يستخدمه [ast-grep](https://ast-grep.github.io/)، وتتيح واجهة التوافق `sg` سطح ast-grep الخاص بـCLI فوق المحرك نفسه.

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

```bash
# Find every constructor call
./vendor/bin/greph -p 'new $CLASS()' src

# Find every method call with any number of arguments
./vendor/bin/greph -p '$obj->$method($$$ARGS)' src

# Find old-style array() calls
./vendor/bin/greph -p 'array($$$ITEMS)' src

# Find void-returning functions
./vendor/bin/greph -p 'function $name($$$PARAMS): void {}' src

# Find isset ternaries that should be ??
./vendor/bin/greph -p 'isset($x) ? $x : $default' src
```

## صياغة الأنماط

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

| الصياغة | ما تطابقه | مثال |
|---|---|---|
| `$VAR` | أي عقدة AST مفردة، تُلتقط باسم `VAR` | يطابق `$x + $y` `foo() + bar` |
| `$_` | أي عقدة AST مفردة، دون التقاطها | يطابق `$_->method()` أي مستقبل |
| `$$$ARGS` | صفرًا أو أكثر من العقد (متغيرة الطول)، تُلتقط باسم `ARGS` | يطابق `func($$$ARGS)` `func()` و`func(1)` و`func(1, 2, 3)` |
| `$VAR` (مكررة) | يجب أن يطابق `$VAR` البنية نفسها في المرتين | يطابق `$x == $x` `a == a`، لا `a == b` |

يكون اسم المتغير الشكلي بحروف كبيرة عرفًا، لكن ذلك ليس إلزاميًا. `$_` بدل شامل لا يلتقط العقدة.

يعمل `$$$VARIADIC` في أي موضع يسمح فيه PHP بقائمة من العقد: وسائط الدوال، وعناصر المصفوفات، وقوائم المعاملات، وأجسام التصريحات. ويخزن الالتقاط التسلسل المطابق بأكمله، بما في ذلك حالة عدم وجود عناصر.

## المتغيرات الشكلية للمعرّفات

أسماء الأصناف والدوال والواجهات والسمات والتعدادات معرّفات وليست تعبيرات، لذا لا يقبل PHP عادةً `$Name` بعد `class`. يعيد Greph كتابة هذه الأنماط قبل تحليلها، ليظل المتغير الشكلي صالحًا:

```bash
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src
```

تُجرى إعادة الكتابة نفسها مع `class` و`interface` و`trait` و`enum` و`function`.

## المتغيرات الشكلية المتكررة

إن إعادة استخدام متغير شكلي داخل النمط نفسه تعني أن موضعيه يجب أن يطابقا العقدة نفسها بنيويًا. وهذا مفيد لاكتشاف الشيفرة الزائدة:

```bash
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src

# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src
```

تنطبق القاعدة نفسها على `$$$VARIADIC`: يجب أن يطابق موضعا `$$$ARGS` التسلسل نفسه.

## تصفية الملفات

يجتاز وضع AST شجرة الملفات باستخدام أداة الاجتياز نفسها المستخدمة في وضع النص، ويستخدم افتراضيًا مرشح نوع الملفات `php`. يمكنك تغييره باستخدام `--type` و`--type-not` و`--glob` و`--no-ignore` و`--hidden`. ويظل الاجتياز ملتزمًا بـ`.gitignore` و`.grephignore` ويتخطى الملفات الثنائية.

```bash
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src
```

## المخرجات

يستخدم الناتج الافتراضي تنسيق `file:line:content` نفسه المستخدم في وضع النص، مع طي الشيفرة المطابقة في سطر واحد:

```text
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
```

مرر `--json` لإصدار مطابقات منظّمة:

```json
[
  {
    "file": "src/Greph.php",
    "start_line": 38,
    "end_line": 38,
    "start_file_pos": 1234,
    "end_file_pos": 1252,
    "code": "new FileWalker()"
  }
]
```

## أخطاء التحليل

يتخطى Greph افتراضيًا بصمت الملفات التي يتعذر تحليلها. وهذا السلوك مناسب عند العمل على قاعدة شيفرة كبيرة تحتوي على PHP تجريبية أو مولّدة. مرر `--strict-parse` (مع `greph-index ast-index search` و`greph-index ast-cache search`) لإظهار خطأ التحليل، أو اضبط `skipParseErrors: false` على `AstSearchOptions` عند استخدام الواجهة مباشرةً.

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

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

$matches = Greph::searchAst(
    '$obj->$method($$$ARGS)',
    'src',
    new AstSearchOptions(jobs: 4),
);

foreach ($matches as $match) {
    echo "{$match->file}:{$match->startLine}\n";
    echo $match->code . "\n";

    foreach ($match->captures as $name => $node) {
        echo "  {$name} = " . get_debug_type($node) . "\n";
    }
}
```

`$match->node` هي `PhpParser\Node` الملتقطة (لذا يمكنك اجتيازها باستخدام PHP-Parser `NodeVisitor`)، ويربط `$match->captures` كل اسم متغير وصفي بالعقدة الملتقطة أو قائمة العقد.

## طريقة العمل

يتكون المسار من:

1. **تحليل النمط**: يعيد `Greph\Ast\PatternParser` كتابة متغيرات المعرّفات الشكلية، ثم يحلل النمط باستخدام PHP-Parser.
2. **تحليل المصدر**: يُحلل كل ملف مرشح مرة واحدة. في وضع `ast-cache`، تُستبدل هذه الخطوة بإلغاء تسلسل من مخزن الذاكرة المؤقتة.
3. **تصفية المرشحين**: يستخرج `Greph\Ast\AstPatternPrefilter` توقيعًا تقريبيًا من النمط (اسم استدعاء أو اسم صنف أو نوع تصريح)، ويستخدمه لتجاوز الملفات التي يستحيل أن تحتوي على تطابق. وفي وضع `ast-index`، تستعلم هذه الخطوة عن مخزن حقائق محسوب مسبقًا.
4. **المطابقة**: يجتاز `Greph\Ast\PatternMatcher` شجرة AST المصدر ويقارنها بشجرة النمط، رابطًا المتغيرات الشكلية أثناء ذلك ومتحققًا من اتساق المتغيرات المكررة.
5. **إصدار النتائج**: تُحوّل العقد المطابقة إلى كائنات `AstMatch` تحتوي على مواضع الأسطر والبايتات ومقطع المصدر الأصلي.

في أعباء العمل المتكررة، يتجاوز [بحث AST المفهرس](https://greph.inline0.dev/ar/docs/indexed/ast) و[بحث AST المخزّن مؤقتًا](https://greph.inline0.dev/ar/docs/indexed/cache) معظم الخطوتين 2 و3.
