Skip to content

Commit

Permalink
fix: parsing of new initializers in promoted constructor properties
Browse files Browse the repository at this point in the history
  • Loading branch information
gskema committed Dec 11, 2023
1 parent d68be72 commit f3e16ed
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to `phpcs-type-sniff` will be documented in this file.

Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.

## 82.2.1 - 2023-12-11
### Fixed
- Fixed parsing of `new` initializers in promoted constructor properties
- Sniff options parsing

## 82.2.0 - 2023-04-07
### Changed
- Remove upper bound PHP requirement so package can be installed on higher PHP versions even if locked.
Expand Down
27 changes: 19 additions & 8 deletions src/Core/Func/FunctionSignatureParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,29 @@ public static function fromTokens(File $file, int $fnPtr): FunctionSignature
}
break;
case T_NEW:
$raw['new'] = 0;
$raw['new'] = true;
break;
case T_OPEN_PARENTHESIS:
if (0 === ($raw['new'] ?? null)) {
$raw['new'] = 1;
if ($raw['new'] ?? false) {
// new constructor args may contain tokens arrays, object constructors, etc.
// so we must skip to next param or to end of function signature
$closingParenthesisPtr = TokenHelper::findClosingParenthesis($file, $ptr);
// Some dumbass may write = new Obj, but it's already a standard warning. This won't crash
if (null !== $closingParenthesisPtr) {
if (!empty($raw)) {
$params[] = static::createParam($raw);
$raw = [];
}
$ptr = $closingParenthesisPtr;
break; // skip to whatever is next
}
} else {
$raw['type'] = ($raw['type'] ?? '') . $token['content']; // intersection type
}
break;
case T_CLOSE_PARENTHESIS:
if (1 === ($raw['new'] ?? null)) {
$raw['new'] = 2;
break;
}
// end of function signature, close parenthesis for new (hopefully) have been skipped
// also possible closing DNF type

// type detected e.g '(int', variable still not detected, means this is before var name: intersection type
if (isset($raw['type']) && !isset($raw['name'])) {
Expand Down Expand Up @@ -239,11 +248,13 @@ protected static function createParam(array $raw): FunctionParam
{
$rawValueType = $raw['default'] ?? '';
if (str_contains($rawValueType, '::')) {
$valueType = null; // a constant is used, need reflection :(
$valueType = null; // a constant is used, need reflection or imported class parsing
} else {
$valueType = TypeFactory::fromRawType($raw['default'] ?? '');
}

// Try when global constant (parsed as FqcnType).
// 'new' flag indicated new constructor - would need to parse imported classes.
if ($valueType instanceof FqcnType && !($raw['new'] ?? false)) {
if (defined($valueType->toString())) { // eg PHP_INT_MAX
$valueType = TypeFactory::fromValue(constant($valueType->toString()));
Expand Down
21 changes: 21 additions & 0 deletions src/Core/TokenHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -453,4 +453,25 @@ public static function isClassExtended(File $file, int $classPtr): bool

return T_EXTENDS === $extendsCode;
}

public static function findClosingParenthesis(File $file, int $openParenthesisPtr): ?int
{
$tokens = $file->getTokens();

$ptr = $openParenthesisPtr;
$openedScopeCount = 1;
while (isset($tokens[++$ptr])) {
$tokenCode = $tokens[$ptr]['code'] ?? null;
if (T_OPEN_PARENTHESIS === $tokenCode) {
$openedScopeCount++;
} elseif (T_CLOSE_PARENTHESIS === $tokenCode) {
$openedScopeCount--;
}
if (0 === $openedScopeCount) {
return $ptr;
}
}

return null;
}
}
11 changes: 11 additions & 0 deletions tests/Sniffs/CompositeCodeElementSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,17 @@ public function dataProcess(): array
],
];

// #24
$dataSets[] = [
[
'addViolationId' => false,
'useReflection' => false,
],
__DIR__ . '/fixtures/TestClass15.php',
[
],
];

return $dataSets;
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Sniffs/fixtures/TestClass13.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function create10(): static

public function method1(
Acme|null $param1 = null,
Acme|string|null $param1 = null
Acme|string|null $param2 = null
): int|null {
}
}
23 changes: 23 additions & 0 deletions tests/Sniffs/fixtures/TestClass15.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Gskema\TypeSniff\Sniffs\fixtures;

class EmailRequest
{
public function __construct(
private readonly Headers $headers = new Headers(['a' => new \stdClass(), 'b' => []]),
private readonly Headers $headers2 = new Headers([])
) {
}
}

class EmailRequest2
{
public function __construct(
private readonly Headers $headers = new Headers,
private readonly Headers $headers2 = new Headers
) {
}
}

0 comments on commit f3e16ed

Please sign in to comment.