Preskoči na vsebino

Iskanje AST

Način AST v Greph išče v izvorni kodi PHP po strukturi, ne po besedilu. Vzorci so zapisani kot običajna koda PHP z metaspremenljivkami $VARIABLE in $$$VARIADIC. nikic/php-parser vzorec razčleni v enako obliko AST kot izvorno kodo, po kateri išče, nato pa primerja vozlišče za vozliščem.

Gre za enak model kot pri ast-grep, združljivostni ovoj sg pa na istem mehanizmu ponuja vmesnik ast-grepCLI.

Hitri primeri

Lupina
# 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

Sintaksa vzorcev

Vzorec je veljavna izvorna koda PHP. Kjer koli bi lahko zapisali pravi izraz ali stavek, lahko za zajem tega vozlišča uporabite metaspremenljivko.

Sintaksa Ujema se z Primer
$VAR Katerim koli posameznim vozliščem AST, zajetim kot VAR $x + $y se ujema z foo() + bar
$_ Katerim koli posameznim vozliščem AST, brez zajema $_->method() se ujema s katerim koli sprejemnikom
$$$ARGS Nič ali več vozlišč (variadično), zajetih kot ARGS func($$$ARGS) se ujema z func(), func(1), func(1, 2, 3)
$VAR (ponovljeno) Isti $VAR se mora obakrat ujemati z isto strukturo $x == $x se ujema z a == a, ne pa z a == b

Ime metaspremenljivke je po dogovoru zapisano z velikimi črkami, vendar ni nujno. $_ je nadomestni znak brez zajema.

$$$VARIADIC deluje povsod, kjer PHP dovoljuje seznam vozlišč: v argumentih funkcij, elementih tabel, seznamih parametrov in telesih stavkov. Zajem shrani celotno ujemajoče se zaporedje (tudi če ne vsebuje nobenega elementa).

Metaspremenljivke identifikatorjev

Imena razredov, funkcij, vmesnikov, traitov in enumov so identifikatorji, ne izrazi, zato PHP običajno ne bi sprejel $Name za class. Greph pred razčlenitvijo preoblikuje te vzorce, zato metaspremenljivka še vedno deluje:

Lupina
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src

Enaka preobrazba se izvede za class, interface, trait, enum in function.

Ponavljajoče se metaspremenljivke

Ponovna uporaba metaspremenljivke v istem vzorcu pomeni, da se morata obe pojavitvi strukturno ujemati z istim vozliščem. To je uporabno za odkrivanje odvečne kode:

Lupina
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src

# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src

Isto pravilo velja za $$$VARIADIC: dve pojavitvi $$$ARGS se morata ujemati z istim zaporedjem.

Filtriranje datotek

Način AST pregleda drevo datotek z istim sprehajalnikom kot besedilni način in kot privzeti filter vrst datotek uporablja php. Preglasite ga z možnostmi --type, --type-not, --glob, --no-ignore in --hidden. Spraševalnik še vedno upošteva .gitignore in .grephignore ter preskoči binarne datoteke.

Lupina
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src

Izhod

Privzeti izhod uporablja enako obliko file:line:content kot besedilni način, pri čemer je ujemajoča se koda strnjena v eno vrstico:

Text
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();

Podajte --json za izpis strukturiranih ujemanj:

JSON
[
  {
    "file": "src/Greph.php",
    "start_line": 38,
    "end_line": 38,
    "start_file_pos": 1234,
    "end_file_pos": 1252,
    "code": "new FileWalker()"
  }
]

Napake pri razčlenjevanju

Privzeto Greph tiho preskoči datoteke, ki jih ni mogoče razčleniti. To je primerno pri izvajanju nad veliko kodno zbirko, ki vsebuje eksperimentalne ali ustvarjene datoteke PHP. Podajte --strict-parse (za greph-index ast-index search in greph-index ast-cache search), da se namesto tega sproži napaka pri razčlenjevanju, ali nastavite skipParseErrors: false v AstSearchOptions, kadar neposredno uporabljate fasado.

Uporaba v kodi

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 je zajeto PhpParser\Node (zato se lahko sprehodite po njem z PHP-Parser NodeVisitor), $match->captures pa preslika vsako ime metaspremenljivke v zajeto vozlišče ali seznam vozlišč.

Kako deluje

Cevovod poteka takole:

  1. Razčlenitev vzorca: Greph\Ast\PatternParser preoblikuje metaspremenljivke identifikatorjev, nato pa vzorec razčleni z PHP-Parser.
  2. Razčlenitev izvorne kode: vsaka kandidatna datoteka se razčleni enkrat. V načinu ast-cache ta korak nadomesti deserializacija iz shrambe predpomnilnika.
  3. Filtriranje kandidatov: Greph\Ast\AstPatternPrefilter izlušči grobi podpis vzorca (ime klica, ime razreda, vrsto stavka) in ga uporabi za preskakovanje datotek, ki očitno ne morejo vsebovati ujemanja. V načinu ast-index ta korak namesto tega poizveduje po vnaprej izračunani shrambi dejstev.
  4. Ujemanje: Greph\Ast\PatternMatcher pregleda izvorni AST in ga primerja z AST vzorca, pri tem veže metaspremenljivke ter zagotavlja doslednost ponavljajočih se spremenljivk.
  5. Izpis rezultatov: ujemajoča se vozlišča se pretvorijo v objekte AstMatch z mesti vrstic in bajtov ter izvirnim izsekom izvorne kode.

Pri ponavljajočih se opravilih indeksirano iskanje AST in iskanje AST iz predpomnilnika preskočita večino 2. in 3. koraka.