Skip to content

Commit

Permalink
Fix easy cases of date related functions
Browse files Browse the repository at this point in the history
Closes humbug#301
  • Loading branch information
theofidry committed Jun 10, 2019
1 parent e699999 commit e431a8a
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 12 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ potentially very difficult to debug due to dissimilar or unsupported package ver
- [Recommendations](#recommendations)
- [Limitations](#limitations)
- [Dynamic symbols](#dynamic-symbols)
- [Date symbols](#date-symbols)
- [Heredoc values](#heredoc-values)
- [Callables](#callables)
- [String values](#string-values)
Expand Down Expand Up @@ -648,6 +649,19 @@ possible such as:
- `const X = 'Symfony\\Component' . '\\Yaml\\Ya_1';`


### Date symbols

You code may be using a convention for the date string formats which could be mistaken for classes, e.g.:

```php
const ISO8601_BASIC = 'Ymd\THis\Z';
```

In this scenario, PHP-Scoper has no way to tell that string `'Ymd\THis\Z'` does not refer to a symbol but is
a date format. In this case, you will have to rely on patchers. Note however that PHP-Scoper will be able to
handle some cases such as, see the [date-spec](specs/misc/date.php).


### Heredoc values

If you consider the following code:
Expand Down
File renamed without changes.
114 changes: 114 additions & 0 deletions specs/misc/date.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

/*
* This file is part of the humbug/php-scoper package.
*
* Copyright (c) 2017 Théo FIDRY <[email protected]>,
* Pádraic Brady <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

return [
'meta' => [
'title' => 'Date related functions/calls',
// Default values. If not specified will be the one used
'prefix' => 'Humbug',
'whitelist' => [],
'whitelist-global-constants' => true,
'whitelist-global-classes' => false,
'whitelist-global-functions' => true,
'registered-classes' => [],
'registered-functions' => [],
],

'date values' => <<<'PHP'
<?php
const ISO8601_BASIC = 'Ymd\THis\Z';
new Foo('d\H\Z');
new DateTime('d\H\Z');
new DateTimeImmutable('d\H\Z');
date_create('d\H\Z');
date('d\H\Z');
gmdate('d\H\Z');
DateTime::createFromFormat('d\H\Z', '15\Feb\2009');
DateTimeImmutable::createFromFormat('d\H\Z', '15\Feb\2009');
date_create_from_format('d\H\Z', '15\Feb\2009');
(new DateTime('now'))->format('d\H\Z');
date_format(new DateTime('now'), 'd\H\Z');
----
<?php
namespace Humbug;
const ISO8601_BASIC = 'Humbug\\Ymd\\THis\\Z';
new \Humbug\Foo('Humbug\\d\\H\\Z');
new \DateTime('d\\H\\Z');
new \DateTimeImmutable('d\\H\\Z');
\date_create('d\\H\\Z');
\date('d\\H\\Z');
\gmdate('d\\H\\Z');
\DateTime::createFromFormat('d\\H\\Z', '15\\Feb\\2009');
\DateTimeImmutable::createFromFormat('d\\H\\Z', '15\\Feb\\2009');
\date_create_from_format('d\\H\\Z', '15\\Feb\\2009');
(new \DateTime('now'))->format('Humbug\\d\\H\\Z');
\date_format(new \DateTime('now'), 'Humbug\\d\\H\\Z');

PHP
,

'date values in a namespace' => <<<'PHP'
<?php
namespace Acme;
use DateTime;
use DateTimeImmutable;
const ISO8601_BASIC = 'Ymd\THis\Z';
new Foo('d\H\Z');
new DateTime('d\H\Z');
new DateTimeImmutable('d\H\Z');
date_create('d\H\Z');
date('d\H\Z');
gmdate('d\H\Z');
DateTime::createFromFormat('d\H\Z', '15\Feb\2009');
DateTimeImmutable::createFromFormat('d\H\Z', '15\Feb\2009');
date_create_from_format('d\H\Z', '15\Feb\2009');
(new DateTime('now'))->format('d\H\Z');
date_format(new DateTime('now'), 'd\H\Z');
----
<?php
namespace Humbug\Acme;
use DateTime;
use DateTimeImmutable;
const ISO8601_BASIC = 'Humbug\\Ymd\\THis\\Z';
new \Humbug\Acme\Foo('Humbug\\d\\H\\Z');
new \DateTime('d\\H\\Z');
new \DateTimeImmutable('d\\H\\Z');
\date_create('d\\H\\Z');
\date('d\\H\\Z');
\gmdate('d\\H\\Z');
\DateTime::createFromFormat('d\\H\\Z', '15\\Feb\\2009');
\DateTimeImmutable::createFromFormat('d\\H\\Z', '15\\Feb\\2009');
\date_create_from_format('d\\H\\Z', '15\\Feb\\2009');
(new \DateTime('now'))->format('Humbug\\d\\H\\Z');
\date_format(new \DateTime('now'), 'Humbug\\d\\H\\Z');

PHP
,
];
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
89 changes: 77 additions & 12 deletions src/PhpParser/NodeVisitor/StringScalarPrefixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
Expand All @@ -38,6 +41,7 @@
use function is_string;
use function preg_match;
use function strpos;
use function strtolower;

/**
* Prefixes the string scalar values when appropriate.
Expand Down Expand Up @@ -68,6 +72,11 @@ final class StringScalarPrefixer extends NodeVisitorAbstract
'trait_exists',
];

private const DATETIME_CLASSES = [
'datetime',
'datetimeimmutable',
];

private $prefix;
private $whitelist;
private $reflector;
Expand Down Expand Up @@ -138,26 +147,52 @@ private function prefixStringScalar(String_ $string): String_

private function prefixStringArg(String_ $string, Arg $parentNode): String_
{
$functionNode = ParentNodeAppender::getParent($parentNode);
$callerNode = ParentNodeAppender::getParent($parentNode);

if (false === ($functionNode instanceof FuncCall)) {
// If belongs to the global namespace then we cannot differentiate the value from a symbol and a regular string
return $this->belongsToTheGlobalNamespace($string)
? $string
: $this->createPrefixedString($string)
;
if ($callerNode instanceof New_) {
return $this->prefixNewStringArg($string, $callerNode);
}

if ($callerNode instanceof FuncCall) {
return $this->prefixFunctionStringArg($string, $callerNode);
}

if ($callerNode instanceof StaticCall) {
return $this->prefixStaticCallStringArg($string, $callerNode);
}
/** @var FuncCall $functionNode */

// If belongs to the global namespace then we cannot differentiate the value from a symbol and a regular
// string
return $this->createPrefixedStringIfDoesNotBelongToGlobalNamespace($string);
}

private function prefixNewStringArg(String_ $string, New_ $newNode): String_
{
$class = $newNode->class;

if (false === ($class instanceof FullyQualified)) {
return $this->createPrefixedStringIfDoesNotBelongToGlobalNamespace($string);
}

if (in_array(strtolower($class->toString()), self::DATETIME_CLASSES, true)) {
return $string;
}

return $this->createPrefixedStringIfDoesNotBelongToGlobalNamespace($string);
}

private function prefixFunctionStringArg(String_ $string, FuncCall $functionNode): String_
{
// In the case of a function call, we allow to prefix strings which could be classes belonging to the global
// namespace in some cases
$functionName = $functionNode->name instanceof Name ? (string) $functionNode->name : null;

if (in_array($functionName, ['date_create', 'date', 'gmdate', 'date_create_from_format'], true)) {
return $string;
}

if (false === in_array($functionName, self::SPECIAL_FUNCTION_NAMES, true)) {
return $this->belongsToTheGlobalNamespace($string)
? $string
: $this->createPrefixedString($string)
;
return $this->createPrefixedStringIfDoesNotBelongToGlobalNamespace($string);
}

if ('function_exists' === $functionName) {
Expand Down Expand Up @@ -193,6 +228,27 @@ private function prefixStringArg(String_ $string, Arg $parentNode): String_
;
}

private function prefixStaticCallStringArg(String_ $string, StaticCall $callNode): String_
{
$class = $callNode->class;

if (false === ($class instanceof FullyQualified)) {
return $this->createPrefixedStringIfDoesNotBelongToGlobalNamespace($string);
}

if (false === in_array(strtolower($class->toString()), self::DATETIME_CLASSES, true)) {
return $this->createPrefixedStringIfDoesNotBelongToGlobalNamespace($string);
}

if ($callNode->name instanceof Identifier
&& 'createFromFormat' === $callNode->name->toString()
) {
return $string;
}

return $this->createPrefixedStringIfDoesNotBelongToGlobalNamespace($string);
}

private function prefixArrayItemString(String_ $string, ArrayItem $parentNode): String_
{
// ArrayItem can lead to two results: either the string is used for `spl_autoload_register()`, e.g.
Expand Down Expand Up @@ -269,6 +325,15 @@ private function isConstantNode(String_ $node): bool
return $parent === $argParent->args[0];
}

private function createPrefixedStringIfDoesNotBelongToGlobalNamespace(String_ $string): String_
{
// If belongs to the global namespace then we cannot differentiate the value from a symbol and a regular string
return $this->belongsToTheGlobalNamespace($string)
? $string
: $this->createPrefixedString($string)
;
}

private function createPrefixedString(String_ $previous): String_
{
$previousValueParts = array_values(
Expand Down

0 comments on commit e431a8a

Please sign in to comment.