Към съдържанието

AST търсене

AST режимът на Greph търси в изходен код на PHP по структура, а не по текст. Шаблоните се записват като обикновен код на PHP с метапроменливи $VARIABLE и $$$VARIADIC. Шаблонът се анализира от nikic/php-parser до същата форма на AST като изходния код, в който се търси, след което се съпоставя възел по възел.

Това е същият модел като при ast-grep, а съвместимият обвиващ слой sg предоставя повърхността на CLI на ast-grep върху същия механизъм.

Кратки примери

Обвивка
# 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 (така че можете да го обхождате с NodeVisitor на PHP-Parser), а $match->captures съпоставя всяко име на метапроменлива с уловения възел или списъка от възли.

Как работи

Конвейерът е следният:

  1. Анализиране на шаблона: Greph\Ast\PatternParser пренаписва метапроменливите за идентификатори, след което анализира шаблона с PHP-Parser.
  2. Анализиране на изходния код: всеки файл кандидат се анализира веднъж. В режим ast-cache тази стъпка се заменя с десериализация от кеш хранилището.
  3. Филтриране на кандидатите: Greph\Ast\AstPatternPrefilter извлича груб подпис от шаблона (име на извикване, име на клас, тип оператор) и го използва, за да пропуска файлове, които очевидно не могат да съдържат съвпадение. В режим ast-index тази стъпка вместо това прави заявка към предварително изградено хранилище на факти.
  4. Съпоставяне: Greph\Ast\PatternMatcher обхожда AST на изходния код и го сравнява с AST на шаблона, като по пътя присвоява стойности на метапроменливите и налага съгласуваност между повторните срещания.
  5. Извеждане на резултатите: съвпадналите възли се преобразуват в обекти AstMatch с позиции на редовете и байтовете и оригиналния откъс от изходния код.

При повтарящи се натоварвания индексираното AST търсене и търсенето в кеширан AST пропускат повечето от стъпки 2 и 3.