본문으로 건너뛰기

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 형식을 사용하며, 매치된 코드를 한 줄로 접어 표시합니다:

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이 포함된 대규모 코드베이스를 검색할 때 적합한 동작입니다. 대신 파싱 오류를 발생시키려면 (greph-index ast-index search 및 greph-index ast-cache search에서) --strict-parse를 전달하거나, 파사드를 직접 사용할 때 AstSearchOptions에서 skipParseErrors: false를 설정하세요.

프로그래밍 방식 사용

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를 순회하며 패턴 AST와 비교하고, 메타변수를 바인딩하면서 반복 변수의 일관성을 확인합니다.
  5. 결과 출력: 매치된 노드는 줄 및 바이트 위치와 원본 소스 일부가 포함된 AstMatch 객체로 변환됩니다.

반복 워크로드에서는 인덱싱된 AST 검색과 캐시된 AST 검색이 2단계와 3단계의 대부분을 건너뜁니다.