diff --git a/.changes/7.x/7.1.0.md b/.changes/7.x/7.1.0.md new file mode 100644 index 00000000..ebfa814e --- /dev/null +++ b/.changes/7.x/7.1.0.md @@ -0,0 +1,16 @@ + +## 7.1.0 - 2024-01-01 + +> **WARNING** +> +> As PHP 8.0 is still supported, database of CompatInfoDB (v5.14) is outdated and does not contain recent PHP 8.3 elements. +> +> But source code that implement PHP 8.3 new features may be detected ! + +### Added + +- PHP 8.3.0 support +- Docker support with official image : +- Compatibility with Symfony Components v7 + +**Full Changelog**: [7.0.3...7.1.0](https://github.com/llaville/php-compatinfo/compare/7.0.3...7.1.0) diff --git a/.github/linters/phpstan.neon.dist b/.github/linters/phpstan.neon.dist index c143cbc7..02689cc1 100644 --- a/.github/linters/phpstan.neon.dist +++ b/.github/linters/phpstan.neon.dist @@ -5,6 +5,7 @@ parameters: excludePaths: - ../../src/Infrastructure/ManifestBuilder.php treatPhpDocTypesAsCertain: false + checkGenericClassInNonGenericObjectType: false ignoreErrors: - message: "#^Method Bartlett\\\\CompatInfo\\\\Application\\\\Analyser\\\\CompatibilityAnalyser\\:\\:leave[a-zA-Z0-9\\_]+\\(\\) is unused\\.$#" diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 9fd204fb..72f04b86 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -5,7 +5,7 @@ on: push: branches: - master - - "7.0" + - "7.1" paths: - docs/** pull_request: @@ -13,9 +13,9 @@ on: jobs: deploy: - uses: llaville/.github/.github/workflows/gh-pages.yml@uml + uses: llaville/.github/.github/workflows/gh-pages.yml@master with: - destination-dir: "7.0" + destination-dir: "7.1" force-orphan: false hook-script: "resources/gh-pages-hook.sh" php-version: "8.1" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1df2c4dc..5ebdf2c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,9 @@ jobs: os: - ubuntu-22.04 php: - - 8.1 + - 8.2 + tools: + - box:4.6 # available since https://github.com/shivammathur/setup-php/releases/tag/2.27.0 steps: - # https://github.com/actions/checkout @@ -37,6 +39,8 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + coverage: "none" + tools: ${{ matrix.tools }} - # https://github.com/ramsey/composer-install name: Install Composer dependencies @@ -56,7 +60,7 @@ jobs: box-manifest manifest:build --ansi -vv -c box.json --output-file=sbom.json box-manifest manifest:build --ansi -vv -c box.json --output-file=console.txt --format console box-manifest manifest:stub --ansi -vv -c box.json --output-file=stub.php --resource console.txt --resource sbom.json - box-manifest box:compile --ansi -vv -c box.json.dist + box compile --ansi -vv -c box.json.dist - # https://github.com/softprops/action-gh-release name: Create Release from current tag @@ -64,7 +68,7 @@ jobs: uses: softprops/action-gh-release@v1 with: # https://github.com/softprops/action-gh-release#-customizing prerelease: false - draft: true + draft: false body_path: ${{ github.workspace }}/.changes/7.x/${{ github.ref_name }}.md # https://github.com/softprops/action-gh-release#%EF%B8%8F-uploading-release-assets files: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e87904c..9d2e2d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## 7.1.0 - 2024-01-01 + +> **WARNING** +> +> As PHP 8.0 is still supported, database of CompatInfoDB (v5.14) is outdated and does not contain recent PHP 8.3 elements. +> +> But source code that implement PHP 8.3 new features may be detected ! + +### Added + +- PHP 8.3.0 support +- Docker support with official image : +- Compatibility with Symfony Components v7 + +**Full Changelog**: [7.0.3...7.1.0](https://github.com/llaville/php-compatinfo/compare/7.0.3...7.1.0) + ## 7.0.3 - 2023-12-30 ### Fixed diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..63c8ed74 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# syntax=docker/dockerfile:1.4 +ARG PHP_VERSION=8.1 + +FROM php:${PHP_VERSION}-cli-alpine + +# https://github.com/opencontainers/image-spec/blob/main/annotations.md + +LABEL org.opencontainers.image.title="llaville/php-compatinfo" +LABEL org.opencontainers.image.description="Docker image of bartlett/php-compatinfo Composer package" +LABEL org.opencontainers.image.source="https://github.com/llaville/php-compatinfo" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.authors="llaville" + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh \ + && cp /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini + +# Create a group and user +RUN addgroup appgroup && adduser appuser -D -G appgroup + +# Tell docker that all future commands should run as the appuser user +USER appuser + +# Install Composer v2 binary version +COPY --from=composer/composer:2-bin /composer /usr/bin/composer +ENV COMPOSER_ALLOW_SUPERUSER 1 +ENV COMPOSER_PREFER_STABLE 1 +RUN composer global require --no-progress bartlett/php-compatinfo ^7.1 +#RUN composer global require --no-progress bartlett/php-compatinfo 7.1.x-dev + +# Following recommendation at https://docs.github.com/en/actions/creating-actions/dockerfile-support-for-github-actions#workdir + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/LICENSE b/LICENSE index 3094565b..3a61c175 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Copyright (c) 2010-2023, Laurent Laville + Copyright (c) 2010-2024, Laurent Laville Credits to : diff --git a/README.md b/README.md index 3b735e3a..64455c17 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,16 @@ **PHP CompatInfo** is a library that can find the minimum version and the extensions required for a piece of code to run. -Running on PHP greater or equal than 7.2 for parsing source code in a format PHP 5.2 to PHP 8.2 +Running on PHP greater or equal than 7.2 for parsing source code in a format PHP 5.2 to PHP 8.3 ## Versions -| Releases | Branch | PHP | Packagist | License | Documentation | -|:--------------|:-------------------------------------------:|:-------------------------------------------------------------:|:---------------------------------------------------------:|:----------------------------------------------:|:----------------------------------------------------------------:| -| Stable v5.5.x | [![Branch 5.5][Branch_55x-img]][Branch_55x] | [![Minimum PHP Version)][PHPVersion_55x-img]][PHPVersion_55x] | [![Stable Version 5.5][Packagist_55x-img]][Packagist_55x] | [![License 5.5][License_55x-img]][License_55x] | [![Documentation 5.5][Documentation_55x-img]][Documentation_55x] | -| Stable v6.5.x | [![Branch 6.5][Branch_65x-img]][Branch_65x] | [![Minimum PHP Version)][PHPVersion_65x-img]][PHPVersion_65x] | [![Stable Version 6.5][Packagist_65x-img]][Packagist_65x] | [![License 6.5][License_65x-img]][License_65x] | [![Documentation 6.5][Documentation_65x-img]][Documentation_65x] | -| Stable v7.0.x | [![Branch 7.0][Branch_70x-img]][Branch_70x] | [![Minimum PHP Version)][PHPVersion_70x-img]][PHPVersion_70x] | [![Stable Version 7.0][Packagist_70x-img]][Packagist_70x] | [![License 7.0][License_70x-img]][License_70x] | [![Documentation 7.0][Documentation_70x-img]][Documentation_70x] | +| Releases | Branch | PHP | Packagist | License | Documentation | +|:----------------|:-------------------------------------------:|:-------------------------------------------------------------:|:---------------------------------------------------------:|:----------------------------------------------:|:----------------------------------------------------------------:| +| Stable v5.5.x | [![Branch 5.5][Branch_55x-img]][Branch_55x] | [![Minimum PHP Version)][PHPVersion_55x-img]][PHPVersion_55x] | [![Stable Version 5.5][Packagist_55x-img]][Packagist_55x] | [![License 5.5][License_55x-img]][License_55x] | [![Documentation 5.5][Documentation_55x-img]][Documentation_55x] | +| Stable v6.5.x | [![Branch 6.5][Branch_65x-img]][Branch_65x] | [![Minimum PHP Version)][PHPVersion_65x-img]][PHPVersion_65x] | [![Stable Version 6.5][Packagist_65x-img]][Packagist_65x] | [![License 6.5][License_65x-img]][License_65x] | [![Documentation 6.5][Documentation_65x-img]][Documentation_65x] | +| Stable v7.0.x | [![Branch 7.0][Branch_70x-img]][Branch_70x] | [![Minimum PHP Version)][PHPVersion_70x-img]][PHPVersion_70x] | [![Stable Version 7.0][Packagist_70x-img]][Packagist_70x] | [![License 7.0][License_70x-img]][License_70x] | [![Documentation 7.0][Documentation_70x-img]][Documentation_70x] | +| Stable v7.1.x | [![Branch 7.1][Branch_71x-img]][Branch_71x] | [![Minimum PHP Version)][PHPVersion_71x-img]][PHPVersion_71x] | [![Stable Version 7.1][Packagist_71x-img]][Packagist_71x] | [![License 7.1][License_71x-img]][License_71x] | [![Documentation 7.1][Documentation_71x-img]][Documentation_71x] | [Branch_55x-img]: https://img.shields.io/badge/branch-5.5-orange [Branch_55x]: https://github.com/llaville/php-compatinfo/tree/5.5 @@ -49,10 +50,21 @@ Running on PHP greater or equal than 7.2 for parsing source code in a format PHP [Documentation_70x-img]: https://img.shields.io/badge/documentation-v7.0-green [Documentation_70x]: https://github.com/llaville/php-compatinfo/tree/7.0/docs +[Branch_71x-img]: https://img.shields.io/badge/branch-7.1-orange +[Branch_71x]: https://github.com/llaville/php-compatinfo/tree/7.1 +[PHPVersion_71x-img]: https://img.shields.io/packagist/php-v/bartlett/php-compatinfo/7.1.0 +[PHPVersion_71x]: https://www.php.net/supported-versions.php +[Packagist_71x-img]: https://img.shields.io/badge/packagist-v7.1.0-blue +[Packagist_71x]: https://packagist.org/packages/bartlett/php-compatinfo +[License_71x-img]: https://img.shields.io/packagist/l/bartlett/php-compatinfo +[License_71x]: https://github.com/llaville/php-compatinfo/blob/7.1/LICENSE +[Documentation_71x-img]: https://img.shields.io/badge/documentation-v7.1-green +[Documentation_71x]: https://github.com/llaville/php-compatinfo/tree/7.1/docs + ## Documentation -All the documentation is available on [website](https://llaville.github.io/php-compatinfo/7.0), -generated from the [docs](https://github.com/llaville/php-compatinfo/tree/7.0/docs) folder. +All the documentation is available on [website](https://llaville.github.io/php-compatinfo/7.1), +generated from the [docs](https://github.com/llaville/php-compatinfo/tree/7.1/docs) folder. ## Contributors diff --git a/composer.json b/composer.json index fb31fc4c..bbc89e5f 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "issues": "https://github.com/llaville/php-compatinfo/issues" }, "require": { - "php": "~8.0 || ~8.1 || ~8.2", + "php": "~8.0 || ~8.1 || ~8.2 || ~8.3", "ext-json": "*", "ext-pcre": "*", "ext-spl": "*", @@ -21,16 +21,16 @@ "nikic/php-parser": "^4.10", "psr/log": "^3.0", "ramsey/uuid": "^3.9 || ^4.0", - "symfony/config": "^5.4 || ^6.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/finder": "^5.4 || ^6.0", - "symfony/dependency-injection": "^5.4 || ^6.0", - "symfony/serializer": "^5.4 || ^6.0", - "symfony/stopwatch": "^5.4 || ^6.0" + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/serializer": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", "bamarni/composer-bin-plugin": "^1.8" }, "authors": [ @@ -68,7 +68,7 @@ }, "extra": { "branch-alias": { - "dev-master": "7.0.x-dev" + "dev-master": "7.1.x-dev" }, "bamarni-bin": { "bin-links": true, diff --git a/config/set/php83.php b/config/set/php83.php new file mode 100644 index 00000000..17cb9d4e --- /dev/null +++ b/config/set/php83.php @@ -0,0 +1,33 @@ +services(); + + $services->defaults() + ->autowire() + ; + + $services->set(OverrideAttributeSniff::class); + $services->set(DynamicClassConstantFetchSniff::class); + $services->set(TypedClassConstantSniff::class); + $services->set(StaticVarInitializerSniff::class); +}; diff --git a/config/set/up-to-php83.php b/config/set/up-to-php83.php new file mode 100644 index 00000000..9ba2f3d4 --- /dev/null +++ b/config/set/up-to-php83.php @@ -0,0 +1,20 @@ +import(__DIR__ . '/up-to-php82.php'); + $containerConfigurator->import(__DIR__ . '/php83.php'); +}; diff --git a/docs/README.md b/docs/README.md index 08ade0d8..c5003226 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,17 +4,23 @@ **PHP CompatInfo** is a library that can find the minimum version and the extensions required for a piece of code to run. -Running on PHP greater or equal than 8.0 for parsing source code in a format PHP 5.2 to PHP 8.2 +Running on PHP greater or equal than 8.0 for parsing source code in a format PHP 5.2 to PHP 8.3 ![Graph Composer](./assets/images/graph-composer.svg) ## Features -- Parse source code in format PHP 5.2 to PHP 8.2 +- Parse source code in format PHP 5.2 to PHP 8.3 - Detect PHP features for each Major/minor versions - Detect versions of all directives, constants, functions, classes, interfaces of 100 extensions and more - Display/Inspect list of extensions, and their versions supported +## Usage + +> Learn more about different usages with console, Docker and programmatically. + +See [Getting-Started's Guide](usage/README.md) to know how to use it. + ## Installation > Learn how to install `CompatInfo` application in different way. diff --git a/docs/components/README.md b/docs/components/README.md index 6864fbe8..0ee24a7d 100644 --- a/docs/components/README.md +++ b/docs/components/README.md @@ -1,35 +1,35 @@ -## Components +# Components - Parsing PHP 5, PHP 7 or PHP 8 code into an abstract syntax tree (AST) is provided by the [PHP-Parser](https://github.com/nikic/PHP-Parser) library. - Contextual elements and minimum PHP versions detection provided by following node visitors. -### PHP-Parser [Node Visitors](./parser/README.md) +## PHP-Parser [Node Visitors](./parser/README.md) - Parent references with the `ParentContextVisitor` - Name Resolution with the `NameResolverVisitor` - Version Resolution with the `VersionResolverVisitor` -### [Profiler](./profiler/README.md) +## [Profiler](./profiler/README.md) - Data Collector(s) with common `DataCollector` and specialized `VersionDataCollector` classes - Data Collector(s) contract with the `CollectorInterface` - Collector Handler for both Profile and Profiler with `CollectorTrait` - Profile information for a single data source with `Profile` -### [Sniffs](./sniffs/README.md) +## [Sniffs](./sniffs/README.md) -They are grouped by categories to solve PHP features (from 4.0 to 8.2) +They are grouped by categories to solve PHP features (from 4.0 to 8.3) - Arrays (3) -- Attributes (3) +- Attributes (4) - Classes (11) -- Constants (4) +- Constants (6) - ControlStructures (4) - Enumerations (1) -- Expressions (3) +- Expressions (4) - Fibers (1) - FunctionCalls (1) - FunctionDeclarations (7) @@ -40,7 +40,7 @@ They are grouped by categories to solve PHP features (from 4.0 to 8.2) - TextProcessing (2) - UseDeclarations (2) -### [Extensions](./extensions/README.md) +## [Extensions](./extensions/README.md) PHPCompatInfo can be extended by registering objects that implement one or more of the following interfaces: @@ -59,7 +59,7 @@ PHPCompatInfo can be extended by registering objects that implement one or more Furthermore, extensions may implement the `Symfony\Component\EventDispatcher\EventSubscriberInterface` in order to have its event handlers automatically registered with the EventDispatcher when the extension is loaded. -### [Polyfills](./polyfills/README.md) +## [Polyfills](./polyfills/README.md) They are identified by services that implements the `Bartlett\CompatInfo\Application\Polyfills\PolyfillInterface`. diff --git a/docs/components/sniffs/README.md b/docs/components/sniffs/README.md index b9008e67..9a404b35 100644 --- a/docs/components/sniffs/README.md +++ b/docs/components/sniffs/README.md @@ -248,6 +248,20 @@ Here is the list of features supported and their corresponding sniffs : [AllowNullFalseTrueAsStandaloneTypes]: https://wiki.php.net/rfc/true-type [DeprecateDollarBraceStringInterpolation]: https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation +## [PHP 8.3](https://www.php.net/manual/en/migration83.php) + +| Sniff category | Sniff class name | PHP Feature | +|----------------|--------------------------------|------------------------------------------------------------------| +| Attributes | OverrideAttributeSniff | [Override attribute][OverrideAttribute] | +| Constants | DynamicClassConstantFetchSniff | [Dynamic Class Constant fetch syntax][DynamicClassConstantFetch] | +| Constants | TypedClassConstantSniff | [Typed Class Constants][TypedClassConstant] | +| Expressions | StaticVarInitializerSniff | [Static Variable initializers][StaticVarInitializer] | + +[DynamicClassConstantFetch]: https://www.php.net/releases/8.3/en.php#dynamic_class_constant_fetch +[OverrideAttribute]: https://www.php.net/releases/8.3/en.php#override_attribute +[StaticVarInitializer]: https://www.php.net/manual/en/migration83.new-features.php#migration83.new-features.core.static-variable-initializers +[TypedClassConstant]: https://www.php.net/releases/8.3/en.php#typed_class_constants + ## Special cases * **Namespaces** declaration have no sniff, because its detected by the `VersionResolverVisitor` diff --git a/docs/exclusions/README.md b/docs/exclusions/README.md index b48eb229..9f7904c5 100644 --- a/docs/exclusions/README.md +++ b/docs/exclusions/README.md @@ -40,7 +40,7 @@ try { $dump = reset($data); var_export($dump); } catch (HandlerFailedException $e) { - foreach ($e->getNestedExceptions() as $ex) { + foreach ($e->getWrappedExceptions() as $ex) { printf('Exception -- %s >> %s%s' . $ex->getMessage(), $ex->getTraceAsString(), PHP_EOL); }; } diff --git a/docs/usage/README.md b/docs/usage/README.md new file mode 100644 index 00000000..9ce2db09 --- /dev/null +++ b/docs/usage/README.md @@ -0,0 +1,5 @@ +# Usage + +1. [Console CLI](console.md) +2. [Docker CLI](docker.md) +3. [Programmatically](programmatically.md) diff --git a/docs/usage/console.md b/docs/usage/console.md new file mode 100644 index 00000000..7a635d4a --- /dev/null +++ b/docs/usage/console.md @@ -0,0 +1,44 @@ + +# Console CLI + +> **WARNING** Depending on your PHP version, with 8.0 you won't have usage of the most up-to-date Database version of `CompatInfoDB` +> +> With PHP 8.1 or greater `CompatInfoDB` 6.1+ is supported, +> adding the `db:new` command that combine `db:create` and `db:init` actions. + +```text +phpCompatInfo version 7.1.0 DB version 5.14.1 + +Usage: + command [options] [arguments] + +Options: + -h, --help Display help for the given command. When no command is given display help for the list command + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi|--no-ansi Force (or disable --no-ansi) ANSI output + -n, --no-interaction Do not ask any interactive question + --profile Display timing and memory usage information + --progress Show progress bar + --output=OUTPUT Affect output to produce results in different format [default: ["console"]] (multiple values allowed) + --debug Display debugging information + -c, --config=CONFIG Read configuration from PHP file + --php=PHP PHP feature version (format Major.Minor) + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + about Shows short information about this package + completion Dump the shell completion script + diagnose Diagnoses the system to identify common errors + help Display help for a command + list List commands + analyser + analyser:run Analyse a data source to find out requirements + db + db:create Create the database schema and load its contents from JSON files + db:init Load JSON file(s) into database + db:list List all references supported in the Database + db:show Show details of a reference supported in the Database + rule + rule:list Display list of Compatibility Analyser rules supported +``` diff --git a/docs/usage/docker.md b/docs/usage/docker.md new file mode 100644 index 00000000..5b47ecec --- /dev/null +++ b/docs/usage/docker.md @@ -0,0 +1,20 @@ + +# Docker CLI + +**IMPORTANT** : Docker image with `latest` tag use the PHP 8.1 runtime ! + +> Please mount your system temporary folder to `/home/appuser/.cache/bartlett` in the container. +> +> **NOTE**: On most Linux distribution, it should be `/tmp` +> +> Don't forget to also mount your source code to `/app` folder (i.e) in the container and make it as the current working directory + +```shell +docker run --rm -it -u "$(id -u):$(id -g)" \ + -v /tmp:/home/appuser/.cache/bartlett \ + -v ${PWD}:/app \ + -w /app \ + ghcr.io/llaville/php-compatinfo:latest +``` + +Then you can run any commands supported by application : `db:*`, `diagnose`, `about`, `analyser:run`, `rule:list` diff --git a/docs/usage/programmatically.md b/docs/usage/programmatically.md new file mode 100644 index 00000000..f1fe405d --- /dev/null +++ b/docs/usage/programmatically.md @@ -0,0 +1,4 @@ + +# Programmatically + +Repository contains an `examples` directory that have a good first to follow. diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..532a8b6a --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +[ "$APP_DEBUG" == 'true' ] && set -x +set -e + +if [ "$APP_DEBUG" == 'true' ] +then + echo "> You will act as user: $(id -u -n)" + echo "$(composer config --global --list)" + /bin/sh -c "ls -l $(composer config --global home)" +fi + +php -d memory_limit=512M "$(composer config --global home)/vendor/bin/phpcompatinfo" $@ diff --git a/examples/api_analyser_run.php b/examples/api_analyser_run.php index 1037d414..e337ee22 100644 --- a/examples/api_analyser_run.php +++ b/examples/api_analyser_run.php @@ -42,7 +42,7 @@ $dump = reset($data); var_export($dump); } catch (HandlerFailedException $e) { - foreach ($e->getNestedExceptions() as $ex) { + foreach ($e->getWrappedExceptions() as $ex) { printf('Exception -- %s%s%s%s', $ex->getMessage(), PHP_EOL, $ex->getTraceAsString(), PHP_EOL); }; } diff --git a/mkdocs.yml b/mkdocs.yml index 085737ba..577d9c90 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,20 +29,8 @@ markdown_extensions: nav: - "Home": README.md - "Getting-Started": installation.md - - "Components": - - "PHP Parser": - - "Visitors": components/parser/README.md - - "Profiler": - - "Data Collectors": components/profiler/README.md - - "Sniffs": components/sniffs/README.md - - "Extensions": components/extensions/README.md - - "Output format": components/extensions/reporter.md - - "Polyfills": components/polyfills/README.md + - "Components": components/README.md - "Configurations": configs/README.md - - "Conditional Code": - - "Introduction": conditional-code/introduction.md - - "Indirect Call": conditional-code/indirect-call.md - - "Multiple Signature": conditional-code/multiple-signature.md - - "Some Limitation": conditional-code/limitation.md + - "Conditional Code": conditional-code/README.md - "Exclusions": exclusions/README.md - "Architecture": architecture/README.md diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c0bfa0cd..3ab75039 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ $context - * @return null|array + * @return float|array|ArrayObject|bool|int|string|null */ - public function normalize($object, $format = null, array $context = []) + public function normalize($object, $format = null, array $context = []): float|array|ArrayObject|bool|int|string|null { $node = $object; $this->attributeNamespacedName = $context['nodeAttributeNamespacedName'] ?? 'bartlett.name'; @@ -74,8 +76,9 @@ public function normalize($object, $format = null, array $context = []) /** * {@inheritDoc} + * @phpstan-ignore-next-line */ - public function supportsNormalization($data, $format = null) + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { if ($data instanceof Node\Expr\New_) { $this->name = $data->class instanceof Node\Stmt\Class_ ? 'class' : (string) $data->class; @@ -155,6 +158,15 @@ public function supportsNormalization($data, $format = null) return true; } + /** + * @link https://symfony.com/blog/new-in-symfony-6-3-performance-improvements#improved-performance-of-serializer-normalizers-denormalizers + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return []; + } + private function getType(Node $node): string { if ($this->isConstantDefineExpression($node)) { diff --git a/src/Application/Sniffs/Attributes/OverrideAttributeSniff.php b/src/Application/Sniffs/Attributes/OverrideAttributeSniff.php new file mode 100644 index 00000000..9ab83c0d --- /dev/null +++ b/src/Application/Sniffs/Attributes/OverrideAttributeSniff.php @@ -0,0 +1,69 @@ + [ + 'name' => $this->getShortClass(), + 'fullDescription' => "Override attribute is available since PHP 8.3.0", + 'helpUri' => '%baseHelpUri%/01_Components/03_Sniffs/Features/#php-83', + ]; + } + + /** + * @inheritDoc + */ + public function enterNode(Node $node) + { + if (!$node instanceof Node\AttributeGroup) { + return null; + } + $found = false; + foreach ($node->attrs as $attr) { + if ($attr->name->toString() == 'Override') { + $found = true; + break; + } + } + if (!$found) { + return null; + } + + if (!$parent = $node->getAttribute($this->attributeParentKeyStore)) { + return null; + } + $this->updateNodeElementVersion($parent, $this->attributeKeyStore, ['php.all' => '8.3.0alpha3']); + $this->updateNodeElementRule($parent, $this->attributeKeyStore, self::CA83); + return null; + } +} diff --git a/src/Application/Sniffs/Constants/DynamicClassConstantFetchSniff.php b/src/Application/Sniffs/Constants/DynamicClassConstantFetchSniff.php new file mode 100644 index 00000000..833092ce --- /dev/null +++ b/src/Application/Sniffs/Constants/DynamicClassConstantFetchSniff.php @@ -0,0 +1,61 @@ + [ + 'name' => $this->getShortClass(), + 'fullDescription' => "Dynamic class constant fetch syntax is available since PHP 8.3.0", + 'helpUri' => '%baseHelpUri%/01_Components/03_Sniffs/Features/#php-83', + ]; + } + + /** + * @inheritDoc + */ + public function enterNode(Node $node) + { + if (!$node instanceof Node\Expr\ClassConstFetch) { + return null; + } + + if ($node->name instanceof Node\Expr\Variable) { + if (!$parent = $node->getAttribute($this->attributeParentKeyStore)) { + return null; + } + $this->updateNodeElementVersion($parent, $this->attributeKeyStore, ['php.min' => '8.3.0alpha1']); + $this->updateNodeElementRule($parent, $this->attributeKeyStore, self::CA83); + } + return null; + } +} diff --git a/src/Application/Sniffs/Constants/TypedClassConstantSniff.php b/src/Application/Sniffs/Constants/TypedClassConstantSniff.php new file mode 100644 index 00000000..2fbe5351 --- /dev/null +++ b/src/Application/Sniffs/Constants/TypedClassConstantSniff.php @@ -0,0 +1,56 @@ +type instanceof Node\Identifier) { + $this->updateNodeElementVersion($node, $this->attributeKeyStore, ['php.min' => '8.3.0alpha1']); + $this->updateNodeElementRule($node, $this->attributeKeyStore, self::CA83); + } + return null; + } + + /** + * @inheritDoc + */ + public function getRules(): Generator + { + yield self::CA83 => [ + 'name' => $this->getShortClass(), + 'fullDescription' => "Type hinting class constants has been introduced in PHP 8.3.0", + 'helpUri' => '%baseHelpUri%/01_Components/03_Sniffs/Features/#php-83', + ]; + } +} diff --git a/src/Application/Sniffs/Expressions/StaticVarInitializerSniff.php b/src/Application/Sniffs/Expressions/StaticVarInitializerSniff.php new file mode 100644 index 00000000..6403ef46 --- /dev/null +++ b/src/Application/Sniffs/Expressions/StaticVarInitializerSniff.php @@ -0,0 +1,59 @@ + [ + 'name' => $this->getShortClass(), + 'fullDescription' => "Static variable initializers syntax is available since PHP 8.3.0", + 'helpUri' => '%baseHelpUri%/01_Components/03_Sniffs/Features/#php-83', + ]; + } + + /** + * @inheritDoc + */ + public function enterNode(Node $node) + { + if (!$node instanceof Node\Stmt\StaticVar) { + return null; + } + + if ($node->default instanceof Node\Expr\CallLike && !$node->default instanceof Node\Expr\New_) { + if (!$parent = $node->getAttribute($this->attributeParentKeyStore)) { + return null; + } + $this->updateNodeElementVersion($parent, $this->attributeKeyStore, ['php.min' => '8.3.0alpha1']); + $this->updateNodeElementRule($parent, $this->attributeKeyStore, self::CA83); + } + return null; + } +} diff --git a/src/Presentation/Console/Application.php b/src/Presentation/Console/Application.php index fa94aa0b..75942291 100644 --- a/src/Presentation/Console/Application.php +++ b/src/Presentation/Console/Application.php @@ -7,7 +7,6 @@ */ namespace Bartlett\CompatInfo\Presentation\Console; -use Bartlett\CompatInfo\Presentation\Console\Command\AbstractCommand; use Bartlett\CompatInfoDb\Infrastructure\Framework\Composer\InstalledVersions; use Symfony\Component\Console\Application as SymfonyApplication; @@ -18,7 +17,7 @@ use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Phar; @@ -32,7 +31,12 @@ */ class Application extends SymfonyApplication implements ApplicationInterface { - use ContainerAwareTrait; + protected ?ContainerInterface $container; + + public function setContainer(ContainerInterface $container = null): void + { + $this->container = $container; + } /** * @var string diff --git a/src/Presentation/Console/ApplicationInterface.php b/src/Presentation/Console/ApplicationInterface.php index ec986b42..65283534 100644 --- a/src/Presentation/Console/ApplicationInterface.php +++ b/src/Presentation/Console/ApplicationInterface.php @@ -8,7 +8,7 @@ namespace Bartlett\CompatInfo\Presentation\Console; use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Console Application contract. @@ -16,10 +16,12 @@ * @since Release 6.0.0 * @author Laurent Laville */ -interface ApplicationInterface extends ContainerAwareInterface +interface ApplicationInterface { public const NAME = 'phpCompatInfo'; + public function setContainer(ContainerInterface $container = null): void; + /** * @return void */ @@ -27,10 +29,8 @@ public function setCommandLoader(CommandLoaderInterface $commandLoader); /** * Gets the name of the application. - * - * @return string */ - public function getName(); + public function getName(): string; public function getInstalledVersion(bool $withRef = true, string $packageName = 'bartlett/php-compatinfo'): ?string; diff --git a/src/Presentation/Console/Command/AboutCommand.php b/src/Presentation/Console/Command/AboutCommand.php index 560d556d..1394b25b 100644 --- a/src/Presentation/Console/Command/AboutCommand.php +++ b/src/Presentation/Console/Command/AboutCommand.php @@ -45,7 +45,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var ApplicationInterface $app */ $app = $this->getApplication(); - $defaultVersion = '7.0'; + $defaultVersion = '7.1'; $lines = [ sprintf( diff --git a/src/Presentation/Console/Command/AnalyserCommand.php b/src/Presentation/Console/Command/AnalyserCommand.php index 50eae514..e9d25850 100644 --- a/src/Presentation/Console/Command/AnalyserCommand.php +++ b/src/Presentation/Console/Command/AnalyserCommand.php @@ -18,6 +18,8 @@ use Symfony\Component\Messenger\Exception\HandlerFailedException; use function sprintf; +use function version_compare; +use const PHP_VERSION; /** * Analyse a data source to find out requirements. @@ -74,7 +76,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->queryBus->query($compatibilityQuery); } catch (HandlerFailedException $e) { $exceptions = []; - foreach ($e->getNestedExceptions() as $exception) { + if (version_compare(PHP_VERSION, '8.1', 'ge')) { + $failures = $e->getWrappedExceptions(); // @phpstan-ignore-line + } else { + // still keep compatibility with Symfony Messenger Component 6.0 + $failures = $e->getNestedExceptions(); + } + foreach ($failures as $exception) { $exceptions[] = $exception->getMessage() . sprintf(' from file "%s" at line %d', $exception->getFile(), $exception->getLine()); } diff --git a/tests/Sniffs/DynamicClassConstantFetchSniffTest.php b/tests/Sniffs/DynamicClassConstantFetchSniffTest.php new file mode 100644 index 00000000..ff3821a0 --- /dev/null +++ b/tests/Sniffs/DynamicClassConstantFetchSniffTest.php @@ -0,0 +1,57 @@ +executeAnalysis($dataSource); + $versions = $metrics[self::$analyserId]['versions']; + + $this->assertEquals( + '8.3.0alpha1', + $versions['php.min'] + ); + + $this->assertEquals( + '', + $versions['php.max'] + ); + } +} diff --git a/tests/Sniffs/OverrideAttributeSniffTest.php b/tests/Sniffs/OverrideAttributeSniffTest.php new file mode 100644 index 00000000..0c1ec93e --- /dev/null +++ b/tests/Sniffs/OverrideAttributeSniffTest.php @@ -0,0 +1,56 @@ +executeAnalysis($dataSource); + $traits = $metrics[self::$analyserId]['traits']; + + $this->assertEquals( + '8.3.0alpha3', // due to native override attribute + $traits['T']['php.all'] + ); + $this->assertEquals( + '7.1.0', // because attribute act as a comment + $traits['T']['php.min'] + ); + } +} diff --git a/tests/Sniffs/StaticVarInitializerSniffTest.php b/tests/Sniffs/StaticVarInitializerSniffTest.php new file mode 100644 index 00000000..f95051d4 --- /dev/null +++ b/tests/Sniffs/StaticVarInitializerSniffTest.php @@ -0,0 +1,50 @@ +executeAnalysis($dataSource); + $functions = $metrics[self::$analyserId]['functions']; + + $this->assertEquals( + '8.3.0alpha1', + $functions['foo']['php.min'] + ); + } +} diff --git a/tests/Sniffs/TypedClassConstantSniffTest.php b/tests/Sniffs/TypedClassConstantSniffTest.php new file mode 100644 index 00000000..696c7ec3 --- /dev/null +++ b/tests/Sniffs/TypedClassConstantSniffTest.php @@ -0,0 +1,83 @@ + [ + 'Test\FOO' => [ + 'php.min' => '8.3.0alpha1', + ], + 'Test\GARPLY' => [ + 'php.min' => '8.3.0alpha1', + ], + 'Test\WALDO' => [ + 'php.min' => '8.3.0alpha1', + ], + ], + ]; + + foreach ($provides as $filename => $versions) { + yield [$filename, $versions]; + } + } + + /** + * Feature test for typed class constants + * + * @link https://github.com/llaville/php-compatinfo/issues/366 + * Typed class constants are detected as PHP 8.3 + * @link https://github.com/php/php-src/commit/414f71a90254cc33896bb3ba953f979f743c198c + * @group features + * @dataProvider typedConstantsProvider + * @return void + * @throws Exception + */ + public function testTypedClassConstants(string $dataSource, array $expectedVersions) + { + $metrics = $this->executeAnalysis($dataSource); + $constants = $metrics[self::$analyserId]['constants']; + + foreach ($expectedVersions as $name => $versions) { + $this->assertArrayHasKey($name, $constants); + foreach ($versions as $key => $value) { + $this->assertEquals($value, $constants[$name][$key]); + } + } + } +} diff --git a/tests/fixtures/sniffs/attributes/override.php b/tests/fixtures/sniffs/attributes/override.php new file mode 100644 index 00000000..ac138825 --- /dev/null +++ b/tests/fixtures/sniffs/attributes/override.php @@ -0,0 +1,13 @@ +