Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ protected function resolveOriginalContentSpacingAndOrder(?string $originalConten
$this->hasClosingBracket = (bool) Strings::match($originalContent, '#\)$#');
}

protected function resolveIsValueQuoted(string $originalContent, $value): bool
{
if ($value === null) {
return false;
}

if (! is_string($value)) {
return false;
}

// @see https://regex101.com/r/VgvK8C/3/
$quotedNamePattern = sprintf('#"%s"#', preg_quote($value, '#'));

return (bool) Strings::match($originalContent, $quotedNamePattern);
}

protected function printWithOptionalQuotes(string $name, $value, bool $isQuoted, bool $isExplicit = true): string
{
$content = '';
if ($isExplicit) {
$content = $name . '=';
}

if (is_array($value)) {
return $content . $this->printArrayItem($value);
}

if ($isQuoted) {
return $content . sprintf('"%s"', $value);
}

return $content . sprintf('%s', $value);
}

/**
* @param PhpDocTagValueNode[] $tagValueNodes
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ final class SymfonyRouteTagValueNode extends AbstractTagValueNode implements Sho
*/
private $condition;

/**
* @var bool
*/
private $isNameQuoted = true;

/**
* @param string[] $localizedPaths
* @param string[] $methods
Expand Down Expand Up @@ -111,13 +116,15 @@ public function __construct(
$this->resolveOriginalContentSpacingAndOrder($originalContent);

// default value without key
if ($this->shouldAddIimplicitPaths()) {
if ($this->shouldAddImplicitPaths()) {
// add path as first item
$this->orderedVisibleItems = array_merge(['path'], (array) $this->orderedVisibleItems);
}

$matches = Strings::match($originalContent, '#requirements={(.*?)(?<separator>(=|:))(.*)}#');
$this->requirementsKeyValueSeparator = $matches['separator'] ?? '=';

$this->isNameQuoted = $this->resolveIsValueQuoted($originalContent, $name);
}

$this->host = $host;
Expand All @@ -131,7 +138,7 @@ public function __toString(): string
];

if ($this->name) {
$contentItems['name'] = sprintf('name="%s"', $this->name);
$contentItems['name'] = $this->printWithOptionalQuotes('name', $this->name, $this->isNameQuoted);
}

if ($this->methods !== []) {
Expand Down Expand Up @@ -191,7 +198,7 @@ private function createPath(): string
return Strings::replace($localizedPaths, '#:#', ': ');
}

private function shouldAddIimplicitPaths(): bool
private function shouldAddImplicitPaths(): bool
{
return ($this->path || $this->localizedPaths) && ! in_array('path', (array) $this->orderedVisibleItems, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

/**
* @see \Rector\BetterPhpDocParser\PhpDocNodeFactory\Symfony\Validator\Constraints\AssertTypePhpDocNodeFactory
*
* @see \Rector\BetterPhpDocParser\Tests\PhpDocParser\SymfonyValidation\AssertTypeTagValueNodeTest
*/
final class AssertTypeTagValueNode extends AbstractConstraintTagValueNode implements ShortNameAwareTagInterface
{
Expand Down Expand Up @@ -37,7 +39,7 @@ public function __construct(array $groups, ?string $message = null, $type = null

if ($originalContent !== null) {
$this->isTypeExplicit = (bool) Strings::contains($originalContent, 'type=');
$this->resolveIsQuotedType($originalContent, $type);
$this->isTypeQuoted = $this->resolveIsValueQuoted($originalContent, $type);
}

$this->resolveOriginalContentSpacingAndOrder($originalContent, 'type');
Expand All @@ -50,7 +52,12 @@ public function __toString(): string
$contentItems = [];

if ($this->type !== null) {
$contentItems['type'] = $this->createType();
$contentItems['type'] = $this->printWithOptionalQuotes(
'type',
$this->type,
$this->isTypeQuoted,
$this->isTypeExplicit
);
}

$contentItems = $this->appendGroups($contentItems);
Expand All @@ -63,41 +70,4 @@ public function getShortName(): string
{
return '@Assert\Type';
}

/**
* @param string|mixed[]|null $type
*/
private function resolveIsQuotedType(string $originalContent, $type): void
{
if ($type === null) {
return;
}

if (! is_string($type)) {
return;
}

// @see https://regex101.com/r/VgvK8C/3/
$quotedTypePattern = sprintf('#"%s"#', preg_quote($type, '#'));

$this->isTypeQuoted = (bool) Strings::match($originalContent, $quotedTypePattern);
}

private function createType(): string
{
$content = '';
if ($this->isTypeExplicit) {
$content = 'type=';
}

if ($this->isTypeQuoted && is_string($this->type)) {
return $content . '"' . $this->type . '"';
}

if (is_array($this->type)) {
return $content . $this->printArrayItem($this->type);
}

return $content . $this->type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Lexer\Lexer;
use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineTagNodeInterface;
use Rector\BetterPhpDocParser\PhpDocNode\Symfony\SymfonyRouteTagValueNode;
use Rector\BetterPhpDocParser\Contract\PhpDocNode\ShortNameAwareTagInterface;
use Rector\BetterPhpDocParser\ValueObject\StartEndValueObject;

final class WhitespaceDetector
Expand All @@ -29,14 +29,14 @@ public function detectOldWhitespaces(Node $node, array $tokens, StartEndValueObj
--$start;
}

for ($i = $start; $i <= $startEndValueObject->getEnd(); ++$i) {
for ($i = $start; $i < $startEndValueObject->getEnd(); ++$i) {
/** @var string $tokenValue */
$tokenValue = $tokens[$i][0];

if ($tokens[$i][1] === Lexer::TOKEN_HORIZONTAL_WS) {
// give back "\s+\*" as well
// do not overlap to previous node
if (($node instanceof DoctrineTagNodeInterface || $node instanceof SymfonyRouteTagValueNode) &&
if (($node instanceof DoctrineTagNodeInterface || $node instanceof ShortNameAwareTagInterface) &&
$i - 1 > $start &&
isset($tokens[$i - 1]) &&
$tokens[$i - 1][1] === Lexer::TOKEN_PHPDOC_EOL
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Rector\BetterPhpDocParser\Tests\PhpDocParser\SymfonyRouteTagParser\Fixture;

use Symfony\Component\Routing\Annotation\Route;

final class RouteName
{
/**
* @Route("/hello/", name=TestController::ROUTE_NAME)
*/
public function run()
{
}
}