Перейти к содержимому

Индексированный поиск по AST

Индексированный поиск по AST — это версия поиска по AST с прогретым кэшем. При первом вызове факты на уровне узлов (имена вызовов, инстанцирования, объявления классов и методов) извлекаются в хранилище на диске; последующие вызовы используют хранилище, чтобы пропускать файлы, в которых заведомо не может быть структурного совпадения. Затем средство сопоставления повторно проверяет кандидатов по исходному коду.

Индекс фактов доступен через подкоманду greph-index ast-index CLI и методы фасада Greph::buildAstIndex() / Greph::searchAstIndexed(). Он также используется в рабочих процессах с прогретыми индексами и манифестными наборами.

Создание индекса

Командная оболочка
# Full build at the current directory
./vendor/bin/greph-index ast-index build .

# Full build at an explicit root
./vendor/bin/greph-index ast-index build path/to/repo

# Use a non-default index directory
./vendor/bin/greph-index ast-index build . --index-dir /tmp/greph-ast-index

# Build a mutable overlay that can refresh cheaply on demand
./vendor/bin/greph-index ast-index build . \
  --lifecycle opportunistic-refresh \
  --auto-refresh-max-files 32 \
  --auto-refresh-max-bytes 1048576

По умолчанию индекс фактов хранится в <root>/.greph-ast-index/.

После успешного создания выводится сводка в одну строку:

Text
Built AST index for 2547 files in .greph-ast-index (84931 fact rows, +2547 ~0 -0 =0)

Четыре счетчика показывают количество добавленных, обновленных, удаленных и не изменившихся с момента предыдущего построения файлов.

Обновление индекса

Командная оболочка
./vendor/bin/greph-index ast-index refresh .

refresh повторно обходит корневой каталог индекса, заново анализирует измененные файлы и перезаписывает только изменившиеся строки фактов. В выводе используются те же четыре счетчика, что и для текстового индекса.

Профили жизненного цикла работают так же, как для прогретых текстовых индексов:

  • static: никогда автоматически не проверяет актуальность и не изменяет данные
  • manual-refresh: сообщает об устаревшем состоянии в stats, но не обновляет индекс во время поиска
  • opportunistic-refresh: обновляет индекс во время поиска, только если набор изменений еще достаточно мал
  • strict-stale-check: отклоняет поиск с устаревшим индексом вместо его обновления

Запросы к индексу

Командная оболочка
./vendor/bin/greph-index ast-index search 'new $CLASS()' src
./vendor/bin/greph-index ast-index search '$obj->$method($$$ARGS)' src
./vendor/bin/greph-index ast-index search --json 'array($$$ITEMS)' src
./vendor/bin/greph-index ast-index search -l 'array($$$ITEMS)' src
./vendor/bin/greph-index ast-index search --trace-plan 'new $CLASS()' src

Синтаксис метапеременных совпадает с нативным поиском по AST. Полную грамматику см. в разделе Режимы / поиск по AST.

Параметры

Параметр Значение
--lang NAME Язык AST. По умолчанию: php
-j N, --jobs N Количество параллельных рабочих процессов
-l, --files-with-matches Выводить только совпавшие файлы
--glob GLOB Включать только файлы, пути которых совпадают с GLOB
--type NAME / --type-not NAME Фильтр по типу файла
--json Выводить данные в формате JSON
--no-ignore Игнорировать .gitignore и .grephignore
--hidden Включать скрытые файлы
--strict-parse Завершать работу с ошибкой при ошибках разбора вместо их пропуска
--fallback MODE Поведение при отсутствии индекса: fail (по умолчанию) или scan
--index-dir DIR Использовать нестандартный каталог индекса
--trace-plan Выводить диагностику планировщика прогретого AST в stderr
--show-index-origin Добавлять в начало вывода метку совпавшего индекса в режиме нескольких индексов или набора

Резервный режим при отсутствии индекса

По умолчанию ast-index search выдает ошибку, если в указанном месте нет индекса. Передайте --fallback scan, чтобы вместо этого выполнить нативное сканирование AST. Это подходящий параметр для инструментов, которым нужен поиск по индексу, когда он доступен, но при этом требуются результаты, даже если индекс еще не создан.

Командная оболочка
./vendor/bin/greph-index ast-index search --fallback scan 'new $CLASS()' src

Можно выполнить поиск сразу в нескольких прогретых индексах AST, повторив --index-dir. Это удобно для таких структур, как ядро WordPress и плагин или тема.

Командная оболочка
./vendor/bin/greph-index ast-index search \
  --index-dir ../wordpress/.greph-ast-index \
  --index-dir ./.greph-ast-index \
  --show-index-origin \
  'new $CLASS()' .

Если включен --show-index-origin, перед результатами выводится имя записи совпавшего прогретого индекса, чтобы было видно, из какого корневого каталога получено совпадение.

Наборы индексов на основе манифеста

Для стабильных конфигураций с прогретыми индексами задайте манифест, чтобы greph-index set работал с группами индексов:

Командная оболочка
./vendor/bin/greph-index set build
./vendor/bin/greph-index set stats --mode ast-index --dry-refresh
./vendor/bin/greph-index set search --mode ast-index --show-index-origin 'new $CLASS()' .

Подкоманды наборов используют те же правила жизненного цикла, проверки актуальности и диагностику планировщика, что и прямой вызов ast-index.

Диагностика планировщика

--trace-plan показывает, как Greph сузил запрос перед запуском полного средства сопоставления. Для ast-index обычно отображаются:

  • укрупненная сигнатура фактов, извлеченная из шаблона
  • количество просканированных прогретых индексов
  • количество файлов, отброшенных фильтром фактов
  • количество файлов-кандидатов, повторно проанализированных для точной структурной проверки

Что хранится в индексе

Для каждого индексируемого файла средство извлечения фактов AST записывает строку для каждого «интересного» узла. Набор типов фактов охватывает конструкции, на которые чаще всего нацелены шаблоны AST:

  • Вызовы функций и методов (по имени вызова)
  • Создание экземпляров классов (по имени класса)
  • Объявления классов, интерфейсов, трейтов и перечислений (по имени)
  • Объявления функций (по имени)
  • Объявления методов (по имени и видимости)
  • Инструкции use

При поступлении запроса Greph\Ast\AstFactQuery преобразует шаблон поиска в укрупненную сигнатуру фактов (например, «должен содержать new любого класса») и выполняет пересечение с хранилищем фактов. Затем файлы, прошедшие пересечение, повторно анализируются и сопоставляются с полным шаблоном.

Это означает, что индекс фактов служит предварительным фильтром, а не хранилищем полных совпадений. На этапе сопоставления исходный код все равно анализируется, поэтому структурная точность такая же, как при нативном поиске по AST; сокращается только набор кандидатов.

Когда использовать

Индексированный режим AST подходит, если:

  • Вы многократно выполняете одни и те же структурные запросы к одной и той же кодовой базе.
  • Шаблоны нацелены на распознаваемые структуры (имя вызова, имя класса, вид объявления), которые сохраняет средство извлечения фактов.
  • Вам нужны результаты быстрее, чем при повторном анализе каждого файла.

Для шаблонов, отличительная особенность которых отсутствует в наборе фактов (например, сложных вложенных выражений без распознаваемого начала), обычно лучше подходит режим кэшированного AST: он полностью пропускает этап анализа и не опирается на укрупненную сигнатуру.

Программное использование

PHP
use Greph\Greph;
use Greph\Ast\AstSearchOptions;
use Greph\Index\IndexLifecycleProfile;

Greph::buildAstIndex('.', lifecycle: IndexLifecycleProfile::OpportunisticRefresh);

$matches = Greph::searchAstIndexed(
    'new $CLASS()',
    'src',
    new AstSearchOptions(jobs: 4, tracePlan: true),
);

searchAstIndexed принимает те же AstSearchOptions, что и нативный поиск по AST, и возвращает результаты той же формы list<AstMatch>. Для работы с несколькими индексами или наборами на основе манифеста используйте searchAstIndexedMany(...) и searchAstIndexedSet(...).