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

[](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
================================================
srctests/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/srcsrc
================================================
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
================================================