Repository: Roave/no-floaters Branch: 1.16.x Commit: f29123d0fb07 Files: 41 Total size: 40.2 KB Directory structure: gitextract_9qceq59d/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── continuous-integration.yml │ └── release-on-milestone-closed.yml ├── .gitignore ├── .laminas-ci.json ├── LICENSE ├── README.md ├── SECURITY.md ├── composer-require-checker.json ├── composer.json ├── infection.json.dist ├── logo/ │ └── roave-no-floaters.blend ├── phpcs.xml.dist ├── phpstan.neon.dist ├── phpunit.xml.dist ├── psalm.xml ├── renovate.json ├── rules.neon ├── src/ │ ├── DisallowFloatAssignedToVariableRule.php │ ├── DisallowFloatEverywhereRule.php │ ├── DisallowFloatInFunctionSignatureRule.php │ ├── DisallowFloatInMethodSignatureRule.php │ ├── DisallowFloatPropertyTypeRule.php │ └── FloatTypeHelper.php └── tests/ ├── asset/ │ ├── assign.php │ ├── expr.php │ ├── function.php │ ├── functionNotAutoloaded.php │ ├── functionWithInterpolatedFloatAndNonFloatParameters.php │ ├── functionWithoutNamespace.php │ ├── method.php │ ├── methodWithConditionalReturnType.php │ ├── methodWithInterpolatedFloatAndNotFloatParameters.php │ └── property.php └── src/ ├── DisallowFloatAssignedToVariableRuleTest.php ├── DisallowFloatEverywhereRuleTest.php ├── DisallowFloatInFunctionSignatureRuleTest.php ├── DisallowFloatInMethodSignatureRuleTest.php ├── DisallowFloatPropertyTypeRuleTest.php └── ScopeWithNodeCallbackInvoker.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ logo export-ignore tests export-ignore .gitignore export-ignore .gitattributes export-ignore .travis.yml export-ignore composer-require-checker.json export-ignore export-ignore export-ignore phpcs.xml.dist export-ignore phpstan.neon.dist export-ignore phpunit.xml.dist export-ignore ================================================ FILE: .github/FUNDING.yml ================================================ tidelift: "packagist/roave/no-floaters" ================================================ FILE: .github/workflows/continuous-integration.yml ================================================ # See https://github.com/laminas/laminas-continuous-integration-action # Generates a job matrix based on current dependencies and supported version # ranges, then runs all those jobs name: "Continuous Integration" on: pull_request: push: jobs: matrix: name: Generate job matrix runs-on: ubuntu-latest outputs: matrix: ${{ steps.matrix.outputs.matrix }} steps: - name: Gather CI configuration id: matrix uses: laminas/laminas-ci-matrix-action@1.33.0 qa: name: QA Checks needs: [ matrix ] runs-on: ${{ matrix.operatingSystem }} strategy: fail-fast: false matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }} steps: - name: ${{ matrix.name }} uses: laminas/laminas-continuous-integration-action@1.43.0 env: "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} "INFECTION_DASHBOARD_API_KEY": ${{ secrets.INFECTION_DASHBOARD_API_KEY }} "STRYKER_DASHBOARD_API_KEY": ${{ secrets.STRYKER_DASHBOARD_API_KEY }} with: job: ${{ matrix.job }} ================================================ FILE: .github/workflows/release-on-milestone-closed.yml ================================================ # https://help.github.com/en/categories/automating-your-workflow-with-github-actions name: "Automatic Releases" on: milestone: types: - "closed" jobs: release: name: "GIT tag, release & create merge-up PR" runs-on: ubuntu-latest steps: - name: "Checkout" uses: "actions/checkout@v6" - name: "Release" uses: "laminas/automatic-releases@v1" with: command-name: "laminas:automatic-releases:release" env: "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Create Merge-Up Pull Request" uses: "laminas/automatic-releases@v1" with: command-name: "laminas:automatic-releases:create-merge-up-pull-request" env: "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Create and/or Switch to new Release Branch" uses: "laminas/automatic-releases@v1" with: command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor" env: "GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Bump Changelog Version On Originating Release Branch" uses: "laminas/automatic-releases@v1" with: command-name: "laminas:automatic-releases:bump-changelog" env: "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Create new milestones" uses: "laminas/automatic-releases@v1" with: command-name: "laminas:automatic-releases:create-milestones" env: "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} ================================================ FILE: .gitignore ================================================ /vendor .phpunit.cache .phpunit.result.cache ================================================ FILE: .laminas-ci.json ================================================ { "extensions": [ "pcov" ], "exclude": [ {"name": "Infection"} ], "additional_checks": [ { "name": "Infection (PCOV)", "job": { "php": "@lowest", "dependencies": "locked", "command": "./vendor/bin/roave-infection-static-analysis-plugin" } } ] } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Roave, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # roave/no-floaters ![`roave/no-floaters`](logo/roave-no-floaters.png) [![Latest Stable Version](https://poser.pugx.org/roave/no-floaters/v/stable.png)](https://packagist.org/packages/roave/no-floaters) This library is a [PHPStan](https://github.com/phpstan/phpstan) plugin that disallows: * declaration of `float` properties * `float` method parameters * `float` method return types * assignment of `float` values to variables or properties The reason for this restriction is that rounding errors coming from floating point arithmetic operations are not acceptable in certain business logic scenario, such as dealing with money, evaluating exam results, rocket science, etc. An example of such problems can be seen with the following typical [example](https://3v4l.org/MJqJe): ```php var_dump((0.7 + 0.1) === 0.8); // output: bool(false) ``` This can mean no trouble at all, or a lot of trouble, depending on how many numbers you are running through your system, so it is advisable to avoid `float` for domains where rounding can potentially lead to trouble. `float` is still perfectly acceptable in many programming contexts, and this ruleset should only be applied where it is critical not to introduce rounding errors. ## Installation ```sh composer require --dev roave/no-floaters ``` ## Configuration In your `phpstan.neon` configuration, add following section: ```neon includes: - vendor/roave/no-floaters/rules.neon ``` Optionally, you can configure the library to disallow any `float`-producing expression at all, by adding following to your `phpstan.neon`: ```neon parameters: disallowFloatsEverywhere: true ``` If the above is enabled, given the following `example-file.php` contents: ```php for consulting/support. ================================================ FILE: SECURITY.md ================================================ ## Security contact information To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ================================================ FILE: composer-require-checker.json ================================================ { "symbol-whitelist": [ "null", "true", "false", "static", "self", "parent", "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", "PHPStan\\Analyser\\Scope", "PHPStan\\Node\\Printer\\Printer", "PHPStan\\Reflection\\FunctionReflection", "PHPStan\\Reflection\\MethodReflection", "PHPStan\\Reflection\\ParametersAcceptor", "PHPStan\\Reflection\\ReflectionProvider", "PHPStan\\Rules\\Rule", "PHPStan\\Rules\\RuleErrorBuilder", "PHPStan\\ShouldNotHappenException", "PHPStan\\Type\\FloatType", "PHPStan\\Type\\LateResolvableType", "PHPStan\\Type\\MixedType", "PHPStan\\Type\\NeverType", "PHPStan\\Type\\Type", "PHPStan\\Type\\VerbosityLevel" ], "php-core-extensions": [ "Core", "date", "pcre", "Reflection", "SPL", "standard", "mbstring" ] } ================================================ FILE: composer.json ================================================ { "name": "roave/no-floaters", "type": "phpstan-extension", "description": "PHPStan Rules to Disallow Float proliferation in contexts where IEEE-754 rounding errors are not acceptable", "license": [ "MIT" ], "require": { "php": "~8.3.0 || ~8.4.0 || ~8.5.0", "nikic/php-parser": "^5.7.0", "phpstan/phpstan": "^2.1.39" }, "require-dev": { "doctrine/coding-standard": "^14.0.0", "maglnet/composer-require-checker": "^4.20.0", "phpstan/phpstan-phpunit": "^2.0.16", "phpstan/phpstan-strict-rules": "^2.0.10", "phpunit/phpunit": "^12.5.11", "psalm/plugin-phpunit": "^0.19.5", "roave/infection-static-analysis-plugin": "^1.43.0", "squizlabs/php_codesniffer": "^4.0.1", "vimeo/psalm": "^6.15.1" }, "autoload": { "psr-4": { "Roave\\PHPStan\\Rules\\Floats\\": "src/" } }, "autoload-dev": { "classmap": [ "tests/asset" ], "psr-4": { "Roave\\PHPStanTest\\Rules\\Floats\\": "tests/src" } }, "config": { "allow-plugins": { "infection/extension-installer": false, "dealerdirect/phpcodesniffer-composer-installer": true }, "platform": { "php": "8.3.99" }, "sort-packages": true } } ================================================ FILE: infection.json.dist ================================================ { "$schema": "vendor/infection/infection/resources/schema.json", "source": { "directories": [ "src" ] }, "logs": { "text": "php://stderr", "github": true }, "mutators": { "@default": true }, "minMsi": 100, "minCoveredMsi": 100 } ================================================ FILE: phpcs.xml.dist ================================================ src tests/src ================================================ FILE: phpstan.neon.dist ================================================ includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - vendor/phpstan/phpstan-strict-rules/rules.neon - rules.neon parameters: paths: - %currentWorkingDirectory%/src - %currentWorkingDirectory%/tests/src level: 8 ================================================ FILE: phpunit.xml.dist ================================================ ./tests/src src ================================================ FILE: psalm.xml ================================================ ================================================ FILE: renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "local>Ocramius/.github:renovate-config" ] } ================================================ FILE: rules.neon ================================================ rules: - Roave\PHPStan\Rules\Floats\DisallowFloatAssignedToVariableRule - Roave\PHPStan\Rules\Floats\DisallowFloatInFunctionSignatureRule - Roave\PHPStan\Rules\Floats\DisallowFloatInMethodSignatureRule - Roave\PHPStan\Rules\Floats\DisallowFloatPropertyTypeRule parameters: disallowFloatsEverywhere: false conditionalTags: Roave\PHPStan\Rules\Floats\DisallowFloatEverywhereRule: phpstan.rules.rule: %disallowFloatsEverywhere% services: - class: Roave\PHPStan\Rules\Floats\DisallowFloatEverywhereRule parametersSchema: disallowFloatsEverywhere: bool() ================================================ FILE: src/DisallowFloatAssignedToVariableRule.php ================================================ */ final class DisallowFloatAssignedToVariableRule implements Rule { public function __construct(private Printer $printer) { } public function getNodeType(): string { return Node::class; } /** * {@inheritDoc} */ public function processNode(Node $node, Scope $scope): array { if (! $node instanceof Node\Expr\AssignOp && ! $node instanceof Node\Expr\Assign) { return []; } $resultType = $scope->getType($node); if (! FloatTypeHelper::isFloat($resultType)) { return []; } return [ RuleErrorBuilder::message(sprintf( 'Cannot assign %s to %s - floats are not allowed.', $resultType->describe(VerbosityLevel::typeOnly()), $this->printer->prettyPrintExpr($node->var), ))->identifier('float.assign')->build(), ]; } } ================================================ FILE: src/DisallowFloatEverywhereRule.php ================================================ * @final not designed for inheritance, but kept open for BC, until the next major release */ class DisallowFloatEverywhereRule implements Rule { public function getNodeType(): string { return Expr::class; } /** * {@inheritDoc} */ public function processNode(Node $node, Scope $scope): array { if ( $node instanceof Node\Expr\AssignOp || $node instanceof Node\Expr\Assign ) { return []; } $nodeType = $scope->getType($node); if (! FloatTypeHelper::isFloat($nodeType)) { return []; } return [ RuleErrorBuilder::message(sprintf( 'Cannot have %s as a result type of this expression - floats are not allowed.', $nodeType->describe(VerbosityLevel::typeOnly()), ))->identifier('float.expression')->build(), ]; } } ================================================ FILE: src/DisallowFloatInFunctionSignatureRule.php ================================================ */ final class DisallowFloatInFunctionSignatureRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) { } public function getNodeType(): string { return Function_::class; } /** * {@inheritDoc} */ public function processNode(Node $node, Scope $scope): array { $functionName = new Name($node->name->toString()); if (! $this->reflectionProvider->hasFunction($functionName, $scope)) { return []; } $functionReflection = $this->reflectionProvider->getFunction($functionName, $scope); $errors = []; foreach ($functionReflection->getVariants() as $functionVariant) { $errors[] = $this->violationsForParameters($functionVariant, $functionReflection); $errors[] = $this->returnTypeViolations($functionVariant, $functionReflection); } return array_merge([], ...$errors); } /** @return list */ private function returnTypeViolations( ParametersAcceptor $function, FunctionReflection $functionReflection, ): array { if (! FloatTypeHelper::isFloat($function->getReturnType())) { return []; } return [ RuleErrorBuilder::message(sprintf( 'Function %s() cannot have %s as its return type - floats are not allowed.', $functionReflection->getName(), $function->getReturnType()->describe(VerbosityLevel::typeOnly()), ))->identifier('float.function')->build(), ]; } /** @return list */ private function violationsForParameters( ParametersAcceptor $function, FunctionReflection $functionReflection, ): array { $parameters = $function->getParameters(); return array_values(array_filter(array_map( static function (ParameterReflection $parameter, int $index) use ($functionReflection): RuleError|null { if (! FloatTypeHelper::isFloat($parameter->getType())) { return null; } return RuleErrorBuilder::message(sprintf( 'Parameter #%d $%s of function %s() cannot have %s as its type - floats are not allowed.', $index + 1, $parameter->getName(), $functionReflection->getName(), $parameter->getType()->describe(VerbosityLevel::typeOnly()), ))->identifier('float.function')->build(); }, $parameters, array_keys($parameters), ), static fn (RuleError|null $error): bool => $error !== null)); } } ================================================ FILE: src/DisallowFloatInMethodSignatureRule.php ================================================ */ final class DisallowFloatInMethodSignatureRule implements Rule { public function getNodeType(): string { return ClassMethod::class; } /** * {@inheritDoc} */ public function processNode(Node $node, Scope $scope): array { if (! $scope->isInClass()) { throw new ShouldNotHappenException(); } /** @psalm-var ClassReflection $classReflection */ $classReflection = $scope->getClassReflection(); $methodName = $node->name->toString(); $method = $classReflection->getNativeMethod($methodName); $errors = []; foreach ($method->getVariants() as $methodVariant) { $errors[] = $this->violationsForParameters($methodVariant, $method); $errors[] = $this->returnTypeViolations($methodVariant, $method); } return array_merge([], ...$errors); } /** @return list */ private function returnTypeViolations( ParametersAcceptor $method, MethodReflection $methodReflection, ): array { if (! FloatTypeHelper::isFloat($method->getReturnType())) { return []; } return [ RuleErrorBuilder::message(sprintf( 'Method %s::%s() cannot have %s as its return type - floats are not allowed.', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $method->getReturnType()->describe(VerbosityLevel::typeOnly()), ))->identifier('float.type')->build(), ]; } /** @return list */ private function violationsForParameters( ParametersAcceptor $function, MethodReflection $methodReflection, ): array { $parameters = $function->getParameters(); return array_values(array_filter(array_map( static function (ParameterReflection $parameter, int $index) use ($methodReflection): RuleError|null { if (! FloatTypeHelper::isFloat($parameter->getType())) { return null; } return RuleErrorBuilder::message(sprintf( 'Parameter #%d $%s of method %s::%s() cannot have %s as its type - floats are not allowed.', $index + 1, $parameter->getName(), $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $parameter->getType()->describe(VerbosityLevel::typeOnly()), ))->identifier('float.type')->build(); }, $parameters, array_keys($parameters), ), static fn (RuleError|null $error): bool => $error !== null)); } } ================================================ FILE: src/DisallowFloatPropertyTypeRule.php ================================================ */ final class DisallowFloatPropertyTypeRule implements Rule { public function getNodeType(): string { return PropertyItem::class; } /** * {@inheritDoc} */ public function processNode(Node $node, Scope $scope): array { if (! $scope->isInClass()) { throw new ShouldNotHappenException(); } /** @psalm-var ClassReflection $classReflection */ $classReflection = $scope->getClassReflection(); $propertyName = $node->name->toString(); $property = $classReflection->getNativeProperty($node->name->toString()); $propertyType = $property->getReadableType(); if (! FloatTypeHelper::isFloat($propertyType)) { return []; } return [ RuleErrorBuilder::message(sprintf( 'Property %s::$%s cannot have %s as its type - floats are not allowed.', $property->getDeclaringClass()->getDisplayName(), $propertyName, $propertyType->describe(VerbosityLevel::typeOnly()), ))->identifier('float.property')->build(), ]; } } ================================================ FILE: src/FloatTypeHelper.php ================================================ resolve()); } return ! (new FloatType())->isSuperTypeOf($type)->no(); } } ================================================ FILE: tests/asset/assign.php ================================================ foo = $float; $this->bar['test'] = $float; $test3 = $str; } public function doBar() { $test = 0; $test += 1; $test += 3.14; } } ================================================ FILE: tests/asset/expr.php ================================================ |string $type * @return ($type is class-string ? TObject : mixed) */ public function denormalize(mixed $data, string $type): mixed; } class Denormalizer implements DenormalizerInterface { public function denormalize(mixed $data, string $type): mixed { return new \stdClass(); } } class DummyTypeConverter { /** * @template RequestedType of 'float'|'int' * @param RequestedType $type * @return (RequestedType is 'float' ? float : int) */ function convertToNumber(mixed $input, string $type): mixed { throw new Exception('irrelevant - ' . gettype($input) . ' - ' . $type); } /** * @template RequestedType of 'float'|'int' * @param RequestedType $type * @return (RequestedType is 'int' ? int : float) */ function convertToNumber2(mixed $input, string $type): mixed { throw new Exception('irrelevant - ' . gettype($input) . ' - ' . $type); } } ================================================ FILE: tests/asset/methodWithInterpolatedFloatAndNotFloatParameters.php ================================================ */ final class DisallowFloatAssignedToVariableRuleTest extends RuleTestCase { protected function getRule(): Rule { /** @phpstan-ignore phpstanApi.constructor */ return new DisallowFloatAssignedToVariableRule(new Printer()); } public function testRule(): void { $this->analyse([__DIR__ . '/../asset/assign.php'], [ [ 'Cannot assign float to $test - floats are not allowed.', 18, ], [ 'Cannot assign float|int to $test2 - floats are not allowed.', 19, ], [ 'Cannot assign float to $this->foo - floats are not allowed.', 21, ], [ 'Cannot assign float to $this->bar[\'test\'] - floats are not allowed.', 22, ], [ 'Cannot assign float to $test - floats are not allowed.', 31, ], ]); } } ================================================ FILE: tests/src/DisallowFloatEverywhereRuleTest.php ================================================ */ final class DisallowFloatEverywhereRuleTest extends RuleTestCase { protected function getRule(): Rule { return new DisallowFloatEverywhereRule(); } public function testRule(): void { $this->analyse([__DIR__ . '/../asset/expr.php'], [ [ 'Cannot have float as a result type of this expression - floats are not allowed.', 6, ], [ 'Cannot have float as a result type of this expression - floats are not allowed.', 7, ], [ 'Cannot have float as a result type of this expression - floats are not allowed.', 7, ], [ 'Cannot have float as a result type of this expression - floats are not allowed.', 10, ], ]); } } ================================================ FILE: tests/src/DisallowFloatInFunctionSignatureRuleTest.php ================================================ */ final class DisallowFloatInFunctionSignatureRuleTest extends RuleTestCase { protected function getRule(): Rule { return new DisallowFloatInFunctionSignatureRule($this->createReflectionProvider()); } public function testRule(): void { require_once __DIR__ . '/../asset/function.php'; $this->analyse([__DIR__ . '/../asset/function.php'], [ [ 'Parameter #1 $float of function DisallowFloatsInFunctionSignatures\doFoo() cannot have float as its type - floats are not allowed.', 11, ], [ 'Parameter #2 $intOrFloat of function DisallowFloatsInFunctionSignatures\doFoo() cannot have float|int as its type - floats are not allowed.', 11, ], [ 'Function DisallowFloatsInFunctionSignatures\doFoo() cannot have float as its return type - floats are not allowed.', 11, ], ]); } public function testRuleWithoutNamespace(): void { require_once __DIR__ . '/../asset/functionWithoutNamespace.php'; $this->analyse([__DIR__ . '/../asset/functionWithoutNamespace.php'], [ [ 'Parameter #1 $float of function doFoo() cannot have float as its type - floats are not allowed.', 9, ], [ 'Parameter #2 $intOrFloat of function doFoo() cannot have float|int as its type - floats are not allowed.', 9, ], [ 'Function doFoo() cannot have float as its return type - floats are not allowed.', 9, ], ]); } public function testRuleShowsAllFloatParametersAsViolations(): void { require_once __DIR__ . '/../asset/functionWithInterpolatedFloatAndNonFloatParameters.php'; $this->analyse([__DIR__ . '/../asset/functionWithInterpolatedFloatAndNonFloatParameters.php'], [ [ 'Parameter #1 $a of function DisallowFloatsInFunctionSignatures\functionWithInterpolatedFloatAndNonFloatParameters() cannot have float as its type - floats are not allowed.', 6, ], [ 'Parameter #3 $c of function DisallowFloatsInFunctionSignatures\functionWithInterpolatedFloatAndNonFloatParameters() cannot have float as its type - floats are not allowed.', 6, ], [ 'Parameter #5 $e of function DisallowFloatsInFunctionSignatures\functionWithInterpolatedFloatAndNonFloatParameters() cannot have float as its type - floats are not allowed.', 6, ], ]); } public function testNotAutoloadedFunction(): void { $this->analyse( [__DIR__ . '/../asset/functionNotAutoloaded.php'], [ [ 'Function nonexistentRoaveFunction() cannot have float as its return type - floats are not allowed.', 3, ], ], ); } } ================================================ FILE: tests/src/DisallowFloatInMethodSignatureRuleTest.php ================================================ */ final class DisallowFloatInMethodSignatureRuleTest extends RuleTestCase { protected function getRule(): Rule { return new DisallowFloatInMethodSignatureRule(); } public function testRule(): void { $this->analyse([__DIR__ . '/../asset/method.php'], [ [ 'Parameter #1 $float of method DisallowFloatsInMethodSignatures\Foo::doFoo() cannot have float as its type - floats are not allowed.', 14, ], [ 'Parameter #2 $intOrFloat of method DisallowFloatsInMethodSignatures\Foo::doFoo() cannot have float|int as its type - floats are not allowed.', 14, ], [ 'Method DisallowFloatsInMethodSignatures\Foo::doFoo() cannot have float as its return type - floats are not allowed.', 14, ], ]); } public function testRuleShowsAllFloatParametersAsViolations(): void { $this->analyse([__DIR__ . '/../asset/methodWithInterpolatedFloatAndNotFloatParameters.php'], [ [ 'Parameter #1 $a of method DisallowFloatsInMethodSignatures\Bar::doFoo() cannot have float as its type - floats are not allowed.', 8, ], [ 'Parameter #3 $c of method DisallowFloatsInMethodSignatures\Bar::doFoo() cannot have float as its type - floats are not allowed.', 8, ], [ 'Parameter #5 $e of method DisallowFloatsInMethodSignatures\Bar::doFoo() cannot have float as its type - floats are not allowed.', 8, ], ]); } /** * Verifies that conditional return types containing mixed are not flagged as floats. * * @see https://github.com/Roave/no-floaters/issues/126 */ public function testRuleDoesNotFlagConditionalReturnTypesContainingMixed(): void { $this->analyse( [__DIR__ . '/../asset/methodWithConditionalReturnType.php'], [ [ 'Method DisallowFloatsInMethodSignatures\DummyTypeConverter::convertToNumber() cannot have (RequestedType of string is string ? float : int) as its return type - floats are not allowed.', 35, ], [ 'Method DisallowFloatsInMethodSignatures\DummyTypeConverter::convertToNumber2() cannot have (RequestedType of string is string ? int : float) as its return type - floats are not allowed.', 44, ], ], ); } /** * Verifies that the impossible scenario of a method signature is not declared in a class method */ public function testRuleWillNotWorkWhenNotInClassScope(): void { $rule = new DisallowFloatInMethodSignatureRule(); $node = self::createStub(ClassMethod::class); $scope = self::createStub(ScopeWithNodeCallbackInvoker::class); $scope ->method('isInClass') ->willReturn(false); $this->expectException(ShouldNotHappenException::class); $this->expectExceptionMessage('Internal error.'); $rule->processNode($node, $scope); } } ================================================ FILE: tests/src/DisallowFloatPropertyTypeRuleTest.php ================================================ */ final class DisallowFloatPropertyTypeRuleTest extends RuleTestCase { protected function getRule(): Rule { return new DisallowFloatPropertyTypeRule(); } public function testRule(): void { $this->analyse([__DIR__ . '/../asset/property.php'], [ [ 'Property DisallowFloatsInProperties\Foo::$foo cannot have float as its type - floats are not allowed.', 9, ], [ 'Property DisallowFloatsInProperties\Foo::$bar cannot have float|int as its type - floats are not allowed.', 12, ], ]); } /** * Verifies that the impossible scenario of a method signature is not declared in a class method */ public function testRuleWillNotWorkWhenNotInClassScope(): void { $rule = new DisallowFloatPropertyTypeRule(); $node = self::createStub(PropertyItem::class); $scope = self::createStub(ScopeWithNodeCallbackInvoker::class); $scope ->method('isInClass') ->willReturn(false); $this->expectException(ShouldNotHappenException::class); $this->expectExceptionMessage('Internal error.'); $rule->processNode($node, $scope); } } ================================================ FILE: tests/src/ScopeWithNodeCallbackInvoker.php ================================================