├── .php_cs.dist.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Code ├── CodeLocation.php └── GenericCodeLocation.php ├── Results ├── FileSearchResults.php ├── Nodes │ ├── ArrayItemNode.php │ ├── ArrayNode.php │ ├── AssignmentNode.php │ ├── AssignmentOperationNode.php │ ├── BinaryOperationNode.php │ ├── ClassConstantNode.php │ ├── ClassDefinitionNode.php │ ├── ClassMethodNode.php │ ├── ClassPropertyNode.php │ ├── CommentNode.php │ ├── FunctionCallNode.php │ ├── FunctionDefinitionNode.php │ ├── MethodCallNode.php │ ├── OperationNode.php │ ├── ParameterNode.php │ ├── PropertyAccessNode.php │ ├── ResultNode.php │ ├── Scalar │ │ ├── NumberNode.php │ │ └── StringNode.php │ ├── StaticMethodCallNode.php │ ├── StaticPropertyAccessNode.php │ ├── Traits │ │ ├── BootsTraits.php │ │ ├── HasLocation.php │ │ ├── HasName.php │ │ ├── HasValue.php │ │ ├── HasVisibility.php │ │ └── TransformsArguments.php │ ├── ValueNode.php │ └── VariableNode.php ├── SearchError.php └── SearchResult.php ├── Searcher.php ├── Support ├── Arr.php ├── Collections │ ├── Arrayable.php │ ├── Collection.php │ └── Enumerable.php ├── ExpressionTransformer.php ├── File.php ├── NameResolver.php ├── StatementTransformer.php ├── VirtualFile.php └── helpers.php └── Visitors ├── AssignmentVisitor.php ├── ClassDefinitionVisitor.php ├── CommentVisitor.php ├── FunctionCallVisitor.php ├── FunctionDefinitionVisitor.php ├── MethodCallVisitor.php ├── NewClassVisitor.php ├── NodeVisitor.php ├── StaticCallVisitor.php ├── StaticPropertyVisitor.php └── VariableReferenceVisitor.php /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR2' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `php-code-search` will be documented in this file. 4 | 5 | --- 6 | 7 | ## 1.11.0 - 2023-11-10 8 | 9 | - update helper function names to avoid issues when running in a Laravel application, thanks to @Hypnopompia for reporting the issue. 10 | 11 | ## 1.10.6 - 2023-11-10 12 | 13 | - fix warnings in PHP 8.1, closes #31 14 | 15 | ## 1.10.5 - 2021-08-17 16 | 17 | - various bug fixes, code cleanup 18 | 19 | ## 1.10.4 - 2021-08-17 20 | 21 | - fix nullable parameter type bug 22 | 23 | ## 1.10.3 - 2021-08-17 24 | 25 | - fix another bug with resolving static calls 26 | 27 | ## 1.10.2 - 2021-08-17 28 | 29 | - fix bug with resolving static method call names 30 | 31 | ## 1.10.1 - 2021-08-03 32 | 33 | - fix nested array bug in `Arr::matchesAny()` 34 | 35 | ## 1.10.0 - 2021-07-29 36 | 37 | - add support for class definition searches using `classes()` 38 | - internal: significant refactoring 39 | 40 | ## 1.9.0 - 2021-07-28 41 | 42 | - add support for function definition searches using `functions()` 43 | 44 | ## 1.8.1 - 2021-07-27 45 | 46 | - require `permafrost-dev/code-snippets` v1.2.0+ 47 | - update `composer.json` keywords 48 | 49 | ## 1.8.0 - 2021-07-27 50 | 51 | - `static()` supports searching for static property accesses like `SomeClass::$myProperty` or `myProperty` 52 | - major internal refactoring of nodes 53 | 54 | ## 1.7.0 - 2021-07-25 55 | 56 | - use latest version of `code-snippets` 57 | - implement multi-line highlighting for code snippets 58 | 59 | ## 1.6.5 - 2021-07-25 60 | 61 | - fix typehint issue 62 | 63 | ## 1.6.4 - 2021-07-25 64 | 65 | - use the `permafrost-dev/code-snippets` package 66 | 67 | ## 1.6.3 - 2021-07-24 68 | 69 | - add `getLineNumber` helper method to the `CodeSnippet` class 70 | 71 | ## 1.6.2 - 2021-07-23 72 | 73 | - fix additional issues with node names 74 | 75 | ## 1.6.1 - 2021-07-22 76 | 77 | - fix issue with function call node names 78 | 79 | ## 1.6.0 - 2021-07-07 80 | 81 | - all function call, static method call, method call nodes have an `args` property containing the value node(s) of the parsed arguments. 82 | - assignment nodes now have a `value` property and `value()` method. 83 | - strings and numbers are converted to `StringNode` and `NumberNode` nodes, respectively. 84 | - most values converted to Node classes that implement either `ResultNode`, `ValueNode`, or both. 85 | - operations (addition, concat, etc.) converted to Node classes that implement `OperationNode`. 86 | - fixed several bugs related to non-matches being returned as matches. 87 | 88 | ## 1.5.3 - 2021-07-07 89 | 90 | - fix issues with `Assignment` nodes 91 | 92 | ## 1.5.2 - 2021-07-07 93 | 94 | - fix issues with `Array`, `ArrayItem` and `ArrayDimFetch` nodes 95 | 96 | ## 1.5.1 - 2021-07-06 97 | 98 | - internal refactoring 99 | 100 | ## 1.5.0 - 2021-07-06 101 | 102 | - rename `FunctionCallLocation` to `GenericCodeLocation` and remove the name property 103 | 104 | ## 1.4.0 - 2021-07-05 105 | 106 | - allow searching for static method calls like `MyClass` or `MyClass::someMethod` 107 | - add `ResultNode` class 108 | - add `node` property to `SearchResult` class 109 | 110 | ## 1.3.2 - 2021-07-05 111 | 112 | - minor fix to method searching 113 | 114 | ## 1.3.1 - 2021-07-05 115 | 116 | - minor fix to variable searching 117 | 118 | ## 1.3.0 - 2021-07-05 119 | 120 | - add `methods` method 121 | - add `variables` method 122 | 123 | ## 1.2.1 - 2021-07-05 124 | 125 | - fix function search feature 126 | 127 | ## 1.2.0 - 2021-07-04 128 | 129 | - add `searchCode` method 130 | 131 | ## 1.1.1 - 2021-07-04 132 | 133 | - add `filename` property to `File` class 134 | 135 | ## 1.1.0 - 2021-07-04 136 | 137 | - add `file` property to `SearchResult` class 138 | 139 | ## 1.0.0 - 2021-07-04 140 | 141 | - initial release 142 | 143 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Permafrost Software LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Searching PHP source code made easy 2 | 3 |

4 | php-code-search logo 5 |

6 | Package Version 7 | Downloads 8 | license 9 | Test Run Status 10 | code coverage 11 |

12 | 13 | --- 14 | 15 | Search PHP source code for function & method calls, variable assignments, classes and more directly from PHP. 16 | 17 | --- 18 | 19 | ## Installation 20 | 21 | ```bash 22 | composer require permafrost-dev/php-code-search 23 | ``` 24 | 25 | ## Searching 26 | 27 | To search a file, use the `search` method. Its only parameter may be either a string containing a valid filename or an instance of `\Permafrost\PhpCodeSearch\Support\File`. 28 | 29 | To search a string instead, use the `searchCode` method. 30 | 31 | The search methods return an instance of `Permafrost\PhpCodeSearch\Results\FileSearchResults`, which has a `results` property. 32 | 33 | Each `result` is an instance of `Permafrost\PhpCodeSearch\Results\SearchResult` with the following properties: 34 | 35 | - `node` - the specific item that was found 36 | - `node->name(): string` 37 | - `location` - the location in the file that the item was found 38 | - `location->startLine(): int` 39 | - `location->endLine(): int` 40 | - `snippet` - a snippet of code lines from the file with the result line in the middle 41 | - `snippet->toString(): string` 42 | - `file()` _(method)_ - provides access to the file that was searched 43 | 44 | ### Searching 45 | 46 | To search through the code in a string or file, use the `Searcher` class: 47 | 48 | ```php 49 | use Permafrost\PhpCodeSearch\Searcher; 50 | 51 | $searcher = new Searcher(); 52 | ``` 53 | 54 | To search a file, use the `search` method, and the `searchCode` method to search a string of code. 55 | 56 | ```php 57 | $searcher 58 | ->functions(['strtolower', 'strtoupper']) 59 | ->search('./file1.php'); 60 | 61 | $searcher 62 | ->variables(['/^one[A-Z]$/']) 63 | ->searchCode('variables(['twoA', '/^one.$/']) 75 | ->searchCode('results as $result) { 83 | echo "Found '{$result->node->name()}' on line {$result->location->startLine}" . PHP_EOL; 84 | } 85 | ``` 86 | 87 | ### Functions 88 | 89 | To search for function calls or definitions, use the `functions` method. 90 | 91 | ```php 92 | // search for references AND definitions for 'strtolower' and/or 'myfunc' 93 | $searcher 94 | ->functions(['strtolower', 'myfunc']) 95 | ->search('file1.php'); 96 | ``` 97 | 98 | ### Method calls 99 | 100 | To search for a method call by name, use the `methods` method. 101 | 102 | Method call nodes have an `args` property that can be looped through to retrieve the arguments for the method call. 103 | 104 | ```php 105 | $results = $searcher 106 | ->methods(['/test(One|Two)/']) 107 | ->searchCode('testOne("hello world 1"); '. 109 | ' $obj->testTwo("hello world", 2); '. 110 | '' 111 | ); 112 | 113 | foreach($results->results as $result) { 114 | echo "Found '{$result->node->name()}' on line {$result->location->startLine}" . PHP_EOL; 115 | 116 | foreach($result->node->args as $arg) { 117 | echo " argument: '{$arg->value}'" . PHP_EOL; 118 | } 119 | } 120 | ``` 121 | 122 | ### Static calls 123 | 124 | To search for static method or property calls, use the `static` method. 125 | 126 | Valid search terms are either a class name like `Cache`, or a class name and a method name like `Cache::remember`. 127 | 128 | ```php 129 | $searcher 130 | ->static(['Ray', 'Cache::has', 'Request::$myProperty']) 131 | ->search('./app/Http/Controllers/MyController.php'); 132 | ``` 133 | 134 | ### Classes 135 | 136 | To search for either a class definition or a class created by the `new` keyword, use the `classes` method. 137 | 138 | ```php 139 | $searcher 140 | ->classes(['MyController']) 141 | ->search('./app/Http/Controllers/MyController.php'); 142 | ``` 143 | 144 | ### Variable assignments 145 | 146 | To search for a variable assignment by variable name, use the `assignments` method. _Note: The `$` should be omitted._ 147 | 148 | ```php 149 | $searcher 150 | ->assignments(['myVar']) 151 | ->search('./app/Http/Controllers/MyController.php'); 152 | ``` 153 | 154 | ### Results without code snippets 155 | 156 | To return search results without associated code snippets, use the `withoutSnippets` method: 157 | 158 | ```php 159 | $searcher 160 | ->withoutSnippets() 161 | ->functions(['strtolower']) 162 | ->search('file1.php'); 163 | ``` 164 | 165 | 166 | ## Testing 167 | 168 | ```bash 169 | ./vendor/bin/phpunit 170 | ``` 171 | 172 | ## Changelog 173 | 174 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 175 | 176 | ## Contributing 177 | 178 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 179 | 180 | ## Security Vulnerabilities 181 | 182 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 183 | 184 | ## Credits 185 | 186 | - [Patrick Organ](https://github.com/patinthehat) 187 | - [All Contributors](../../contributors) 188 | 189 | ## License 190 | 191 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 192 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "permafrost-dev/php-code-search", 3 | "description": "Search PHP code for function & method calls, variable assignments, and more", 4 | "keywords": [ 5 | "permafrost", 6 | "code", 7 | "search", 8 | "sourcecode", 9 | "php" 10 | ], 11 | "homepage": "https://github.com/permafrost-dev/php-code-search", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Patrick Organ", 16 | "email": "patrick@permafrost.dev", 17 | "homepage": "https://permafrost.dev", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": "^7.4|^8.0", 23 | "nikic/php-parser": "^5.0", 24 | "permafrost-dev/code-snippets": "^1.2.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^9.5", 28 | "spatie/phpunit-snapshot-assertions": "^4.2" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Permafrost\\PhpCodeSearch\\": "src/" 33 | }, 34 | "files": [ 35 | "src/Support/helpers.php" 36 | ] 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Permafrost\\PhpCodeSearch\\Tests\\": "tests" 41 | }, 42 | "files": [ 43 | "tests/helpers.php" 44 | ] 45 | }, 46 | "scripts": { 47 | "test": "vendor/bin/phpunit" 48 | }, 49 | "config": { 50 | "sort-packages": true 51 | }, 52 | "minimum-stability": "dev", 53 | "prefer-stable": true, 54 | "funding": [ 55 | { 56 | "type": "github", 57 | "url": "https://github.com/sponsors/permafrost-dev" 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /src/Code/CodeLocation.php: -------------------------------------------------------------------------------- 1 | startLine = $startLine; 21 | $this->endLine = $endLine; 22 | } 23 | 24 | public static function create(int $startLine, int $endLine): self 25 | { 26 | return new static($startLine, $endLine); 27 | } 28 | 29 | public static function createFromNode(Node $node): self 30 | { 31 | return new static($node->getStartLine(), $node->getEndLine()); 32 | } 33 | 34 | public function column(): int 35 | { 36 | return $this->column; 37 | } 38 | 39 | public function endLine(): int 40 | { 41 | return $this->endLine; 42 | } 43 | 44 | public function startLine(): int 45 | { 46 | return $this->startLine; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Results/FileSearchResults.php: -------------------------------------------------------------------------------- 1 | file = $file; 31 | $this->withSnippets = $withSnippets; 32 | } 33 | 34 | public function add(ResultNode $resultNode, CodeLocation $location): self 35 | { 36 | $snippet = $this->makeSnippet($resultNode->location()->startLine(), $resultNode->location()->endLine()); 37 | 38 | $this->results[] = new SearchResult($resultNode, $location, $snippet, $this->file); 39 | 40 | return $this; 41 | } 42 | 43 | public function hasErrors(): bool 44 | { 45 | return count($this->errors) > 0; 46 | } 47 | 48 | public function addError(SearchError $errorResult): self 49 | { 50 | $this->errors[] = $errorResult; 51 | 52 | return $this; 53 | } 54 | 55 | protected function makeSnippet(int $startLine, int $endLine, int $lineCount = 8): ?CodeSnippet 56 | { 57 | if (! $this->withSnippets) { 58 | return null; 59 | } 60 | 61 | return (new CodeSnippet()) 62 | ->surroundingLines($startLine, $endLine) 63 | ->snippetLineCount($lineCount) 64 | ->fromFile($this->file->getRealPath()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Results/Nodes/ArrayItemNode.php: -------------------------------------------------------------------------------- 1 | key = ExpressionTransformer::parserNodeToResultNode($node->key); 23 | $this->value = ExpressionTransformer::parserNodeToResultNode($node->value); 24 | $this->location = GenericCodeLocation::createFromNode($node); 25 | } 26 | 27 | public function name(): string 28 | { 29 | return $this->key; 30 | } 31 | 32 | public function value() 33 | { 34 | return $this->value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Results/Nodes/ArrayNode.php: -------------------------------------------------------------------------------- 1 | items; 26 | } 27 | 28 | $this->value = $this->transformArgumentsToNodes($value); 29 | $this->location = GenericCodeLocation::createFromNode($node); 30 | } 31 | 32 | public function value() 33 | { 34 | return $this->value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Results/Nodes/AssignmentNode.php: -------------------------------------------------------------------------------- 1 | variableName = $node->var->name; 26 | $this->value = ExpressionTransformer::parserNodeToResultNode($node->expr); 27 | $this->location = GenericCodeLocation::createFromNode($node); 28 | $this->name = $this->name(); 29 | } 30 | 31 | public static function create(Node\Expr\Assign $node): self 32 | { 33 | return new static(...func_get_args()); 34 | } 35 | 36 | public function name(): string 37 | { 38 | return $this->variableName; 39 | } 40 | 41 | public function value() 42 | { 43 | return $this->value; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Results/Nodes/AssignmentOperationNode.php: -------------------------------------------------------------------------------- 1 | name = $node->var->name; 23 | $this->value = ExpressionTransformer::parserNodeToResultNode($node->expr); 24 | $this->location = GenericCodeLocation::createFromNode($node); 25 | } 26 | 27 | public function name(): string 28 | { 29 | return $this->name; 30 | } 31 | 32 | public function value() 33 | { 34 | return $this->value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Results/Nodes/BinaryOperationNode.php: -------------------------------------------------------------------------------- 1 | symbol = $node->getOperatorSigil(); 29 | $this->left = ExpressionTransformer::parserNodeToResultNode($node->left); 30 | $this->right = ExpressionTransformer::parserNodeToResultNode($node->right); 31 | 32 | // $this->value = ''; 33 | // if (property_exists($this->left, 'value') && property_exists($this->right, 'value')) { 34 | $leftValue = ''; 35 | $rightValue = ''; 36 | 37 | $this->value = ExpressionTransformer::binaryOperationNodeToValue($this); 38 | 39 | $this->location = GenericCodeLocation::createFromNode($node); 40 | } 41 | 42 | public function symbol(): string 43 | { 44 | return $this->symbol; 45 | } 46 | 47 | public function left() 48 | { 49 | return $this->left; 50 | } 51 | 52 | public function right() 53 | { 54 | return $this->right; 55 | } 56 | 57 | public function value() 58 | { 59 | return $this->value; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Results/Nodes/ClassConstantNode.php: -------------------------------------------------------------------------------- 1 | bootTraits($node); 23 | $this->bootHasValue($node->consts[0]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Results/Nodes/ClassDefinitionNode.php: -------------------------------------------------------------------------------- 1 | bootTraits($node); 34 | 35 | $this->extends = NameResolver::resolve($node->extends); 36 | $this->implements = NameResolver::resolveAll($node->implements); 37 | $this->properties = StatementTransformer::parserNodesToResultNode($node->getProperties()); 38 | $this->methods = StatementTransformer::parserNodesToResultNode($node->getMethods()); 39 | $this->constants = StatementTransformer::parserNodesToResultNode($node->getConstants()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Results/Nodes/ClassMethodNode.php: -------------------------------------------------------------------------------- 1 | bootTraits($node); 33 | 34 | $this->isStatic = $node->isStatic(); 35 | $this->isAbstract = $node->isAbstract(); 36 | $this->returnType = NameResolver::resolve($node->getReturnType()); 37 | $this->params = StatementTransformer::parserNodesToResultNode($node->params); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Results/Nodes/ClassPropertyNode.php: -------------------------------------------------------------------------------- 1 | bootTraits($node); 27 | 28 | $this->isStatic = $node->isStatic(); 29 | $this->default = ExpressionTransformer::parserNodeToResultNode($node->props[0]->default); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Results/Nodes/CommentNode.php: -------------------------------------------------------------------------------- 1 | value = $node->getText(); 19 | $this->location = GenericCodeLocation::createFromNode($node); 20 | } 21 | 22 | public static function create(Comment $node): self 23 | { 24 | return new static(...func_get_args()); 25 | } 26 | 27 | public function name(): string 28 | { 29 | return $this->value; 30 | } 31 | 32 | public function value(): string 33 | { 34 | return $this->value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Results/Nodes/FunctionCallNode.php: -------------------------------------------------------------------------------- 1 | bootTraits($node); 23 | 24 | $this->args = ExpressionTransformer::parserNodesToResultNodes($node->args); 25 | } 26 | 27 | public static function create(Node\Expr\FuncCall $node): self 28 | { 29 | return new static(...func_get_args()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Results/Nodes/FunctionDefinitionNode.php: -------------------------------------------------------------------------------- 1 | bootTraits($node); 25 | 26 | $this->args = StatementTransformer::parserNodesToResultNode($node->getParams()); 27 | } 28 | 29 | public static function create(Node\Stmt\Function_ $node): self 30 | { 31 | return new static(...func_get_args()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Results/Nodes/MethodCallNode.php: -------------------------------------------------------------------------------- 1 | variableName = $node->var->name; 30 | $this->methodName = $node->name->toString(); 31 | $this->args = $this->transformArgumentsToNodes($node->args); 32 | $this->name = $this->name(); 33 | $this->location = GenericCodeLocation::createFromNode($node); 34 | } 35 | 36 | public static function create(Node\Expr\MethodCall $node): self 37 | { 38 | return new static(...func_get_args()); 39 | } 40 | 41 | public function name(): string 42 | { 43 | return "\${$this->variableName}->{$this->methodName}"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Results/Nodes/OperationNode.php: -------------------------------------------------------------------------------- 1 | name = $node->var->name; 25 | 26 | if ($node->type instanceof Node\NullableType) { 27 | $this->type = $node->getType(); 28 | } else { 29 | $this->type = opt($node->type)->toString(); 30 | } 31 | 32 | $this->default = $node->default ? ExpressionTransformer::parserNodeToResultNode($node->default) : null; 33 | $this->location = GenericCodeLocation::createFromNode($node); 34 | } 35 | 36 | public static function create(Node\Param $node): self 37 | { 38 | return new static(...func_get_args()); 39 | } 40 | 41 | public function defaultValue() 42 | { 43 | return $this->default; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Results/Nodes/PropertyAccessNode.php: -------------------------------------------------------------------------------- 1 | objectName = $node->var->name; 22 | $this->propertyName = $node->name->toString(); 23 | $this->location = GenericCodeLocation::createFromNode($node); 24 | } 25 | 26 | public function name(): string 27 | { 28 | return $this->objectName; 29 | } 30 | 31 | public function value() 32 | { 33 | return $this->propertyName; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Results/Nodes/ResultNode.php: -------------------------------------------------------------------------------- 1 | value; 22 | 23 | if ($value instanceof LNumber || $value instanceof DNumber) { 24 | $value = $value->value; 25 | } 26 | 27 | $this->value = $value; 28 | $this->location = GenericCodeLocation::createFromNode($node); 29 | } 30 | 31 | public function value() 32 | { 33 | return $this->value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Results/Nodes/Scalar/StringNode.php: -------------------------------------------------------------------------------- 1 | value; 20 | 21 | if ($value instanceof String_) { 22 | $value = $value->value; 23 | } 24 | 25 | $this->value = $value; 26 | $this->location = GenericCodeLocation::createFromNode($node); 27 | } 28 | 29 | public function value() 30 | { 31 | return $this->value; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Results/Nodes/StaticMethodCallNode.php: -------------------------------------------------------------------------------- 1 | className = $node->class->toString(); 30 | $this->methodName = $node->name->toString(); 31 | $this->args = $this->transformArgumentsToNodes($node->args); 32 | $this->name = $this->name(); 33 | $this->location = GenericCodeLocation::createFromNode($node); 34 | } 35 | 36 | public static function create(Node\Expr\StaticCall $node): self 37 | { 38 | return new static(...func_get_args()); 39 | } 40 | 41 | public function name(): string 42 | { 43 | return "{$this->className}::{$this->methodName}"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Results/Nodes/StaticPropertyAccessNode.php: -------------------------------------------------------------------------------- 1 | objectName = $node->class->toString(); 22 | $this->propertyName = $node->name->toString(); 23 | $this->location = GenericCodeLocation::createFromNode($node); 24 | } 25 | 26 | public function name(): string 27 | { 28 | return $this->objectName; 29 | } 30 | 31 | public function value() 32 | { 33 | return $this->propertyName; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Results/Nodes/Traits/BootsTraits.php: -------------------------------------------------------------------------------- 1 | getTraitNames()) 14 | ->map(function (string $name) { 15 | if (strpos($name, __NAMESPACE__) === false) { 16 | return null; 17 | } 18 | 19 | return Arr::last(explode('\\', $name)); 20 | }) 21 | ->filter() 22 | ->each(function (string $name) use ($node) { 23 | $bootMethodName = 'boot'.$name; 24 | 25 | if (method_exists($this, $bootMethodName)) { 26 | $this->$bootMethodName($node); 27 | } 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Results/Nodes/Traits/HasLocation.php: -------------------------------------------------------------------------------- 1 | location; 16 | } 17 | 18 | public function withLocation(CodeLocation $location): self 19 | { 20 | $this->location = $location; 21 | 22 | return $this; 23 | } 24 | 25 | protected function bootHasLocation($node): void 26 | { 27 | $this->location = GenericCodeLocation::createFromNode($node); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Results/Nodes/Traits/HasName.php: -------------------------------------------------------------------------------- 1 | name; 15 | } 16 | 17 | protected function bootHasName($node): void 18 | { 19 | $this->name = NameResolver::resolve($node); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Results/Nodes/Traits/HasValue.php: -------------------------------------------------------------------------------- 1 | value; 16 | } 17 | 18 | protected function bootHasValue($node): void 19 | { 20 | if (property_exists($node, 'value')) { 21 | $this->value = ExpressionTransformer::parserNodeToResultNode($node->value); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Results/Nodes/Traits/HasVisibility.php: -------------------------------------------------------------------------------- 1 | initVisibilityAttribute($node); 14 | } 15 | 16 | protected function initVisibilityAttribute($node): void 17 | { 18 | $visibilityMap = [ 19 | 'isPublic' => 'public', 20 | 'isPrivate' => 'private', 21 | 'isProtected' => 'protected', 22 | ]; 23 | 24 | foreach ($visibilityMap as $method => $visibility) { 25 | if ($node->$method()) { 26 | $this->visibility = $visibility; 27 | 28 | return; 29 | } 30 | } 31 | 32 | $this->visibility = 'unknown'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Results/Nodes/Traits/TransformsArguments.php: -------------------------------------------------------------------------------- 1 | name = NameResolver::resolve($node); 20 | $this->location = GenericCodeLocation::createFromNode($node); 21 | } 22 | 23 | public static function create(Node $node): self 24 | { 25 | return new static(...func_get_args()); 26 | } 27 | 28 | public function name(): string 29 | { 30 | return $this->name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Results/SearchError.php: -------------------------------------------------------------------------------- 1 | error = $error; 16 | $this->message = $message; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Results/SearchResult.php: -------------------------------------------------------------------------------- 1 | node = $node; 37 | $this->location = $location; 38 | $this->snippet = $snippet; 39 | $this->file = is_string($file) ? new File($file) : $file; 40 | } 41 | 42 | public function file() 43 | { 44 | return $this->file; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Searcher.php: -------------------------------------------------------------------------------- 1 | parser = $parser ?? (new ParserFactory())->createForHostVersion(); 59 | } 60 | 61 | public function assignments(array $varNames): self 62 | { 63 | $this->assignments = array_merge($this->assignments, $varNames); 64 | 65 | return $this; 66 | } 67 | 68 | public function classes(array $names): self 69 | { 70 | $this->classes = array_merge($this->classes, $names); 71 | 72 | return $this; 73 | } 74 | 75 | public function functions(array $names): self 76 | { 77 | $this->functions = array_merge($this->functions, $names); 78 | 79 | return $this; 80 | } 81 | 82 | public function methods(array $names): self 83 | { 84 | $this->methods = array_merge($this->methods, $names); 85 | 86 | return $this; 87 | } 88 | 89 | public function static(array $names): self 90 | { 91 | $this->static = array_merge($this->static, $names); 92 | 93 | return $this; 94 | } 95 | 96 | public function variables(array $names): self 97 | { 98 | $this->variables = array_merge($this->variables, $names); 99 | 100 | return $this; 101 | } 102 | 103 | public function withoutSnippets(): self 104 | { 105 | $this->withSnippets = false; 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * @param File|VirtualFile|string $file 112 | * @return FileSearchResults 113 | */ 114 | public function search($file): FileSearchResults 115 | { 116 | $file = is_string($file) ? new File($file) : $file; 117 | 118 | $results = new FileSearchResults($file, $this->withSnippets); 119 | 120 | if (! $this->parseFile($file, $results)) { 121 | return $results; 122 | } 123 | 124 | $this->traverseNodes( 125 | $results, 126 | $this->findAllReferences($this->ast) 127 | ); 128 | 129 | return $results; 130 | } 131 | 132 | public function searchCode(string $code): FileSearchResults 133 | { 134 | return $this->search(new VirtualFile($code)); 135 | } 136 | 137 | /** 138 | * @param \Permafrost\PhpCodeSearch\Support\File|\Permafrost\CodeSnippets\File $file 139 | * @param FileSearchResults $results 140 | * @return bool 141 | */ 142 | protected function parseFile($file, FileSearchResults $results): bool 143 | { 144 | try { 145 | /** @var array|Stmt[] $ast */ 146 | $this->ast = $this->parser->parse($file->contents()); 147 | } catch (Error $error) { 148 | $results->addError(new SearchError($error, "Parse error: {$error->getMessage()}")); 149 | 150 | return false; 151 | } 152 | 153 | return true; 154 | } 155 | 156 | protected function findAllReferences(array $ast): array 157 | { 158 | $nodeMap = [ 159 | Node\Expr\Assign::class => $this->assignments, 160 | Node\Expr\FuncCall::class => $this->functions, 161 | Node\Expr\MethodCall::class => $this->methods, 162 | Node\Expr\New_::class => $this->classes, 163 | Node\Expr\StaticCall::class => $this->static, 164 | Node\Expr\StaticPropertyFetch::class => $this->static, 165 | Node\Expr\Variable::class => $this->variables, 166 | Node\Stmt\Class_::class => $this->classes, 167 | Node\Stmt\Function_::class => $this->functions, 168 | ]; 169 | 170 | $result = []; 171 | 172 | foreach ($nodeMap as $parserNodeClass => $names) { 173 | $result[] = $this->findReferences($ast, $parserNodeClass, $names); 174 | } 175 | 176 | return $this->sortNodesByLineNumber(...$result); 177 | } 178 | 179 | protected function findReferences(array $ast, string $class, array $names): array 180 | { 181 | $nodes = (new NodeFinder())->findInstanceOf($ast, $class); 182 | 183 | return collection($nodes)->filter(function (Node $node) use ($names) { 184 | $name = NameResolver::resolve($node) ?? false; 185 | 186 | return $name && Arr::matchesAny($name, $names, true); 187 | })->all(); 188 | } 189 | 190 | protected function traverseNodes(FileSearchResults $results, array $nodes): void 191 | { 192 | $traverser = new NodeTraverser(); 193 | 194 | $traverser->addVisitor(new AssignmentVisitor($results, $this->assignments)); 195 | $traverser->addVisitor(new ClassDefinitionVisitor($results, $this->classes)); 196 | $traverser->addVisitor(new FunctionCallVisitor($results, $this->functions)); 197 | $traverser->addVisitor(new FunctionDefinitionVisitor($results, $this->functions)); 198 | $traverser->addVisitor(new MethodCallVisitor($results, $this->methods)); 199 | $traverser->addVisitor(new NewClassVisitor($results, $this->classes)); 200 | $traverser->addVisitor(new StaticCallVisitor($results, $this->static)); 201 | $traverser->addVisitor(new StaticPropertyVisitor($results, $this->static)); 202 | $traverser->addVisitor(new VariableReferenceVisitor($results, $this->variables)); 203 | 204 | $traverser->traverse($nodes); 205 | } 206 | 207 | protected function sortNodesByLineNumber(...$items): array 208 | { 209 | $result = array_merge(...$items); 210 | 211 | usort($result, function ($aNode, $bNode) { 212 | if ($aNode->getAttribute('startLine') > $bNode->getAttribute('startLine')) { 213 | return 1; 214 | } 215 | 216 | if ($aNode->getAttribute('startLine') < $bNode->getAttribute('startLine')) { 217 | return -1; 218 | } 219 | 220 | return 0; 221 | }); 222 | 223 | return $result; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/Support/Arr.php: -------------------------------------------------------------------------------- 1 | map(function ($str) use ($values, $allowRegex) { 50 | if (is_array($str)) { 51 | return self::matchesAny($str, $values, $allowRegex); 52 | } 53 | 54 | return self::matches((string)$str, $values, $allowRegex); 55 | })->filter(function ($value) { 56 | return $value; 57 | })->count() > 0; 58 | } 59 | 60 | /** 61 | * Determine whether the given value is array accessible. 62 | * 63 | * @param mixed $value 64 | * @return bool 65 | */ 66 | public static function accessible($value): bool 67 | { 68 | return is_array($value) || $value instanceof \ArrayAccess; 69 | } 70 | 71 | /** 72 | * Collapse an array of arrays into a single array. 73 | * 74 | * @param iterable $array 75 | * @return array 76 | */ 77 | public static function collapse(iterable $array): array 78 | { 79 | $results = []; 80 | 81 | foreach ($array as $values) { 82 | if ($values instanceof Collection) { 83 | $values = $values->all(); 84 | } elseif (! is_array($values)) { 85 | continue; 86 | } 87 | 88 | $results[] = $values; 89 | } 90 | 91 | return array_merge([], ...$results); 92 | } 93 | 94 | /** 95 | * Determine if the given key exists in the provided array. 96 | * 97 | * @param \ArrayAccess|array $array 98 | * @param string|int $key 99 | * @return bool 100 | */ 101 | public static function exists($array, $key): bool 102 | { 103 | if ($array instanceof Collection) { 104 | return $array->has($key); 105 | } 106 | 107 | if ($array instanceof \ArrayAccess) { 108 | return $array->offsetExists($key); 109 | } 110 | 111 | return array_key_exists($key, $array); 112 | } 113 | 114 | /** 115 | * Return the first element in an array passing a given truth test. 116 | * 117 | * @param iterable $array 118 | * @param callable|null $callback 119 | * @param mixed $default 120 | * @return mixed 121 | */ 122 | public static function first($array, callable $callback = null, $default = null) 123 | { 124 | if (is_null($callback)) { 125 | if (empty($array)) { 126 | return val($default); 127 | } 128 | 129 | foreach ($array as $item) { 130 | return $item; 131 | } 132 | } 133 | 134 | foreach ($array as $key => $value) { 135 | if ($callback($value, $key)) { 136 | return $value; 137 | } 138 | } 139 | 140 | return val($default); 141 | } 142 | 143 | /** 144 | * Return the last element in an array passing a given truth test. 145 | * 146 | * @param array $array 147 | * @param callable|null $callback 148 | * @param mixed $default 149 | * @return mixed 150 | */ 151 | public static function last(array $array, callable $callback = null, $default = null) 152 | { 153 | if (is_null($callback)) { 154 | return empty($array) ? val($default) : end($array); 155 | } 156 | 157 | return static::first(array_reverse($array, true), $callback, $default); 158 | } 159 | 160 | /** 161 | * Pluck an array of values from an array. 162 | * 163 | * @param iterable $array 164 | * @param string|array|int|null $value 165 | * @param string|array|null $key 166 | * @return array 167 | */ 168 | public static function pluck($array, $value, $key = null): array 169 | { 170 | $results = []; 171 | 172 | [$value, $key] = static::explodePluckParameters($value, $key); 173 | 174 | foreach ($array as $item) { 175 | $itemValue = get_data($item, $value); 176 | 177 | // If the key is "null", we will just append the value to the array and keep 178 | // looping. Otherwise we will key the array using the value of the key we 179 | // received from the developer. Then we'll return the final array form. 180 | if (is_null($key)) { 181 | $results[] = $itemValue; 182 | } else { 183 | $itemKey = get_data($item, $key); 184 | 185 | if (is_object($itemKey) && method_exists($itemKey, '__toString')) { 186 | $itemKey = (string) $itemKey; 187 | } 188 | 189 | $results[$itemKey] = $itemValue; 190 | } 191 | } 192 | 193 | return $results; 194 | } 195 | 196 | /** 197 | * Explode the "value" and "key" arguments passed to "pluck". 198 | * 199 | * @param string|array $value 200 | * @param string|array|null $key 201 | * @return array 202 | */ 203 | protected static function explodePluckParameters($value, $key): array 204 | { 205 | $value = is_string($value) ? explode('.', $value) : $value; 206 | 207 | $key = is_null($key) || is_array($key) ? $key : explode('.', $key); 208 | 209 | return [$value, $key]; 210 | } 211 | 212 | public static function where(array $array, callable $callback): array 213 | { 214 | return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); 215 | } 216 | 217 | protected static function isRegexPattern(string $str): bool 218 | { 219 | return strpos($str, '/') === 0 220 | && substr($str, -1) === '/' 221 | && strlen($str) > 1; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Support/Collections/Arrayable.php: -------------------------------------------------------------------------------- 1 | items = $this->getArrayableItems($items); 17 | } 18 | 19 | public function all(): array 20 | { 21 | return $this->items; 22 | } 23 | 24 | public function count(): int 25 | { 26 | return count($this->items); 27 | } 28 | 29 | public function each(callable $callback): self 30 | { 31 | foreach ($this as $key => $item) { 32 | if ($callback($item, $key) === false) { 33 | break; 34 | } 35 | } 36 | 37 | return $this; 38 | } 39 | 40 | public function filter(callable $callback = null): self 41 | { 42 | if ($callback) { 43 | return new static(Arr::where($this->items, $callback)); 44 | } 45 | 46 | return new static(array_filter($this->items)); 47 | } 48 | 49 | /** 50 | * Get the first item from the collection passing the given truth test. 51 | * 52 | * @param callable|null $callback 53 | * @param mixed $default 54 | * @return mixed 55 | */ 56 | public function first(callable $callback = null, $default = null) 57 | { 58 | return Arr::first($this->items, $callback, $default); 59 | } 60 | 61 | public function get($key, $default = null) 62 | { 63 | if (array_key_exists($key, $this->items)) { 64 | return $this->items[$key]; 65 | } 66 | 67 | return $default; 68 | } 69 | 70 | public function has($key): bool 71 | { 72 | $keys = is_array($key) ? $key : func_get_args(); 73 | 74 | foreach ($keys as $value) { 75 | if (! array_key_exists($value, $this->items)) { 76 | return false; 77 | } 78 | } 79 | 80 | return true; 81 | } 82 | 83 | public function keys(): self 84 | { 85 | return new static(array_keys($this->items)); 86 | } 87 | 88 | public function map(callable $callback): self 89 | { 90 | $keys = array_keys($this->items); 91 | 92 | $items = array_map($callback, $this->items, $keys); 93 | 94 | return new static(array_combine($keys, $items)); 95 | } 96 | 97 | public function mapWithKeys(callable $callback): self 98 | { 99 | $result = []; 100 | 101 | foreach ($this->items as $key => $value) { 102 | $assoc = $callback($value, $key); 103 | 104 | foreach ($assoc as $mapKey => $mapValue) { 105 | $result[$mapKey] = $mapValue; 106 | } 107 | } 108 | 109 | return new static($result); 110 | } 111 | 112 | /** 113 | * Get the values of a given key. 114 | * 115 | * @param string|array|int|null $value 116 | * @param string|null $key 117 | * @return static 118 | */ 119 | public function pluck($value, $key = null): self 120 | { 121 | return new static(Arr::pluck($this->items, $value, $key)); 122 | } 123 | 124 | public function push(...$values): self 125 | { 126 | foreach ($values as $value) { 127 | $this->items[] = $value; 128 | } 129 | 130 | return $this; 131 | } 132 | 133 | public function put($key, $value): self 134 | { 135 | $this->offsetSet($key, $value); 136 | 137 | return $this; 138 | } 139 | 140 | public function sort($callback = null): self 141 | { 142 | $items = $this->items; 143 | 144 | $callback && is_callable($callback) 145 | ? uasort($items, $callback) 146 | : asort($items, $callback ?? SORT_REGULAR); 147 | 148 | return new static($items); 149 | } 150 | 151 | public function toArray(): array 152 | { 153 | return $this->map(function ($value) { 154 | return $value instanceof Arrayable ? $value->toArray() : $value; 155 | })->all(); 156 | } 157 | 158 | public function values(): self 159 | { 160 | return new static(array_values($this->items)); 161 | } 162 | 163 | public function where($key, $operator = null, $value = null): self 164 | { 165 | return $this->filter($this->operatorForWhere(...func_get_args())); 166 | } 167 | 168 | /** 169 | * Filter items by the given key value pair. 170 | * 171 | * @param string $key 172 | * @param mixed $values 173 | * @param bool $strict 174 | * @return static 175 | */ 176 | public function whereIn(string $key, $values, bool $strict = false): self 177 | { 178 | $values = $this->getArrayableItems($values); 179 | 180 | return $this->filter(function ($item) use ($key, $values, $strict) { 181 | return in_array(get_data($item, $key), $values, $strict); 182 | }); 183 | } 184 | 185 | protected function getArrayableItems($items): array 186 | { 187 | if (is_array($items)) { 188 | return $items; 189 | } 190 | 191 | if ($items instanceof Arrayable) { 192 | return $items->toArray(); 193 | } 194 | 195 | return (array)$items; 196 | } 197 | 198 | protected function operatorForWhere(string $key, ?string $operator = null, $value = null) 199 | { 200 | if (func_num_args() === 1) { 201 | $value = true; 202 | 203 | $operator = '='; 204 | } 205 | 206 | if (func_num_args() === 2) { 207 | $value = $operator; 208 | 209 | $operator = '='; 210 | } 211 | 212 | return function ($item) use ($key, $operator, $value) { 213 | $retrieved = get_data($item, $key); 214 | 215 | $strings = array_filter([$retrieved, $value], function ($value) { 216 | return is_string($value) || (is_object($value) && method_exists($value, '__toString')); 217 | }); 218 | 219 | if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { 220 | return in_array($operator, ['!=', '<>', '!==']); 221 | } 222 | 223 | switch ($operator) { 224 | default: 225 | case '=': 226 | case '==': return $retrieved == $value; 227 | case '!=': 228 | case '<>': return $retrieved != $value; 229 | case '<': return $retrieved < $value; 230 | case '>': return $retrieved > $value; 231 | case '<=': return $retrieved <= $value; 232 | case '>=': return $retrieved >= $value; 233 | case '===': return $retrieved === $value; 234 | case '!==': return $retrieved !== $value; 235 | } 236 | }; 237 | } 238 | 239 | public function getIterator(): \ArrayIterator 240 | { 241 | return new \ArrayIterator($this->items); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/Support/Collections/Enumerable.php: -------------------------------------------------------------------------------- 1 | items[$offset]); 13 | } 14 | 15 | #[\ReturnTypeWillChange] 16 | public function offsetGet($offset) 17 | { 18 | return $this->items[$offset]; 19 | } 20 | 21 | #[\ReturnTypeWillChange] 22 | public function offsetSet($key, $value) 23 | { 24 | if (is_null($key)) { 25 | $this->items[] = $value; 26 | } else { 27 | $this->items[$key] = $value; 28 | } 29 | } 30 | 31 | #[\ReturnTypeWillChange] 32 | public function offsetUnset($offset) 33 | { 34 | unset($this->items[$offset]); 35 | } 36 | 37 | #[\ReturnTypeWillChange] 38 | public function rewind() 39 | { 40 | $this->position = 0; 41 | } 42 | 43 | #[\ReturnTypeWillChange] 44 | public function current() 45 | { 46 | return $this->items[$this->position]; 47 | } 48 | 49 | #[\ReturnTypeWillChange] 50 | public function key() 51 | { 52 | return $this->position; 53 | } 54 | 55 | #[\ReturnTypeWillChange] 56 | public function next() 57 | { 58 | ++$this->position; 59 | } 60 | 61 | #[\ReturnTypeWillChange] 62 | public function valid() 63 | { 64 | return isset($this->items[$this->position]); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Support/ExpressionTransformer.php: -------------------------------------------------------------------------------- 1 | map(function ($node) { 39 | return static::parserNodeToResultNode($node); 40 | })->all(); 41 | } 42 | 43 | public static function parserNodeToResultNode($node) 44 | { 45 | $value = $node; 46 | 47 | $nodeMap = [ 48 | Array_::class => ArrayNode::class, 49 | //ArrayItem::class => ArrayItemNode::class, 50 | Assign::class => AssignmentNode::class, 51 | AssignOp::class => AssignmentOperationNode::class, 52 | BinaryOp::class => BinaryOperationNode::class, 53 | DNumber::class => NumberNode::class, 54 | FuncCall::class => FunctionCallNode::class, 55 | LNumber::class => NumberNode::class, 56 | MethodCall::class => MethodCallNode::class, 57 | PropertyFetch::class => PropertyAccessNode::class, 58 | StaticCall::class => StaticMethodCallNode::class, 59 | StaticPropertyFetch::class => StaticPropertyAccessNode::class, 60 | String_::class => StringNode::class, 61 | Variable::class => VariableNode::class, 62 | ]; 63 | 64 | if ($node instanceof Array_) { 65 | return static::parserNodesToResultNodes($value->items); 66 | } 67 | 68 | if ($node instanceof ArrayItem) { 69 | return new ArrayItemNode($node); 70 | } 71 | 72 | if ($node instanceof Arg) { 73 | $value = $node->value; 74 | } 75 | 76 | foreach ($nodeMap as $parserNodeClass => $resultNodeClass) { 77 | if ($value instanceof $parserNodeClass) { 78 | return new $resultNodeClass($value); 79 | } 80 | } 81 | 82 | return $node; 83 | } 84 | 85 | public static function binaryOperationNodeToValue(BinaryOperationNode $node) 86 | { 87 | $nodeMap = [ 88 | 'left' => '', 89 | 'right' => '', 90 | ]; 91 | 92 | foreach ($nodeMap as $name => &$value) { 93 | $sideNode = $node->$name; 94 | 95 | if ($sideNode instanceof BinaryOperationNode) { 96 | $value = static::binaryOperationNodeToValue($sideNode); 97 | } 98 | 99 | if ($sideNode instanceof NumberNode) { 100 | $value = $sideNode->value; 101 | } 102 | 103 | if ($sideNode instanceof StringNode) { 104 | $value = str_replace("'", "\\'", $sideNode->value); 105 | $value = "'{$value}'"; 106 | } 107 | 108 | if ($sideNode instanceof VariableNode) { 109 | $value = '$' . $sideNode->name; 110 | } 111 | } 112 | 113 | return $nodeMap['left'] . ' ' . $node->symbol() . ' ' . $nodeMap['right']; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Support/File.php: -------------------------------------------------------------------------------- 1 | path = $path; 26 | $this->filename = $this->getRealPath(); 27 | } 28 | 29 | public function file(): SplFileObject 30 | { 31 | if (! $this->file) { 32 | $this->file = new SplFileObject($this->path); 33 | } 34 | 35 | return $this->file; 36 | } 37 | 38 | public function exists(): bool 39 | { 40 | return file_exists($this->getRealPath()); 41 | } 42 | 43 | public function contents(): string 44 | { 45 | return file_get_contents($this->path); 46 | } 47 | 48 | public function getRealPath(): string 49 | { 50 | return realpath($this->path); 51 | } 52 | 53 | public function numberOfLines(): int 54 | { 55 | $this->file()->seek(PHP_INT_MAX); 56 | 57 | return $this->file()->key(); 58 | } 59 | 60 | public function getLine(?int $lineNumber = null): string 61 | { 62 | if ($lineNumber === null) { 63 | return $this->getNextLine(); 64 | } 65 | 66 | $this->file()->seek($lineNumber - 1); 67 | 68 | return $this->file()->current(); 69 | } 70 | 71 | public function getNextLine(): string 72 | { 73 | $this->file()->next(); 74 | 75 | return $this->file()->current(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Support/NameResolver.php: -------------------------------------------------------------------------------- 1 | toString(); 21 | } 22 | 23 | if ($node instanceof Node\Stmt\Property) { 24 | return $node->props[0]->name; 25 | } 26 | 27 | if ($node instanceof Node\Stmt\ClassConst) { 28 | return $node->consts[0]->name; 29 | } 30 | 31 | if (self::propertiesExist($node, ['class', 'name'])) { 32 | $class = static::resolve($node->class); 33 | $name = static::resolve($node->name); 34 | 35 | if ($node instanceof Node\Expr\MethodCall) { 36 | return $name; 37 | } 38 | 39 | if ($node instanceof Node\Expr\StaticCall) { 40 | if (is_array($class)) { 41 | $class = $name; 42 | } 43 | 44 | return [$class, "{$class}::{$name}"]; 45 | } 46 | 47 | if ($node instanceof Node\Expr\StaticPropertyFetch) { 48 | return [$name, "{$class}::\${$name}"]; 49 | } 50 | 51 | return [$class, $name]; 52 | } 53 | 54 | if (property_exists($node, 'name')) { 55 | return static::resolve($node->name); 56 | } 57 | 58 | if (property_exists($node, 'var')) { 59 | return static::resolve($node->var); 60 | } 61 | 62 | if (property_exists($node, 'class')) { 63 | return static::resolve($node->class); 64 | } 65 | 66 | return null; 67 | } 68 | 69 | public static function resolveAll(array $nodes): array 70 | { 71 | return collection($nodes)->each(function ($node) { 72 | return self::resolve($node); 73 | })->filter()->all(); 74 | } 75 | 76 | protected static function propertiesExist($object, array $propertyNames): bool 77 | { 78 | foreach ($propertyNames as $propertyName) { 79 | if (! property_exists($object, $propertyName)) { 80 | return false; 81 | } 82 | } 83 | 84 | return true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Support/StatementTransformer.php: -------------------------------------------------------------------------------- 1 | ParameterNode::class, 17 | Node\Stmt\Property::class => ClassPropertyNode::class, 18 | Node\Stmt\ClassMethod::class => ClassMethodNode::class, 19 | Node\Stmt\ClassConst::class => ClassConstantNode::class, 20 | ]; 21 | 22 | foreach ($map as $parserNodeClass => $resultNodeClass) { 23 | if ($node instanceof $parserNodeClass) { 24 | return new $resultNodeClass($node); 25 | } 26 | } 27 | 28 | return $node; 29 | } 30 | 31 | public static function parserNodesToResultNode(array $nodes): array 32 | { 33 | return collection($nodes)->map(function ($node) { 34 | return self::parserNodeToResultNode($node); 35 | })->toArray(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Support/VirtualFile.php: -------------------------------------------------------------------------------- 1 | createTempFile($code); 10 | 11 | $this->filename = $this->getRealPath(); 12 | } 13 | 14 | public function __destruct() 15 | { 16 | $this->unlink(); 17 | } 18 | 19 | public function unlink() 20 | { 21 | if (! strpos(dirname($this->path), sys_get_temp_dir())) { 22 | return; 23 | } 24 | 25 | if (is_file($this->path)) { 26 | unlink($this->path); 27 | } 28 | } 29 | 30 | protected function createTempFile(string $contents): bool 31 | { 32 | $this->path = tempnam(sys_get_temp_dir(), 'pcs'); 33 | 34 | return file_put_contents($this->path, $contents) !== false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Support/helpers.php: -------------------------------------------------------------------------------- 1 | $segment) { 51 | unset($key[$i]); 52 | 53 | if (is_null($segment)) { 54 | return $target; 55 | } 56 | 57 | if ($segment === '*') { 58 | if ($target instanceof Collection) { 59 | $target = $target->all(); 60 | } elseif (! is_iterable($target)) { 61 | return val($default); 62 | } 63 | 64 | $result = []; 65 | 66 | foreach ($target as $item) { 67 | $result[] = get_data($item, $key); 68 | } 69 | 70 | return in_array('*', $key) ? Arr::collapse($result) : $result; 71 | } 72 | 73 | if (Arr::accessible($target) && Arr::exists($target, $segment)) { 74 | $target = $target[$segment]; 75 | } elseif (is_object($target) && isset($target->{$segment})) { 76 | $target = $target->{$segment}; 77 | } else { 78 | return val($default); 79 | } 80 | } 81 | 82 | return $target; 83 | } 84 | } 85 | 86 | /** @codeCoverageIgnore */ 87 | if (! function_exists('val')) { 88 | /** 89 | * Return the default value of the given value. 90 | * 91 | * @param mixed $value 92 | * @return mixed 93 | */ 94 | function val($value, ...$args) 95 | { 96 | return $value instanceof Closure ? $value(...$args) : $value; 97 | } 98 | } 99 | 100 | if (! function_exists('collection')) { 101 | function collection($items) 102 | { 103 | return new Collection($items); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Visitors/AssignmentVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 23 | $this->names = $names; 24 | } 25 | 26 | public function enterNode(Node $node) 27 | { 28 | if ($node instanceof Node\Expr\Assign) { 29 | if (! $node->var instanceof Node\Expr\ArrayDimFetch) { 30 | $name = NameResolver::resolve($node->var); 31 | 32 | if (is_array($name)) { 33 | $name = array_pop($name); 34 | } 35 | 36 | if (Arr::matches($name ?? '', $this->names, true)) { 37 | $resultNode = AssignmentNode::create($node); 38 | 39 | $this->results->add($resultNode, $resultNode->location()); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Visitors/ClassDefinitionVisitor.php: -------------------------------------------------------------------------------- 1 | name->toString(), $this->names, true)) { 15 | $resultNode = new ClassDefinitionNode($node); 16 | 17 | $this->results->add($resultNode, $resultNode->location()); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Visitors/CommentVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 22 | $this->patterns = $patterns; 23 | } 24 | 25 | public function enterNode(Node $node) 26 | { 27 | if ($node instanceof Comment) { 28 | if (Arr::matches($node->getText(), $this->patterns)) { 29 | $resultNode = CommentNode::create($node); 30 | 31 | $this->results->add($resultNode, $resultNode->location()); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Visitors/FunctionCallVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 23 | $this->names = $names; 24 | } 25 | 26 | public function enterNode(Node $node) 27 | { 28 | if ($node instanceof FuncCall) { 29 | if (Arr::matches(NameResolver::resolve($node), $this->names, true)) { 30 | $resultNode = FunctionCallNode::create($node); 31 | 32 | $this->results->add($resultNode, $resultNode->location()); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Visitors/FunctionDefinitionVisitor.php: -------------------------------------------------------------------------------- 1 | name->toString(), $this->names, true)) { 16 | $resultNode = FunctionDefinitionNode::create($node); 17 | 18 | $this->results->add($resultNode, $resultNode->location()); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Visitors/MethodCallVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 21 | $this->names = $names; 22 | } 23 | 24 | public function enterNode(Node $node) 25 | { 26 | if ($node instanceof Node\Expr\MethodCall) { 27 | if (Arr::matches($node->name->toString(), $this->names, true)) { 28 | $resultNode = MethodCallNode::create($node); 29 | 30 | $this->results->add($resultNode, $resultNode->location()); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Visitors/NewClassVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 22 | $this->names = $names; 23 | } 24 | 25 | public function enterNode(Node $node) 26 | { 27 | if ($node instanceof Node\Expr\New_) { 28 | if (Arr::matches(NameResolver::resolve($node->class), $this->names, true)) { 29 | $resultNode = VariableNode::create($node); 30 | 31 | $this->results->add($resultNode, $resultNode->location()); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Visitors/NodeVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 18 | $this->names = $names; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Visitors/StaticCallVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 22 | $this->names = $names; 23 | } 24 | 25 | public function enterNode(Node $node) 26 | { 27 | if ($node instanceof Node\Expr\StaticCall) { 28 | $name = NameResolver::resolve($node)[0] ?? $node->class->toString(); 29 | $methodName = $node->name->toString(); 30 | 31 | if (Arr::matches($name, $this->names, true) || Arr::matches("{$name}::{$methodName}", $this->names, true)) { 32 | $resultNode = StaticMethodCallNode::create($node); 33 | 34 | $this->results->add($resultNode, $resultNode->location()); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Visitors/StaticPropertyVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 21 | $this->names = $names; 22 | } 23 | 24 | public function enterNode(Node $node) 25 | { 26 | if ($node instanceof Node\Expr\StaticPropertyFetch) { 27 | $name = $node->class->toString(); 28 | $methodName = $node->name->toString(); 29 | 30 | if (Arr::matches($methodName, $this->names, true) || Arr::matches("{$name}::\${$methodName}", $this->names, true)) { 31 | $resultNode = new StaticPropertyAccessNode($node); 32 | 33 | $this->results->add($resultNode, $resultNode->location()); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Visitors/VariableReferenceVisitor.php: -------------------------------------------------------------------------------- 1 | results = $results; 22 | $this->names = $names; 23 | } 24 | 25 | public function enterNode(Node $node) 26 | { 27 | if ($node instanceof Node\Expr\Variable) { 28 | if (Arr::matches($node->name, $this->names, true)) { 29 | $resultNode = VariableNode::create($node); 30 | 31 | $this->results->add($resultNode, $resultNode->location()); 32 | } 33 | } 34 | } 35 | } 36 | --------------------------------------------------------------------------------