composer update

This commit is contained in:
Manish Verma
2018-12-05 10:50:52 +05:30
parent 9eabcacfa7
commit 4addd1e9c6
3328 changed files with 156676 additions and 138988 deletions

View File

@@ -42,13 +42,13 @@ class Param implements PhpParser\Builder
}
/**
* Sets type hint for the parameter.
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType $type Type hint to use
* @param string|Node\Name|Node\NullableType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*/
public function setTypeHint($type) {
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
if ($this->type == 'void') {
throw new \LogicException('Parameter type cannot be void');
@@ -57,6 +57,19 @@ class Param implements PhpParser\Builder
return $this;
}
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*
* @deprecated Use setType() instead
*/
public function setTypeHint($type) {
return $this->setType($type);
}
/**
* Make the parameter accept the value by reference.
*

View File

@@ -0,0 +1,64 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Builder;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class TraitUse implements Builder
{
protected $traits = [];
protected $adaptations = [];
/**
* Creates a trait use builder.
*
* @param Node\Name|string ...$traits Names of used traits
*/
public function __construct(...$traits) {
foreach ($traits as $trait) {
$this->and($trait);
}
}
/**
* Adds used trait.
*
* @param Node\Name|string $trait Trait name
*
* @return $this The builder instance (for fluid interface)
*/
public function and($trait) {
$this->traits[] = BuilderHelpers::normalizeName($trait);
return $this;
}
/**
* Adds trait adaptation.
*
* @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation
*
* @return $this The builder instance (for fluid interface)
*/
public function with($adaptation) {
$adaptation = BuilderHelpers::normalizeNode($adaptation);
if (!$adaptation instanceof Stmt\TraitUseAdaptation) {
throw new \LogicException('Adaptation must have type TraitUseAdaptation');
}
$this->adaptations[] = $adaptation;
return $this;
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
return new Stmt\TraitUse($this->traits, $this->adaptations);
}
}

View File

@@ -0,0 +1,148 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Builder;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class TraitUseAdaptation implements Builder
{
const TYPE_UNDEFINED = 0;
const TYPE_ALIAS = 1;
const TYPE_PRECEDENCE = 2;
/** @var int Type of building adaptation */
protected $type;
protected $trait;
protected $method;
protected $modifier = null;
protected $alias = null;
protected $insteadof = [];
/**
* Creates a trait use adaptation builder.
*
* @param Node\Name|string|null $trait Name of adaptated trait
* @param Node\Identifier|string $method Name of adaptated method
*/
public function __construct($trait, $method) {
$this->type = self::TYPE_UNDEFINED;
$this->trait = is_null($trait)? null: BuilderHelpers::normalizeName($trait);
$this->method = BuilderHelpers::normalizeIdentifier($method);
}
/**
* Sets alias of method.
*
* @param Node\Identifier|string $alias Alias for adaptated method
*
* @return $this The builder instance (for fluid interface)
*/
public function as($alias) {
if ($this->type === self::TYPE_UNDEFINED) {
$this->type = self::TYPE_ALIAS;
}
if ($this->type !== self::TYPE_ALIAS) {
throw new \LogicException('Cannot set alias for not alias adaptation buider');
}
$this->alias = $alias;
return $this;
}
/**
* Sets adaptated method public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Sets adaptated method protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Sets adaptated method private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Adds overwritten traits.
*
* @param Node\Name|string ...$traits Traits for overwrite
*
* @return $this The builder instance (for fluid interface)
*/
public function insteadof(...$traits) {
if ($this->type === self::TYPE_UNDEFINED) {
if (is_null($this->trait)) {
throw new \LogicException('Precedence adaptation must have trait');
}
$this->type = self::TYPE_PRECEDENCE;
}
if ($this->type !== self::TYPE_PRECEDENCE) {
throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider');
}
foreach ($traits as $trait) {
$this->insteadof[] = BuilderHelpers::normalizeName($trait);
}
return $this;
}
protected function setModifier(int $modifier) {
if ($this->type === self::TYPE_UNDEFINED) {
$this->type = self::TYPE_ALIAS;
}
if ($this->type !== self::TYPE_ALIAS) {
throw new \LogicException('Cannot set access modifier for not alias adaptation buider');
}
if (is_null($this->modifier)) {
$this->modifier = $modifier;
} else {
throw new \LogicException('Multiple access type modifiers are not allowed');
}
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
switch ($this->type) {
case self::TYPE_ALIAS:
return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias);
case self::TYPE_PRECEDENCE:
return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof);
default:
throw new \LogicException('Type of adaptation is not defined');
}
}
}

View File

@@ -57,6 +57,34 @@ class BuilderFactory
return new Builder\Trait_($name);
}
/**
* Creates a trait use builder.
*
* @param Node\Name|string ...$traits Trait names
*
* @return Builder\TraitUse The create trait use builder
*/
public function useTrait(...$traits) : Builder\TraitUse {
return new Builder\TraitUse(...$traits);
}
/**
* Creates a trait use adaptation builder.
*
* @param Node\Name|string|null $trait Trait name
* @param Node\Identifier|string $method Method name
*
* @return Builder\TraitUseAdaptation The create trait use adaptation builder
*/
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
if (is_null($method)) {
$method = $trait;
$trait = null;
}
return new Builder\TraitUseAdaptation($trait, $method);
}
/**
* Creates a method builder.
*
@@ -104,14 +132,36 @@ class BuilderFactory
/**
* Creates a namespace/class use builder.
*
* @param string|Node\Name $name Name to alias
* @param Node\Name|string $name Name of the entity (namespace or class) to alias
*
* @return Builder\Use_ The create use builder
* @return Builder\Use_ The created use builder
*/
public function use($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_NORMAL);
}
/**
* Creates a function use builder.
*
* @param Node\Name|string $name Name of the function to alias
*
* @return Builder\Use_ The created use function builder
*/
public function useFunction($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_FUNCTION);
}
/**
* Creates a constant use builder.
*
* @param Node\Name|string $name Name of the const to alias
*
* @return Builder\Use_ The created use const builder
*/
public function useConst($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
}
/**
* Creates node a for a literal value.
*
@@ -123,6 +173,21 @@ class BuilderFactory
return BuilderHelpers::normalizeValue($value);
}
/**
* Creates variable node.
*
* @param string|Expr $name Name
*
* @return Expr\Variable
*/
public function var($name) : Expr\Variable {
if (!\is_string($name) && !$name instanceof Expr) {
throw new \LogicException('Variable name must be string or Expr');
}
return new Expr\Variable($name);
}
/**
* Normalizes an argument list.
*
@@ -218,6 +283,18 @@ class BuilderFactory
public function constFetch($name) : Expr\ConstFetch {
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
}
/**
* Creates a property fetch node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Property name
*
* @return Expr\PropertyFetch
*/
public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch {
return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name));
}
/**
* Creates a class constant fetch node.

View File

@@ -71,7 +71,7 @@ final class BuilderHelpers
return new Identifier($name);
}
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
throw new \LogicException('Expected string or instance of Node\Identifier');
}
/**

View File

@@ -2,7 +2,202 @@
namespace PhpParser\Lexer;
use PhpParser\Error;
use PhpParser\ErrorHandler;
class Emulative extends \PhpParser\Lexer
{
/* No features requiring emulation have been added in PHP > 7.0 */
}
const PHP_7_3 = '7.3.0dev';
/**
* @var array Patches used to reverse changes introduced in the code
*/
private $patches;
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
$this->patches = [];
$preparedCode = $this->prepareCode($code);
if (null === $preparedCode) {
// Nothing to emulate, yay
parent::startLexing($code, $errorHandler);
return;
}
$collector = new ErrorHandler\Collecting();
parent::startLexing($preparedCode, $collector);
$this->fixupTokens();
$errors = $collector->getErrors();
if (!empty($errors)) {
$this->fixupErrors($errors);
foreach ($errors as $error) {
$errorHandler->handleError($error);
}
}
}
/**
* Prepares code for emulation. If nothing has to be emulated null is returned.
*
* @param string $code
* @return null|string
*/
private function prepareCode(string $code) {
if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
return null;
}
if (strpos($code, '<<<') === false) {
// Definitely doesn't contain heredoc/nowdoc
return null;
}
$flexibleDocStringRegex = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
if (!preg_match_all($flexibleDocStringRegex, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found
return null;
}
// Keep track of how much we need to adjust string offsets due to the modifications we
// already made
$posDelta = 0;
foreach ($matches as $match) {
$indentation = $match['indentation'][0];
$indentationStart = $match['indentation'][1];
$separator = $match['separator'][0];
$separatorStart = $match['separator'][1];
if ($indentation === '' && $separator !== '') {
// Ordinary heredoc/nowdoc
continue;
}
if ($indentation !== '') {
// Remove indentation
$indentationLen = strlen($indentation);
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
$this->patches[] = [$indentationStart + $posDelta, 'add', $indentation];
$posDelta -= $indentationLen;
}
if ($separator === '') {
// Insert newline as separator
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
$this->patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
$posDelta += 1;
}
}
if (empty($this->patches)) {
// We did not end up emulating anything
return null;
}
return $code;
}
private function fixupTokens() {
assert(count($this->patches) > 0);
// Load first patch
$patchIdx = 0;
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// We use a manual loop over the tokens, because we modify the array on the fly
$pos = 0;
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
$token = $this->tokens[$i];
if (\is_string($token)) {
// We assume that patches don't apply to string tokens
$pos += \strlen($token);
continue;
}
$len = \strlen($token[1]);
$posDelta = 0;
while ($patchPos >= $pos && $patchPos < $pos + $len) {
$patchTextLen = \strlen($patchText);
if ($patchType === 'remove') {
if ($patchPos === $pos && $patchTextLen === $len) {
// Remove token entirely
array_splice($this->tokens, $i, 1, []);
$i--;
$c--;
} else {
// Remove from token string
$this->tokens[$i][1] = substr_replace(
$token[1], '', $patchPos - $pos + $posDelta, $patchTextLen
);
$posDelta -= $patchTextLen;
}
} elseif ($patchType === 'add') {
// Insert into the token string
$this->tokens[$i][1] = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
);
$posDelta += $patchTextLen;
} else {
assert(false);
}
// Fetch the next patch
$patchIdx++;
if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done
return;
}
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// Multiple patches may apply to the same token. Reload the current one to check
// If the new patch applies
$token = $this->tokens[$i];
}
$pos += $len;
}
// A patch did not apply
assert(false);
}
/**
* Fixup line and position information in errors.
*
* @param Error[] $errors
*/
private function fixupErrors(array $errors) {
foreach ($errors as $error) {
$attrs = $error->getAttributes();
$posDelta = 0;
$lineDelta = 0;
foreach ($this->patches as $patch) {
list($patchPos, $patchType, $patchText) = $patch;
if ($patchPos >= $attrs['startFilePos']) {
// No longer relevant
break;
}
if ($patchType === 'add') {
$posDelta += strlen($patchText);
$lineDelta += substr_count($patchText, "\n");
} else {
$posDelta -= strlen($patchText);
$lineDelta -= substr_count($patchText, "\n");
}
}
$attrs['startFilePos'] += $posDelta;
$attrs['endFilePos'] += $posDelta;
$attrs['startLine'] += $lineDelta;
$attrs['endLine'] += $lineDelta;
$error->setAttributes($attrs);
}
}
}

View File

@@ -10,7 +10,7 @@ class StaticCall extends Expr
{
/** @var Node\Name|Expr Class name */
public $class;
/** @var string|Identifier|Expr Method name */
/** @var Identifier|Expr Method name */
public $name;
/** @var Node\Arg[] Arguments */
public $args;

View File

@@ -134,29 +134,6 @@ class String_ extends Scalar
}
throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
}
/**
* @internal
*
* Parses a constant doc string.
*
* @param string $startToken Doc string start token content (<<<SMTHG)
* @param string $str String token content
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
*
* @return string Parsed string
*/
public static function parseDocString(string $startToken, string $str, bool $parseUnicodeEscape = true) : string {
// strip last newline (thanks tokenizer for sticking it into the string!)
$str = preg_replace('~(\r\n|\n|\r)\z~', '', $str);
// nowdoc string
if (false !== strpos($startToken, '\'')) {
return $str;
}
return self::parseEscapeSequences($str, null, $parseUnicodeEscape);
}
public function getType() : string {
return 'Scalar_String';

View File

@@ -30,17 +30,23 @@ class NodeTraverser implements NodeTraverserInterface
*/
const REMOVE_NODE = 3;
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will not be called as well.
* leaveNode() will be invoked for visitors that has enterNode() method invoked.
*/
const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4;
/** @var NodeVisitor[] Visitors */
protected $visitors;
protected $visitors = [];
/** @var bool Whether traversal should be stopped */
protected $stopTraversal;
/**
* Constructs a node traverser.
*/
public function __construct() {
$this->visitors = [];
// for BC
}
/**
@@ -111,7 +117,9 @@ class NodeTraverser implements NodeTraverserInterface
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
$breakVisitorIndex = null;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
@@ -119,6 +127,10 @@ class NodeTraverser implements NodeTraverserInterface
$subNode = $return;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
$breakVisitorIndex = $visitorIndex;
break;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
@@ -137,8 +149,9 @@ class NodeTraverser implements NodeTraverserInterface
}
}
foreach ($this->visitors as $visitor) {
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->leaveNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
@@ -157,6 +170,10 @@ class NodeTraverser implements NodeTraverserInterface
);
}
}
if ($breakVisitorIndex === $visitorIndex) {
break;
}
}
}
}
@@ -177,7 +194,9 @@ class NodeTraverser implements NodeTraverserInterface
foreach ($nodes as $i => &$node) {
if ($node instanceof Node) {
$traverseChildren = true;
foreach ($this->visitors as $visitor) {
$breakVisitorIndex = null;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
@@ -185,6 +204,10 @@ class NodeTraverser implements NodeTraverserInterface
$node = $return;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
$breakVisitorIndex = $visitorIndex;
break;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
@@ -203,8 +226,9 @@ class NodeTraverser implements NodeTraverserInterface
}
}
foreach ($this->visitors as $visitor) {
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->leaveNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
@@ -229,6 +253,10 @@ class NodeTraverser implements NodeTraverserInterface
);
}
}
if ($breakVisitorIndex === $visitorIndex) {
break;
}
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');

View File

@@ -2264,12 +2264,10 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
436 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
$this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], false), $attrs);
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], false);
},
437 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
$this->semValue = new Scalar\String_('', $attrs);
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], false);
},
438 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
@@ -2405,8 +2403,7 @@ class Php5 extends \PhpParser\ParserAbstract
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
},
482 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
483 => function ($stackPos) {
$this->semValue = array();

View File

@@ -2201,20 +2201,17 @@ class Php7 extends \PhpParser\ParserAbstract
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
445 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
$this->semValue = new Scalar\String_(Scalar\String_::parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)]), $attrs);
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
446 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(2-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(2-1)], $matches); $attrs['docLabel'] = $matches[1];;
$this->semValue = new Scalar\String_('', $attrs);
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], true);
},
447 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
},
448 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = strpos($this->semStack[$stackPos-(3-1)], "'") === false ? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; preg_match('/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/', $this->semStack[$stackPos-(3-1)], $matches); $attrs['docLabel'] = $matches[1];;
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, true); } } $s->value = preg_replace('~(\r\n|\n|\r)\z~', '', $s->value); if ('' === $s->value) array_pop($this->semStack[$stackPos-(3-2)]);; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
449 => function ($stackPos) {
$this->semValue = null;

View File

@@ -9,6 +9,7 @@ namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\Encapsed;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
@@ -710,6 +711,119 @@ abstract class ParserAbstract implements Parser
return new LNumber($num, $attributes);
}
protected function stripIndentation(
string $string, int $indentLen, string $indentChar,
bool $newlineAtStart, bool $newlineAtEnd, array $attributes
) {
if ($indentLen === 0) {
return $string;
}
$start = $newlineAtStart ? '(?:(?<=\n)|\A)' : '(?<=\n)';
$end = $newlineAtEnd ? '(?:(?=[\r\n])|\z)' : '(?=[\r\n])';
$regex = '/' . $start . '([ \t]*)(' . $end . ')?/';
return preg_replace_callback(
$regex,
function ($matches) use ($indentLen, $indentChar, $attributes) {
$prefix = substr($matches[1], 0, $indentLen);
if (false !== strpos($prefix, $indentChar === " " ? "\t" : " ")) {
$this->emitError(new Error(
'Invalid indentation - tabs and spaces cannot be mixed', $attributes
));
} elseif (strlen($prefix) < $indentLen && !isset($matches[2])) {
$this->emitError(new Error(
'Invalid body indentation level ' .
'(expecting an indentation level of at least ' . $indentLen . ')',
$attributes
));
}
return substr($matches[0], strlen($prefix));
},
$string
);
}
protected function parseDocString(
string $startToken, $contents, string $endToken,
array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape
) {
$kind = strpos($startToken, "'") === false
? String_::KIND_HEREDOC : String_::KIND_NOWDOC;
$regex = '/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/';
$result = preg_match($regex, $startToken, $matches);
assert($result === 1);
$label = $matches[1];
$result = preg_match('/\A[ \t]*/', $endToken, $matches);
assert($result === 1);
$indentation = $matches[0];
$attributes['kind'] = $kind;
$attributes['docLabel'] = $label;
$attributes['docIndentation'] = $indentation;
$indentHasSpaces = false !== strpos($indentation, " ");
$indentHasTabs = false !== strpos($indentation, "\t");
if ($indentHasSpaces && $indentHasTabs) {
$this->emitError(new Error(
'Invalid indentation - tabs and spaces cannot be mixed',
$endTokenAttributes
));
// Proceed processing as if this doc string is not indented
$indentation = '';
}
$indentLen = \strlen($indentation);
$indentChar = $indentHasSpaces ? " " : "\t";
if (\is_string($contents)) {
if ($contents === '') {
return new String_('', $attributes);
}
$contents = $this->stripIndentation(
$contents, $indentLen, $indentChar, true, true, $attributes
);
$contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents);
if ($kind === String_::KIND_HEREDOC) {
$contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape);
}
return new String_($contents, $attributes);
} else {
assert(count($contents) > 0);
if (!$contents[0] instanceof Node\Scalar\EncapsedStringPart) {
// If there is no leading encapsed string part, pretend there is an empty one
$this->stripIndentation(
'', $indentLen, $indentChar, true, false, $contents[0]->getAttributes()
);
}
$newContents = [];
foreach ($contents as $i => $part) {
if ($part instanceof Node\Scalar\EncapsedStringPart) {
$isLast = $i === \count($contents) - 1;
$part->value = $this->stripIndentation(
$part->value, $indentLen, $indentChar,
$i === 0, $isLast, $part->getAttributes()
);
$part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape);
if ($isLast) {
$part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value);
}
if ('' === $part->value) {
continue;
}
}
$newContents[] = $part;
}
return new Encapsed($newContents, $attributes);
}
}
protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere
try {