Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1431694
Printer.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
30 KB
Referenced Files
None
Subscribers
None
Printer.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
PHPStan\PhpDocParser\Printer
;
use
LogicException
;
use
PHPStan\PhpDocParser\Ast\Attribute
;
use
PHPStan\PhpDocParser\Ast\Comment
;
use
PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode
;
use
PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode
;
use
PHPStan\PhpDocParser\Ast\Node
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagMethodValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagPropertyValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineAnnotation
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArgument
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArray
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArrayItem
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ParamClosureThisTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ParamOutTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\RequireExtendsTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\RequireImplementsTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode
;
use
PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode
;
use
PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode
;
use
PHPStan\PhpDocParser\Ast\Type\ArrayShapeUnsealedTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\CallableTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode
;
use
PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode
;
use
PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\ConstTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\GenericTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\NullableTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode
;
use
PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode
;
use
PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\ThisTypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\TypeNode
;
use
PHPStan\PhpDocParser\Ast\Type\UnionTypeNode
;
use
PHPStan\PhpDocParser\Lexer\Lexer
;
use
PHPStan\PhpDocParser\Parser\TokenIterator
;
use
function
array_keys
;
use
function
array_map
;
use
function
assert
;
use
function
count
;
use
function
get_class
;
use
function
get_object_vars
;
use
function
implode
;
use
function
in_array
;
use
function
is_array
;
use
function
preg_match_all
;
use
function
sprintf
;
use
function
str_replace
;
use
function
strlen
;
use
function
strpos
;
use
function
trim
;
use
const
PREG_SET_ORDER
;
/**
* Inspired by https://github.com/nikic/PHP-Parser/tree/36a6dcd04e7b0285e8f0868f44bd4927802f7df1
*
* Copyright (c) 2011, Nikita Popov
* All rights reserved.
*/
final
class
Printer
{
/** @var Differ<Node> */
private
Differ
$differ
;
/**
* Map From "{$class}->{$subNode}" to string that should be inserted
* between elements of this list subnode
*
* @var array<string, string>
*/
private
array
$listInsertionMap
=
[
PhpDocNode
::
class
.
'->children'
=>
"
\n
* "
,
UnionTypeNode
::
class
.
'->types'
=>
'|'
,
IntersectionTypeNode
::
class
.
'->types'
=>
'&'
,
ArrayShapeNode
::
class
.
'->items'
=>
', '
,
ObjectShapeNode
::
class
.
'->items'
=>
', '
,
CallableTypeNode
::
class
.
'->parameters'
=>
', '
,
CallableTypeNode
::
class
.
'->templateTypes'
=>
', '
,
GenericTypeNode
::
class
.
'->genericTypes'
=>
', '
,
ConstExprArrayNode
::
class
.
'->items'
=>
', '
,
MethodTagValueNode
::
class
.
'->parameters'
=>
', '
,
DoctrineArray
::
class
.
'->items'
=>
', '
,
DoctrineAnnotation
::
class
.
'->arguments'
=>
', '
,
];
/**
* [$find, $extraLeft, $extraRight]
*
* @var array<string, array{string|null, string, string}>
*/
private
array
$emptyListInsertionMap
=
[
CallableTypeNode
::
class
.
'->parameters'
=>
[
'('
,
''
,
''
],
ArrayShapeNode
::
class
.
'->items'
=>
[
'{'
,
''
,
''
],
ObjectShapeNode
::
class
.
'->items'
=>
[
'{'
,
''
,
''
],
DoctrineArray
::
class
.
'->items'
=>
[
'{'
,
''
,
''
],
DoctrineAnnotation
::
class
.
'->arguments'
=>
[
'('
,
''
,
''
],
];
/** @var array<string, list<class-string<TypeNode>>> */
private
array
$parenthesesMap
=
[
CallableTypeNode
::
class
.
'->returnType'
=>
[
CallableTypeNode
::
class
,
UnionTypeNode
::
class
,
IntersectionTypeNode
::
class
,
],
ArrayTypeNode
::
class
.
'->type'
=>
[
CallableTypeNode
::
class
,
UnionTypeNode
::
class
,
IntersectionTypeNode
::
class
,
ConstTypeNode
::
class
,
NullableTypeNode
::
class
,
],
OffsetAccessTypeNode
::
class
.
'->type'
=>
[
CallableTypeNode
::
class
,
UnionTypeNode
::
class
,
IntersectionTypeNode
::
class
,
NullableTypeNode
::
class
,
],
];
/** @var array<string, list<class-string<TypeNode>>> */
private
array
$parenthesesListMap
=
[
IntersectionTypeNode
::
class
.
'->types'
=>
[
IntersectionTypeNode
::
class
,
UnionTypeNode
::
class
,
NullableTypeNode
::
class
,
],
UnionTypeNode
::
class
.
'->types'
=>
[
IntersectionTypeNode
::
class
,
UnionTypeNode
::
class
,
NullableTypeNode
::
class
,
],
];
public
function
printFormatPreserving
(
PhpDocNode
$node
,
PhpDocNode
$originalNode
,
TokenIterator
$originalTokens
):
string
{
$this
->
differ
=
new
Differ
(
static
function
(
$a
,
$b
)
{
if
(
$a
instanceof
Node
&&
$b
instanceof
Node
)
{
return
$a
===
$b
->
getAttribute
(
Attribute
::
ORIGINAL_NODE
);
}
return
false
;
});
$tokenIndex
=
0
;
$result
=
$this
->
printArrayFormatPreserving
(
$node
->
children
,
$originalNode
->
children
,
$originalTokens
,
$tokenIndex
,
PhpDocNode
::
class
,
'children'
,
);
if
(
$result
!==
null
)
{
return
$result
.
$originalTokens
->
getContentBetween
(
$tokenIndex
,
$originalTokens
->
getTokenCount
());
}
return
$this
->
print
(
$node
);
}
public
function
print
(
Node
$node
):
string
{
if
(
$node
instanceof
PhpDocNode
)
{
return
"/**
\n
*"
.
implode
(
"
\n
*"
,
array_map
(
function
(
PhpDocChildNode
$child
):
string
{
$s
=
$this
->
print
(
$child
);
return
$s
===
''
?
''
:
' '
.
$s
;
},
$node
->
children
,
))
.
"
\n
*/"
;
}
if
(
$node
instanceof
PhpDocTextNode
)
{
return
$node
->
text
;
}
if
(
$node
instanceof
PhpDocTagNode
)
{
if
(
$node
->
value
instanceof
DoctrineTagValueNode
)
{
return
$this
->
print
(
$node
->
value
);
}
return
trim
(
sprintf
(
'%s %s'
,
$node
->
name
,
$this
->
print
(
$node
->
value
)));
}
if
(
$node
instanceof
PhpDocTagValueNode
)
{
return
$this
->
printTagValue
(
$node
);
}
if
(
$node
instanceof
TypeNode
)
{
return
$this
->
printType
(
$node
);
}
if
(
$node
instanceof
ConstExprNode
)
{
return
$this
->
printConstExpr
(
$node
);
}
if
(
$node
instanceof
MethodTagValueParameterNode
)
{
$type
=
$node
->
type
!==
null
?
$this
->
print
(
$node
->
type
)
.
' '
:
''
;
$isReference
=
$node
->
isReference
?
'&'
:
''
;
$isVariadic
=
$node
->
isVariadic
?
'...'
:
''
;
$default
=
$node
->
defaultValue
!==
null
?
' = '
.
$this
->
print
(
$node
->
defaultValue
)
:
''
;
return
"{$type}{$isReference}{$isVariadic}{$node->parameterName}{$default}"
;
}
if
(
$node
instanceof
CallableTypeParameterNode
)
{
$type
=
$this
->
print
(
$node
->
type
)
.
' '
;
$isReference
=
$node
->
isReference
?
'&'
:
''
;
$isVariadic
=
$node
->
isVariadic
?
'...'
:
''
;
$isOptional
=
$node
->
isOptional
?
'='
:
''
;
return
trim
(
"{$type}{$isReference}{$isVariadic}{$node->parameterName}"
)
.
$isOptional
;
}
if
(
$node
instanceof
ArrayShapeUnsealedTypeNode
)
{
if
(
$node
->
keyType
!==
null
)
{
return
sprintf
(
'<%s, %s>'
,
$this
->
printType
(
$node
->
keyType
),
$this
->
printType
(
$node
->
valueType
));
}
return
sprintf
(
'<%s>'
,
$this
->
printType
(
$node
->
valueType
));
}
if
(
$node
instanceof
DoctrineAnnotation
)
{
return
(
string
)
$node
;
}
if
(
$node
instanceof
DoctrineArgument
)
{
return
(
string
)
$node
;
}
if
(
$node
instanceof
DoctrineArray
)
{
return
(
string
)
$node
;
}
if
(
$node
instanceof
DoctrineArrayItem
)
{
return
(
string
)
$node
;
}
if
(
$node
instanceof
ArrayShapeItemNode
)
{
if
(
$node
->
keyName
!==
null
)
{
return
sprintf
(
'%s%s: %s'
,
$this
->
print
(
$node
->
keyName
),
$node
->
optional
?
'?'
:
''
,
$this
->
printType
(
$node
->
valueType
),
);
}
return
$this
->
printType
(
$node
->
valueType
);
}
if
(
$node
instanceof
ObjectShapeItemNode
)
{
if
(
$node
->
keyName
!==
null
)
{
return
sprintf
(
'%s%s: %s'
,
$this
->
print
(
$node
->
keyName
),
$node
->
optional
?
'?'
:
''
,
$this
->
printType
(
$node
->
valueType
),
);
}
return
$this
->
printType
(
$node
->
valueType
);
}
throw
new
LogicException
(
sprintf
(
'Unknown node type %s'
,
get_class
(
$node
)));
}
private
function
printTagValue
(
PhpDocTagValueNode
$node
):
string
{
// only nodes that contain another node are handled here
// the rest falls back on (string) $node
if
(
$node
instanceof
AssertTagMethodValueNode
)
{
$isNegated
=
$node
->
isNegated
?
'!'
:
''
;
$isEquality
=
$node
->
isEquality
?
'='
:
''
;
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$isNegated}{$isEquality}{$type} {$node->parameter}->{$node->method}() {$node->description}"
);
}
if
(
$node
instanceof
AssertTagPropertyValueNode
)
{
$isNegated
=
$node
->
isNegated
?
'!'
:
''
;
$isEquality
=
$node
->
isEquality
?
'='
:
''
;
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$isNegated}{$isEquality}{$type} {$node->parameter}->{$node->property} {$node->description}"
);
}
if
(
$node
instanceof
AssertTagValueNode
)
{
$isNegated
=
$node
->
isNegated
?
'!'
:
''
;
$isEquality
=
$node
->
isEquality
?
'='
:
''
;
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$isNegated}{$isEquality}{$type} {$node->parameter} {$node->description}"
);
}
if
(
$node
instanceof
ExtendsTagValueNode
||
$node
instanceof
ImplementsTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->description}"
);
}
if
(
$node
instanceof
MethodTagValueNode
)
{
$static
=
$node
->
isStatic
?
'static '
:
''
;
$returnType
=
$node
->
returnType
!==
null
?
$this
->
printType
(
$node
->
returnType
)
.
' '
:
''
;
$parameters
=
implode
(
', '
,
array_map
(
fn
(
MethodTagValueParameterNode
$parameter
):
string
=>
$this
->
print
(
$parameter
),
$node
->
parameters
));
$description
=
$node
->
description
!==
''
?
" {$node->description}"
:
''
;
$templateTypes
=
count
(
$node
->
templateTypes
)
>
0
?
'<'
.
implode
(
', '
,
array_map
(
fn
(
TemplateTagValueNode
$templateTag
):
string
=>
$this
->
print
(
$templateTag
),
$node
->
templateTypes
))
.
'>'
:
''
;
return
"{$static}{$returnType}{$node->methodName}{$templateTypes}({$parameters}){$description}"
;
}
if
(
$node
instanceof
MixinTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->description}"
);
}
if
(
$node
instanceof
RequireExtendsTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->description}"
);
}
if
(
$node
instanceof
RequireImplementsTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->description}"
);
}
if
(
$node
instanceof
ParamOutTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->parameterName} {$node->description}"
);
}
if
(
$node
instanceof
ParamTagValueNode
)
{
$reference
=
$node
->
isReference
?
'&'
:
''
;
$variadic
=
$node
->
isVariadic
?
'...'
:
''
;
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$reference}{$variadic}{$node->parameterName} {$node->description}"
);
}
if
(
$node
instanceof
ParamImmediatelyInvokedCallableTagValueNode
)
{
return
trim
(
"{$node->parameterName} {$node->description}"
);
}
if
(
$node
instanceof
ParamLaterInvokedCallableTagValueNode
)
{
return
trim
(
"{$node->parameterName} {$node->description}"
);
}
if
(
$node
instanceof
ParamClosureThisTagValueNode
)
{
return
trim
(
"{$node->type} {$node->parameterName} {$node->description}"
);
}
if
(
$node
instanceof
PureUnlessCallableIsImpureTagValueNode
)
{
return
trim
(
"{$node->parameterName} {$node->description}"
);
}
if
(
$node
instanceof
PropertyTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->propertyName} {$node->description}"
);
}
if
(
$node
instanceof
ReturnTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->description}"
);
}
if
(
$node
instanceof
SelfOutTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
$type
.
' '
.
$node
->
description
);
}
if
(
$node
instanceof
TemplateTagValueNode
)
{
$upperBound
=
$node
->
bound
!==
null
?
' of '
.
$this
->
printType
(
$node
->
bound
)
:
''
;
$lowerBound
=
$node
->
lowerBound
!==
null
?
' super '
.
$this
->
printType
(
$node
->
lowerBound
)
:
''
;
$default
=
$node
->
default
!==
null
?
' = '
.
$this
->
printType
(
$node
->
default
)
:
''
;
return
trim
(
"{$node->name}{$upperBound}{$lowerBound}{$default} {$node->description}"
);
}
if
(
$node
instanceof
ThrowsTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->description}"
);
}
if
(
$node
instanceof
TypeAliasImportTagValueNode
)
{
return
trim
(
"{$node->importedAlias} from "
.
$this
->
printType
(
$node
->
importedFrom
)
.
(
$node
->
importedAs
!==
null
?
" as {$node->importedAs}"
:
''
),
);
}
if
(
$node
instanceof
TypeAliasTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$node->alias} {$type}"
);
}
if
(
$node
instanceof
UsesTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} {$node->description}"
);
}
if
(
$node
instanceof
VarTagValueNode
)
{
$type
=
$this
->
printType
(
$node
->
type
);
return
trim
(
"{$type} "
.
trim
(
"{$node->variableName} {$node->description}"
));
}
return
(
string
)
$node
;
}
private
function
printType
(
TypeNode
$node
):
string
{
if
(
$node
instanceof
ArrayShapeNode
)
{
$items
=
array_map
(
fn
(
ArrayShapeItemNode
$item
):
string
=>
$this
->
print
(
$item
),
$node
->
items
);
if
(!
$node
->
sealed
)
{
$items
[]
=
'...'
.
(
$node
->
unsealedType
===
null
?
''
:
$this
->
print
(
$node
->
unsealedType
));
}
return
$node
->
kind
.
'{'
.
implode
(
', '
,
$items
)
.
'}'
;
}
if
(
$node
instanceof
ArrayTypeNode
)
{
return
$this
->
printOffsetAccessType
(
$node
->
type
)
.
'[]'
;
}
if
(
$node
instanceof
CallableTypeNode
)
{
if
(
$node
->
returnType
instanceof
CallableTypeNode
||
$node
->
returnType
instanceof
UnionTypeNode
||
$node
->
returnType
instanceof
IntersectionTypeNode
)
{
$returnType
=
$this
->
wrapInParentheses
(
$node
->
returnType
);
}
else
{
$returnType
=
$this
->
printType
(
$node
->
returnType
);
}
$template
=
$node
->
templateTypes
!==
[]
?
'<'
.
implode
(
', '
,
array_map
(
fn
(
TemplateTagValueNode
$templateNode
):
string
=>
$this
->
print
(
$templateNode
),
$node
->
templateTypes
))
.
'>'
:
''
;
$parameters
=
implode
(
', '
,
array_map
(
fn
(
CallableTypeParameterNode
$parameterNode
):
string
=>
$this
->
print
(
$parameterNode
),
$node
->
parameters
));
return
"{$node->identifier}{$template}({$parameters}): {$returnType}"
;
}
if
(
$node
instanceof
ConditionalTypeForParameterNode
)
{
return
sprintf
(
'(%s %s %s ? %s : %s)'
,
$node
->
parameterName
,
$node
->
negated
?
'is not'
:
'is'
,
$this
->
printType
(
$node
->
targetType
),
$this
->
printType
(
$node
->
if
),
$this
->
printType
(
$node
->
else
),
);
}
if
(
$node
instanceof
ConditionalTypeNode
)
{
return
sprintf
(
'(%s %s %s ? %s : %s)'
,
$this
->
printType
(
$node
->
subjectType
),
$node
->
negated
?
'is not'
:
'is'
,
$this
->
printType
(
$node
->
targetType
),
$this
->
printType
(
$node
->
if
),
$this
->
printType
(
$node
->
else
),
);
}
if
(
$node
instanceof
ConstTypeNode
)
{
return
$this
->
printConstExpr
(
$node
->
constExpr
);
}
if
(
$node
instanceof
GenericTypeNode
)
{
$genericTypes
=
[];
foreach
(
$node
->
genericTypes
as
$index
=>
$type
)
{
$variance
=
$node
->
variances
[
$index
]
??
GenericTypeNode
::
VARIANCE_INVARIANT
;
if
(
$variance
===
GenericTypeNode
::
VARIANCE_INVARIANT
)
{
$genericTypes
[]
=
$this
->
printType
(
$type
);
}
elseif
(
$variance
===
GenericTypeNode
::
VARIANCE_BIVARIANT
)
{
$genericTypes
[]
=
'*'
;
}
else
{
$genericTypes
[]
=
sprintf
(
'%s %s'
,
$variance
,
$this
->
print
(
$type
));
}
}
return
$node
->
type
.
'<'
.
implode
(
', '
,
$genericTypes
)
.
'>'
;
}
if
(
$node
instanceof
IdentifierTypeNode
)
{
return
$node
->
name
;
}
if
(
$node
instanceof
IntersectionTypeNode
||
$node
instanceof
UnionTypeNode
)
{
$items
=
[];
foreach
(
$node
->
types
as
$type
)
{
if
(
$type
instanceof
IntersectionTypeNode
||
$type
instanceof
UnionTypeNode
||
$type
instanceof
NullableTypeNode
)
{
$items
[]
=
$this
->
wrapInParentheses
(
$type
);
continue
;
}
$items
[]
=
$this
->
printType
(
$type
);
}
return
implode
(
$node
instanceof
IntersectionTypeNode
?
'&'
:
'|'
,
$items
);
}
if
(
$node
instanceof
InvalidTypeNode
)
{
return
(
string
)
$node
;
}
if
(
$node
instanceof
NullableTypeNode
)
{
if
(
$node
->
type
instanceof
IntersectionTypeNode
||
$node
->
type
instanceof
UnionTypeNode
)
{
return
'?('
.
$this
->
printType
(
$node
->
type
)
.
')'
;
}
return
'?'
.
$this
->
printType
(
$node
->
type
);
}
if
(
$node
instanceof
ObjectShapeNode
)
{
$items
=
array_map
(
fn
(
ObjectShapeItemNode
$item
):
string
=>
$this
->
print
(
$item
),
$node
->
items
);
return
'object{'
.
implode
(
', '
,
$items
)
.
'}'
;
}
if
(
$node
instanceof
OffsetAccessTypeNode
)
{
return
$this
->
printOffsetAccessType
(
$node
->
type
)
.
'['
.
$this
->
printType
(
$node
->
offset
)
.
']'
;
}
if
(
$node
instanceof
ThisTypeNode
)
{
return
(
string
)
$node
;
}
throw
new
LogicException
(
sprintf
(
'Unknown node type %s'
,
get_class
(
$node
)));
}
private
function
wrapInParentheses
(
TypeNode
$node
):
string
{
return
'('
.
$this
->
printType
(
$node
)
.
')'
;
}
private
function
printOffsetAccessType
(
TypeNode
$type
):
string
{
if
(
$type
instanceof
CallableTypeNode
||
$type
instanceof
UnionTypeNode
||
$type
instanceof
IntersectionTypeNode
||
$type
instanceof
NullableTypeNode
)
{
return
$this
->
wrapInParentheses
(
$type
);
}
return
$this
->
printType
(
$type
);
}
private
function
printConstExpr
(
ConstExprNode
$node
):
string
{
// this is fine - ConstExprNode classes do not contain nodes that need smart printer logic
return
(
string
)
$node
;
}
/**
* @param Node[] $nodes
* @param Node[] $originalNodes
*/
private
function
printArrayFormatPreserving
(
array
$nodes
,
array
$originalNodes
,
TokenIterator
$originalTokens
,
int
&
$tokenIndex
,
string
$parentNodeClass
,
string
$subNodeName
):
?
string
{
$diff
=
$this
->
differ
->
diffWithReplacements
(
$originalNodes
,
$nodes
);
$mapKey
=
$parentNodeClass
.
'->'
.
$subNodeName
;
$insertStr
=
$this
->
listInsertionMap
[
$mapKey
]
??
null
;
$result
=
''
;
$beforeFirstKeepOrReplace
=
true
;
$delayedAdd
=
[];
$insertNewline
=
false
;
[
$isMultiline
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
]
=
$this
->
isMultiline
(
$tokenIndex
,
$originalNodes
,
$originalTokens
);
if
(
$insertStr
===
"
\n
* "
)
{
$insertStr
=
sprintf
(
'%s%s*%s'
,
$originalTokens
->
getDetectedNewline
()
??
"
\n
"
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
}
foreach
(
$diff
as
$i
=>
$diffElem
)
{
$diffType
=
$diffElem
->
type
;
$arrItem
=
$diffElem
->
new
;
$origArrayItem
=
$diffElem
->
old
;
if
(
$diffType
===
DiffElem
::
TYPE_KEEP
||
$diffType
===
DiffElem
::
TYPE_REPLACE
)
{
$beforeFirstKeepOrReplace
=
false
;
if
(!
$arrItem
instanceof
Node
||
!
$origArrayItem
instanceof
Node
)
{
return
null
;
}
/** @var int $itemStartPos */
$itemStartPos
=
$origArrayItem
->
getAttribute
(
Attribute
::
START_INDEX
);
/** @var int $itemEndPos */
$itemEndPos
=
$origArrayItem
->
getAttribute
(
Attribute
::
END_INDEX
);
if
(
$itemStartPos
<
0
||
$itemEndPos
<
0
||
$itemStartPos
<
$tokenIndex
)
{
throw
new
LogicException
();
}
$comments
=
$arrItem
->
getAttribute
(
Attribute
::
COMMENTS
)
??
[];
$origComments
=
$origArrayItem
->
getAttribute
(
Attribute
::
COMMENTS
)
??
[];
$commentStartPos
=
count
(
$origComments
)
>
0
?
$origComments
[
0
]->
startIndex
:
$itemStartPos
;
assert
(
$commentStartPos
>=
0
);
$result
.=
$originalTokens
->
getContentBetween
(
$tokenIndex
,
$itemStartPos
);
if
(
count
(
$delayedAdd
)
>
0
)
{
foreach
(
$delayedAdd
as
$delayedAddNode
)
{
$parenthesesNeeded
=
isset
(
$this
->
parenthesesListMap
[
$mapKey
])
&&
in_array
(
get_class
(
$delayedAddNode
),
$this
->
parenthesesListMap
[
$mapKey
],
true
);
if
(
$parenthesesNeeded
)
{
$result
.=
'('
;
}
if
(
$insertNewline
)
{
$delayedAddComments
=
$delayedAddNode
->
getAttribute
(
Attribute
::
COMMENTS
)
??
[];
if
(
count
(
$delayedAddComments
)
>
0
)
{
$result
.=
$this
->
printComments
(
$delayedAddComments
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
$result
.=
sprintf
(
'%s%s*%s'
,
$originalTokens
->
getDetectedNewline
()
??
"
\n
"
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
}
}
$result
.=
$this
->
printNodeFormatPreserving
(
$delayedAddNode
,
$originalTokens
);
if
(
$parenthesesNeeded
)
{
$result
.=
')'
;
}
if
(
$insertNewline
)
{
$result
.=
$insertStr
.
sprintf
(
'%s%s*%s'
,
$originalTokens
->
getDetectedNewline
()
??
"
\n
"
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
}
else
{
$result
.=
$insertStr
;
}
}
$delayedAdd
=
[];
}
$parenthesesNeeded
=
isset
(
$this
->
parenthesesListMap
[
$mapKey
])
&&
in_array
(
get_class
(
$arrItem
),
$this
->
parenthesesListMap
[
$mapKey
],
true
)
&&
!
in_array
(
get_class
(
$origArrayItem
),
$this
->
parenthesesListMap
[
$mapKey
],
true
);
$addParentheses
=
$parenthesesNeeded
&&
!
$originalTokens
->
hasParentheses
(
$itemStartPos
,
$itemEndPos
);
if
(
$addParentheses
)
{
$result
.=
'('
;
}
if
(
$comments
!==
$origComments
)
{
if
(
count
(
$comments
)
>
0
)
{
$result
.=
$this
->
printComments
(
$comments
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
$result
.=
sprintf
(
'%s%s*%s'
,
$originalTokens
->
getDetectedNewline
()
??
"
\n
"
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
}
}
$result
.=
$this
->
printNodeFormatPreserving
(
$arrItem
,
$originalTokens
);
if
(
$addParentheses
)
{
$result
.=
')'
;
}
$tokenIndex
=
$itemEndPos
+
1
;
}
elseif
(
$diffType
===
DiffElem
::
TYPE_ADD
)
{
if
(
$insertStr
===
null
)
{
return
null
;
}
if
(!
$arrItem
instanceof
Node
)
{
return
null
;
}
if
(
$insertStr
===
', '
&&
$isMultiline
||
count
(
$arrItem
->
getAttribute
(
Attribute
::
COMMENTS
)
??
[])
>
0
)
{
$insertStr
=
','
;
$insertNewline
=
true
;
}
if
(
$beforeFirstKeepOrReplace
)
{
// Will be inserted at the next "replace" or "keep" element
$delayedAdd
[]
=
$arrItem
;
continue
;
}
/** @var int $itemEndPos */
$itemEndPos
=
$tokenIndex
-
1
;
if
(
$insertNewline
)
{
$comments
=
$arrItem
->
getAttribute
(
Attribute
::
COMMENTS
)
??
[];
$result
.=
$insertStr
;
if
(
count
(
$comments
)
>
0
)
{
$result
.=
sprintf
(
'%s%s*%s'
,
$originalTokens
->
getDetectedNewline
()
??
"
\n
"
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
$result
.=
$this
->
printComments
(
$comments
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
}
$result
.=
sprintf
(
'%s%s*%s'
,
$originalTokens
->
getDetectedNewline
()
??
"
\n
"
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
}
else
{
$result
.=
$insertStr
;
}
$parenthesesNeeded
=
isset
(
$this
->
parenthesesListMap
[
$mapKey
])
&&
in_array
(
get_class
(
$arrItem
),
$this
->
parenthesesListMap
[
$mapKey
],
true
);
if
(
$parenthesesNeeded
)
{
$result
.=
'('
;
}
$result
.=
$this
->
printNodeFormatPreserving
(
$arrItem
,
$originalTokens
);
if
(
$parenthesesNeeded
)
{
$result
.=
')'
;
}
$tokenIndex
=
$itemEndPos
+
1
;
}
elseif
(
$diffType
===
DiffElem
::
TYPE_REMOVE
)
{
if
(!
$origArrayItem
instanceof
Node
)
{
return
null
;
}
/** @var int $itemStartPos */
$itemStartPos
=
$origArrayItem
->
getAttribute
(
Attribute
::
START_INDEX
);
/** @var int $itemEndPos */
$itemEndPos
=
$origArrayItem
->
getAttribute
(
Attribute
::
END_INDEX
);
if
(
$itemStartPos
<
0
||
$itemEndPos
<
0
)
{
throw
new
LogicException
();
}
if
(
$i
===
0
)
{
// If we're removing from the start, keep the tokens before the node and drop those after it,
// instead of the other way around.
$originalTokensArray
=
$originalTokens
->
getTokens
();
for
(
$j
=
$tokenIndex
;
$j
<
$itemStartPos
;
$j
++)
{
if
(
$originalTokensArray
[
$j
][
Lexer
::
TYPE_OFFSET
]
===
Lexer
::
TOKEN_PHPDOC_EOL
)
{
break
;
}
$result
.=
$originalTokensArray
[
$j
][
Lexer
::
VALUE_OFFSET
];
}
}
$tokenIndex
=
$itemEndPos
+
1
;
}
}
if
(
count
(
$delayedAdd
)
>
0
)
{
if
(!
isset
(
$this
->
emptyListInsertionMap
[
$mapKey
]))
{
return
null
;
}
[
$findToken
,
$extraLeft
,
$extraRight
]
=
$this
->
emptyListInsertionMap
[
$mapKey
];
if
(
$findToken
!==
null
)
{
$originalTokensArray
=
$originalTokens
->
getTokens
();
for
(;
$tokenIndex
<
count
(
$originalTokensArray
);
$tokenIndex
++)
{
$result
.=
$originalTokensArray
[
$tokenIndex
][
Lexer
::
VALUE_OFFSET
];
if
(
$originalTokensArray
[
$tokenIndex
][
Lexer
::
VALUE_OFFSET
]
!==
$findToken
)
{
continue
;
}
$tokenIndex
++;
break
;
}
}
$first
=
true
;
$result
.=
$extraLeft
;
foreach
(
$delayedAdd
as
$delayedAddNode
)
{
if
(!
$first
)
{
$result
.=
$insertStr
;
if
(
$insertNewline
)
{
$result
.=
sprintf
(
'%s%s*%s'
,
$originalTokens
->
getDetectedNewline
()
??
"
\n
"
,
$beforeAsteriskIndent
,
$afterAsteriskIndent
);
}
}
$result
.=
$this
->
printNodeFormatPreserving
(
$delayedAddNode
,
$originalTokens
);
$first
=
false
;
}
$result
.=
$extraRight
;
}
return
$result
;
}
/**
* @param list<Comment> $comments
*/
private
function
printComments
(
array
$comments
,
string
$beforeAsteriskIndent
,
string
$afterAsteriskIndent
):
string
{
$formattedComments
=
[];
foreach
(
$comments
as
$comment
)
{
$formattedComments
[]
=
str_replace
(
"
\n
"
,
"
\n
"
.
$beforeAsteriskIndent
.
'*'
.
$afterAsteriskIndent
,
$comment
->
getReformattedText
());
}
return
implode
(
"
\n
$beforeAsteriskIndent*$afterAsteriskIndent"
,
$formattedComments
);
}
/**
* @param array<Node|null> $nodes
* @return array{bool, string, string}
*/
private
function
isMultiline
(
int
$initialIndex
,
array
$nodes
,
TokenIterator
$originalTokens
):
array
{
$isMultiline
=
count
(
$nodes
)
>
1
;
$pos
=
$initialIndex
;
$allText
=
''
;
/** @var Node|null $node */
foreach
(
$nodes
as
$node
)
{
if
(!
$node
instanceof
Node
)
{
continue
;
}
$endPos
=
$node
->
getAttribute
(
Attribute
::
END_INDEX
)
+
1
;
$text
=
$originalTokens
->
getContentBetween
(
$pos
,
$endPos
);
$allText
.=
$text
;
if
(
strpos
(
$text
,
"
\n
"
)
===
false
)
{
// We require that a newline is present between *every* item. If the formatting
// is inconsistent, with only some items having newlines, we don't consider it
// as multiline
$isMultiline
=
false
;
}
$pos
=
$endPos
;
}
$c
=
preg_match_all
(
'~
\n
(?<before>[
\\
x09
\\
x20]*)
\*
(?<after>
\\
x20*)~'
,
$allText
,
$matches
,
PREG_SET_ORDER
);
if
(
$c
===
0
)
{
return
[
$isMultiline
,
' '
,
' '
];
}
$before
=
''
;
$after
=
''
;
foreach
(
$matches
as
$match
)
{
if
(
strlen
(
$match
[
'before'
])
>
strlen
(
$before
))
{
$before
=
$match
[
'before'
];
}
if
(
strlen
(
$match
[
'after'
])
<=
strlen
(
$after
))
{
continue
;
}
$after
=
$match
[
'after'
];
}
$before
=
strlen
(
$before
)
===
0
?
' '
:
$before
;
$after
=
strlen
(
$after
)
===
0
?
' '
:
$after
;
return
[
$isMultiline
,
$before
,
$after
];
}
private
function
printNodeFormatPreserving
(
Node
$node
,
TokenIterator
$originalTokens
):
string
{
/** @var Node|null $originalNode */
$originalNode
=
$node
->
getAttribute
(
Attribute
::
ORIGINAL_NODE
);
if
(
$originalNode
===
null
)
{
return
$this
->
print
(
$node
);
}
$class
=
get_class
(
$node
);
if
(
$class
!==
get_class
(
$originalNode
))
{
throw
new
LogicException
();
}
$startPos
=
$originalNode
->
getAttribute
(
Attribute
::
START_INDEX
);
$endPos
=
$originalNode
->
getAttribute
(
Attribute
::
END_INDEX
);
if
(
$startPos
<
0
||
$endPos
<
0
)
{
throw
new
LogicException
();
}
$result
=
''
;
$pos
=
$startPos
;
$subNodeNames
=
array_keys
(
get_object_vars
(
$node
));
foreach
(
$subNodeNames
as
$subNodeName
)
{
$subNode
=
$node
->
$subNodeName
;
$origSubNode
=
$originalNode
->
$subNodeName
;
if
(
(!
$subNode
instanceof
Node
&&
$subNode
!==
null
)
||
(!
$origSubNode
instanceof
Node
&&
$origSubNode
!==
null
)
)
{
if
(
$subNode
===
$origSubNode
)
{
// Unchanged, can reuse old code
continue
;
}
if
(
is_array
(
$subNode
)
&&
is_array
(
$origSubNode
))
{
// Array subnode changed, we might be able to reconstruct it
$listResult
=
$this
->
printArrayFormatPreserving
(
$subNode
,
$origSubNode
,
$originalTokens
,
$pos
,
$class
,
$subNodeName
,
);
if
(
$listResult
===
null
)
{
return
$this
->
print
(
$node
);
}
$result
.=
$listResult
;
continue
;
}
return
$this
->
print
(
$node
);
}
if
(
$origSubNode
===
null
)
{
if
(
$subNode
===
null
)
{
// Both null, nothing to do
continue
;
}
return
$this
->
print
(
$node
);
}
$subStartPos
=
$origSubNode
->
getAttribute
(
Attribute
::
START_INDEX
);
$subEndPos
=
$origSubNode
->
getAttribute
(
Attribute
::
END_INDEX
);
if
(
$subStartPos
<
0
||
$subEndPos
<
0
)
{
throw
new
LogicException
();
}
if
(
$subEndPos
<
$subStartPos
)
{
return
$this
->
print
(
$node
);
}
if
(
$subNode
===
null
)
{
return
$this
->
print
(
$node
);
}
$result
.=
$originalTokens
->
getContentBetween
(
$pos
,
$subStartPos
);
$mapKey
=
get_class
(
$node
)
.
'->'
.
$subNodeName
;
$parenthesesNeeded
=
isset
(
$this
->
parenthesesMap
[
$mapKey
])
&&
in_array
(
get_class
(
$subNode
),
$this
->
parenthesesMap
[
$mapKey
],
true
);
if
(
$subNode
->
getAttribute
(
Attribute
::
ORIGINAL_NODE
)
!==
null
)
{
$parenthesesNeeded
=
$parenthesesNeeded
&&
!
in_array
(
get_class
(
$subNode
->
getAttribute
(
Attribute
::
ORIGINAL_NODE
)),
$this
->
parenthesesMap
[
$mapKey
],
true
);
}
$addParentheses
=
$parenthesesNeeded
&&
!
$originalTokens
->
hasParentheses
(
$subStartPos
,
$subEndPos
);
if
(
$addParentheses
)
{
$result
.=
'('
;
}
$result
.=
$this
->
printNodeFormatPreserving
(
$subNode
,
$originalTokens
);
if
(
$addParentheses
)
{
$result
.=
')'
;
}
$pos
=
$subEndPos
+
1
;
}
return
$result
.
$originalTokens
->
getContentBetween
(
$pos
,
$endPos
+
1
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 20:58 (1 d, 11 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
72/19/53219858cdbafb9d05c326d8ea69
Default Alt Text
Printer.php (30 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment