├── .editorconfig ├── .github ├── CODEOWNERS └── workflows │ ├── ci.yml │ └── generate-since.yml ├── .gitignore ├── README.md ├── bin └── wp-since ├── check-plugin.php ├── composer.json ├── composer.lock ├── generate-since-json.php ├── phpcs.xml ├── src ├── Checker │ └── CompatibilityChecker.php ├── Resolver │ ├── IgnoreRulesResolver.php │ ├── InlineIgnoreResolver.php │ └── VersionResolver.php ├── Runner │ └── PluginCheckCommand.php ├── Scanner │ ├── PluginScanner.php │ ├── SymbolExtractorVisitor.php │ └── SymbolHandlers │ │ ├── FunctionCallHandler.php │ │ ├── MethodCallHandler.php │ │ ├── NewClassHandler.php │ │ ├── StaticCallHandler.php │ │ └── SymbolHandlerInterface.php └── Utils │ ├── TablePrinter.php │ └── VersionHelper.php ├── tests ├── CompatibilityCheckerTest.php ├── Integration │ └── FullCompatibilityFlowTest.php ├── PluginScannerTest.php ├── Resolver │ └── IgnoreRulesResolverTest.php ├── TablePrinterTest.php ├── VersionHelperTest.php ├── VersionResolverTest.php └── fixtures │ ├── file-a.php │ ├── file-b.php │ ├── plugin-full-test │ ├── .distignore │ ├── ignore-this.php │ ├── ignored-folder │ │ └── fake1.php │ ├── ignored-no-slash │ │ └── file.php │ ├── plugin-full-test.php │ └── readme.txt │ ├── plugin-ignore-comment │ └── plugin-ignore-comment.php │ ├── plugin-with-header │ └── plugin-with-header.php │ ├── plugin-with-readme-only │ ├── plugin-with-readme-only.php │ └── readme.txt │ └── plugin-without-version │ ├── plugin-without-version.php │ └── readme.txt └── wp-since.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 4 11 | trim_trailing_whitespace = true 12 | 13 | [*.php] 14 | indent_style = space 15 | indent_size = 4 16 | max_line_length = 120 17 | 18 | [*.{json,yml,yaml,xml}] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [Makefile] 23 | indent_style = tab 24 | 25 | [*.md] 26 | trim_trailing_whitespace = false 27 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @eduardovillao 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Lint & Test PHP Project 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | quality-checks: 7 | name: Code Quality & Tests 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup PHP 15 | uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: "8.2" 18 | tools: composer, phpunit, phpcs 19 | 20 | - name: Install dependencies 21 | run: composer install --no-interaction --prefer-dist 22 | 23 | - name: Run PHPCS (PSR-12) 24 | run: composer lint 25 | 26 | - name: Run PHPUnit tests 27 | run: composer tests:unit 28 | -------------------------------------------------------------------------------- /.github/workflows/generate-since.yml: -------------------------------------------------------------------------------- 1 | name: Generate WP Since Map 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout project 12 | uses: actions/checkout@v4 13 | 14 | - name: Install PHP depencencies 15 | run: | 16 | sudo apt-get update 17 | sudo apt-get install -y php php-cli php-mbstring unzip curl 18 | 19 | - name: Install Composer 20 | run: | 21 | curl -sS https://getcomposer.org/installer | php 22 | sudo mv composer.phar /usr/local/bin/composer 23 | 24 | - name: Install Composer dependencies 25 | run: composer install 26 | 27 | - name: Download WordPress 28 | run: | 29 | curl -O https://wordpress.org/latest.zip 30 | unzip latest.zip -d wp-source 31 | mv wp-source/wordpress/* wp-source/ 32 | 33 | - name: Generate wp-since.json 34 | run: php generate-since-json.php 35 | 36 | - name: Remove temporary files 37 | run: rm -rf wp.tar.gz wp-source 38 | 39 | - name: Install GitHub CLI 40 | run: sudo apt install gh -y 41 | 42 | - name: Authenticate GitHub CLI 43 | env: 44 | GH_TOKEN: ${{ secrets.WP_SINCE_ACTION }} 45 | run: echo "${GH_TOKEN}" | gh auth login --with-token 46 | 47 | - name: Create new branch 48 | run: | 49 | BRANCH_NAME=update-since-map-${{ github.run_number }} 50 | git checkout -b $BRANCH_NAME 51 | echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV 52 | 53 | - name: Commit changes 54 | run: | 55 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 56 | git config --global user.name "GitHub Actions" 57 | git add wp-since.json 58 | DATE=$(date +"%Y-%m-%d") 59 | git commit -m "chore(data): update since map on $DATE" || echo "Nada pra commitar" 60 | git push origin $BRANCH_NAME 61 | 62 | - name: Create Pull Request 63 | env: 64 | GH_TOKEN: ${{ secrets.WP_SINCE_ACTION }} 65 | run: | 66 | gh pr create --head "$BRANCH_NAME" --base main \ 67 | --title "chore(data): update since map" \ 68 | --body "This PR was automatically generated by a GitHub Action. It updates the \`wp-since.json\` file with the latest WordPress version and its release date." 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Since 2 | 3 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 4 | [![Code Style: PSR-12](https://img.shields.io/badge/code%20style-PSR--12-blue)](https://www.php-fig.org/psr/psr-12/) 5 | [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)](./tests) 6 | 7 | **Make sure your plugin works with the right WordPress version — automatically.** 8 | Scans your WordPress plugin to detect all used core symbols and validates them against their official @since versions for accurate compatibility checks. 9 | 10 | ## ✨ How It Works 11 | 12 | Ever struggled to define the correct minimum WordPress version for your plugin? 13 | 14 | Worried about accidentally using functions or APIs that don’t exist in declared minimum WP version? 15 | 16 | `wp-since` helps you avoid those headaches by automatically analyzing your plugin’s code and checking compatibility against real WordPress versions. 17 | 18 | ### Here’s what it does: 19 | 20 | - 🧠 Scans your plugin for used: 21 | - Functions 22 | - Classes 23 | - Class methods (static and instance) 24 | - Action and filter hooks 25 | - 📖 Reads the declared Requires at least: version from your `readme.txt` 26 | - 🗂️ Compares those symbols with a version map built from WordPress core using `@since` tags 27 | - 🚨 Reports any used symbols that require a newer WP version than what’s declared 28 | 29 | ### Example Output 30 | 31 | Let’s say your plugin uses `register_setting()` (introduced in WP `5.5`), but your `readme.txt` declares compatibility with WordPress `5.4`: 32 | 33 | ```bash 34 | 🔍 Scanning plugin files... 35 | ✅ Found readme.txt → Minimum version declared: 5.4 36 | 37 | 🚨 Compatibility issues found: 38 | 39 | ┌──────────────────────┬──────────────────┐ 40 | │ Symbol │ Introduced in WP │ 41 | ├──────────────────────┼──────────────────┤ 42 | │ register_setting │ 5.5.0 │ 43 | └──────────────────────┴──────────────────┘ 44 | 45 | 📌 Suggested version required: 5.5.0 46 | ``` 47 | 48 | Now imagine your code is fully aligned with your declared version: 49 | 50 | ```bash 51 | 🔍 Scanning plugin files... 52 | ✅ Found readme.txt → Minimum version declared: 5.5 53 | 54 | 🎉 No compatibility issues found! 55 | ``` 56 | 57 | Simple. Powerful. Automatic. 58 | Because your plugin deserves reliable compatibility. 59 | 60 | ## 🚀 Usage 61 | 62 | **Requirements** 63 | 64 | - PHP 7.4+ 65 | - Composer 66 | 67 | 🛠️ Install via Composer (recommended) 68 | 69 | ```bash 70 | composer require --dev eduardovillao/wp-since 71 | ``` 72 | 73 | ▶️ Run the compatibility check 74 | 75 | ```bash 76 | ./vendor/bin/wp-since check ./path-to-your-plugin 77 | ``` 78 | 79 | ### 🧹 Ignore Files & Folders 80 | 81 | By default, wp-since scans all `.php` files in your plugin directory. 82 | 83 | But what about files that don’t make it into your final plugin zip — like tests or dev tools? No worries — wp-since respects your ignore rules. 84 | 85 | **Supported ignore sources:** 86 | 87 | - `.distignore` 88 | - `.gitattributes` with `export-ignore` 89 | 90 | If any of those files are present, wp-since will automatically ignore the listed files or folders during analysis — just like svn export or plugin deployment. 91 | 92 | Example: .gitattributes 93 | 94 | ```txt 95 | /tests/ export-ignore 96 | /tools/debug.php export-ignore 97 | ``` 98 | 99 | Example: .distignore 100 | 101 | ```txt 102 | /tests 103 | /tools/debug.php 104 | ``` 105 | 106 | > These paths will be excluded from compatibility checks. This helps avoid false positives caused by test or development files. 107 | 108 | ### 📝 Inline Ignore 109 | 110 | You can ignore specific lines from the scan by adding a special inline comment. 111 | 112 | This is useful when you conditionally use a newer function but know it’s safe, like: 113 | 114 | ```php 115 | if (function_exists('wp_some_new_func')) { 116 | return wp_some_new_func(); // @wp-since ignore 117 | } 118 | ``` 119 | 120 | > Only inline comments on the same line will be considered — comments above the line won’t trigger ignores. 121 | 122 | ## 🛠️ Coming Soon 123 | 124 | - GitHub Action integration 125 | - HTML/Markdown reports 126 | - Export for CI/CD pipelines 127 | 128 | ## 📜 License 129 | 130 | MIT © [Eduardo Villão](https://github.com/eduardovillao) 131 | Use freely, contribute gladly. 132 | -------------------------------------------------------------------------------- /bin/wp-since: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =7.4", 8 | "nikic/php-parser": "^4.15" 9 | }, 10 | "require-dev": { 11 | "phpunit/phpunit": "^10.5", 12 | "squizlabs/php_codesniffer": "^3.12" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "WP_Since\\": "src/" 17 | } 18 | }, 19 | "bin": [ 20 | "bin/wp-since" 21 | ], 22 | "scripts": { 23 | "check": "php bin/wp-since check ./tests/fixtures/plugin-full-test", 24 | "generate-since": "php generate-since-json.php", 25 | "tests:unit": "phpunit tests --testdox --colors", 26 | "lint": "phpcs", 27 | "lint:fix": "phpcbf" 28 | }, 29 | "minimum-stability": "stable", 30 | "prefer-stable": true, 31 | "support": { 32 | "issues": "https://github.com/eduardovillao/wp-since/issues", 33 | "source": "https://github.com/eduardovillao/wp-since" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "be2810f9ad06c975eb7bba257dc54afb", 8 | "packages": [ 9 | { 10 | "name": "nikic/php-parser", 11 | "version": "v4.19.4", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/nikic/PHP-Parser.git", 15 | "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", 20 | "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-tokenizer": "*", 25 | "php": ">=7.1" 26 | }, 27 | "require-dev": { 28 | "ircmaxell/php-yacc": "^0.0.7", 29 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 30 | }, 31 | "bin": [ 32 | "bin/php-parse" 33 | ], 34 | "type": "library", 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "4.9-dev" 38 | } 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "PhpParser\\": "lib/PhpParser" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "BSD-3-Clause" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Nikita Popov" 52 | } 53 | ], 54 | "description": "A PHP parser written in PHP", 55 | "keywords": [ 56 | "parser", 57 | "php" 58 | ], 59 | "support": { 60 | "issues": "https://github.com/nikic/PHP-Parser/issues", 61 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" 62 | }, 63 | "time": "2024-09-29T15:01:53+00:00" 64 | } 65 | ], 66 | "packages-dev": [ 67 | { 68 | "name": "myclabs/deep-copy", 69 | "version": "1.13.0", 70 | "source": { 71 | "type": "git", 72 | "url": "https://github.com/myclabs/DeepCopy.git", 73 | "reference": "024473a478be9df5fdaca2c793f2232fe788e414" 74 | }, 75 | "dist": { 76 | "type": "zip", 77 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", 78 | "reference": "024473a478be9df5fdaca2c793f2232fe788e414", 79 | "shasum": "" 80 | }, 81 | "require": { 82 | "php": "^7.1 || ^8.0" 83 | }, 84 | "conflict": { 85 | "doctrine/collections": "<1.6.8", 86 | "doctrine/common": "<2.13.3 || >=3 <3.2.2" 87 | }, 88 | "require-dev": { 89 | "doctrine/collections": "^1.6.8", 90 | "doctrine/common": "^2.13.3 || ^3.2.2", 91 | "phpspec/prophecy": "^1.10", 92 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 93 | }, 94 | "type": "library", 95 | "autoload": { 96 | "files": [ 97 | "src/DeepCopy/deep_copy.php" 98 | ], 99 | "psr-4": { 100 | "DeepCopy\\": "src/DeepCopy/" 101 | } 102 | }, 103 | "notification-url": "https://packagist.org/downloads/", 104 | "license": [ 105 | "MIT" 106 | ], 107 | "description": "Create deep copies (clones) of your objects", 108 | "keywords": [ 109 | "clone", 110 | "copy", 111 | "duplicate", 112 | "object", 113 | "object graph" 114 | ], 115 | "support": { 116 | "issues": "https://github.com/myclabs/DeepCopy/issues", 117 | "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" 118 | }, 119 | "funding": [ 120 | { 121 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 122 | "type": "tidelift" 123 | } 124 | ], 125 | "time": "2025-02-12T12:17:51+00:00" 126 | }, 127 | { 128 | "name": "phar-io/manifest", 129 | "version": "2.0.4", 130 | "source": { 131 | "type": "git", 132 | "url": "https://github.com/phar-io/manifest.git", 133 | "reference": "54750ef60c58e43759730615a392c31c80e23176" 134 | }, 135 | "dist": { 136 | "type": "zip", 137 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", 138 | "reference": "54750ef60c58e43759730615a392c31c80e23176", 139 | "shasum": "" 140 | }, 141 | "require": { 142 | "ext-dom": "*", 143 | "ext-libxml": "*", 144 | "ext-phar": "*", 145 | "ext-xmlwriter": "*", 146 | "phar-io/version": "^3.0.1", 147 | "php": "^7.2 || ^8.0" 148 | }, 149 | "type": "library", 150 | "extra": { 151 | "branch-alias": { 152 | "dev-master": "2.0.x-dev" 153 | } 154 | }, 155 | "autoload": { 156 | "classmap": [ 157 | "src/" 158 | ] 159 | }, 160 | "notification-url": "https://packagist.org/downloads/", 161 | "license": [ 162 | "BSD-3-Clause" 163 | ], 164 | "authors": [ 165 | { 166 | "name": "Arne Blankerts", 167 | "email": "arne@blankerts.de", 168 | "role": "Developer" 169 | }, 170 | { 171 | "name": "Sebastian Heuer", 172 | "email": "sebastian@phpeople.de", 173 | "role": "Developer" 174 | }, 175 | { 176 | "name": "Sebastian Bergmann", 177 | "email": "sebastian@phpunit.de", 178 | "role": "Developer" 179 | } 180 | ], 181 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 182 | "support": { 183 | "issues": "https://github.com/phar-io/manifest/issues", 184 | "source": "https://github.com/phar-io/manifest/tree/2.0.4" 185 | }, 186 | "funding": [ 187 | { 188 | "url": "https://github.com/theseer", 189 | "type": "github" 190 | } 191 | ], 192 | "time": "2024-03-03T12:33:53+00:00" 193 | }, 194 | { 195 | "name": "phar-io/version", 196 | "version": "3.2.1", 197 | "source": { 198 | "type": "git", 199 | "url": "https://github.com/phar-io/version.git", 200 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 201 | }, 202 | "dist": { 203 | "type": "zip", 204 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 205 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 206 | "shasum": "" 207 | }, 208 | "require": { 209 | "php": "^7.2 || ^8.0" 210 | }, 211 | "type": "library", 212 | "autoload": { 213 | "classmap": [ 214 | "src/" 215 | ] 216 | }, 217 | "notification-url": "https://packagist.org/downloads/", 218 | "license": [ 219 | "BSD-3-Clause" 220 | ], 221 | "authors": [ 222 | { 223 | "name": "Arne Blankerts", 224 | "email": "arne@blankerts.de", 225 | "role": "Developer" 226 | }, 227 | { 228 | "name": "Sebastian Heuer", 229 | "email": "sebastian@phpeople.de", 230 | "role": "Developer" 231 | }, 232 | { 233 | "name": "Sebastian Bergmann", 234 | "email": "sebastian@phpunit.de", 235 | "role": "Developer" 236 | } 237 | ], 238 | "description": "Library for handling version information and constraints", 239 | "support": { 240 | "issues": "https://github.com/phar-io/version/issues", 241 | "source": "https://github.com/phar-io/version/tree/3.2.1" 242 | }, 243 | "time": "2022-02-21T01:04:05+00:00" 244 | }, 245 | { 246 | "name": "phpunit/php-code-coverage", 247 | "version": "10.1.16", 248 | "source": { 249 | "type": "git", 250 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 251 | "reference": "7e308268858ed6baedc8704a304727d20bc07c77" 252 | }, 253 | "dist": { 254 | "type": "zip", 255 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", 256 | "reference": "7e308268858ed6baedc8704a304727d20bc07c77", 257 | "shasum": "" 258 | }, 259 | "require": { 260 | "ext-dom": "*", 261 | "ext-libxml": "*", 262 | "ext-xmlwriter": "*", 263 | "nikic/php-parser": "^4.19.1 || ^5.1.0", 264 | "php": ">=8.1", 265 | "phpunit/php-file-iterator": "^4.1.0", 266 | "phpunit/php-text-template": "^3.0.1", 267 | "sebastian/code-unit-reverse-lookup": "^3.0.0", 268 | "sebastian/complexity": "^3.2.0", 269 | "sebastian/environment": "^6.1.0", 270 | "sebastian/lines-of-code": "^2.0.2", 271 | "sebastian/version": "^4.0.1", 272 | "theseer/tokenizer": "^1.2.3" 273 | }, 274 | "require-dev": { 275 | "phpunit/phpunit": "^10.1" 276 | }, 277 | "suggest": { 278 | "ext-pcov": "PHP extension that provides line coverage", 279 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 280 | }, 281 | "type": "library", 282 | "extra": { 283 | "branch-alias": { 284 | "dev-main": "10.1.x-dev" 285 | } 286 | }, 287 | "autoload": { 288 | "classmap": [ 289 | "src/" 290 | ] 291 | }, 292 | "notification-url": "https://packagist.org/downloads/", 293 | "license": [ 294 | "BSD-3-Clause" 295 | ], 296 | "authors": [ 297 | { 298 | "name": "Sebastian Bergmann", 299 | "email": "sebastian@phpunit.de", 300 | "role": "lead" 301 | } 302 | ], 303 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 304 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 305 | "keywords": [ 306 | "coverage", 307 | "testing", 308 | "xunit" 309 | ], 310 | "support": { 311 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 312 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", 313 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" 314 | }, 315 | "funding": [ 316 | { 317 | "url": "https://github.com/sebastianbergmann", 318 | "type": "github" 319 | } 320 | ], 321 | "time": "2024-08-22T04:31:57+00:00" 322 | }, 323 | { 324 | "name": "phpunit/php-file-iterator", 325 | "version": "4.1.0", 326 | "source": { 327 | "type": "git", 328 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 329 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" 330 | }, 331 | "dist": { 332 | "type": "zip", 333 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", 334 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", 335 | "shasum": "" 336 | }, 337 | "require": { 338 | "php": ">=8.1" 339 | }, 340 | "require-dev": { 341 | "phpunit/phpunit": "^10.0" 342 | }, 343 | "type": "library", 344 | "extra": { 345 | "branch-alias": { 346 | "dev-main": "4.0-dev" 347 | } 348 | }, 349 | "autoload": { 350 | "classmap": [ 351 | "src/" 352 | ] 353 | }, 354 | "notification-url": "https://packagist.org/downloads/", 355 | "license": [ 356 | "BSD-3-Clause" 357 | ], 358 | "authors": [ 359 | { 360 | "name": "Sebastian Bergmann", 361 | "email": "sebastian@phpunit.de", 362 | "role": "lead" 363 | } 364 | ], 365 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 366 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 367 | "keywords": [ 368 | "filesystem", 369 | "iterator" 370 | ], 371 | "support": { 372 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 373 | "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", 374 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" 375 | }, 376 | "funding": [ 377 | { 378 | "url": "https://github.com/sebastianbergmann", 379 | "type": "github" 380 | } 381 | ], 382 | "time": "2023-08-31T06:24:48+00:00" 383 | }, 384 | { 385 | "name": "phpunit/php-invoker", 386 | "version": "4.0.0", 387 | "source": { 388 | "type": "git", 389 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 390 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" 391 | }, 392 | "dist": { 393 | "type": "zip", 394 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", 395 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", 396 | "shasum": "" 397 | }, 398 | "require": { 399 | "php": ">=8.1" 400 | }, 401 | "require-dev": { 402 | "ext-pcntl": "*", 403 | "phpunit/phpunit": "^10.0" 404 | }, 405 | "suggest": { 406 | "ext-pcntl": "*" 407 | }, 408 | "type": "library", 409 | "extra": { 410 | "branch-alias": { 411 | "dev-main": "4.0-dev" 412 | } 413 | }, 414 | "autoload": { 415 | "classmap": [ 416 | "src/" 417 | ] 418 | }, 419 | "notification-url": "https://packagist.org/downloads/", 420 | "license": [ 421 | "BSD-3-Clause" 422 | ], 423 | "authors": [ 424 | { 425 | "name": "Sebastian Bergmann", 426 | "email": "sebastian@phpunit.de", 427 | "role": "lead" 428 | } 429 | ], 430 | "description": "Invoke callables with a timeout", 431 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 432 | "keywords": [ 433 | "process" 434 | ], 435 | "support": { 436 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 437 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" 438 | }, 439 | "funding": [ 440 | { 441 | "url": "https://github.com/sebastianbergmann", 442 | "type": "github" 443 | } 444 | ], 445 | "time": "2023-02-03T06:56:09+00:00" 446 | }, 447 | { 448 | "name": "phpunit/php-text-template", 449 | "version": "3.0.1", 450 | "source": { 451 | "type": "git", 452 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 453 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" 454 | }, 455 | "dist": { 456 | "type": "zip", 457 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", 458 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", 459 | "shasum": "" 460 | }, 461 | "require": { 462 | "php": ">=8.1" 463 | }, 464 | "require-dev": { 465 | "phpunit/phpunit": "^10.0" 466 | }, 467 | "type": "library", 468 | "extra": { 469 | "branch-alias": { 470 | "dev-main": "3.0-dev" 471 | } 472 | }, 473 | "autoload": { 474 | "classmap": [ 475 | "src/" 476 | ] 477 | }, 478 | "notification-url": "https://packagist.org/downloads/", 479 | "license": [ 480 | "BSD-3-Clause" 481 | ], 482 | "authors": [ 483 | { 484 | "name": "Sebastian Bergmann", 485 | "email": "sebastian@phpunit.de", 486 | "role": "lead" 487 | } 488 | ], 489 | "description": "Simple template engine.", 490 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 491 | "keywords": [ 492 | "template" 493 | ], 494 | "support": { 495 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 496 | "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", 497 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" 498 | }, 499 | "funding": [ 500 | { 501 | "url": "https://github.com/sebastianbergmann", 502 | "type": "github" 503 | } 504 | ], 505 | "time": "2023-08-31T14:07:24+00:00" 506 | }, 507 | { 508 | "name": "phpunit/php-timer", 509 | "version": "6.0.0", 510 | "source": { 511 | "type": "git", 512 | "url": "https://github.com/sebastianbergmann/php-timer.git", 513 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" 514 | }, 515 | "dist": { 516 | "type": "zip", 517 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", 518 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", 519 | "shasum": "" 520 | }, 521 | "require": { 522 | "php": ">=8.1" 523 | }, 524 | "require-dev": { 525 | "phpunit/phpunit": "^10.0" 526 | }, 527 | "type": "library", 528 | "extra": { 529 | "branch-alias": { 530 | "dev-main": "6.0-dev" 531 | } 532 | }, 533 | "autoload": { 534 | "classmap": [ 535 | "src/" 536 | ] 537 | }, 538 | "notification-url": "https://packagist.org/downloads/", 539 | "license": [ 540 | "BSD-3-Clause" 541 | ], 542 | "authors": [ 543 | { 544 | "name": "Sebastian Bergmann", 545 | "email": "sebastian@phpunit.de", 546 | "role": "lead" 547 | } 548 | ], 549 | "description": "Utility class for timing", 550 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 551 | "keywords": [ 552 | "timer" 553 | ], 554 | "support": { 555 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 556 | "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" 557 | }, 558 | "funding": [ 559 | { 560 | "url": "https://github.com/sebastianbergmann", 561 | "type": "github" 562 | } 563 | ], 564 | "time": "2023-02-03T06:57:52+00:00" 565 | }, 566 | { 567 | "name": "phpunit/phpunit", 568 | "version": "10.5.45", 569 | "source": { 570 | "type": "git", 571 | "url": "https://github.com/sebastianbergmann/phpunit.git", 572 | "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" 573 | }, 574 | "dist": { 575 | "type": "zip", 576 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", 577 | "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", 578 | "shasum": "" 579 | }, 580 | "require": { 581 | "ext-dom": "*", 582 | "ext-json": "*", 583 | "ext-libxml": "*", 584 | "ext-mbstring": "*", 585 | "ext-xml": "*", 586 | "ext-xmlwriter": "*", 587 | "myclabs/deep-copy": "^1.12.1", 588 | "phar-io/manifest": "^2.0.4", 589 | "phar-io/version": "^3.2.1", 590 | "php": ">=8.1", 591 | "phpunit/php-code-coverage": "^10.1.16", 592 | "phpunit/php-file-iterator": "^4.1.0", 593 | "phpunit/php-invoker": "^4.0.0", 594 | "phpunit/php-text-template": "^3.0.1", 595 | "phpunit/php-timer": "^6.0.0", 596 | "sebastian/cli-parser": "^2.0.1", 597 | "sebastian/code-unit": "^2.0.0", 598 | "sebastian/comparator": "^5.0.3", 599 | "sebastian/diff": "^5.1.1", 600 | "sebastian/environment": "^6.1.0", 601 | "sebastian/exporter": "^5.1.2", 602 | "sebastian/global-state": "^6.0.2", 603 | "sebastian/object-enumerator": "^5.0.0", 604 | "sebastian/recursion-context": "^5.0.0", 605 | "sebastian/type": "^4.0.0", 606 | "sebastian/version": "^4.0.1" 607 | }, 608 | "suggest": { 609 | "ext-soap": "To be able to generate mocks based on WSDL files" 610 | }, 611 | "bin": [ 612 | "phpunit" 613 | ], 614 | "type": "library", 615 | "extra": { 616 | "branch-alias": { 617 | "dev-main": "10.5-dev" 618 | } 619 | }, 620 | "autoload": { 621 | "files": [ 622 | "src/Framework/Assert/Functions.php" 623 | ], 624 | "classmap": [ 625 | "src/" 626 | ] 627 | }, 628 | "notification-url": "https://packagist.org/downloads/", 629 | "license": [ 630 | "BSD-3-Clause" 631 | ], 632 | "authors": [ 633 | { 634 | "name": "Sebastian Bergmann", 635 | "email": "sebastian@phpunit.de", 636 | "role": "lead" 637 | } 638 | ], 639 | "description": "The PHP Unit Testing framework.", 640 | "homepage": "https://phpunit.de/", 641 | "keywords": [ 642 | "phpunit", 643 | "testing", 644 | "xunit" 645 | ], 646 | "support": { 647 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 648 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 649 | "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" 650 | }, 651 | "funding": [ 652 | { 653 | "url": "https://phpunit.de/sponsors.html", 654 | "type": "custom" 655 | }, 656 | { 657 | "url": "https://github.com/sebastianbergmann", 658 | "type": "github" 659 | }, 660 | { 661 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 662 | "type": "tidelift" 663 | } 664 | ], 665 | "time": "2025-02-06T16:08:12+00:00" 666 | }, 667 | { 668 | "name": "sebastian/cli-parser", 669 | "version": "2.0.1", 670 | "source": { 671 | "type": "git", 672 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 673 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" 674 | }, 675 | "dist": { 676 | "type": "zip", 677 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", 678 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", 679 | "shasum": "" 680 | }, 681 | "require": { 682 | "php": ">=8.1" 683 | }, 684 | "require-dev": { 685 | "phpunit/phpunit": "^10.0" 686 | }, 687 | "type": "library", 688 | "extra": { 689 | "branch-alias": { 690 | "dev-main": "2.0-dev" 691 | } 692 | }, 693 | "autoload": { 694 | "classmap": [ 695 | "src/" 696 | ] 697 | }, 698 | "notification-url": "https://packagist.org/downloads/", 699 | "license": [ 700 | "BSD-3-Clause" 701 | ], 702 | "authors": [ 703 | { 704 | "name": "Sebastian Bergmann", 705 | "email": "sebastian@phpunit.de", 706 | "role": "lead" 707 | } 708 | ], 709 | "description": "Library for parsing CLI options", 710 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 711 | "support": { 712 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 713 | "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", 714 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" 715 | }, 716 | "funding": [ 717 | { 718 | "url": "https://github.com/sebastianbergmann", 719 | "type": "github" 720 | } 721 | ], 722 | "time": "2024-03-02T07:12:49+00:00" 723 | }, 724 | { 725 | "name": "sebastian/code-unit", 726 | "version": "2.0.0", 727 | "source": { 728 | "type": "git", 729 | "url": "https://github.com/sebastianbergmann/code-unit.git", 730 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" 731 | }, 732 | "dist": { 733 | "type": "zip", 734 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", 735 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", 736 | "shasum": "" 737 | }, 738 | "require": { 739 | "php": ">=8.1" 740 | }, 741 | "require-dev": { 742 | "phpunit/phpunit": "^10.0" 743 | }, 744 | "type": "library", 745 | "extra": { 746 | "branch-alias": { 747 | "dev-main": "2.0-dev" 748 | } 749 | }, 750 | "autoload": { 751 | "classmap": [ 752 | "src/" 753 | ] 754 | }, 755 | "notification-url": "https://packagist.org/downloads/", 756 | "license": [ 757 | "BSD-3-Clause" 758 | ], 759 | "authors": [ 760 | { 761 | "name": "Sebastian Bergmann", 762 | "email": "sebastian@phpunit.de", 763 | "role": "lead" 764 | } 765 | ], 766 | "description": "Collection of value objects that represent the PHP code units", 767 | "homepage": "https://github.com/sebastianbergmann/code-unit", 768 | "support": { 769 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 770 | "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" 771 | }, 772 | "funding": [ 773 | { 774 | "url": "https://github.com/sebastianbergmann", 775 | "type": "github" 776 | } 777 | ], 778 | "time": "2023-02-03T06:58:43+00:00" 779 | }, 780 | { 781 | "name": "sebastian/code-unit-reverse-lookup", 782 | "version": "3.0.0", 783 | "source": { 784 | "type": "git", 785 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 786 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" 787 | }, 788 | "dist": { 789 | "type": "zip", 790 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", 791 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", 792 | "shasum": "" 793 | }, 794 | "require": { 795 | "php": ">=8.1" 796 | }, 797 | "require-dev": { 798 | "phpunit/phpunit": "^10.0" 799 | }, 800 | "type": "library", 801 | "extra": { 802 | "branch-alias": { 803 | "dev-main": "3.0-dev" 804 | } 805 | }, 806 | "autoload": { 807 | "classmap": [ 808 | "src/" 809 | ] 810 | }, 811 | "notification-url": "https://packagist.org/downloads/", 812 | "license": [ 813 | "BSD-3-Clause" 814 | ], 815 | "authors": [ 816 | { 817 | "name": "Sebastian Bergmann", 818 | "email": "sebastian@phpunit.de" 819 | } 820 | ], 821 | "description": "Looks up which function or method a line of code belongs to", 822 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 823 | "support": { 824 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 825 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" 826 | }, 827 | "funding": [ 828 | { 829 | "url": "https://github.com/sebastianbergmann", 830 | "type": "github" 831 | } 832 | ], 833 | "time": "2023-02-03T06:59:15+00:00" 834 | }, 835 | { 836 | "name": "sebastian/comparator", 837 | "version": "5.0.3", 838 | "source": { 839 | "type": "git", 840 | "url": "https://github.com/sebastianbergmann/comparator.git", 841 | "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" 842 | }, 843 | "dist": { 844 | "type": "zip", 845 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", 846 | "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", 847 | "shasum": "" 848 | }, 849 | "require": { 850 | "ext-dom": "*", 851 | "ext-mbstring": "*", 852 | "php": ">=8.1", 853 | "sebastian/diff": "^5.0", 854 | "sebastian/exporter": "^5.0" 855 | }, 856 | "require-dev": { 857 | "phpunit/phpunit": "^10.5" 858 | }, 859 | "type": "library", 860 | "extra": { 861 | "branch-alias": { 862 | "dev-main": "5.0-dev" 863 | } 864 | }, 865 | "autoload": { 866 | "classmap": [ 867 | "src/" 868 | ] 869 | }, 870 | "notification-url": "https://packagist.org/downloads/", 871 | "license": [ 872 | "BSD-3-Clause" 873 | ], 874 | "authors": [ 875 | { 876 | "name": "Sebastian Bergmann", 877 | "email": "sebastian@phpunit.de" 878 | }, 879 | { 880 | "name": "Jeff Welch", 881 | "email": "whatthejeff@gmail.com" 882 | }, 883 | { 884 | "name": "Volker Dusch", 885 | "email": "github@wallbash.com" 886 | }, 887 | { 888 | "name": "Bernhard Schussek", 889 | "email": "bschussek@2bepublished.at" 890 | } 891 | ], 892 | "description": "Provides the functionality to compare PHP values for equality", 893 | "homepage": "https://github.com/sebastianbergmann/comparator", 894 | "keywords": [ 895 | "comparator", 896 | "compare", 897 | "equality" 898 | ], 899 | "support": { 900 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 901 | "security": "https://github.com/sebastianbergmann/comparator/security/policy", 902 | "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" 903 | }, 904 | "funding": [ 905 | { 906 | "url": "https://github.com/sebastianbergmann", 907 | "type": "github" 908 | } 909 | ], 910 | "time": "2024-10-18T14:56:07+00:00" 911 | }, 912 | { 913 | "name": "sebastian/complexity", 914 | "version": "3.2.0", 915 | "source": { 916 | "type": "git", 917 | "url": "https://github.com/sebastianbergmann/complexity.git", 918 | "reference": "68ff824baeae169ec9f2137158ee529584553799" 919 | }, 920 | "dist": { 921 | "type": "zip", 922 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", 923 | "reference": "68ff824baeae169ec9f2137158ee529584553799", 924 | "shasum": "" 925 | }, 926 | "require": { 927 | "nikic/php-parser": "^4.18 || ^5.0", 928 | "php": ">=8.1" 929 | }, 930 | "require-dev": { 931 | "phpunit/phpunit": "^10.0" 932 | }, 933 | "type": "library", 934 | "extra": { 935 | "branch-alias": { 936 | "dev-main": "3.2-dev" 937 | } 938 | }, 939 | "autoload": { 940 | "classmap": [ 941 | "src/" 942 | ] 943 | }, 944 | "notification-url": "https://packagist.org/downloads/", 945 | "license": [ 946 | "BSD-3-Clause" 947 | ], 948 | "authors": [ 949 | { 950 | "name": "Sebastian Bergmann", 951 | "email": "sebastian@phpunit.de", 952 | "role": "lead" 953 | } 954 | ], 955 | "description": "Library for calculating the complexity of PHP code units", 956 | "homepage": "https://github.com/sebastianbergmann/complexity", 957 | "support": { 958 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 959 | "security": "https://github.com/sebastianbergmann/complexity/security/policy", 960 | "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" 961 | }, 962 | "funding": [ 963 | { 964 | "url": "https://github.com/sebastianbergmann", 965 | "type": "github" 966 | } 967 | ], 968 | "time": "2023-12-21T08:37:17+00:00" 969 | }, 970 | { 971 | "name": "sebastian/diff", 972 | "version": "5.1.1", 973 | "source": { 974 | "type": "git", 975 | "url": "https://github.com/sebastianbergmann/diff.git", 976 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" 977 | }, 978 | "dist": { 979 | "type": "zip", 980 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", 981 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", 982 | "shasum": "" 983 | }, 984 | "require": { 985 | "php": ">=8.1" 986 | }, 987 | "require-dev": { 988 | "phpunit/phpunit": "^10.0", 989 | "symfony/process": "^6.4" 990 | }, 991 | "type": "library", 992 | "extra": { 993 | "branch-alias": { 994 | "dev-main": "5.1-dev" 995 | } 996 | }, 997 | "autoload": { 998 | "classmap": [ 999 | "src/" 1000 | ] 1001 | }, 1002 | "notification-url": "https://packagist.org/downloads/", 1003 | "license": [ 1004 | "BSD-3-Clause" 1005 | ], 1006 | "authors": [ 1007 | { 1008 | "name": "Sebastian Bergmann", 1009 | "email": "sebastian@phpunit.de" 1010 | }, 1011 | { 1012 | "name": "Kore Nordmann", 1013 | "email": "mail@kore-nordmann.de" 1014 | } 1015 | ], 1016 | "description": "Diff implementation", 1017 | "homepage": "https://github.com/sebastianbergmann/diff", 1018 | "keywords": [ 1019 | "diff", 1020 | "udiff", 1021 | "unidiff", 1022 | "unified diff" 1023 | ], 1024 | "support": { 1025 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1026 | "security": "https://github.com/sebastianbergmann/diff/security/policy", 1027 | "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" 1028 | }, 1029 | "funding": [ 1030 | { 1031 | "url": "https://github.com/sebastianbergmann", 1032 | "type": "github" 1033 | } 1034 | ], 1035 | "time": "2024-03-02T07:15:17+00:00" 1036 | }, 1037 | { 1038 | "name": "sebastian/environment", 1039 | "version": "6.1.0", 1040 | "source": { 1041 | "type": "git", 1042 | "url": "https://github.com/sebastianbergmann/environment.git", 1043 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" 1044 | }, 1045 | "dist": { 1046 | "type": "zip", 1047 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", 1048 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", 1049 | "shasum": "" 1050 | }, 1051 | "require": { 1052 | "php": ">=8.1" 1053 | }, 1054 | "require-dev": { 1055 | "phpunit/phpunit": "^10.0" 1056 | }, 1057 | "suggest": { 1058 | "ext-posix": "*" 1059 | }, 1060 | "type": "library", 1061 | "extra": { 1062 | "branch-alias": { 1063 | "dev-main": "6.1-dev" 1064 | } 1065 | }, 1066 | "autoload": { 1067 | "classmap": [ 1068 | "src/" 1069 | ] 1070 | }, 1071 | "notification-url": "https://packagist.org/downloads/", 1072 | "license": [ 1073 | "BSD-3-Clause" 1074 | ], 1075 | "authors": [ 1076 | { 1077 | "name": "Sebastian Bergmann", 1078 | "email": "sebastian@phpunit.de" 1079 | } 1080 | ], 1081 | "description": "Provides functionality to handle HHVM/PHP environments", 1082 | "homepage": "https://github.com/sebastianbergmann/environment", 1083 | "keywords": [ 1084 | "Xdebug", 1085 | "environment", 1086 | "hhvm" 1087 | ], 1088 | "support": { 1089 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1090 | "security": "https://github.com/sebastianbergmann/environment/security/policy", 1091 | "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" 1092 | }, 1093 | "funding": [ 1094 | { 1095 | "url": "https://github.com/sebastianbergmann", 1096 | "type": "github" 1097 | } 1098 | ], 1099 | "time": "2024-03-23T08:47:14+00:00" 1100 | }, 1101 | { 1102 | "name": "sebastian/exporter", 1103 | "version": "5.1.2", 1104 | "source": { 1105 | "type": "git", 1106 | "url": "https://github.com/sebastianbergmann/exporter.git", 1107 | "reference": "955288482d97c19a372d3f31006ab3f37da47adf" 1108 | }, 1109 | "dist": { 1110 | "type": "zip", 1111 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", 1112 | "reference": "955288482d97c19a372d3f31006ab3f37da47adf", 1113 | "shasum": "" 1114 | }, 1115 | "require": { 1116 | "ext-mbstring": "*", 1117 | "php": ">=8.1", 1118 | "sebastian/recursion-context": "^5.0" 1119 | }, 1120 | "require-dev": { 1121 | "phpunit/phpunit": "^10.0" 1122 | }, 1123 | "type": "library", 1124 | "extra": { 1125 | "branch-alias": { 1126 | "dev-main": "5.1-dev" 1127 | } 1128 | }, 1129 | "autoload": { 1130 | "classmap": [ 1131 | "src/" 1132 | ] 1133 | }, 1134 | "notification-url": "https://packagist.org/downloads/", 1135 | "license": [ 1136 | "BSD-3-Clause" 1137 | ], 1138 | "authors": [ 1139 | { 1140 | "name": "Sebastian Bergmann", 1141 | "email": "sebastian@phpunit.de" 1142 | }, 1143 | { 1144 | "name": "Jeff Welch", 1145 | "email": "whatthejeff@gmail.com" 1146 | }, 1147 | { 1148 | "name": "Volker Dusch", 1149 | "email": "github@wallbash.com" 1150 | }, 1151 | { 1152 | "name": "Adam Harvey", 1153 | "email": "aharvey@php.net" 1154 | }, 1155 | { 1156 | "name": "Bernhard Schussek", 1157 | "email": "bschussek@gmail.com" 1158 | } 1159 | ], 1160 | "description": "Provides the functionality to export PHP variables for visualization", 1161 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1162 | "keywords": [ 1163 | "export", 1164 | "exporter" 1165 | ], 1166 | "support": { 1167 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1168 | "security": "https://github.com/sebastianbergmann/exporter/security/policy", 1169 | "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" 1170 | }, 1171 | "funding": [ 1172 | { 1173 | "url": "https://github.com/sebastianbergmann", 1174 | "type": "github" 1175 | } 1176 | ], 1177 | "time": "2024-03-02T07:17:12+00:00" 1178 | }, 1179 | { 1180 | "name": "sebastian/global-state", 1181 | "version": "6.0.2", 1182 | "source": { 1183 | "type": "git", 1184 | "url": "https://github.com/sebastianbergmann/global-state.git", 1185 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" 1186 | }, 1187 | "dist": { 1188 | "type": "zip", 1189 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", 1190 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", 1191 | "shasum": "" 1192 | }, 1193 | "require": { 1194 | "php": ">=8.1", 1195 | "sebastian/object-reflector": "^3.0", 1196 | "sebastian/recursion-context": "^5.0" 1197 | }, 1198 | "require-dev": { 1199 | "ext-dom": "*", 1200 | "phpunit/phpunit": "^10.0" 1201 | }, 1202 | "type": "library", 1203 | "extra": { 1204 | "branch-alias": { 1205 | "dev-main": "6.0-dev" 1206 | } 1207 | }, 1208 | "autoload": { 1209 | "classmap": [ 1210 | "src/" 1211 | ] 1212 | }, 1213 | "notification-url": "https://packagist.org/downloads/", 1214 | "license": [ 1215 | "BSD-3-Clause" 1216 | ], 1217 | "authors": [ 1218 | { 1219 | "name": "Sebastian Bergmann", 1220 | "email": "sebastian@phpunit.de" 1221 | } 1222 | ], 1223 | "description": "Snapshotting of global state", 1224 | "homepage": "https://www.github.com/sebastianbergmann/global-state", 1225 | "keywords": [ 1226 | "global state" 1227 | ], 1228 | "support": { 1229 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1230 | "security": "https://github.com/sebastianbergmann/global-state/security/policy", 1231 | "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" 1232 | }, 1233 | "funding": [ 1234 | { 1235 | "url": "https://github.com/sebastianbergmann", 1236 | "type": "github" 1237 | } 1238 | ], 1239 | "time": "2024-03-02T07:19:19+00:00" 1240 | }, 1241 | { 1242 | "name": "sebastian/lines-of-code", 1243 | "version": "2.0.2", 1244 | "source": { 1245 | "type": "git", 1246 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1247 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" 1248 | }, 1249 | "dist": { 1250 | "type": "zip", 1251 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", 1252 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", 1253 | "shasum": "" 1254 | }, 1255 | "require": { 1256 | "nikic/php-parser": "^4.18 || ^5.0", 1257 | "php": ">=8.1" 1258 | }, 1259 | "require-dev": { 1260 | "phpunit/phpunit": "^10.0" 1261 | }, 1262 | "type": "library", 1263 | "extra": { 1264 | "branch-alias": { 1265 | "dev-main": "2.0-dev" 1266 | } 1267 | }, 1268 | "autoload": { 1269 | "classmap": [ 1270 | "src/" 1271 | ] 1272 | }, 1273 | "notification-url": "https://packagist.org/downloads/", 1274 | "license": [ 1275 | "BSD-3-Clause" 1276 | ], 1277 | "authors": [ 1278 | { 1279 | "name": "Sebastian Bergmann", 1280 | "email": "sebastian@phpunit.de", 1281 | "role": "lead" 1282 | } 1283 | ], 1284 | "description": "Library for counting the lines of code in PHP source code", 1285 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1286 | "support": { 1287 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1288 | "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", 1289 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" 1290 | }, 1291 | "funding": [ 1292 | { 1293 | "url": "https://github.com/sebastianbergmann", 1294 | "type": "github" 1295 | } 1296 | ], 1297 | "time": "2023-12-21T08:38:20+00:00" 1298 | }, 1299 | { 1300 | "name": "sebastian/object-enumerator", 1301 | "version": "5.0.0", 1302 | "source": { 1303 | "type": "git", 1304 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1305 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" 1306 | }, 1307 | "dist": { 1308 | "type": "zip", 1309 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", 1310 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", 1311 | "shasum": "" 1312 | }, 1313 | "require": { 1314 | "php": ">=8.1", 1315 | "sebastian/object-reflector": "^3.0", 1316 | "sebastian/recursion-context": "^5.0" 1317 | }, 1318 | "require-dev": { 1319 | "phpunit/phpunit": "^10.0" 1320 | }, 1321 | "type": "library", 1322 | "extra": { 1323 | "branch-alias": { 1324 | "dev-main": "5.0-dev" 1325 | } 1326 | }, 1327 | "autoload": { 1328 | "classmap": [ 1329 | "src/" 1330 | ] 1331 | }, 1332 | "notification-url": "https://packagist.org/downloads/", 1333 | "license": [ 1334 | "BSD-3-Clause" 1335 | ], 1336 | "authors": [ 1337 | { 1338 | "name": "Sebastian Bergmann", 1339 | "email": "sebastian@phpunit.de" 1340 | } 1341 | ], 1342 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1343 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1344 | "support": { 1345 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1346 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" 1347 | }, 1348 | "funding": [ 1349 | { 1350 | "url": "https://github.com/sebastianbergmann", 1351 | "type": "github" 1352 | } 1353 | ], 1354 | "time": "2023-02-03T07:08:32+00:00" 1355 | }, 1356 | { 1357 | "name": "sebastian/object-reflector", 1358 | "version": "3.0.0", 1359 | "source": { 1360 | "type": "git", 1361 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1362 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" 1363 | }, 1364 | "dist": { 1365 | "type": "zip", 1366 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", 1367 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", 1368 | "shasum": "" 1369 | }, 1370 | "require": { 1371 | "php": ">=8.1" 1372 | }, 1373 | "require-dev": { 1374 | "phpunit/phpunit": "^10.0" 1375 | }, 1376 | "type": "library", 1377 | "extra": { 1378 | "branch-alias": { 1379 | "dev-main": "3.0-dev" 1380 | } 1381 | }, 1382 | "autoload": { 1383 | "classmap": [ 1384 | "src/" 1385 | ] 1386 | }, 1387 | "notification-url": "https://packagist.org/downloads/", 1388 | "license": [ 1389 | "BSD-3-Clause" 1390 | ], 1391 | "authors": [ 1392 | { 1393 | "name": "Sebastian Bergmann", 1394 | "email": "sebastian@phpunit.de" 1395 | } 1396 | ], 1397 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1398 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1399 | "support": { 1400 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1401 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" 1402 | }, 1403 | "funding": [ 1404 | { 1405 | "url": "https://github.com/sebastianbergmann", 1406 | "type": "github" 1407 | } 1408 | ], 1409 | "time": "2023-02-03T07:06:18+00:00" 1410 | }, 1411 | { 1412 | "name": "sebastian/recursion-context", 1413 | "version": "5.0.0", 1414 | "source": { 1415 | "type": "git", 1416 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1417 | "reference": "05909fb5bc7df4c52992396d0116aed689f93712" 1418 | }, 1419 | "dist": { 1420 | "type": "zip", 1421 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", 1422 | "reference": "05909fb5bc7df4c52992396d0116aed689f93712", 1423 | "shasum": "" 1424 | }, 1425 | "require": { 1426 | "php": ">=8.1" 1427 | }, 1428 | "require-dev": { 1429 | "phpunit/phpunit": "^10.0" 1430 | }, 1431 | "type": "library", 1432 | "extra": { 1433 | "branch-alias": { 1434 | "dev-main": "5.0-dev" 1435 | } 1436 | }, 1437 | "autoload": { 1438 | "classmap": [ 1439 | "src/" 1440 | ] 1441 | }, 1442 | "notification-url": "https://packagist.org/downloads/", 1443 | "license": [ 1444 | "BSD-3-Clause" 1445 | ], 1446 | "authors": [ 1447 | { 1448 | "name": "Sebastian Bergmann", 1449 | "email": "sebastian@phpunit.de" 1450 | }, 1451 | { 1452 | "name": "Jeff Welch", 1453 | "email": "whatthejeff@gmail.com" 1454 | }, 1455 | { 1456 | "name": "Adam Harvey", 1457 | "email": "aharvey@php.net" 1458 | } 1459 | ], 1460 | "description": "Provides functionality to recursively process PHP variables", 1461 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1462 | "support": { 1463 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1464 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" 1465 | }, 1466 | "funding": [ 1467 | { 1468 | "url": "https://github.com/sebastianbergmann", 1469 | "type": "github" 1470 | } 1471 | ], 1472 | "time": "2023-02-03T07:05:40+00:00" 1473 | }, 1474 | { 1475 | "name": "sebastian/type", 1476 | "version": "4.0.0", 1477 | "source": { 1478 | "type": "git", 1479 | "url": "https://github.com/sebastianbergmann/type.git", 1480 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" 1481 | }, 1482 | "dist": { 1483 | "type": "zip", 1484 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", 1485 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", 1486 | "shasum": "" 1487 | }, 1488 | "require": { 1489 | "php": ">=8.1" 1490 | }, 1491 | "require-dev": { 1492 | "phpunit/phpunit": "^10.0" 1493 | }, 1494 | "type": "library", 1495 | "extra": { 1496 | "branch-alias": { 1497 | "dev-main": "4.0-dev" 1498 | } 1499 | }, 1500 | "autoload": { 1501 | "classmap": [ 1502 | "src/" 1503 | ] 1504 | }, 1505 | "notification-url": "https://packagist.org/downloads/", 1506 | "license": [ 1507 | "BSD-3-Clause" 1508 | ], 1509 | "authors": [ 1510 | { 1511 | "name": "Sebastian Bergmann", 1512 | "email": "sebastian@phpunit.de", 1513 | "role": "lead" 1514 | } 1515 | ], 1516 | "description": "Collection of value objects that represent the types of the PHP type system", 1517 | "homepage": "https://github.com/sebastianbergmann/type", 1518 | "support": { 1519 | "issues": "https://github.com/sebastianbergmann/type/issues", 1520 | "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" 1521 | }, 1522 | "funding": [ 1523 | { 1524 | "url": "https://github.com/sebastianbergmann", 1525 | "type": "github" 1526 | } 1527 | ], 1528 | "time": "2023-02-03T07:10:45+00:00" 1529 | }, 1530 | { 1531 | "name": "sebastian/version", 1532 | "version": "4.0.1", 1533 | "source": { 1534 | "type": "git", 1535 | "url": "https://github.com/sebastianbergmann/version.git", 1536 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" 1537 | }, 1538 | "dist": { 1539 | "type": "zip", 1540 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", 1541 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", 1542 | "shasum": "" 1543 | }, 1544 | "require": { 1545 | "php": ">=8.1" 1546 | }, 1547 | "type": "library", 1548 | "extra": { 1549 | "branch-alias": { 1550 | "dev-main": "4.0-dev" 1551 | } 1552 | }, 1553 | "autoload": { 1554 | "classmap": [ 1555 | "src/" 1556 | ] 1557 | }, 1558 | "notification-url": "https://packagist.org/downloads/", 1559 | "license": [ 1560 | "BSD-3-Clause" 1561 | ], 1562 | "authors": [ 1563 | { 1564 | "name": "Sebastian Bergmann", 1565 | "email": "sebastian@phpunit.de", 1566 | "role": "lead" 1567 | } 1568 | ], 1569 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1570 | "homepage": "https://github.com/sebastianbergmann/version", 1571 | "support": { 1572 | "issues": "https://github.com/sebastianbergmann/version/issues", 1573 | "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" 1574 | }, 1575 | "funding": [ 1576 | { 1577 | "url": "https://github.com/sebastianbergmann", 1578 | "type": "github" 1579 | } 1580 | ], 1581 | "time": "2023-02-07T11:34:05+00:00" 1582 | }, 1583 | { 1584 | "name": "squizlabs/php_codesniffer", 1585 | "version": "3.12.1", 1586 | "source": { 1587 | "type": "git", 1588 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", 1589 | "reference": "ea16a1f3719783345febd3aab41beb55c8c84bfd" 1590 | }, 1591 | "dist": { 1592 | "type": "zip", 1593 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ea16a1f3719783345febd3aab41beb55c8c84bfd", 1594 | "reference": "ea16a1f3719783345febd3aab41beb55c8c84bfd", 1595 | "shasum": "" 1596 | }, 1597 | "require": { 1598 | "ext-simplexml": "*", 1599 | "ext-tokenizer": "*", 1600 | "ext-xmlwriter": "*", 1601 | "php": ">=5.4.0" 1602 | }, 1603 | "require-dev": { 1604 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" 1605 | }, 1606 | "bin": [ 1607 | "bin/phpcbf", 1608 | "bin/phpcs" 1609 | ], 1610 | "type": "library", 1611 | "extra": { 1612 | "branch-alias": { 1613 | "dev-master": "3.x-dev" 1614 | } 1615 | }, 1616 | "notification-url": "https://packagist.org/downloads/", 1617 | "license": [ 1618 | "BSD-3-Clause" 1619 | ], 1620 | "authors": [ 1621 | { 1622 | "name": "Greg Sherwood", 1623 | "role": "Former lead" 1624 | }, 1625 | { 1626 | "name": "Juliette Reinders Folmer", 1627 | "role": "Current lead" 1628 | }, 1629 | { 1630 | "name": "Contributors", 1631 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" 1632 | } 1633 | ], 1634 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 1635 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 1636 | "keywords": [ 1637 | "phpcs", 1638 | "standards", 1639 | "static analysis" 1640 | ], 1641 | "support": { 1642 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", 1643 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", 1644 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 1645 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" 1646 | }, 1647 | "funding": [ 1648 | { 1649 | "url": "https://github.com/PHPCSStandards", 1650 | "type": "github" 1651 | }, 1652 | { 1653 | "url": "https://github.com/jrfnl", 1654 | "type": "github" 1655 | }, 1656 | { 1657 | "url": "https://opencollective.com/php_codesniffer", 1658 | "type": "open_collective" 1659 | }, 1660 | { 1661 | "url": "https://thanks.dev/u/gh/phpcsstandards", 1662 | "type": "thanks_dev" 1663 | } 1664 | ], 1665 | "time": "2025-04-04T12:57:55+00:00" 1666 | }, 1667 | { 1668 | "name": "theseer/tokenizer", 1669 | "version": "1.2.3", 1670 | "source": { 1671 | "type": "git", 1672 | "url": "https://github.com/theseer/tokenizer.git", 1673 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" 1674 | }, 1675 | "dist": { 1676 | "type": "zip", 1677 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", 1678 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", 1679 | "shasum": "" 1680 | }, 1681 | "require": { 1682 | "ext-dom": "*", 1683 | "ext-tokenizer": "*", 1684 | "ext-xmlwriter": "*", 1685 | "php": "^7.2 || ^8.0" 1686 | }, 1687 | "type": "library", 1688 | "autoload": { 1689 | "classmap": [ 1690 | "src/" 1691 | ] 1692 | }, 1693 | "notification-url": "https://packagist.org/downloads/", 1694 | "license": [ 1695 | "BSD-3-Clause" 1696 | ], 1697 | "authors": [ 1698 | { 1699 | "name": "Arne Blankerts", 1700 | "email": "arne@blankerts.de", 1701 | "role": "Developer" 1702 | } 1703 | ], 1704 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1705 | "support": { 1706 | "issues": "https://github.com/theseer/tokenizer/issues", 1707 | "source": "https://github.com/theseer/tokenizer/tree/1.2.3" 1708 | }, 1709 | "funding": [ 1710 | { 1711 | "url": "https://github.com/theseer", 1712 | "type": "github" 1713 | } 1714 | ], 1715 | "time": "2024-03-03T12:36:25+00:00" 1716 | } 1717 | ], 1718 | "aliases": [], 1719 | "minimum-stability": "stable", 1720 | "stability-flags": [], 1721 | "prefer-stable": false, 1722 | "prefer-lowest": false, 1723 | "platform": { 1724 | "php": ">=7.4" 1725 | }, 1726 | "platform-dev": [], 1727 | "plugin-api-version": "2.6.0" 1728 | } 1729 | -------------------------------------------------------------------------------- /generate-since-json.php: -------------------------------------------------------------------------------- 1 | create(ParserFactory::PREFER_PHP7); 39 | $result = []; 40 | 41 | class SinceExtractor extends NodeVisitorAbstract 42 | { 43 | private $file; 44 | private $result; 45 | 46 | public function __construct($file, &$result) 47 | { 48 | $this->file = $file; 49 | $this->result = &$result; 50 | } 51 | 52 | public function enterNode(Node $node) 53 | { 54 | $doc = $node->getDocComment(); 55 | $docText = $doc ? $doc->getText() : null; 56 | $since = $this->extractTag($docText, '@since'); 57 | $deprecated = $this->extractTag($docText, '@deprecated'); 58 | 59 | if ($node instanceof Node\Stmt\Function_) { 60 | $this->addResult($node->name->toString(), 'function', $since, $deprecated); 61 | } elseif ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Interface_ || $node instanceof Node\Stmt\Trait_) { 62 | $type = $node instanceof Node\Stmt\Class_ ? 'class' : ($node instanceof Node\Stmt\Interface_ ? 'interface' : 'trait'); 63 | $this->addResult($node->name->toString(), $type, $since, $deprecated); 64 | } elseif ($node instanceof Node\Stmt\ClassMethod && !$node->isPrivate()) { 65 | $class = $node->getAttribute('parent'); 66 | $className = $class instanceof Node\Stmt\Class_ && $class->name ? $class->name->toString() : 'Anonymous'; 67 | $methodName = $node->name->toString(); 68 | $methodSince = $since ?: ($class && $class->getDocComment() ? $this->extractTag($class->getDocComment()->getText(), '@since') : null); 69 | if ($className !== 'Anonymous' && $methodSince) { 70 | $this->addResult("$className::$methodName", 'method', $methodSince, $deprecated); 71 | } 72 | } elseif ( 73 | $node instanceof Node\Expr\FuncCall && 74 | $node->name instanceof Node\Name && 75 | in_array($node->name->toString(), ['do_action', 'apply_filters'], true) 76 | ) { 77 | $hookNameNode = $node->args[0]->value ?? null; 78 | if ($hookNameNode instanceof Node\Scalar\String_) { 79 | $hookName = $hookNameNode->value; 80 | $this->addResult($hookName, 'hook', $since, $deprecated); 81 | } 82 | } 83 | } 84 | 85 | private function extractTag($docText, $tag) 86 | { 87 | if ($docText && preg_match('/' . preg_quote($tag) . '\s+([0-9.]+)/', $docText, $matches)) { 88 | $version = $matches[1]; 89 | if ($version === 'MU') return '3.0.0'; 90 | if (preg_match('/^\d+\.\d+(\.\d+)?$/', $version)) return $version; 91 | } 92 | return null; 93 | } 94 | 95 | private function addResult($name, $type, $since, $deprecated) 96 | { 97 | if ($since) { 98 | $this->result[$name] = array_filter([ 99 | 'type' => $type, 100 | 'since' => $since, 101 | 'deprecated' => $deprecated, 102 | 'file' => $this->file 103 | ]); 104 | } 105 | } 106 | } 107 | 108 | $rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($sourceDir)); 109 | 110 | foreach ($rii as $file) { 111 | if ($file->isDir() || $file->getExtension() !== 'php') continue; 112 | $relativePath = str_replace($sourceDir . '/', '', $file->getPathname()); 113 | 114 | foreach ($excludedPaths as $excluded) { 115 | if (strpos($relativePath, $excluded) === 0) continue 2; 116 | } 117 | 118 | try { 119 | $code = file_get_contents($file->getPathname()); 120 | $ast = $parser->parse($code); 121 | 122 | $traverser = new NodeTraverser(); 123 | $traverser->addVisitor(new ParentConnectingVisitor()); 124 | $traverser->addVisitor(new SinceExtractor($relativePath, $result)); 125 | $traverser->traverse($ast); 126 | 127 | } catch (Error $e) { 128 | echo "Processing error {$relativePath}: {$e->getMessage()}\n"; 129 | } 130 | } 131 | 132 | file_put_contents($outputPath, json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 133 | echo "✅ File generated in: {$outputPath}\n"; 134 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PHP_CodeSniffer configuration for WP-Since 4 | 5 | 6 | 7 | 8 | 9 | src 10 | tests 11 | 12 | 13 | vendor/* 14 | tests/fixtures/* 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Checker/CompatibilityChecker.php: -------------------------------------------------------------------------------- 1 | sinceMap = $sinceMap; 14 | } 15 | 16 | public function check(array $symbols, string $declaredVersion): array 17 | { 18 | $incompatible = []; 19 | 20 | foreach ($symbols as $symbol) { 21 | if ( 22 | isset($this->sinceMap[$symbol]) && 23 | VersionHelper::compare($declaredVersion, $this->sinceMap[$symbol]['since']) < 0 24 | ) { 25 | $incompatible[$symbol] = $this->sinceMap[$symbol]['since']; 26 | } 27 | } 28 | 29 | return $incompatible; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Resolver/IgnoreRulesResolver.php: -------------------------------------------------------------------------------- 1 | $line !== '' && $line[0] !== '#'); 29 | } 30 | 31 | private static function parseGitAttributes(string $file): array 32 | { 33 | $lines = file($file); 34 | $ignores = []; 35 | 36 | foreach ($lines as $line) { 37 | if (strpos($line, 'export-ignore') !== false) { 38 | $parts = preg_split('/\s+/', trim($line)); 39 | if (!empty($parts[0])) { 40 | $ignores[] = $parts[0]; 41 | } 42 | } 43 | } 44 | 45 | return $ignores; 46 | } 47 | 48 | public static function shouldIgnore(string $relativePath, array $ignorePaths): bool 49 | { 50 | foreach ($ignorePaths as $ignored) { 51 | $normalized = ltrim($ignored, '/'); 52 | if ( 53 | $relativePath === $normalized || 54 | str_starts_with($relativePath, rtrim($normalized, '/') . '/') 55 | ) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Resolver/InlineIgnoreResolver.php: -------------------------------------------------------------------------------- 1 | ['startLine']]); 12 | $lexer->startLexing($code); 13 | 14 | $ignoredLines = []; 15 | 16 | foreach ($lexer->getTokens() as $token) { 17 | if (is_array($token) && in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) { 18 | if (strpos($token[1], '@wp-since ignore') !== false) { 19 | $ignoredLines[] = $token[2]; 20 | } 21 | } 22 | } 23 | 24 | return $ignoredLines; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Resolver/VersionResolver.php: -------------------------------------------------------------------------------- 1 | $version, 'source' => 'main plugin file header']; 15 | } 16 | } 17 | 18 | $readme = self::findReadmeFile($pluginPath); 19 | if ($readme) { 20 | $version = self::extractVersionFromReadme($readme); 21 | if ($version) { 22 | return ['version' => $version, 'source' => 'readme']; 23 | } 24 | } 25 | 26 | return null; 27 | } 28 | 29 | private static function findMainPluginFile(string $pluginPath): ?string 30 | { 31 | $basename = basename($pluginPath); 32 | $candidate = "{$pluginPath}/{$basename}.php"; 33 | if (file_exists($candidate)) { 34 | return $candidate; 35 | } 36 | 37 | return null; 38 | } 39 | 40 | private static function extractVersionFromPluginHeader(string $file): ?string 41 | { 42 | $contents = file_get_contents($file); 43 | if (!$contents) { 44 | return null; 45 | } 46 | 47 | if (preg_match('/^\s*\*\s+Requires at least:\s*([0-9.]+)/mi', $contents, $matches)) { 48 | return $matches[1]; 49 | } 50 | 51 | return null; 52 | } 53 | 54 | private static function findReadmeFile(string $pluginPath): ?string 55 | { 56 | foreach (['readme.txt', 'README.txt'] as $filename) { 57 | $full = "{$pluginPath}/{$filename}"; 58 | if (file_exists($full)) { 59 | return $full; 60 | } 61 | } 62 | return null; 63 | } 64 | 65 | private static function extractVersionFromReadme(string $file): ?string 66 | { 67 | $lines = file($file); 68 | foreach ($lines as $line) { 69 | if (stripos($line, 'Requires at least:') === 0) { 70 | if (preg_match('/Requires at least:\s*([0-9.]+)/i', $line, $matches)) { 71 | return $matches[1]; 72 | } 73 | } 74 | } 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Runner/PluginCheckCommand.php: -------------------------------------------------------------------------------- 1 | check($usedSymbols, $declaredVersion); 38 | 39 | if (count($incompatible)) { 40 | echo "🚨 Compatibility issues found:\n\n"; 41 | $rows = []; 42 | foreach ($incompatible as $symbol => $version) { 43 | $rows[] = [$symbol, $version]; 44 | } 45 | TablePrinter::render($rows, ['Symbol', 'Introduced in WP']); 46 | 47 | $versions = array_values($incompatible); 48 | $maxVersion = array_reduce($versions, function ($carry, $v) { 49 | return VersionHelper::compare($carry, $v) < 0 ? $v : $carry; 50 | }, $declaredVersion); 51 | 52 | echo "📌 Suggested version required: {$maxVersion}\n"; 53 | return 1; 54 | } 55 | 56 | echo "✅ All good! Your plugin is compatible with WP {$declaredVersion}.\n"; 57 | return 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Scanner/PluginScanner.php: -------------------------------------------------------------------------------- 1 | create(ParserFactory::PREFER_PHP7); 17 | $traverser = new NodeTraverser(); 18 | 19 | $usedSymbols = []; 20 | $varMap = []; 21 | 22 | $traverser->addVisitor(new ParentConnectingVisitor()); 23 | 24 | $ignorePaths = IgnoreRulesResolver::getIgnoredPaths($path); 25 | $rii = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); 26 | foreach ($rii as $file) { 27 | $relativePath = str_replace($path . '/', '', $file->getPathname()); 28 | 29 | if ( 30 | $file->isDir() || 31 | $file->getExtension() !== 'php' || 32 | IgnoreRulesResolver::shouldIgnore($relativePath, $ignorePaths) 33 | ) { 34 | continue; 35 | } 36 | 37 | $code = file_get_contents($file->getPathname()); 38 | $ignoredLines = InlineIgnoreResolver::extractIgnoredLines($code); 39 | 40 | $visitor = new SymbolExtractorVisitor($usedSymbols, $varMap, $ignoredLines); 41 | $traverser->addVisitor($visitor); 42 | 43 | try { 44 | $stmts = $parser->parse($code); 45 | $traverser->traverse($stmts); 46 | } catch (\Exception $e) { 47 | // Add error handling 48 | } 49 | 50 | $traverser->removeVisitor($visitor); 51 | } 52 | 53 | return array_unique($usedSymbols); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Scanner/SymbolExtractorVisitor.php: -------------------------------------------------------------------------------- 1 | usedSymbols = &$usedSymbols; 21 | $this->varMap = &$varMap; 22 | $this->ignoredLines = $ignoredLines; 23 | 24 | $this->handlers = [ 25 | new SymbolHandlers\FunctionCallHandler(), 26 | new SymbolHandlers\NewClassHandler(), 27 | new SymbolHandlers\StaticCallHandler(), 28 | new SymbolHandlers\MethodCallHandler(), 29 | ]; 30 | } 31 | 32 | public function enterNode(Node $node) 33 | { 34 | if (in_array($node->getStartLine(), $this->ignoredLines, true)) { 35 | return null; 36 | } 37 | 38 | foreach ($this->handlers as $handler) { 39 | if ($handler->supports($node)) { 40 | $symbols = $handler->extract($node, $this->varMap); 41 | foreach ($symbols as $symbol) { 42 | $this->usedSymbols[] = $symbol; 43 | } 44 | break; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Scanner/SymbolHandlers/FunctionCallHandler.php: -------------------------------------------------------------------------------- 1 | name instanceof Node\Name; 12 | } 13 | 14 | public function extract(Node $node, array &$varMap = []): array 15 | { 16 | $symbols = [(string) $node->name]; 17 | 18 | if (in_array((string) $node->name, ['do_action', 'apply_filters'], true)) { 19 | $hookNameNode = $node->args[0]->value ?? null; 20 | if ($hookNameNode instanceof Node\Scalar\String_) { 21 | $symbols[] = $hookNameNode->value; 22 | } 23 | } 24 | 25 | return $symbols; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Scanner/SymbolHandlers/MethodCallHandler.php: -------------------------------------------------------------------------------- 1 | var instanceof Node\Expr\Variable && 13 | $node->name instanceof Node\Identifier; 14 | } 15 | 16 | public function extract(Node $node, array &$varMap = []): array 17 | { 18 | $varName = $node->var->name; 19 | $method = (string) $node->name; 20 | 21 | if (is_string($varName) && isset($varMap[$varName])) { 22 | $class = $varMap[$varName]; 23 | return ["$class::$method"]; 24 | } 25 | 26 | return []; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Scanner/SymbolHandlers/NewClassHandler.php: -------------------------------------------------------------------------------- 1 | class instanceof Node\Name; 12 | } 13 | 14 | public function extract(Node $node, array &$varMap = []): array 15 | { 16 | $symbols = [(string) $node->class]; 17 | 18 | $parent = $node->getAttribute('parent'); 19 | if ( 20 | $parent instanceof Node\Expr\Assign && 21 | $parent->var instanceof Node\Expr\Variable && 22 | is_string($parent->var->name) 23 | ) { 24 | $varMap[$parent->var->name] = (string) $node->class; 25 | } 26 | 27 | return $symbols; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Scanner/SymbolHandlers/StaticCallHandler.php: -------------------------------------------------------------------------------- 1 | class instanceof Node\Name && 13 | $node->name instanceof Node\Identifier; 14 | } 15 | 16 | public function extract(Node $node, array &$varMap = []): array 17 | { 18 | $class = (string) $node->class; 19 | $method = (string) $node->name; 20 | return ["$class::$method"]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Scanner/SymbolHandlers/SymbolHandlerInterface.php: -------------------------------------------------------------------------------- 1 | $header) { 11 | $widths[$i] = strlen($header); 12 | } 13 | 14 | foreach ($rows as $row) { 15 | foreach ($row as $i => $cell) { 16 | $widths[$i] = max($widths[$i], strlen($cell)); 17 | } 18 | } 19 | 20 | $drawLine = function ($left, $middle, $right, $fill = '─') use ($widths) { 21 | echo $left; 22 | foreach ($widths as $i => $w) { 23 | echo str_repeat($fill, $w + 2); 24 | echo $i < count($widths) - 1 ? $middle : $right; 25 | } 26 | echo "\n"; 27 | }; 28 | 29 | $drawRow = function ($row, $sep = '│') use ($widths) { 30 | echo $sep; 31 | foreach ($row as $i => $cell) { 32 | echo ' ' . str_pad($cell, $widths[$i]) . ' ' . $sep; 33 | } 34 | echo "\n"; 35 | }; 36 | 37 | $drawLine('┌', '┬', '┐'); 38 | $drawRow($headers); 39 | $drawLine('├', '┼', '┤'); 40 | foreach ($rows as $row) { 41 | $drawRow($row); 42 | } 43 | $drawLine('└', '┴', '┘'); 44 | echo "\n"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Utils/VersionHelper.php: -------------------------------------------------------------------------------- 1 | B 25 | */ 26 | public static function compare(string $versionA, string $versionB): int 27 | { 28 | return version_compare( 29 | self::normalize($versionA), 30 | self::normalize($versionB) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/CompatibilityCheckerTest.php: -------------------------------------------------------------------------------- 1 | ['since' => '5.5.0'], 14 | 'WP_Query' => ['since' => '3.0.0'], 15 | 'MyClass::boot' => ['since' => '6.2.0'], 16 | 'custom_hook' => ['since' => '6.0.0'], 17 | ]; 18 | 19 | $symbols = [ 20 | 'register_setting', 21 | 'WP_Query', 22 | 'MyClass::boot', 23 | 'custom_hook', 24 | ]; 25 | 26 | $declaredVersion = '5.5'; 27 | 28 | $checker = new CompatibilityChecker($sinceMap); 29 | $incompatible = $checker->check($symbols, $declaredVersion); 30 | 31 | $this->assertArrayHasKey('MyClass::boot', $incompatible); 32 | $this->assertArrayHasKey('custom_hook', $incompatible); 33 | $this->assertArrayNotHasKey('register_setting', $incompatible); 34 | $this->assertArrayNotHasKey('WP_Query', $incompatible); 35 | $this->assertEquals('6.2.0', $incompatible['MyClass::boot']); 36 | } 37 | 38 | public function testAllSymbolsCompatible() 39 | { 40 | $sinceMap = [ 41 | 'function_one' => ['since' => '5.1.0'], 42 | 'function_two' => ['since' => '5.0.0'], 43 | ]; 44 | 45 | $symbols = ['function_one', 'function_two']; 46 | $declaredVersion = '5.5.0'; 47 | 48 | $checker = new CompatibilityChecker($sinceMap); 49 | $incompatible = $checker->check($symbols, $declaredVersion); 50 | 51 | $this->assertEmpty($incompatible); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Integration/FullCompatibilityFlowTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($declaredVersion, 'Declared version should not be null'); 20 | $this->assertEquals('5.5', $declaredVersion['version']); 21 | 22 | $sinceMap = [ 23 | 'add_option' => ['since' => '2.0.0'], 24 | 'WP_Query' => ['since' => '3.0.0'], 25 | 'WP_Filesystem::get_contents' => ['since' => '5.1.0'], 26 | 'WP_User::add_cap' => ['since' => '5.7.0'], 27 | 'my_custom_hook' => ['since' => '6.0.0'], 28 | 'my_filter_hook' => ['since' => '5.3.0'], 29 | ]; 30 | 31 | $checker = new CompatibilityChecker($sinceMap); 32 | $incompatible = $checker->check($symbols, $declaredVersion['version']); 33 | 34 | $this->assertArrayHasKey('WP_User::add_cap', $incompatible); 35 | $this->assertArrayHasKey('my_custom_hook', $incompatible); 36 | 37 | $this->assertArrayNotHasKey('add_option', $incompatible); 38 | $this->assertArrayNotHasKey('WP_Query', $incompatible); 39 | $this->assertArrayNotHasKey('WP_Filesystem::get_contents', $incompatible); 40 | $this->assertArrayNotHasKey('my_filter_hook', $incompatible); 41 | 42 | $expected = array_keys($sinceMap); 43 | foreach ($expected as $symbol) { 44 | $this->assertContains($symbol, $symbols, "Missing symbol: {$symbol}"); 45 | 46 | // phpcs:disable Generic.Files.LineLength.TooLong 47 | $this->assertNotContains('some_ignored_func_folder', $symbols, 'Should ignore folder from /ignored-folder/'); 48 | $this->assertNotContains('some_ignored_func_file', $symbols, 'Should ignore specific file /ignore-this.php'); 49 | $this->assertNotContains('some_ignored_func_noslash', $symbols, 'Should ignore folder from ignored-no-slash/'); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/PluginScannerTest.php: -------------------------------------------------------------------------------- 1 | assertContains($symbol, $symbols, "Missing: $symbol"); 26 | } 27 | } 28 | 29 | public function testIgnoresSymbolsMarkedWithIgnoreComment() 30 | { 31 | $path = __DIR__ . '/fixtures/plugin-ignore-comment'; 32 | $symbols = PluginScanner::scan($path); 33 | 34 | $this->assertNotContains('add_option', $symbols, 'Should ignore symbol with @wp-since ignore'); 35 | $this->assertNotContains('should_be_ignored', $symbols, 'Should ignore symbolwith @wp-since ignore'); 36 | $this->assertNotContains('wp_is_block_theme', $symbols, 'Should ignore symbol with @wp-since ignore'); 37 | $this->assertNotContains('should_be_ignored_space', $symbols, 'Should ignore symbol with @wp-since ignore'); 38 | $this->assertContains('do_action', $symbols, 'Should detect function call without ignore comment'); 39 | $this->assertContains('my_custom_hook', $symbols, 'Should detect function call without ignore comment'); 40 | $this->assertContains('register_setting', $symbols, 'Should detect function call without ignore comment'); 41 | $this->assertContains('wp_detected_function', $symbols, 'Should detect function call without ignore comment'); 42 | $this->assertContains('need_detect', $symbols, 'Should detect function call without ignore comment'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Resolver/IgnoreRulesResolverTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(IgnoreRulesResolver::shouldIgnore('tests/helper.php', $ignorePaths)); 15 | $this->assertTrue(IgnoreRulesResolver::shouldIgnore('assets/img/logo.png', $ignorePaths)); 16 | $this->assertTrue(IgnoreRulesResolver::shouldIgnore('example.php', $ignorePaths)); 17 | } 18 | 19 | public function testShouldNotIgnoreNonMatchingPath() 20 | { 21 | $ignorePaths = ['tests/', 'vendor/', 'docs/readme.txt']; 22 | 23 | $this->assertFalse(IgnoreRulesResolver::shouldIgnore('src/Plugin.php', $ignorePaths)); 24 | $this->assertFalse(IgnoreRulesResolver::shouldIgnore('includes/functions.php', $ignorePaths)); 25 | } 26 | 27 | public function testShouldIgnoreNestedPaths() 28 | { 29 | $ignorePaths = ['admin/']; 30 | 31 | $this->assertTrue(IgnoreRulesResolver::shouldIgnore('admin/settings/page.php', $ignorePaths)); 32 | $this->assertTrue(IgnoreRulesResolver::shouldIgnore('admin/page.php', $ignorePaths)); 33 | } 34 | 35 | public function testShouldIgnoreWithOrWithoutLeadingSlash() 36 | { 37 | $ignorePaths = ['/build/', 'temp/']; 38 | 39 | $this->assertTrue(IgnoreRulesResolver::shouldIgnore('build/bundle.js', $ignorePaths)); 40 | $this->assertTrue(IgnoreRulesResolver::shouldIgnore('temp/cache.php', $ignorePaths)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/TablePrinterTest.php: -------------------------------------------------------------------------------- 1 | assertStringContainsString('register_setting', $output); 23 | $this->assertStringContainsString('some_function', $output); 24 | $this->assertStringContainsString('┌', $output); 25 | $this->assertStringContainsString('┴', $output); 26 | $this->assertStringContainsString('│ Name', $output); 27 | $this->assertStringContainsString('│ Version', $output); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/VersionHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('5.5.0', VersionHelper::normalize('5.5')); 13 | $this->assertEquals('5.0.0', VersionHelper::normalize('5')); 14 | $this->assertEquals('6.1.2', VersionHelper::normalize('6.1.2')); 15 | } 16 | 17 | public function testCompareVersions() 18 | { 19 | $this->assertSame(0, VersionHelper::compare('5.5', '5.5.0')); 20 | $this->assertSame(0, VersionHelper::compare('6.1.0', '6.1')); 21 | $this->assertSame(1, VersionHelper::compare('6.2', '6.1.5')); 22 | $this->assertSame(-1, VersionHelper::compare('5.9', '6.0')); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/VersionResolverTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('6.2', $resolved['version']); 16 | $this->assertEquals('main plugin file header', $resolved['source']); 17 | } 18 | 19 | public function testExtractsVersionFromReadmeIfNoHeader() 20 | { 21 | $path = __DIR__ . '/fixtures/plugin-with-readme-only'; 22 | $resolved = VersionResolver::resolve($path); 23 | 24 | $this->assertEquals('5.8', $resolved['version']); 25 | $this->assertEquals('readme', $resolved['source']); 26 | } 27 | 28 | public function testReturnsNullIfVersionNotFoundAnywhere() 29 | { 30 | $path = __DIR__ . '/fixtures/plugin-without-version'; 31 | $resolved = VersionResolver::resolve($path); 32 | 33 | $this->assertNull($resolved); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/fixtures/file-a.php: -------------------------------------------------------------------------------- 1 | add_cap('edit_posts'); 26 | 27 | do_action('my_custom_hook', 'param'); 28 | apply_filters('my_filter_hook', 'value'); 29 | -------------------------------------------------------------------------------- /tests/fixtures/plugin-full-test/readme.txt: -------------------------------------------------------------------------------- 1 | === Test Plugin === 2 | Contributors: evcode 3 | Donate link: https://eduardovillao.me/ 4 | Tags: delivery, wordpress delivery, delivery whatsapp 5 | Requires at least: 5.5 6 | Tested up to: 6.7 7 | Stable tag: 2.0 8 | Requires PHP: 7.4 9 | License: GPLv2License 10 | URI:https://www.gnu.org/licenses/gpl-2.0.html 11 | -------------------------------------------------------------------------------- /tests/fixtures/plugin-ignore-comment/plugin-ignore-comment.php: -------------------------------------------------------------------------------- 1 |