Skip to content

Instantly share code, notes, and snippets.

@o0h
Last active August 15, 2023 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save o0h/5c41734f6f6036cd5cb67dda59386f92 to your computer and use it in GitHub Desktop.
Save o0h/5c41734f6f6036cd5cb67dda59386f92 to your computer and use it in GitHub Desktop.
PHPのファイルに差分があるかを(astを使って)調べる君
<?php
declare(strict_types=1);
const CHECK_PHP_VERSION = 80;
if (!extension_loaded('ast')) {
abort('php-ast is required, but not installed. Abort.');
}
if ($argc < 2) {
abort('Please input the base branch or commit hash.');
}
$baseBranch = $argv[1];
$headBranch = $argv[2] ?? 'HEAD';
foreach ([$baseBranch, $headBranch] as $branch) {
$verifyBranch = shell_exec(sprintf(
'git rev-parse --verify -q %s',
escapeshellarg($branch)
));
if ($verifyBranch === null) {
abort("Branch '{$branch}' is ambigous.");
}
}
println("# Check diff between {$baseBranch}...{$headBranch}");
printMarkdownRowHeader([$baseBranch, $headBranch]);
$baseCommit = shell_exec(sprintf(
'git log --pretty=oneline -n 1 %s',
escapeshellarg($baseBranch)
));
$headCommit = shell_exec(sprintf(
'git log --pretty=oneline -n 1 %s',
escapeshellarg($headBranch)
));
printMarkdownRow([$baseCommit, $headCommit]);
println('## Diff');
$phpDiffFiles = shell_exec(sprintf(
'git --no-pager diff --name-status %s..%s -- "*.php"',
escapeshellarg($baseBranch),
escapeshellarg($headBranch)
));
if (!$phpDiffFiles) {
println('No differences were found in the PHP files. Exit.');
exit(0);
}
$diffFiles = (string)shell_exec(sprintf(
'git --no-pager diff --name-status %s..%s -- ":(exclude)*.php"',
escapeshellarg($baseBranch),
escapeshellarg($headBranch)
));
$diffFiles = array_filter(explode(PHP_EOL, $diffFiles));
if ($diffFiles) {
println('### non-PHP Files');
printMarkdownRowHeader(['filename', 'status']);
foreach ($diffFiles as $diffFile) {
printMarkdownRow(explode("\t", $diffFile));
}
}
println('### PHP Files');
$phpDiffFiles = array_filter(explode(PHP_EOL, trim($phpDiffFiles)));
$header = ['filename', 'status', 'BASE', 'HEAD', 'ast-changed'];
printMarkdownRowHeader($header);
foreach ($phpDiffFiles as $diffFile) {
[$status, $path] = explode("\t", $diffFile);
$row = [
'path' => $path,
'status' => $status,
...array_fill_keys([$baseBranch, $headBranch], ''),
'changed' => '',
];
switch ($status) {
case 'M':
$row[$headBranch] = getAstHash($headBranch, $path);
$row[$baseBranch] = getAstHash($baseBranch, $path);
break;
case 'A':
$row[$headBranch] = getAstHash($headBranch, $path);
break;
case 'D':
$row[$baseBranch] = getAstHash($baseBranch, $path);
break;
}
if ($row[$baseBranch] === $row[$headBranch]) {
$row['changed'] = 'NO CHANGE';
}
printMarkdownRow($row);
}
// obtained from: https://github.com/nikic/php-ast/blob/v1.1.0/util.php
function astDump($ast): string
{
if (is_string($ast)) {
return '"' . $ast . '"';
} elseif ($ast === null) {
return 'null';
} elseif (is_scalar($ast)) {
return (string)$ast;
}
$dump = ast\get_kind_name($ast->kind);
foreach ($ast->children as $name => $child) {
if ($name === 'docComment') {
continue;
}
$dump .= "\n\t\$i: " . str_replace("\n", "\n\t", astDump($child));
}
return $dump;
}
/**
* Get the AST hash for the given branch and file path.
*
* @param string $branch The name of the branch.
* @param string $path The path to the file.
* @return string The MD5 hash of the AST dump.
*/
function getAstHash(string $branch, string $path)
{
$source = shell_exec(sprintf('git show %s:"%s"', escapeshellarg($branch), $path));
$ast = \ast\parse_code($source, CHECK_PHP_VERSION);
return md5(astDump($ast));
}
/**
* Abort the execution and display an error message.
*
* @param string $errorMessage The error message to display.
* @return void
*/
function abort(string $errorMessage)
{
fwrite(STDERR, $errorMessage);
exit(1);
}
/**
* Print a message to the standard output.
*
* @param string $message The message to be printed.
* @return void
*/
function println(string $message): void
{
fwrite(STDOUT, $message . PHP_EOL);
}
/**
* Print the markdown row header.
*
* @param array $header The array of header elements.
* @return void
*/
function printMarkdownRowHeader(array $header): void
{
printMarkdownRow($header);
printMarkdownRow(array_fill_keys(range(0, count($header) - 1), ' ---- '));
}
/**
* Print a row in Markdown table format.
*
* @param array $row An array containing the values of each cell in the row.
* @return void
*/
function printMarkdownRow(array $row): void
{
$separator = ' | ';
$row = array_map('trim', $row);
println(trim($separator . implode($separator, $row) . $separator));
}

Check diff between main...tmp

main tmp
48261f6f838b529b003fa3a26eb770ea2b7bf06c wip 6c0c3b15f07051277993e24b7ce876e0a5baa3c4 Create README

Diff

non-PHP Files

filename status
README A

PHP Files

filename status BASE HEAD ast-changed
hello.php M b9b45a8d7d4608bce4541443e0db1ec7 b9b45a8d7d4608bce4541443e0db1ec7 NO CHANGE
prototype.php M 39bfc6a9f6632f1723bc13d5b234c396 3278e5a8d39c10c4cb30b3232af507d7
src/Command/EchoHashCommand.php D f5612c756ba27d3be6a479387ee97dcd
src/Parser/Parser.php D db232594d101bc1a7e4f40e67e6dc54c
src/Parser/Validation.php D 69850a9556020ed7d4234cc13f1d1c57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment