├── .github └── workflows │ ├── code_coverage.yml │ ├── imports.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Filters.php ├── Filters │ ├── InArray.php │ └── SameName.php ├── Finder.php ├── Keywords │ ├── Any.php │ ├── BlackSpace.php │ ├── Boolean.php │ ├── Cast.php │ ├── ClassRef.php │ ├── Comment.php │ ├── Comparison.php │ ├── DocBlock.php │ ├── FloatNum.php │ ├── FullClassRef.php │ ├── GlobalFunctionCall.php │ ├── InBetween.php │ ├── Integer.php │ ├── MethodVisibility.php │ ├── Name.php │ ├── Number.php │ ├── RepeatingPattern.php │ ├── Statement.php │ ├── Str.php │ ├── Until.php │ ├── Variable.php │ └── WhiteSpace.php ├── PatternParser.php ├── PatternParser │ ├── Filters.php │ └── Normalizer.php ├── PhpSyntax.php ├── Placeholders.php ├── PostReplace.php ├── Replacer.php ├── Searcher.php ├── Str.php ├── Stringify.php ├── Tokenizer.php └── Tokens.php └── tests ├── BaseTestClass.php ├── BeginWithOptionalPlaceHolderTest.php ├── BooleanTest.php ├── CallsPrivateMethods.php ├── CastTest.php ├── ClassReferenceTest.php ├── CommentPlaceholderTest.php ├── CommentingPatternsTest.php ├── CompareTest.php ├── FiltersPredicateTest.php ├── FullClassReferenceTest.php ├── GlobalFunctionTest.php ├── NotWhitespaceTest.php ├── NumberPlaceHolderTest.php ├── PostReplaceTest.php ├── RefactorPatternParsingTest.php ├── RepeatingTest.php ├── StatementTest.php ├── UntilTest.php ├── VisibilityTest.php ├── WhitespaceTest.php └── stubs ├── EolSimplePostControllerResult.stub ├── NoWhiteSpaceSimplePostController.stub ├── OptionalWhiteSpaceSimplePostController.stub ├── ResultSimplePostController.stub ├── SimplePostController.stub ├── SimplePostController2.stub └── refactor_patterns.php /.github/workflows/code_coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage 2 | 3 | # Controls when the workflow will run 4 | on: 5 | push: 6 | branches: [ "main" ] 7 | pull_request: 8 | branches: [ "main" ] 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | test: 16 | # The type of runner that the job will run on 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: [ubuntu-latest] 22 | php: [8.3] 23 | name: PHP ${{ matrix.php }} - OS ${{ matrix.os }} 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 3 30 | - name: Setup PHP v${{ matrix.php }} 31 | uses: shivammathur/setup-php@v2 32 | with: 33 | php-version: ${{ matrix.php }} 34 | extensions: mbstring, exif, iconv, tokenizer, fileinfo, pcov 35 | ini-values: pcov.directory=. 36 | coverage: PCOV 37 | 38 | - name: Install dependencies 39 | run: | 40 | composer update --prefer-dist --no-interaction --no-progress 41 | 42 | - name: PHPUnit 43 | run: | 44 | ./vendor/bin/phpunit --stop-on-failure --coverage-clover ./clover.xml 45 | 46 | #- name: Make code coverage badge 47 | # uses: timkrase/phpunit-coverage-badge@v1.2.1 48 | # with: 49 | # coverage_badge_path: output/coverage.svg 50 | # repo_token: ${{ secrets.GITHUB_TOKEN }} 51 | # push_badge: true 52 | - name: Upload coverage results to Coveralls 53 | env: 54 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | run: | 56 | composer global require php-coveralls/php-coveralls 57 | php-coveralls --coverage_clover=./clover.xml --json_path=./coverall.xml -v 58 | -------------------------------------------------------------------------------- /.github/workflows/imports.yml: -------------------------------------------------------------------------------- 1 | name: Check Imports 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Validate composer.json and composer.lock 21 | run: composer validate --strict 22 | 23 | - name: Cache Composer packages 24 | id: composer-cache 25 | uses: actions/cache@v3 26 | with: 27 | path: vendor 28 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-php- 31 | 32 | - name: Install dependencies 33 | run: composer install --prefer-dist --no-progress && composer require imanghafoori/php-imports-analyzer 34 | 35 | - name: Check Imports 36 | run: ./vendor/bin/check_imports 37 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | # Controls when the workflow will run 4 | on: 5 | pull_request: 6 | branches: [ "main" ] 7 | 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | test: 14 | # The type of runner that the job will run on 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: true 18 | matrix: 19 | php: ["8.4", "8.3", "8.2", "8.1", "8.0", "7.4", "7.3", "7.2"] 20 | dependency-version: [prefer-stable] 21 | 22 | name: P${{ matrix.php }} - ${{ matrix.dependency-version }} 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v2 27 | 28 | - name: Cache dependencies 29 | uses: actions/cache@v2 30 | with: 31 | path: ~/.composer/cache/files 32 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 33 | 34 | - name: Setup PHP 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php }} 38 | extensions: mbstring, exif, iconv, tokenizer 39 | coverage: none 40 | 41 | - name: Install dependencies 42 | run: | 43 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction 44 | - name: Execute tests 45 | run: vendor/bin/phpunit 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .phpunit.result.cache 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Iman 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Php Smart Search/replace Functionality 2 | [![tests](https://github.com/imanghafoori1/php-smart-search-replace/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/imanghafoori1/php-smart-search-replace/actions/workflows/tests.yml) 3 | [![Coverage Status](https://coveralls.io/repos/github/imanghafoori1/php-smart-search-replace/badge.svg?branch=main)](https://coveralls.io/github/imanghafoori1/php-smart-search-replace?branch=main) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/imanghafoori/php-search-replace.svg?style=flat-square)](https://packagist.org/packages/imanghafoori/php-search-replace) 5 | [![Latest Stable Version](https://poser.pugx.org/imanghafoori/php-search-replace/v/stable?format=flat-square)](https://packagist.org/packages/imanghafoori/php-search-replace) 6 | [![MIT Licensed](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 7 | [![Check Imports](https://github.com/imanghafoori1/php-smart-search-replace/actions/workflows/imports.yml/badge.svg?branch=main)](https://github.com/imanghafoori1/php-smart-search-replace/actions/workflows/imports.yml) 8 | 9 | 10 | ## It is much easier than using regex. 11 | 12 | ### Installation: 13 | 14 | ``` 15 | composer require imanghafoori/php-search-replace 16 | ``` 17 | 18 | ### Usage: 19 | 20 | 21 | 22 | 1- Lets say you want to remove double semi-colon occurances like these: 23 | ```php 24 | $user = 1;; 25 | $user = 2; ; 26 | $user = 3; 27 | ; 28 | 29 | ``` 30 | Then you can define a pattern like this: 31 | ```php 32 | $pattern = [';;' => ['replace' => ';']]; 33 | ``` 34 | This will catch all the 3 cases above since the neutral php whitespaces are ignored while searching. 35 | 36 | ------------------- 37 | 38 | #### Placeholders: 39 | 40 | Here is a copmerehensive list of placeholders you can use: 41 | 42 | - `` or ``: for variables like: `$user` 43 | - `` or ``: for hard coded strings: `'hello'` or "hello" 44 | - ``: for class references: `\App\User::where(...` , `User::where` 45 | - ``: only for full references: `\App\User::` 46 | - ``: to capture all the code until you reach a certain character. 47 | - ``: for commands (does not capture doc-blocks) 48 | - ``: for doc-blocks 49 | - ``: to capture a whole php statement. 50 | - `""` or ``: for method or function names. `->where` or `::where` 51 | - ``: for whitespace blocks 52 | - ``: for non-whitespace 53 | - `` or `''`: for true or false (acts case-insensetive) 54 | - ``: for numeric values 55 | - ``: for type-casts like: `(array) $a;` 56 | - `` or ``: for integer values 57 | - ``: for public, protected, private 58 | - ``: for floating point number 59 | - `""`: to detect global function calls. 60 | - ``: to capture code within a pair of `{...}` or `(...)` or `[...]` 61 | - ``: captures any token. 62 | - **You can also define your own keywords if needed!** 63 | 64 | You just define a class for your new keyword and append the class path to the end of `Finder::$keywords[] = MyKeyword::class` property. 65 | Just like the default keywords. 66 | 67 | 68 | ### Example: 69 | lets say you want to remove the optional comma from arrays: 70 | ```php 71 | $a = [ 72 | '1', 73 | '2', 74 | ]; 75 | $b = ['1','2',]; 76 | ``` 77 | Then you can define a pattern like this: 78 | ```php 79 | $pattern = [',?]' => ['replace' => '"<1>"]']]; 80 | ``` 81 | Here the `?` mean an optional whitespace may reside there, and the `"<1>"` means the value that matches the first placeholder should be put there. 82 | 83 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imanghafoori/php-search-replace", 3 | "description": "Smart search replace functionality for php source code", 4 | "keywords": [ 5 | "php-search-replace" 6 | ], 7 | "homepage": "https://github.com/imanghafoori1/php-smart-search-replace", 8 | "license": "MIT", 9 | "type": "library", 10 | "authors": [ 11 | { 12 | "name": "Iman", 13 | "email": "imanghafoori1@gmail.com", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^7.1.3|8.0.*|8.1.*|8.2.*|8.3.*|8.4.*" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "^8.0|^9.0|^10.0|^11.0", 22 | "symfony/var-dumper": "^5.3" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Imanghafoori\\SearchReplace\\": "src" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Imanghafoori\\SearchReplace\\Tests\\": "tests" 32 | } 33 | }, 34 | "scripts": { 35 | "test": "vendor/bin/phpunit", 36 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 37 | 38 | }, 39 | "config": { 40 | "sort-packages": true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | ./src 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Filters.php: -------------------------------------------------------------------------------- 1 | Filters\InArray::class, 9 | 'same_name' => Filters\SameName::class, 10 | ]; 11 | 12 | public static function apply($filterings, $data, $tokens) 13 | { 14 | $placeholderVals = $data['values']; 15 | foreach ($filterings as $i => $filters) { 16 | foreach ($filters as $filterName => $values) { 17 | if (is_int($filterName) && is_array($values)) { 18 | if (count($values) === 2 && is_callable($values[0])) { 19 | if (! call_user_func_array($values[0], [$placeholderVals[$i - 1], $values[1], $tokens, $placeholderVals, $i - 1])) { 20 | return false; 21 | } 22 | } 23 | } elseif (isset(self::$filters[$filterName])) { 24 | $filterClass = self::$filters[$filterName]; 25 | if (! $filterClass::check($placeholderVals[$i - 1], $values, $tokens, $placeholderVals, $i - 1)) { 26 | return false; 27 | } 28 | } 29 | } 30 | } 31 | 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Filters/InArray.php: -------------------------------------------------------------------------------- 1 | $i - $pIndex, 'end' => $end, 'values' => $matchedValues, 'repeatings' => $repeatings]; 123 | if (Filters::apply($filters, $data, $tokens)) { 124 | if (! $predicate || call_user_func($predicate, $data, $tokens)) { 125 | $mutator && $matchedValues = call_user_func($mutator, $matchedValues); 126 | $matchesCount++; 127 | $matches[] = ['start' => $i - $optionalPatternMatchCount, 'end' => $end, 'values' => $matchedValues, 'repeatings' => $repeatings]; 128 | } 129 | } 130 | 131 | $end > $i && $i = $end - 1; // fast-forward 132 | $i++; 133 | if ($maxMatch && $matchesCount === $maxMatch) { 134 | return $matches; 135 | } 136 | } 137 | 138 | return $matches; 139 | } 140 | 141 | public static function compareIt($tToken, int $type, $token, &$i) 142 | { 143 | if ($tToken[0] === $type) { 144 | return $tToken; 145 | } 146 | 147 | if (self::isOptional($token)) { 148 | $i--; 149 | 150 | return [T_WHITESPACE, '']; 151 | } 152 | } 153 | 154 | public static function matchesAny($avoidResultIn, $newTokens) 155 | { 156 | foreach ($avoidResultIn as $pattern) { 157 | $_matchedValues = Finder::getMatches(Tokenizer::tokenize($pattern), $newTokens); 158 | if ($_matchedValues) { 159 | return true; 160 | } 161 | } 162 | 163 | return false; 164 | } 165 | 166 | public static function isRepeatingPattern($pToken) 167 | { 168 | if ($pToken[0] === T_CONSTANT_ENCAPSED_STRING && self::startsWith($pName = trim($pToken[1], '\'\"'), ''); 170 | } 171 | } 172 | 173 | public static function getPortion($start, $end, $tokens) 174 | { 175 | $output = ''; 176 | for ($i = $start - 1; $i < $end; $i++) { 177 | $output .= $tokens[$i][1] ?? $tokens[$i][0]; 178 | } 179 | 180 | return $output; 181 | } 182 | 183 | private static function optionalStartingTokens($optionalStartingTokens, $tokens, $i) 184 | { 185 | if (! $optionalStartingTokens) { 186 | return [0, []]; 187 | } 188 | 189 | [$matchedValues, $optionalMatchCount] = self::compareOptionalTokens($optionalStartingTokens, $tokens, $i - 1); 190 | 191 | return [$optionalMatchCount, $matchedValues]; 192 | } 193 | 194 | public static function extractValue($matches, $first = '') 195 | { 196 | $segments = [$first]; 197 | 198 | foreach ($matches as $match) { 199 | $segments[] = $match[0][1]; 200 | } 201 | 202 | return [T_STRING, implode('\\', $segments), $match[0][2]]; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Keywords/Any.php: -------------------------------------------------------------------------------- 1 | '; 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $placeholderValues[] = $tokens[$startFrom]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Keywords/BlackSpace.php: -------------------------------------------------------------------------------- 1 | '; 10 | } 11 | 12 | public static function getValue($tokens, &$startFrom, &$placeholderValues, $pToken) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if ($t[0] === T_WHITESPACE) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Keywords/Boolean.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 12 | } 13 | 14 | public static function getValue($tokens, &$startFrom, &$placeholderValues) 15 | { 16 | if (self::isBoolean($tokens, $startFrom)) { 17 | $placeholderValues[] = $tokens[$startFrom]; 18 | } else { 19 | return false; 20 | } 21 | } 22 | 23 | public static function isBoolean($tokens, $startFrom) 24 | { 25 | $t = $tokens[$startFrom]; 26 | [$next] = Tokens::getNextToken($tokens, $startFrom); 27 | 28 | return $next !== '(' && $t[0] === T_STRING && in_array(strtolower($t[1]), ['true', 'false'], true); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Keywords/Cast.php: -------------------------------------------------------------------------------- 1 | '; 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if (! in_array($t[0], [ 17 | T_STRING_CAST, 18 | T_OBJECT_CAST, 19 | T_DOUBLE_CAST, 20 | T_BOOL_CAST, 21 | T_ARRAY_CAST, 22 | T_INT_CAST, 23 | ])) { 24 | return false; 25 | } 26 | 27 | $placeholderValues[] = $t; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Keywords/ClassRef.php: -------------------------------------------------------------------------------- 1 | '; 15 | } 16 | 17 | public static function getValue($tokens, &$startFrom, &$placeholderValues) 18 | { 19 | $tToken = $tokens[$startFrom] ?? '_'; 20 | $classRef = ['classRef' => '\\""']; 21 | 22 | if ($tToken[0] === T_NS_SEPARATOR) { 23 | $repeatingClassRef = PatternParser::tokenize('""'); 24 | $matches = Tokens::compareTokens($repeatingClassRef, $tokens, $startFrom, $classRef); 25 | 26 | if (! $matches) { 27 | return false; 28 | } 29 | $startFrom = $matches[0]; 30 | $placeholderValues[] = Finder::extractValue($matches[2][0]); 31 | } elseif ($tToken[0] === T_STRING) { 32 | $repeatingPattern = Tokenizer::tokenize('""""'); 33 | $matches = Tokens::compareTokens($repeatingPattern, $tokens, $startFrom, $classRef); 34 | if (! $matches) { 35 | $placeholderValues[] = $tToken; 36 | } else { 37 | $startFrom = $matches[0]; 38 | $placeholderValues[] = Finder::extractValue($matches[2][0], $matches[1][0][1]); 39 | } 40 | } elseif (defined('T_NAME_QUALIFIED') && ($tToken[0] === T_NAME_QUALIFIED || $tToken[0] === T_NAME_FULLY_QUALIFIED)) { 41 | $placeholderValues[] = [T_STRING, $tToken[1], $tToken[2]]; 42 | } else { 43 | return false; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Keywords/Comment.php: -------------------------------------------------------------------------------- 1 | '; 12 | } 13 | 14 | public static function getValue($tokens, &$startFrom, &$placeholderValues, $pToken) { 15 | $tToken = $tokens[$startFrom] ?? '_'; 16 | $result = Finder::compareIt($tToken, T_COMMENT, $pToken[1], $startFrom); 17 | if ($result === null) { 18 | return false; 19 | } 20 | $placeholderValues[] = $result; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/Comparison.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if (! in_array($t[0], [ 17 | T_IS_EQUAL, // == 18 | T_IS_IDENTICAL, // === 19 | T_IS_GREATER_OR_EQUAL, // >= 20 | T_IS_SMALLER_OR_EQUAL, // <= 21 | T_IS_NOT_EQUAL, // != 22 | T_IS_NOT_IDENTICAL, // !== 23 | T_SPACESHIP, // <=> 24 | '>', 25 | '<', 26 | ])) { 27 | return false; 28 | } 29 | 30 | $placeholderValues[] = $t; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Keywords/DocBlock.php: -------------------------------------------------------------------------------- 1 | '; 10 | } 11 | 12 | public static function getValue($tokens, &$startFrom, &$placeholderValues) { 13 | $t = $tokens[$startFrom] ?? '_'; 14 | 15 | if ($t[0] !== T_DOC_COMMENT) { 16 | return false; 17 | } 18 | 19 | $placeholderValues[] = $t; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Keywords/FloatNum.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if ($t[0] !== T_DNUMBER) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/FullClassRef.php: -------------------------------------------------------------------------------- 1 | '; 14 | } 15 | 16 | public static function getValue($tokens, &$startFrom, &$placeholderValues) 17 | { 18 | $tToken = $tokens[$startFrom] ?? '_'; 19 | 20 | if (defined('T_NAME_FULLY_QUALIFIED')) { 21 | if ($tToken[0] !== T_NAME_FULLY_QUALIFIED) { 22 | return false; 23 | } 24 | 25 | $placeholderValues[] = [T_STRING, $tToken[1], $tToken[2]]; 26 | 27 | return; 28 | } 29 | 30 | if ($tToken[0] !== T_NS_SEPARATOR) { 31 | return false; 32 | } 33 | 34 | $absClassRef = ['classRef' => '\\""']; 35 | $repeatingClassRef = PatternParser::tokenize('""'); 36 | 37 | $isMatch = Tokens::compareTokens($repeatingClassRef, $tokens, $startFrom, $absClassRef); 38 | 39 | if (! $isMatch) { 40 | return false; 41 | } 42 | 43 | $placeholderValues[] = Finder::extractValue($isMatch[2][0]); 44 | $startFrom = $isMatch[0]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Keywords/GlobalFunctionCall.php: -------------------------------------------------------------------------------- 1 | '; 13 | } 14 | 15 | public static function getValue($tokens, &$startFrom, &$placeholderValues) 16 | { 17 | if (! self::mustStart($tokens, $startFrom)) { 18 | return false; 19 | } 20 | 21 | $tToken = $tokens[$startFrom] ?? '_'; 22 | 23 | if ($tToken[0] === T_NS_SEPARATOR) { 24 | $matches = Tokens::compareTokens(PatternParser::tokenize('\\""'), $tokens, $startFrom); 25 | if (! $matches) { 26 | return false; 27 | } 28 | 29 | $strValue = self::concatinate($matches[1]); 30 | $startFrom = $matches[0]; 31 | $placeholderValues[] = $strValue; 32 | } elseif ($tToken[0] === T_STRING) { 33 | $placeholderValues[] = $tToken; 34 | } elseif (defined('T_NAME_FULLY_QUALIFIED') && $tToken[0] === T_NAME_FULLY_QUALIFIED) { 35 | $placeholderValues[] = [T_STRING, $tToken[1], $tToken[2]]; 36 | } else { 37 | return false; 38 | } 39 | } 40 | 41 | private static function concatinate(array $matches) 42 | { 43 | $segments = ['']; 44 | foreach ($matches as $match) { 45 | $segments[] = $match[1]; 46 | } 47 | 48 | return [T_STRING, implode('\\', $segments), $match[2]]; 49 | } 50 | 51 | private static function getPrevToken($tokens, $i) 52 | { 53 | $i--; 54 | $token = $tokens[$i] ?? '_'; 55 | while ($token[0] == T_WHITESPACE || $token[0] == T_COMMENT) { 56 | $i--; 57 | $token = $tokens[$i]; 58 | } 59 | 60 | return [$token, $i]; 61 | } 62 | 63 | private static function mustStart($tokens, $i) 64 | { 65 | [$prev, $prevI] = self::getPrevToken($tokens, $i); 66 | 67 | if ($prev[0] === T_NS_SEPARATOR) { 68 | [$prev] = self::getPrevToken($tokens, $prevI); 69 | } 70 | 71 | $excluded = [T_NEW, T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_FUNCTION]; 72 | defined('T_NULLSAFE_OBJECT_OPERATOR') && $excluded[] = T_NULLSAFE_OBJECT_OPERATOR; 73 | 74 | if (in_array($prev[0], $excluded)) { 75 | return false; 76 | } 77 | 78 | return true; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Keywords/InBetween.php: -------------------------------------------------------------------------------- 1 | '; 13 | } 14 | 15 | public static function getValue( 16 | $tokens, 17 | &$startFrom, 18 | &$placeholderValues, 19 | $pToken, 20 | $pattern, 21 | $pi, 22 | $j 23 | ) { 24 | $startingToken = $pattern[$j - 1]; // may use getPreviousToken() 25 | self::validate($startingToken, $pattern[$j + 1]); 26 | 27 | [$_value, $startFrom] = self::readUntilMatch($pi, $tokens, $startingToken); 28 | $placeholderValues[] = $_value; 29 | } 30 | 31 | private static function readUntilMatch($i, $tokens, $startingToken) 32 | { 33 | $anti = self::getAnti($startingToken); 34 | $untilTokens = []; 35 | $line = 1; 36 | $level = 0; 37 | for ($k = $i + 1; true; $k++) { 38 | if ($tokens[$k] === $anti && $level === 0) { 39 | break; 40 | } 41 | 42 | $tokens[$k] === $startingToken && $level--; 43 | $tokens[$k] === $anti && $level++; 44 | 45 | ! $line && isset($tokens[$k][2]) && $line = $tokens[$k][2]; 46 | $untilTokens[] = $tokens[$k]; 47 | } 48 | 49 | $startFrom = $k - 1; 50 | $value = [T_STRING, Stringify::fromTokens($untilTokens), $line]; 51 | 52 | return [$value, $startFrom]; 53 | } 54 | 55 | private static function getAnti($startingToken) 56 | { 57 | return [ 58 | '(' => ')', 59 | '{' => '}', 60 | '[' => ']', 61 | ][$startingToken]; 62 | } 63 | 64 | private static function validate($startingToken, $pattern) 65 | { 66 | if (! in_array($startingToken, ['(', '[', '{'], true)) { 67 | throw new Exception('pattern invalid'); 68 | } 69 | 70 | if (self::getAnti($startingToken) !== $pattern) { 71 | throw new Exception('pattern invalid'); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/Keywords/Integer.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if ($t[0] !== T_LNUMBER) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/MethodVisibility.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if (! in_array($t[0], [T_PUBLIC, T_PROTECTED, T_PRIVATE])) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/Name.php: -------------------------------------------------------------------------------- 1 | '; 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if ($t[0] !== T_STRING) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/Number.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if ($t[0] !== T_LNUMBER && $t[0] !== T_DNUMBER) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/RepeatingPattern.php: -------------------------------------------------------------------------------- 1 | '; 12 | } 13 | 14 | public static function getValue($tokens, &$startFrom, &$placeholderValues) 15 | { 16 | [$_value, $startFrom] = self::readExpression($startFrom, $tokens); 17 | $placeholderValues[] = $_value; 18 | } 19 | 20 | private static function readExpression($i, $tokens) 21 | { 22 | $level = 0; 23 | $collected = []; 24 | $line = 1; 25 | 26 | for ($k = $i; true; $k++) { 27 | if (! isset($tokens[$k])){ 28 | return ['', $k]; 29 | } 30 | $nextToken = $tokens[$k]; 31 | 32 | if (in_array($nextToken, [';', ',', ']'], true) && $level === 0) { 33 | $value = [T_STRING, Stringify::fromTokens($collected), $line]; 34 | 35 | return [$value, $k - 1]; 36 | } 37 | $collected[] = $nextToken; 38 | 39 | if (in_array($nextToken[0], ['[', '(', '{', T_CURLY_OPEN], true)) { 40 | $level++; 41 | } 42 | 43 | if (in_array($nextToken[0], [']', ')', '}'], true)) { 44 | $level--; 45 | } 46 | 47 | isset($nextToken[2]) && $line = $nextToken[2]; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Keywords/Str.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if ($t[0] !== T_CONSTANT_ENCAPSED_STRING) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/Until.php: -------------------------------------------------------------------------------- 1 | '; 12 | } 13 | 14 | public static function getValue( 15 | $tokens, 16 | &$startFrom, 17 | &$placeholderValues, 18 | $pToken, 19 | $pattern, 20 | $pi, 21 | $j 22 | ) { 23 | [$_value, $startFrom] = self::readUntil($pi, $tokens, $pattern[$j + 1]); 24 | $placeholderValues[] = $_value; 25 | } 26 | 27 | private static function readUntil($pi, $tokens, $pattern) 28 | { 29 | $untilTokens = []; 30 | $line = 1; 31 | for ($k = $pi + 1; $tokens[$k] !== $pattern; $k++) { 32 | ! $line && isset($tokens[$k][2]) && $line = $tokens[$k][2]; 33 | $untilTokens[] = $tokens[$k]; 34 | } 35 | $placeholderValue = [T_STRING, Stringify::fromTokens($untilTokens), $line]; 36 | 37 | return [$placeholderValue, $k - 1]; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/Keywords/Variable.php: -------------------------------------------------------------------------------- 1 | ', ''], true); 10 | } 11 | 12 | public static function getValue($tokens, $startFrom, &$placeholderValues) 13 | { 14 | $t = $tokens[$startFrom]; 15 | 16 | if ($t[0] !== T_VARIABLE) { 17 | return false; 18 | } 19 | 20 | $placeholderValues[] = $t; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Keywords/WhiteSpace.php: -------------------------------------------------------------------------------- 1 | '; 12 | } 13 | 14 | public static function getValue($tokens, &$startFrom, &$placeholderValues, $pToken) 15 | { 16 | $tToken = $tokens[$startFrom] ?? '_'; 17 | $result = Finder::compareIt($tToken, T_WHITESPACE, $pToken[1], $startFrom); 18 | if ($result === null) { 19 | return false; 20 | } 21 | $placeholderValues[] = $result; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/PatternParser.php: -------------------------------------------------------------------------------- 1 | true, 11 | 'predicate' => null, 12 | 'mutator' => null, 13 | 'named_patterns' => [], 14 | 'filters' => [], 15 | 'avoid_syntax_errors' => false, 16 | 'post_replace' => [], 17 | ]; 18 | 19 | $analyzedPatterns = []; 20 | 21 | $all = self::getSearchPatterns(); 22 | 23 | foreach ($patterns as $to) { 24 | $normalize && $to = PatternParser\Normalizer::normalize($to, $all); 25 | 26 | [$tokens, $addedFilters] = PatternParser\Filters::extractFilter($to['search']); 27 | $tokens = ['search' => $tokens] + $to + $defaults; 28 | foreach ($addedFilters as $addedFilter) { 29 | $tokens['filters'][$addedFilter[0]]['in_array'] = $addedFilter[1]; 30 | } 31 | $analyzedPatterns[] = $tokens; 32 | } 33 | 34 | return $analyzedPatterns; 35 | } 36 | 37 | private static function getSearchPatterns() 38 | { 39 | $names = implode(',', [ 40 | 'white_space', 41 | 'compare', 42 | 'comparison', 43 | 'not_whitespace', 44 | 'string', 45 | 'str', 46 | 'variable', 47 | 'var', 48 | 'statement', 49 | 'in_between', 50 | 'any', 51 | 'cast', 52 | 'number', 53 | 'int', 54 | 'integer', 55 | 'doc_block', 56 | 'name', 57 | 'visibility', 58 | 'float', 59 | 'comment', 60 | 'until', 61 | 'full_class_ref', 62 | 'class_ref', 63 | 'bool', 64 | 'boolean', 65 | ]); 66 | 67 | return [ 68 | // the order of the patterns matter. 69 | ['search' => '<"">?', 'replace' => '"<"<1>">?"',], 70 | ['search' => '<"">', 'replace' => '"<"<1>">"',], 71 | ]; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/PatternParser/Filters.php: -------------------------------------------------------------------------------- 1 | $pToken) { 17 | if ($pToken[0] !== T_CONSTANT_ENCAPSED_STRING) { 18 | continue; 19 | } 20 | 21 | // If is placeholder "" 22 | if ($pToken[1][1] === '<' && '>' === $pToken[1][strlen($pToken[1]) - 2]) { 23 | $count++; 24 | } else { 25 | continue; 26 | } 27 | 28 | $ids = self::getPlaceholderIds(); 29 | foreach ($ids as [$id, $mutator]) { 30 | if (Finder::startsWith(trim($pToken[1], '\'\"'), "<$id:")) { 31 | $tokens[$i][1] = "'<$id>'"; 32 | $readParams = self::getParams($pToken, $id); 33 | $mutator && $readParams = $mutator($readParams); 34 | $addedFilters[] = [$count, $readParams]; 35 | } 36 | } 37 | } 38 | 39 | return [$tokens, $addedFilters]; 40 | } 41 | 42 | private static function getPlaceholderIds() 43 | { 44 | return [ 45 | [ 46 | 'global_func_call', 47 | function ($values) { 48 | $values = $u = explode(',', $values); 49 | foreach ($u as $val) { 50 | $values[] = '\\'.$val; 51 | } 52 | 53 | return $values; 54 | }, 55 | ], 56 | ['name', null], 57 | ]; 58 | } 59 | 60 | private static function getParams($pToken, $id) 61 | { 62 | $pName = trim($pToken[1], '\'\"'); 63 | 64 | return rtrim(Str::replaceFirst("<$id:", '', $pName), '>'); 65 | } 66 | } -------------------------------------------------------------------------------- /src/PatternParser/Normalizer.php: -------------------------------------------------------------------------------- 1 | '<"">', 'replace' => '"<"<1>">"',]] 17 | )); 18 | 19 | return $to; 20 | } 21 | 22 | private static function addQuotes(string $search, array $all) 23 | { 24 | [$tokens,] = Searcher::searchParsed($all, token_get_all('&1', escapeshellarg(__DIR__.'/tmp.php'))); 11 | unlink(__DIR__.'/tmp.php'); 12 | 13 | return preg_match('!No syntax errors detected!', $output); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Placeholders.php: -------------------------------------------------------------------------------- 1 | $pt) { 11 | if (! self::isOptionalPlaceholder($pt)) { 12 | return $i; 13 | } 14 | } 15 | 16 | return $i; 17 | } 18 | 19 | private static function isOptionalPlaceholder($token) 20 | { 21 | if ($token[0] !== T_CONSTANT_ENCAPSED_STRING) { 22 | return false; 23 | } 24 | 25 | return Finder::endsWith($token[1], '>?"') || Finder::endsWith($token[1], ">?'"); 26 | } 27 | } -------------------------------------------------------------------------------- /src/PostReplace.php: -------------------------------------------------------------------------------- 1 | $postReplace) { 12 | [$tokens, $lines] = Searcher::search([$key => $postReplace], token_get_all(Stringify::fromTokens($tokens))); 13 | $lines && $wasReplaced = true; 14 | } 15 | 16 | return [$tokens, $wasReplaced]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Replacer.php: -------------------------------------------------------------------------------- 1 | $t) { 43 | $r = Finder::isRepeatingPattern($t); 44 | if (! $r) { 45 | continue; 46 | } 47 | [$num, $pName] = explode(':', $r); 48 | $pattern = $namedPatterns[$pName]; 49 | 50 | $newTokens = self::applyRepeats($repeating, $pattern, $newTokens, $index); 51 | } 52 | 53 | return Stringify::fromTokens($newTokens); 54 | } 55 | 56 | public static function applyOnReplacements($replace, $values) 57 | { 58 | if (is_callable($replace)) { 59 | return call_user_func($replace, $values); 60 | } 61 | 62 | $newValue = $replace; 63 | foreach ($values as $number => $value) { 64 | ! is_array($value[0]) && $newValue = str_replace(['"<'.($number + 1).'>"', "'<".($number + 1).">'"], $value[1] ?? $value[0], $newValue); 65 | } 66 | 67 | return $newValue; 68 | } 69 | 70 | private static function replaceTokens($tokens, $from, $to, string $with) 71 | { 72 | $lineNumber = 0; 73 | 74 | for ($i = $from; $i <= $to; $i++) { 75 | if ($i === $from) { 76 | $lineNumber = $tokens[$i][2] ?? 0; 77 | $tokens[$i] = [T_STRING, $with, 1]; 78 | continue; 79 | } 80 | 81 | if ($i > $from && $i <= $to) { 82 | ! $lineNumber && ($lineNumber = $tokens[$i][2] ?? 0); 83 | $tokens[$i] = [T_STRING, '', 1]; 84 | } 85 | } 86 | 87 | $j = 0; 88 | while ($lineNumber === 0 && $j < 5) { 89 | $j++; 90 | $lineNumber = $tokens[$i++][2] ?? 0; 91 | } 92 | 93 | return [$tokens, $lineNumber]; 94 | } 95 | 96 | private static function applyRepeats($repeating, $pattern, $newTokens, $index) 97 | { 98 | foreach ($repeating as $repeat) { 99 | $repeatsValues = []; 100 | foreach ($repeat as $r) { 101 | $repeatsValues[] = self::applyOnReplacements($pattern, $r); 102 | } 103 | $newTokens[$index][1] = implode('', $repeatsValues); 104 | } 105 | 106 | return $newTokens; 107 | } 108 | } -------------------------------------------------------------------------------- /src/Searcher.php: -------------------------------------------------------------------------------- 1 | $patternMatch) { 79 | [$tokens, $replacementLines] = self::applyAllMatches($patternMatch, $replacePatterns[$pi]['replace'], $tokens, $replacementLines); 80 | } 81 | 82 | return [$tokens, $replacementLines]; 83 | }*/ 84 | 85 | public static function searchParsed($patterns, $tokens) 86 | { 87 | return self::searchReplaceMultiplePatterns(PatternParser::parsePatterns($patterns,false), $tokens); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Str.php: -------------------------------------------------------------------------------- 1 | T_WHITESPACE, 33 | T_COMMENT => T_COMMENT, 34 | //',' => ',', 35 | ]; 36 | 37 | public static function compareTokens($pattern, $tokens, $startFrom, $namedPatterns = [], $ignoreWhitespace = true) 38 | { 39 | $pi = $j = 0; 40 | $tCount = count($tokens); 41 | $pCount = count($pattern); 42 | $repeating = $placeholderValues = []; 43 | 44 | $pToken = $pattern[$j]; 45 | 46 | while ($startFrom < $tCount && $j < $pCount) { 47 | if ($pToken[0] === T_CONSTANT_ENCAPSED_STRING && $pToken[1][1] === '<') { 48 | $trimmed = trim($pToken[1], '\'\"?'); 49 | if (Finder::startsWith($trimmed, '', '']))) { 81 | return self::getNextToken($tokens, $startFrom, T_WHITESPACE); 82 | } elseif (isset($pToken[1]) && self::is($pToken, '')) { 83 | return self::getNextToken($tokens, $startFrom, T_COMMENT); 84 | } else { 85 | return self::getNextToken($tokens, $startFrom); 86 | } 87 | } 88 | 89 | public static function getNextToken($tokens, $i, $notIgnored = null) 90 | { 91 | $ignored = self::$ignored; 92 | 93 | if ($notIgnored) { 94 | unset($ignored[$notIgnored]); 95 | } 96 | 97 | $i++; 98 | $token = $tokens[$i] ?? '_'; 99 | while (in_array($token[0], $ignored, true)) { 100 | $i++; 101 | $token = $tokens[$i] ?? [null, null]; 102 | } 103 | 104 | return [$token, $i]; 105 | } 106 | 107 | public static function is($token, $keyword) 108 | { 109 | return in_array(trim($token[1], '\'\"?'), (array) $keyword, true); 110 | } 111 | 112 | private static function checkKeywords(&$startFrom, &$placeholderValues, $trimmed, $tokens, $pToken, $pattern, $pi, $j) 113 | { 114 | foreach (self::$keywords as $classToken) { 115 | if ($classToken::is($trimmed)) { 116 | if ($classToken::getValue($tokens, $startFrom, $placeholderValues, $pToken, $pattern, $pi, $j) === false) { 117 | return false; 118 | } else { 119 | break; 120 | } 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /tests/BaseTestClass.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '?"?""?"]', 15 | 'replace' => ']', 16 | ] 17 | ]; 18 | 19 | $startFile = 'assertEquals($resultFile, $newVersion); 23 | } 24 | 25 | /** @test */ 26 | public function begins_with_optional_white_space() 27 | { 28 | $patterns = [ 29 | 'name'=> [ 30 | 'search' => "'?'];'?'", 31 | 'replace' => ',"<1>"];"<2>"'], 32 | ]; 33 | $startCode = 'assertEquals($resultCode, $newVersion); 39 | $this->assertEquals([1], $replacedAt); 40 | } 41 | 42 | /** @test */ 43 | public function begins_with_optional_white_space_5() 44 | { 45 | $patterns = [ 46 | 'name'=> [ 47 | 'search' => "'?'];'?'", 48 | 'replace' => ',"<1>"];"<2>"'], 49 | ]; 50 | $start_code = 'assertEquals($resultCode, $newVersion); 56 | $this->assertEquals([1], $replacedAt); 57 | } 58 | 59 | /** @test */ 60 | public function begins_with_optional_white_space3() 61 | { 62 | $patterns = [ 63 | 'name'=> [ 64 | 'search' => "'?'];'?'", 65 | 'replace' => ',"<1>"];"<2>"'], 66 | ]; 67 | $startCode = 'assertEquals($resultCode, $newVersion); 73 | $this->assertEquals([1], $replacedAt); 74 | } 75 | 76 | /** @test */ 77 | public function begins_with_optional_white_space_2() 78 | { 79 | $patterns = [ 80 | 'name'=> [ 81 | 'search' => "'?''?'];", 82 | 'replace' => '];"<1>";'], 83 | ]; 84 | $startCode = 'assertEquals($resultCode, $newVersion); 90 | $this->assertEquals([1], $replacedAt); 91 | } 92 | 93 | /** @test */ 94 | public function begins_with_optional_white_space_4() 95 | { 96 | $patterns = [ 97 | 'name'=> [ 98 | 'search' => "'?'''];", 99 | 'replace' => '];'], 100 | ]; 101 | $startCode = 'assertEquals($resultCode, $newVersion); 107 | $this->assertEquals([1], $replacedAt); 108 | } 109 | /** @test */ 110 | public function begins_with_optional_white_space_s4() 111 | { 112 | $patterns = [ 113 | 'name'=> [ 114 | 'search' => "'?''?'];", 115 | 'replace' => '];"<1>""<2>";'], 116 | ]; 117 | $startCode = 'assertEquals($resultCode, $newVersion); 123 | $this->assertEquals([1], $replacedAt); 124 | 125 | $patterns = [ 126 | 'name'=> [ 127 | 'search' => "'?''?'];", 128 | 'replace' => ']"<1>""<2>";'], 129 | ]; 130 | $startCode = 'assertEquals($resultCode, $newVersion); 136 | $this->assertEquals([1], $replacedAt); 137 | 138 | $patterns = [ 139 | 'name'=> [ 140 | 'search' => "'?''?'];", 141 | 'replace' => '];"<1>""<2>";'], 142 | ]; 143 | $startCode = 'assertEquals($resultCode, $newVersion); 149 | $this->assertEquals([1], $replacedAt); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/BooleanTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '; ""; "?"; ?;', 15 | 'replace' => '"<4>";<3>;<2>;"<1>";' 16 | ], 17 | ]; 18 | 19 | $startFile = 'assertEquals($resultFile, $newVersion); 24 | $this->assertEquals([1], $replacedAt); 25 | } 26 | 27 | /** @test */ 28 | public function boolean() 29 | { 30 | $patterns = [ 31 | 'name' => [ 32 | 'search' => '""; ""; ""; "";', 33 | 'replace' => '"<4>";"<3>";"<2>";"<1>";' 34 | ], 35 | ]; 36 | 37 | $startFile = 'assertEquals($resultFile, $newVersion); 42 | 43 | $this->assertEquals([1], $replacedAt); 44 | } 45 | 46 | /** @test */ 47 | public function boolean_1() 48 | { 49 | $patterns = [ 50 | 'name' => [ 51 | 'search' => '""', 52 | 'replace' => 'aa' 53 | ], 54 | ]; 55 | 56 | $start_File = 'assertEquals($resultFile, $newVersion); 61 | 62 | $this->assertEquals([], $replacedAt); 63 | } 64 | 65 | /** @test */ 66 | public function boolean_2() 67 | { 68 | $patterns = [ 69 | 'name' => [ 70 | 'search' => '""', 71 | 'replace' => 'aa' 72 | ], 73 | ]; 74 | 75 | $start_File = 'assertEquals($resultFile, $newVersion); 80 | 81 | $this->assertEquals([], $replacedAt); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/CallsPrivateMethods.php: -------------------------------------------------------------------------------- 1 | $method(...$parameters); 20 | })->call($object); 21 | } 22 | } -------------------------------------------------------------------------------- /tests/CastTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '""', 15 | 'replace' => '', 16 | ] 17 | ]; 18 | 19 | $startFile = 'assertEquals($resultFile, $newVersion); 23 | 24 | $startFile = 'assertEquals($resultFile, $newVersion); 28 | 29 | $startFile = 'assertEquals($resultFile, $newVersion); 33 | 34 | $startFile = 'assertEquals($resultFile, $newVersion); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/ClassReferenceTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => "new ''(); new Foo(); new ''();", 15 | 'replace' => '"<1>"::class;"<2>"::class;'], 16 | ]; 17 | $startCode = 'assertEquals($resultCode, $newVersion); 22 | $this->assertEquals([1], $replacedAt); 23 | } 24 | 25 | /** @test */ 26 | public function class_ref32() 27 | { 28 | $patterns = [ 29 | 'name' => [ 30 | 'search' => "new ''(); new ''(); new ''();", 31 | 'replace' => '"<1>"::class; "<2>"::class; "<3>"::class;'], 32 | ]; 33 | $startCode = 'assertEquals($resultCode, $newVersion); 38 | $this->assertEquals([1], $replacedAt); 39 | } 40 | 41 | /** @test */ 42 | public function class_ref_3() 43 | { 44 | $patterns = [ 45 | 'name' => [ 46 | 'search' => "new ''();", 47 | 'replace' => ''], 48 | ]; 49 | $startCode = 'assertEquals($resultCode, $newVersion); 54 | $this->assertEquals([1], $replacedAt); 55 | } 56 | 57 | /** @test */ 58 | public function class_ref_6() 59 | { 60 | $patterns = [ 61 | 'name' => [ 62 | 'search' => "''();", 63 | 'replace' => ''], 64 | ]; 65 | $startCode = 'assertEquals($resultCode, $newVersion); 70 | $this->assertEquals([1], $replacedAt); 71 | } 72 | 73 | /** @test */ 74 | public function class_ref_4() 75 | { 76 | $patterns = [ 77 | 'name' => [ 78 | 'search' => "new ''();", 79 | 'replace' => ''], 80 | ]; 81 | $startCode = 'assertEquals($resultCode, $newVersion); 86 | $this->assertEquals([1], $replacedAt); 87 | 88 | 89 | $startCode = 'assertEquals($resultCode, $newVersion); 93 | $this->assertEquals([1], $replacedAt); 94 | 95 | $startCode = 'assertEquals($resultCode, $newVersion); 99 | $this->assertEquals([1], $replacedAt); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/CommentPlaceholderTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '""]', 15 | 'replace' => ']', 16 | ] 17 | ]; 18 | 19 | $startFile = 'assertEquals($resultFile, $newVersion); 23 | } 24 | 25 | /** @test */ 26 | public function match_comment_without_quote() 27 | { 28 | $patterns = [ 29 | 'name' => [ 30 | 'search' => ']', 31 | 'replace' => ']', 32 | ] 33 | ]; 34 | 35 | $startFile = 'assertEquals($resultFile, $newVersion); 39 | } 40 | 41 | /** @test */ 42 | public function match_comment_() 43 | { 44 | $patterns = [ 45 | 'name' => [ 46 | 'search' => '""', 47 | 'replace' => '', 48 | ] 49 | ]; 50 | 51 | $startFile = 'assertEquals($resultFile, trim($newVersion)); 57 | } 58 | 59 | /** @test */ 60 | public function doc_comment() 61 | { 62 | $patterns = [ 63 | 'name' => [ 64 | 'search' => '""', 65 | 'replace' => '', 66 | ] 67 | ]; 68 | 69 | $startFile = 'assertEquals($resultFile, $newVersion); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/CommentingPatternsTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '["<10:any>""<8:white_space>?"]', 15 | 'replace' => '["<1>""<2>","<1>"]', 16 | 'predicate' => function ($matches) { 17 | return $matches['values'][0][0] === T_CONSTANT_ENCAPSED_STRING; 18 | } 19 | ] 20 | ]; 21 | 22 | $startFile = 'assertEquals($resultFile, $newVersion); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/CompareTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '2 3;', 15 | 'replace' => '4 <1> 5;', 16 | ] 17 | ]; 18 | 19 | $startFile = 'assertEquals($resultFile, $newVersion); 23 | 24 | $startFile = 'assertEquals($resultFile, $newVersion); 28 | 29 | $startFile = '= 3;'; 30 | $resultFile = '= 5;'; 31 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 32 | $this->assertEquals($resultFile, $newVersion); 33 | $this->assertEquals(1, $replacedAt[0]); 34 | 35 | $patterns = [ 36 | 'name' => [ 37 | 'search' => '2 3;', 38 | 'replace' => '4 <1> 5;', 39 | ] 40 | ]; 41 | 42 | $startFile = 'assertEquals($resultFile, $newVersion); 46 | $this->assertEquals(1, $replacedAt[0]); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/FiltersPredicateTest.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'search' => '::h();', 18 | 'replace' => "", 19 | 'filters' => [ 20 | 1 => [ 21 | [ 22 | function ($placeholderVal, $parameter) { 23 | return in_array($placeholderVal[1], $parameter); 24 | }, ['h', 'g'], 25 | ], 26 | ], 27 | ], 28 | ], 29 | ]; 30 | 31 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 32 | 33 | $this->assertEquals($resultFile, $newVersion); 34 | $this->assertEquals([1, 1], $replacedAt); 35 | } 36 | 37 | /** @test */ 38 | public function inline_filters_1() 39 | { 40 | $startFile = ' [ 45 | 'search' => '::h();', 46 | 'replace' => "", 47 | 'filters' => [ 48 | 1 => [ 49 | [ 50 | function ($placeholderVal, $parameter) { 51 | return in_array($placeholderVal[1], $parameter); 52 | }, ['h', 'g'], 53 | ], 54 | ], 55 | ], 56 | ], 57 | ]; 58 | 59 | [$newVersion, $replacedAt] = Searcher::searchReplaceFirst($patterns, token_get_all($startFile)); 60 | 61 | $this->assertEquals($resultFile, $newVersion); 62 | $this->assertEquals([1], $replacedAt); 63 | 64 | //////////////////////////////////////////// 65 | 66 | $startFile = ' [ 71 | 'search' => '::h();', 72 | 'replace' => "", 73 | 'filters' => [ 74 | 1 => [ 75 | [ 76 | function ($placeholderVal, $parameter) { 77 | return in_array($placeholderVal[1], $parameter); 78 | }, ['k', 'g'], 79 | ], 80 | ], 81 | ], 82 | ], 83 | ]; 84 | 85 | [$newVersion, $replacedAt] = Searcher::searchReplaceFirst($patterns, token_get_all($startFile)); 86 | 87 | $this->assertEquals($resultFile, $newVersion); 88 | $this->assertEquals([1], $replacedAt); 89 | } 90 | 91 | /** @test */ 92 | public function filters() 93 | { 94 | $startFile = ' [ 101 | 'search' => '""::h();', 102 | 'replace' => "", 103 | 'filters' => [ 104 | 1 => [ 105 | 'in_array' => ['h', 'g'], 106 | ] 107 | ] 108 | ], 109 | ]; 110 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 111 | 112 | $this->assertEquals($resultFile, $newVersion); 113 | $this->assertEquals([1, 1], $replacedAt); 114 | 115 | //////////////////////////////////////////// 116 | 117 | $patterns = [ 118 | 'name' => [ 119 | 'search' => '""::h();', 120 | 'replace' => "", 121 | 'filters' => [ 122 | 1 => [ 123 | 'in_array' => 'h,g', 124 | ] 125 | ] 126 | ], 127 | ]; 128 | 129 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 130 | 131 | $this->assertEquals($resultFile, $newVersion); 132 | $this->assertEquals([1, 1], $replacedAt); 133 | } 134 | 135 | /** @test */ 136 | public function inline_params_for_name_filter() 137 | { 138 | $startFile = ' [ 143 | 'search' => '""::h();', 144 | 'replace' => "", 145 | ], 146 | ]; 147 | 148 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 149 | 150 | $this->assertEquals($resultFile, $newVersion); 151 | $this->assertEquals([1, 1], $replacedAt); 152 | } 153 | 154 | /** @test */ 155 | public function capturing_predicate() 156 | { 157 | $patterns = [ 158 | "name" => [ 159 | 'search' => "'' = '';", 160 | 'replace' => '', 161 | 'predicate' => function ($matches) { 162 | return $matches['values'][0][1] === $matches['values'][1][1]; 163 | } 164 | ], 165 | ]; 166 | $startFile = 'assertEquals($resultFile, $newVersion); 177 | $this->assertEquals([3], $replacedAt); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tests/FullClassReferenceTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => "new ''(); ''();", 15 | 'replace' => '"<1>";'], 16 | ]; 17 | $startCode = 'assertEquals($resultCode, $newVersion); 22 | $this->assertEquals([1], $replacedAt); 23 | } 24 | 25 | /** @test */ 26 | public function full_class_ref_does_not_capture_semi_qualified_refs() 27 | { 28 | $patterns = [ 29 | 'name' => [ 30 | 'search' => "new ''();", 31 | 'replace' => '"<1>";'], 32 | ]; 33 | $startCode = 'assertEquals($resultCode, $newVersion); 38 | $this->assertEquals([], $replacedAt); 39 | } 40 | 41 | /** @test */ 42 | public function full_class_ref_not_capture_non_qualified_refs() 43 | { 44 | $patterns = [ 45 | 'name' => [ 46 | 'search' => "new ''();", 47 | 'replace' => '"<1>";'], 48 | ]; 49 | $startCode = 'assertEquals($resultCode, $newVersion); 54 | $this->assertEquals([], $replacedAt); 55 | } 56 | 57 | /** @test */ 58 | public function full_class_ref_not_capture_non_qualified_ref_1() 59 | { 60 | $patterns = [ 61 | 'name' => [ 62 | 'search' => "''();", 63 | 'replace' => '"<1>"();'], 64 | ]; 65 | 66 | $startCode = 'assertEquals($resultCode, $newVersion); 71 | $this->assertEquals([1], $replacedAt); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/GlobalFunctionTest.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'search' => ";''()", 16 | 'replace' => '' 17 | ], 18 | ]; 19 | 20 | $startFile_ = ' dd(); jj();dd();dd(); \dd(); \kk();'; 21 | $resultFile = ' dd(); jj(); \kk();'; 22 | 23 | $tokens = token_get_all($startFile_); 24 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, $tokens); 25 | 26 | $this->assertEquals($resultFile, $newVersion); 27 | } 28 | 29 | /** @test */ 30 | public function function_call() 31 | { 32 | $patterns = [ 33 | "name" => [ 34 | 'search' => "''('');", 35 | 'replace' => '' 36 | ], 37 | ]; 38 | 39 | 40 | $start_File = ' dd();dd(); \dd(); dump();'; 41 | $resultFile = ' dd(); '; 42 | 43 | $tokens = token_get_all($start_File); 44 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, $tokens); 45 | 46 | $this->assertEquals($resultFile, $newVersion); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /tests/NotWhitespaceTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => ',', 15 | 'replace' => ', <1>', 16 | ] 17 | ]; 18 | 19 | $start = 'assertEquals($result, $newVersion); 23 | $this->assertEquals([0], $replacedAt); 24 | 25 | $start = 'assertEquals($result, $newVersion); 29 | $this->assertEquals([], $replacedAt); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/NumberPlaceHolderTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '["",""]', 15 | 'replace' => '[]', 16 | ] 17 | ]; 18 | 19 | $startFile = 'assertEquals($resultFile, $newVersion); 23 | } 24 | 25 | /** @test */ 26 | public function integer_number() 27 | { 28 | $patterns = [ 29 | 'name' => [ 30 | 'search' => '""', 31 | 'replace' => '', 32 | ] 33 | ]; 34 | 35 | $startFile = 'assertEquals($resultFile, $newVersion); 39 | } 40 | 41 | /** @test */ 42 | public function float_number() 43 | { 44 | $patterns = [ 45 | 'name' => [ 46 | 'search' => '"",', 47 | 'replace' => '', 48 | ] 49 | ]; 50 | 51 | $startFile = 'assertEquals($resultFile, $newVersion); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/PostReplaceTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => ']', 15 | 'replace' => ',]', 16 | 'post_replace' => [ 17 | 'name1' => ['search' => ',,]', 'replace' => ',]'], 18 | 'name2' => ['search' => '[,]', 'replace' => '[]'], 19 | ], 20 | ] 21 | ]; 22 | 23 | $startFile = 'assertEquals($resultFile, $newVersion); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/RefactorPatternParsingTest.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'search' => '["""?"]', 17 | 'replace' => '["<1>""<2>","<1>"]', 18 | 'predicate' => function ($matches) { 19 | return $matches['values'][0][0] === T_CONSTANT_ENCAPSED_STRING; 20 | } 21 | ] 22 | ]; 23 | 24 | $startFile = 'assertEquals($resultFile, $newVersion); 28 | } 29 | 30 | /** @test */ 31 | public function any_keyword3() 32 | { 33 | $patterns = [ 34 | 'name' => [ 35 | 'search' => '"""?"]', 36 | 'replace' => '"<1>""<2>","<1>"]', 37 | 'predicate' => function ($matches) { 38 | return $matches['values'][0][0] === T_CONSTANT_ENCAPSED_STRING; 39 | } 40 | ] 41 | ]; 42 | 43 | $startFile = 'assertEquals($resultFile, $newVersion); 47 | } 48 | 49 | /** @test */ 50 | public function match_comment() 51 | { 52 | $patterns = [ 53 | 'name' => [ 54 | 'search' => '""]', 55 | 'replace' => ']', 56 | ] 57 | ]; 58 | 59 | $startFile = 'assertEquals($resultFile, $newVersion); 63 | } 64 | 65 | /** @test */ 66 | public function match_optional_comment() 67 | { 68 | $patterns = [ 69 | 'name' => [ 70 | 'search' => '"?""?"]', 71 | 'replace' => ']', 72 | ] 73 | ]; 74 | 75 | $startFile = 'assertEquals($resultFile, $newVersion); 79 | } 80 | 81 | /** @test */ 82 | public function match_optional_comment_2() 83 | { 84 | $patterns = [ 85 | 'name' => [ 86 | 'search' => '"?""?"]', 87 | 'replace' => ']', 88 | ] 89 | ]; 90 | 91 | $startFile = 'assertEquals($resultFile, $newVersion); 95 | } 96 | 97 | /** @test */ 98 | public function match_optional_comment_4() 99 | { 100 | $patterns = [ 101 | 'name' => [ 102 | 'search' => '"?""?""?"]', 103 | 'replace' => ']', 104 | ] 105 | ]; 106 | 107 | $startFile = 'assertEquals($resultFile, $newVersion); 111 | } 112 | 113 | /** @test */ 114 | public function capturing_place_holders() 115 | { 116 | $patterns = [ 117 | "name" => [ 118 | 'search' => "if (!'' && '') { return response()->''(['message' => __(''),], ''); }", 119 | 'replace' => 'Foo::bar(<1>, "<2>", <3>(), "<4>");' 120 | ], 121 | 'name2' => [ 122 | 'search' => 'foo(false, true, null);', 123 | 'replace' => 'bar("hi");' 124 | ], 125 | ]; 126 | $startFile = file_get_contents(__DIR__.'/stubs/SimplePostController.stub'); 127 | $resultFile = file_get_contents(__DIR__.'/stubs/ResultSimplePostController.stub'); 128 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 129 | 130 | $this->assertEquals($resultFile, $newVersion); 131 | $this->assertEquals([15, 22, 25, 26], $replacedAt); 132 | } 133 | 134 | /** @test */ 135 | public function basic_capturing_place_holders() 136 | { 137 | $patterns = [ 138 | "name" => [ 139 | 'search' => "'' = 1;", 140 | 'replace' => "'<1>';" 141 | ], 142 | ]; 143 | $startFile = 'assertEquals($resultFile, $newVersion); 148 | $this->assertEquals([1], $replacedAt); 149 | 150 | // with double-quotes 151 | $patterns = [ 152 | 'name' => [ 153 | 'search' => '"" = 1;', 154 | 'replace' => "'<1>';" 155 | ], 156 | ]; 157 | $startFile = 'assertEquals($resultFile, $newVersion); 162 | $this->assertEquals([1], $replacedAt); 163 | 164 | } 165 | 166 | /** @test */ 167 | public function can_parse_patterns() 168 | { 169 | $patterns = [ 170 | "name" => [ 171 | 'search' => "if (!'' && '') { return response()->''(['message' => __(''),], ''); }", 172 | 'replace' => 'Foo::bar("<1>", "<2>", "<3>"(), "<4>");' 173 | ], 174 | 175 | 'name2' => [ 176 | 'search' => 'foo(false, true, null);', 177 | 'replace' => 'bar("hi");' 178 | ], 179 | ]; 180 | 181 | $sampleFileTokens = token_get_all(file_get_contents(__DIR__.'/stubs/SimplePostController.stub')); 182 | 183 | $patterns = PatternParser::parsePatterns($patterns); 184 | foreach ($patterns as $pIndex => $pattern) { 185 | $matches[$pIndex] = Finder::getMatches($pattern['search'], $sampleFileTokens, $pattern['predicate'], $pattern['mutator']); 186 | } 187 | 188 | $this->assertEquals($matches[0][0]['values'], 189 | [ 190 | [T_VARIABLE, '$user', 15], 191 | [T_STRING, 'true', 15], 192 | [T_STRING, 'json', 17], 193 | [T_CONSTANT_ENCAPSED_STRING, "'hi'", 17], 194 | [T_LNUMBER, 404, 17], 195 | ] 196 | ); 197 | 198 | $start = $matches[0][0]['start']; 199 | $this->assertEquals($sampleFileTokens[$start][1], 'if'); 200 | 201 | $end = $matches[0][0]['end']; 202 | $this->assertEquals($sampleFileTokens[$end], '}'); 203 | 204 | $this->assertEquals($matches[0][1]['values'], 205 | [ 206 | [T_VARIABLE, '$club', 22], 207 | [T_STRING, 'FALSE', 22], 208 | [T_STRING, 'json', 23], 209 | [T_CONSTANT_ENCAPSED_STRING, "'Hello'", 23], 210 | [T_LNUMBER, 403, 23], 211 | ] 212 | ); 213 | 214 | $start = $matches[0][1]['start']; 215 | $this->assertEquals($sampleFileTokens[$start][1], 'if'); 216 | 217 | $end = $matches[0][1]['end']; 218 | $this->assertEquals($sampleFileTokens[$end], '}'); 219 | 220 | $start = $matches[1][0]['start']; 221 | $this->assertEquals($sampleFileTokens[$start][1], 'foo'); 222 | 223 | $end = $matches[1][0]['end']; 224 | $this->assertEquals($sampleFileTokens[$end], ';'); 225 | 226 | $start = $matches[1][1]['start']; 227 | $this->assertEquals($sampleFileTokens[$start][1], 'foo'); 228 | 229 | $end = $matches[1][1]['end']; 230 | $this->assertEquals($sampleFileTokens[$end], ';'); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /tests/RepeatingTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => "'''';'';", 15 | 'replace' => '"<1>";"<2>";', 16 | 'named_patterns' => [ 17 | 'exp' => '->a("", "")', 18 | ], 19 | ], 20 | ]; 21 | 22 | $startFile = 'a("s1", "a1")->a("s2", "a2"); $r;'; 23 | $resultFile = 'assertEquals($resultFile, $newVersion); 26 | } 27 | 28 | /** @test */ 29 | public function optional_comment_placeholder_q5() 30 | { 31 | $patterns = [ 32 | "name"=> [ 33 | 'search' => "'''';", 34 | 'replace' => '"<1>""";', 35 | 'named_patterns' => [ 36 | 'exp' => '->a("", "")', 37 | 'subs' => '->a("<2>", "<1>")', 38 | ] 39 | ], 40 | ]; 41 | 42 | $startFile = 'a("s1", "a1")->a("s2", "a2")->a("g3", "k3");'; 43 | $resultFile = 'a("a1", "s1")->a("a2", "s2")->a("k3", "g3");'; 44 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 45 | $this->assertEquals($resultFile, $newVersion); 46 | } 47 | 48 | /** @test */ 49 | public function optional_comment_placeholder_qd5() 50 | { 51 | $patterns = [ 52 | "name"=> [ 53 | 'search' => "User::where('', '')''", 54 | 'replace' => 'User::where(["<1>" => "<2>", ""])', 55 | 'named_patterns' => [ 56 | 'exp' => '->where("", "")', 57 | 'subs' => '"<1>" => "<2>", ', 58 | ] 59 | ], 60 | ]; 61 | 62 | 63 | $startFile = 'where("s2", "a2")->where("g3", "k3")->get();'; 64 | $resultFile = ' "a1", "s2" => "a2", "g3" => "k3", ])->get();'; 65 | 66 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 67 | $this->assertEquals($resultFile, $newVersion); 68 | } 69 | 70 | /** @test */ 71 | public function optional_comment_placeholder_qgd5() 72 | { 73 | $patterns = [ 74 | "name"=> [ 75 | 'search' => "''", 76 | 'replace' => '->where([""])', 77 | 'named_patterns' => [ 78 | 'exp' => '->where("", "")', 79 | 'subs' => '"<1>" => "<2>", ', 80 | ] 81 | ], 82 | ]; 83 | 84 | 85 | $startFile = 'where("s1", "a1")->where("s2", "a2")->where("g3", "k3")->get();'; 86 | $resultFile = 'where(["s1" => "a1", "s2" => "a2", "g3" => "k3", ])->get();'; 87 | 88 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 89 | $this->assertEquals($resultFile, $newVersion); 90 | } 91 | 92 | /** @test */ 93 | public function optional_comment_placeholder_qg1() 94 | { 95 | $patterns = [ 96 | "name"=> [ 97 | 'search' => "''->get();", 98 | 'replace' => '->where([""])->met();', 99 | 'named_patterns' => [ 100 | 'exp' => '->where("", "")', 101 | 'subs' => '"<1>" => "<2>", ', 102 | ] 103 | ], 104 | ]; 105 | 106 | 107 | $startFile = 'where("s1", "a1")->where("s2", "a2")->where("g3", "k3")->get();'; 108 | $resultFile = 'where(["s1" => "a1", "s2" => "a2", "g3" => "k3", ])->met();'; 109 | 110 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 111 | $this->assertEquals($resultFile, $newVersion); 112 | } 113 | 114 | /** @test */ 115 | public function optional_comment_placeholder_qrg1() 116 | { 117 | $patterns = [ 118 | "name"=> [ 119 | 'search' => "''->get();", 120 | 'replace' => '->where([""])->met();', 121 | 'named_patterns' => [ 122 | 'exp' => '->where("", "")', 123 | 'subs' => '"<1>" => "<2>", ', 124 | ] 125 | ], 126 | ]; 127 | 128 | $startFile = "where('family', 'Ghafoori') 131 | ->where('job', 'be_to_che') 132 | ->where('website', 'codino.org') 133 | ->get(); 134 | 135 | User::where('_name', 'Iman___') 136 | ->where('_family', 'Ghafoori___') 137 | ->where('_web_site', '_codino__org_') 138 | ->get();"; 139 | 140 | $resultFile = "where(['family' => 'Ghafoori', 'job' => 'be_to_che', 'website' => 'codino.org', ])->met(); 143 | 144 | User::where('_name', 'Iman___') 145 | ->where(['_family' => 'Ghafoori___', '_web_site' => '_codino__org_', ])->met();"; 146 | 147 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 148 | $this->assertEquals($resultFile, $newVersion); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/StatementTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '$user = ""' , 15 | 'replace' => '"<1>"' 16 | ], 17 | ]; 18 | 19 | $startCode = 'assertEquals($resultCode, $newVersion); 25 | $this->assertEquals([1], $replacedAt); 26 | } 27 | 28 | /** @test */ 29 | public function statement_2() 30 | { 31 | $patterns = [ 32 | 'name' => [ 33 | 'search' => '"";$a = 1;' , 34 | 'replace' => '<1>' 35 | ], 36 | ]; 37 | $startCode = 'assertEquals($resultCode, $newVersion); 43 | $this->assertEquals([1], $replacedAt); 44 | } 45 | 46 | /** @test */ 47 | public function statement_3() 48 | { 49 | $patterns = [ 50 | 'name' => [ 51 | 'search' => '["a" => "a", "b" => "" ];' , 52 | 'replace' => '"<1>"' 53 | ], 54 | ]; 55 | $startCode = ' "a", "b" => User::where(function() { "a"; })->get() ];'; 56 | $resultCode = 'get() '; 57 | 58 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startCode)); 59 | 60 | $this->assertEquals($resultCode, $newVersion); 61 | $this->assertEquals([1], $replacedAt); 62 | } 63 | 64 | /** @test */ 65 | public function statement_4() 66 | { 67 | $patterns = [ 68 | 'name' => [ 69 | 'search' => '"";' , 70 | 'replace' => '' 71 | ], 72 | ]; 73 | 74 | $startCode = 'assertEquals($resultCode, $newVersion); 80 | } 81 | 82 | /** @test */ 83 | public function statement_5() 84 | { 85 | $patterns = [ 86 | 'name' => [ 87 | 'search' => '$user = "";"";' , 88 | 'replace' => '"<1>""<2>"' 89 | ], 90 | ]; 91 | $startCode = 'assertEquals($resultCode, $newVersion); 97 | $this->assertEquals([1], $replacedAt); 98 | } 99 | 100 | /** @test */ 101 | public function statement_6() 102 | { 103 | $patterns = [ 104 | 'name' => [ 105 | 'search' => '$user = ;;' , 106 | 'replace' => '<1><2>' 107 | ], 108 | ]; 109 | $startCode = 'assertEquals($resultCode, $newVersion); 115 | $this->assertEquals([1], $replacedAt); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /tests/UntilTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => 'return response()"";', 15 | 'replace' => 'response()"<1>"->throwResponse();' 16 | ], 17 | ]; 18 | 19 | $startFile = file_get_contents(__DIR__.'/stubs/SimplePostController.stub'); 20 | $resultFile = file_get_contents(__DIR__.'/stubs/SimplePostController2.stub'); 21 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 22 | 23 | $this->assertEquals($resultFile, $newVersion); 24 | 25 | $this->assertEquals([16, 23], $replacedAt); 26 | } 27 | 28 | /** @test */ 29 | public function in_between() 30 | { 31 | $patterns = [ 32 | "name" => [ 33 | 'search' => "if(''){}", 34 | 'replace' => 'if(true) {"<1>";}' 35 | ], 36 | ]; 37 | 38 | $startFile = 'bar()) {}'; 39 | $resultFile = 'bar();}'; 40 | 41 | $tokens = token_get_all($startFile); 42 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, $tokens); 43 | 44 | $this->assertEquals($resultFile, $newVersion); 45 | 46 | $this->assertEquals([1], $replacedAt); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/VisibilityTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '""', 15 | 'replace' => '', 16 | ] 17 | ]; 18 | 19 | $startFile = 'assertEquals($resultFile, $newVersion); 23 | 24 | 25 | $startFile = 'assertEquals($resultFile, $newVersion); 29 | 30 | $startFile = 'assertEquals($resultFile, $newVersion); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/WhitespaceTest.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'search' => '[""]', 15 | 'replace' => function ($values) { 16 | $this->assertEquals(' ', $values[0][1]); 17 | $this->assertEquals(T_WHITESPACE, $values[0][0]); 18 | return '[]'; 19 | }, 20 | ] 21 | ]; 22 | 23 | $start = 'assertEquals($result, $newVersion); 27 | $this->assertEquals([1, 1], $replacedAt); 28 | } 29 | 30 | /** @test */ 31 | public function match_white_space52() 32 | { 33 | $patterns = [ 34 | 'name' => [ 35 | 'search' => '["<1:white_space>"]', 36 | 'replace' => function ($values) { 37 | $this->assertEquals(' ', $values[0][1]); 38 | $this->assertEquals(T_WHITESPACE, $values[0][0]); 39 | return '[]'; 40 | }, 41 | ] 42 | ]; 43 | 44 | $start = 'assertEquals($result, $newVersion); 48 | $this->assertEquals([1, 1], $replacedAt); 49 | } 50 | 51 | /** @test */ 52 | public function match_white_space() 53 | { 54 | $patterns = [ 55 | 'name' => [ 56 | 'search' => '[""]', 57 | 'replace' => '[]', 58 | ] 59 | ]; 60 | 61 | $startFile = 'assertEquals($resultFile, $newVersion); 65 | $this->assertEquals([1, 1], $replacedAt); 66 | } 67 | 68 | /** @test */ 69 | public function white_space() 70 | { 71 | $patterns = [ 72 | "name" => 73 | [ 74 | 'search' => "use App\Club;''use App\Events\MemberCommentedClubPost;", 75 | 'replace' => "use App\Club; use App\Events\MemberCommentedClubPost;", 76 | ], 77 | 78 | "name2" => 79 | [ 80 | 'search' => "use Illuminate\Http\Request;''", 81 | 'replace' => '' 82 | ], 83 | ]; 84 | $startFile = file_get_contents(__DIR__.'/stubs/SimplePostController.stub'); 85 | 86 | $resultFile = file_get_contents(__DIR__.'/stubs/EolSimplePostControllerResult.stub'); 87 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 88 | 89 | $this->assertEquals($resultFile, $newVersion); 90 | $this->assertEquals([5, 8], $replacedAt); 91 | } 92 | 93 | /** @test */ 94 | public function white_space_placeholder() 95 | { 96 | $patterns = [ 97 | "name" => [ 98 | 'search' => ")''{", 99 | 'replace' => '){' 100 | ], 101 | ]; 102 | $startFile = file_get_contents(__DIR__.'/stubs/SimplePostController.stub'); 103 | $resultFile = file_get_contents(__DIR__.'/stubs/NoWhiteSpaceSimplePostController.stub'); 104 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 105 | 106 | $this->assertEquals($resultFile, $newVersion); 107 | $this->assertEquals([13, 15, 20], $replacedAt); 108 | } 109 | 110 | /** @test */ 111 | public function optional_white_space_placeholder() 112 | { 113 | $patterns = [ 114 | "name" => [ 115 | 'search' => "response('?')'?'->json", 116 | 'replace' => 'response()"<2>"->mson' 117 | ], 118 | ]; 119 | $startFile = file_get_contents(__DIR__.'/stubs/SimplePostController.stub'); 120 | 121 | $resultFile = file_get_contents(__DIR__.'/stubs/OptionalWhiteSpaceSimplePostController.stub'); 122 | [$newVersion, $replacedAt] = Searcher::searchReplace($patterns, token_get_all($startFile)); 123 | 124 | $this->assertEquals($resultFile, $newVersion); 125 | $this->assertEquals([16, 23], $replacedAt); 126 | } 127 | 128 | /** @test */ 129 | public function match_white_space_empty() 130 | { 131 | $patterns = [ 132 | 'name' => [ 133 | 'search' => '""', 134 | 'replace' => '', 135 | ] 136 | ]; 137 | 138 | $start = 'assertEquals($result, $newVersion); 142 | $this->assertEquals([1], $replacedAt); 143 | } 144 | 145 | /** @test */ 146 | public function optional_comment_placeholder() 147 | { 148 | $patterns = [ 149 | "name" => [ 150 | 'search' => ";'?''';", 151 | 'replace' => ';"<1>""<2>"' 152 | ], 153 | ]; 154 | $startFile = 'assertEquals($resultFile, $newVersion); 160 | $this->assertEquals([1], $replacedAt); 161 | } 162 | 163 | /** @test */ 164 | public function optional_comment_placeholder_2() 165 | { 166 | $patterns = [ 167 | "name" => [ 168 | 'search' => ";'?''?';", 169 | 'replace' => ';"<1>""<2>""<1>";' 170 | ], 171 | ]; 172 | 173 | $startFile = 'assertEquals($resultFile, $newVersion); 178 | $this->assertEquals([1], $replacedAt); 179 | } 180 | 181 | /** @test */ 182 | public function optional_comment_placeholder_3() 183 | { 184 | $patterns = [ 185 | "name" => [ 186 | 'search' => ";'?''?';", 187 | 'replace' => ';"<1>""<2>""<1>";' 188 | ], 189 | ]; 190 | 191 | $startFile = 'assertEquals($resultFile, $newVersion); 196 | $this->assertEquals([1], $replacedAt); 197 | } 198 | 199 | /** @test */ 200 | public function optional_comment_placeholder_32() 201 | { 202 | $patterns = [ 203 | "name" => [ 204 | 'search' => "'?''?';", 205 | 'replace' => '"<1>""<2>""<1>";' 206 | ], 207 | ]; 208 | 209 | $startFile = 'assertEquals($resultFile, $newVersion); 214 | $this->assertEquals([1, 1], $replacedAt); 215 | } 216 | 217 | /** @test */ 218 | public function optional_comment_placeholder_4() 219 | { 220 | $patterns = [ 221 | "name" => [ 222 | 'search' => ";'?''?';", 223 | 'replace' => ';"<1>""<2>""<1>";' 224 | ], 225 | ]; 226 | 227 | $startFile = 'assertEquals($resultFile, $newVersion); 232 | $this->assertEquals([1], $replacedAt); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /tests/stubs/EolSimplePostControllerResult.stub: -------------------------------------------------------------------------------- 1 | json(["message" => __('hi'),], 404); 16 | } 17 | } 18 | public function index2(User $user, Club $club, Request $request) 19 | { 20 | if(!$club && FALSE){ 21 | return response()->json(['message' => __('Hello'),], 403); 22 | } 23 | foo(FALSE, TRUE, NULL); 24 | foo(false, true, null); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/stubs/NoWhiteSpaceSimplePostController.stub: -------------------------------------------------------------------------------- 1 | json(["message" => __('hi'),], 404); 17 | } 18 | } 19 | public function index2(User $user, Club $club, Request $request){ 20 | if(!$club && FALSE){ 21 | return response()->json(['message' => __('Hello'),], 403); 22 | } 23 | foo(FALSE, TRUE, NULL); 24 | foo(false, true, null); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/stubs/OptionalWhiteSpaceSimplePostController.stub: -------------------------------------------------------------------------------- 1 | mson(["message" => __('hi'),], 404); 18 | } 19 | } 20 | public function index2(User $user, Club $club, Request $request) 21 | { 22 | if(!$club && FALSE){ 23 | return response()->mson(['message' => __('Hello'),], 403); 24 | } 25 | foo(FALSE, TRUE, NULL); 26 | foo(false, true, null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/stubs/ResultSimplePostController.stub: -------------------------------------------------------------------------------- 1 | json(["message" => __('hi'),], 404); 18 | } 19 | } 20 | public function index2(User $user, Club $club, Request $request) 21 | { 22 | if(!$club && FALSE){ 23 | return response()->json(['message' => __('Hello'),], 403); 24 | } 25 | foo(FALSE, TRUE, NULL); 26 | foo(false, true, null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/stubs/SimplePostController2.stub: -------------------------------------------------------------------------------- 1 | json(["message" => __('hi'),], 404)->throwResponse(); 18 | } 19 | } 20 | public function index2(User $user, Club $club, Request $request) 21 | { 22 | if(!$club && FALSE){ 23 | response()->json(['message' => __('Hello'),], 403)->throwResponse(); 24 | } 25 | foo(FALSE, TRUE, NULL); 26 | foo(false, true, null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/stubs/refactor_patterns.php: -------------------------------------------------------------------------------- 1 |