From 5b9d442a0696f58944d61df0df9905dd4d2a3e0c Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 18:33:37 -0400 Subject: [PATCH 01/20] test: add golden-master parser harness - Add bin/generate-golden.php to snapshot parser output as JSON - Add WordPress-free PHPUnit golden test + standalone bootstrap/config - Share corpus discovery and normalization via tests/golden/golden.php - Cover all tests/source/*.php and tests/**/*.inc fixtures (16 entries) Pins parse_files() output key-for-key so the parser can be rewritten onto the modern stack without silently changing the importer's contract. --- bin/generate-golden.php | 50 +++++++++ phpunit-golden.xml.dist | 20 ++++ tests/golden/README.md | 71 +++++++++++++ tests/golden/bootstrap.php | 20 ++++ tests/golden/golden.php | 153 ++++++++++++++++++++++++++++ tests/golden/test-golden-master.php | 61 +++++++++++ 6 files changed, 375 insertions(+) create mode 100644 bin/generate-golden.php create mode 100644 phpunit-golden.xml.dist create mode 100644 tests/golden/README.md create mode 100644 tests/golden/bootstrap.php create mode 100644 tests/golden/golden.php create mode 100644 tests/golden/test-golden-master.php diff --git a/bin/generate-golden.php b/bin/generate-golden.php new file mode 100644 index 00000000..e940773c --- /dev/null +++ b/bin/generate-golden.php @@ -0,0 +1,50 @@ + $entry ) { + $json = \WP_Parser\Golden\to_json( \WP_Parser\Golden\parse_entry( $entry ) ); + file_put_contents( \WP_Parser\Golden\snapshot_path( $slug ), $json ); + printf( " wrote %-32s %6d bytes\n", $slug, strlen( $json ) ); + $count++; +} + +printf( "\nDone. %d snapshot(s) written to %s\n", $count, $snapshots ); +printf( "PHP %s | php-parser/reflection as installed in vendor/\n", PHP_VERSION ); diff --git a/phpunit-golden.xml.dist b/phpunit-golden.xml.dist new file mode 100644 index 00000000..6972c11a --- /dev/null +++ b/phpunit-golden.xml.dist @@ -0,0 +1,20 @@ + + + + + + tests/golden + + + diff --git a/tests/golden/README.md b/tests/golden/README.md new file mode 100644 index 00000000..fd0ebaee --- /dev/null +++ b/tests/golden/README.md @@ -0,0 +1,71 @@ +# Golden-master parser harness + +A **characterization test** that pins the parser's output so it can be rewritten +safely. The plugin's whole downstream (the importer, the DevHub theme, the +Posts-to-Posts relationships) consumes only the array returned by +`WP_Parser\parse_files()`. The migration's #1 rule is that the rewritten parser +must reproduce that array **key-for-key**. + +This harness captures the **old** parser's output as JSON snapshots, then asserts +the current parser still matches them. + +``` +bin/generate-golden.php # writes snapshots from the installed parser +tests/golden/golden.php # shared corpus + normalize + JSON helpers +tests/golden/bootstrap.php # standalone (WordPress-free) PHPUnit bootstrap +tests/golden/test-golden-master.php # the comparison test +tests/golden/snapshots/*.json # committed baseline (the oracle) +phpunit-golden.xml.dist # config for this suite +``` + +The corpus is every `tests/source/*.php` fixture plus every `tests/**/*.inc` +fixture, each parsed on its own. `parse_files()` is WordPress-free, so nothing +here needs the WP test framework. + +## 1. Capture the baseline (do this once, on the OLD stack) + +The old stack (`phpdocumentor/reflection ~3.0`, `nikic/php-parser 1.x`) does not +run cleanly on PHP 8.x, so generate the baseline on **PHP 7.4**. Docker is the +reliable way: + +```bash +# From the repo root. +docker run --rm -it -v "$PWD":/app -w /app php:7.4-cli bash -c ' + apt-get update -qq && apt-get install -y -qq git unzip >/dev/null + curl -sS https://getcomposer.org/installer | php -- --quiet + php composer.phar install --no-interaction --no-progress + php bin/generate-golden.php +' +``` + +> If `composer install` chokes on the unmaintained `scribu/*` packages (they are +> not needed for parsing), generate against a minimal install instead: +> ```bash +> docker run --rm -it -v "$PWD":/app -w /app php:7.4-cli bash -c ' +> curl -sS https://getcomposer.org/installer | php -- --quiet +> php composer.phar require --no-interaction phpdocumentor/reflection:~3.0 erusev/parsedown:~1.7 +> php bin/generate-golden.php +> ' +> ``` + +Then commit `tests/golden/snapshots/`. **Never regenerate these against the new +parser** — that would erase the oracle. + +## 2. Run the test (any time, on whatever stack is installed) + +```bash +composer install +./vendor/bin/phpunit -c phpunit-golden.xml.dist +``` + +- On the **old** stack the test passes trivially (parser vs. its own snapshot) — + this proves the harness is wired correctly before any code changes. +- During the **rewrite** every dropped field, changed type, or reordered entry + becomes a hard failure naming the exact fixture. + +## 3. Intentional changes + +If the rewrite *intentionally* changes output (e.g. adding class-constant +visibility, issue #224), regenerate the affected snapshot, **review the JSON diff +in the commit**, and call it out in the PR. The snapshots are reviewed artifacts, +not throwaway fixtures. diff --git a/tests/golden/bootstrap.php b/tests/golden/bootstrap.php new file mode 100644 index 00000000..fac9e748 --- /dev/null +++ b/tests/golden/bootstrap.php @@ -0,0 +1,20 @@ + Map of slug => entry. + */ +function corpus() { + $root = repo_root(); + $files = array(); + + foreach ( (array) glob( $root . '/tests/source/*.php' ) as $file ) { + $files[] = $file; + } + + $dir = $root . '/tests/phpunit/tests'; + if ( is_dir( $dir ) ) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ) + ); + foreach ( $iterator as $file ) { + if ( 'inc' === $file->getExtension() ) { + $files[] = $file->getPathname(); + } + } + } + + sort( $files ); // Deterministic ordering across platforms. + + $corpus = array(); + foreach ( $files as $file ) { + $corpus[ slug( $file ) ] = array( + 'files' => array( $file ), + 'root' => dirname( $file ), + ); + } + + return $corpus; +} + +/** + * Turn a fixture path into a stable, filesystem-safe snapshot slug. + * + * @param string $file Absolute path to a fixture. + * @return string + */ +function slug( $file ) { + $relative = ltrim( str_replace( repo_root(), '', $file ), '/\\' ); + $relative = preg_replace( '#^tests/phpunit/tests/#', '', $relative ); + $relative = preg_replace( '#^tests/#', '', $relative ); + $relative = preg_replace( '#\.(php|inc)$#', '', $relative ); + + return str_replace( array( '/', '\\' ), '__', $relative ); +} + +/** + * Absolute path to a slug's snapshot file. + * + * @param string $slug Snapshot slug. + * @return string + */ +function snapshot_path( $slug ) { + return snapshots_dir() . '/' . $slug . '.json'; +} + +/** + * Scrub environment-specific values from parser output so it is reproducible. + * + * Only the absolute root path is scrubbed; every other value is content-derived + * and must match exactly between the old and rewritten parsers. + * + * @param array $data Output of parse_files(). + * @return array + */ +function normalize( array $data ) { + foreach ( $data as &$file ) { + if ( isset( $file['root'] ) ) { + $file['root'] = ROOT_PLACEHOLDER; + } + } + unset( $file ); + + return $data; +} + +/** + * Canonical JSON encoding shared by the generator and the test, so the comparison + * is byte-for-byte. + * + * @param mixed $data Data to encode. + * @return string + */ +function to_json( $data ) { + return json_encode( + $data, + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + ) . "\n"; +} + +/** + * Parse a corpus entry and return its normalized output array. + * + * @param array{files:string[],root:string} $entry Corpus entry. + * @return array + */ +function parse_entry( array $entry ) { + return normalize( \WP_Parser\parse_files( $entry['files'], $entry['root'] ) ); +} diff --git a/tests/golden/test-golden-master.php b/tests/golden/test-golden-master.php new file mode 100644 index 00000000..4e8d71e8 --- /dev/null +++ b/tests/golden/test-golden-master.php @@ -0,0 +1,61 @@ +markTestSkipped( + "No golden snapshot for '{$slug}'. Generate it on the old stack with bin/generate-golden.php." + ); + } + + $expected = file_get_contents( $snapshot ); + $actual = \WP_Parser\Golden\to_json( \WP_Parser\Golden\parse_entry( $entry ) ); + + $this->assertSame( + $expected, + $actual, + "Parser output drifted from the golden master for '{$slug}'." + ); + } + + /** + * @return array + */ + public function corpus_provider() { + $cases = array(); + foreach ( \WP_Parser\Golden\corpus() as $slug => $entry ) { + $cases[ $slug ] = array( $slug, $entry ); + } + + return $cases; + } +} From d9eb25ba53d56e6ee47a02b2222ebf241566af44 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 18:39:21 -0400 Subject: [PATCH 02/20] test: capture golden-master baseline on PHP 7.4 - Add 16 JSON snapshots of the old parser's parse_files() output - Add bin/generate-golden-docker.sh to reproduce them on php:7.4-cli - Document the validated PHP 7.4 generation recipe The old stack emits warnings on PHP 8.x, so the baseline is generated on 7.4 and frozen as the regression oracle for the rewrite. --- bin/generate-golden-docker.sh | 25 + tests/golden/README.md | 33 +- tests/golden/snapshots/export__docblocks.json | 291 +++ tests/golden/snapshots/export__hooks.json | 122 + tests/golden/snapshots/export__namespace.json | 27 + .../snapshots/export__uses__constructor.json | 132 ++ .../snapshots/export__uses__methods.json | 139 ++ .../snapshots/export__uses__nested.json | 169 ++ tests/golden/snapshots/import__file.json | 75 + tests/golden/snapshots/source__actions.json | 486 ++++ .../snapshots/source__bad-class-doc.json | 83 + .../snapshots/source__class-property-doc.json | 308 +++ .../snapshots/source__class_method_doc.json | 1990 +++++++++++++++++ .../snapshots/source__deprecated-file.json | 112 + tests/golden/snapshots/source__filters.json | 477 ++++ tests/golden/snapshots/source__functions.json | 575 +++++ .../golden/snapshots/source__good-class.json | 341 +++ .../snapshots/source__relationships.json | 494 ++++ 18 files changed, 5860 insertions(+), 19 deletions(-) create mode 100755 bin/generate-golden-docker.sh create mode 100644 tests/golden/snapshots/export__docblocks.json create mode 100644 tests/golden/snapshots/export__hooks.json create mode 100644 tests/golden/snapshots/export__namespace.json create mode 100644 tests/golden/snapshots/export__uses__constructor.json create mode 100644 tests/golden/snapshots/export__uses__methods.json create mode 100644 tests/golden/snapshots/export__uses__nested.json create mode 100644 tests/golden/snapshots/import__file.json create mode 100644 tests/golden/snapshots/source__actions.json create mode 100644 tests/golden/snapshots/source__bad-class-doc.json create mode 100644 tests/golden/snapshots/source__class-property-doc.json create mode 100644 tests/golden/snapshots/source__class_method_doc.json create mode 100644 tests/golden/snapshots/source__deprecated-file.json create mode 100644 tests/golden/snapshots/source__filters.json create mode 100644 tests/golden/snapshots/source__functions.json create mode 100644 tests/golden/snapshots/source__good-class.json create mode 100644 tests/golden/snapshots/source__relationships.json diff --git a/bin/generate-golden-docker.sh b/bin/generate-golden-docker.sh new file mode 100755 index 00000000..265d988f --- /dev/null +++ b/bin/generate-golden-docker.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# Regenerate the golden-master snapshots on PHP 7.4 — the last PHP version the old +# parser (phpdocumentor/reflection ~3.0 + nikic/php-parser 1.x) runs on cleanly. +# On PHP 8.x that stack emits a flood of deprecations/warnings and produces +# unreliable output, so the baseline MUST be captured on 7.4. +# +# Reuses the host's already-installed vendor/ (pure PHP, version-agnostic), so no +# Composer or build tooling is needed inside the container. +# +# Prereq: composer install --no-dev # installs the OLD locked parser stack +# Usage: bin/generate-golden-docker.sh +# +set -euo pipefail +cd "$(dirname "$0")/.." + +if [ ! -f vendor/phpdocumentor/reflection/composer.json ]; then + echo "ERROR: old parser not installed. Run: composer install --no-dev" >&2 + exit 1 +fi + +exec docker run --rm \ + -u "$(id -u):$(id -g)" -e HOME=/tmp \ + -v "$PWD":/app -w /app \ + php:7.4-cli php bin/generate-golden.php diff --git a/tests/golden/README.md b/tests/golden/README.md index fd0ebaee..704fbfc0 100644 --- a/tests/golden/README.md +++ b/tests/golden/README.md @@ -24,29 +24,24 @@ here needs the WP test framework. ## 1. Capture the baseline (do this once, on the OLD stack) -The old stack (`phpdocumentor/reflection ~3.0`, `nikic/php-parser 1.x`) does not -run cleanly on PHP 8.x, so generate the baseline on **PHP 7.4**. Docker is the -reliable way: +The old stack (`phpdocumentor/reflection ~3.0`, `nikic/php-parser 1.x`) floods +PHP 8.x with deprecations/warnings and produces unreliable output, so the +baseline must be captured on **PHP 7.4**. The installed `vendor/` is pure PHP and +version-agnostic, so install it on the host and just run the generator inside a +PHP 7.4 container: ```bash -# From the repo root. -docker run --rm -it -v "$PWD":/app -w /app php:7.4-cli bash -c ' - apt-get update -qq && apt-get install -y -qq git unzip >/dev/null - curl -sS https://getcomposer.org/installer | php -- --quiet - php composer.phar install --no-interaction --no-progress - php bin/generate-golden.php -' +composer install --no-dev # installs the OLD locked parser stack (skips phpunit ^7) +bin/generate-golden-docker.sh # runs bin/generate-golden.php inside php:7.4-cli ``` -> If `composer install` chokes on the unmaintained `scribu/*` packages (they are -> not needed for parsing), generate against a minimal install instead: -> ```bash -> docker run --rm -it -v "$PWD":/app -w /app php:7.4-cli bash -c ' -> curl -sS https://getcomposer.org/installer | php -- --quiet -> php composer.phar require --no-interaction phpdocumentor/reflection:~3.0 erusev/parsedown:~1.7 -> php bin/generate-golden.php -> ' -> ``` +The helper reuses the host's `vendor/` and writes files as your user (no Docker +root-owned files). It is equivalent to: + +```bash +docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD":/app -w /app \ + php:7.4-cli php bin/generate-golden.php +``` Then commit `tests/golden/snapshots/`. **Never regenerate these against the new parser** — that would erase the oracle. diff --git a/tests/golden/snapshots/export__docblocks.json b/tests/golden/snapshots/export__docblocks.json new file mode 100644 index 00000000..ceb6a663 --- /dev/null +++ b/tests/golden/snapshots/export__docblocks.json @@ -0,0 +1,291 @@ +[ + { + "file": { + "description": "This is the file-level docblock summary.", + "long_description": "

This is the file-level docblock description, which may span multiple lines. In fact, this one does. It spans more than two full lines, continuing on to the third line.

", + "tags": [ + { + "name": "since", + "content": "1.5.0" + } + ] + }, + "path": "docblocks.inc", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "apply_filters", + "line": 69, + "end_line": 69 + }, + { + "name": "apply_filters_ref_array", + "line": 74, + "end_line": 74 + }, + { + "name": "do_action", + "line": 83, + "end_line": 83 + }, + { + "name": "do_action", + "line": 85, + "end_line": 85 + }, + { + "name": "do_action_ref_array", + "line": 90, + "end_line": 90 + } + ] + }, + "hooks": [ + { + "name": "test_filter", + "line": 69, + "end_line": 69, + "type": "filter", + "arguments": [ + "$var" + ], + "doc": { + "description": "A filter.", + "long_description": "", + "tags": [] + } + }, + { + "name": "test_ref_array_filter", + "line": 74, + "end_line": 74, + "type": "filter_reference", + "arguments": [ + "array(&$var)" + ], + "doc": { + "description": "A reference array filter.", + "long_description": "", + "tags": [] + } + }, + { + "name": "test_action", + "line": 83, + "end_line": 83, + "type": "action", + "arguments": [ + "$post" + ], + "doc": { + "description": "A test action.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "3.7.0" + }, + { + "name": "param", + "content": "Post object.", + "types": [ + "\\WP_Post" + ], + "variable": "$post" + } + ] + } + }, + { + "name": "undocumented_hook", + "line": 85, + "end_line": 85, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "test_ref_array_action", + "line": 90, + "end_line": 90, + "type": "action_reference", + "arguments": [ + "array(&$var)" + ], + "doc": { + "description": "A reference array action.", + "long_description": "", + "tags": [] + } + } + ], + "functions": [ + { + "name": "test_func", + "namespace": "global", + "aliases": [], + "line": 25, + "end_line": 29, + "arguments": [ + { + "name": "$var", + "default": null, + "type": "" + }, + { + "name": "$num", + "default": null, + "type": "" + } + ], + "doc": { + "description": "This is a function docblock.", + "long_description": "

This function is just a test, but we've added this description anyway.

", + "tags": [ + { + "name": "since", + "content": "2.6.0" + }, + { + "name": "param", + "content": "A string value.", + "types": [ + "string" + ], + "variable": "$var" + }, + { + "name": "param", + "content": "A number.", + "types": [ + "int" + ], + "variable": "$num" + }, + { + "name": "return", + "content": "Whether the function was called correctly.", + "types": [ + "bool" + ] + } + ] + }, + "hooks": [] + } + ], + "classes": [ + { + "name": "Test_Class", + "namespace": "global", + "line": 40, + "end_line": 64, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [ + { + "name": "$a_string", + "line": 49, + "end_line": 49, + "default": null, + "static": false, + "visibility": "public", + "doc": { + "description": "This is a docblock for a class property.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "3.0.0" + }, + { + "name": "var", + "content": "", + "types": [ + "string" + ], + "variable": "" + } + ] + } + } + ], + "methods": [ + { + "name": "test_method", + "namespace": "", + "aliases": [], + "line": 61, + "end_line": 63, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$var", + "default": null, + "type": "" + }, + { + "name": "$arr", + "default": null, + "type": "" + } + ], + "doc": { + "description": "This is a method docblock.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "4.5.0" + }, + { + "name": "param", + "content": "A parameter.", + "types": [ + "mixed" + ], + "variable": "$var" + }, + { + "name": "param", + "content": "Another parameter.", + "types": [ + "array" + ], + "variable": "$arr" + }, + { + "name": "return", + "content": "The first param.", + "types": [ + "mixed" + ] + } + ] + } + } + ], + "doc": { + "description": "This is a class docblock.", + "long_description": "

This is the more wordy description: This is a comment with two *'s at the start, which means that it is a doc comment. Docblock comments are comment blocks used to document code. This one documents the Test_Class class.

", + "tags": [ + { + "name": "since", + "content": "3.5.2" + } + ] + } + } + ] + } +] diff --git a/tests/golden/snapshots/export__hooks.json b/tests/golden/snapshots/export__hooks.json new file mode 100644 index 00000000..c53ca048 --- /dev/null +++ b/tests/golden/snapshots/export__hooks.json @@ -0,0 +1,122 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "hooks.inc", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "do_action", + "line": 3, + "end_line": 3 + }, + { + "name": "do_action", + "line": 4, + "end_line": 4 + }, + { + "name": "do_action", + "line": 5, + "end_line": 5 + }, + { + "name": "do_action", + "line": 6, + "end_line": 6 + }, + { + "name": "do_action", + "line": 7, + "end_line": 7 + }, + { + "name": "apply_filters", + "line": 8, + "end_line": 8 + } + ] + }, + "hooks": [ + { + "name": "plain_action", + "line": 3, + "end_line": 3, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "action_with_double_quotes", + "line": 4, + "end_line": 4, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "{$variable}-action", + "line": 5, + "end_line": 5, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "another-{$variable}-action", + "line": 6, + "end_line": 6, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "hook_{$object->property}_pre", + "line": 7, + "end_line": 7, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "plain_filter", + "line": 8, + "end_line": 8, + "type": "filter", + "arguments": [ + "$variable", + "$filter_context" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/export__namespace.json b/tests/golden/snapshots/export__namespace.json new file mode 100644 index 00000000..9a95c6fb --- /dev/null +++ b/tests/golden/snapshots/export__namespace.json @@ -0,0 +1,27 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "namespace.inc", + "root": "{{ROOT}}", + "functions": [ + { + "name": "ohai", + "namespace": "Awesome\\Space", + "aliases": [], + "line": 5, + "end_line": 5, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [] + } + ] + } +] diff --git a/tests/golden/snapshots/export__uses__constructor.json b/tests/golden/snapshots/export__uses__constructor.json new file mode 100644 index 00000000..2eabf4ee --- /dev/null +++ b/tests/golden/snapshots/export__uses__constructor.json @@ -0,0 +1,132 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "constructor.inc", + "root": "{{ROOT}}", + "uses": { + "methods": [ + { + "name": "__construct", + "class": "\\WP_Query", + "static": false, + "line": 3, + "end_line": 3 + }, + { + "name": "__construct", + "class": "$class", + "static": false, + "line": 20, + "end_line": 20 + } + ] + }, + "functions": [ + { + "name": "test", + "namespace": "global", + "aliases": [], + "line": 5, + "end_line": 7, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "methods": [ + { + "name": "__construct", + "class": "\\My_Class", + "static": false, + "line": 6, + "end_line": 6 + } + ] + } + } + ], + "classes": [ + { + "name": "My_Class", + "namespace": "global", + "line": 9, + "end_line": 18, + "final": false, + "abstract": false, + "extends": "\\Parent_Class", + "implements": [], + "properties": [], + "methods": [ + { + "name": "instance", + "namespace": "", + "aliases": [], + "line": 11, + "end_line": 13, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "__construct", + "class": "\\My_Class", + "static": false, + "line": 12, + "end_line": 12 + } + ] + } + }, + { + "name": "parent", + "namespace": "", + "aliases": [], + "line": 15, + "end_line": 17, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "__construct", + "class": "\\Parent_Class", + "static": false, + "line": 16, + "end_line": 16 + } + ] + } + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/export__uses__methods.json b/tests/golden/snapshots/export__uses__methods.json new file mode 100644 index 00000000..39c8895c --- /dev/null +++ b/tests/golden/snapshots/export__uses__methods.json @@ -0,0 +1,139 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "methods.inc", + "root": "{{ROOT}}", + "uses": { + "methods": [ + { + "name": "static_method", + "class": "\\My_Class", + "static": true, + "line": 3, + "end_line": 3 + }, + { + "name": "update", + "class": "$wpdb", + "static": false, + "line": 5, + "end_line": 5 + } + ] + }, + "functions": [ + { + "name": "test", + "namespace": "global", + "aliases": [], + "line": 7, + "end_line": 11, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "methods": [ + { + "name": "another_method", + "class": "\\Another_Class", + "static": true, + "line": 8, + "end_line": 8 + }, + { + "name": "call_method", + "class": "get_class()", + "static": false, + "line": 10, + "end_line": 10 + } + ], + "functions": [ + { + "name": "get_class", + "line": 10, + "end_line": 10 + } + ] + } + } + ], + "classes": [ + { + "name": "My_Class", + "namespace": "global", + "line": 13, + "end_line": 21, + "final": false, + "abstract": false, + "extends": "\\Parent_Class", + "implements": [], + "properties": [], + "methods": [ + { + "name": "static_method", + "namespace": "", + "aliases": [], + "line": 15, + "end_line": 20, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "do_static_stuff", + "class": "\\Another_Class", + "static": true, + "line": 16, + "end_line": 16 + }, + { + "name": "do_stuff", + "class": "\\My_Class", + "static": true, + "line": 17, + "end_line": 17 + }, + { + "name": "go", + "class": "\\My_Class", + "static": false, + "line": 18, + "end_line": 18 + }, + { + "name": "do_parental_stuff", + "class": "\\Parent_Class", + "static": true, + "line": 19, + "end_line": 19 + } + ] + } + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/export__uses__nested.json b/tests/golden/snapshots/export__uses__nested.json new file mode 100644 index 00000000..692a03bf --- /dev/null +++ b/tests/golden/snapshots/export__uses__nested.json @@ -0,0 +1,169 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "nested.inc", + "root": "{{ROOT}}", + "functions": [ + { + "name": "sub_test", + "namespace": "global", + "aliases": [], + "line": 7, + "end_line": 12, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "functions": [ + { + "name": "b_function", + "line": 9, + "end_line": 9 + } + ], + "methods": [ + { + "name": "static_method", + "class": "\\My_Class", + "static": true, + "line": 11, + "end_line": 11 + } + ] + } + }, + { + "name": "test", + "namespace": "global", + "aliases": [], + "line": 3, + "end_line": 17, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "functions": [ + { + "name": "a_function", + "line": 5, + "end_line": 5 + }, + { + "name": "sub_test", + "line": 14, + "end_line": 14 + } + ], + "methods": [ + { + "name": "do_things", + "class": "\\My_Class", + "static": true, + "line": 16, + "end_line": 16 + } + ] + } + }, + { + "name": "sub_method_test", + "namespace": "global", + "aliases": [], + "line": 25, + "end_line": 30, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "functions": [ + { + "name": "b_function", + "line": 27, + "end_line": 27 + } + ], + "methods": [ + { + "name": "a_method", + "class": "\\My_Class", + "static": true, + "line": 29, + "end_line": 29 + } + ] + } + } + ], + "classes": [ + { + "name": "My_Class", + "namespace": "global", + "line": 19, + "end_line": 34, + "final": false, + "abstract": false, + "extends": "\\Parent_Class", + "implements": [], + "properties": [], + "methods": [ + { + "name": "a_method", + "namespace": "", + "aliases": [], + "line": 21, + "end_line": 33, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "do_it", + "class": "\\My_Class", + "static": false, + "line": 23, + "end_line": 23 + } + ], + "functions": [ + { + "name": "do_things", + "line": 32, + "end_line": 32 + } + ] + } + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/import__file.json b/tests/golden/snapshots/import__file.json new file mode 100644 index 00000000..9d6f3696 --- /dev/null +++ b/tests/golden/snapshots/import__file.json @@ -0,0 +1,75 @@ +[ + { + "file": { + "description": "File summary.", + "long_description": "

This is the longer file description. It can be very long, and even span multiple lines, if hard wrapped as it should be.

", + "tags": [ + { + "name": "package", + "content": "Something" + }, + { + "name": "since", + "content": "1.0.0" + } + ] + }, + "path": "file.inc", + "root": "{{ROOT}}", + "functions": [ + { + "name": "wp_parser_test_func", + "namespace": "global", + "aliases": [], + "line": 25, + "end_line": 28, + "arguments": [ + { + "name": "$var", + "default": null, + "type": "" + }, + { + "name": "$ids", + "default": "array()", + "type": "array" + } + ], + "doc": { + "description": "This is a function summary.", + "long_description": "

This function is just here for tests. This is its longer description.

", + "tags": [ + { + "name": "since", + "content": "1.4.0" + }, + { + "name": "param", + "content": "A string variable which is the first parameter.", + "types": [ + "string" + ], + "variable": "$var" + }, + { + "name": "param", + "content": "An array of user IDs.", + "types": [ + "int[]" + ], + "variable": "$ids" + }, + { + "name": "return", + "content": "The return type is random. (Not really.)", + "types": [ + "mixed" + ] + } + ] + }, + "hooks": [] + } + ] + } +] diff --git a/tests/golden/snapshots/source__actions.json b/tests/golden/snapshots/source__actions.json new file mode 100644 index 00000000..03db0c8a --- /dev/null +++ b/tests/golden/snapshots/source__actions.json @@ -0,0 +1,486 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "actions.php", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "do_action", + "line": 15, + "end_line": 15 + }, + { + "name": "do_action", + "line": 29, + "end_line": 29 + }, + { + "name": "do_action", + "line": 43, + "end_line": 43 + }, + { + "name": "do_action", + "line": 55, + "end_line": 55 + }, + { + "name": "do_action", + "line": 67, + "end_line": 67 + }, + { + "name": "do_action", + "line": 79, + "end_line": 79 + }, + { + "name": "do_action", + "line": 92, + "end_line": 92 + }, + { + "name": "do_action", + "line": 105, + "end_line": 105 + }, + { + "name": "do_action", + "line": 118, + "end_line": 118 + }, + { + "name": "do_action", + "line": 120, + "end_line": 120 + }, + { + "name": "do_action", + "line": 121, + "end_line": 121 + }, + { + "name": "do_action", + "line": 122, + "end_line": 122 + } + ] + }, + "hooks": [ + { + "name": "good_doc_static_action", + "line": 15, + "end_line": 15, + "type": "action", + "arguments": [ + "$option", + "$old_value", + "$value" + ], + "doc": { + "description": "This is a well documented action.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.9.0" + }, + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "good_doc_dynamic_action_{$option}", + "line": 29, + "end_line": 29, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "This is a well documented dynamic action.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.9.0" + }, + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "good_doc_double_quotes_dynamic_action_{$option}", + "line": 43, + "end_line": 43, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "This is a well documented dynamic action.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.9.0" + }, + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "missing_since_static_action", + "line": 55, + "end_line": 55, + "type": "action", + "arguments": [ + "$option", + "$old_value", + "$value" + ], + "doc": { + "description": "This is an action missing the \"since\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "missing_since_dynamic_action_{$option}", + "line": 67, + "end_line": 67, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "This is a dynamic action missing the \"since\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "missing_since_double_quotes_dynamic_action_{$option}", + "line": 79, + "end_line": 79, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "This is a dynamic action missing the \"since\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "missing_param_static_action", + "line": 92, + "end_line": 92, + "type": "action", + "arguments": [ + "$option", + "$old_value", + "$value" + ], + "doc": { + "description": "This is an action missing a \"param\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.9.0" + }, + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "missing_param_dynamic_action_{$option}", + "line": 105, + "end_line": 105, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "This is a well documented dynamic action.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.9.0" + }, + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "missing_param_double_quotes_dynamic_action_{$option}", + "line": 118, + "end_line": 118, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "This is a well documented dynamic action.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.9.0" + }, + { + "name": "param", + "content": "Name of the option to update.", + "types": [ + "string" + ], + "variable": "$option" + }, + { + "name": "param", + "content": "The new option value.", + "types": [ + "mixed" + ], + "variable": "$value" + } + ] + } + }, + { + "name": "no_doc_static_action", + "line": 120, + "end_line": 120, + "type": "action", + "arguments": [ + "$option", + "$old_value", + "$value" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "no_doc_dynamic_action_{$option}", + "line": 121, + "end_line": 121, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "no_doc_double_quotes_dymanic_action_{$option}", + "line": 122, + "end_line": 122, + "type": "action", + "arguments": [ + "$old_value", + "$value" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/source__bad-class-doc.json b/tests/golden/snapshots/source__bad-class-doc.json new file mode 100644 index 00000000..7db2bbbf --- /dev/null +++ b/tests/golden/snapshots/source__bad-class-doc.json @@ -0,0 +1,83 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "bad-class-doc.php", + "root": "{{ROOT}}", + "classes": [ + { + "name": "No_Since_Class", + "namespace": "global", + "line": 13, + "end_line": 15, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [], + "doc": { + "description": "WordPress Error class.", + "long_description": "

Container for checking for WordPress errors and error messages. Return WP_Error and use {@link is_wp_error()} to check if this class is returned.
Many core WordPress functions pass this class in the event of an error and if not handled properly will result in code errors.

", + "tags": [ + { + "name": "package", + "content": "WordPress" + } + ] + } + }, + { + "name": "No_Package_Class", + "namespace": "global", + "line": 27, + "end_line": 29, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [], + "doc": { + "description": "WordPress Error class.", + "long_description": "

Container for checking for WordPress errors and error messages. Return WP_Error and use {@link is_wp_error()} to check if this class is returned.
Many core WordPress functions pass this class in the event of an error and if not handled properly will result in code errors.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + } + ] + } + }, + { + "name": "No_Description_Class", + "namespace": "global", + "line": 35, + "end_line": 37, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [], + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "package", + "content": "WordPress" + }, + { + "name": "since", + "content": "2.1.0" + } + ] + } + } + ] + } +] diff --git a/tests/golden/snapshots/source__class-property-doc.json b/tests/golden/snapshots/source__class-property-doc.json new file mode 100644 index 00000000..4266843d --- /dev/null +++ b/tests/golden/snapshots/source__class-property-doc.json @@ -0,0 +1,308 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "class-property-doc.php", + "root": "{{ROOT}}", + "classes": [ + { + "name": "Bad_Property_Doc", + "namespace": "global", + "line": 14, + "end_line": 91, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [ + { + "name": "$private_good_doc_property", + "line": 23, + "end_line": 23, + "default": "'foo'", + "static": false, + "visibility": "private", + "doc": { + "description": "Stores the list of errors.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "private" + } + ] + } + }, + { + "name": "$private_missing_description_property", + "line": 30, + "end_line": 30, + "default": "'string'", + "static": false, + "visibility": "private", + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "private" + } + ] + } + }, + { + "name": "$private_missing_since_property", + "line": 38, + "end_line": 38, + "default": "'foo'", + "static": false, + "visibility": "private", + "doc": { + "description": "Stores the list of errors.", + "long_description": "", + "tags": [ + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "private" + } + ] + } + }, + { + "name": "$private_missing_var_property", + "line": 46, + "end_line": 46, + "default": "'foo'", + "static": false, + "visibility": "private", + "doc": { + "description": "Stores the list of errors.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "private" + } + ] + } + }, + { + "name": "$private_missing_access_property", + "line": 52, + "end_line": 52, + "default": "'string'", + "static": false, + "visibility": "private", + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + } + ] + } + }, + { + "name": "$public_good_doc_property", + "line": 61, + "end_line": 61, + "default": "'foo'", + "static": false, + "visibility": "public", + "doc": { + "description": "Stores the list of errors.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "public" + } + ] + } + }, + { + "name": "$public_missing_description_property", + "line": 68, + "end_line": 68, + "default": "'string'", + "static": false, + "visibility": "public", + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "public" + } + ] + } + }, + { + "name": "$public_missing_since_property", + "line": 76, + "end_line": 76, + "default": "'foo'", + "static": false, + "visibility": "public", + "doc": { + "description": "Stores the list of errors.", + "long_description": "", + "tags": [ + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "public" + } + ] + } + }, + { + "name": "$public_missing_var_property", + "line": 84, + "end_line": 84, + "default": "'foo'", + "static": false, + "visibility": "public", + "doc": { + "description": "Stores the list of errors.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "public" + } + ] + } + }, + { + "name": "$public_missing_access_property", + "line": 90, + "end_line": 90, + "default": "'string'", + "static": false, + "visibility": "public", + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + } + ] + } + } + ], + "methods": [], + "doc": { + "description": "WordPress Error class.", + "long_description": "

Container for checking for WordPress errors and error messages. Return WP_Error and use {@link is_wp_error()} to check if this class is returned.
Many core WordPress functions pass this class in the event of an error and if not handled properly will result in code errors.

", + "tags": [ + { + "name": "package", + "content": "WordPress" + }, + { + "name": "since", + "content": "2.1.0" + } + ] + } + } + ] + } +] diff --git a/tests/golden/snapshots/source__class_method_doc.json b/tests/golden/snapshots/source__class_method_doc.json new file mode 100644 index 00000000..87dbd52c --- /dev/null +++ b/tests/golden/snapshots/source__class_method_doc.json @@ -0,0 +1,1990 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "class_method_doc.php", + "root": "{{ROOT}}", + "classes": [ + { + "name": "Various_Method_Docs_Class", + "namespace": "global", + "line": 12, + "end_line": 478, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [ + { + "name": "public_method_missing_description", + "namespace": "", + "aliases": [], + "line": 27, + "end_line": 29, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "public" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "public_method_missing_since", + "namespace": "", + "aliases": [], + "line": 47, + "end_line": 49, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "access", + "content": "public" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "public_method_missing_param", + "namespace": "", + "aliases": [], + "line": 65, + "end_line": 67, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "public" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "public_method_missing_return", + "namespace": "", + "aliases": [], + "line": 85, + "end_line": 87, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "public" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + } + ] + } + }, + { + "name": "public_method_missing_access", + "namespace": "", + "aliases": [], + "line": 105, + "end_line": 107, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "public_method_missing_see", + "namespace": "", + "aliases": [], + "line": 125, + "end_line": 127, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "public" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "public_method_missing_link", + "namespace": "", + "aliases": [], + "line": 145, + "end_line": 147, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "public" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "public_method_missing_global", + "namespace": "", + "aliases": [], + "line": 165, + "end_line": 167, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "public" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "protected_method_missing_description", + "namespace": "", + "aliases": [], + "line": 182, + "end_line": 184, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "protected" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "protected_method_missing_since", + "namespace": "", + "aliases": [], + "line": 202, + "end_line": 204, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "access", + "content": "protected" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "protected_method_missing_param", + "namespace": "", + "aliases": [], + "line": 220, + "end_line": 222, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "protected" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "protected_method_missing_return", + "namespace": "", + "aliases": [], + "line": 240, + "end_line": 242, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "protected" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + } + ] + } + }, + { + "name": "protected_method_missing_access", + "namespace": "", + "aliases": [], + "line": 260, + "end_line": 262, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "protected_method_missing_see", + "namespace": "", + "aliases": [], + "line": 280, + "end_line": 282, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "protected" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "protected_method_missing_link", + "namespace": "", + "aliases": [], + "line": 300, + "end_line": 302, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "protected" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "protected_method_missing_global", + "namespace": "", + "aliases": [], + "line": 320, + "end_line": 322, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "protected" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "private_method_missing_description", + "namespace": "", + "aliases": [], + "line": 337, + "end_line": 339, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "private" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "private_method_missing_since", + "namespace": "", + "aliases": [], + "line": 357, + "end_line": 359, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "access", + "content": "private" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "private_method_missing_param", + "namespace": "", + "aliases": [], + "line": 375, + "end_line": 377, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "private" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "private_method_missing_return", + "namespace": "", + "aliases": [], + "line": 395, + "end_line": 397, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "private" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + } + ] + } + }, + { + "name": "private_method_missing_access", + "namespace": "", + "aliases": [], + "line": 415, + "end_line": 417, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "private_method_missing_see", + "namespace": "", + "aliases": [], + "line": 435, + "end_line": 437, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "private" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "private_method_missing_link", + "namespace": "", + "aliases": [], + "line": 455, + "end_line": 457, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "private" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "private_method_missing_global", + "namespace": "", + "aliases": [], + "line": 475, + "end_line": 477, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "Short description.", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "access", + "content": "private" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + } + ], + "doc": { + "description": "This is a properly documented class.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "package", + "content": "WordPress" + }, + { + "name": "since", + "content": "2.1.0" + } + ] + } + } + ] + } +] diff --git a/tests/golden/snapshots/source__deprecated-file.json b/tests/golden/snapshots/source__deprecated-file.json new file mode 100644 index 00000000..51fd1ac6 --- /dev/null +++ b/tests/golden/snapshots/source__deprecated-file.json @@ -0,0 +1,112 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "deprecated-file.php", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "_deprecated_file", + "line": 2, + "end_line": 2, + "deprecation_version": "1.0" + }, + { + "name": "apply_filters", + "line": 23, + "end_line": 23 + }, + { + "name": "do_action", + "line": 28, + "end_line": 28 + } + ] + }, + "hooks": [ + { + "name": "deprecated_filter", + "line": 23, + "end_line": 23, + "type": "filter", + "arguments": [ + "$var" + ], + "doc": { + "description": "This filter should be marked as deprecated since 1.0", + "long_description": "", + "tags": [] + } + }, + { + "name": "deprecated_action", + "line": 28, + "end_line": 28, + "type": "action", + "arguments": [], + "doc": { + "description": "This action should be marked as deprecated since 1.0", + "long_description": "", + "tags": [] + } + } + ], + "functions": [ + { + "name": "should_be_deprecated", + "namespace": "global", + "aliases": [], + "line": 18, + "end_line": 18, + "arguments": [], + "doc": { + "description": "This function should be marked as deprecated since 1.0", + "long_description": "", + "tags": [] + }, + "hooks": [] + } + ], + "classes": [ + { + "name": "Should_Be_Deprectated", + "namespace": "global", + "line": 7, + "end_line": 13, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [ + { + "name": "should_be_deprecated", + "namespace": "", + "aliases": [], + "line": 12, + "end_line": 12, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "This method should be marked as deprecated since 1.0", + "long_description": "", + "tags": [] + } + } + ], + "doc": { + "description": "This class should be marked as deprecated since 1.0", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/source__filters.json b/tests/golden/snapshots/source__filters.json new file mode 100644 index 00000000..d00e817f --- /dev/null +++ b/tests/golden/snapshots/source__filters.json @@ -0,0 +1,477 @@ +[ + { + "file": { + "description": "This is a well documented filter.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "param", + "content": "Key/value pairs of strings.", + "types": [ + "array" + ], + "variable": "$mce_translation" + }, + { + "name": "param", + "content": "Locale.", + "types": [ + "string" + ], + "variable": "$mce_locale" + } + ] + }, + "path": "filters.php", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "apply_filters", + "line": 13, + "end_line": 13 + }, + { + "name": "apply_filters", + "line": 26, + "end_line": 26 + }, + { + "name": "apply_filters", + "line": 39, + "end_line": 39 + }, + { + "name": "apply_filters", + "line": 50, + "end_line": 50 + }, + { + "name": "apply_filters", + "line": 61, + "end_line": 61 + }, + { + "name": "apply_filters", + "line": 72, + "end_line": 72 + }, + { + "name": "apply_filters", + "line": 84, + "end_line": 84 + }, + { + "name": "apply_filters", + "line": 96, + "end_line": 96 + }, + { + "name": "apply_filters", + "line": 108, + "end_line": 108 + }, + { + "name": "apply_filters", + "line": 122, + "end_line": 122 + }, + { + "name": "apply_filters", + "line": 124, + "end_line": 124 + }, + { + "name": "apply_filters", + "line": 126, + "end_line": 126 + }, + { + "name": "apply_filters", + "line": 128, + "end_line": 128 + } + ] + }, + "hooks": [ + { + "name": "good_static_filter", + "line": 13, + "end_line": 13, + "type": "filter", + "arguments": [ + "$mce_translation", + "$mce_locale" + ], + "doc": { + "description": "This is a well documented filter.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "param", + "content": "Key/value pairs of strings.", + "types": [ + "array" + ], + "variable": "$mce_translation" + }, + { + "name": "param", + "content": "Locale.", + "types": [ + "string" + ], + "variable": "$mce_locale" + } + ] + } + }, + { + "name": "good_dynamic_filter_{$option}", + "line": 26, + "end_line": 26, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "This is a well documented dynamic filter.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.6.0" + }, + { + "name": "param", + "content": "The new, unserialized option value.", + "types": [ + "mixed" + ], + "variable": "$value" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + } + ] + } + }, + { + "name": "good_double_quotes_dynamic_filter_{$option}", + "line": 39, + "end_line": 39, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "This is a well documented dynamic filter.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.6.0" + }, + { + "name": "param", + "content": "The new, unserialized option value.", + "types": [ + "mixed" + ], + "variable": "$value" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + } + ] + } + }, + { + "name": "missing_since_static_filter", + "line": 50, + "end_line": 50, + "type": "filter", + "arguments": [ + "$mce_translation", + "$mce_locale" + ], + "doc": { + "description": "This is a filter missing the \"since\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "param", + "content": "The new, unserialized option value.", + "types": [ + "mixed" + ], + "variable": "$value" + }, + { + "name": "param", + "content": "Locale.", + "types": [ + "string" + ], + "variable": "$mce_locale" + } + ] + } + }, + { + "name": "missing_since_dynamic_filter_{$option}", + "line": 61, + "end_line": 61, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "This is a dynamic filter missing the \"since\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "param", + "content": "The new, unserialized option value.", + "types": [ + "mixed" + ], + "variable": "$value" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + } + ] + } + }, + { + "name": "missing_since_double_quotes_dynamic_filter_{$option}", + "line": 72, + "end_line": 72, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "This is a dynamic filter missing the \"since\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "param", + "content": "The new, unserialized option value.", + "types": [ + "mixed" + ], + "variable": "$value" + }, + { + "name": "param", + "content": "The old option value.", + "types": [ + "mixed" + ], + "variable": "$old_value" + } + ] + } + }, + { + "name": "missing_param_static_filter", + "line": 84, + "end_line": 84, + "type": "filter", + "arguments": [ + "$mce_translation", + "$mce_locale" + ], + "doc": { + "description": "This is a filter missing one \"param\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.6.0" + }, + { + "name": "param", + "content": "Locale.", + "types": [ + "string" + ], + "variable": "$mce_locale" + } + ] + } + }, + { + "name": "missing_param_dynamic_filter_{$option}", + "line": 96, + "end_line": 96, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "This is a dynamic filter missing one \"param\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.6.0" + }, + { + "name": "param", + "content": "Locale.", + "types": [ + "string" + ], + "variable": "$mce_locale" + } + ] + } + }, + { + "name": "missing_param_double_quotes_dynamic_filter_{$option}", + "line": 108, + "end_line": 108, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "This is a dynamic filter missing one \"param\" line.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.6.0" + }, + { + "name": "param", + "content": "Locale.", + "types": [ + "string" + ], + "variable": "$mce_locale" + } + ] + } + }, + { + "name": "multiple_since_tags", + "line": 122, + "end_line": 122, + "type": "filter", + "arguments": [ + "$first_parameter", + "$second_parameter" + ], + "doc": { + "description": "This is a filter with multiple since tags", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "1.0" + }, + { + "name": "since", + "content": "1.9", + "description": "Added a new parameter to the filter" + }, + { + "name": "param", + "content": "", + "types": [ + "string" + ], + "variable": "$first_parameter" + }, + { + "name": "param", + "content": "", + "types": [ + "string" + ], + "variable": "$second_parameter" + } + ] + } + }, + { + "name": "no_doc_static_filter", + "line": 124, + "end_line": 124, + "type": "filter", + "arguments": [ + "$mce_translation", + "$mce_locale" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "no_doc_dynamic_filter_{$option}", + "line": 126, + "end_line": 126, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "no_doc_double_quotes_dynamic_filter_{$option}", + "line": 128, + "end_line": 128, + "type": "filter", + "arguments": [ + "$value", + "$old_value" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/source__functions.json b/tests/golden/snapshots/source__functions.json new file mode 100644 index 00000000..59326372 --- /dev/null +++ b/tests/golden/snapshots/source__functions.json @@ -0,0 +1,575 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "functions.php", + "root": "{{ROOT}}", + "functions": [ + { + "name": "good_doc_function", + "namespace": "global", + "aliases": [], + "line": 18, + "end_line": 20, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_long_description_function", + "namespace": "global", + "aliases": [], + "line": 35, + "end_line": 37, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_description_function", + "namespace": "global", + "aliases": [], + "line": 50, + "end_line": 52, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "3.9.4" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_since_function", + "namespace": "global", + "aliases": [], + "line": 67, + "end_line": 69, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "

Long description.

", + "tags": [ + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_see_function", + "namespace": "global", + "aliases": [], + "line": 85, + "end_line": 87, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_link_function", + "namespace": "global", + "aliases": [], + "line": 103, + "end_line": 105, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_global_function", + "namespace": "global", + "aliases": [], + "line": 121, + "end_line": 123, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_param_function", + "namespace": "global", + "aliases": [], + "line": 138, + "end_line": 140, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "return", + "content": "Description.", + "types": [ + "\\type" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "missing_return_function", + "namespace": "global", + "aliases": [], + "line": 156, + "end_line": 158, + "arguments": [ + { + "name": "$thing", + "default": null, + "type": "" + }, + { + "name": "$var", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Short description. (use period)", + "long_description": "

Long description.

", + "tags": [ + { + "name": "since", + "content": "3.9.0" + }, + { + "name": "see", + "content": "relied on", + "refers": "Function/method/class" + }, + { + "name": "link", + "content": "URL", + "link": "URL" + }, + { + "name": "global", + "content": "type $varname Short description." + }, + { + "name": "param", + "content": "Description.", + "types": [ + "\\type" + ], + "variable": "$thing" + }, + { + "name": "param", + "content": "Optional. Description.", + "types": [ + "\\type" + ], + "variable": "$var" + } + ] + }, + "hooks": [] + } + ] + } +] diff --git a/tests/golden/snapshots/source__good-class.json b/tests/golden/snapshots/source__good-class.json new file mode 100644 index 00000000..32699a65 --- /dev/null +++ b/tests/golden/snapshots/source__good-class.json @@ -0,0 +1,341 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "good-class.php", + "root": "{{ROOT}}", + "classes": [ + { + "name": "Good_Doc_Class", + "namespace": "global", + "line": 12, + "end_line": 91, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [ + { + "name": "$good_doc_private_property_from_good_doc_class", + "line": 21, + "end_line": 21, + "default": "array()", + "static": false, + "visibility": "private", + "doc": { + "description": "This is a properly documented private property.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "private" + } + ] + } + }, + { + "name": "$good_doc_protected_property_from_good_doc_class", + "line": 30, + "end_line": 30, + "default": "array()", + "static": false, + "visibility": "protected", + "doc": { + "description": "This is a properly documented protected property.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "protected" + } + ] + } + }, + { + "name": "$good_doc_public_property_from_good_doc_class", + "line": 39, + "end_line": 39, + "default": "array()", + "static": false, + "visibility": "public", + "doc": { + "description": "This is a properly documented public property.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "var", + "content": "", + "types": [ + "array" + ], + "variable": "" + }, + { + "name": "access", + "content": "public" + } + ] + } + } + ], + "methods": [ + { + "name": "good_doc_public_method_from_good_doc_class", + "namespace": "", + "aliases": [], + "line": 54, + "end_line": 56, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "This is a properly documented public method.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "good_doc_protected_method_from_good_doc_class", + "namespace": "", + "aliases": [], + "line": 71, + "end_line": 73, + "final": false, + "abstract": false, + "static": false, + "visibility": "protected", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "This is a properly documented protected method.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + }, + { + "name": "good_doc_private_method_from_good_doc_class", + "namespace": "", + "aliases": [], + "line": 88, + "end_line": 90, + "final": false, + "abstract": false, + "static": false, + "visibility": "private", + "arguments": [ + { + "name": "$code", + "default": "''", + "type": "" + }, + { + "name": "$message", + "default": "''", + "type": "" + }, + { + "name": "$data", + "default": "''", + "type": "" + } + ], + "doc": { + "description": "This is a properly documented private method.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "since", + "content": "2.1.0" + }, + { + "name": "param", + "content": "Error code", + "types": [ + "string", + "int" + ], + "variable": "$code" + }, + { + "name": "param", + "content": "Error message", + "types": [ + "string" + ], + "variable": "$message" + }, + { + "name": "param", + "content": "Optional. Error data.", + "types": [ + "mixed" + ], + "variable": "$data" + }, + { + "name": "return", + "content": "", + "types": [ + "\\WP_Error" + ] + } + ] + } + } + ], + "doc": { + "description": "This is a properly documented class.", + "long_description": "

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

", + "tags": [ + { + "name": "package", + "content": "WordPress" + }, + { + "name": "since", + "content": "2.1.0" + } + ] + } + } + ] + } +] diff --git a/tests/golden/snapshots/source__relationships.json b/tests/golden/snapshots/source__relationships.json new file mode 100644 index 00000000..42457c07 --- /dev/null +++ b/tests/golden/snapshots/source__relationships.json @@ -0,0 +1,494 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "relationships.php", + "root": "{{ROOT}}", + "functions": [ + { + "name": "relate_function1", + "namespace": "global", + "aliases": [], + "line": 3, + "end_line": 5, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "functions": [ + { + "name": "relate_function2", + "line": 4, + "end_line": 4 + } + ] + } + }, + { + "name": "relate_function2", + "namespace": "global", + "aliases": [], + "line": 7, + "end_line": 15, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [ + { + "name": "relate-hook", + "line": 12, + "end_line": 12, + "type": "filter", + "arguments": [ + "true" + ], + "doc": { + "description": "A relationship hook", + "long_description": "", + "tags": [] + } + } + ], + "uses": { + "functions": [ + { + "name": "apply_filters", + "line": 12, + "end_line": 12 + } + ] + } + }, + { + "name": "relate_function3", + "namespace": "global", + "aliases": [], + "line": 17, + "end_line": 19, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "methods": [ + { + "name": "relate_method1", + "class": "\\wpdb", + "static": true, + "line": 18, + "end_line": 18 + } + ] + } + }, + { + "name": "relate_function4", + "namespace": "global", + "aliases": [], + "line": 21, + "end_line": 25, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "methods": [ + { + "name": "__construct", + "class": "\\wpdb", + "static": false, + "line": 22, + "end_line": 22 + }, + { + "name": "relate_method4", + "class": "$wpdb", + "static": false, + "line": 24, + "end_line": 24 + } + ] + } + }, + { + "name": "relate_function5", + "namespace": "global", + "aliases": [], + "line": 27, + "end_line": 29, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "methods": [ + { + "name": "some_function", + "class": "\\wpdb::relate_method2()", + "static": false, + "line": 28, + "end_line": 28 + }, + { + "name": "relate_method2", + "class": "\\wpdb", + "static": true, + "line": 28, + "end_line": 28 + } + ] + } + }, + { + "name": "relate_function6", + "namespace": "global", + "aliases": [], + "line": 31, + "end_line": 33, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "methods": [ + { + "name": "relate_method1", + "class": "wp_screen()", + "static": false, + "line": 32, + "end_line": 32 + } + ], + "functions": [ + { + "name": "wp_screen", + "line": 32, + "end_line": 32 + } + ] + } + } + ], + "classes": [ + { + "name": "wpdb", + "namespace": "global", + "line": 35, + "end_line": 76, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [ + { + "name": "__construct", + "namespace": "", + "aliases": [], + "line": 37, + "end_line": 37, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "relate_method1", + "namespace": "", + "aliases": [], + "line": 39, + "end_line": 41, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "relate_method2", + "class": "\\wpdb", + "static": true, + "line": 40, + "end_line": 40 + } + ] + } + }, + { + "name": "relate_method2", + "namespace": "", + "aliases": [], + "line": 43, + "end_line": 53, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "functions": [ + { + "name": "apply_filters", + "line": 52, + "end_line": 52 + } + ] + }, + "hooks": [ + { + "name": "meh-hook", + "line": 52, + "end_line": 52, + "type": "filter", + "arguments": [ + "$meh" + ], + "doc": { + "description": "Filter a aCustomize setting value in un-slashed form.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "3.5.0" + }, + { + "name": "param", + "content": "Value of the setting.", + "types": [ + "mixed" + ], + "variable": "$value" + }, + { + "name": "param", + "content": "WP_Customize_Setting instance.", + "types": [ + "\\WP_Customize_Setting" + ], + "variable": "$this" + } + ] + } + } + ] + }, + { + "name": "relate_method3", + "namespace": "", + "aliases": [], + "line": 55, + "end_line": 57, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "functions": [ + { + "name": "relate_function1", + "line": 56, + "end_line": 56 + } + ] + } + }, + { + "name": "relate_method4", + "namespace": "", + "aliases": [], + "line": 59, + "end_line": 61, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "functions": [ + { + "name": "relate_function2", + "line": 60, + "end_line": 60 + } + ] + } + }, + { + "name": "relate_method5", + "namespace": "", + "aliases": [], + "line": 63, + "end_line": 65, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "relate_method4", + "class": "\\wpdb", + "static": false, + "line": 64, + "end_line": 64 + } + ] + } + }, + { + "name": "relate_method6", + "namespace": "", + "aliases": [], + "line": 67, + "end_line": 69, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "relate_method1", + "class": "\\wpdb", + "static": true, + "line": 68, + "end_line": 68 + } + ] + } + }, + { + "name": "relate_method7", + "namespace": "", + "aliases": [], + "line": 71, + "end_line": 75, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "uses": { + "methods": [ + { + "name": "relate_method5", + "class": "$wpdb", + "static": false, + "line": 74, + "end_line": 74 + } + ] + } + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "WP_Screen", + "namespace": "global", + "line": 78, + "end_line": 80, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [ + { + "name": "relate_method1", + "namespace": "", + "aliases": [], + "line": 79, + "end_line": 79, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] From 89221065bbd8e816ca5b0983b1eb950779e62a2e Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 18:56:18 -0400 Subject: [PATCH 03/20] fix: bump phpunit-polyfills to ^1.1 for WP tests - Update yoast/phpunit-polyfills ^1.0 -> ^1.1 (1.0.3 => 1.1.5) - Required by the modern WordPress PHPUnit suite (needs Polyfills >= 1.1.0) Fixes the 'Version mismatch detected for the PHPUnit Polyfills' fatal so the export + import suite runs green. Parser deps left untouched. Addresses #244. --- composer.json | 2 +- composer.lock | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index aa09726d..f8e69ce4 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "require-dev" : { "phpunit/phpunit": "^7", "spatie/phpunit-watcher": "^1.23", - "yoast/phpunit-polyfills": "^1.0" + "yoast/phpunit-polyfills": "^1.1" }, "scripts" : { "test": "phpunit", diff --git a/composer.lock b/composer.lock index 55aea9d6..c76ba114 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "96d200642f6aded313abd6eb270909ee", + "content-hash": "a45fa2f4af99cabc2b162423ed60e886", "packages": [ { "name": "composer/installers", @@ -3522,16 +3522,16 @@ }, { "name": "yoast/phpunit-polyfills", - "version": "1.0.3", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "5ea3536428944955f969bc764bbe09738e151ada" + "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/5ea3536428944955f969bc764bbe09738e151ada", - "reference": "5ea3536428944955f969bc764bbe09738e151ada", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/41aaac462fbd80feb8dd129e489f4bbc53fe26b0", + "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0", "shasum": "" }, "require": { @@ -3539,13 +3539,14 @@ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { - "yoast/yoastcs": "^2.2.0" + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.x-dev", - "dev-develop": "1.x-dev" + "dev-main": "4.x-dev" } }, "autoload": { @@ -3577,9 +3578,10 @@ ], "support": { "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2021-11-23T01:37:03+00:00" + "time": "2025-08-10T04:54:36+00:00" }, { "name": "yosymfony/resource-watcher", @@ -3650,6 +3652,6 @@ "platform": { "php": ">=5.4" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } From e616b2569a7c5097a4fcb7d040198dcd573319b2 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 18:57:47 -0400 Subject: [PATCH 04/20] docs: document WP integration suite + green baseline - How to run the export+import suite via wp-env (npm scripts or npx) - Record the baseline: 22 tests / 125 assertions on PHP 7.4 - Note the GitHub HTTPS->SSH git-rewrite gotcha and the per-process fix --- tests/phpunit/README.md | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/phpunit/README.md diff --git a/tests/phpunit/README.md b/tests/phpunit/README.md new file mode 100644 index 00000000..ce4abb0a --- /dev/null +++ b/tests/phpunit/README.md @@ -0,0 +1,56 @@ +# WordPress integration test suite + +These tests exercise the **WordPress side** of the plugin — the importer turning +parsed data into posts/taxonomies/meta, and the related templates — so they need +a real WordPress install. They complement the [golden-master parser +harness](../golden/README.md), which is WordPress-free and only covers +`parse_files()`. + +- `tests/phpunit/tests/export/**` — parser output assertions +- `tests/phpunit/tests/import/file.php` — end-to-end importer test (posts, terms, + `_wp-parser_*` meta) + +## Baseline status + +On the **old parser stack** (PHP 7.4, `phpdocumentor/reflection ~3.0`) the suite +is green: + +``` +PHPUnit 7.5.20 — OK (22 tests, 125 assertions) +``` + +This is the regression baseline. Every migration stage must keep it green (run on +PHP 7.4 until Stage 2 moves the floor to PHP 8.2 + PHPUnit 9). + +## Running locally (wp-env / Docker) + +Requires Docker and Node. The environment is defined in `.wp-env.json` (PHP 7.4, +plugin + Posts-to-Posts). + +```bash +npm install # installs @wordpress/env +npm run test:phpunit:setup # wp-env start + composer install (in the container) +npm run test:phpunit # runs phpunit -c phpunit.xml.dist in tests-wordpress +``` + +Or drive `@wordpress/env` directly without the npm scripts: + +```bash +npx @wordpress/env start +npx @wordpress/env run --env-cwd='wp-content/plugins/phpdoc-parser' tests-wordpress composer install +npx @wordpress/env run --env-cwd='wp-content/plugins/phpdoc-parser' tests-wordpress \ + vendor/phpunit/phpunit/phpunit -c phpunit.xml.dist +``` + +### Gotcha: GitHub HTTPS→SSH git rewrite + +`wp-env start` clones `WordPress/wordpress-develop` over **HTTPS**. If your global +git config rewrites GitHub HTTPS to SSH (e.g. +`url."git@github.com:".insteadOf "https://github.com/"`) and outbound SSH port 22 +is blocked, the clone fails with `Connection to ... port 22: Operation timed out`. + +Neutralize the rewrite for that one command — your config files stay untouched: + +```bash +GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null npx @wordpress/env start +``` From 2a96c4831730b6677c770a194525bae5ad785323 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 20:51:43 -0400 Subject: [PATCH 05/20] build: migrate to PHP 8.2 and modern parser stack - Drop phpdocumentor/reflection ~3.0; add nikic/php-parser ^5, phpstan/phpdoc-parser ^2, reflection-docblock ^6, type-resolver ^2 - Bump php to >=8.2, phpunit to ^9, polyfills to ^2; pin resolve platform to php 8.2 for reproducible locks - .wp-env.json to PHP 8.2; CI matrix to 8.2/8.3/8.4; phpunit.xml.dist to the v9 schema (coverage/include) - Guard parser-dependent tests to skip (not fatal) while File_Reflector is rewritten in Stage 4 The old parser is intentionally non-functional until Stage 4; both suites report skips. The PHP 7.4 golden snapshots are unchanged. --- .github/workflows/unit-test.yml | 8 +- .gitignore | 1 + .wp-env.json | 2 +- composer.json | 16 +- composer.lock | 2237 ++++++++++++-------- phpunit.xml.dist | 11 +- tests/golden/golden.php | 19 + tests/golden/test-golden-master.php | 6 + tests/phpunit/includes/export-testcase.php | 14 + 9 files changed, 1405 insertions(+), 909 deletions(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 9e5ea6a9..8507df8e 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -12,7 +12,9 @@ jobs: fail-fast: false matrix: php: - - '7.4' + - '8.2' + - '8.3' + - '8.4' env: WP_ENV_PHP_VERSION: ${{ matrix.php }} @@ -25,9 +27,7 @@ jobs: run: npm install - name: Setup Environment - run: | - rm composer.lock - npm run setup + run: npm run setup - name: Test run: npm run test diff --git a/.gitignore b/.gitignore index 82f9b225..7e15528b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vendor coverage node_modules +.phpunit.result.cache diff --git a/.wp-env.json b/.wp-env.json index aaf14faf..357dc595 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,5 +1,5 @@ { - "phpVersion": "7.4", + "phpVersion": "8.2", "plugins": [ ".", "https://downloads.wordpress.org/plugin/posts-to-posts.latest-stable.zip" diff --git a/composer.json b/composer.json index f8e69ce4..5527b571 100644 --- a/composer.json +++ b/composer.json @@ -20,18 +20,21 @@ "issues": "https://github.com/WordPress/phpdoc-parser/issues" }, "require" : { - "php" : ">=5.4", - "composer/installers" : "~1.0", - "phpdocumentor/reflection" : "~3.0", + "php" : ">=8.2", + "composer/installers" : "^1.0 || ^2.0", + "nikic/php-parser" : "^5.0", + "phpstan/phpdoc-parser" : "^2.0", + "phpdocumentor/reflection-docblock": "^6.0", + "phpdocumentor/type-resolver" : "^2.0", "erusev/parsedown" : "~1.7", "scribu/lib-posts-to-posts": "dev-master@dev", "scribu/scb-framework" : "dev-master@dev", "psr/log" : "~1.0" }, "require-dev" : { - "phpunit/phpunit": "^7", + "phpunit/phpunit": "^9", "spatie/phpunit-watcher": "^1.23", - "yoast/phpunit-polyfills": "^1.1" + "yoast/phpunit-polyfills": "^2.0" }, "scripts" : { "test": "phpunit", @@ -43,6 +46,9 @@ "files" : ["lib/runner.php", "lib/template.php"] }, "config": { + "platform": { + "php": "8.2" + }, "allow-plugins": { "composer/installers": true }, diff --git a/composer.lock b/composer.lock index c76ba114..37b43abf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,43 +4,41 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a45fa2f4af99cabc2b162423ed60e886", + "content-hash": "c33d9a0a445fbf3ecd891ef43c6e92c0", "packages": [ { "name": "composer/installers", - "version": "v1.12.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19" + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19", - "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0 || ^2.0" - }, - "replace": { - "roundcube/plugin-installer": "*", - "shama/baton": "*" + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" }, "require-dev": { - "composer/composer": "1.6.* || ^2.0", - "composer/semver": "^1 || ^3", - "phpstan/phpstan": "^0.12.55", - "phpstan/phpstan-phpunit": "^0.12.16", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.3" + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" }, "type": "composer-plugin", "extra": { "class": "Composer\\Installers\\Plugin", "branch-alias": { - "dev-main": "1.x-dev" - } + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true }, "autoload": { "psr-4": { @@ -61,7 +59,6 @@ "description": "A multi-framework Composer library installer", "homepage": "https://composer.github.io/installers/", "keywords": [ - "Craft", "Dolibarr", "Eliasis", "Hurad", @@ -82,7 +79,6 @@ "Whmcs", "WolfCMS", "agl", - "aimeos", "annotatecms", "attogram", "bitrix", @@ -91,6 +87,7 @@ "cockpit", "codeigniter", "concrete5", + "concreteCMS", "croogo", "dokuwiki", "drupal", @@ -101,7 +98,6 @@ "grav", "installer", "itop", - "joomla", "known", "kohana", "laravel", @@ -110,6 +106,7 @@ "magento", "majima", "mako", + "matomo", "mediawiki", "miaoxing", "modulework", @@ -129,9 +126,7 @@ "silverstripe", "sydes", "sylius", - "symfony", "tastyigniter", - "typo3", "wordpress", "yawik", "zend", @@ -139,7 +134,7 @@ ], "support": { "issues": "https://github.com/composer/installers/issues", - "source": "https://github.com/composer/installers/tree/v1.12.0" + "source": "https://github.com/composer/installers/tree/v2.3.0" }, "funding": [ { @@ -155,28 +150,76 @@ "type": "tidelift" } ], - "time": "2021-09-13T08:19:44+00:00" + "time": "2024-06-24T20:46:46+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" }, { "name": "erusev/parsedown", - "version": "1.7.4", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + "reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/96baaad00f71ba04d76e45b4620f54d3beabd6f7", + "reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=5.3.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35" + "phpunit/phpunit": "^7.5|^8.5|^9.6" }, "type": "library", "autoload": { @@ -203,38 +246,53 @@ ], "support": { "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" + "source": "https://github.com/erusev/parsedown/tree/1.8.0" }, - "time": "2019-12-30T22:54:17+00:00" + "funding": [ + { + "url": "https://github.com/erusev", + "type": "github" + } + ], + "time": "2026-02-16T11:41:01+00:00" }, { "name": "nikic/php-parser", - "version": "v1.4.1", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=5.3" + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "5.x-dev" } }, "autoload": { - "files": [ - "lib/bootstrap.php" - ] + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -252,103 +310,105 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/1.x" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2015-09-19T14:15:08+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { - "name": "phpdocumentor/reflection", - "version": "3.0.1", + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/Reflection.git", - "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d" + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d", - "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "nikic/php-parser": "^1.0", - "php": ">=5.3.3", - "phpdocumentor/reflection-docblock": "~2.0", - "psr/log": "~1.0" - }, - "require-dev": { - "behat/behat": "~2.4", - "mockery/mockery": "~0.8", - "phpunit/phpunit": "~4.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/", - "tests/unit/", - "tests/mocks/" - ] + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Reflection library to do Static Analysis for PHP Projects", + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", "homepage": "http://www.phpdoc.org", "keywords": [ + "FQSEN", "phpDocumentor", "phpdoc", "reflection", "static analysis" ], "support": { - "issues": "https://github.com/phpDocumentor/Reflection/issues", - "source": "https://github.com/phpDocumentor/Reflection/tree/master" + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "time": "2016-05-21T08:42:32+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "2.0.5", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", - "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", "shasum": "" }, "require": { - "php": ">=5.3.3" + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] + "psr-4": { + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -358,14 +418,124 @@ "authors": [ { "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/2.x" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + }, + "time": "2026-03-18T20:49:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + }, + "time": "2026-01-06T21:53:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2016-01-25T08:17:30+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { "name": "psr/log", @@ -498,6 +668,72 @@ "wiki": "https://github.com/scribu/wp-scb-framework/wiki" }, "time": "2020-03-15T20:51:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.4.0" + }, + "time": "2026-05-20T13:07:01+00:00" } ], "packages-dev": [ @@ -579,25 +815,25 @@ }, { "name": "clue/term-react", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/clue/reactphp-term.git", - "reference": "eb6eb063eda04a714ef89f066586a2c49588f7ca" + "reference": "00f297dc597eaee2ebf98af8f27cca5d21d60fa3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-term/zipball/eb6eb063eda04a714ef89f066586a2c49588f7ca", - "reference": "eb6eb063eda04a714ef89f066586a2c49588f7ca", + "url": "https://api.github.com/repos/clue/reactphp-term/zipball/00f297dc597eaee2ebf98af8f27cca5d21d60fa3", + "reference": "00f297dc597eaee2ebf98af8f27cca5d21d60fa3", "shasum": "" }, "require": { "php": ">=5.3", - "react/stream": "^1.0 || ^0.7" + "react/stream": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/event-loop": "^1.2" }, "type": "library", "autoload": { @@ -636,7 +872,7 @@ ], "support": { "issues": "https://github.com/clue/reactphp-term/issues", - "source": "https://github.com/clue/reactphp-term/tree/v1.3.0" + "source": "https://github.com/clue/reactphp-term/tree/v1.4.0" }, "funding": [ { @@ -648,20 +884,20 @@ "type": "github" } ], - "time": "2020-11-06T11:50:12+00:00" + "time": "2024-01-30T10:22:09+00:00" }, { "name": "clue/utf8-react", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/clue/reactphp-utf8.git", - "reference": "8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96" + "reference": "d5cd04d39cb5457aa5df830b7c4b301d2694217e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-utf8/zipball/8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96", - "reference": "8bc3f8c874cdf642c8f10f9ae93aadb8cd63da96", + "url": "https://api.github.com/repos/clue/reactphp-utf8/zipball/d5cd04d39cb5457aa5df830b7c4b301d2694217e", + "reference": "d5cd04d39cb5457aa5df830b7c4b301d2694217e", "shasum": "" }, "require": { @@ -669,7 +905,7 @@ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4 || ^0.3" }, "require-dev": { - "phpunit/phpunit": "^9.3 ||^5.7 || ^4.8", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "react/stream": "^1.0 || ^0.7" }, "type": "library", @@ -699,7 +935,7 @@ ], "support": { "issues": "https://github.com/clue/reactphp-utf8/issues", - "source": "https://github.com/clue/reactphp-utf8/tree/v1.2.0" + "source": "https://github.com/clue/reactphp-utf8/tree/v1.3.0" }, "funding": [ { @@ -711,34 +947,34 @@ "type": "github" } ], - "time": "2020-11-06T11:48:09+00:00" + "time": "2023-12-06T14:52:17+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -765,7 +1001,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -781,32 +1017,32 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "evenement/evenement", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/igorw/evenement.git", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { - "psr-0": { - "Evenement": "src" + "psr-4": { + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -826,37 +1062,48 @@ ], "support": { "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/master" + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2017-07-23T21:35:13+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { "name": "jolicode/jolinotif", - "version": "v2.4.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/jolicode/JoliNotif.git", - "reference": "a15bfc0d5aef432f150385924ede4e099643edb7" + "reference": "6f6490f8fd062deda601a8bfc03ea03305253d29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jolicode/JoliNotif/zipball/a15bfc0d5aef432f150385924ede4e099643edb7", - "reference": "a15bfc0d5aef432f150385924ede4e099643edb7", + "url": "https://api.github.com/repos/jolicode/JoliNotif/zipball/6f6490f8fd062deda601a8bfc03ea03305253d29", + "reference": "6f6490f8fd062deda601a8bfc03ea03305253d29", "shasum": "" }, "require": { - "php": ">=7.4", - "symfony/process": "^4.0|^5.0|^6.0" + "jolicode/php-os-helper": "^0.3", + "php": ">=8.2", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^3", + "symfony/process": "^6.4 || ^7.3 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.0", - "symfony/finder": "^5.0", - "symfony/phpunit-bridge": "^5.0" + "monolog/monolog": "^3.9", + "symfony/finder": "^6.4 || ^7.3 || ^8.0", + "symfony/phpunit-bridge": "^7.3 || ^8.0" + }, + "suggest": { + "ext-ffi": "Needed to send notifications via libnotify on Linux" }, "bin": [ "jolinotif" ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Joli\\JoliNotif\\": "src/" @@ -882,7 +1129,7 @@ ], "support": { "issues": "https://github.com/jolicode/JoliNotif/issues", - "source": "https://github.com/jolicode/JoliNotif/tree/v2.4.0" + "source": "https://github.com/jolicode/JoliNotif/tree/v3.2.0" }, "funding": [ { @@ -890,20 +1137,70 @@ "type": "tidelift" } ], - "time": "2021-12-01T16:20:42+00:00" + "time": "2025-10-16T21:31:31+00:00" + }, + { + "name": "jolicode/php-os-helper", + "version": "v0.3.0", + "source": { + "type": "git", + "url": "https://github.com/jolicode/php-os-helper.git", + "reference": "e71596ae67d477fe214a5908f5522b9e4d19fd37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jolicode/php-os-helper/zipball/e71596ae67d477fe214a5908f5522b9e4d19fd37", + "reference": "e71596ae67d477fe214a5908f5522b9e4d19fd37", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "JoliCode\\PhpOsHelper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Loïck Piera", + "email": "pyrech@gmail.com" + } + ], + "description": "Helpers to detect the OS of the machine where PHP is running.", + "keywords": [ + "linux", + "os", + "osx", + "php", + "windows" + ], + "support": { + "issues": "https://github.com/jolicode/php-os-helper/issues", + "source": "https://github.com/jolicode/php-os-helper/tree/v0.3.0" + }, + "time": "2025-10-11T13:02:06+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -911,11 +1208,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -941,7 +1239,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -949,32 +1247,34 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1006,26 +1306,32 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2018-07-08T19:23:20+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "2.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -1057,168 +1363,112 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/master" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2018-07-08T19:19:57+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpspec/prophecy", - "version": "v1.10.3", + "name": "phpunit/php-code-coverage", + "version": "9.2.32", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "451c3cd1418cf640de218914901e51b064abb093" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", - "reference": "451c3cd1418cf640de218914901e51b064abb093", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10.x-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" + "coverage", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, - "time": "2020-03-05T15:02:03+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "6.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "suggest": { - "ext-xdebug": "^2.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master" - }, - "time": "2018-10-31T16:06:48+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1245,7 +1495,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -1253,26 +1503,38 @@ "type": "github" } ], - "time": "2021-12-02T12:42:26+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { - "name": "phpunit/php-text-template", - "version": "1.2.1", + "name": "phpunit/php-invoker", + "version": "3.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -1289,41 +1551,47 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "template" + "process" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" }, - "time": "2015-06-21T13:50:34+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" }, { - "name": "phpunit/php-timer", - "version": "2.1.3", + "name": "phpunit/php-text-template", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1342,14 +1610,14 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "timer" + "template" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" }, "funding": [ { @@ -1357,33 +1625,32 @@ "type": "github" } ], - "time": "2020-11-30T08:20:02+00:00" + "time": "2020-10-26T05:33:50+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "3.1.3", + "name": "phpunit/php-timer", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1398,17 +1665,18 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "tokenizer" + "timer" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.3" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" }, "funding": [ { @@ -1416,58 +1684,54 @@ "type": "github" } ], - "abandoned": true, - "time": "2021-07-26T12:15:06+00:00" + "time": "2020-10-26T13:16:10+00:00" }, { "name": "phpunit/phpunit", - "version": "7.5.20", + "version": "9.6.34", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" + "reference": "b36f02317466907a230d3aa1d34467041271ef4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a", + "reference": "b36f02317466907a230d3aa1d34467041271ef4a", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", - "sebastian/version": "^2.0.1" - }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, - "require-dev": { - "ext-pdo": "*" + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.10", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.8", + "sebastian/global-state": "^5.0.8", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", + "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -1475,10 +1739,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -1503,28 +1770,56 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34" }, - "time": "2020-01-08T08:45:45+00:00" + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:45:00+00:00" }, { "name": "psr/container", - "version": "1.1.2", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -1551,39 +1846,37 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-11-05T16:50:12+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "react/event-loop", - "version": "v1.3.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137" + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/187fb56f46d424afb6ec4ad089269c72eec2e137", - "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "suggest": { - "ext-event": "~1.0 for ExtEventLoop", - "ext-pcntl": "For signal handling support when using the StreamSelectLoop", - "ext-uv": "* for ExtUvLoop" + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, "type": "library", "autoload": { "psr-4": { - "React\\EventLoop\\": "src" + "React\\EventLoop\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1619,32 +1912,28 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.3.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-03-17T11:10:22+00:00" + "time": "2025-11-17T20:46:25+00:00" }, { "name": "react/stream", - "version": "v1.2.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9" + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/7a423506ee1903e89f1e08ec5f0ed430ff784ae9", - "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", "shasum": "" }, "require": { @@ -1654,12 +1943,12 @@ }, "require-dev": { "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { "psr-4": { - "React\\Stream\\": "src" + "React\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1701,44 +1990,152 @@ ], "support": { "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.2.0" + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { - "url": "https://github.com/WyriHaximus", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ { - "url": "https://github.com/clue", + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2021-07-11T12:37:55+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1760,7 +2157,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" }, "funding": [ { @@ -1768,34 +2165,34 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.3", + "version": "4.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", - "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", "shasum": "" }, "require": { - "php": ">=7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1834,7 +2231,76 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:22:56+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -1842,33 +2308,33 @@ "type": "github" } ], - "time": "2020-11-30T08:04:30+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "3.0.3", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1900,7 +2366,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -1908,27 +2374,27 @@ "type": "github" } ], - "time": "2020-11-30T07:59:04+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", - "version": "4.2.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-posix": "*" @@ -1936,7 +2402,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -1963,7 +2429,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -1971,34 +2437,34 @@ "type": "github" } ], - "time": "2020-11-30T07:53:42+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.4", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { - "php": ">=7.0", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2033,42 +2499,57 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2021-11-11T13:51:24+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -2076,7 +2557,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2101,36 +2582,111 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" }, - "time": "2017-04-27T15:39:26+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:10:35+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2152,7 +2708,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" }, "funding": [ { @@ -2160,32 +2716,32 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2207,7 +2763,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, "funding": [ { @@ -2215,32 +2771,32 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2267,40 +2823,109 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2025-08-10T06:57:39+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.2", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -2315,14 +2940,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -2330,29 +2956,29 @@ "type": "github" } ], - "time": "2020-11-30T07:30:19+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2375,40 +3001,45 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" }, - "time": "2016-10-03T07:35:21+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { "name": "spatie/phpunit-watcher", - "version": "1.23.6", + "version": "1.24.4", "source": { "type": "git", "url": "https://github.com/spatie/phpunit-watcher.git", - "reference": "c192fff763810c8378511bcf0069df4b91478866" + "reference": "3b0e68596c70eba9bec99566b5e2199afb9f289d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/phpunit-watcher/zipball/c192fff763810c8378511bcf0069df4b91478866", - "reference": "c192fff763810c8378511bcf0069df4b91478866", + "url": "https://api.github.com/repos/spatie/phpunit-watcher/zipball/3b0e68596c70eba9bec99566b5e2199afb9f289d", + "reference": "3b0e68596c70eba9bec99566b5e2199afb9f289d", "shasum": "" }, "require": { - "clue/stdio-react": "^2.4", - "jolicode/jolinotif": "^2.2", - "php": "^7.2 | ^8.0 | ^8.1", - "symfony/console": "^5 | ^6", - "symfony/finder": "^5.4 | ^6", - "symfony/process": "^5.4 | ^6", - "symfony/yaml": "^5.2 | ^6", - "yosymfony/resource-watcher": "^2.0 | ^3.0" + "clue/stdio-react": "^2.6", + "jolicode/jolinotif": "^2.7.1 || ^3.0", + "php": "^8.1", + "symfony/console": "^6.0 || ^7.0.7 || ^8.0", + "symfony/finder": "^6.0 || ^7.0.7 || ^8.0", + "symfony/process": "^6.0 || ^7.0.7 || ^8.0", + "symfony/yaml": "^6.0 || ^7.0.7 || ^8.0" }, "conflict": { - "symfony/console": "<5.2", - "yosymfony/resource-watcher": "<2.0" + "symfony/console": "<5.2" }, "require-dev": { - "phpunit/phpunit": "^8.6 | ^9.0" + "phpunit/phpunit": "^10.5.20 | ^11.1.3 || ^12.0", + "symfony/filesystem": "^6.0 || ^7.0.7 || ^8.0" }, "bin": [ "phpunit-watcher" @@ -2439,58 +3070,59 @@ ], "support": { "issues": "https://github.com/spatie/phpunit-watcher/issues", - "source": "https://github.com/spatie/phpunit-watcher/tree/1.23.6" + "source": "https://github.com/spatie/phpunit-watcher/tree/1.24.4" }, - "time": "2022-01-31T11:57:13+00:00" + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-01-05T09:33:23+00:00" }, { "name": "symfony/console", - "version": "v5.4.12", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", - "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "url": "https://api.github.com/repos/symfony/console/zipball/85095d2573eaefaf35e40b9513a9bf09f72cd217", + "reference": "85095d2573eaefaf35e40b9513a9bf09f72cd217", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -2519,12 +3151,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.12" + "source": "https://github.com/symfony/console/tree/v7.4.13" }, "funding": [ { @@ -2535,38 +3167,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-08-17T13:18:05+00:00" + "time": "2026-05-24T08:56:14+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.2", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "autoload": { @@ -2591,7 +3227,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -2602,31 +3238,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/finder", - "version": "v5.4.11", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + "reference": "e0be088d22278583a82da281886e8c3592fbf149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "url": "https://api.github.com/repos/symfony/finder/zipball/e0be088d22278583a82da281886e8c3592fbf149", + "reference": "e0be088d22278583a82da281886e8c3592fbf149", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -2654,7 +3295,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.11" + "source": "https://github.com/symfony/finder/tree/v7.4.8" }, "funding": [ { @@ -2665,29 +3306,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-07-29T07:37:50+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -2697,12 +3342,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2736,7 +3378,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -2747,41 +3389,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2817,7 +3460,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" }, "funding": [ { @@ -2828,41 +3471,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2026-05-26T05:58:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2901,7 +3545,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -2912,29 +3556,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2944,12 +3593,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2984,86 +3630,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" }, "funding": [ { @@ -3075,86 +3642,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -3162,25 +3650,24 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/process", - "version": "v5.4.11", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + "reference": "f5804be144caceb570f6747519999636b664f24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "url": "https://api.github.com/repos/symfony/process/zipball/f5804be144caceb570f6747519999636b664f24c", + "reference": "f5804be144caceb570f6747519999636b664f24c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -3208,7 +3695,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.11" + "source": "https://github.com/symfony/process/tree/v7.4.13" }, "funding": [ { @@ -3219,52 +3706,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2026-05-23T16:05:06+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3291,7 +3782,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -3302,43 +3793,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/string", - "version": "v5.4.12", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058" + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/2fc515e512d721bf31ea76bd02fe23ada4640058", - "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058", + "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -3377,7 +3873,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.12" + "source": "https://github.com/symfony/string/tree/v7.4.13" }, "funding": [ { @@ -3388,40 +3884,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-08-12T17:03:11+00:00" + "time": "2026-05-23T15:23:29+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.12", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c" + "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", - "reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a7ec3b1156faf8815db7683ec7c1e7338e6f977c", + "reference": "a7ec3b1156faf8815db7683ec7c1e7338e6f977c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "symfony/console": "^6.4|^7.0|^8.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -3452,7 +3949,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.12" + "source": "https://github.com/symfony/yaml/tree/v7.4.13" }, "funding": [ { @@ -3463,25 +3960,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-08-02T15:52:22+00:00" + "time": "2026-05-25T06:06:12+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -3510,7 +4011,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -3518,25 +4019,25 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "yoast/phpunit-polyfills", - "version": "1.1.5", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0" + "reference": "1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/41aaac462fbd80feb8dd129e489f4bbc53fe26b0", - "reference": "41aaac462fbd80feb8dd129e489f4bbc53fe26b0", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27", + "reference": "1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27", "shasum": "" }, "require": { - "php": ">=5.4", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "php": ">=5.6", + "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", @@ -3581,64 +4082,7 @@ "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2025-08-10T04:54:36+00:00" - }, - { - "name": "yosymfony/resource-watcher", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/yosymfony/resource-watcher.git", - "reference": "2f197cee0231c06db865d4ad2d8d7cd3faead2f8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yosymfony/resource-watcher/zipball/2f197cee0231c06db865d4ad2d8d7cd3faead2f8", - "reference": "2f197cee0231c06db865d4ad2d8d7cd3faead2f8", - "shasum": "" - }, - "require": { - "php": ">=5.6", - "symfony/finder": "^2.7|^3.0|^4.0|^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7", - "symfony/filesystem": "^2.7|^3.0|^4.0|^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Yosymfony\\ResourceWatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Victor Puertas", - "email": "vpgugr@gmail.com" - } - ], - "description": "A simple resource watcher using Symfony Finder", - "homepage": "http://yosymfony.com", - "keywords": [ - "finder", - "resources", - "symfony", - "watcher" - ], - "support": { - "issues": "https://github.com/yosymfony/resource-watcher/issues", - "source": "https://github.com/yosymfony/resource-watcher/tree/master" - }, - "time": "2020-06-10T14:58:36+00:00" + "time": "2025-08-10T05:13:49+00:00" } ], "aliases": [], @@ -3650,8 +4094,11 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.4" + "php": ">=8.2" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.2" + }, "plugin-api-version": "2.9.0" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 71b40114..6285bf81 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,4 +1,7 @@ + - - + + lib - - + + diff --git a/tests/golden/golden.php b/tests/golden/golden.php index 1f3b42a8..12eca92d 100644 --- a/tests/golden/golden.php +++ b/tests/golden/golden.php @@ -151,3 +151,22 @@ function to_json( $data ) { function parse_entry( array $entry ) { return normalize( \WP_Parser\parse_files( $entry['files'], $entry['root'] ) ); } + +/** + * Whether the parser is currently loadable. + * + * During the migration — after Stage 2 drops phpdocumentor/reflection but before + * Stage 4 rewrites File_Reflector onto nikic/php-parser 5 — the old + * phpDocumentor\Reflection\FileReflector base class is gone, so loading the + * parser throws. This lets the golden test (and the WP suite) SKIP rather than + * ERROR until the new visitor lands. + * + * @return bool + */ +function parser_is_functional() { + try { + return class_exists( '\WP_Parser\File_Reflector' ); + } catch ( \Throwable $e ) { + return false; + } +} diff --git a/tests/golden/test-golden-master.php b/tests/golden/test-golden-master.php index 4e8d71e8..94c0a0bd 100644 --- a/tests/golden/test-golden-master.php +++ b/tests/golden/test-golden-master.php @@ -29,6 +29,12 @@ class Golden_Master_Test extends TestCase { * @param array $entry Corpus entry: { files, root }. */ public function test_output_matches_golden( $slug, array $entry ) { + if ( ! \WP_Parser\Golden\parser_is_functional() ) { + $this->markTestSkipped( + 'Parser not loadable yet (migration in progress — File_Reflector rewrite pending in Stage 4).' + ); + } + $snapshot = \WP_Parser\Golden\snapshot_path( $slug ); if ( ! file_exists( $snapshot ) ) { diff --git a/tests/phpunit/includes/export-testcase.php b/tests/phpunit/includes/export-testcase.php index d74bf9f4..8e5235b9 100644 --- a/tests/phpunit/includes/export-testcase.php +++ b/tests/phpunit/includes/export-testcase.php @@ -41,6 +41,20 @@ public function set_up() { parent::set_up(); if ( ! $this->export_data ) { + + // During the migration the parser is temporarily unloadable (Stage 2 + // drops phpdocumentor/reflection; Stage 4 rewrites File_Reflector). + // Skip rather than fatal until the new parser lands. + try { + $parser_loadable = class_exists( '\WP_Parser\File_Reflector' ); + } catch ( \Throwable $e ) { + $parser_loadable = false; + } + + if ( ! $parser_loadable ) { + $this->markTestSkipped( 'Parser not loadable (migration in progress); File_Reflector unavailable.' ); + } + $this->parse_file(); } } From a64c8e908805d2aef2a630449e10c245dbcf6f41 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 21:01:40 -0400 Subject: [PATCH 06/20] refactor: rewrite pretty printer onto php-parser 5 - Pretty_Printer now extends PhpParser\PrettyPrinter\Standard; prettyPrintArg mirrors prettyPrintExpr state handling (resetState + handleMagicTokens), replacing the removed v1 noIndentToken stripping - Add WordPress-free unit suite (tests/unit + phpunit-unit.xml.dist) covering the pretty printer and NameResolver(replaceNodes:false) foundation - Unit tests assert the exact hook-name/arg strings frozen in the golden snapshots, proving correctness before File_Reflector is wired up Stage 3 of the parser modernization. 5 tests / 9 assertions green on PHP 8.2. --- lib/class-pretty-printer.php | 29 +++++++++----- phpunit-unit.xml.dist | 23 +++++++++++ tests/unit/bootstrap.php | 22 +++++++++++ tests/unit/name-resolver-test.php | 63 ++++++++++++++++++++++++++++++ tests/unit/pretty-printer-test.php | 63 ++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 phpunit-unit.xml.dist create mode 100644 tests/unit/bootstrap.php create mode 100644 tests/unit/name-resolver-test.php create mode 100644 tests/unit/pretty-printer-test.php diff --git a/lib/class-pretty-printer.php b/lib/class-pretty-printer.php index 31c37e28..54a183e3 100644 --- a/lib/class-pretty-printer.php +++ b/lib/class-pretty-printer.php @@ -2,21 +2,32 @@ namespace WP_Parser; -use PHPParser_Node_Arg; -use PHPParser_PrettyPrinter_Default; +use PhpParser\Node; +use PhpParser\PrettyPrinter\Standard; /** - * Extends default printer for arguments. + * Extends the default pretty printer to render a single call argument. + * + * Used by the hook and method-call reflectors to turn argument and caller AST + * nodes back into source-equivalent strings. */ -class Pretty_Printer extends PHPParser_PrettyPrinter_Default { +class Pretty_Printer extends Standard { + /** - * Pretty prints an argument. + * Pretty print a single call argument. + * + * php-parser exposes no public method for a lone Arg node, so this mirrors + * prettyPrintExpr()'s state handling — reset + magic-token cleanup around the + * internal Arg printer. This replaces the v1 `noIndentToken` stripping, which + * no longer exists in php-parser 5. * - * @param PHPParser_Node_Arg $node Expression argument + * @param Node\Arg $node Argument node. * - * @return string Pretty printed argument + * @return string Pretty printed argument. */ - public function prettyPrintArg( PHPParser_Node_Arg $node ) { - return str_replace( "\n" . $this->noIndentToken, "\n", $this->p( $node ) ); + public function prettyPrintArg( Node\Arg $node ) { + $this->resetState(); + + return $this->handleMagicTokens( $this->p( $node ) ); } } diff --git a/phpunit-unit.xml.dist b/phpunit-unit.xml.dist new file mode 100644 index 00000000..97b77933 --- /dev/null +++ b/phpunit-unit.xml.dist @@ -0,0 +1,23 @@ + + + + + + tests/unit + + + diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php new file mode 100644 index 00000000..c0b7179e --- /dev/null +++ b/tests/unit/bootstrap.php @@ -0,0 +1,22 @@ +namespacedName`. + * Function_Call_Reflector (Stage 6) reads that attribute. This pins the behavior. + * + * @package WP_Parser\Tests\Unit + */ + +namespace WP_Parser\Tests\Unit; + +use PhpParser\Node; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; +use PhpParser\ParserFactory; +use PHPUnit\Framework\TestCase; + +class Name_Resolver_Test extends TestCase { + + /** + * Resolve a snippet with NameResolver in non-replacing mode. + * + * @param string $code PHP source (without the opening tag). + * @return Node\Stmt[] + */ + private function resolve( $code ) { + $stmts = ( new ParserFactory() )->createForNewestSupportedVersion()->parse( "addVisitor( new NameResolver( null, array( 'replaceNodes' => false ) ) ); + + return $traverser->traverse( $stmts ); + } + + public function test_attaches_namespaced_name_to_unqualified_call() { + $stmts = $this->resolve( "namespace Awesome\\Space;\nohai();" ); + + $call = $stmts[0]->stmts[0]->expr; // Namespace_ -> Expression -> FuncCall + $this->assertInstanceOf( Node\Expr\FuncCall::class, $call ); + + // Original Name node is preserved (replaceNodes:false). + $this->assertInstanceOf( Node\Name::class, $call->name ); + + // Unqualified calls in a namespace cannot be statically resolved (global + // fallback), so they receive a namespacedName attribute. + $namespaced = $call->name->getAttribute( 'namespacedName' ); + $this->assertNotNull( $namespaced ); + $this->assertSame( 'Awesome\\Space\\ohai', $namespaced->toString() ); + } + + public function test_resolves_fully_qualified_call() { + $stmts = $this->resolve( "namespace Awesome\\Space;\n\\Other\\Place\\greet();" ); + + $call = $stmts[0]->stmts[0]->expr; + $resolved = $call->name->getAttribute( 'resolvedName' ); + + $this->assertNotNull( $resolved ); + $this->assertSame( 'Other\\Place\\greet', $resolved->toString() ); + } +} diff --git a/tests/unit/pretty-printer-test.php b/tests/unit/pretty-printer-test.php new file mode 100644 index 00000000..89d0fd50 --- /dev/null +++ b/tests/unit/pretty-printer-test.php @@ -0,0 +1,63 @@ +createForNewestSupportedVersion()->parse( "expr; // Stmt\Expression -> Expr\FuncCall + } + + public function test_pretty_prints_simple_string_hook_name() { + $call = $this->first_call( "do_action( 'plain_action' );" ); + $printer = new Pretty_Printer(); + + $this->assertSame( "'plain_action'", $printer->prettyPrintExpr( $call->args[0]->value ) ); + } + + public function test_pretty_prints_concatenated_hook_name() { + $call = $this->first_call( "do_action( \$variable . '-action' );" ); + $printer = new Pretty_Printer(); + + $this->assertSame( "\$variable . '-action'", $printer->prettyPrintExpr( $call->args[0]->value ) ); + } + + public function test_pretty_prints_filter_arguments() { + $call = $this->first_call( "apply_filters( 'plain_filter', \$variable, \$filter_context );" ); + $printer = new Pretty_Printer(); + + $args = array_map( + function ( Node\Arg $arg ) use ( $printer ) { + return $printer->prettyPrintArg( $arg ); + }, + $call->args + ); + + // The leading name arg is shifted off by Hook_Reflector; here we assert the + // full rendered list, matching the golden contract for plain_filter. + $this->assertSame( array( "'plain_filter'", '$variable', '$filter_context' ), $args ); + } +} From a758e42bed005dc563cfc69997af31d71cb3ecd4 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 21:20:09 -0400 Subject: [PATCH 07/20] feat: rewrite File_Reflector on php-parser 5 - Replace the phpDocumentor FileReflector subclass with a NodeVisitorAbstract that walks the AST and collects functions, classes, methods, properties and arguments into thin reflector wrappers (lib/class-reflectors.php) - Re-expose only the reflector API runner.php consumes, keeping the exported array shape identical; collect on leaveNode so nested functions order inner-first like the legacy parser; resolve extends/implements to \FQN - DocBlocks return null and hooks/$uses are deferred (Stages 5-6) - Add File_Reflector unit tests; structure matches the golden oracle 16/16 (full golden match pending docblocks + hooks) Stage 4 of the parser modernization. Unit suite: 8 tests / 33 assertions. --- lib/class-file-reflector.php | 303 +++++++++------------- lib/class-reflectors.php | 392 +++++++++++++++++++++++++++++ tests/unit/file-reflector-test.php | 85 +++++++ 3 files changed, 598 insertions(+), 182 deletions(-) create mode 100644 lib/class-reflectors.php create mode 100644 tests/unit/file-reflector-test.php diff --git a/lib/class-file-reflector.php b/lib/class-file-reflector.php index 62f401d7..47d69708 100644 --- a/lib/class-file-reflector.php +++ b/lib/class-file-reflector.php @@ -2,235 +2,174 @@ namespace WP_Parser; -use phpDocumentor\Reflection; -use phpDocumentor\Reflection\FileReflector; +use PhpParser\Node; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; +use PhpParser\NodeVisitorAbstract; +use PhpParser\ParserFactory; /** - * Reflection class for a full file. + * Reflects a single PHP file using nikic/php-parser 5. * - * Extends the FileReflector from phpDocumentor to parse out WordPress - * hooks and note function relationships. + * Replaces the former phpDocumentor FileReflector subclass. It walks the AST as + * a NodeVisitor, collecting the structural elements (functions, classes, their + * methods and properties) that runner.php exports, while keeping the exported + * array shape identical. + * + * Hook detection (do_action/apply_filters) and the per-element $uses list are + * added in a later stage; for now $uses stays empty. */ -class File_Reflector extends FileReflector { +class File_Reflector extends NodeVisitorAbstract { + /** - * List of elements used in global scope in this file, indexed by element type. + * Elements used in file scope, indexed by element type (hooks, functions, methods). * - * @var array { - * @type Hook_Reflector[] $hooks The action and filters. - * @type Function_Call_Reflector[] $functions The functions called. - * } + * @var array */ public $uses = array(); + protected $filename; + protected $contents; + protected $path; + + /** @var Function_Reflector[] */ + protected $functions = array(); + + /** @var Class_Reflector[] */ + protected $classes = array(); + + protected $constants = array(); + protected $includes = array(); + + /** @var string Current namespace ('global' at file scope). */ + protected $namespace = 'global'; + + /** @var array Map of import alias => fully qualified name for the current namespace. */ + protected $aliases = array(); + /** - * List of elements used in the current class scope, indexed by method. - * - * @var array[][] {@see \WP_Parser\File_Reflector::$uses} + * @param string $filename Absolute path to the file to reflect. */ - protected $method_uses_queue = array(); + public function __construct( $filename ) { + $this->filename = $filename; + $this->contents = file_get_contents( $filename ); + } /** - * Stack of classes/methods/functions currently being parsed. + * Set the (root-relative) path reported for this file. * - * @see \WP_Parser\FileReflector::getLocation() - * @var \phpDocumentor\Reflection\BaseReflector[] + * @param string $path */ - protected $location = array(); + public function setFilename( $path ) { + $this->path = $path; + } /** - * Last DocBlock associated with a non-documentable element. - * - * @var \PHPParser_Comment_Doc + * @return string */ - protected $last_doc = null; + public function getFilename() { + return $this->path; + } /** - * Add hooks to the queue and update the node stack when we enter a node. - * - * If we are entering a class, function or method, we push it to the location - * stack. This is just so that we know whether we are in the file scope or not, - * so that hooks in the main file scope can be added to the file. - * - * We also check function calls to see if there are any actions or hooks. If - * there are, they are added to the file's hooks if in the global scope, or if - * we are in a function/method, they are added to the queue. They will be - * assigned to the function by leaveNode(). We also check for any other function - * calls and treat them similarly, so that we can export a list of functions - * used by each element. - * - * Finally, we pick up any docblocks for nodes that usually aren't documentable, - * so they can be assigned to the hooks to which they may belong. - * - * @param \PHPParser_Node $node + * Parse and walk the file, populating the structural collections. */ - public function enterNode( \PHPParser_Node $node ) { - parent::enterNode( $node ); - - switch ( $node->getType() ) { - // Add classes, functions, and methods to the current location stack - case 'Stmt_Class': - case 'Stmt_Function': - case 'Stmt_ClassMethod': - array_push( $this->location, $node ); - break; - - // Parse out hook definitions and function calls and add them to the queue. - case 'Expr_FuncCall': - $function = new Function_Call_Reflector( $node, $this->context ); - - // Add the call to the list of functions used in this scope. - $this->getLocation()->uses['functions'][] = $function; - - if ( $this->isFilter( $node ) ) { - if ( $this->last_doc && ! $node->getDocComment() ) { - $node->setAttribute( 'comments', array( $this->last_doc ) ); - $this->last_doc = null; - } - - $hook = new Hook_Reflector( $node, $this->context ); - - // Add it to the list of hooks used in this scope. - $this->getLocation()->uses['hooks'][] = $hook; - } - break; - - // Parse out method calls, so we can export where methods are used. - case 'Expr_MethodCall': - $method = new Method_Call_Reflector( $node, $this->context ); - - // Add it to the list of methods used in this scope. - $this->getLocation()->uses['methods'][] = $method; - break; - - // Parse out method calls, so we can export where methods are used. - case 'Expr_StaticCall': - $method = new Static_Method_Call_Reflector( $node, $this->context ); - - // Add it to the list of methods used in this scope. - $this->getLocation()->uses['methods'][] = $method; - break; - - // Parse out `new Class()` calls as uses of Class::__construct(). - case 'Expr_New': - $method = new \WP_Parser\Method_Call_Reflector( $node, $this->context ); - - // Add it to the list of methods used in this scope. - $this->getLocation()->uses['methods'][] = $method; - break; + public function process() { + $parser = ( new ParserFactory() )->createForNewestSupportedVersion(); + + try { + $stmts = $parser->parse( $this->contents ); + } catch ( \PhpParser\Error $e ) { + $stmts = null; } - // Pick up DocBlock from non-documentable elements so that it can be assigned - // to the next hook if necessary. We don't do this for name nodes, since even - // though they aren't documentable, they still carry the docblock from their - // corresponding class/constant/function/etc. that they are the name of. If - // we don't ignore them, we'll end up picking up docblocks that are already - // associated with a named element, and so aren't really from a non- - // documentable element after all. - if ( ! $this->isNodeDocumentable( $node ) && 'Name' !== $node->getType() && ( $docblock = $node->getDocComment() ) ) { - $this->last_doc = $docblock; + if ( null === $stmts ) { + return; } + + $traverser = new NodeTraverser(); + // replaceNodes:false keeps the original Name nodes and attaches resolved + // names as attributes — the v5 analog of v1's $node->namespacedName. + $traverser->addVisitor( new NameResolver( null, array( 'replaceNodes' => false ) ) ); + $traverser->addVisitor( $this ); + $traverser->traverse( $stmts ); } /** - * Assign queued hooks to functions and update the node stack on leaving a node. + * Track the current namespace and import aliases as we enter nodes. * - * We can now access the function/method reflectors, so we can assign any queued - * hooks to them. The reflector for a node isn't created until the node is left. + * @param Node $node * - * @param \PHPParser_Node $node + * @return null */ - public function leaveNode( \PHPParser_Node $node ) { - - parent::leaveNode( $node ); - - switch ( $node->getType() ) { - case 'Stmt_Class': - $class = end( $this->classes ); - if ( ! empty( $this->method_uses_queue ) ) { - /** @var Reflection\ClassReflector\MethodReflector $method */ - foreach ( $class->getMethods() as $method ) { - if ( isset( $this->method_uses_queue[ $method->getName() ] ) ) { - if ( isset( $this->method_uses_queue[ $method->getName() ]['methods'] ) ) { - /* - * For methods used in a class, set the class on the method call. - * That allows us to later get the correct class name for $this, self, parent. - */ - foreach ( $this->method_uses_queue[ $method->getName() ]['methods'] as $method_call ) { - /** @var Method_Call_Reflector $method_call */ - $method_call->set_class( $class ); - } - } - - $method->uses = $this->method_uses_queue[ $method->getName() ]; - } - } - } - - $this->method_uses_queue = array(); - array_pop( $this->location ); - break; - - case 'Stmt_Function': - $function = array_pop( $this->location ); - if ( isset( $function->uses ) && ! empty( $function->uses ) ) { - end( $this->functions )->uses = $function->uses; - } - break; - - case 'Stmt_ClassMethod': - $method = array_pop( $this->location ); - - /* - * Store the list of elements used by this method in the queue. We'll - * assign them to the method upon leaving the class (see above). - */ - if ( ! empty( $method->uses ) ) { - $this->method_uses_queue[ $method->name ] = $method->uses; - } - break; + public function enterNode( Node $node ) { + if ( $node instanceof Node\Stmt\Namespace_ ) { + $this->namespace = $node->name ? $node->name->toString() : 'global'; + $this->aliases = array(); + } elseif ( $node instanceof Node\Stmt\Use_ ) { + foreach ( $node->uses as $use ) { + $this->aliases[ $use->getAlias()->toString() ] = $use->name->toString(); + } } + + return null; } /** - * @param \PHPParser_Node $node + * Collect structural elements as the traverser leaves each node. + * + * Functions and classes are recorded on leave (not enter) so that nested + * functions are listed before their enclosing function — matching the order + * the legacy parser produced. + * + * @param Node $node * - * @return bool + * @return null */ - protected function isFilter( \PHPParser_Node $node ) { - // Ignore variable functions - if ( 'Name' !== $node->name->getType() ) { - return false; + public function leaveNode( Node $node ) { + if ( $node instanceof Node\Stmt\Function_ ) { + $this->functions[] = new Function_Reflector( $node, $this->namespace, $this->aliases ); + } elseif ( $node instanceof Node\Stmt\Class_ && null !== $node->name ) { + $this->classes[] = new Class_Reflector( $node, $this->namespace, $this->aliases ); } - $calling = (string) $node->name; + return null; + } - $functions = array( - 'apply_filters', - 'apply_filters_ref_array', - 'apply_filters_deprecated', - 'do_action', - 'do_action_ref_array', - 'do_action_deprecated', - ); + /** + * The file-level docblock. Added with the docblock adapter (Stage 5). + * + * @return null + */ + public function getDocBlock() { + return null; + } - return in_array( $calling, $functions ); + /** + * @return Function_Reflector[] + */ + public function getFunctions() { + return $this->functions; } /** - * @return File_Reflector + * @return Class_Reflector[] */ - protected function getLocation() { - return empty( $this->location ) ? $this : end( $this->location ); + public function getClasses() { + return $this->classes; } /** - * @param \PHPParser_Node $node - * - * @return bool + * @return array + */ + public function getConstants() { + return $this->constants; + } + + /** + * @return array */ - protected function isNodeDocumentable( \PHPParser_Node $node ) { - return parent::isNodeDocumentable( $node ) - || ( $node instanceof \PHPParser_Node_Expr_FuncCall - && $this->isFilter( $node ) ); + public function getIncludes() { + return $this->includes; } } diff --git a/lib/class-reflectors.php b/lib/class-reflectors.php new file mode 100644 index 00000000..95d70b8e --- /dev/null +++ b/lib/class-reflectors.php @@ -0,0 +1,392 @@ +prettyPrintExpr( $expr ); + } + + /** + * Render a type node to a string, or '' when there is no type. + * + * @param Node\Identifier|Node\Name|Node\ComplexType|null $type + * @return string + */ + public static function type_string( $type ) { + if ( null === $type ) { + return ''; + } + + if ( $type instanceof Node\NullableType ) { + return '?' . self::type_string( $type->type ); + } + + if ( $type instanceof Node\UnionType ) { + return implode( '|', array_map( array( self::class, 'type_string' ), $type->types ) ); + } + + if ( $type instanceof Node\IntersectionType ) { + return implode( '&', array_map( array( self::class, 'type_string' ), $type->types ) ); + } + + // Identifier or Name. + return $type->toString(); + } + + /** + * Map php-parser visibility flags to a visibility string. + * + * @param Node\Stmt\ClassMethod|Node\Stmt\Property $node + * @return string + */ + public static function visibility( $node ) { + if ( $node->isPrivate() ) { + return 'private'; + } + + if ( $node->isProtected() ) { + return 'protected'; + } + + return 'public'; + } + + /** + * Resolve a class reference (extends/implements) to the legacy "\Fully\Qualified" + * form. Uses the NameResolver's resolvedName attribute when available so aliased + * and namespaced names resolve correctly; returns '' when there is no reference. + * + * @param Node\Name|null $name + * @return string + */ + public static function class_name( $name ) { + if ( null === $name ) { + return ''; + } + + $resolved = $name->getAttribute( 'resolvedName' ); + + return '\\' . ( $resolved ? $resolved->toString() : $name->toString() ); + } + + /** + * Build Argument_Reflectors for a list of parameters. + * + * @param Node\Param[] $params + * @return Argument_Reflector[] + */ + public static function arguments( array $params ) { + $arguments = array(); + foreach ( $params as $param ) { + $arguments[] = new Argument_Reflector( $param ); + } + + return $arguments; + } +} + +/** + * A function argument (parameter). + */ +class Argument_Reflector { + + /** @var Node\Param */ + protected $node; + + public function __construct( Node\Param $node ) { + $this->node = $node; + } + + public function getName() { + return '$' . ( is_string( $this->node->var->name ) ? $this->node->var->name : '' ); + } + + public function getDefault() { + return Reflector_Helpers::default_value( $this->node->default ); + } + + public function getType() { + return Reflector_Helpers::type_string( $this->node->type ); + } + + public function getNode() { + return $this->node; + } +} + +/** + * A top-level (or namespaced) function definition. + */ +class Function_Reflector { + + /** @var array|null Elements used by this function; populated during traversal. */ + public $uses; + + /** @var Node\Stmt\Function_ */ + protected $node; + protected $namespace; + protected $aliases; + + public function __construct( Node\Stmt\Function_ $node, $namespace, array $aliases ) { + $this->node = $node; + $this->namespace = $namespace; + $this->aliases = $aliases; + } + + public function getShortName() { + return $this->node->name->toString(); + } + + public function getNamespace() { + return $this->namespace; + } + + public function getNamespaceAliases() { + return $this->aliases; + } + + public function getLineNumber() { + return $this->node->getStartLine(); + } + + public function getNode() { + return $this->node; + } + + public function getArguments() { + return Reflector_Helpers::arguments( $this->node->params ); + } + + public function getDocBlock() { + return null; // Stage 5. + } +} + +/** + * A class definition. + */ +class Class_Reflector { + + /** @var Node\Stmt\Class_ */ + protected $node; + protected $namespace; + protected $aliases; + + public function __construct( Node\Stmt\Class_ $node, $namespace, array $aliases ) { + $this->node = $node; + $this->namespace = $namespace; + $this->aliases = $aliases; + } + + public function getShortName() { + return $this->node->name->toString(); + } + + public function getNamespace() { + return $this->namespace; + } + + public function getNamespaceAliases() { + return $this->aliases; + } + + public function getLineNumber() { + return $this->node->getStartLine(); + } + + public function getNode() { + return $this->node; + } + + public function isFinal() { + return $this->node->isFinal(); + } + + public function isAbstract() { + return $this->node->isAbstract(); + } + + public function getParentClass() { + return Reflector_Helpers::class_name( $this->node->extends ); + } + + public function getInterfaces() { + $interfaces = array(); + foreach ( $this->node->implements as $interface ) { + $interfaces[] = Reflector_Helpers::class_name( $interface ); + } + + return $interfaces; + } + + public function getProperties() { + $properties = array(); + foreach ( $this->node->getProperties() as $property ) { + foreach ( $property->props as $prop ) { + $properties[] = new Property_Reflector( $property, $prop ); + } + } + + return $properties; + } + + public function getMethods() { + $methods = array(); + foreach ( $this->node->getMethods() as $method ) { + $methods[] = new Method_Reflector( $method, $this->aliases ); + } + + return $methods; + } + + public function getDocBlock() { + return null; // Stage 5. + } +} + +/** + * A class method. + */ +class Method_Reflector { + + /** @var array|null Elements used by this method; populated during traversal. */ + public $uses; + + /** @var Node\Stmt\ClassMethod */ + protected $node; + protected $aliases; + + public function __construct( Node\Stmt\ClassMethod $node, array $aliases ) { + $this->node = $node; + $this->aliases = $aliases; + } + + public function getShortName() { + return $this->node->name->toString(); + } + + public function getNamespace() { + return ''; // Methods carry no namespace in the legacy output. + } + + public function getNamespaceAliases() { + return $this->aliases; + } + + public function getLineNumber() { + return $this->node->getStartLine(); + } + + public function getNode() { + return $this->node; + } + + public function isFinal() { + return $this->node->isFinal(); + } + + public function isAbstract() { + return $this->node->isAbstract(); + } + + public function isStatic() { + return $this->node->isStatic(); + } + + public function getVisibility() { + return Reflector_Helpers::visibility( $this->node ); + } + + public function getArguments() { + return Reflector_Helpers::arguments( $this->node->params ); + } + + public function getDocBlock() { + return null; // Stage 5. + } +} + +/** + * A single declared class property. + */ +class Property_Reflector { + + /** @var Node\Stmt\Property */ + protected $stmt; + + /** @var Node\PropertyItem|object The individual property within the declaration. */ + protected $prop; + + public function __construct( Node\Stmt\Property $stmt, $prop ) { + $this->stmt = $stmt; + $this->prop = $prop; + } + + public function getName() { + return '$' . $this->prop->name->toString(); + } + + public function getLineNumber() { + return $this->stmt->getStartLine(); + } + + public function getNode() { + return $this->stmt; + } + + public function getDefault() { + return Reflector_Helpers::default_value( $this->prop->default ); + } + + public function isStatic() { + return $this->stmt->isStatic(); + } + + public function getVisibility() { + return Reflector_Helpers::visibility( $this->stmt ); + } + + public function getDocBlock() { + return null; // Stage 5. + } +} diff --git a/tests/unit/file-reflector-test.php b/tests/unit/file-reflector-test.php new file mode 100644 index 00000000..103c8054 --- /dev/null +++ b/tests/unit/file-reflector-test.php @@ -0,0 +1,85 @@ +setFilename( basename( $path ) ); + $file->process(); + + return $file; + } + + public function test_extracts_namespaced_function() { + $functions = $this->reflect( 'phpunit/tests/export/namespace.inc' )->getFunctions(); + + $this->assertCount( 1, $functions ); + $this->assertSame( 'ohai', $functions[0]->getShortName() ); + $this->assertSame( 'Awesome\\Space', $functions[0]->getNamespace() ); + $this->assertSame( array(), $functions[0]->getNamespaceAliases() ); + $this->assertSame( 5, $functions[0]->getLineNumber() ); + $this->assertSame( array(), $functions[0]->getArguments() ); + } + + public function test_nested_functions_ordered_inner_first_and_extends_resolved() { + $file = $this->reflect( 'phpunit/tests/export/uses/nested.inc' ); + + $names = array_map( + static function ( $fn ) { + return $fn->getShortName(); + }, + $file->getFunctions() + ); + // Inner functions complete first, matching the legacy parser's order. + $this->assertSame( array( 'sub_test', 'test', 'sub_method_test' ), $names ); + + $classes = $file->getClasses(); + $this->assertCount( 1, $classes ); + $this->assertSame( 'My_Class', $classes[0]->getShortName() ); + $this->assertSame( '\\Parent_Class', $classes[0]->getParentClass() ); + } + + public function test_extracts_class_members() { + $class = $this->reflect( 'source/good-class.php' )->getClasses()[0]; + + $this->assertSame( 'Good_Doc_Class', $class->getShortName() ); + $this->assertSame( 'global', $class->getNamespace() ); + $this->assertFalse( $class->isAbstract() ); + $this->assertFalse( $class->isFinal() ); + $this->assertSame( '', $class->getParentClass() ); + + $property = $class->getProperties()[0]; + $this->assertSame( '$good_doc_private_property_from_good_doc_class', $property->getName() ); + $this->assertSame( 'private', $property->getVisibility() ); + $this->assertFalse( $property->isStatic() ); + $this->assertSame( 'array()', $property->getDefault() ); + + $method = $class->getMethods()[0]; + $this->assertSame( '', $method->getNamespace() ); + $this->assertSame( 'public', $method->getVisibility() ); + $arguments = $method->getArguments(); + $this->assertSame( '$code', $arguments[0]->getName() ); + $this->assertSame( "''", $arguments[0]->getDefault() ); + $this->assertSame( '', $arguments[0]->getType() ); + } +} From 48ecb40fdbd960d8442ff17e78426fa72c60fa89 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 21:41:40 -0400 Subject: [PATCH 08/20] feat: add reflection-docblock 6 adapter for docblocks - Add lib/class-docblock-adapter.php wrapping reflection-docblock 6 to emit the legacy {description, long_description, tags[]} shape; distinct tag adapters so export_docblock's method_exists() probes select the right keys - type_to_legacy_strings() maps type-resolver 2 types to string[] (\WP_Post, int[], unions); long_description reproduced via Parsedown block parsing - Reconstruct @see (InvalidTag) and @link to match the loose legacy parsing - Wire getDocBlock() through every reflector; detect the file-level docblock (first-docblock heuristic incl. the open-tag-adjacency quirk) - Add docblock adapter unit tests Stage 5. Golden: 7/16 full, 16/16 structure+docblocks (hooks/uses pending). Unit suite: 11 tests / 50 assertions. --- lib/class-docblock-adapter.php | 359 +++++++++++++++++++++++++++ lib/class-file-reflector.php | 90 ++++++- lib/class-reflectors.php | 40 +-- tests/unit/docblock-adapter-test.php | 71 ++++++ 4 files changed, 542 insertions(+), 18 deletions(-) create mode 100644 lib/class-docblock-adapter.php create mode 100644 tests/unit/docblock-adapter-test.php diff --git a/lib/class-docblock-adapter.php b/lib/class-docblock-adapter.php new file mode 100644 index 00000000..9a0205af --- /dev/null +++ b/lib/class-docblock-adapter.php @@ -0,0 +1,359 @@ +docblock = $docblock; + } + + /** + * Build an adapter from a node's doc comment, or null when there is none. + * + * @param \PhpParser\Node $node Node carrying the doc comment. + * @param string $namespace Namespace to resolve types against ('global' => root). + * @param array $aliases Import aliases (alias => FQN). + * + * @return Docblock_Adapter|null + */ + public static function from_node( $node, $namespace, array $aliases ) { + $comment = $node->getDocComment(); + if ( null === $comment ) { + return null; + } + + return self::from_text( $comment->getText(), $namespace, $aliases ); + } + + /** + * Build an adapter from raw docblock text. + * + * @param string $text + * @param string $namespace + * @param array $aliases + * + * @return Docblock_Adapter|null + */ + public static function from_text( $text, $namespace, array $aliases ) { + static $factory = null; + if ( null === $factory ) { + $factory = DocBlockFactory::createInstance(); + } + + $context = new Context( 'global' === $namespace ? '' : $namespace, $aliases ); + + try { + $docblock = $factory->create( $text, $context ); + } catch ( \Throwable $e ) { + return null; + } + + return new self( $docblock ); + } + + public function getShortDescription() { + return $this->docblock->getSummary(); + } + + public function getLongDescription() { + return new Description_Adapter( $this->docblock->getDescription() ); + } + + /** + * @return object[] Tag adapters. + */ + public function getTags() { + $tags = array(); + foreach ( $this->docblock->getTags() as $tag ) { + $tags[] = self::adapt_tag( $tag ); + } + + return $tags; + } + + /** + * Map a reflection-docblock tag to the adapter exposing the right legacy methods. + * + * @param Tags\BaseTag $tag + * + * @return object + */ + protected static function adapt_tag( $tag ) { + $name = $tag->getName(); + + // Tags reflection-docblock can't parse strictly (e.g. @see with a non-FQSEN + // reference) come back as InvalidTag; reconstruct from the raw body to match + // the legacy loose parsing. + if ( $tag instanceof Tags\InvalidTag ) { + return self::adapt_invalid_tag( $tag, $name ); + } + + $description = self::render_description( method_exists( $tag, 'getDescription' ) ? $tag->getDescription() : null ); + + // @param and @var: type + variable name. + if ( $tag instanceof Tags\Param || $tag instanceof Tags\Var_ ) { + $variable = $tag->getVariableName(); + + return new Param_Tag_Adapter( + $name, + $description, + self::type_to_legacy_strings( $tag->getType() ), + $variable ? '$' . $variable : '' + ); + } + + // @return and other typed tags: type, no variable. + if ( $tag instanceof Tags\TagWithType ) { + return new Typed_Tag_Adapter( $name, $description, self::type_to_legacy_strings( $tag->getType() ) ); + } + + // @since and @deprecated: version becomes the content. + if ( $tag instanceof Tags\Since || $tag instanceof Tags\Deprecated ) { + return new Version_Tag_Adapter( $name, $description, (string) $tag->getVersion() ); + } + + // @see: a reference. + if ( $tag instanceof Tags\See ) { + return new See_Tag_Adapter( $name, $description, (string) $tag->getReference() ); + } + + // @link: a URL. When no description is given the URL itself is the content. + if ( $tag instanceof Tags\Link ) { + $link = $tag->getLink(); + + return new Link_Tag_Adapter( $name, '' !== $description ? $description : $link, $link ); + } + + return new Generic_Tag_Adapter( $name, $description ); + } + + /** + * Reconstruct a tag reflection-docblock flagged invalid, recovering its body + * from the rendered "@name body" string. + * + * @param Tags\InvalidTag $tag + * @param string $name + * + * @return object + */ + protected static function adapt_invalid_tag( $tag, $name ) { + $body = preg_replace( '/^@' . preg_quote( $name, '/' ) . '\s*/', '', $tag->render() ); + + // @see / @uses: the first token is the reference, the remainder the description. + if ( 'see' === $name || 'uses' === $name ) { + $parts = preg_split( '/\s+/', $body, 2 ); + + return new See_Tag_Adapter( + $name, + isset( $parts[1] ) ? $parts[1] : '', + isset( $parts[0] ) ? $parts[0] : '' + ); + } + + return new Generic_Tag_Adapter( $name, $body ); + } + + /** + * Render a tag's description object to a string. + * + * @param mixed $description + * + * @return string + */ + protected static function render_description( $description ) { + return $description ? $description->render() : ''; + } + + /** + * Convert a type-resolver Type to the legacy array of type strings. + * + * Compound (union) types are split into their parts; everything else renders + * to a single string. Object types resolve to a leading-backslash FQN via the + * docblock Context (e.g. WP_Post => \WP_Post). + * + * @param Type|null $type + * + * @return string[] + */ + public static function type_to_legacy_strings( $type ) { + if ( null === $type ) { + return array(); + } + + if ( $type instanceof Compound ) { + $types = array(); + foreach ( $type as $part ) { + $types[] = (string) $part; + } + + return $types; + } + + return array( (string) $type ); + } +} + +/** + * Wraps a reflection-docblock Description, exposing the legacy getFormattedContents(). + */ +class Description_Adapter { + + /** @var DocBlock\Description */ + protected $description; + + public function __construct( $description ) { + $this->description = $description; + } + + /** + * The long description as formatted HTML. + * + * The legacy reflection-docblock ran the body through a Markdown block parser + * (wrapping paragraphs in

); export_docblock() then collapses the soft line + * breaks via fix_newlines(). Parsedown reproduces that output. + * + * @return string + */ + public function getFormattedContents() { + $text = $this->description->render(); + + if ( '' !== $text && class_exists( 'Parsedown' ) ) { + $text = \Parsedown::instance()->text( $text ); + } + + return $text; + } +} + +/** + * Base tag: name + description only (e.g. @access, @global, @package). + */ +class Generic_Tag_Adapter { + + protected $name; + protected $description; + + public function __construct( $name, $description ) { + $this->name = $name; + $this->description = $description; + } + + public function getName() { + return $this->name; + } + + public function getDescription() { + return $this->description; + } +} + +/** + * A tag carrying types but no variable (e.g. @return). + */ +class Typed_Tag_Adapter extends Generic_Tag_Adapter { + + protected $types; + + public function __construct( $name, $description, array $types ) { + parent::__construct( $name, $description ); + $this->types = $types; + } + + public function getTypes() { + return $this->types; + } +} + +/** + * A tag carrying types and a variable name (e.g. @param, @var). + */ +class Param_Tag_Adapter extends Typed_Tag_Adapter { + + protected $variable; + + public function __construct( $name, $description, array $types, $variable ) { + parent::__construct( $name, $description, $types ); + $this->variable = $variable; + } + + public function getVariableName() { + return $this->variable; + } +} + +/** + * A versioned tag (e.g. @since, @deprecated). + */ +class Version_Tag_Adapter extends Generic_Tag_Adapter { + + protected $version; + + public function __construct( $name, $description, $version ) { + parent::__construct( $name, $description ); + $this->version = $version; + } + + public function getVersion() { + return $this->version; + } +} + +/** + * A reference tag (e.g. @see). + */ +class See_Tag_Adapter extends Generic_Tag_Adapter { + + protected $reference; + + public function __construct( $name, $description, $reference ) { + parent::__construct( $name, $description ); + $this->reference = $reference; + } + + public function getReference() { + return $this->reference; + } +} + +/** + * A link tag (e.g. @link). + */ +class Link_Tag_Adapter extends Generic_Tag_Adapter { + + protected $link; + + public function __construct( $name, $description, $link ) { + parent::__construct( $name, $description ); + $this->link = $link; + } + + public function getLink() { + return $this->link; + } +} diff --git a/lib/class-file-reflector.php b/lib/class-file-reflector.php index 47d69708..2dd2bc3d 100644 --- a/lib/class-file-reflector.php +++ b/lib/class-file-reflector.php @@ -2,6 +2,7 @@ namespace WP_Parser; +use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; @@ -41,6 +42,9 @@ class File_Reflector extends NodeVisitorAbstract { protected $constants = array(); protected $includes = array(); + /** @var Docblock_Adapter|null The file-level docblock, if any. */ + protected $file_docblock = null; + /** @var string Current namespace ('global' at file scope). */ protected $namespace = 'global'; @@ -87,6 +91,8 @@ public function process() { return; } + $this->file_docblock = $this->detect_file_docblock( $stmts ); + $traverser = new NodeTraverser(); // replaceNodes:false keeps the original Name nodes and attaches resolved // names as attributes — the v5 analog of v1's $node->namespacedName. @@ -137,14 +143,94 @@ public function leaveNode( Node $node ) { } /** - * The file-level docblock. Added with the docblock adapter (Stage 5). + * The file-level docblock, or null when the file has none. * - * @return null + * @return Docblock_Adapter|null */ public function getDocBlock() { + return $this->file_docblock; + } + + /** + * Detect the file-level docblock. + * + * The file docblock is the first docblock in the file, unless that docblock + * directly documents the first structural element (function/class/...). So a + * lone docblock before a `function`/`class` belongs to that element, but a + * docblock before a non-structural statement (or a second docblock preceding + * the first element) floats to the file. + * + * @param Node[] $stmts + * + * @return Docblock_Adapter|null + */ + protected function detect_file_docblock( array $stmts ) { + if ( empty( $stmts ) ) { + return null; + } + + $first = $stmts[0]; + + // A docblock attached before a `namespace` declaration is a file docblock. + if ( $first instanceof Node\Stmt\Namespace_ ) { + $docs = $this->doc_comments( $first ); + if ( ! empty( $docs ) ) { + return Docblock_Adapter::from_text( $docs[0]->getText(), 'global', array() ); + } + + if ( empty( $first->stmts ) ) { + return null; + } + + $first = $first->stmts[0]; + } + + $docs = $this->doc_comments( $first ); + + // Two docblocks before the first element: the first one is the file docblock. + if ( count( $docs ) >= 2 ) { + return Docblock_Adapter::from_text( $docs[0]->getText(), 'global', array() ); + } + + // A single docblock before a non-structural statement floats to the file only + // when it is attached to the open tag (no blank line after `is_structural( $first ) && $docs[0]->getStartLine() <= 2 ) { + return Docblock_Adapter::from_text( $docs[0]->getText(), 'global', array() ); + } + return null; } + /** + * The Doc comments attached to a node, in source order. + * + * @param Node $node + * + * @return Doc[] + */ + protected function doc_comments( Node $node ) { + $docs = array(); + foreach ( $node->getComments() as $comment ) { + if ( $comment instanceof Doc ) { + $docs[] = $comment; + } + } + + return $docs; + } + + /** + * Whether a node is a documentable structural element. + * + * @param Node $node + * + * @return bool + */ + protected function is_structural( Node $node ) { + return $node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike; + } + /** * @return Function_Reflector[] */ diff --git a/lib/class-reflectors.php b/lib/class-reflectors.php index 95d70b8e..c8829e3e 100644 --- a/lib/class-reflectors.php +++ b/lib/class-reflectors.php @@ -3,10 +3,9 @@ * Lightweight structural reflectors built on nikic/php-parser 5 nodes. * * These replace the slice of the phpDocumentor 3 reflector API that runner.php - * consumes (getShortName/getNamespace/getArguments/...), keeping the exported - * array shape identical. Each wraps a php-parser node and exposes only what the - * exporter needs. DocBlock parsing is added by the docblock adapter (Stage 5); - * getDocBlock() returns null until then. + * consumes (getShortName/getNamespace/getArguments/getDocBlock/...), keeping the + * exported array shape identical. Each wraps a php-parser node and exposes only + * what the exporter needs. Docblocks are parsed lazily via Docblock_Adapter. * * @package WP_Parser */ @@ -198,7 +197,7 @@ public function getArguments() { } public function getDocBlock() { - return null; // Stage 5. + return Docblock_Adapter::from_node( $this->node, $this->namespace, $this->aliases ); } } @@ -263,7 +262,7 @@ public function getProperties() { $properties = array(); foreach ( $this->node->getProperties() as $property ) { foreach ( $property->props as $prop ) { - $properties[] = new Property_Reflector( $property, $prop ); + $properties[] = new Property_Reflector( $property, $prop, $this->namespace, $this->aliases ); } } @@ -273,14 +272,14 @@ public function getProperties() { public function getMethods() { $methods = array(); foreach ( $this->node->getMethods() as $method ) { - $methods[] = new Method_Reflector( $method, $this->aliases ); + $methods[] = new Method_Reflector( $method, $this->namespace, $this->aliases ); } return $methods; } public function getDocBlock() { - return null; // Stage 5. + return Docblock_Adapter::from_node( $this->node, $this->namespace, $this->aliases ); } } @@ -294,11 +293,15 @@ class Method_Reflector { /** @var Node\Stmt\ClassMethod */ protected $node; + + /** @var string Namespace used to resolve docblock types (the class's namespace). */ + protected $resolve_namespace; protected $aliases; - public function __construct( Node\Stmt\ClassMethod $node, array $aliases ) { - $this->node = $node; - $this->aliases = $aliases; + public function __construct( Node\Stmt\ClassMethod $node, $resolve_namespace, array $aliases ) { + $this->node = $node; + $this->resolve_namespace = $resolve_namespace; + $this->aliases = $aliases; } public function getShortName() { @@ -342,7 +345,7 @@ public function getArguments() { } public function getDocBlock() { - return null; // Stage 5. + return Docblock_Adapter::from_node( $this->node, $this->resolve_namespace, $this->aliases ); } } @@ -357,9 +360,14 @@ class Property_Reflector { /** @var Node\PropertyItem|object The individual property within the declaration. */ protected $prop; - public function __construct( Node\Stmt\Property $stmt, $prop ) { - $this->stmt = $stmt; - $this->prop = $prop; + protected $namespace; + protected $aliases; + + public function __construct( Node\Stmt\Property $stmt, $prop, $namespace, array $aliases ) { + $this->stmt = $stmt; + $this->prop = $prop; + $this->namespace = $namespace; + $this->aliases = $aliases; } public function getName() { @@ -387,6 +395,6 @@ public function getVisibility() { } public function getDocBlock() { - return null; // Stage 5. + return Docblock_Adapter::from_node( $this->stmt, $this->namespace, $this->aliases ); } } diff --git a/tests/unit/docblock-adapter-test.php b/tests/unit/docblock-adapter-test.php new file mode 100644 index 00000000..44ca466d --- /dev/null +++ b/tests/unit/docblock-adapter-test.php @@ -0,0 +1,71 @@ +doc( "/**\n * Summary here.\n *\n * Long\n * description.\n */" ); + + $this->assertSame( 'Summary here.', $db->getShortDescription() ); + $this->assertStringContainsString( '

', $db->getLongDescription()->getFormattedContents() ); + } + + public function test_param_return_since_tags() { + $db = $this->doc( + "/**\n * S.\n *\n * @since 1.2.0\n * @param string \$var A value.\n" + . " * @param WP_Post \$post Post object.\n * @return bool Result.\n */" + ); + + $tags = $db->getTags(); + + // @since exposes a version but no types (so export_docblock keeps it a version tag). + $this->assertSame( 'since', $tags[0]->getName() ); + $this->assertSame( '1.2.0', $tags[0]->getVersion() ); + $this->assertFalse( method_exists( $tags[0], 'getTypes' ) ); + + // @param: types + variable. + $this->assertSame( 'param', $tags[1]->getName() ); + $this->assertSame( array( 'string' ), $tags[1]->getTypes() ); + $this->assertSame( '$var', $tags[1]->getVariableName() ); + + // Class types resolve to a leading-backslash FQN. + $this->assertSame( array( '\\WP_Post' ), $tags[2]->getTypes() ); + + // @return: types, no variable. + $this->assertSame( array( 'bool' ), $tags[3]->getTypes() ); + $this->assertFalse( method_exists( $tags[3], 'getVariableName' ) ); + } + + public function test_see_and_link_reconstruction() { + $db = $this->doc( "/**\n * S.\n *\n * @see Function/method/class relied on\n * @link URL\n */" ); + + $tags = $db->getTags(); + + // @see is invalid to reflection-docblock (non-FQSEN); reconstructed loosely. + $this->assertSame( 'see', $tags[0]->getName() ); + $this->assertSame( 'Function/method/class', $tags[0]->getReference() ); + $this->assertSame( 'relied on', $tags[0]->getDescription() ); + + // @link with no description uses the URL as content. + $this->assertSame( 'link', $tags[1]->getName() ); + $this->assertSame( 'URL', $tags[1]->getLink() ); + $this->assertSame( 'URL', $tags[1]->getDescription() ); + } +} From 1b414e7452f5782bbcfd6cf366d43247767f71fa Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 22:02:04 -0400 Subject: [PATCH 09/20] feat: capture WordPress hooks and per-element uses - Rewrite the four call/hook reflectors onto php-parser 5 (Function_Call, Method_Call, Static_Method_Call, Hook): restore the WP-globals class map, self/parent/$this resolution, the full hook-type switch, name cleanup, arg shift - File_Reflector records hooks (do_action/apply_filters + variants) and per-element $uses via node attributes (not dynamic props), with the last_doc carry-over for undocumented hooks and per-method called-in-class assignment - Add a Class_Name_Resolver pass that fully-qualifies class-position names so a nested Class::m() caller prints as \Class::m() while function names stay unqualified, matching the legacy php-parser 1 output - Docblock adapter: reconstruct @param/@var InvalidTags (e.g. $this) with type resolution via the docblock context - PHPUnit 9: assertInternalType -> assertIsArray in export-testcase Stage 6 - parser rewrite complete. Golden 16/16, WP 22/22, unit 11/50 all green. --- lib/class-docblock-adapter.php | 57 +++++- lib/class-file-reflector.php | 187 +++++++++++++++++-- lib/class-function-call-reflector.php | 61 ++++--- lib/class-hook-reflector.php | 52 +++++- lib/class-method-call-reflector.php | 202 +++++++++++---------- lib/class-reflectors.php | 12 ++ lib/class-static-method-call-reflector.php | 16 +- tests/phpunit/includes/export-testcase.php | 16 +- 8 files changed, 439 insertions(+), 164 deletions(-) diff --git a/lib/class-docblock-adapter.php b/lib/class-docblock-adapter.php index 9a0205af..d9bca0b7 100644 --- a/lib/class-docblock-adapter.php +++ b/lib/class-docblock-adapter.php @@ -18,6 +18,7 @@ use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlock\Tags; use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Context; @@ -29,8 +30,12 @@ class Docblock_Adapter { /** @var DocBlock */ protected $docblock; - public function __construct( DocBlock $docblock ) { + /** @var Context|null */ + protected $context; + + public function __construct( DocBlock $docblock, Context $context = null ) { $this->docblock = $docblock; + $this->context = $context; } /** @@ -74,7 +79,7 @@ public static function from_text( $text, $namespace, array $aliases ) { return null; } - return new self( $docblock ); + return new self( $docblock, $context ); } public function getShortDescription() { @@ -91,7 +96,7 @@ public function getLongDescription() { public function getTags() { $tags = array(); foreach ( $this->docblock->getTags() as $tag ) { - $tags[] = self::adapt_tag( $tag ); + $tags[] = $this->adapt_tag( $tag ); } return $tags; @@ -104,14 +109,14 @@ public function getTags() { * * @return object */ - protected static function adapt_tag( $tag ) { + protected function adapt_tag( $tag ) { $name = $tag->getName(); // Tags reflection-docblock can't parse strictly (e.g. @see with a non-FQSEN - // reference) come back as InvalidTag; reconstruct from the raw body to match - // the legacy loose parsing. + // reference, or @param with a $this variable) come back as InvalidTag; + // reconstruct from the raw body to match the legacy loose parsing. if ( $tag instanceof Tags\InvalidTag ) { - return self::adapt_invalid_tag( $tag, $name ); + return $this->adapt_invalid_tag( $tag, $name ); } $description = self::render_description( method_exists( $tag, 'getDescription' ) ? $tag->getDescription() : null ); @@ -162,7 +167,7 @@ protected static function adapt_tag( $tag ) { * * @return object */ - protected static function adapt_invalid_tag( $tag, $name ) { + protected function adapt_invalid_tag( $tag, $name ) { $body = preg_replace( '/^@' . preg_quote( $name, '/' ) . '\s*/', '', $tag->render() ); // @see / @uses: the first token is the reference, the remainder the description. @@ -176,9 +181,45 @@ protected static function adapt_invalid_tag( $tag, $name ) { ); } + // @param / @var with a variable reflection-docblock rejects (e.g. $this): + // " <$variable> ". + if ( 'param' === $name || 'var' === $name || 'property' === $name ) { + $parts = preg_split( '/\s+/', $body, 3 ); + + if ( isset( $parts[1] ) && 0 === strpos( $parts[1], '$' ) ) { + return new Param_Tag_Adapter( + $name, + isset( $parts[2] ) ? $parts[2] : '', + $this->resolve_type_string( $parts[0] ), + $parts[1] + ); + } + } + return new Generic_Tag_Adapter( $name, $body ); } + /** + * Resolve a written type string to the legacy array of type strings, using the + * docblock context (so class names get the leading-backslash FQN form). + * + * @param string $type_string + * + * @return string[] + */ + protected function resolve_type_string( $type_string ) { + static $resolver = null; + if ( null === $resolver ) { + $resolver = new TypeResolver(); + } + + try { + return self::type_to_legacy_strings( $resolver->resolve( $type_string, $this->context ) ); + } catch ( \Throwable $e ) { + return array( $type_string ); + } + } + /** * Render a tag's description object to a string. * diff --git a/lib/class-file-reflector.php b/lib/class-file-reflector.php index 2dd2bc3d..d856a710 100644 --- a/lib/class-file-reflector.php +++ b/lib/class-file-reflector.php @@ -14,16 +14,14 @@ * * Replaces the former phpDocumentor FileReflector subclass. It walks the AST as * a NodeVisitor, collecting the structural elements (functions, classes, their - * methods and properties) that runner.php exports, while keeping the exported - * array shape identical. - * - * Hook detection (do_action/apply_filters) and the per-element $uses list are - * added in a later stage; for now $uses stays empty. + * methods and properties) that runner.php exports, the WordPress hooks declared + * via do_action()/apply_filters(), and the functions/methods each element uses — + * all while keeping the exported array shape identical. */ class File_Reflector extends NodeVisitorAbstract { /** - * Elements used in file scope, indexed by element type (hooks, functions, methods). + * Elements used in file scope, indexed by element type (functions, methods, hooks). * * @var array */ @@ -45,6 +43,12 @@ class File_Reflector extends NodeVisitorAbstract { /** @var Docblock_Adapter|null The file-level docblock, if any. */ protected $file_docblock = null; + /** @var Node[] Stack of scope nodes (function/method/class) currently open. */ + protected $location = array(); + + /** @var Doc|null Last docblock seen on a non-documentable node, for the next hook. */ + protected $last_doc = null; + /** @var string Current namespace ('global' at file scope). */ protected $namespace = 'global'; @@ -93,22 +97,29 @@ public function process() { $this->file_docblock = $this->detect_file_docblock( $stmts ); + // Pass 1: resolve names. replaceNodes:false keeps the original Name nodes and + // attaches resolved names as attributes — the v5 analog of v1's namespacedName. + $resolver = new NodeTraverser(); + $resolver->addVisitor( new NameResolver( null, array( 'replaceNodes' => false ) ) ); + $stmts = $resolver->traverse( $stmts ); + + // Pass 2: fully-qualify class-position names (so a nested `Class::m()` caller + // prints as `\Class::m()`) while leaving function names alone, then reflect. $traverser = new NodeTraverser(); - // replaceNodes:false keeps the original Name nodes and attaches resolved - // names as attributes — the v5 analog of v1's $node->namespacedName. - $traverser->addVisitor( new NameResolver( null, array( 'replaceNodes' => false ) ) ); + $traverser->addVisitor( new Class_Name_Resolver() ); $traverser->addVisitor( $this ); $traverser->traverse( $stmts ); } /** - * Track the current namespace and import aliases as we enter nodes. + * Track scope, record hook/function/method usage, and carry hook docblocks. * * @param Node $node * * @return null */ public function enterNode( Node $node ) { + // Track namespace and import aliases. if ( $node instanceof Node\Stmt\Namespace_ ) { $this->namespace = $node->name ? $node->name->toString() : 'global'; $this->aliases = array(); @@ -118,6 +129,40 @@ public function enterNode( Node $node ) { } } + // Maintain the scope stack so calls are attributed to the right element. + if ( $node instanceof Node\Stmt\Function_ + || $node instanceof Node\Stmt\ClassMethod + || $node instanceof Node\Stmt\Class_ ) { + $this->location[] = $node; + } + + // Record function/method/hook usage. + if ( $node instanceof Node\Expr\FuncCall ) { + $this->add_use( 'functions', new Function_Call_Reflector( $node ) ); + + if ( $this->is_filter( $node ) ) { + if ( $this->last_doc && null === $node->getDocComment() ) { + $node->setAttribute( 'comments', array( $this->last_doc ) ); + $this->last_doc = null; + } + + $this->add_use( 'hooks', new Hook_Reflector( $node, $this->namespace, $this->aliases ) ); + } + } elseif ( $node instanceof Node\Expr\MethodCall ) { + $this->add_use( 'methods', new Method_Call_Reflector( $node ) ); + } elseif ( $node instanceof Node\Expr\StaticCall ) { + $this->add_use( 'methods', new Static_Method_Call_Reflector( $node ) ); + } elseif ( $node instanceof Node\Expr\New_ ) { + $this->add_use( 'methods', new Method_Call_Reflector( $node ) ); + } + + // Carry a docblock from a non-documentable node to the next hook. + if ( ! $this->is_node_documentable( $node ) + && ! ( $node instanceof Node\Name ) + && null !== $node->getDocComment() ) { + $this->last_doc = $node->getDocComment(); + } + return null; } @@ -126,7 +171,8 @@ public function enterNode( Node $node ) { * * Functions and classes are recorded on leave (not enter) so that nested * functions are listed before their enclosing function — matching the order - * the legacy parser produced. + * the legacy parser produced. Leaving a class is also when its methods' calls + * learn which class they were made in (for $this/self/parent resolution). * * @param Node $node * @@ -136,12 +182,103 @@ public function leaveNode( Node $node ) { if ( $node instanceof Node\Stmt\Function_ ) { $this->functions[] = new Function_Reflector( $node, $this->namespace, $this->aliases ); } elseif ( $node instanceof Node\Stmt\Class_ && null !== $node->name ) { - $this->classes[] = new Class_Reflector( $node, $this->namespace, $this->aliases ); + $class = new Class_Reflector( $node, $this->namespace, $this->aliases ); + $this->set_called_in_class( $node, $class ); + $this->classes[] = $class; + } + + if ( ! empty( $this->location ) && end( $this->location ) === $node ) { + array_pop( $this->location ); } return null; } + /** + * Append a used element to the current scope (file, function, or method). + * + * Method/function scope uses are stored on the scope node as an attribute, so + * the matching reflector picks them up when it is built. + * + * @param string $type Use type: functions, methods, or hooks. + * @param object $item The reflector for the used element. + */ + protected function add_use( $type, $item ) { + if ( empty( $this->location ) ) { + $this->uses[ $type ][] = $item; + + return; + } + + $scope = end( $this->location ); + $uses = $scope->getAttribute( 'wp_parser_uses', array() ); + $uses[ $type ][] = $item; + $scope->setAttribute( 'wp_parser_uses', $uses ); + } + + /** + * Tell each method call within a class which class it was made in. + * + * @param Node\Stmt\Class_ $node + * @param Class_Reflector $class + */ + protected function set_called_in_class( Node\Stmt\Class_ $node, Class_Reflector $class ) { + foreach ( $node->getMethods() as $method ) { + $uses = $method->getAttribute( 'wp_parser_uses' ); + + if ( empty( $uses['methods'] ) ) { + continue; + } + + foreach ( $uses['methods'] as $call ) { + if ( $call instanceof Method_Call_Reflector ) { + $call->set_class( $class ); + } + } + } + } + + /** + * Whether a function call is a WordPress hook declaration. + * + * @param Node\Expr\FuncCall $node + * + * @return bool + */ + protected function is_filter( Node\Expr\FuncCall $node ) { + if ( ! ( $node->name instanceof Node\Name ) ) { + return false; + } + + $functions = array( + 'apply_filters', + 'apply_filters_ref_array', + 'apply_filters_deprecated', + 'do_action', + 'do_action_ref_array', + 'do_action_deprecated', + ); + + return in_array( (string) $node->name, $functions, true ); + } + + /** + * Whether a node is a documentable structural element (or a hook). + * + * @param Node $node + * + * @return bool + */ + protected function is_node_documentable( Node $node ) { + return $node instanceof Node\Stmt\Function_ + || $node instanceof Node\Stmt\ClassLike + || $node instanceof Node\Stmt\ClassMethod + || $node instanceof Node\Stmt\Property + || $node instanceof Node\Stmt\ClassConst + || $node instanceof Node\Stmt\Const_ + || ( $node instanceof Node\Expr\FuncCall && $this->is_filter( $node ) ); + } + /** * The file-level docblock, or null when the file has none. * @@ -259,3 +396,29 @@ public function getIncludes() { return $this->includes; } } + +/** + * Replaces class-position names (static calls, `new`, class-const/static-property + * fetches, instanceof) with their resolved fully-qualified form, so they render + * with a leading backslash when pretty printed — while leaving function/constant + * names untouched. This reproduces the legacy php-parser 1 name-resolution output. + */ +class Class_Name_Resolver extends NodeVisitorAbstract { + + public function enterNode( Node $node ) { + $is_class_ref = $node instanceof Node\Expr\StaticCall + || $node instanceof Node\Expr\New_ + || $node instanceof Node\Expr\ClassConstFetch + || $node instanceof Node\Expr\StaticPropertyFetch + || $node instanceof Node\Expr\Instanceof_; + + if ( $is_class_ref && $node->class instanceof Node\Name ) { + $resolved = $node->class->getAttribute( 'resolvedName' ); + if ( null !== $resolved ) { + $node->class = $resolved; + } + } + + return null; + } +} diff --git a/lib/class-function-call-reflector.php b/lib/class-function-call-reflector.php index 233e6d87..c4efab6e 100644 --- a/lib/class-function-call-reflector.php +++ b/lib/class-function-call-reflector.php @@ -1,51 +1,56 @@ node = $node; + } /** - * Returns the name for this Reflector instance. + * The called function's name. + * + * Namespaced calls resolve to a leading-backslash FQN (via the NameResolver + * namespacedName attribute); global calls stay unqualified, matching the + * legacy output (e.g. apply_filters, do_action). * * @return string */ public function getName() { - if ( isset( $this->node->namespacedName ) ) { - return '\\' . implode( '\\', $this->node->namespacedName->parts ); - } + $name = $this->node->name; - $shortName = $this->getShortName(); + if ( $name instanceof Node\Name ) { + $namespaced = $name->getAttribute( 'namespacedName' ); + if ( null !== $namespaced ) { + return '\\' . $namespaced->toString(); + } - if ( is_a( $shortName, 'PHPParser_Node_Name_FullyQualified' ) ) { - return '\\' . (string) $shortName; + return $name->toString(); } - if ( is_a( $shortName, 'PHPParser_Node_Name' ) ) { - return (string) $shortName; + if ( $name instanceof Node\Expr\Variable && is_string( $name->name ) ) { + return $name->name; } - /** @var \PHPParser_Node_Expr_ArrayDimFetch $shortName */ - if ( is_a( $shortName, 'PHPParser_Node_Expr_ArrayDimFetch' ) ) { - $var = $shortName->var->name; - $dim = $shortName->dim->name->parts[0]; - - return "\${$var}[{$dim}]"; - } + return Reflector_Helpers::pretty_print_expr( $name ); + } - /** @var \PHPParser_Node_Expr_Variable $shortName */ - if ( is_a( $shortName, 'PHPParser_Node_Expr_Variable' ) ) { - return $shortName->name; - } + public function getLineNumber() { + return $this->node->getStartLine(); + } - return (string) $shortName; + public function getNode() { + return $this->node; } } diff --git a/lib/class-hook-reflector.php b/lib/class-hook-reflector.php index 50075e0f..d13dafcb 100644 --- a/lib/class-hook-reflector.php +++ b/lib/class-hook-reflector.php @@ -1,24 +1,39 @@ node = $node; + $this->namespace = $namespace; + $this->aliases = $aliases; + } /** * @return string */ public function getName() { - $printer = new PHPParser_PrettyPrinter_Default; + $printer = new Pretty_Printer(); + return $this->cleanupName( $printer->prettyPrintExpr( $this->node->args[0]->value ) ); } /** + * Normalize a hook name expression to the documented form. + * * @param string $name * * @return string @@ -60,6 +75,7 @@ public function getShortName() { */ public function getType() { $type = 'filter'; + switch ( (string) $this->node->name ) { case 'do_action': $type = 'action'; @@ -73,7 +89,7 @@ public function getType() { case 'apply_filters_ref_array': $type = 'filter_reference'; break; - case 'apply_filters_deprecated'; + case 'apply_filters_deprecated': $type = 'filter_deprecated'; break; } @@ -85,15 +101,31 @@ public function getType() { * @return array */ public function getArgs() { - $printer = new Pretty_Printer; + $printer = new Pretty_Printer(); $args = array(); + foreach ( $this->node->args as $arg ) { $args[] = $printer->prettyPrintArg( $arg ); } - // Skip the filter name + // Skip the hook name. array_shift( $args ); return $args; } + + public function getLineNumber() { + return $this->node->getStartLine(); + } + + public function getNode() { + return $this->node; + } + + /** + * @return Docblock_Adapter|null + */ + public function getDocBlock() { + return Docblock_Adapter::from_node( $this->node, $this->namespace, $this->aliases ); + } } diff --git a/lib/class-method-call-reflector.php b/lib/class-method-call-reflector.php index 570d5a54..0cda3ab4 100644 --- a/lib/class-method-call-reflector.php +++ b/lib/class-method-call-reflector.php @@ -1,158 +1,178 @@ node = $node; + } + /** * Returns the name for this Reflector instance. * - * @return string[] Index 0 is the calling instance, 1 is the method name. + * @return string[] Index 0 is the calling instance/class, 1 is the method name. */ public function getName() { - - if ( 'Expr_New' === $this->node->getType() ) { - $name = '__construct'; + if ( $this->node instanceof Node\Expr\New_ ) { + $name = '__construct'; $caller = $this->node->class; } else { - $name = $this->getShortName(); + $name = $this->node->name instanceof Node\Identifier ? $this->node->name->toString() : ''; $caller = $this->node->var; } - if ( $caller instanceof \PHPParser_Node_Expr ) { - $printer = new Pretty_Printer; - $caller = $printer->prettyPrintExpr( $caller ); - } elseif ( $caller instanceof \PHPParser_Node_Name_FullyQualified ) { - $caller = '\\' . $caller->toString(); - } elseif ( $caller instanceof \PHPParser_Node_Name ) { - $caller = $caller->toString(); - } + return array( $this->resolve_caller( $caller ), $name ); + } - $caller = $this->_resolveName( $caller ); + /** + * Resolve a caller node to its legacy string form. + * + * @param Node $caller + * + * @return string + */ + protected function resolve_caller( $caller ) { + if ( $caller instanceof Node\Name ) { + $lower = strtolower( $caller->toString() ); - // If the caller is a function, convert it to the function name - if ( is_a( $caller, 'PHPParser_Node_Expr_FuncCall' ) ) { + if ( 'self' === $lower || 'parent' === $lower || 'static' === $lower ) { + return $this->map_class( $this->resolve_relative_name( $caller->toString() ) ); + } - // Add parentheses to signify this is a function call - /** @var \PHPParser_Node_Expr_FuncCall $caller */ - $caller = implode( '\\', $caller->name->parts ) . '()'; + return $this->map_class( Reflector_Helpers::class_name( $caller ) ); } - $class_mapping = $this->_getClassMapping(); - if ( array_key_exists( $caller, $class_mapping ) ) { - $caller = $class_mapping[ $caller ]; + if ( $caller instanceof Node\Expr ) { + return $this->map_class( $this->resolve_relative_name( Reflector_Helpers::pretty_print_expr( $caller ) ) ); } - return array( $caller, $name ); + return $this->map_class( (string) $caller ); } /** - * Set the class that this method was called within. + * Apply the WordPress global-variable => class mapping. + * + * @param string $caller * - * @param ClassReflector $class + * @return string */ - public function set_class( ClassReflector $class ) { + protected function map_class( $caller ) { + $mapping = $this->get_class_mapping(); - $this->called_in_class = $class; + return array_key_exists( $caller, $mapping ) ? $mapping[ $caller ] : $caller; } /** - * Returns whether or not this method call is a static call + * Set the class that this method was called within. * - * @return bool Whether or not this method call is a static call + * @param Class_Reflector $class */ - public function isStatic() { - return false; + public function set_class( Class_Reflector $class ) { + $this->called_in_class = $class; } /** - * Returns a mapping from variable names to a class name, leverages globals for most used classes + * Whether this is a static call. * - * @return array Class mapping to map variable names to classes + * @return bool */ - protected function _getClassMapping() { - - // List of global use generated using following command: - // ack "global \\\$[^;]+;" --no-filename | tr -d '\t' | sort | uniq | sed "s/global //g" | sed "s/, /,/g" | tr , '\n' | sed "s/;//g" | sort | uniq | sed "s/\\\$//g" | sed "s/[^ ][^ ]*/'&' => ''/g" - // There is probably an easier way, there are currently no globals that are classes starting with an underscore - $wp_globals = array( - 'authordata' => 'WP_User', - 'custom_background' => 'Custom_Background', - 'custom_image_header' => 'Custom_Image_Header', - 'phpmailer' => 'PHPMailer', - 'post' => 'WP_Post', - 'userdata' => 'WP_User', // This can also be stdClass, but you can't call methods on an stdClass - 'wp' => 'WP', - 'wp_admin_bar' => 'WP_Admin_Bar', - 'wp_customize' => 'WP_Customize_Manager', - 'wp_embed' => 'WP_Embed', - 'wp_filesystem' => 'WP_Filesystem', - 'wp_hasher' => 'PasswordHash', // This can be overridden by plugins, for core assume this is ours - 'wp_json' => 'Services_JSON', - 'wp_list_table' => 'WP_List_Table', // This one differs because there are a lot of different List Tables, assume they all only overwrite existing functions on WP_List_Table - 'wp_locale' => 'WP_Locale', - 'wp_object_cache' => 'WP_Object_Cache', - 'wp_query' => 'WP_Query', - 'wp_rewrite' => 'WP_Rewrite', - 'wp_roles' => 'WP_Roles', - 'wp_scripts' => 'WP_Scripts', - 'wp_styles' => 'WP_Styles', - 'wp_the_query' => 'WP_Query', - 'wp_widget_factory' => 'WP_Widget_Factory', - 'wp_xmlrpc_server' => 'wp_xmlrpc_server', // This can be overridden by plugins, for core assume this is ours - 'wpdb' => 'wpdb', - ); - - $wp_functions = array( - 'get_current_screen()' => 'WP_Screen', - '_get_list_table()' => 'WP_List_Table', // This one differs because there are a lot of different List Tables, assume they all only overwrite existing functions on WP_List_Table - 'wp_get_theme()' => 'WP_Theme', - ); + public function isStatic() { + return false; + } - $class_mapping = array_merge( $wp_globals, $wp_functions ); + public function getLineNumber() { + return $this->node->getStartLine(); + } - return $class_mapping; + public function getNode() { + return $this->node; } /** - * Resolve a class name from self/parent. + * Resolve $this/self/parent against the enclosing class. * - * @param string $class The class name. + * @param string $class * - * @return string The resolved class name. + * @return string */ - protected function _resolveName( $class ) { - + protected function resolve_relative_name( $class ) { if ( ! $this->called_in_class ) { return $class; } - switch ( $class ) { case '$this': case 'self': $namespace = (string) $this->called_in_class->getNamespace(); $namespace = ( 'global' !== $namespace ) ? $namespace . '\\' : ''; - $class = '\\' . $namespace . $this->called_in_class->getShortName(); - break; + + return '\\' . $namespace . $this->called_in_class->getShortName(); + case 'parent': - $class = '\\' . $this->called_in_class->getNode()->extends->toString(); - break; + return $this->called_in_class->getParentClass(); } return $class; } + + /** + * A mapping from common WordPress global variable names to their class. + * + * @return array + */ + protected function get_class_mapping() { + $wp_globals = array( + 'authordata' => 'WP_User', + 'custom_background' => 'Custom_Background', + 'custom_image_header' => 'Custom_Image_Header', + 'phpmailer' => 'PHPMailer', + 'post' => 'WP_Post', + 'userdata' => 'WP_User', + 'wp' => 'WP', + 'wp_admin_bar' => 'WP_Admin_Bar', + 'wp_customize' => 'WP_Customize_Manager', + 'wp_embed' => 'WP_Embed', + 'wp_filesystem' => 'WP_Filesystem', + 'wp_hasher' => 'PasswordHash', + 'wp_json' => 'Services_JSON', + 'wp_list_table' => 'WP_List_Table', + 'wp_locale' => 'WP_Locale', + 'wp_object_cache' => 'WP_Object_Cache', + 'wp_query' => 'WP_Query', + 'wp_rewrite' => 'WP_Rewrite', + 'wp_roles' => 'WP_Roles', + 'wp_scripts' => 'WP_Scripts', + 'wp_styles' => 'WP_Styles', + 'wp_the_query' => 'WP_Query', + 'wp_widget_factory' => 'WP_Widget_Factory', + 'wp_xmlrpc_server' => 'wp_xmlrpc_server', + 'wpdb' => 'wpdb', + ); + + $wp_functions = array( + 'get_current_screen()' => 'WP_Screen', + '_get_list_table()' => 'WP_List_Table', + 'wp_get_theme()' => 'WP_Theme', + ); + + return array_merge( $wp_globals, $wp_functions ); + } } diff --git a/lib/class-reflectors.php b/lib/class-reflectors.php index c8829e3e..47c76d24 100644 --- a/lib/class-reflectors.php +++ b/lib/class-reflectors.php @@ -31,6 +31,16 @@ private static function printer() { return self::$printer; } + /** + * Pretty print an expression to its source string. + * + * @param Node\Expr $expr + * @return string + */ + public static function pretty_print_expr( $expr ) { + return self::printer()->prettyPrintExpr( $expr ); + } + /** * Render a default-value expression to its source string, or null when absent. * @@ -170,6 +180,7 @@ public function __construct( Node\Stmt\Function_ $node, $namespace, array $alias $this->node = $node; $this->namespace = $namespace; $this->aliases = $aliases; + $this->uses = $node->getAttribute( 'wp_parser_uses' ); } public function getShortName() { @@ -302,6 +313,7 @@ public function __construct( Node\Stmt\ClassMethod $node, $resolve_namespace, ar $this->node = $node; $this->resolve_namespace = $resolve_namespace; $this->aliases = $aliases; + $this->uses = $node->getAttribute( 'wp_parser_uses' ); } public function getShortName() { diff --git a/lib/class-static-method-call-reflector.php b/lib/class-static-method-call-reflector.php index 9d39c7c2..9058cc06 100644 --- a/lib/class-static-method-call-reflector.php +++ b/lib/class-static-method-call-reflector.php @@ -1,10 +1,14 @@ node->class; - $prefix = ( is_a( $class, 'PHPParser_Node_Name_FullyQualified' ) ) ? '\\' : ''; - $class = $prefix . $this->_resolveName( implode( '\\', $class->parts ) ); + $method = $this->node->name instanceof Node\Identifier ? $this->node->name->toString() : ''; - return array( $class, $this->getShortName() ); + return array( $this->resolve_caller( $this->node->class ), $method ); } /** diff --git a/tests/phpunit/includes/export-testcase.php b/tests/phpunit/includes/export-testcase.php index 8e5235b9..87c07845 100644 --- a/tests/phpunit/includes/export-testcase.php +++ b/tests/phpunit/includes/export-testcase.php @@ -148,7 +148,7 @@ protected function assertFunctionUses( $type, $function_name, $entity ) { , $function_name ); - $this->assertInternalType( 'array', $function_data ); + $this->assertIsArray( $function_data ); $this->assertEntityUses( $function_data, $type, $entity ); } @@ -167,7 +167,7 @@ protected function assertFunctionNotUses( $type, $function_name, $entity ) { , $function_name ); - $this->assertInternalType( 'array', $function_data ); + $this->assertIsArray( $function_data ); $this->assertEntityNotUses( $function_data, $type, $entity ); } @@ -187,7 +187,7 @@ protected function assertMethodUses( $type, $class_name, $method_name, $entity ) , $class_name ); - $this->assertInternalType( 'array', $class_data ); + $this->assertIsArray( $class_data ); $method_data = $this->find_entity_data_in( $class_data @@ -195,7 +195,7 @@ protected function assertMethodUses( $type, $class_name, $method_name, $entity ) , $method_name ); - $this->assertInternalType( 'array', $method_data ); + $this->assertIsArray( $method_data ); $this->assertEntityUses( $method_data, $type, $entity ); } @@ -215,7 +215,7 @@ protected function assertMethodNotUses( $type, $class_name, $method_name, $entit , $class_name ); - $this->assertInternalType( 'array', $class_data ); + $this->assertIsArray( $class_data ); $method_data = $this->find_entity_data_in( $class_data @@ -223,7 +223,7 @@ protected function assertMethodNotUses( $type, $class_name, $method_name, $entit , $method_name ); - $this->assertInternalType( 'array', $method_data ); + $this->assertIsArray( $method_data ); $this->assertEntityNotUses( $method_data, $type, $entity ); } @@ -419,7 +419,7 @@ protected function assertClassHasDocs( $class, $docs ) { protected function assertMethodHasDocs( $class, $method, $docs ) { $class = $this->find_entity_data_in( $this->export_data, 'classes', $class ); - $this->assertInternalType( 'array', $class ); + $this->assertIsArray( $class ); $method = $this->find_entity_data_in( $class, 'methods', $method ); $this->assertEntityHasDocs( $method, $docs ); @@ -435,7 +435,7 @@ protected function assertMethodHasDocs( $class, $method, $docs ) { protected function assertPropertyHasDocs( $class, $property, $docs ) { $class = $this->find_entity_data_in( $this->export_data, 'classes', $class ); - $this->assertInternalType( 'array', $class ); + $this->assertIsArray( $class ); $property = $this->find_entity_data_in( $class, 'properties', $property ); $this->assertEntityHasDocs( $property, $docs ); From 8953774916e4a7c4d1fd7750372beb5912591207 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 22:06:02 -0400 Subject: [PATCH 10/20] refactor: drop dead phpDocumentor refs and migration skip guards - Remove the obsolete 'use phpDocumentor\Reflection\*' imports from runner.php (those reflectors are gone); update stale PHPDoc type hints to the new wrappers - Remove the now-permanent parser_is_functional() skip guards from the golden and WP suites; the parser is always loadable, so the tests always run Stage 7 cleanup. Golden 16/16, WP 22/22, unit 11/50 still green. --- lib/runner.php | 17 +++++------------ tests/golden/golden.php | 19 ------------------- tests/golden/test-golden-master.php | 6 ------ tests/phpunit/includes/export-testcase.php | 14 -------------- 4 files changed, 5 insertions(+), 51 deletions(-) diff --git a/lib/runner.php b/lib/runner.php index ba3efdd4..56883943 100644 --- a/lib/runner.php +++ b/lib/runner.php @@ -2,13 +2,6 @@ namespace WP_Parser; -use phpDocumentor\Reflection\BaseReflector; -use phpDocumentor\Reflection\ClassReflector\MethodReflector; -use phpDocumentor\Reflection\ClassReflector\PropertyReflector; -use phpDocumentor\Reflection\FunctionReflector; -use phpDocumentor\Reflection\FunctionReflector\ArgumentReflector; -use phpDocumentor\Reflection\ReflectionAbstract; - /** * @param string $directory * @@ -189,7 +182,7 @@ function ( $matches ) use ( $replacement_string ) { } /** - * @param BaseReflector|ReflectionAbstract $element + * @param object $element A reflector exposing getDocBlock(). * * @return array */ @@ -269,7 +262,7 @@ function export_hooks( array $hooks ) { } /** - * @param ArgumentReflector[] $arguments + * @param Argument_Reflector[] $arguments * * @return array */ @@ -288,7 +281,7 @@ function export_arguments( array $arguments ) { } /** - * @param PropertyReflector[] $properties + * @param Property_Reflector[] $properties * * @return array */ @@ -312,7 +305,7 @@ function export_properties( array $properties ) { } /** - * @param MethodReflector[] $methods + * @param Method_Reflector[] $methods * * @return array */ @@ -366,7 +359,7 @@ function export_uses( array $uses ) { foreach ( $uses as $type => $used_elements ) { - /** @var MethodReflector|FunctionReflector $element */ + /** @var Method_Call_Reflector|Function_Call_Reflector $element */ foreach ( $used_elements as $element ) { $name = $element->getName(); diff --git a/tests/golden/golden.php b/tests/golden/golden.php index 12eca92d..1f3b42a8 100644 --- a/tests/golden/golden.php +++ b/tests/golden/golden.php @@ -151,22 +151,3 @@ function to_json( $data ) { function parse_entry( array $entry ) { return normalize( \WP_Parser\parse_files( $entry['files'], $entry['root'] ) ); } - -/** - * Whether the parser is currently loadable. - * - * During the migration — after Stage 2 drops phpdocumentor/reflection but before - * Stage 4 rewrites File_Reflector onto nikic/php-parser 5 — the old - * phpDocumentor\Reflection\FileReflector base class is gone, so loading the - * parser throws. This lets the golden test (and the WP suite) SKIP rather than - * ERROR until the new visitor lands. - * - * @return bool - */ -function parser_is_functional() { - try { - return class_exists( '\WP_Parser\File_Reflector' ); - } catch ( \Throwable $e ) { - return false; - } -} diff --git a/tests/golden/test-golden-master.php b/tests/golden/test-golden-master.php index 94c0a0bd..4e8d71e8 100644 --- a/tests/golden/test-golden-master.php +++ b/tests/golden/test-golden-master.php @@ -29,12 +29,6 @@ class Golden_Master_Test extends TestCase { * @param array $entry Corpus entry: { files, root }. */ public function test_output_matches_golden( $slug, array $entry ) { - if ( ! \WP_Parser\Golden\parser_is_functional() ) { - $this->markTestSkipped( - 'Parser not loadable yet (migration in progress — File_Reflector rewrite pending in Stage 4).' - ); - } - $snapshot = \WP_Parser\Golden\snapshot_path( $slug ); if ( ! file_exists( $snapshot ) ) { diff --git a/tests/phpunit/includes/export-testcase.php b/tests/phpunit/includes/export-testcase.php index 87c07845..88a061fd 100644 --- a/tests/phpunit/includes/export-testcase.php +++ b/tests/phpunit/includes/export-testcase.php @@ -41,20 +41,6 @@ public function set_up() { parent::set_up(); if ( ! $this->export_data ) { - - // During the migration the parser is temporarily unloadable (Stage 2 - // drops phpdocumentor/reflection; Stage 4 rewrites File_Reflector). - // Skip rather than fatal until the new parser lands. - try { - $parser_loadable = class_exists( '\WP_Parser\File_Reflector' ); - } catch ( \Throwable $e ) { - $parser_loadable = false; - } - - if ( ! $parser_loadable ) { - $this->markTestSkipped( 'Parser not loadable (migration in progress); File_Reflector unavailable.' ); - } - $this->parse_file(); } } From 45c6f8c8cab1d4816ac4ec05cc43fdfe81346c02 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 22:11:37 -0400 Subject: [PATCH 11/20] fix: handle anonymous classes in method-call resolver Found by an end-to-end 'wp parser export' of the full WordPress wp-includes (1043 files): 'new class extends Foo {}' reached the string-cast fallback in Method_Call_Reflector and fataled. Return '' for a nameless class instead. Add a regression unit test. Full wp-includes now exports cleanly: 3409 functions, 690 classes, 6247 methods, 2427 hooks, zero fatals. --- lib/class-method-call-reflector.php | 3 ++- tests/unit/file-reflector-test.php | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/class-method-call-reflector.php b/lib/class-method-call-reflector.php index 0cda3ab4..5356c836 100644 --- a/lib/class-method-call-reflector.php +++ b/lib/class-method-call-reflector.php @@ -64,7 +64,8 @@ protected function resolve_caller( $caller ) { return $this->map_class( $this->resolve_relative_name( Reflector_Helpers::pretty_print_expr( $caller ) ) ); } - return $this->map_class( (string) $caller ); + // Anonymous class (e.g. `new class extends Foo {}`) — no name to report. + return ''; } /** diff --git a/tests/unit/file-reflector-test.php b/tests/unit/file-reflector-test.php index 103c8054..befdf782 100644 --- a/tests/unit/file-reflector-test.php +++ b/tests/unit/file-reflector-test.php @@ -82,4 +82,31 @@ public function test_extracts_class_members() { $this->assertSame( "''", $arguments[0]->getDefault() ); $this->assertSame( '', $arguments[0]->getType() ); } + + public function test_handles_anonymous_class_without_fatal() { + // Real WordPress core instantiates anonymous classes; this must not fatal. + $tmp = tempnam( sys_get_temp_dir(), 'wpp' ); + file_put_contents( + $tmp, + "setFilename( 'anon.php' ); + $file->process(); + } finally { + unlink( $tmp ); + } + + $functions = $file->getFunctions(); + $this->assertCount( 1, $functions ); + + $uses = $functions[0]->uses; + $this->assertNotEmpty( $uses['methods'] ); + + $name = $uses['methods'][0]->getName(); + $this->assertSame( '', $name[0] ); // Anonymous class — no name. + $this->assertSame( '__construct', $name[1] ); + } } From fc2e72e8d5c747f10bd8bfad0280c3fc3e2afd28 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 22:46:41 -0400 Subject: [PATCH 12/20] fix: drop redundant Posts-to-Posts plugin from wp-env The plugin bundles scribu/lib-posts-to-posts + scb-framework via Composer, but .wp-env.json also installed the standalone posts-to-posts plugin. Both load the scribu framework, so 'wp-env start' fataled during plugin activation (Cannot redeclare scb_init() / class P2P_Storage not found), failing CI setup before any test ran. Drop the standalone plugin; the bundled copy provides P2P_Storage (verified: phpdoc-parser activates clean, WP 22/22 + golden 16/16 + unit 12/12). --- .wp-env.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.wp-env.json b/.wp-env.json index 357dc595..ce17c31e 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,7 +1,6 @@ { "phpVersion": "8.2", "plugins": [ - ".", - "https://downloads.wordpress.org/plugin/posts-to-posts.latest-stable.zip" + "." ] } From 0faea00c458e5a536d7c86fe28559c9fdc57b2b3 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sat, 30 May 2026 22:55:04 -0400 Subject: [PATCH 13/20] fix: guard activation hook against missing Posts-to-Posts CI runs 'wp-env start' (which activates the plugin) before composer installs vendor/, so P2P_Storage isn't loaded at activation -> 'class P2P_Storage not found' fatal that failed CI setup. Wrap the activation callbacks in a class_exists() check; Relationships already creates the P2P tables on demand, so this is safe. Verified: plugin activates clean with vendor absent. --- plugin.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugin.php b/plugin.php index e858aec4..2fb63e11 100644 --- a/plugin.php +++ b/plugin.php @@ -20,8 +20,15 @@ $wp_parser->on_load(); } -register_activation_hook( __FILE__, array( 'P2P_Storage', 'init' ) ); -register_activation_hook( __FILE__, array( 'P2P_Storage', 'install' ) ); +register_activation_hook( __FILE__, function () { + // The bundled Posts-to-Posts library may not be loaded at activation time + // (e.g. before Composer dependencies are installed in CI). Relationships also + // creates these tables on demand, so guard against a fatal here. + if ( class_exists( 'P2P_Storage' ) ) { + \P2P_Storage::init(); + \P2P_Storage::install(); + } +} ); // TODO safer handling for uninstall //register_uninstall_hook( __FILE__, array( 'P2P_Storage', 'uninstall' ) ); From 35e0541407f3985559a62a9176d241ef396e63c4 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sun, 31 May 2026 00:45:30 -0400 Subject: [PATCH 14/20] fix: match legacy FQN form for class-typed args and @see refs - Fully-qualify class-name argument typehints (\WP_Post), leaving built-in Identifier types (int, string, array) bare. Argument types previously dropped the leading backslash the legacy parser emitted. - Strip reflection-docblock's FQSEN normalization backslash from @see references so they read as written, matching the legacy output. Both are the leading-backslash discrepancy dd32 flagged on upstream PR #247 (present in our parser too); verified against the legacy oracle. --- lib/class-docblock-adapter.php | 8 ++++++-- lib/class-reflectors.php | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/class-docblock-adapter.php b/lib/class-docblock-adapter.php index d9bca0b7..eeac4591 100644 --- a/lib/class-docblock-adapter.php +++ b/lib/class-docblock-adapter.php @@ -143,9 +143,13 @@ protected function adapt_tag( $tag ) { return new Version_Tag_Adapter( $name, $description, (string) $tag->getVersion() ); } - // @see: a reference. + // @see: a reference. reflection-docblock normalizes the target to an FQSEN + // (always leading-backslash); the legacy parser kept it as written, so undo + // the single normalization backslash to match. if ( $tag instanceof Tags\See ) { - return new See_Tag_Adapter( $name, $description, (string) $tag->getReference() ); + $reference = preg_replace( '/^\\\\/', '', (string) $tag->getReference() ); + + return new See_Tag_Adapter( $name, $description, $reference ); } // @link: a URL. When no description is given the URL itself is the content. diff --git a/lib/class-reflectors.php b/lib/class-reflectors.php index 47c76d24..6a21dc9a 100644 --- a/lib/class-reflectors.php +++ b/lib/class-reflectors.php @@ -78,7 +78,12 @@ public static function type_string( $type ) { return implode( '&', array_map( array( self::class, 'type_string' ), $type->types ) ); } - // Identifier or Name. + // Class-name types resolve to the legacy "\Fully\Qualified" form; built-in + // Identifier types (int, string, array, void, …) stay bare. + if ( $type instanceof Node\Name ) { + return self::class_name( $type ); + } + return $type->toString(); } From 1cd198fc837ae4e1ba1a5811dfa704daa66cf9a6 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sun, 31 May 2026 00:45:30 -0400 Subject: [PATCH 15/20] test: cover docblock hard cases from upstream PR #247 - Add type-tags.inc fixture + legacy golden: the WordPress @type hash @param stays inline in content and is not extracted, per dd32 and johnbillion (the wporg-developer theme depends on this). - Extend docblocks.inc (additive) with @see references, a typed-hash method, and a markdown-heavy description; regenerate the legacy oracle. - Unit tests lock the modern @param syntaxes the old parser mangled (?type, parenthesized unions) and modern code typehints (?WP_Post, union, return types) that php-parser v1 could not parse at all. --- tests/golden/snapshots/export__docblocks.json | 142 +++++++-- tests/golden/snapshots/export__type-tags.json | 296 ++++++++++++++++++ tests/phpunit/tests/export/docblocks.inc | 56 ++++ tests/phpunit/tests/export/type-tags.inc | 117 +++++++ tests/unit/docblock-adapter-test.php | 40 +++ tests/unit/file-reflector-test.php | 27 ++ 6 files changed, 657 insertions(+), 21 deletions(-) create mode 100644 tests/golden/snapshots/export__type-tags.json create mode 100644 tests/phpunit/tests/export/type-tags.inc diff --git a/tests/golden/snapshots/export__docblocks.json b/tests/golden/snapshots/export__docblocks.json index ceb6a663..58d9a323 100644 --- a/tests/golden/snapshots/export__docblocks.json +++ b/tests/golden/snapshots/export__docblocks.json @@ -16,36 +16,36 @@ "functions": [ { "name": "apply_filters", - "line": 69, - "end_line": 69 + "line": 100, + "end_line": 100 }, { "name": "apply_filters_ref_array", - "line": 74, - "end_line": 74 + "line": 105, + "end_line": 105 }, { "name": "do_action", - "line": 83, - "end_line": 83 + "line": 114, + "end_line": 114 }, { "name": "do_action", - "line": 85, - "end_line": 85 + "line": 116, + "end_line": 116 }, { "name": "do_action_ref_array", - "line": 90, - "end_line": 90 + "line": 121, + "end_line": 121 } ] }, "hooks": [ { "name": "test_filter", - "line": 69, - "end_line": 69, + "line": 100, + "end_line": 100, "type": "filter", "arguments": [ "$var" @@ -58,8 +58,8 @@ }, { "name": "test_ref_array_filter", - "line": 74, - "end_line": 74, + "line": 105, + "end_line": 105, "type": "filter_reference", "arguments": [ "array(&$var)" @@ -72,8 +72,8 @@ }, { "name": "test_action", - "line": 83, - "end_line": 83, + "line": 114, + "end_line": 114, "type": "action", "arguments": [ "$post" @@ -99,8 +99,8 @@ }, { "name": "undocumented_hook", - "line": 85, - "end_line": 85, + "line": 116, + "end_line": 116, "type": "action", "arguments": [], "doc": { @@ -111,8 +111,8 @@ }, { "name": "test_ref_array_action", - "line": 90, - "end_line": 90, + "line": 121, + "end_line": 121, "type": "action_reference", "arguments": [ "array(&$var)" @@ -177,6 +177,20 @@ ] }, "hooks": [] + }, + { + "name": "test_markdown_in_description", + "namespace": "global", + "aliases": [], + "line": 144, + "end_line": 146, + "arguments": [], + "doc": { + "description": "This method has all kinds of _\"markdown formatting\"_, many of which are not supported.", + "long_description": "

Example code:

foo();\nbar();

Lists:

  • Item 1
  • Item 2

This is a quote:

Quoted Content.
On multiple lines.

Inline Formatting includes Headings.

There's also italics and bold.

", + "tags": [] + }, + "hooks": [] } ], "classes": [ @@ -184,7 +198,7 @@ "name": "Test_Class", "namespace": "global", "line": 40, - "end_line": 64, + "end_line": 95, "final": false, "abstract": false, "extends": "", @@ -273,6 +287,92 @@ } ] } + }, + { + "name": "test_method_see", + "namespace": "", + "aliases": [], + "line": 71, + "end_line": 72, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "A test method that contains various @see.", + "long_description": "", + "tags": [ + { + "name": "see", + "content": "", + "refers": "self::test_method_typed_hash()" + }, + { + "name": "see", + "content": "The WordPress Foundation.", + "refers": "https://wordpressfoundation.org/" + } + ] + } + }, + { + "name": "test_method_typed_hash", + "namespace": "", + "aliases": [], + "line": 91, + "end_line": 93, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$hashed_array", + "default": null, + "type": "array" + }, + { + "name": "$post_or_user", + "default": null, + "type": "" + }, + { + "name": "$nullable_post", + "default": null, + "type": "\\WP_Post" + } + ], + "doc": { + "description": "This is a method that has a param with multiple types within.", + "long_description": "", + "tags": [ + { + "name": "param", + "content": "{ The parameters for this function.
@type int $time The current epoch.
@type ?string $nullable_string A nullable string.
@type string|array List of items:
- 'item1'
- 'item2' Default is 'item1'.
}", + "types": [ + "array" + ], + "variable": "$hashed_array" + }, + { + "name": "param", + "content": "A Post or User.", + "types": [ + "\\WP_Post", + "\\WP_User" + ], + "variable": "$post_or_user" + }, + { + "name": "return", + "content": "An empty array.", + "types": [ + "array" + ] + } + ] + } } ], "doc": { diff --git a/tests/golden/snapshots/export__type-tags.json b/tests/golden/snapshots/export__type-tags.json new file mode 100644 index 00000000..bb496c82 --- /dev/null +++ b/tests/golden/snapshots/export__type-tags.json @@ -0,0 +1,296 @@ +[ + { + "file": { + "description": "Test file for @type tag handling.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "1.0.0" + } + ] + }, + "path": "type-tags.inc", + "root": "{{ROOT}}", + "functions": [ + { + "name": "function_with_hash_notation", + "namespace": "global", + "aliases": [], + "line": 23, + "end_line": 25, + "arguments": [ + { + "name": "$args", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Function with hash notation in @param.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "1.0.0" + }, + { + "name": "param", + "content": "{ Optional. Array of arguments.
@type bool $enabled Whether the feature is enabled. Default false.
@type string $label The label to display.
@type int $max_items Maximum number of items. Default 10.
}", + "types": [ + "array" + ], + "variable": "$args" + }, + { + "name": "return", + "content": "True on success.", + "types": [ + "bool" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "function_with_multiple_hash_params", + "namespace": "global", + "aliases": [], + "line": 45, + "end_line": 46, + "arguments": [ + { + "name": "$options", + "default": null, + "type": "" + }, + { + "name": "$settings", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Function with multiple hash notation params.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "2.0.0" + }, + { + "name": "param", + "content": "{ Configuration options.
@type string $name The name.
@type int $count The count.
}", + "types": [ + "array" + ], + "variable": "$options" + }, + { + "name": "param", + "content": "{ Additional settings.
@type bool $active Whether active.
}", + "types": [ + "array" + ], + "variable": "$settings" + }, + { + "name": "return", + "content": "", + "types": [ + "void" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "function_with_simple_params", + "namespace": "global", + "aliases": [], + "line": 57, + "end_line": 59, + "arguments": [ + { + "name": "$name", + "default": null, + "type": "" + }, + { + "name": "$count", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Function with simple params (no hash notation).", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "1.0.0" + }, + { + "name": "param", + "content": "The name.", + "types": [ + "string" + ], + "variable": "$name" + }, + { + "name": "param", + "content": "The count.", + "types": [ + "int" + ], + "variable": "$count" + }, + { + "name": "return", + "content": "Success.", + "types": [ + "bool" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "function_with_return_hash", + "namespace": "global", + "aliases": [], + "line": 74, + "end_line": 76, + "arguments": [], + "doc": { + "description": "Function with hash notation in @return.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "1.0.0" + }, + { + "name": "return", + "content": "{ Result data.
@type int[] $updated An array of updated IDs.
@type int[] $skipped An array of skipped IDs.
@type string[] $errors An array of error messages.
}", + "types": [ + "array" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "function_with_param_and_return_hash", + "namespace": "global", + "aliases": [], + "line": 96, + "end_line": 98, + "arguments": [ + { + "name": "$options", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Function with hash notation in both @param and @return.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "1.0.0" + }, + { + "name": "param", + "content": "{ Input options.
@type string $mode The processing mode.
@type bool $force Whether to force the operation.
}", + "types": [ + "array" + ], + "variable": "$options" + }, + { + "name": "return", + "content": "{ Output data.
@type bool $success Whether the operation succeeded.
@type string $message A status message.
}", + "types": [ + "array" + ] + } + ] + }, + "hooks": [] + }, + { + "name": "function_with_mixed_params", + "namespace": "global", + "aliases": [], + "line": 115, + "end_line": 117, + "arguments": [ + { + "name": "$name", + "default": null, + "type": "" + }, + { + "name": "$config", + "default": null, + "type": "" + }, + { + "name": "$limit", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Function with mixed params: some with hash, some without.", + "long_description": "", + "tags": [ + { + "name": "since", + "content": "1.0.0" + }, + { + "name": "param", + "content": "Simple string param.", + "types": [ + "string" + ], + "variable": "$name" + }, + { + "name": "param", + "content": "{ Configuration array.
@type int $timeout Timeout in seconds.
@type bool $retry Whether to retry on failure.
}", + "types": [ + "array" + ], + "variable": "$config" + }, + { + "name": "param", + "content": "Simple int param.", + "types": [ + "int" + ], + "variable": "$limit" + }, + { + "name": "return", + "content": "Success.", + "types": [ + "bool" + ] + } + ] + }, + "hooks": [] + } + ] + } +] diff --git a/tests/phpunit/tests/export/docblocks.inc b/tests/phpunit/tests/export/docblocks.inc index 05686f51..813e95a2 100644 --- a/tests/phpunit/tests/export/docblocks.inc +++ b/tests/phpunit/tests/export/docblocks.inc @@ -61,6 +61,37 @@ class Test_Class { public function test_method( $var, $arr ) { return $var; } + + /** + * A test method that contains various @see. + * + * @see self::test_method_typed_hash() + * @see https://wordpressfoundation.org/ The WordPress Foundation. + */ + public function test_method_see() { + } + + /** + * This is a method that has a param with multiple types within. + * + * @param array $hashed_array { + * The parameters for this function. + * + * @type int $time The current epoch. + * @type ?string $nullable_string A nullable string. + * @type string|array List of items: + * - 'item1' + * - 'item2' + * Default is 'item1'. + * } + * @param WP_Post|WP_User $post_or_user A Post or User. + * + * @return array An empty array. + */ + public function test_method_typed_hash( array $hashed_array, $post_or_user, WP_Post $nullable_post ) { + return []; + } + } /** @@ -88,3 +119,28 @@ do_action( 'undocumented_hook' ); * A reference array action. */ do_action_ref_array( 'test_ref_array_action', array( &$var ) ); + +/** + * This method has all kinds of _"markdown formatting"_, many of which are not supported. + * + * Example `code`: + * + * foo(); + * bar(); + * + * Lists: + * - Item 1 + * - Item 2 + * + * This is a quote: + * + * > Quoted Content. + * > On multiple lines. + * + * ## Inline Formatting includes Headings. + * + * There's also _italics_ and **bold**. + */ +function test_markdown_in_description() { + +} \ No newline at end of file diff --git a/tests/phpunit/tests/export/type-tags.inc b/tests/phpunit/tests/export/type-tags.inc new file mode 100644 index 00000000..29a98cd9 --- /dev/null +++ b/tests/phpunit/tests/export/type-tags.inc @@ -0,0 +1,117 @@ +assertSame( 'URL', $tags[1]->getLink() ); $this->assertSame( 'URL', $tags[1]->getDescription() ); } + + /** + * A @see target reflection-docblock recognizes as an FQSEN comes back normalized + * with a leading backslash; the legacy parser kept it as written, so the adapter + * strips the single normalization backslash. (Golden: tests/.../export/docblocks.) + */ + public function test_see_fqsen_reference_drops_normalization_backslash() { + $db = $this->doc( "/**\n * S.\n *\n * @see self::other_method() The description.\n */" ); + + $tags = $db->getTags(); + + $this->assertSame( 'see', $tags[0]->getName() ); + $this->assertSame( 'self::other_method()', $tags[0]->getReference() ); + $this->assertSame( 'The description.', $tags[0]->getDescription() ); + } + + /** + * Modern @param type syntaxes the legacy parser mangled (a leading "?" nullable + * and parenthesized unions) — reflection-docblock 6 parses them correctly. The + * old parser produced garbage for these, so they are excluded from the byte-for- + * byte golden suite and locked here instead. Class names still resolve to the + * leading-backslash FQN form the legacy output used. + */ + public function test_modern_param_type_syntax() { + $db = $this->doc( + "/**\n * S.\n *\n" + . " * @param ?string \$nullable_string A nullable string.\n" + . " * @param ( WP_Post | null ) \$nullable_post A nullable post.\n */" + ); + + $tags = $db->getTags(); + + $this->assertSame( 'param', $tags[0]->getName() ); + $this->assertSame( array( '?string' ), $tags[0]->getTypes() ); + $this->assertSame( '$nullable_string', $tags[0]->getVariableName() ); + + $this->assertSame( 'param', $tags[1]->getName() ); + $this->assertSame( array( '\\WP_Post', 'null' ), $tags[1]->getTypes() ); + $this->assertSame( '$nullable_post', $tags[1]->getVariableName() ); + } } diff --git a/tests/unit/file-reflector-test.php b/tests/unit/file-reflector-test.php index befdf782..d07f337a 100644 --- a/tests/unit/file-reflector-test.php +++ b/tests/unit/file-reflector-test.php @@ -109,4 +109,31 @@ public function test_handles_anonymous_class_without_fatal() { $this->assertSame( '', $name[0] ); // Anonymous class — no name. $this->assertSame( '__construct', $name[1] ); } + + public function test_handles_modern_typehints() { + // The legacy php-parser v1 fataled on nullable typehints, so there is no + // golden oracle for these — reproducing them is the migration's whole point. + // Nullable and union typehints (and return types) must parse, and class names + // resolve to the leading-backslash FQN form the legacy output used elsewhere. + $tmp = tempnam( sys_get_temp_dir(), 'wpp' ); + file_put_contents( + $tmp, + "setFilename( 'modern.php' ); + $file->process(); + } finally { + unlink( $tmp ); + } + + $arguments = $file->getFunctions()[0]->getArguments(); + + $this->assertSame( '$post', $arguments[0]->getName() ); + $this->assertSame( '?\\WP_Post', $arguments[0]->getType() ); + $this->assertSame( '$id', $arguments[1]->getName() ); + $this->assertSame( 'int|string', $arguments[1]->getType() ); + } } From 9b937349f9cdd6d35c70986b76ac518ca3bf6cf2 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sun, 31 May 2026 00:45:30 -0400 Subject: [PATCH 16/20] feat: extract file constants/includes and fix file-docblock claiming - Extract file-level constants (define() calls anywhere + the const keyword) and include/require statements with their legacy type labels ("Include", "Require Once", ...), via new Include_Reflector and Constant_Reflector wrappers. File_Reflector previously returned [] for both, silently dropping these from the export contract. - Fix the file-docblock heuristic: a docblock attached to the open tag floats to the file only when the first statement does not claim it. Hooks, define(), and include/require claim it (as the legacy parser does); only plain calls and assignments leave it for the file. The old check missed bare hooks/define()/require, mis-attributing their docblock to the file on real wp-load.php-style files. - Lock both with a golden fixture (constants-includes.inc, minted from the legacy parser on PHP 7.4) and unit tests. Found while mining upstream PR #247 for export-contract gaps. --- lib/class-file-reflector.php | 103 ++++++++++++++- lib/class-reflectors.php | 58 +++++++++ .../snapshots/export__constants-includes.json | 123 ++++++++++++++++++ .../tests/export/constants-includes.inc | 29 +++++ tests/unit/file-reflector-test.php | 32 +++++ 5 files changed, 338 insertions(+), 7 deletions(-) create mode 100644 tests/golden/snapshots/export__constants-includes.json create mode 100644 tests/phpunit/tests/export/constants-includes.inc diff --git a/lib/class-file-reflector.php b/lib/class-file-reflector.php index d856a710..61673366 100644 --- a/lib/class-file-reflector.php +++ b/lib/class-file-reflector.php @@ -156,6 +156,22 @@ public function enterNode( Node $node ) { $this->add_use( 'methods', new Method_Call_Reflector( $node ) ); } + // Record file includes and constants (define() and the const keyword), + // captured wherever they appear — matching the legacy FileReflector output. + if ( $node instanceof Node\Expr\Include_ ) { + $this->add_include( $node ); + } elseif ( $node instanceof Node\Expr\FuncCall && $this->is_define( $node ) ) { + $this->add_define( $node ); + } elseif ( $node instanceof Node\Stmt\Const_ ) { + foreach ( $node->consts as $const ) { + $this->constants[] = new Constant_Reflector( + $const->name->toString(), + $const->getStartLine(), + Reflector_Helpers::default_value( $const->value ) + ); + } + } + // Carry a docblock from a non-documentable node to the next hook. if ( ! $this->is_node_documentable( $node ) && ! ( $node instanceof Node\Name ) @@ -216,6 +232,59 @@ protected function add_use( $type, $item ) { $scope->setAttribute( 'wp_parser_uses', $uses ); } + /** + * Record an include/require statement with the legacy "Include"/"Require" (Once) + * type label. The name is the literal path when written as a plain string, or '' + * for a computed expression (e.g. dirname( __FILE__ ) . '/bootstrap.php'). + * + * @param Node\Expr\Include_ $node + */ + protected function add_include( Node\Expr\Include_ $node ) { + static $types = array( + Node\Expr\Include_::TYPE_INCLUDE => 'Include', + Node\Expr\Include_::TYPE_INCLUDE_ONCE => 'Include Once', + Node\Expr\Include_::TYPE_REQUIRE => 'Require', + Node\Expr\Include_::TYPE_REQUIRE_ONCE => 'Require Once', + ); + + $this->includes[] = new Include_Reflector( + $node->expr instanceof Node\Scalar\String_ ? $node->expr->value : '', + $node->getStartLine(), + isset( $types[ $node->type ] ) ? $types[ $node->type ] : '' + ); + } + + /** + * Whether a function call is a call to define(). + * + * @param Node\Expr\FuncCall $node + * @return bool + */ + protected function is_define( Node\Expr\FuncCall $node ) { + return $node->name instanceof Node\Name + && 'define' === strtolower( ltrim( $node->name->toString(), '\\' ) ); + } + + /** + * Record a constant declared via define( 'NAME', value ). Only a string-literal + * name is recorded, matching the legacy parser (a computed name has no short name). + * + * @param Node\Expr\FuncCall $node + */ + protected function add_define( Node\Expr\FuncCall $node ) { + $args = $node->getArgs(); + + if ( ! isset( $args[0], $args[1] ) || ! ( $args[0]->value instanceof Node\Scalar\String_ ) ) { + return; + } + + $this->constants[] = new Constant_Reflector( + $args[0]->value->value, + $node->getStartLine(), + Reflector_Helpers::default_value( $args[1]->value ) + ); + } + /** * Tell each method call within a class which class it was made in. * @@ -329,10 +398,12 @@ protected function detect_file_docblock( array $stmts ) { return Docblock_Adapter::from_text( $docs[0]->getText(), 'global', array() ); } - // A single docblock before a non-structural statement floats to the file only - // when it is attached to the open tag (no blank line after `is_structural( $first ) && $docs[0]->getStartLine() <= 2 ) { + // A single docblock floats to the file only when it is attached to the open + // tag (no blank line after `claims_docblock( $first ) && $docs[0]->getStartLine() <= 2 ) { return Docblock_Adapter::from_text( $docs[0]->getText(), 'global', array() ); } @@ -358,14 +429,32 @@ protected function doc_comments( Node $node ) { } /** - * Whether a node is a documentable structural element. + * Whether the first statement in a file claims a preceding docblock as its own, + * keeping that docblock from floating up to become the file docblock. The legacy + * parser reflects — and so attaches the docblock to — structural elements + * (function/class/const), hooks, define() constants, and include/require, but + * not plain function calls or assignments. * * @param Node $node * * @return bool */ - protected function is_structural( Node $node ) { - return $node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike; + protected function claims_docblock( Node $node ) { + if ( $node instanceof Node\Stmt\Function_ + || $node instanceof Node\Stmt\ClassLike + || $node instanceof Node\Stmt\Const_ ) { + return true; + } + + if ( $node instanceof Node\Stmt\Expression ) { + $expr = $node->expr; + + return $expr instanceof Node\Expr\Include_ + || ( $expr instanceof Node\Expr\FuncCall + && ( $this->is_filter( $expr ) || $this->is_define( $expr ) ) ); + } + + return false; } /** diff --git a/lib/class-reflectors.php b/lib/class-reflectors.php index 6a21dc9a..9705c2ee 100644 --- a/lib/class-reflectors.php +++ b/lib/class-reflectors.php @@ -415,3 +415,61 @@ public function getDocBlock() { return Docblock_Adapter::from_node( $this->stmt, $this->namespace, $this->aliases ); } } + +/** + * Reflects a single include/require statement, exposing the surface runner.php + * reads. The type is the legacy human-readable label ("Include", "Require Once"). + */ +class Include_Reflector { + + protected $name; + protected $line; + protected $type; + + public function __construct( $name, $line, $type ) { + $this->name = $name; + $this->line = $line; + $this->type = $type; + } + + public function getName() { + return $this->name; + } + + public function getLineNumber() { + return $this->line; + } + + public function getType() { + return $this->type; + } +} + +/** + * Reflects a file-level constant — a define() call or the const keyword — exposing + * the surface runner.php reads. The value is the pretty-printed default expression. + */ +class Constant_Reflector { + + protected $name; + protected $line; + protected $value; + + public function __construct( $name, $line, $value ) { + $this->name = $name; + $this->line = $line; + $this->value = $value; + } + + public function getShortName() { + return $this->name; + } + + public function getLineNumber() { + return $this->line; + } + + public function getValue() { + return $this->value; + } +} diff --git a/tests/golden/snapshots/export__constants-includes.json b/tests/golden/snapshots/export__constants-includes.json new file mode 100644 index 00000000..4ed5bc8d --- /dev/null +++ b/tests/golden/snapshots/export__constants-includes.json @@ -0,0 +1,123 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "constants-includes.inc", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "dirname", + "line": 6, + "end_line": 6 + }, + { + "name": "define", + "line": 11, + "end_line": 11 + }, + { + "name": "define", + "line": 12, + "end_line": 12 + } + ] + }, + "includes": [ + { + "name": "", + "line": 6, + "type": "Require Once" + }, + { + "name": "helper.php", + "line": 7, + "type": "Include" + }, + { + "name": "", + "line": 8, + "type": "Require" + }, + { + "name": "once.php", + "line": 9, + "type": "Include Once" + }, + { + "name": "inside-function.php", + "line": 19, + "type": "Include" + } + ], + "constants": [ + { + "name": "MY_CONSTANT", + "line": 11, + "value": "42" + }, + { + "name": "MY_STRING", + "line": 12, + "value": "'hello'" + }, + { + "name": "MY_CONST_KEYWORD", + "line": 13, + "value": "'world'" + }, + { + "name": "INSIDE_FUNC", + "line": 20, + "value": "true" + } + ], + "functions": [ + { + "name": "uses_constant", + "namespace": "global", + "aliases": [], + "line": 18, + "end_line": 22, + "arguments": [], + "doc": { + "description": "A function with an include and a define inside.", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "functions": [ + { + "name": "define", + "line": 20, + "end_line": 20 + } + ] + } + } + ], + "classes": [ + { + "name": "Has_Const", + "namespace": "global", + "line": 27, + "end_line": 29, + "final": false, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [], + "doc": { + "description": "A class with a constant.", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/phpunit/tests/export/constants-includes.inc b/tests/phpunit/tests/export/constants-includes.inc new file mode 100644 index 00000000..932f12bf --- /dev/null +++ b/tests/phpunit/tests/export/constants-includes.inc @@ -0,0 +1,29 @@ +assertSame( '$id', $arguments[1]->getName() ); $this->assertSame( 'int|string', $arguments[1]->getType() ); } + + public function test_file_docblock_claimed_by_first_statement() { + // A docblock attached to the open tag (line <= 2) floats up to the file only + // when the first statement does not claim it. Hooks, define(), and require/ + // include claim it; plain calls and assignments do not. Matches the legacy + // parser (see the filters/actions/constants-includes golden fixtures). + $cases = array( + "apply_filters( 'h', \$x );" => '', // hook claims it + "define( 'X', 1 );" => '', // constant claims it + "require_once 'x.php';" => '', // include claims it + "my_func( \$x );" => 'Doc.', // plain call: floats to the file + "\$x = 1;" => 'Doc.', // assignment: floats to the file + ); + + foreach ( $cases as $statement => $expected ) { + $tmp = tempnam( sys_get_temp_dir(), 'wpp' ); + file_put_contents( $tmp, "setFilename( 'fd.php' ); + $file->process(); + } finally { + unlink( $tmp ); + } + + $doc = $file->getDocBlock(); + $actual = $doc ? $doc->getShortDescription() : ''; + + $this->assertSame( $expected, $actual, "first statement: {$statement}" ); + } + } } From a0058f21e60798af8904e025e47b777d41c07551 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sun, 31 May 2026 01:13:50 -0400 Subject: [PATCH 17/20] test: add hooks and class-feature golden coverage Mining upstream PR #247: probed two contract corners our corpus never exercised; both already match the legacy parser byte-for-byte. - hooks-extra: all 6 hook variants (action/filter x ref_array/deprecated), argument shapes, and hook-name normalization (concat -> interpolation). - class-features: abstract/final, extends + multiple implements, method aliases, multi-property declarations, and trait/interface exclusion. --- .../snapshots/export__class-features.json | 161 ++++++++++++ .../golden/snapshots/export__hooks-extra.json | 244 ++++++++++++++++++ tests/phpunit/tests/export/class-features.inc | 36 +++ tests/phpunit/tests/export/hooks-extra.inc | 20 ++ 4 files changed, 461 insertions(+) create mode 100644 tests/golden/snapshots/export__class-features.json create mode 100644 tests/golden/snapshots/export__hooks-extra.json create mode 100644 tests/phpunit/tests/export/class-features.inc create mode 100644 tests/phpunit/tests/export/hooks-extra.inc diff --git a/tests/golden/snapshots/export__class-features.json b/tests/golden/snapshots/export__class-features.json new file mode 100644 index 00000000..1fc7f64d --- /dev/null +++ b/tests/golden/snapshots/export__class-features.json @@ -0,0 +1,161 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "class-features.inc", + "root": "{{ROOT}}", + "classes": [ + { + "name": "Base_Thing", + "namespace": "global", + "line": 6, + "end_line": 24, + "final": false, + "abstract": true, + "extends": "\\Parent_Thing", + "implements": [ + "\\Interface_One", + "\\Interface_Two" + ], + "properties": [ + { + "name": "$static_prop", + "line": 9, + "end_line": 9, + "default": "array()", + "static": true, + "visibility": "public", + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "$untyped_prop", + "line": 10, + "end_line": 10, + "default": null, + "static": false, + "visibility": "protected", + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "$a", + "line": 11, + "end_line": 11, + "default": null, + "static": false, + "visibility": "private", + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "$b", + "line": 11, + "end_line": 11, + "default": "'x'", + "static": false, + "visibility": "private", + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ], + "methods": [ + { + "name": "must_implement", + "namespace": "", + "aliases": [], + "line": 16, + "end_line": 16, + "final": false, + "abstract": true, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$arg", + "default": null, + "type": "" + } + ], + "doc": { + "description": "Must be implemented.", + "long_description": "", + "tags": [] + } + }, + { + "name": "cannot_override", + "namespace": "", + "aliases": [], + "line": 21, + "end_line": 21, + "final": true, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [], + "doc": { + "description": "Cannot be overridden.", + "long_description": "", + "tags": [] + } + }, + { + "name": "static_method", + "namespace": "", + "aliases": [], + "line": 23, + "end_line": 23, + "final": false, + "abstract": false, + "static": true, + "visibility": "public", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ], + "doc": { + "description": "Class features fixture.", + "long_description": "", + "tags": [] + } + }, + { + "name": "Final_Thing", + "namespace": "global", + "line": 26, + "end_line": 28, + "final": true, + "abstract": false, + "extends": "", + "implements": [], + "properties": [], + "methods": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/golden/snapshots/export__hooks-extra.json b/tests/golden/snapshots/export__hooks-extra.json new file mode 100644 index 00000000..8dbfccfc --- /dev/null +++ b/tests/golden/snapshots/export__hooks-extra.json @@ -0,0 +1,244 @@ +[ + { + "file": { + "description": "", + "long_description": "", + "tags": [] + }, + "path": "hooks-extra.inc", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "do_action", + "line": 6, + "end_line": 6 + }, + { + "name": "do_action", + "line": 7, + "end_line": 7 + }, + { + "name": "do_action_ref_array", + "line": 8, + "end_line": 8 + }, + { + "name": "do_action_deprecated", + "line": 9, + "end_line": 9 + }, + { + "name": "apply_filters", + "line": 11, + "end_line": 11 + }, + { + "name": "apply_filters", + "line": 12, + "end_line": 12 + }, + { + "name": "apply_filters_ref_array", + "line": 13, + "end_line": 13 + }, + { + "name": "apply_filters_deprecated", + "line": 14, + "end_line": 14 + }, + { + "name": "apply_filters", + "line": 16, + "end_line": 16 + }, + { + "name": "do_action", + "line": 18, + "end_line": 18 + }, + { + "name": "do_action", + "line": 19, + "end_line": 19 + }, + { + "name": "do_action", + "line": 20, + "end_line": 20 + } + ] + }, + "hooks": [ + { + "name": "simple_action", + "line": 6, + "end_line": 6, + "type": "action", + "arguments": [], + "doc": { + "description": "Comprehensive hook variants.", + "long_description": "", + "tags": [] + } + }, + { + "name": "action_with_args", + "line": 7, + "end_line": 7, + "type": "action", + "arguments": [ + "$a", + "$b" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "action_ref", + "line": 8, + "end_line": 8, + "type": "action_reference", + "arguments": [ + "array(&$x)" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "deprecated_action", + "line": 9, + "end_line": 9, + "type": "action_deprecated", + "arguments": [ + "array($y)", + "'5.0.0'", + "'new_action'" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "simple_filter", + "line": 11, + "end_line": 11, + "type": "filter", + "arguments": [ + "$value" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "filter_with_args", + "line": 12, + "end_line": 12, + "type": "filter", + "arguments": [ + "$value", + "$a", + "$b" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "filter_ref", + "line": 13, + "end_line": 13, + "type": "filter_reference", + "arguments": [ + "array($value, &$x)" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "deprecated_filter", + "line": 14, + "end_line": 14, + "type": "filter_deprecated", + "arguments": [ + "array($value)", + "'5.0.0'", + "'new_filter'" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "assigned_filter", + "line": 16, + "end_line": 16, + "type": "filter", + "arguments": [ + "$default" + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "interpolated_{$dynamic}_action", + "line": 18, + "end_line": 18, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "{$variable_hook}", + "line": 19, + "end_line": 19, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + }, + { + "name": "dynamic_{$suffix}", + "line": 20, + "end_line": 20, + "type": "action", + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/phpunit/tests/export/class-features.inc b/tests/phpunit/tests/export/class-features.inc new file mode 100644 index 00000000..4c2819eb --- /dev/null +++ b/tests/phpunit/tests/export/class-features.inc @@ -0,0 +1,36 @@ + Date: Sun, 31 May 2026 01:13:50 -0400 Subject: [PATCH 18/20] fix: resolve namespaced names, aliases, and FQ hooks like the legacy parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mining upstream PR #247 surfaced four name-resolution gaps in namespaced code (which in wp-includes means the bundled SimplePie/Requests/PHPMailer libraries dd32 diffed): - Method namespace now reports its enclosing namespace (My\Plugin), not '' — '' is correct only for the global namespace. - Exported namespace aliases on functions and methods are fully-qualified ("\Other\Thing"), matching the legacy output (dd32's leading-backslash note). - Function-use names resolve like the legacy parser: unqualified global- fallback calls stay bare (count), while fully-qualified, qualified, and use-function-imported calls become a leading-backslash FQN (\do_action, \Other\helper, \My\Plugin\Sub\thing). The previous code read the wrong resolver attribute and left them all bare. - A fully-qualified \do_action is a plain function call, not a hook. Locked with a golden fixture (namespaced-uses.inc, minted from the legacy parser on PHP 7.4) and a unit test. --- lib/class-file-reflector.php | 4 +- lib/class-function-call-reflector.php | 20 ++- lib/class-reflectors.php | 24 ++- .../snapshots/export__namespaced-uses.json | 164 ++++++++++++++++++ .../phpunit/tests/export/namespaced-uses.inc | 32 ++++ tests/unit/file-reflector-test.php | 35 ++++ 6 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 tests/golden/snapshots/export__namespaced-uses.json create mode 100644 tests/phpunit/tests/export/namespaced-uses.inc diff --git a/lib/class-file-reflector.php b/lib/class-file-reflector.php index 61673366..f5023146 100644 --- a/lib/class-file-reflector.php +++ b/lib/class-file-reflector.php @@ -315,7 +315,9 @@ protected function set_called_in_class( Node\Stmt\Class_ $node, Class_Reflector * @return bool */ protected function is_filter( Node\Expr\FuncCall $node ) { - if ( ! ( $node->name instanceof Node\Name ) ) { + // A fully-qualified call (\do_action) is a plain function call, not a hook — + // the legacy parser only recognised the unqualified hook function names. + if ( ! ( $node->name instanceof Node\Name ) || $node->name instanceof Node\Name\FullyQualified ) { return false; } diff --git a/lib/class-function-call-reflector.php b/lib/class-function-call-reflector.php index c4efab6e..82bdbf3e 100644 --- a/lib/class-function-call-reflector.php +++ b/lib/class-function-call-reflector.php @@ -21,9 +21,10 @@ public function __construct( Node\Expr\FuncCall $node ) { /** * The called function's name. * - * Namespaced calls resolve to a leading-backslash FQN (via the NameResolver - * namespacedName attribute); global calls stay unqualified, matching the - * legacy output (e.g. apply_filters, do_action). + * Reproduces the legacy resolution: an unqualified name that resolves to itself + * is a global-fallback call and stays bare (count, apply_filters); a fully- + * qualified, qualified, or use-function-imported name resolves to a leading- + * backslash FQN (\do_action, \Other\helper, \My\Plugin\Sub\thing). * * @return string */ @@ -31,9 +32,16 @@ public function getName() { $name = $this->node->name; if ( $name instanceof Node\Name ) { - $namespaced = $name->getAttribute( 'namespacedName' ); - if ( null !== $namespaced ) { - return '\\' . $namespaced->toString(); + $resolved = $name->getAttribute( 'resolvedName' ); + + // Unqualified names resolving to themselves stay bare (global fallback). + if ( $name->isUnqualified() + && ( null === $resolved || $resolved->toString() === $name->toString() ) ) { + return $name->toString(); + } + + if ( null !== $resolved ) { + return '\\' . $resolved->toString(); } return $name->toString(); diff --git a/lib/class-reflectors.php b/lib/class-reflectors.php index 9705c2ee..de3c4684 100644 --- a/lib/class-reflectors.php +++ b/lib/class-reflectors.php @@ -123,6 +123,22 @@ public static function class_name( $name ) { return '\\' . ( $resolved ? $resolved->toString() : $name->toString() ); } + /** + * Render a namespace-alias map for export, fully-qualifying each target with a + * leading backslash (alias => "\Fully\Qualified"), matching the legacy output. + * + * @param array $aliases Map of alias => fully-qualified name (no leading slash). + * @return array + */ + public static function export_aliases( array $aliases ) { + $out = array(); + foreach ( $aliases as $alias => $fqn ) { + $out[ $alias ] = '\\' . ltrim( $fqn, '\\' ); + } + + return $out; + } + /** * Build Argument_Reflectors for a list of parameters. * @@ -197,7 +213,7 @@ public function getNamespace() { } public function getNamespaceAliases() { - return $this->aliases; + return Reflector_Helpers::export_aliases( $this->aliases ); } public function getLineNumber() { @@ -326,11 +342,13 @@ public function getShortName() { } public function getNamespace() { - return ''; // Methods carry no namespace in the legacy output. + // A method carries its enclosing namespace; the global namespace is reported + // as '' (not 'global', unlike functions/classes), matching the legacy output. + return 'global' === $this->resolve_namespace ? '' : $this->resolve_namespace; } public function getNamespaceAliases() { - return $this->aliases; + return Reflector_Helpers::export_aliases( $this->aliases ); } public function getLineNumber() { diff --git a/tests/golden/snapshots/export__namespaced-uses.json b/tests/golden/snapshots/export__namespaced-uses.json new file mode 100644 index 00000000..a42e12c9 --- /dev/null +++ b/tests/golden/snapshots/export__namespaced-uses.json @@ -0,0 +1,164 @@ +[ + { + "file": { + "description": "Namespaced name-resolution fixture.", + "long_description": "", + "tags": [] + }, + "path": "namespaced-uses.inc", + "root": "{{ROOT}}", + "functions": [ + { + "name": "namespaced_func", + "namespace": "My\\Plugin", + "aliases": { + "Thing": "\\Other\\Thing", + "Aliased": "\\Some\\Long\\ClassName", + "helper": "\\Other\\helper" + }, + "line": 30, + "end_line": 32, + "arguments": [ + { + "name": "$t", + "default": null, + "type": "\\Other\\Thing" + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "methods": [ + { + "name": "create", + "class": "\\Some\\Long\\ClassName", + "static": true, + "line": 31, + "end_line": 31 + } + ] + } + } + ], + "classes": [ + { + "name": "Widget", + "namespace": "My\\Plugin", + "line": 12, + "end_line": 28, + "final": false, + "abstract": false, + "extends": "\\WP_Widget", + "implements": [ + "\\Other\\Thing" + ], + "properties": [], + "methods": [ + { + "name": "render", + "namespace": "My\\Plugin", + "aliases": { + "Thing": "\\Other\\Thing", + "Aliased": "\\Some\\Long\\ClassName", + "helper": "\\Other\\helper" + }, + "line": 19, + "end_line": 27, + "final": false, + "abstract": false, + "static": false, + "visibility": "public", + "arguments": [ + { + "name": "$t", + "default": null, + "type": "\\Other\\Thing" + } + ], + "doc": { + "description": "Render.", + "long_description": "", + "tags": [ + { + "name": "param", + "content": "A thing.", + "types": [ + "\\Other\\Thing" + ], + "variable": "$t" + }, + { + "name": "return", + "content": "", + "types": [ + "\\Some\\Long\\ClassName" + ] + } + ] + }, + "uses": { + "methods": [ + { + "name": "__construct", + "class": "\\Some\\Long\\ClassName", + "static": false, + "line": 20, + "end_line": 20 + }, + { + "name": "make", + "class": "\\Other\\Thing", + "static": true, + "line": 21, + "end_line": 21 + }, + { + "name": "helper", + "class": "\\My\\Plugin\\Widget", + "static": true, + "line": 22, + "end_line": 22 + }, + { + "name": "widget", + "class": "\\WP_Widget", + "static": true, + "line": 23, + "end_line": 23 + }, + { + "name": "__construct", + "class": "\\DateTime", + "static": false, + "line": 26, + "end_line": 26 + } + ], + "functions": [ + { + "name": "\\do_action", + "line": 24, + "end_line": 24 + }, + { + "name": "\\Other\\helper", + "line": 25, + "end_line": 25 + } + ] + } + } + ], + "doc": { + "description": "", + "long_description": "", + "tags": [] + } + } + ] + } +] diff --git a/tests/phpunit/tests/export/namespaced-uses.inc b/tests/phpunit/tests/export/namespaced-uses.inc new file mode 100644 index 00000000..dc68df64 --- /dev/null +++ b/tests/phpunit/tests/export/namespaced-uses.inc @@ -0,0 +1,32 @@ +assertSame( $expected, $actual, "first statement: {$statement}" ); } } + + public function test_namespaced_function_and_hook_resolution() { + // In a namespace, function-use names resolve to a leading-backslash FQN unless + // they are unqualified global-fallback calls (which stay bare). A fully- + // qualified \do_action is a plain function call, not a hook. (Golden coverage: + // tests/.../export/namespaced-uses.) + $tmp = tempnam( sys_get_temp_dir(), 'wpp' ); + file_put_contents( + $tmp, + "setFilename( 'ns.php' ); + $file->process(); + } finally { + unlink( $tmp ); + } + + $function = $file->getFunctions()[0]; + $this->assertSame( 'My\\Ns', $function->getNamespace() ); + + $names = array_map( + static function ( $use ) { + return $use->getName(); + }, + $function->uses['functions'] + ); + $this->assertSame( array( '\\Other\\helper', '\\do_action', 'count' ), $names ); + + // The fully-qualified \do_action is a function use, not a hook. + $this->assertEmpty( isset( $function->uses['hooks'] ) ? $function->uses['hooks'] : array() ); + } } From 207b2e4946023bb9d86968c1b31c921685b5fc5b Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sun, 31 May 2026 01:20:04 -0400 Subject: [PATCH 19/20] test: add closure call-attribution golden coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mining upstream PR #247: calls inside closures (anonymous functions used as hook callbacks, assigned to variables, or nested in functions) attribute to the enclosing named scope (file or function), not the closure — already matching the legacy parser byte-for-byte. --- tests/golden/snapshots/export__closures.json | 64 ++++++++++++++++++++ tests/phpunit/tests/export/closures.inc | 21 +++++++ 2 files changed, 85 insertions(+) create mode 100644 tests/golden/snapshots/export__closures.json create mode 100644 tests/phpunit/tests/export/closures.inc diff --git a/tests/golden/snapshots/export__closures.json b/tests/golden/snapshots/export__closures.json new file mode 100644 index 00000000..accc9740 --- /dev/null +++ b/tests/golden/snapshots/export__closures.json @@ -0,0 +1,64 @@ +[ + { + "file": { + "description": "Closures and callables.", + "long_description": "", + "tags": [] + }, + "path": "closures.inc", + "root": "{{ROOT}}", + "uses": { + "functions": [ + { + "name": "add_action", + "line": 6, + "end_line": 9 + }, + { + "name": "do_something", + "line": 7, + "end_line": 7 + }, + { + "name": "helper", + "line": 12, + "end_line": 12 + } + ], + "methods": [ + { + "name": "method", + "class": "$obj", + "static": false, + "line": 8, + "end_line": 8 + } + ] + }, + "functions": [ + { + "name": "with_closure", + "namespace": "global", + "aliases": [], + "line": 15, + "end_line": 21, + "arguments": [], + "doc": { + "description": "", + "long_description": "", + "tags": [] + }, + "hooks": [], + "uses": { + "functions": [ + { + "name": "inner_call", + "line": 17, + "end_line": 17 + } + ] + } + } + ] + } +] diff --git a/tests/phpunit/tests/export/closures.inc b/tests/phpunit/tests/export/closures.inc new file mode 100644 index 00000000..d2add3ae --- /dev/null +++ b/tests/phpunit/tests/export/closures.inc @@ -0,0 +1,21 @@ +method(); +} ); + +$callback = function ( $arg ) use ( $x ) { + return helper( $arg ); +}; + +function with_closure() { + $fn = function () { + inner_call(); + }; + + return $fn; +} From fb435f042cbaacb4da15f44e9eb677ab210eceaf Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Sun, 31 May 2026 20:43:34 -0400 Subject: [PATCH 20/20] docs: document new reflector classes and namespaced accessors - Add @var and method docblocks to Include_Reflector and Constant_Reflector. - Document the method namespace rule (global reported as '') and the fully-qualified alias export on the function/method accessors. --- lib/class-reflectors.php | 56 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/class-reflectors.php b/lib/class-reflectors.php index de3c4684..ebf020cb 100644 --- a/lib/class-reflectors.php +++ b/lib/class-reflectors.php @@ -212,6 +212,11 @@ public function getNamespace() { return $this->namespace; } + /** + * The namespace aliases in scope, fully-qualified for export (alias => "\FQN"). + * + * @return array + */ public function getNamespaceAliases() { return Reflector_Helpers::export_aliases( $this->aliases ); } @@ -341,12 +346,21 @@ public function getShortName() { return $this->node->name->toString(); } + /** + * The method's enclosing namespace. The global namespace is reported as '' + * (not 'global', unlike functions and classes), matching the legacy output. + * + * @return string + */ public function getNamespace() { - // A method carries its enclosing namespace; the global namespace is reported - // as '' (not 'global', unlike functions/classes), matching the legacy output. return 'global' === $this->resolve_namespace ? '' : $this->resolve_namespace; } + /** + * The namespace aliases in scope, fully-qualified for export (alias => "\FQN"). + * + * @return array + */ public function getNamespaceAliases() { return Reflector_Helpers::export_aliases( $this->aliases ); } @@ -440,24 +454,43 @@ public function getDocBlock() { */ class Include_Reflector { + /** @var string The included path when written as a string literal, or '' when computed. */ protected $name; + + /** @var int The line the include/require appears on. */ protected $line; + + /** @var string The legacy type label ("Include", "Include Once", "Require", "Require Once"). */ protected $type; + /** + * @param string $name The included path, or '' for a computed expression. + * @param int $line The line number. + * @param string $type The legacy type label. + */ public function __construct( $name, $line, $type ) { $this->name = $name; $this->line = $line; $this->type = $type; } + /** + * @return string + */ public function getName() { return $this->name; } + /** + * @return int + */ public function getLineNumber() { return $this->line; } + /** + * @return string + */ public function getType() { return $this->type; } @@ -469,24 +502,43 @@ public function getType() { */ class Constant_Reflector { + /** @var string The constant's name (the define() name, or the const keyword identifier). */ protected $name; + + /** @var int The line the constant is declared on. */ protected $line; + + /** @var string The pretty-printed value expression. */ protected $value; + /** + * @param string $name The constant name. + * @param int $line The line number. + * @param string $value The pretty-printed value expression. + */ public function __construct( $name, $line, $value ) { $this->name = $name; $this->line = $line; $this->value = $value; } + /** + * @return string + */ public function getShortName() { return $this->name; } + /** + * @return int + */ public function getLineNumber() { return $this->line; } + /** + * @return string + */ public function getValue() { return $this->value; }