diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5a98fda..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 2 -updates: -- package-ecosystem: composer - directory: "/" - schedule: - interval: daily - time: "04:00" - open-pull-requests-limit: 10 diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 3608a6d..b411eca 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -10,13 +10,16 @@ jobs: phpstan: name: phpstan runs-on: ubuntu-latest + strategy: + matrix: + php: [ "7.4", "8.1" ] steps: - uses: actions/checkout@v2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pspell, intl, iconv coverage: none diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a13f6c6..225c84b 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ["7.4", "8.0"] + php: ["7.4", "8.0", "8.1"] stability: [--prefer-lowest, --prefer-stable] env: PHP_VERSION: ${{ matrix.php }} @@ -46,7 +46,7 @@ jobs: - name: Run tests run: | - export WITH_COVERAGE=$(if [[ ("${{ matrix.php }}" = "8.0") && ("${{ matrix.stability }}" = "--prefer-stable") ]]; then echo "true"; else echo "false"; fi) + export WITH_COVERAGE=$(if [[ ("${{ matrix.php }}" = "8.1") && ("${{ matrix.stability }}" = "--prefer-stable") ]]; then echo "true"; else echo "false"; fi) echo "WITH_COVERAGE=${WITH_COVERAGE}" >> $GITHUB_ENV make vendor make tests diff --git a/Makefile b/Makefile index ae03b48..7946277 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ DOCKER_COMPOSE ?= docker-compose EXEC_PHP = $(DOCKER_COMPOSE) run --rm -T php -PHP_VERSION ?= 8.0 +PHP_VERSION ?= 8.1 DEPS_STRATEGY ?= --prefer-stable COMPOSER = $(EXEC_PHP) composer WITH_COVERAGE ?= "FALSE" @@ -63,11 +63,17 @@ phpcs: PHP_VERSION=7.4 $(PHP_CS_FIXER) --dry-run phpcbf: - $(PHP_CS_FIXER) + PHP_VERSION=7.4 $(PHP_CS_FIXER) phpstan: vendor $(EXEC_PHP) vendor/bin/phpstan analyse src -c phpstan.neon -a vendor/autoload.php +phpstan: vendor + $(EXEC_PHP) vendor/bin/phpstan analyse src -c phpstan.neon -a vendor/autoload.php + +phpstan-baseline: vendor + $(EXEC_PHP) vendor/bin/phpstan analyse src -c phpstan.neon -a vendor/autoload.php --generate-baseline + infection: vendor $(EXEC_PHP) vendor/bin/phpunit --coverage-xml=build/coverage/coverage-xml --log-junit=build/coverage/phpunit.junit.xml $(EXEC_PHP) php infection.phar --threads=4 --coverage=build/coverage --min-covered-msi=74 diff --git a/composer.json b/composer.json index d6698b4..32608d1 100644 --- a/composer.json +++ b/composer.json @@ -21,11 +21,11 @@ } ], "require": { - "php": "^7.4|^8.0", + "php": "^7.4 | ^8.0", "nyholm/psr7": "^1.3", "psr/http-client": "^1.0", - "symfony/process": "^3.3|^4.0|^5.0", - "thecodingmachine/safe": "^1.0", + "symfony/process": "^4.4.30 | ^5.0 |^6.0", + "thecodingmachine/safe": "^1.0 | ^2.0", "webmozart/assert": "^1.3" }, "require-dev": { @@ -39,13 +39,13 @@ "phpstan/phpstan-phpunit": "^1.0.0", "phpunit/phpunit": "^9.5", "pixelrobin/php-feather": "^1.0", - "symfony/filesystem": "^4.2 || ^5.0", - "symfony/finder": "^4.2 || ^5.0", - "symfony/http-client": "^5.0", - "thecodingmachine/phpstan-safe-rule": "v1.1.0" + "symfony/filesystem": "^4.4 || ^5.0 || ^6.0", + "symfony/finder": "^4.4 || ^5.0 || ^6.0", + "symfony/http-client": "^5.0 || ^6.0", + "thecodingmachine/phpstan-safe-rule": "^1.1" }, "suggest": { - "symfony/http-client": "A PSR-18 Client implementation to use spellcheckers relying on http api endpoints" + "symfony/http-client": "A PSR-18 Client implementation to use spellcheckers that relies on HTTP APIs" }, "autoload": { "psr-4": { diff --git a/docker-compose.yml b/docker-compose.yml index 95ee4b8..8b9e245 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,11 +2,11 @@ version: '3.4' services: php: - image: tigitz/phpspellchecker:${PHP_VERSION:-8.0} + image: tigitz/phpspellchecker:${PHP_VERSION:-8.1} build: context: docker/php args: - PHP_VERSION: ${PHP_VERSION:-8.0} + PHP_VERSION: ${PHP_VERSION:-8.1} volumes: - .:/usr/src/myapp - ./cache:/root/composer/cache diff --git a/phpstan.neon b/phpstan.neon index bd8b41f..96eb35f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,19 +6,68 @@ includes: parameters: level: max - reportUnmatchedIgnoredErrors: true + + # Some errors ignored are specific to a php version so they're reported as unmatched when phpstan is ran with a + # different php version + reportUnmatchedIgnoredErrors: false + treatPhpDocTypesAsCertain: false ignoreErrors: - # yeah OK Process is technically and iterable type... + # Missing strict comparison + - '#^Construct empty\(\) is not allowed. Use more strict comparison.$#' + + # thecodingmachine/safe and thecodingmachine/phpstan-safe-rule don't support 8.1 Pspell - - message: "#no value type specified in iterable type Symfony\\\\Component\\\\Process\\\\Process#" - path: %currentWorkingDirectory% + message: "#^Function pspell_config_create is unsafe to use\\. It can return FALSE instead of throwing an exception\\. Please add 'use function Safe\\\\pspell_config_create;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php - # It's checked by Assert:count(1) - - message: "#^Parameter \\#1 \\$language of function Safe\\\\pspell_config_create expects string, string\\|false given\\.$#" + message: "#^Function pspell_config_ignore is unsafe to use\\. It can return FALSE instead of throwing an exception\\. Please add 'use function Safe\\\\pspell_config_ignore;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library\\.$#" count: 1 path: src/Spellchecker/PHPPspell.php - # Missing strict comparison - - '#^Construct empty\(\) is not allowed. Use more strict comparison.$#' + - + message: "#^Function pspell_config_mode is unsafe to use\\. It can return FALSE instead of throwing an exception\\. Please add 'use function Safe\\\\pspell_config_mode;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Function pspell_new_config is unsafe to use\\. It can return FALSE instead of throwing an exception\\. Please add 'use function Safe\\\\pspell_new_config;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Parameter \\#1 \\$dictionary of function pspell_check expects int, int\\|false given\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Parameter \\#1 \\$dictionary of function pspell_suggest expects int, int\\|false given\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Parameter \\#1 \\$conf of function pspell_config_ignore expects int, int\\|false given\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Parameter \\#1 \\$conf of function pspell_config_mode expects int, int\\|false given\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Parameter \\#1 \\$config of function pspell_new_config expects int, int\\|false given\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Parameter \\#1 \\$pspell of function pspell_check expects int, int\\|false given\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php + + - + message: "#^Parameter \\#1 \\$pspell of function pspell_suggest expects int, int\\|false given\\.$#" + count: 1 + path: src/Spellchecker/PHPPspell.php diff --git a/src/Spellchecker/PHPPspell.php b/src/Spellchecker/PHPPspell.php index 9c70f74..e72eb5f 100644 --- a/src/Spellchecker/PHPPspell.php +++ b/src/Spellchecker/PHPPspell.php @@ -6,6 +6,7 @@ use PhpSpellcheck\Exception\RuntimeException; use PhpSpellcheck\Misspelling; +use PhpSpellcheck\MisspellingInterface; use Webmozart\Assert\Assert; class PHPPspell implements SpellcheckerInterface @@ -29,9 +30,9 @@ class PHPPspell implements SpellcheckerInterface * @see http://php.net/manual/en/function.pspell-config-mode.php * @see http://php.net/manual/en/function.pspell-config-ignore.php * - * @param int $mode the mode parameter is the mode in which the spellchecker will work + * @param int|null $mode the mode parameter is the mode in which the spellchecker will work * @param int $numberOfCharactersLowerLimit Words less than n characters will be skipped - * @param Aspell|null $aspell Aspell spellchecker that pspell extension is using underneath. Used to help retrieving supported languages + * @param Aspell|null $aspell Aspell spellchecker that pspell extension is using underneath. Used to help retrieve supported languages */ public function __construct( ?int $mode = null, @@ -60,9 +61,36 @@ public function check( string $text, array $languages, array $context + ): iterable { + if (PHP_VERSION_ID < 80100) { + return $this->checkBefore81($text, $languages, $context); + } + + return $this->checkAfter81($text, $languages, $context); + } + + /** + * {@inheritdoc} + */ + public function getSupportedLanguages(): iterable + { + return $this->aspell->getSupportedLanguages(); + } + + /** + * @param array $context + * @param array $languages + * + * @return iterable + */ + private function checkBefore81( + string $text, + array $languages, + array $context ): iterable { Assert::count($languages, 1, 'PHPPspell spellchecker doesn\'t support multi-language check'); + $chosenLanguage = current($languages); $pspellConfig = \Safe\pspell_config_create(current($languages)); \Safe\pspell_config_mode($pspellConfig, $this->mode); \Safe\pspell_config_ignore($pspellConfig, $this->numberOfCharactersLowerLimit); @@ -73,13 +101,12 @@ public function check( /** @var string $line */ foreach ($lines as $lineNumber => $line) { $words = explode(' ', \Safe\preg_replace("/(?!['’-])(\p{P}|\+|--)/u", '', $line)); - foreach ($words as $key => $word) { + foreach ($words as $word) { if (!pspell_check($dictionary, $word)) { $suggestions = pspell_suggest($dictionary, $word); - Assert::isArray( $suggestions, - \Safe\sprintf('pspell_suggest method failed with dictionary "%s" and word "%s"', $dictionary, $word) + \Safe\sprintf('pspell_suggest method failed with language "%s" and word "%s"', $chosenLanguage, $word) ); yield new Misspelling($word, 0, $lineNumber + 1, $suggestions, $context); @@ -89,10 +116,40 @@ public function check( } /** - * {@inheritdoc} + * @param array $context + * @param array $languages + * + * @return iterable */ - public function getSupportedLanguages(): iterable - { - return $this->aspell->getSupportedLanguages(); + private function checkAfter81( + string $text, + array $languages, + array $context + ): iterable { + Assert::count($languages, 1, 'PHPPspell spellchecker doesn\'t support multi-language check'); + + $chosenLanguage = current($languages); + $pspellConfig = pspell_config_create($chosenLanguage); + pspell_config_mode($pspellConfig, $this->mode); + pspell_config_ignore($pspellConfig, $this->numberOfCharactersLowerLimit); + $dictionary = pspell_new_config($pspellConfig); + + $lines = explode(PHP_EOL, $text); + + /** @var string $line */ + foreach ($lines as $lineNumber => $line) { + $words = explode(' ', \Safe\preg_replace("/(?!['’-])(\p{P}|\+|--)/u", '', $line)); + foreach ($words as $word) { + if (!pspell_check($dictionary, $word)) { + $suggestions = pspell_suggest($dictionary, $word); + Assert::isArray( + $suggestions, + \Safe\sprintf('pspell_suggest method failed with language "%s" and word "%s"', $chosenLanguage, $word) + ); + + yield new Misspelling($word, 0, $lineNumber + 1, $suggestions, $context); + } + } + } } } diff --git a/src/Utils/ProcessRunner.php b/src/Utils/ProcessRunner.php index 24e3cf2..9161367 100644 --- a/src/Utils/ProcessRunner.php +++ b/src/Utils/ProcessRunner.php @@ -16,13 +16,6 @@ class ProcessRunner */ public static function run(Process $process, $timeout = null, callable $callback = null, array $env = []): Process { - if (method_exists($process, 'inheritEnvironmentVariables')) { - // Symfony 3.2+ - $process->inheritEnvironmentVariables(true); - } else { - // Symfony < 3.2 - $process->setEnv(['LANG' => getenv('LANG')]); - } $process->setTimeout($timeout); try {