Last active
July 31, 2024 10:59
-
-
Save pounard/7ad24b39c5e51a76105f46c1e06e3ea6 to your computer and use it in GitHub Desktop.
Finds duplicated functions names in PHP codebase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Run in folder where to scan: | |
* php -f /path/to/duplicated_functions.php | |
* | |
* For more detailed debug information: | |
* DEBUG=1 php -f /path/to/duplicated_functions.php | |
*/ | |
declare (strict_types=1); | |
function display(string $message, ?string $prefix = null) { | |
if ($prefix) { | |
echo "[" . $prefix . "] "; | |
} | |
echo $message . "\n"; | |
} | |
function debug(string $message, ?string $prefix = null) { | |
if (getenv('DEBUG')) { | |
display($message, $prefix); | |
} | |
} | |
function warning(string $message, ?string $prefix = null) { | |
display($message, $prefix ?? 'WARNING'); | |
} | |
function listfilesin(string $dirname, string $pattern = '@\.(php|inc|module|install)@'): array { | |
$handle = opendir($dirname); | |
$ret = []; | |
while ($filename = readdir($handle)) { | |
if ('.' === $filename || '..' === $filename) { | |
continue; | |
} | |
$absolute = $dirname . '/' . $filename; | |
if (is_dir($absolute)) { | |
foreach (listfilesin($absolute) as $nested) { | |
$ret[] = $nested; | |
} | |
} else if (preg_match($pattern, $filename)) { | |
$ret[] = $absolute; | |
} else { | |
debug($absolute, 'ignoring'); | |
} | |
} | |
return $ret; | |
} | |
class FunctionOccurence | |
{ | |
public function __construct( | |
public string $name, | |
public string $filename, | |
public int $line, | |
) {} | |
} | |
class FunctionOccurenceSet | |
{ | |
public int $count = 0; | |
/** @var FunctionOccurence[] */ | |
public array $occurences = []; | |
public function __construct( | |
public string $name | |
) {} | |
public function add(string $filename, int $line) { | |
$this->occurences[] = new FunctionOccurence($this->name, $filename, $line); | |
$this->count++; | |
} | |
} | |
class FunctionMap | |
{ | |
public array $functions = []; | |
public function register(string $name, string $filename, int $line) | |
{ | |
$set = ($this->functions[$name] ??= new FunctionOccurenceSet($name)); | |
assert($set instanceof FunctionOccurenceSet); | |
$set->add($filename, $line); | |
} | |
/** @return FunctionOccurenceSet[] */ | |
public function findDuplicates(): iterable | |
{ | |
foreach ($this->functions as $set) { | |
if (1 < $set->count) { | |
yield $set; | |
} | |
} | |
} | |
} | |
$map = new FunctionMap(); | |
foreach (listfilesin(getcwd()) as $filename) { | |
debug($filename, 'found'); | |
$data = file_get_contents($filename); | |
if ($data) { | |
$depth = 0; | |
$inFunctionDeclaration = false; $inStringOrComplexExpr = false; | |
foreach (PhpToken::tokenize($data) as $token) { | |
assert($token instanceof PhpToken); | |
if ($token->isIgnorable()) { | |
continue; | |
} | |
// Ignore "${EXPR}" | |
if ($token->is(T_DOLLAR_OPEN_CURLY_BRACES) || $token->is(T_CURLY_OPEN)) { | |
$inStringOrComplexExpr = true; | |
continue; | |
} | |
$name = $token->getTokenName(); | |
if ('{' === $name) { | |
$depth++; | |
} else if ('}' === $name) { | |
if ($inStringOrComplexExpr) { | |
$inStringOrComplexExpr = false; | |
} else { | |
$depth--; | |
} | |
} | |
if (0 > $depth) { | |
warning(sprintf("Missed at least one opening brace '{' in '%s' at line %d", $filename, $token->line)); | |
$depth = 0; // Avoid repeating the warning. | |
} | |
if (0 < $depth) { | |
// Looking for root namespace functions, ignoring depths. | |
continue; | |
} | |
if ($token->is(T_FUNCTION)) { | |
$inFunctionDeclaration = true; | |
} else if ($token->is(T_WHITESPACE)) { | |
// Ignore that. | |
} else if ($token->is(T_STRING)) { | |
if ($inFunctionDeclaration) { | |
// Function name. | |
$map->register($token->text, $filename, $token->line); | |
$inFunctionDeclaration = false; | |
} | |
} | |
} | |
} else { | |
warning(sprintf("Could not read file '%s'", $filename)); | |
} | |
} | |
foreach ($map->findDuplicates() as $set) { | |
assert($set instanceof FunctionOccurenceSet); | |
echo $set->name . "()\n"; | |
foreach ($set->occurences as $occurence) { | |
assert($occurence instanceof FunctionOccurence); | |
echo " - " . $occurence->filename . " at line " . $occurence->line . "\n"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment