├── .gitignore ├── composer.json ├── extract.sh ├── README.md ├── LICENSE ├── download.php └── analyze_ternary.php /.gitignore: -------------------------------------------------------------------------------- 1 | sources/ 2 | zipballs/ 3 | vendor/ 4 | composer.lock 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "nikic/php-parser": "^4.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /extract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | GLOBIGNORE=".:.." 3 | for zip in zipballs/*/*.zip; do 4 | target=${zip/zipballs/sources} 5 | target=${target/.zip/} 6 | echo $target 7 | if [ -d $target ]; then 8 | continue 9 | fi 10 | 11 | echo "Extracting..." 12 | mkdir -p $target 13 | unzip $zip -d $target 14 | subdir=($target/*) 15 | mv $subdir/* $target 16 | rmdir $subdir 17 | done 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Analysis of popular composer packages 2 | ------------------------------------- 3 | 4 | This repository contains a couple of scripts to download the most popular composer packages and 5 | analyze them. Usage: 6 | 7 | ``` 8 | # Download 1000 most popular packages 9 | php download.php 0 1000 10 | ./extract.sh 11 | ``` 12 | 13 | The `zipballs/` directory contains downloaded archives, while `sources/` contains the extracted 14 | sources. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /download.php: -------------------------------------------------------------------------------- 1 | $package['name']; 13 | $id++; 14 | if ($id >= $max) { 15 | return; 16 | } 17 | } 18 | } 19 | } 20 | 21 | if ($argc < 3) { 22 | echo "Usage: download.php min-package max-package\n"; 23 | return; 24 | } 25 | 26 | $minPackage = $argv[1]; 27 | $maxPackage = $argv[2]; 28 | foreach (getTopPackages($minPackage, $maxPackage) as $i => $packageName) { 29 | echo "[$i] $packageName\n"; 30 | $packageName = strtolower($packageName); 31 | $url = 'https://packagist.org/packages/' . $packageName . '.json'; 32 | $json = json_decode(file_get_contents($url), true); 33 | $versions = $json['package']['versions']; 34 | if (isset($versions['dev-master'])) { 35 | $version = 'dev-master'; 36 | } else if (isset($versions['dev-main'])) { 37 | $version = 'dev-main'; 38 | } else { 39 | // Pick latest version. 40 | $keys = array_keys($versions); 41 | $version = $keys[0]; 42 | } 43 | 44 | $package = $versions[$version]; 45 | if ($package['dist'] === null) { 46 | echo "Skipping due to missing dist\n"; 47 | continue; 48 | } 49 | 50 | $dist = $package['dist']['url']; 51 | $zipball = __DIR__ . '/zipballs/' . $packageName . '.zip'; 52 | if (!file_exists($zipball)) { 53 | echo "Downloading $version...\n"; 54 | $dir = dirname($zipball); 55 | if (!is_dir($dir)) { 56 | mkdir($dir, 0777, true); 57 | } 58 | 59 | exec("wget $dist -O $zipball", $execOutput, $execRetval); 60 | if ($execRetval !== 0) { 61 | echo "wget failed: $execOutput\n"; 62 | break; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /analyze_ternary.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'comments', 'startLine', 'endLine', 10 | 'startFilePos', 'endFilePos', 11 | ] 12 | ]); 13 | $parser = new PhpParser\Parser\Php7($lexer); 14 | 15 | $visitor = new class extends PhpParser\NodeVisitorAbstract { 16 | public $path = null; 17 | public $code = null; 18 | public $totalArrayDimFetches = 0; 19 | public $alternativeArrayDimFetches = 0; 20 | 21 | public function enterNode(PhpParser\Node $node) { 22 | if (!$node instanceof Expr\ArrayDimFetch) { 23 | return; 24 | } 25 | 26 | $this->totalArrayDimFetches++; 27 | if ($this->code[$node->getEndFilePos()] !== '}') { 28 | return; 29 | } 30 | 31 | // Special case: Don't recognize ${foo[x]} 32 | if (substr($this->code, $node->getStartFilePos(), 2) === '${') { 33 | return; 34 | } 35 | 36 | $this->alternativeArrayDimFetches++; 37 | echo $this->path . ":" . $node->getStartLine() . "\n"; 38 | echo " " . $this->getCode($node) . "\n"; 39 | 40 | /*if (!$node instanceof Expr\Ternary) { 41 | return; 42 | } 43 | 44 | $cond = $node->cond; 45 | if (!$cond instanceof Expr\Ternary) { 46 | return; 47 | } 48 | 49 | if ($node->if === null && $cond->if === null) { 50 | return; 51 | } 52 | 53 | echo $this->path . ":" . $node->getStartLine() . "\n"; 54 | 55 | // Inaccurate... 56 | $endPos = $cond->getEndFilePos(); 57 | if ($this->code[$endPos+1] === ')') { 58 | echo "With parens\n\n"; 59 | } else { 60 | echo "WITHOUT parens\n\n"; 61 | }*/ 62 | 63 | /*if ($node instanceof Expr\BinaryOp\Plus || 64 | $node instanceof Expr\BinaryOp\Minus 65 | ) { 66 | if (!$node->left instanceof Expr\BinaryOp\Concat) { 67 | return; 68 | } 69 | } else if ($node instanceof Expr\BinaryOp\ShiftLeft || 70 | $node instanceof Expr\BinaryOp\ShiftRight 71 | ) { 72 | if (!$node->left instanceof Expr\BinaryOp\Concat && 73 | !$node->right instanceof Expr\BinaryOp\Concat 74 | ) { 75 | return; 76 | } 77 | } else { 78 | return; 79 | } 80 | 81 | echo $this->path . ":" . $node->getStartLine() . "\n"; 82 | echo "POSSIBLE\n\n";*/ 83 | } 84 | 85 | private function getCode(PhpParser\Node $node) { 86 | $startPos = $node->getStartFilePos(); 87 | $endPos = $node->getEndFilePos(); 88 | return substr($this->code, $startPos, $endPos - $startPos + 1); 89 | } 90 | }; 91 | 92 | $traverser = new PhpParser\NodeTraverser; 93 | $traverser->addVisitor($visitor); 94 | 95 | $it = new RecursiveIteratorIterator( 96 | new RecursiveDirectoryIterator(__DIR__ . '/sources'), 97 | RecursiveIteratorIterator::LEAVES_ONLY 98 | ); 99 | 100 | $i = 0; 101 | foreach ($it as $f) { 102 | $path = $f->getPathName(); 103 | if (!preg_match('/\.php$/', $path)) { 104 | continue; 105 | } 106 | 107 | if (++$i % 1000 == 0) { 108 | echo $i . "\n"; 109 | } 110 | 111 | $code = file_get_contents($path); 112 | try { 113 | $stmts = $parser->parse($code); 114 | } catch (PhpParser\Error $e) { 115 | echo $path . "\n"; 116 | echo "Parse error: " . $e->getMessage() . "\n"; 117 | continue; 118 | } 119 | 120 | $visitor->path = $path; 121 | $visitor->code = $code; 122 | $traverser->traverse($stmts); 123 | } 124 | 125 | echo "Total array dim fetches: ", $visitor->totalArrayDimFetches, "\n"; 126 | echo "Alternative array dim fetches: ", $visitor->alternativeArrayDimFetches, "\n"; 127 | --------------------------------------------------------------------------------