انتقل إلى المحتوى

بحث AST

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

هذا هو النموذج نفسه الذي يستخدمه ast-grep، وتتيح واجهة التوافق sg سطح ast-grep الخاص بـCLI فوق المحرك نفسه.

أمثلة سريعة

سطر الأوامر
# 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 كتابة هذه الأنماط قبل تحليلها، ليظل المتغير الشكلي صالحًا:

سطر الأوامر
./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.

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

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

سطر الأوامر
# 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 ويتخطى الملفات الثنائية.

سطر الأوامر
./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 المفهرس وبحث AST المخزّن مؤقتًا معظم الخطوتين 2 و3.