AST 검색
Greph의 AST 모드는 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 |
캡처된 VAR로 저장되는 모든 단일 AST 노드 |
$x + $y는 foo() + bar와 매치됩니다 |
$_ |
캡처되지 않는 모든 단일 AST 노드 | $_->method()는 모든 리시버와 매치됩니다 |
$$$ARGS |
ARGS로 캡처되는 0개 이상의 노드 (가변) |
func($$$ARGS)는 func(), func(1), func(1, 2, 3)과 매치됩니다 |
$VAR (반복) |
같은 $VAR가 두 위치에서 동일한 구조와 매치되어야 함 |
$x == $x는 a == a와 매치되지만 a == b와는 매치되지 않습니다 |
메타변수 이름은 관례상 대문자지만 반드시 그럴 필요는 없습니다. $_는 캡처하지 않는 와일드카드입니다.
$$$VARIADIC는 PHP에서 노드 목록을 허용하는 모든 위치에서 사용할 수 있습니다. 함수 인수, 배열 항목, 매개변수 목록, 문장 본문이 여기에 해당합니다. 캡처에는 0개 항목도 포함해 매치된 전체 시퀀스가 저장됩니다.
식별자 메타변수
클래스, 함수, 인터페이스, 트레이트, 열거형 이름은 표현식이 아니라 식별자이므로 class 뒤에 $Name을 쓰면 일반적으로 PHP에서 허용되지 않습니다. 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 형식을 사용하며, 매치된 코드를 한 줄로 접어 표시합니다:
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
구조화된 매치를 출력하려면 --json을 전달하세요:
[
{
"file": "src/Greph.php",
"start_line": 38,
"end_line": 38,
"start_file_pos": 1234,
"end_file_pos": 1252,
"code": "new FileWalker()"
}
]
파싱 오류
기본적으로 Greph은 파싱에 실패한 파일을 조용히 건너뜁니다. 실험용 또는 생성된 PHP이 포함된 대규모 코드베이스를 검색할 때 적합한 동작입니다. 대신 파싱 오류를 발생시키려면 (greph-index ast-index search 및 greph-index ast-cache search에서) --strict-parse를 전달하거나, 파사드를 직접 사용할 때 AstSearchOptions에서 skipParseErrors: false를 설정하세요.
프로그래밍 방식 사용
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는 각 메타변수 이름을 캡처된 노드 또는 노드 목록에 매핑합니다.
작동 방식
파이프라인은 다음과 같습니다:
- 패턴 파싱:
Greph\Ast\PatternParser은 식별자 메타변수를 다시 작성한 다음 PHP-Parser로 패턴을 파싱합니다. - 소스 파싱: 각 후보 파일을 한 번 파싱합니다.
ast-cache모드에서는 이 단계가 캐시 저장소의 역직렬화로 대체됩니다. - 후보 필터링:
Greph\Ast\AstPatternPrefilter은 패턴에서 대략적인 시그니처 (호출 이름, 클래스 이름, 문장 유형)를 추출해 매치가 있을 수 없는 파일을 건너뜁니다.ast-index모드에서는 미리 계산된 사실 저장소를 대신 조회합니다. - 매칭:
Greph\Ast\PatternMatcher은 소스 AST를 순회하며 패턴 AST와 비교하고, 메타변수를 바인딩하면서 반복 변수의 일관성을 확인합니다. - 결과 출력: 매치된 노드는 줄 및 바이트 위치와 원본 소스 일부가 포함된
AstMatch객체로 변환됩니다.
반복 워크로드에서는 인덱싱된 AST 검색과 캐시된 AST 검색이 2단계와 3단계의 대부분을 건너뜁니다.