├── .gitignore ├── docs ├── logo.png └── logo_128px.png ├── generate-docs.sh ├── .travis.yml ├── src ├── Objects │ ├── TraitNode.php │ ├── InterfaceStatementNode.php │ ├── ClassStatementNode.php │ ├── TraitAdaptationStatementNode.php │ ├── NameExpressionNode.php │ ├── ClassNameScalarNode.php │ ├── InterfaceMethodNode.php │ ├── ClassMemberLookupNode.php │ ├── ClassConstantLookupNode.php │ ├── TraitMethodReferenceNode.php │ ├── NewNode.php │ ├── TraitPrecedenceNode.php │ ├── TraitAliasNode.php │ ├── TraitUseNode.php │ ├── ModifiersNode.php │ ├── VisibilityTrait.php │ ├── ObjectPropertyNode.php │ ├── MethodTrait.php │ ├── ClassMethodCallNode.php │ └── ObjectMethodCallNode.php ├── Types │ ├── ArrayElementNode.php │ ├── HeredocNode.php │ ├── StringVariableNode.php │ ├── InterpolatedStringNode.php │ ├── FloatNode.php │ ├── IntegerNode.php │ ├── ScalarNode.php │ ├── BooleanNode.php │ ├── NullNode.php │ ├── TrueNode.php │ ├── FalseNode.php │ ├── ArrayPairNode.php │ └── StringNode.php ├── BlankStatementNode.php ├── Functions │ ├── EvalNode.php │ ├── EmptyNode.php │ ├── IssetNode.php │ ├── UnsetNode.php │ ├── HaltCompilerNode.php │ ├── DefineNode.php │ ├── ListNode.php │ ├── LexicalVariableNode.php │ ├── CallbackCallNode.php │ ├── FunctionCallNode.php │ ├── ParameterNodeCollection.php │ ├── CallNode.php │ ├── FunctionDeclarationNode.php │ ├── FunctionTrait.php │ └── ArgumentTrait.php ├── Operators │ ├── AddNode.php │ ├── CloneNode.php │ ├── FloatCastNode.php │ ├── PrintNode.php │ ├── ArrayCastNode.php │ ├── CastNode.php │ ├── ConcatNode.php │ ├── DivideNode.php │ ├── ElvisNode.php │ ├── EqualNode.php │ ├── ObjectCastNode.php │ ├── PowerNode.php │ ├── StringCastNode.php │ ├── UnsetCastNode.php │ ├── AssignNode.php │ ├── BooleanCastNode.php │ ├── IntegerCastNode.php │ ├── ModulusNode.php │ ├── AddAssignNode.php │ ├── BitwiseOrNode.php │ ├── InstanceOfNode.php │ ├── LogicalOrNode.php │ ├── MultiplyNode.php │ ├── NegateNode.php │ ├── NotEqualNode.php │ ├── SubtractNode.php │ ├── BitwiseAndNode.php │ ├── BitwiseNotNode.php │ ├── BitwiseXorNode.php │ ├── BooleanAndNode.php │ ├── BooleanOrNode.php │ ├── ConcatAssignNode.php │ ├── DivideAssignNode.php │ ├── IdenticalNode.php │ ├── LessThanNode.php │ ├── LogicalAndNode.php │ ├── LogicalXorNode.php │ ├── PlusNode.php │ ├── PowerAssignNode.php │ ├── ModulusAssignNode.php │ ├── MultiplyAssignNode.php │ ├── PreDecrementNode.php │ ├── PreIncrementNode.php │ ├── SubtractAssignNode.php │ ├── BitwiseOrAssignNode.php │ ├── GreaterThanNode.php │ ├── NotIdenticalNode.php │ ├── PostDecrementNode.php │ ├── PostIncrementNode.php │ ├── BitwiseAndAssignNode.php │ ├── BitwiseXorAssignNode.php │ ├── SuppressWarningNode.php │ ├── BitwiseShiftLeftNode.php │ ├── AssignReferenceNode.php │ ├── BitwiseShiftRightNode.php │ ├── LessThanOrEqualToNode.php │ ├── BitwiseShiftLeftAssignNode.php │ ├── BitwiseShiftRightAssignNode.php │ ├── GreaterThanOrEqualToNode.php │ ├── BooleanNotNode.php │ ├── UnaryOperationNode.php │ ├── BinaryOperationNode.php │ └── TernaryOperationNode.php ├── PartialNode.php ├── ControlStructures │ ├── IncludeNode.php │ ├── RequireNode.php │ ├── RequireOnceNode.php │ ├── IncludeOnceNode.php │ ├── GotoLabelNode.php │ ├── GotoStatementNode.php │ ├── ExitNode.php │ ├── DefaultNode.php │ ├── ImportNode.php │ ├── ContinueStatementNode.php │ ├── BreakStatementNode.php │ ├── DeclareDirectiveNode.php │ ├── CaseNode.php │ ├── WhileNode.php │ ├── SwitchNode.php │ ├── ReturnStatementNode.php │ ├── DeclareNode.php │ ├── AltSyntaxTrait.php │ ├── DoWhileNode.php │ ├── ElseIfNode.php │ ├── ForeachNode.php │ ├── ForNode.php │ └── IfNode.php ├── BacktickNode.php ├── TemplateNode.php ├── PartialCommentNode.php ├── Generators │ ├── YieldStatementNode.php │ └── YieldNode.php ├── HiddenNode.php ├── Variables │ ├── VariableExpressionNode.php │ ├── VariableVariableNode.php │ ├── CompoundVariableNode.php │ ├── ReferenceVariableNode.php │ ├── GlobalStatementNode.php │ ├── StaticVariableNode.php │ ├── VariableNode.php │ └── StaticVariableStatementNode.php ├── Constants │ ├── DirMagicConstantNode.php │ ├── ClassMagicConstantNode.php │ ├── FileMagicConstantNode.php │ ├── LineMagicConstantNode.php │ ├── TraitMagicConstantNode.php │ ├── MethodMagicConstantNode.php │ ├── FunctionMagicConstantNode.php │ ├── NamespaceMagicConstantNode.php │ ├── ConstantDeclarationNode.php │ ├── ConstantDeclarationStatementNode.php │ └── ConstantNode.php ├── UnsetStatementNode.php ├── ExpressionStatementNode.php ├── Exceptions │ ├── ThrowStatementNode.php │ ├── CatchNode.php │ └── TryCatchNode.php ├── ParenthesisNode.php ├── ParenTrait.php ├── VisitorInterface.php ├── UncommentTrait.php ├── SplatNode.php ├── EchoStatementNode.php ├── EchoTagStatementNode.php ├── Namespaces │ ├── UseDeclarationBlockNode.php │ ├── IdentifierNameTrait.php │ ├── NamespaceNode.php │ └── UseDeclarationStatementNode.php ├── ParserException.php ├── WhitespaceNode.php ├── ExpressionNode.php ├── FileUtil.php ├── DocCommentTrait.php ├── Operator.php ├── VisitorBase.php ├── Types.php ├── FormatterFactory.php ├── ParentNodeInterface.php ├── RootNode.php ├── StatementNode.php ├── StatementBlockNode.php ├── TokenIterator.php ├── LineCommentBlockNode.php ├── TokenNode.php ├── CommentNode.php └── ArrayLookupNode.php ├── tests ├── files │ └── basic.php ├── WhitespaceNodeTest.php ├── TokenNodeTest.php ├── TryCatchNodeTest.php ├── ReturnStatementNodeTest.php ├── FunctionCallNodeTest.php ├── BooleanNotNodeTest.php ├── VariableNodeTest.php ├── StatementBlockNodeTest.php ├── TokenIteratorTest.php ├── DocCommentTraitTest.php ├── StringNodeTest.php ├── LineCommentBlockNodeTest.php ├── ClassMethodNodeTest.php ├── ObjectPropertyNodeTest.php ├── BinaryOperationNodeTest.php ├── DocCommentTest.php ├── BooleanNodeTest.php ├── ObjectMethodCallNodeTest.php ├── NamespaceNodeTest.php ├── IdentifierNameTraitTest.php ├── RootNodeTest.php ├── InterfaceNodeTest.php ├── ClassMemberNodeTest.php ├── ArrayLookupNodeTest.php ├── StatementNodeTest.php ├── UseDeclarationStatementNodeTest.php ├── FunctionDeclarationNodeTest.php ├── ParameterTraitTest.php ├── ArgumentTraitTest.php └── AnonymousFunctionNodeTest.php ├── config ├── drupal.json └── psr2.json ├── phpunit.xml.dist ├── bin └── format.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | docs/html 3 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grom358/pharborist/HEAD/docs/logo.png -------------------------------------------------------------------------------- /docs/logo_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grom358/pharborist/HEAD/docs/logo_128px.png -------------------------------------------------------------------------------- /generate-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | apigen generate --source src --destination docs/html --access-levels public --charset UTF8 --title Pharborist 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | 8 | script: 9 | - composer install 10 | - phpunit --coverage-text 11 | 12 | -------------------------------------------------------------------------------- /src/Objects/TraitNode.php: -------------------------------------------------------------------------------- 1 | ' comparison. 6 | */ 7 | class GreaterThanNode extends BinaryOperationNode { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/Operators/NotIdenticalNode.php: -------------------------------------------------------------------------------- 1 | >' operation. 6 | */ 7 | class BitwiseShiftRightNode extends BinaryOperationNode { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/Functions/ListNode.php: -------------------------------------------------------------------------------- 1 | >=' assignment. 6 | */ 7 | class BitwiseShiftRightAssignNode extends BinaryOperationNode { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/Objects/ClassStatementNode.php: -------------------------------------------------------------------------------- 1 | =' comparison. 6 | */ 7 | class GreaterThanOrEqualToNode extends BinaryOperationNode { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/HiddenNode.php: -------------------------------------------------------------------------------- 1 | assertEquals(4, $node->getNewlineCount()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Constants/FunctionMagicConstantNode.php: -------------------------------------------------------------------------------- 1 | assertEquals('T_STRING', $id->getTypeName()); 8 | 9 | $comma = new TokenNode(',', ','); 10 | $this->assertEquals(',', $comma->getTypeName()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Types/FloatNode.php: -------------------------------------------------------------------------------- 1 | getText(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Types/IntegerNode.php: -------------------------------------------------------------------------------- 1 | getText(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/UnsetStatementNode.php: -------------------------------------------------------------------------------- 1 | functionCall; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ControlStructures/GotoLabelNode.php: -------------------------------------------------------------------------------- 1 | label; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ControlStructures/GotoStatementNode.php: -------------------------------------------------------------------------------- 1 | label; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ExpressionStatementNode.php: -------------------------------------------------------------------------------- 1 | expression; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ControlStructures/ExitNode.php: -------------------------------------------------------------------------------- 1 | expression; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Exceptions/ThrowStatementNode.php: -------------------------------------------------------------------------------- 1 | expression; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ParenthesisNode.php: -------------------------------------------------------------------------------- 1 | expression; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/TryCatchNodeTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($tryCatch->catches('\InvalidArgumentException')); 10 | $this->assertFalse($tryCatch->catches('\DomainException')); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ./tests 9 | 10 | 11 | 12 | 13 | 14 | ./vendor 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Functions/CallbackCallNode.php: -------------------------------------------------------------------------------- 1 | callback; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ControlStructures/DefaultNode.php: -------------------------------------------------------------------------------- 1 | body; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/ReturnStatementNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('return TRUE;', $ret->getText()); 12 | $this->assertInstanceOf('\Pharborist\Types\TrueNode', $ret->getExpression()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ParenTrait.php: -------------------------------------------------------------------------------- 1 | openParen; 20 | } 21 | 22 | /** 23 | * @return TokenNode 24 | */ 25 | public function getCloseParen() { 26 | return $this->closeParen; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Variables/VariableVariableNode.php: -------------------------------------------------------------------------------- 1 | variable; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/FunctionCallNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('test', $function_call->getName()->getText()); 9 | $function_call->setName('hello'); 10 | $this->assertEquals('hello', $function_call->getName()->getText()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/VisitorInterface.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('\Pharborist\Operators\BooleanNotNode', $not); 11 | $this->assertSame($expr, $not->getOperand()); 12 | $this->assertEquals('!empty($foo)', $not->getText()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ControlStructures/ImportNode.php: -------------------------------------------------------------------------------- 1 | expression; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/UncommentTrait.php: -------------------------------------------------------------------------------- 1 | getCommentText()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Objects/ClassNameScalarNode.php: -------------------------------------------------------------------------------- 1 | className; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Variables/CompoundVariableNode.php: -------------------------------------------------------------------------------- 1 | expression; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Constants/ConstantDeclarationNode.php: -------------------------------------------------------------------------------- 1 | value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bin/format.php: -------------------------------------------------------------------------------- 1 | format($tree); 12 | file_put_contents($filename, $tree->getText()); 13 | } 14 | 15 | $directory = $argv[1]; 16 | 17 | $files = FileUtil::findFiles($directory); 18 | foreach ($files as $filename) { 19 | formatFile($filename); 20 | } 21 | -------------------------------------------------------------------------------- /src/SplatNode.php: -------------------------------------------------------------------------------- 1 | expression; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Types/ScalarNode.php: -------------------------------------------------------------------------------- 1 | variable; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/EchoStatementNode.php: -------------------------------------------------------------------------------- 1 | expressions; 18 | } 19 | 20 | /** 21 | * Return the expressions being echoed. 22 | * 23 | * @return NodeCollection|ExpressionNode[] 24 | */ 25 | public function getExpressions() { 26 | return $this->expressions->getItems(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/EchoTagStatementNode.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class EchoTagStatementNode extends StatementNode { 10 | /** 11 | * @var CommaListNode 12 | */ 13 | protected $expressions; 14 | 15 | /** 16 | * @return CommaListNode 17 | */ 18 | public function getExpressionList() { 19 | return $this->expressions; 20 | } 21 | 22 | /** 23 | * @return NodeCollection|ExpressionNode[] 24 | */ 25 | public function getExpressions() { 26 | return $this->expressions->getItems(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Namespaces/UseDeclarationBlockNode.php: -------------------------------------------------------------------------------- 1 | childrenByInstance('\Pharborist\Namespaces\UseDeclarationStatementNode'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ParserException.php: -------------------------------------------------------------------------------- 1 | assertEquals('form', $var->getName()); 11 | } 12 | 13 | /** 14 | * @depends testGetName 15 | */ 16 | public function testSetName() { 17 | $var = new VariableNode(T_VARIABLE, '$x'); 18 | $var->setName('$y'); 19 | $this->assertEquals('y', $var->getName()); 20 | $var->setName('z'); 21 | $this->assertEquals('z', $var->getName()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grom358/pharborist", 3 | "description": "Pharborist builds a syntax tree for PHP that can be traversed and manipulated.", 4 | "type": "library", 5 | "keywords": [ 6 | "standards", 7 | "syntax" 8 | ], 9 | "license": "GPL", 10 | "authors": [ 11 | { 12 | "name": "Cameron Zemek", 13 | "role": "lead" 14 | } 15 | ], 16 | "autoload": { 17 | "psr-4" : {"Pharborist\\": "src/"} 18 | }, 19 | "require": { 20 | "php": ">=5.4", 21 | "phpdocumentor/reflection-docblock": "2.0.*" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "4.2.*", 25 | "apigen/apigen": "2.8.*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Generators/YieldNode.php: -------------------------------------------------------------------------------- 1 | key; 27 | } 28 | 29 | /** 30 | * @return Node 31 | */ 32 | public function getValue() { 33 | return $this->value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ControlStructures/ContinueStatementNode.php: -------------------------------------------------------------------------------- 1 | level; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ControlStructures/BreakStatementNode.php: -------------------------------------------------------------------------------- 1 | level; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ControlStructures/DeclareDirectiveNode.php: -------------------------------------------------------------------------------- 1 | name; 27 | } 28 | 29 | /** 30 | * @return ExpressionNode 31 | */ 32 | public function getValue() { 33 | return $this->value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Variables/GlobalStatementNode.php: -------------------------------------------------------------------------------- 1 | variables; 22 | } 23 | 24 | /** 25 | * @return NodeCollection|VariableExpressionNode[] 26 | */ 27 | public function getVariables() { 28 | return $this->variables->getItems(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Objects/InterfaceMethodNode.php: -------------------------------------------------------------------------------- 1 | getStatements()[0]->remove(); 21 | return $method_node; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/StatementBlockNodeTest.php: -------------------------------------------------------------------------------- 1 | getThen(); 10 | $this->assertInstanceOf('\Pharborist\StatementBlockNode', $statementBlock); 11 | $this->assertEquals('{ hello(); }', $statementBlock->getText()); 12 | $statementBlock->appendStatement(Parser::parseSnippet('world();')); 13 | $this->assertEquals('{ hello(); world();}', $statementBlock->getText()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ControlStructures/CaseNode.php: -------------------------------------------------------------------------------- 1 | matchOn; 27 | } 28 | 29 | /** 30 | * @return StatementBlockNode 31 | */ 32 | public function getBody() { 33 | return $this->body; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Variables/StaticVariableNode.php: -------------------------------------------------------------------------------- 1 | name; 29 | } 30 | 31 | /** 32 | * @return Node 33 | */ 34 | public function getInitialValue() { 35 | return $this->initialValue; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ControlStructures/WhileNode.php: -------------------------------------------------------------------------------- 1 | condition; 31 | } 32 | 33 | /** 34 | * @return Node 35 | */ 36 | public function getBody() { 37 | return $this->body; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Objects/ClassMemberLookupNode.php: -------------------------------------------------------------------------------- 1 | className; 29 | } 30 | 31 | /** 32 | * @return Node 33 | */ 34 | public function getMemberName() { 35 | return $this->memberName; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Variables/VariableNode.php: -------------------------------------------------------------------------------- 1 | getText(), '$'); 19 | } 20 | 21 | /** 22 | * @param string $name 23 | * The name of the parameter, with or without the leading $. 24 | * 25 | * @return $this 26 | */ 27 | public function setName($name) { 28 | return $this->setText('$' . ltrim($name, '$')); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Objects/ClassConstantLookupNode.php: -------------------------------------------------------------------------------- 1 | className; 28 | } 29 | 30 | /** 31 | * @return TokenNode 32 | */ 33 | public function getConstantName() { 34 | return $this->constantName; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Objects/TraitMethodReferenceNode.php: -------------------------------------------------------------------------------- 1 | traitName; 27 | } 28 | 29 | /** 30 | * @return Node 31 | */ 32 | public function getMethodReference() { 33 | return $this->methodReference; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Operators/BooleanNotNode.php: -------------------------------------------------------------------------------- 1 | addChild(Token::not(), 'operator'); 24 | /** @var \Pharborist\Node $expr */ 25 | $not->addChild($expr->remove(), 'operand'); 26 | return $not; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/ControlStructures/SwitchNode.php: -------------------------------------------------------------------------------- 1 | switchOn; 26 | } 27 | 28 | /** 29 | * @return NodeCollection|CaseNode[] 30 | */ 31 | public function getCases() { 32 | return new NodeCollection($this->childrenByInstance('\Pharborist\StatementNode'), FALSE); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Objects/NewNode.php: -------------------------------------------------------------------------------- 1 | className; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Functions/FunctionCallNode.php: -------------------------------------------------------------------------------- 1 | name; 21 | } 22 | 23 | /** 24 | * @param string|\Pharborist\Namespaces\NameNode $name 25 | * @return $this 26 | */ 27 | public function setName($name) { 28 | if (is_string($name)) { 29 | $name = NameNode::create($name); 30 | } 31 | $this->name->replaceWith($name); 32 | $this->name = $name; 33 | return $this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Variables/StaticVariableStatementNode.php: -------------------------------------------------------------------------------- 1 | variables; 27 | } 28 | 29 | /** 30 | * @return NodeCollection|StaticVariableNode[] 31 | */ 32 | public function getVariables() { 33 | return $this->variables->getItems(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ControlStructures/ReturnStatementNode.php: -------------------------------------------------------------------------------- 1 | getText() . ';'); 27 | } 28 | 29 | /** 30 | * @return ExpressionNode 31 | */ 32 | public function getExpression() { 33 | return $this->expression; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Types/BooleanNode.php: -------------------------------------------------------------------------------- 1 | getConfig('nl'); 12 | $this->newlineCount = substr_count($this->getText(), $nl); 13 | } 14 | } 15 | 16 | public static function create($whitespace) { 17 | return new WhitespaceNode(T_WHITESPACE, $whitespace); 18 | } 19 | 20 | public function setText($text) { 21 | $nl = FormatterFactory::getDefaultFormatter()->getConfig('nl'); 22 | $this->newlineCount = substr_count($text, $nl); 23 | return parent::setText($text); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Objects/TraitPrecedenceNode.php: -------------------------------------------------------------------------------- 1 | traitMethodReference; 29 | } 30 | 31 | /** 32 | * @return NodeCollection|NameNode[] 33 | */ 34 | public function getTraitNames() { 35 | return $this->traitNames->getItems(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Types/NullNode.php: -------------------------------------------------------------------------------- 1 | getConfig('boolean_null_upper'); 22 | $node = new NullNode(); 23 | $node->addChild(NameNode::create($is_upper ? 'NULL' : 'null'), 'constantName'); 24 | return $node; 25 | } 26 | 27 | /** 28 | * @return null 29 | */ 30 | public function toValue() { 31 | return NULL; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Constants/ConstantDeclarationStatementNode.php: -------------------------------------------------------------------------------- 1 | declarations; 25 | } 26 | 27 | /** 28 | * @return NodeCollection|ConstantDeclarationNode[] 29 | */ 30 | public function getDeclarations() { 31 | return $this->declarations->getItems(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/TokenIteratorTest.php: -------------------------------------------------------------------------------- 1 | peek(0); 9 | $this->assertSame($test, $peek); 10 | $this->assertNull($iterator->peek(1)); 11 | 12 | $this->assertFalse($iterator->hasNext()); 13 | $this->assertNull($iterator->next()); 14 | $this->assertEquals(1, $iterator->getLineNumber()); 15 | $this->assertEquals(5, $iterator->getColumnNumber()); 16 | } 17 | 18 | public function testEmpty() { 19 | $iterator = new TokenIterator([]); 20 | $this->assertNull($iterator->peek(0)); 21 | $this->assertEquals(1, $iterator->getLineNumber()); 22 | $this->assertEquals(1, $iterator->getColumnNumber()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/DocCommentTraitTest.php: -------------------------------------------------------------------------------- 1 | setDocComment(DocCommentNode::create('Ni!')); 11 | $comment = $node->getDocComment(); 12 | $this->assertInstanceOf('\Pharborist\DocCommentNode', $comment); 13 | $this->assertEquals('Ni!', $comment->getCommentText()); 14 | } 15 | 16 | public function testChangeDocComment() { 17 | $node = ClassNode::create('Foo'); 18 | $node->setDocComment(DocCommentNode::create('Ni!')); 19 | $node->setDocComment(DocCommentNode::create('Noo!')); 20 | $comment = $node->getDocComment(); 21 | $this->assertInstanceOf('\Pharborist\DocCommentNode', $comment); 22 | $this->assertEquals('Noo!', $comment->getCommentText()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /tests/StringNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('Goodbye, cruel world!', $string->toValue()); 11 | 12 | $string = StringNode::create('"I\'ll harrr to that!"'); 13 | $this->assertEquals("I'll harrr to that!", $string->toValue()); 14 | 15 | // Test escaped characters in double quoted string. 16 | $string = <<<'EOF' 17 | "h\145llo\\n\nw\x6Frld" 18 | EOF; 19 | $this->assertEquals("hello\\n\nworld", StringNode::create($string)->toValue()); 20 | 21 | // Test escaped characters in single quoted string. 22 | $string = <<<'EOF' 23 | 'it\'s \a\\\'live' 24 | EOF; 25 | $this->assertEquals("it's \\a\\'live", StringNode::create($string)->toValue()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/LineCommentBlockNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($line_comment_text, $comment_node->getText()); 18 | 19 | $comment_node->addIndent(' '); 20 | $expected = <<<'EOF' 21 | // hello 22 | // world 23 | 24 | EOF; 25 | $this->assertEquals($expected, $comment_node->getText()); 26 | 27 | $comment_node->addIndent(' '); 28 | $expected = <<<'EOF' 29 | // hello 30 | // world 31 | 32 | EOF; 33 | $this->assertEquals($expected, $comment_node->getText()); 34 | 35 | $comment_node->removeIndent(); 36 | $this->assertEquals($line_comment_text, $comment_node->getText()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ControlStructures/DeclareNode.php: -------------------------------------------------------------------------------- 1 | directives; 31 | } 32 | 33 | /** 34 | * @return NodeCollection|DeclareDirectiveNode[] 35 | */ 36 | public function getDirectives() { 37 | return $this->directives->getItems(); 38 | } 39 | 40 | /** 41 | * @return Node 42 | */ 43 | public function getBody() { 44 | return $this->body; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/ClassMethodNodeTest.php: -------------------------------------------------------------------------------- 1 | getMethod('baz'); 10 | $this->assertEquals('\Foo::baz', $method->getFullyQualifiedName()); 11 | } 12 | 13 | public function testSetName() { 14 | /** @var \Pharborist\Objects\ClassNode $class */ 15 | $class = Parser::parseSnippet('class Foo { public function baz() {} }'); 16 | /** @var \Pharborist\Objects\ClassMethodNode $method */ 17 | $method = $class->getMethod('baz'); 18 | $method->setName('bar'); 19 | $this->assertEquals('bar', $method->getName()->getText()); 20 | $this->assertEquals('public function bar() {}', $method->getText()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Objects/TraitAliasNode.php: -------------------------------------------------------------------------------- 1 | traitMethodReference; 31 | } 32 | 33 | /** 34 | * @return TokenNode 35 | */ 36 | public function getVisibility() { 37 | return $this->visibility; 38 | } 39 | 40 | /** 41 | * @return TokenNode 42 | */ 43 | public function getAlias() { 44 | return $this->alias; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ExpressionNode.php: -------------------------------------------------------------------------------- 1 | baz();` 16 | * - Logical expressions: `($a && $b)` 17 | * - Function call arguments: `foo(--$baz)` 18 | * - Comparisons: `$a > $b` 19 | * 20 | * Expressions are "smaller" than statements, in the sense that a statement 21 | * is usually composed of least one expression. Expressions can contain other 22 | * expressions -- a nested function call, for instance. Any node that implements 23 | * this interface is considered by PHP to be an expression. 24 | */ 25 | interface ExpressionNode extends NodeInterface, ArrayElementNode { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/Types/TrueNode.php: -------------------------------------------------------------------------------- 1 | getConfig('boolean_null_upper'); 24 | $node = new TrueNode(); 25 | $node->addChild(NameNode::create($is_upper ? 'TRUE' : 'true'), 'constantName'); 26 | return $node; 27 | } 28 | 29 | /** 30 | * Gets the boolean value of the node. 31 | * 32 | * @return boolean 33 | */ 34 | public function toValue() { 35 | return TRUE; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Operators/UnaryOperationNode.php: -------------------------------------------------------------------------------- 1 | operator; 28 | } 29 | 30 | /** 31 | * @return ExpressionNode 32 | */ 33 | public function getOperand() { 34 | return $this->operand; 35 | } 36 | 37 | /** 38 | * @param ExpressionNode $operand 39 | * @return $this 40 | */ 41 | public function setOperand(ExpressionNode $operand) { 42 | /** @var \Pharborist\Node $operand */ 43 | $this->operand->replaceWith($operand); 44 | return $this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/FileUtil.php: -------------------------------------------------------------------------------- 1 | $object) { 26 | $files[] = $name; 27 | } 28 | return $files; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Types/FalseNode.php: -------------------------------------------------------------------------------- 1 | getConfig('boolean_null_upper'); 24 | $node = new FalseNode(); 25 | $node->addChild(NameNode::create($is_upper ? 'FALSE' : 'false'), 'constantName'); 26 | return $node; 27 | } 28 | 29 | /** 30 | * Gets the boolean value of the node. 31 | * 32 | * @return boolean 33 | */ 34 | public function toValue() { 35 | return FALSE; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ControlStructures/AltSyntaxTrait.php: -------------------------------------------------------------------------------- 1 | openColon; 27 | } 28 | 29 | /** 30 | * The end keyword delimiter for end of control structure. 31 | * 32 | * @return TokenNode 33 | */ 34 | public function getEndKeyword() { 35 | return $this->endKeyword; 36 | } 37 | 38 | /** 39 | * Return if control structure is using the altnerative syntax. 40 | * 41 | * @return bool 42 | */ 43 | public function isAlterativeSyntax() { 44 | return $this->endKeyword !== NULL; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ControlStructures/DoWhileNode.php: -------------------------------------------------------------------------------- 1 | body; 36 | } 37 | 38 | /** 39 | * @return ExpressionNode 40 | */ 41 | public function getCondition() { 42 | return $this->condition; 43 | } 44 | 45 | /** 46 | * The T_WHILE keyword token. 47 | * 48 | * @return TokenNode 49 | */ 50 | public function getWhileKeyword() { 51 | return $this->whileKeyword; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ControlStructures/ElseIfNode.php: -------------------------------------------------------------------------------- 1 | condition; 36 | } 37 | 38 | /** 39 | * The colon (':') delimiter for body of statements. 40 | * 41 | * @return TokenNode 42 | */ 43 | public function getOpenColon() { 44 | return $this->openColon; 45 | } 46 | 47 | /** 48 | * @return Node 49 | */ 50 | public function getThen() { 51 | return $this->then; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Objects/TraitUseNode.php: -------------------------------------------------------------------------------- 1 | traits->getItems(); 28 | } 29 | 30 | /** 31 | * @return StatementBlockNode 32 | */ 33 | public function getAdaptationBlock() { 34 | return $this->adaptations; 35 | } 36 | 37 | /** 38 | * @return TraitAdaptationStatementNode[] 39 | */ 40 | public function getAdaptations() { 41 | if ($this->adaptations === NULL) { 42 | return new NodeCollection(); 43 | } 44 | return $this->adaptations->getStatements(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Objects/ModifiersNode.php: -------------------------------------------------------------------------------- 1 | abstract; 38 | } 39 | 40 | /** 41 | * @return TokenNode 42 | */ 43 | public function getFinal() { 44 | return $this->final; 45 | } 46 | 47 | /** 48 | * @return TokenNode 49 | */ 50 | public function getStatic() { 51 | return $this->static; 52 | } 53 | 54 | /** 55 | * @return TokenNode 56 | */ 57 | public function getVisibility() { 58 | return $this->visibility; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ControlStructures/ForeachNode.php: -------------------------------------------------------------------------------- 1 | onEach; 40 | } 41 | 42 | /** 43 | * @return Node 44 | */ 45 | public function getKey() { 46 | return $this->key; 47 | } 48 | 49 | /** 50 | * @return Node 51 | */ 52 | public function getValue() { 53 | return $this->value; 54 | } 55 | 56 | /** 57 | * @return Node 58 | */ 59 | public function getBody() { 60 | return $this->body; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Exceptions/CatchNode.php: -------------------------------------------------------------------------------- 1 | exceptionType; 36 | } 37 | 38 | /** 39 | * Returns the variable for the caught exception. 40 | * 41 | * @return VariableNode 42 | */ 43 | public function getVariable() { 44 | return $this->variable; 45 | } 46 | 47 | /** 48 | * @return StatementBlockNode 49 | */ 50 | public function getBody() { 51 | return $this->body; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/ObjectPropertyNodeTest.php: -------------------------------------------------------------------------------- 1 | bar->baz'); 10 | $this->assertInstanceOf('\Pharborist\Objects\ObjectPropertyNode', $property); 11 | $this->assertEquals('bar', $property->getRootProperty()->getText()); 12 | 13 | $property = Parser::parseExpression('$foo->$bar'); 14 | $this->assertInstanceOf('\Pharborist\Objects\ObjectPropertyNode', $property); 15 | $this->assertEquals('$bar', $property->getRootProperty()->getText()); 16 | } 17 | 18 | /** 19 | * @depends testGetRootProperty 20 | */ 21 | public function testGetPropertyName() { 22 | /** @var ObjectPropertyNode $property */ 23 | $property = Parser::parseExpression('$node->field_foo'); 24 | $this->assertEquals('field_foo', $property->getPropertyName()); 25 | 26 | $property = Parser::parseExpression('$node->$field["value"]'); 27 | $this->assertNull($property->getPropertyName()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Types/ArrayPairNode.php: -------------------------------------------------------------------------------- 1 | key; 27 | } 28 | 29 | /** 30 | * @return Node 31 | */ 32 | public function getValue() { 33 | return $this->value; 34 | } 35 | 36 | /** 37 | * @param Node $key 38 | * Array element's key. 39 | * @param Node $value 40 | * Array element's value. 41 | * 42 | * @return ArrayPairNode 43 | */ 44 | public static function create($key, $value) { 45 | $node = new ArrayPairNode(); 46 | $node->addChild($key, 'key'); 47 | $node->addChild(Token::space()); 48 | $node->addChild(Token::doubleArrow()); 49 | $node->addChild(Token::space()); 50 | $node->addChild($value, 'value'); 51 | return $node; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ControlStructures/ForNode.php: -------------------------------------------------------------------------------- 1 | initial; 41 | } 42 | 43 | /** 44 | * @return CommaListNode 45 | */ 46 | public function getCondition() { 47 | return $this->condition; 48 | } 49 | 50 | /** 51 | * @return CommaListNode 52 | */ 53 | public function getStep() { 54 | return $this->step; 55 | } 56 | 57 | /** 58 | * @return Node 59 | */ 60 | public function getBody() { 61 | return $this->body; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/BinaryOperationNodeTest.php: -------------------------------------------------------------------------------- 1 | op = Parser::parseSnippet('$doAliensExist = TRUE;')->firstChild(); 12 | } 13 | 14 | public function testInstanceOf() { 15 | $this->assertInstanceOf('Pharborist\Operators\AssignNode', $this->op); 16 | } 17 | 18 | public function testGetLeftOperand() { 19 | $this->assertInstanceOf('Pharborist\Variables\VariableNode', $this->op->getLeftOperand()); 20 | $this->assertEquals('doAliensExist', $this->op->getLeftOperand()->getName()); 21 | } 22 | 23 | public function testGetOperator() { 24 | $this->assertInstanceOf('Pharborist\TokenNode', $this->op->getOperator()); 25 | $this->assertEquals('=', $this->op->getOperator()->getText()); 26 | } 27 | 28 | public function testGetRightOperand() { 29 | $this->assertInstanceOf('Pharborist\Constants\ConstantNode', $this->op->getRightOperand()); 30 | $this->assertEquals('TRUE', $this->op->getRightOperand()->getText()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/DocCommentTest.php: -------------------------------------------------------------------------------- 1 | getStatements()[0]; 26 | /** @var \Pharborist\Objects\InterfaceMethodNode $method */ 27 | $method = $interface->getStatements()[0]; 28 | $comment = DocCommentNode::create('Test'); 29 | $method->setDocComment($comment); 30 | $this->assertEquals($expected, $tree->getText()); 31 | } 32 | 33 | public function testClassMethodNode() { 34 | $method = ClassMethodNode::create('foo'); 35 | $method->setDocComment(DocCommentNode::create('{@inheritdoc}')); 36 | $expected = <<<'END' 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function foo() {} 41 | END; 42 | $this->assertEquals($expected, $method->getText()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/BooleanNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('TRUE', $true->getText()); 10 | 11 | $true = BooleanNode::create(FALSE); 12 | $this->assertEquals('FALSE', $true->getText()); 13 | } 14 | 15 | public function testToUpper() { 16 | $default_formatter = FormatterFactory::getDefaultFormatter(); 17 | $formatter = new Formatter(['boolean_null_upper' => FALSE]); 18 | FormatterFactory::setDefaultFormatter($formatter); 19 | $true = BooleanNode::create(TRUE); 20 | $this->assertEquals('true', $true->getText()); 21 | $this->assertEquals('TRUE', $true->toUpperCase()->getText()); 22 | FormatterFactory::setDefaultFormatter($default_formatter); 23 | } 24 | 25 | public function testToLower() { 26 | $default_formatter = FormatterFactory::getDefaultFormatter(); 27 | $formatter = new Formatter(['boolean_null_upper' => TRUE]); 28 | FormatterFactory::setDefaultFormatter($formatter); 29 | $true = BooleanNode::create(TRUE); 30 | $this->assertEquals('TRUE', $true->getText()); 31 | $this->assertEquals('true', $true->toLowerCase()->getText()); 32 | FormatterFactory::setDefaultFormatter($default_formatter); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Exceptions/TryCatchNode.php: -------------------------------------------------------------------------------- 1 | try; 26 | } 27 | 28 | /** 29 | * @return CatchNode[] 30 | */ 31 | public function getCatches() { 32 | return $this->childrenByInstance('\Pharborist\Exceptions\CatchNode'); 33 | } 34 | 35 | /** 36 | * @return Node 37 | */ 38 | public function getFinally() { 39 | return $this->finally; 40 | } 41 | 42 | /** 43 | * Returns if this try/catch has a catch for a certain exception type. 44 | * 45 | * @param string $exception ... 46 | * At least one exception type to check for. Each should be a fully qualified 47 | * name, e.g. `\Exception` instead of `Exception`. 48 | * 49 | * @return boolean 50 | */ 51 | public function catches($exception) { 52 | $exceptions = func_get_args(); 53 | 54 | foreach ($this->getCatches() as $catch) { 55 | if (in_array($catch->getExceptionType()->getAbsolutePath(), $exceptions, TRUE)) { 56 | return TRUE; 57 | } 58 | } 59 | return FALSE; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Functions/ParameterNodeCollection.php: -------------------------------------------------------------------------------- 1 | nodes) as $node) { 18 | if ($node instanceof ParameterNode) { 19 | if ($node->getName() === $offset) { 20 | return TRUE; 21 | } 22 | } 23 | } 24 | return FALSE; 25 | } 26 | return isset($this->nodes[$offset]); 27 | } 28 | 29 | /** 30 | * Implements \ArrayAccess::offsetGet(). 31 | * 32 | * @param integer $offset 33 | * 34 | * @return ParameterNode 35 | */ 36 | public function offsetGet($offset) { 37 | if (is_string($offset)) { 38 | // To deal with php allowing function test($a, $a) loop in reverse. 39 | foreach (array_reverse($this->nodes) as $node) { 40 | if ($node instanceof ParameterNode) { 41 | if ($node->getName() === $offset) { 42 | return $node; 43 | } 44 | } 45 | } 46 | return NULL; 47 | } 48 | return parent::offsetGet($offset); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Operators/BinaryOperationNode.php: -------------------------------------------------------------------------------- 1 | left; 32 | } 33 | 34 | /** 35 | * @param ExpressionNode $operand 36 | * @return $this 37 | */ 38 | public function setLeftOperand(ExpressionNode $operand) { 39 | /** @var Node $operand */ 40 | $this->left->replaceWith($operand); 41 | return $this; 42 | } 43 | 44 | /** 45 | * @return Node 46 | */ 47 | public function getOperator() { 48 | return $this->operator; 49 | } 50 | 51 | /** 52 | * @return ExpressionNode 53 | */ 54 | public function getRightOperand() { 55 | return $this->right; 56 | } 57 | 58 | /** 59 | * @param ExpressionNode $operand 60 | * @return $this 61 | */ 62 | public function setRightOperand(ExpressionNode $operand) { 63 | /** @var Node $operand */ 64 | $this->right->replaceWith($operand); 65 | return $this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/DocCommentTrait.php: -------------------------------------------------------------------------------- 1 | previousToken(); 18 | if (empty($whitespace_token) || $whitespace_token->getType() !== T_WHITESPACE) { 19 | return ''; 20 | } 21 | $nl = FormatterFactory::getDefaultFormatter()->getConfig('nl'); 22 | $lines = explode($nl, $whitespace_token->getText()); 23 | $last_line = end($lines); 24 | return $last_line; 25 | } 26 | 27 | /** 28 | * @return DocCommentNode 29 | */ 30 | public function getDocComment() { 31 | return $this->docComment; 32 | } 33 | 34 | /** 35 | * @param DocCommentNode $comment 36 | * @return $this 37 | */ 38 | public function setDocComment(DocCommentNode $comment) { 39 | if (isset($this->docComment)) { 40 | $this->docComment->remove(); 41 | } 42 | 43 | $indent = $this->getIndent(); 44 | $comment->setIndent($indent); 45 | $nl = FormatterFactory::getDefaultFormatter()->getConfig('nl'); 46 | /** @var ParentNode $this */ 47 | $this->firstChild()->before([ 48 | $comment, 49 | WhitespaceNode::create($nl . $indent), 50 | ]); 51 | $this->docComment = $comment; 52 | 53 | return $this; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Operator.php: -------------------------------------------------------------------------------- 1 | operator; 28 | } 29 | 30 | /** 31 | * Colon node. Only used by ternary operator. 32 | * @var PartialNode 33 | */ 34 | public $colon; 35 | 36 | /** 37 | * Then node. Only used by ternary operator. 38 | */ 39 | public $then; 40 | 41 | /** 42 | * @var int 43 | */ 44 | public $mode; 45 | 46 | /** 47 | * @var int 48 | */ 49 | public $precedence; 50 | 51 | /** 52 | * @var int 53 | */ 54 | public $associativity; 55 | 56 | /** 57 | * @var int 58 | */ 59 | public $type; 60 | 61 | /** 62 | * @var bool 63 | */ 64 | public $hasBinaryMode; 65 | 66 | /** 67 | * @var bool 68 | */ 69 | public $hasUnaryMode; 70 | 71 | /** 72 | * Node class to create if unary operator. 73 | * @var string 74 | */ 75 | public $unaryClassName; 76 | 77 | /** 78 | * Node class to create if binary operator. 79 | * @var string 80 | */ 81 | public $binaryClassName; 82 | } 83 | -------------------------------------------------------------------------------- /tests/ObjectMethodCallNodeTest.php: -------------------------------------------------------------------------------- 1 | call = Parser::parseSnippet('$mulder->scully();')->firstChild(); 12 | } 13 | 14 | public function testGetObject() { 15 | $this->assertInstanceOf('Pharborist\Variables\VariableNode', $this->call->getObject()); 16 | $this->assertEquals('mulder', $this->call->getObject()->getName()); 17 | } 18 | 19 | public function testGetMethodName() { 20 | $this->assertEquals('scully', $this->call->getMethodName()); 21 | } 22 | 23 | public function testSetMethodName() { 24 | $this->call->setMethodName('skinner'); 25 | $this->assertEquals('skinner', $this->call->getMethodName()); 26 | $this->assertEquals('$mulder->skinner()', $this->call->getText()); 27 | } 28 | 29 | public function testGetPreviousCall() { 30 | $call = Parser::parseSnippet('\Drupal::database()->insert("razmatazz");')->firstChild(); 31 | $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $call); 32 | $this->assertInstanceOf('\Pharborist\Objects\ClassMethodCallNode', $call->getPreviousCall()); 33 | 34 | $call = Parser::parseSnippet('$raz->matazz();')->firstChild(); 35 | $this->assertInstanceOf('\Pharborist\Objects\ObjectMethodCallNode', $call); 36 | $this->assertNull($call->getPreviousCall()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/NamespaceNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('\Top\Sub', $namespace_node->getName()->getAbsolutePath()); 11 | $this->assertNotNull($namespace_node->getBody()); 12 | } 13 | 14 | public function testUseDeclarations() { 15 | $snippet = <<<'EOF' 16 | namespace Test { 17 | use const Other\MY_CONST; 18 | use function Other\my_func; 19 | use Other\MyClass; 20 | use Other\OtherClass as Bind; 21 | class TestClass {} 22 | } 23 | EOF; 24 | /** @var NamespaceNode $namespace */ 25 | $namespace = Parser::parseSnippet($snippet); 26 | 27 | $declarations = $namespace->getUseDeclarations(); 28 | $this->assertCount(4, $declarations); 29 | 30 | $aliases = $namespace->getClassAliases(); 31 | $this->assertCount(2, $aliases); 32 | $this->assertArrayHasKey('MyClass', $aliases); 33 | $this->assertEquals('\Other\MyClass', $aliases['MyClass']); 34 | $this->assertArrayHasKey('Bind', $aliases); 35 | $this->assertEquals('\Other\OtherClass', $aliases['Bind']); 36 | 37 | $class_node = $namespace->find(Filter::isClass('TestClass'))[0]; 38 | $this->assertTrue($namespace->owns($class_node)); 39 | $class_node = ClassNode::create('Dummy'); 40 | $this->assertFalse($namespace->owns($class_node)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Constants/ConstantNode.php: -------------------------------------------------------------------------------- 1 | addChild($name, 'constantName'); 24 | return $node; 25 | } 26 | 27 | /** 28 | * @var \Pharborist\Namespaces\NameNode 29 | */ 30 | protected $constantName; 31 | 32 | /** 33 | * @return \Pharborist\Namespaces\NameNode 34 | */ 35 | public function getConstantName() { 36 | return $this->constantName; 37 | } 38 | 39 | /** 40 | * Convert the constant into uppercase. 41 | * 42 | * @return $this 43 | */ 44 | public function toUpperCase() { 45 | $token = $this->getConstantName()->lastToken(); 46 | $token->setText(strtoupper($token->getText())); 47 | return $this; 48 | } 49 | 50 | /** 51 | * Convert the constant into lowercase. 52 | * 53 | * @return $this 54 | */ 55 | public function toLowerCase() { 56 | $token = $this->getConstantName()->lastToken(); 57 | $token->setText(strtolower($token->getText())); 58 | return $this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/VisitorBase.php: -------------------------------------------------------------------------------- 1 | methodCache[$node_class_name][$prefix])) { 19 | return $this->methodCache[$node_class_name][$prefix]; 20 | } 21 | else { 22 | $methods = []; 23 | $classes = array_merge([$node_class_name], class_parents($node), class_implements($node)); 24 | foreach ($classes as $class_name) { 25 | $class_name = static::getShortClassName($class_name); 26 | $method_name = $prefix . $class_name; 27 | if (method_exists($this, $method_name)) { 28 | $methods[] = $method_name; 29 | } 30 | } 31 | $methods = array_reverse($methods); 32 | $this->methodCache[$node_class_name][$prefix] = $methods; 33 | return $methods; 34 | } 35 | } 36 | 37 | public function visit(Node $node) { 38 | foreach ($this->getMethods($node, 'visit') as $method_name) { 39 | $this->$method_name($node); 40 | } 41 | } 42 | 43 | public function visitEnd(ParentNode $node) { 44 | foreach ($this->getMethods($node, 'end') as $method_name) { 45 | $this->$method_name($node); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/IdentifierNameTraitTest.php: -------------------------------------------------------------------------------- 1 | getBody()->children(Filter::isFunction('hello_world'))[0]; 17 | $this->assertEquals('hello_world', $function->getName()->getText()); 18 | $this->assertSame($namespace_node, $function->getNamespace()); 19 | $this->assertTrue($function->inNamespace($namespace_node)); 20 | $this->assertTrue($function->inNamespace('\Test')); 21 | $this->assertFalse($function->inNamespace('\Dummy\Test')); 22 | } 23 | 24 | /** 25 | * @expectedException \InvalidArgumentException 26 | */ 27 | public function testInvalid() { 28 | $snippet = <<<'EOF' 29 | namespace Test { 30 | function hello_world() { 31 | echo 'hello world!', PHP_EOL; 32 | } 33 | } 34 | EOF; 35 | /** @var \Pharborist\Namespaces\NamespaceNode $namespace_node */ 36 | $namespace_node = Parser::parseSnippet($snippet); 37 | /** @var \Pharborist\Functions\FunctionDeclarationNode $function */ 38 | $function = $namespace_node->getBody()->children(Filter::isFunction('hello_world'))[0]; 39 | $this->assertTrue($function->inNamespace(new \stdClass())); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Types.php: -------------------------------------------------------------------------------- 1 | assertEquals("getText()); 9 | 10 | $doc = RootNode::create('Pharborist\Test'); 11 | $this->assertEquals("getText()); 12 | 13 | $ns = $doc->children(Filter::isInstanceOf('\Pharborist\Namespaces\NamespaceNode'))[0]; 14 | $this->assertEquals('\Pharborist\Test', $ns->getName()->getAbsolutePath()); 15 | } 16 | 17 | public function testNSHelpers() { 18 | $doc = RootNode::create('Pharborist'); 19 | 20 | $this->assertTrue($doc->hasNamespace('Pharborist')); 21 | $this->assertFalse($doc->hasNamespace('\Drupal')); 22 | $this->assertContains('Pharborist', $doc->getNamespaceNames()); 23 | $this->assertContains('\Pharborist', $doc->getNamespaceNames(TRUE)); 24 | 25 | $namespaces = $doc->getNamespaces(); 26 | $this->assertInstanceOf('\Pharborist\NodeCollection', $namespaces); 27 | $this->assertCount(1, $namespaces); 28 | 29 | $this->assertNull($doc->getNamespace('Drupal')); 30 | $ns = $doc->getNamespace('Pharborist'); 31 | $this->assertInstanceOf('\Pharborist\Namespaces\NamespaceNode', $ns); 32 | $this->assertSame($ns, $namespaces[0]); 33 | 34 | $code = <<<'END' 35 | assertTrue($doc->hasNamespace('RoundTable\Knights\MontyPython')); 44 | $this->assertContains('\RoundTable\Knights\MontyPython', $doc->getNamespaceNames(TRUE)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Objects/VisibilityTrait.php: -------------------------------------------------------------------------------- 1 | visibility; 18 | } 19 | 20 | /** 21 | * @param string|integer|TokenNode|NULL $visibility 22 | * @return $this 23 | */ 24 | public function setVisibility($visibility) { 25 | if ($visibility === NULL) { 26 | $this->removeVisibility(); 27 | } 28 | else { 29 | if ($visibility === 'private' || $visibility === T_PRIVATE) { 30 | $visibility = Token::_private(); 31 | } 32 | elseif ($visibility === 'protected' || $visibility === T_PROTECTED) { 33 | $visibility = Token::_protected(); 34 | } 35 | elseif ($visibility === 'public' || $visibility === T_PUBLIC) { 36 | $visibility = Token::_public(); 37 | } 38 | 39 | if (isset($this->visibility)) { 40 | $this->visibility->replaceWith($visibility); 41 | } 42 | else { 43 | /** @var \Pharborist\ParentNode $this */ 44 | $this->prepend([ 45 | $visibility, 46 | Token::space(), 47 | ]); 48 | $this->visibility = $visibility; 49 | } 50 | } 51 | return $this; 52 | } 53 | 54 | /** 55 | * Remove the visibility modifier. 56 | */ 57 | protected function removeVisibility() { 58 | // Remove whitespace after visibility keyword. 59 | $this->visibility->next()->remove(); 60 | // Remove visibility keyword. 61 | $this->visibility->remove(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/FormatterFactory.php: -------------------------------------------------------------------------------- 1 | format($node); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Namespaces/IdentifierNameTrait.php: -------------------------------------------------------------------------------- 1 | name; 22 | } 23 | 24 | /** 25 | * @return NamespaceNode 26 | */ 27 | public function getNamespace() { 28 | return $this->name->getNamespace(); 29 | } 30 | 31 | /** 32 | * Set the identifier name of this node. 33 | * 34 | * @param string $name 35 | * New name. 36 | * @return $this 37 | */ 38 | public function setName($name) { 39 | /** @var TokenNode $identifier */ 40 | $identifier = $this->name->firstChild(); 41 | $identifier->setText($name); 42 | return $this; 43 | } 44 | 45 | /** 46 | * Determine if this node belongs to namespace. 47 | * 48 | * @param string|NamespaceNode $ns 49 | * Either the absolute namespace path or a NamespaceNode. 50 | * 51 | * @return boolean 52 | * TRUE if the node belongs to the given namespace. 53 | */ 54 | public function inNamespace($ns) { 55 | if (is_string($ns)) { 56 | $namespace_node = $this->name->getNamespace(); 57 | $namespace = $namespace_node === NULL ? '' : $namespace_node->getName()->getAbsolutePath(); 58 | return $ns === $namespace; 59 | } 60 | elseif ($ns instanceof NamespaceNode) { 61 | return $this->name->getNamespace() === $ns; 62 | } 63 | else { 64 | throw new \InvalidArgumentException(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Operators/TernaryOperationNode.php: -------------------------------------------------------------------------------- 1 | condition; 33 | } 34 | 35 | /** 36 | * @param ExpressionNode $condition 37 | * 38 | * @return $this 39 | */ 40 | public function setCondition(ExpressionNode $condition) { 41 | /** @var \Pharborist\Node $condition */ 42 | $this->condition->replaceWith($condition); 43 | return $this; 44 | } 45 | 46 | /** 47 | * @return ExpressionNode 48 | */ 49 | public function getThen() { 50 | return $this->then; 51 | } 52 | 53 | /** 54 | * @param ExpressionNode $then 55 | * 56 | * @return $this 57 | */ 58 | public function setThen(ExpressionNode $then) { 59 | /** @var \Pharborist\Node $then */ 60 | $this->then->replaceWith($then); 61 | return $this; 62 | } 63 | 64 | /** 65 | * @return ExpressionNode 66 | */ 67 | public function getElse() { 68 | return $this->else; 69 | } 70 | 71 | /** 72 | * @param ExpressionNode $else 73 | * 74 | * @return $this 75 | */ 76 | public function setElse(ExpressionNode $else) { 77 | /** @var \Pharborist\Node $else */ 78 | $this->condition->replaceWith($else); 79 | return $this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pharborist 2 | ========== 3 | 4 | ![Pharborist logo](./docs/logo_128px.png "Pharborist logo") 5 | 6 | A PHP library to query and transform PHP source code via tree operations. 7 | 8 | # Roadmap 9 | * [ ] Tests with 100% code coverage 10 | * [x] Integration with a third party library for PHPDoc comments 11 | * [ ] API to ease querying and transforming of the syntax tree 12 | * [ ] Build a PHP source code formatter using the library (Partially completed). 13 | * [ ] Build a PHP code check using the library 14 | 15 | Below is an example of how the API might look once its more developed: 16 | 17 | ```php 18 | // Add use declaration if it does not already exist. Use UtilityString alias if conflict 19 | $alias = $tree->ensureUseDeclaration('Drupal\Component\Utility\String', 'UtilityString'); 20 | // Find all calls to check_plain and rename them to use String::checkPlain 21 | $function_calls = $tree->find(Filter::functionCall('check_plain')); 22 | foreach ($function_calls as $call) { 23 | $class_method_call = ClassMethodCallNode::create($alias, 'check_plain', $call->getArgumentList()); 24 | $call->replaceWith($class_method_call); 25 | } 26 | ``` 27 | 28 | # Usage 29 | ```php 30 | require_once 'vendor/autoload.php'; 31 | 32 | use Pharborist\Parser; 33 | use Pharborist\Namespaces\NamespaceNode; 34 | use Pharborist\Filter; 35 | 36 | $filename = $argv[1]; 37 | $tree = Parser::parseFile($filename); 38 | 39 | // check there only one namespace declaration 40 | $namespaces = $tree->children(Filter::isInstanceOf('\Pharborist\Namespaces\NamespaceNode')); 41 | if ($namespaces->count() > 1) { 42 | die('More then one namespace at line ' . $namespaces[1]->getLineNumber() . PHP_EOL); 43 | } 44 | ``` 45 | [![Build Status](https://travis-ci.org/grom358/pharborist.png?branch=master)](https://travis-ci.org/grom358/pharborist) 46 | -------------------------------------------------------------------------------- /src/ControlStructures/IfNode.php: -------------------------------------------------------------------------------- 1 | condition; 48 | } 49 | 50 | /** 51 | * @return Node 52 | */ 53 | public function getThen() { 54 | return $this->then; 55 | } 56 | 57 | /** 58 | * @return NodeCollection|ElseIfNode[] 59 | */ 60 | public function getElseIfs() { 61 | return new NodeCollection($this->childrenByInstance('\Pharborist\ControlStructures\ElseIfNode'), FALSE); 62 | } 63 | 64 | /** 65 | * The T_ELSE keyword token. 66 | * 67 | * @return TokenNode 68 | */ 69 | public function getElseKeyword() { 70 | return $this->elseKeyword; 71 | } 72 | 73 | /** 74 | * The colon ':' token when using alternative syntax. 75 | * 76 | * @return TokenNode 77 | */ 78 | public function getElseColon() { 79 | return $this->elseColon; 80 | } 81 | 82 | /** 83 | * @return Node 84 | */ 85 | public function getElse() { 86 | return $this->else; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Objects/ObjectPropertyNode.php: -------------------------------------------------------------------------------- 1 | property`. 11 | */ 12 | class ObjectPropertyNode extends ParentNode implements VariableExpressionNode { 13 | /** 14 | * @var Node 15 | */ 16 | protected $object; 17 | 18 | /** 19 | * @var Node 20 | */ 21 | protected $property; 22 | 23 | /** 24 | * @return Node 25 | */ 26 | public function getObject() { 27 | return $this->object; 28 | } 29 | 30 | /** 31 | * @return Node 32 | */ 33 | public function getProperty() { 34 | return $this->property; 35 | } 36 | 37 | /** 38 | * Returns the name of the property if it's an identifier (ie. T_STRING TokenNode). 39 | * 40 | * @return string|NULL 41 | * Name of the property or NULL if not an identifier (eg. dynamic property 42 | * name). 43 | */ 44 | public function getPropertyName() { 45 | $root_property = $this->getRootProperty(); 46 | if ($root_property instanceof TokenNode && $root_property->getType() === T_STRING) { 47 | return $root_property->getText(); 48 | } 49 | return NULL; 50 | } 51 | 52 | /** 53 | * Returns the root property. 54 | * 55 | * For example, given an expression like $foo->bar->baz this method will 56 | * return the identifier (T_STRING TokenNode) 'bar'. 57 | * 58 | * @return Node 59 | * The node for the root property. 60 | */ 61 | public function getRootProperty() { 62 | if ($this->object instanceof ObjectPropertyNode) { 63 | return $this->object->getRootProperty(); 64 | } 65 | else { 66 | return $this->property; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Objects/MethodTrait.php: -------------------------------------------------------------------------------- 1 | name; 32 | } 33 | 34 | /** 35 | * @param string|TokenNode $name 36 | * 37 | * @return $this 38 | */ 39 | public function setName($name) { 40 | if (is_string($name)) { 41 | $name = Token::identifier($name); 42 | } 43 | $this->name->replaceWith($name); 44 | return $this; 45 | } 46 | 47 | /** 48 | * @return bool 49 | */ 50 | public function isStatic() { 51 | return $this->static !== NULL; 52 | } 53 | 54 | /** 55 | * @return TokenNode 56 | */ 57 | public function getStatic() { 58 | return $this->static; 59 | } 60 | 61 | /** 62 | * @param boolean $is_static 63 | * @return $this 64 | */ 65 | public function setStatic($is_static) { 66 | if ($is_static) { 67 | if (!isset($this->static)) { 68 | // Insert before T_FUNCTION. 69 | $function_token = $this->name->previous()->previous(); 70 | $this->static = Token::_static(); 71 | $function_token->before([ 72 | $this->static, 73 | Token::space(), 74 | ]); 75 | } 76 | } 77 | else { 78 | if (isset($this->static)) { 79 | // Remove whitespace after static keyword. 80 | $this->static->next()->remove(); 81 | // Remove static keyword. 82 | $this->static->remove(); 83 | } 84 | } 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/InterfaceNodeTest.php: -------------------------------------------------------------------------------- 1 | assertSame(['wambooli'], $interface->getMethodNames()); 9 | } 10 | 11 | public function testHasMethod() { 12 | /** @var \Pharborist\Objects\InterfaceNode $interface */ 13 | $interface = Parser::parseSnippet('interface Foo { public function wambooli(); }'); 14 | $this->assertTrue($interface->hasMethod('wambooli')); 15 | $this->assertFalse($interface->hasMethod('blorf')); 16 | } 17 | 18 | public function testGetMethods() { 19 | /** @var \Pharborist\Objects\InterfaceNode $interface */ 20 | $interface = Parser::parseSnippet('interface Foo { public function wambooli(); }'); 21 | $methods = $interface->getMethods(); 22 | $this->assertInstanceOf('\Pharborist\NodeCollection', $methods); 23 | $this->assertEquals(1, $methods->count()); 24 | } 25 | 26 | public function testGetMethod() { 27 | /** @var \Pharborist\Objects\InterfaceNode $interface */ 28 | $interface = Parser::parseSnippet('interface Foo { public function wambooli(); }'); 29 | $method = $interface->getMethod('wambooli'); 30 | $this->assertInstanceOf('\Pharborist\Objects\InterfaceMethodNode', $method); 31 | $this->assertEquals('wambooli', $method->getName()->getText()); 32 | $this->assertNull($interface->getMethod('harrr')); 33 | } 34 | 35 | public function testGetConstants() { 36 | /** @var \Pharborist\Objects\InterfaceNode $interface */ 37 | $interface = Parser::parseSnippet('interface Foo { const ANSWER = 42; }'); 38 | $constants = $interface->getConstants(); 39 | $this->assertCount(1, $constants); 40 | /** @var \Pharborist\Constants\ConstantDeclarationNode $constant */ 41 | $constant = $constants[0]; 42 | $this->assertInstanceOf('\Pharborist\Constants\ConstantDeclarationNode', $constant); 43 | $this->assertEquals('ANSWER', $constant->getName()); 44 | $this->assertEquals('42', $constant->getValue()->getText()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Functions/CallNode.php: -------------------------------------------------------------------------------- 1 | bar(); 14 | * Foo::bar(); 15 | * $foo('bar'); 16 | * ``` 17 | */ 18 | abstract class CallNode extends ParentNode { 19 | use ArgumentTrait; 20 | use ParenTrait; 21 | 22 | /** 23 | * Allows you to append a method call to this one, building a chain of method 24 | * calls. 25 | * 26 | * For example: 27 | * ``` 28 | * // \Drupal::entityManager() 29 | * $classCall = ClassMethodCallNode::create('\Drupal', 'entityManager'); 30 | * 31 | * $methodCall = $classCall->appendMethodCall('getDefinitions'); 32 | * echo $methodCall->getText(); // \Drupal::entityManager()->getDefinitions() 33 | * echo $methodCall->getObject(); // \Drupal::entityManager() 34 | * echo $methodCall->getMethodName(); // getDefinitions 35 | * 36 | * // You can chain yet another call, and keep going as long as you want. 37 | * 38 | * $methodCall = $methodCall->appendMethodCall('clearCache') 39 | * echo $methodCall->getText(); // \Drupal::entityManager()->getDefinitions()->clearCache() 40 | * 41 | * // These methods are chainable themselves, so you can build an entire call chain 42 | * // in one fell swoop. 43 | * 44 | * $chain = ClassMethodCallNode::create('Foo', 'bar')->appendMethodCall('baz')->appendMethodCall('zorg'); 45 | * echo $chain->getText(); // Foo::bar()->baz()->zorg() 46 | * ``` 47 | * 48 | * @param string $method_name 49 | * The name of the method to call. 50 | * 51 | * @return \Pharborist\Objects\ObjectMethodCallNode 52 | * The newly-created method call, in which every previous part of the chain will be the 53 | * "object", and $method_name will be the "method". The call will be created without 54 | * arguments, but you can add some using appendArgument(). 55 | */ 56 | public function appendMethodCall($method_name) { 57 | $method_call = ObjectMethodCallNode::create(clone $this, $method_name); 58 | $this->replaceWith($method_call); 59 | return $method_call; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/ClassMemberNodeTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('\Pharborist\Objects\ClassMemberListNode', $member); 13 | $this->assertEquals('public $lancelot;', $member->getText()); 14 | 15 | $member = ClassMemberNode::create('robin', StringNode::create("'cowardly'"), 'protected'); 16 | $this->assertInstanceOf('\Pharborist\Objects\ClassMemberListNode', $member); 17 | $this->assertEquals('protected $robin = \'cowardly\';', $member->getText()); 18 | } 19 | 20 | public function testStatic() { 21 | /** @var ClassNode $class_node */ 22 | $class_node = Parser::parseSnippet('class Foo { public $bar; }'); 23 | /** @var ClassMemberNode $a */ 24 | $a = $class_node->getProperties()[0]; 25 | $this->assertFalse($a->isStatic()); 26 | $this->assertNull($a->getStatic()); 27 | $a->setStatic(TRUE); 28 | $this->assertTrue($a->isStatic()); 29 | $this->assertSame('public static $bar;', $a->closest(Filter::isInstanceOf('\Pharborist\Objects\ClassMemberListNode'))->getText()); 30 | 31 | $class_node = Parser::parseSnippet('class Bar { protected static $baz; }'); 32 | /** @var ClassMemberNode $b */ 33 | $b = $class_node->getProperties()[0]; 34 | $this->assertTrue($b->isStatic()); 35 | $this->assertInstanceOf('\Pharborist\TokenNode', $b->getStatic()); 36 | $this->assertSame(T_STATIC, $b->getStatic()->getType()); 37 | $b->setStatic(FALSE); 38 | $this->assertFalse($b->isStatic()); 39 | $this->assertSame('protected $baz;', $b->closest(Filter::isInstanceOf('\Pharborist\Objects\ClassMemberListNode'))->getText()); 40 | } 41 | 42 | public function testVisibility() { 43 | /** @var ClassNode $class_node */ 44 | $class_node = Parser::parseSnippet('class Foo { public $bar; }'); 45 | /** @var ClassMemberNode $a */ 46 | $a = $class_node->getProperties()[0]; 47 | $this->assertEquals('private $bar;', $a->setVisibility('private')->parent()->parent()->getText()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/ArrayLookupNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('$form_state[\'storage\']', $lookup->getText()); 10 | } 11 | 12 | public function testGetKeys() { 13 | $lookup = Parser::parseExpression('$foo["bar"]["baz"]'); 14 | $keys = $lookup->getKeys(); 15 | $this->assertInternalType('array', $keys); 16 | $this->assertCount(2, $keys); 17 | $this->assertInstanceOf('\Pharborist\Types\StringNode', $keys[0]); 18 | $this->assertEquals('bar', $keys[0]->toValue()); 19 | $this->assertInstanceOf('\Pharborist\Types\StringNode', $keys[1]); 20 | $this->assertEquals('baz', $keys[1]->toValue()); 21 | } 22 | 23 | public function testHasScalarKeys() { 24 | $this->assertTrue(Parser::parseExpression('$foo["bar"]["baz"][3]')->hasScalarKeys()); 25 | $this->assertFalse(Parser::parseExpression('$foo[$bar]["baz"]')->hasScalarKeys()); 26 | } 27 | 28 | public function testExtractKeys() { 29 | $this->assertSame(['bar', 'baz', 3], Parser::parseExpression('$foo["bar"]["baz"][3]')->extractKeys()); 30 | } 31 | 32 | /** 33 | * @expectedException \DomainException 34 | */ 35 | public function testExtractNonScalarKeys() { 36 | Parser::parseExpression('$foo[$bar][baz()][30]')->extractKeys(); 37 | } 38 | 39 | public function testGetRoot() { 40 | /** @var ArrayLookupNode $lookup */ 41 | $lookup = Parser::parseExpression('$foo["bar"]["baz"][0]'); 42 | $this->assertInstanceOf('\Pharborist\ArrayLookupNode', $lookup); 43 | $root = $lookup->getRootArray(); 44 | $this->assertInstanceOf('\Pharborist\Variables\VariableNode', $root); 45 | $this->assertEquals('$foo', $root->getText()); 46 | 47 | $lookup = Parser::parseExpression('foo()["bar"]'); 48 | $this->assertInstanceOf('\Pharborist\ArrayLookupNode', $lookup); 49 | $root = $lookup->getRootArray(); 50 | $this->assertInstanceOf('\Pharborist\Functions\FunctionCallNode', $root); 51 | $this->assertEquals('foo', $root->getName()->getText()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ParentNodeInterface.php: -------------------------------------------------------------------------------- 1 | addChild(Token::openTag()); 21 | if (is_string($ns) && $ns) { 22 | NamespaceNode::create($ns)->appendTo($node)->after(Token::newline()); 23 | } 24 | return $node; 25 | } 26 | 27 | /** 28 | * Returns if this document contains a particular namespace. 29 | * 30 | * @param string $ns 31 | * The name of the namespace to look for. 32 | * 33 | * @return boolean 34 | */ 35 | public function hasNamespace($ns) { 36 | return in_array($ns, $this->getNamespaceNames()); 37 | } 38 | 39 | /** 40 | * Returns every namespace in this document. 41 | * 42 | * @return \Pharborist\NodeCollection 43 | */ 44 | public function getNamespaces() { 45 | return $this->children(Filter::isInstanceOf('\Pharborist\Namespaces\NamespaceNode')); 46 | } 47 | 48 | /** 49 | * Returns a particular namespace, if it exists. 50 | * 51 | * @param string $ns 52 | * The name of the namespace to look for. 53 | * 54 | * @return \Pharborist\Namespaces\NamespaceNode|NULL 55 | */ 56 | public function getNamespace($ns) { 57 | $namespaces = $this 58 | ->getNamespaces() 59 | ->filter(function(NamespaceNode $node) use ($ns) { 60 | return $node->getName()->getPath() === $ns; 61 | }); 62 | 63 | return $namespaces->isEmpty() ? NULL : $namespaces[0]; 64 | } 65 | 66 | /** 67 | * Returns the name of every namespace in this document. 68 | * 69 | * @param boolean $absolute 70 | * 71 | * @return string[] 72 | */ 73 | public function getNamespaceNames($absolute = FALSE) { 74 | $iterator = function(NamespaceNode $ns) use ($absolute) { 75 | $name = $ns->getName(); 76 | return $absolute ? $name->getAbsolutePath() : $name->getPath(); 77 | }; 78 | return array_map($iterator, $this->getNamespaces()->toArray()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Types/StringNode.php: -------------------------------------------------------------------------------- 1 | getText(); 34 | $quote_char = $text[0]; 35 | $text = substr($text, 1, -1); 36 | if ($quote_char === '"') { 37 | $rules = array( 38 | preg_quote('\\\\') => '\\', 39 | preg_quote('\n') => "\n", 40 | preg_quote('\t') => "\t", 41 | preg_quote('\"') => '"', 42 | preg_quote('\$') => '$', 43 | preg_quote('\r') => "\r", 44 | preg_quote('\v') => "\v", 45 | preg_quote('\f') => "\f", 46 | preg_quote('\e') => "\e", 47 | '\\\\[0-7]{1,3}' => '__octal__', 48 | '\\\\x[0-9A-Fa-f]{1,2}' => '__hex__', 49 | ); 50 | } 51 | else { 52 | $rules = array( 53 | preg_quote('\\\\') => '\\', 54 | preg_quote("\\'") => "'" 55 | ); 56 | } 57 | $replacements = array_values($rules); 58 | $regex = '@(' . implode(')|(', array_keys($rules)) . ')@'; 59 | return preg_replace_callback($regex, function ($matches) use ($replacements) { 60 | // find the first non-empty element (but skipping $matches[0]) using a quick for loop 61 | for ($i = 1; '' === $matches[$i]; ++$i); 62 | 63 | $match = $matches[0]; 64 | $replacement = $replacements[$i - 1]; 65 | if ($replacement === '__octal__') { 66 | $replacement = chr(octdec(substr($match, 1))); 67 | } 68 | elseif ($replacement === '__hex__') { 69 | $replacement = chr(hexdec(substr($match, 2))); 70 | } 71 | return $replacement; 72 | }, $text); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/StatementNode.php: -------------------------------------------------------------------------------- 1 | A statement is a single executable unit of PHP code. You can think of a 8 | * statement as a single "sentence" of code, usually ending with 9 | * a semicolon. A single statement usually (but not always!) occupies a single 10 | * line.

11 | *

Here's an example of a perfectly valid statement:

12 | *
echo "Let's not go to Camelot. 'Tis a silly place.\n";
13 | *

Statements can contain other statements, or a block of statements surrounded 14 | * by curly braces. A single statement is usually made up of one or more 15 | * expressions.

16 | *

Declarations are also statements. For instance, if/elseif/else and switch/case 17 | * structures are statements, including all of their blocks. So is are class and function 18 | * declarations. The body of the class or function is a statement block, but it's 19 | * contained by the class or function declaration, which is a statement.

20 | */ 21 | abstract class StatementNode extends ParentNode { 22 | /** 23 | * Gets the number of lines spanned by this statement. 24 | * 25 | * @return integer 26 | * Always returns at least one, because any statement will be at least 27 | * one line long. 28 | */ 29 | public function getLineCount() { 30 | $count = 1; 31 | 32 | $this 33 | ->find(Filter::isInstanceOf('\Pharborist\WhitespaceNode')) 34 | ->each(function(WhitespaceNode $node) use (&$count) { 35 | $count += $node->getNewlineCount(); 36 | }); 37 | 38 | return $count; 39 | } 40 | 41 | /** 42 | * Creates a commented-out version of this statement. 43 | * 44 | * @return \Pharborist\CommentNode|\Pharborist\LineCommentBlockNode 45 | */ 46 | public function toComment() { 47 | return CommentNode::create($this->getText()); 48 | } 49 | 50 | /** 51 | * Adds a line comment block above the statement. 52 | * 53 | * @param \Pharborist\LineCommentBlockNode|string $comment 54 | * The comment to add. 55 | * 56 | * @return $this 57 | * 58 | * @throws \InvalidArgumentException 59 | */ 60 | public function addCommentAbove($comment) { 61 | if ($comment instanceof LineCommentBlockNode) { 62 | $this->before($comment); 63 | } 64 | elseif (is_string($comment)) { 65 | $this->addCommentAbove(LineCommentBlockNode::create($comment)); 66 | } 67 | else { 68 | throw new \InvalidArgumentException(); 69 | } 70 | return $this; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Objects/ClassMethodCallNode.php: -------------------------------------------------------------------------------- 1 | className; 30 | } 31 | 32 | /** 33 | * @param string|Node $class_name 34 | * @return $this 35 | */ 36 | public function setClassName($class_name) { 37 | if (is_string($class_name)) { 38 | $class_name = Token::identifier($class_name); 39 | } 40 | $this->className->replaceWith($class_name); 41 | $this->className = $class_name; 42 | return $this; 43 | } 44 | 45 | /** 46 | * @return Node 47 | */ 48 | public function getMethodName() { 49 | return $this->methodName; 50 | } 51 | 52 | /** 53 | * @param string|Node $method_name 54 | * @return $this 55 | */ 56 | public function setMethodName($method_name) { 57 | if (is_string($method_name)) { 58 | $method_name = Token::identifier($method_name); 59 | } 60 | $this->methodName->replaceWith($method_name); 61 | $this->methodName = $method_name; 62 | return $this; 63 | } 64 | 65 | /** 66 | * Creates a method call on a class with an empty argument list. 67 | * 68 | * @param Node|string $class_name 69 | * The class node which is typically NameNode of class. 70 | * @param string $method_name 71 | * The name of the called method. 72 | * 73 | * @return static 74 | */ 75 | public static function create($class_name, $method_name) { 76 | if (is_string($class_name)) { 77 | $class_name = NameNode::create($class_name); 78 | } 79 | /** @var ClassMethodCallNode $node */ 80 | $node = new static(); 81 | $node->addChild($class_name, 'className'); 82 | $node->addChild(Token::doubleColon()); 83 | $node->addChild(Token::identifier($method_name), 'methodName'); 84 | $node->addChild(Token::openParen(), 'openParen'); 85 | $node->addChild(new CommaListNode(), 'arguments'); 86 | $node->addChild(Token::closeParen(), 'closeParen'); 87 | return $node; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/StatementBlockNode.php: -------------------------------------------------------------------------------- 1 | head; 13 | while ($child) { 14 | if ($child instanceof StatementNode) { 15 | $matches[] = $child; 16 | } 17 | elseif ($child instanceof StatementBlockNode) { 18 | $matches = array_merge($matches, $child->_getStatements()); 19 | } 20 | $child = $child->next; 21 | } 22 | return $matches; 23 | } 24 | 25 | /** 26 | * @return NodeCollection|StatementNode[] 27 | */ 28 | public function getStatements() { 29 | return new NodeCollection($this->_getStatements(), FALSE); 30 | } 31 | 32 | /** 33 | * Get the use declarations of this statement block. 34 | * 35 | * @return NodeCollection|UseDeclarationNode[] 36 | * Use declarations. 37 | */ 38 | public function getUseDeclarations() { 39 | $declarations = []; 40 | /** @var \Pharborist\Namespaces\UseDeclarationBlockNode[] $use_blocks */ 41 | $use_blocks = $this->children(Filter::isInstanceOf('\Pharborist\Namespaces\UseDeclarationBlockNode')); 42 | foreach ($use_blocks as $use_block) { 43 | foreach ($use_block->getDeclarationStatements() as $use_statement) { 44 | $declarations = array_merge($declarations, $use_statement->getDeclarations()->toArray()); 45 | } 46 | } 47 | return new NodeCollection($declarations, FALSE); 48 | } 49 | 50 | /** 51 | * Return mapping of class names to fully qualified names. 52 | * 53 | * @return array 54 | * Associative array of namespace alias to fully qualified names. 55 | */ 56 | public function getClassAliases() { 57 | $mappings = array(); 58 | foreach ($this->getUseDeclarations() as $use_declaration) { 59 | if ($use_declaration->isClass()) { 60 | $mappings[$use_declaration->getBoundedName()] = $use_declaration->getName()->getAbsolutePath(); 61 | } 62 | } 63 | return $mappings; 64 | } 65 | 66 | /** 67 | * Append statement to block. 68 | * 69 | * @param StatementNode $statementNode 70 | * Statement to append. 71 | * @return $this 72 | */ 73 | public function appendStatement(StatementNode $statementNode) { 74 | if (!$this->isEmpty() && $this->firstToken()->getType() === '{') { 75 | $this->lastChild()->before($statementNode); 76 | } 77 | else { 78 | $this->appendChild($statementNode); 79 | } 80 | return $this; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Functions/FunctionDeclarationNode.php: -------------------------------------------------------------------------------- 1 | appendParameter($parameter); 39 | } 40 | } 41 | return $function; 42 | } 43 | 44 | /** 45 | * @var StatementBlockNode 46 | */ 47 | protected $body; 48 | 49 | /** 50 | * Set the name of the declared function. 51 | * 52 | * @param string $name 53 | * New function name. 54 | * @return $this 55 | */ 56 | public function setName($name) { 57 | /** @var TokenNode $function_name */ 58 | $function_name = $this->getName()->firstChild(); 59 | $function_name->setText($name); 60 | return $this; 61 | } 62 | 63 | /** 64 | * @return StatementBlockNode 65 | */ 66 | public function getBody() { 67 | return $this->body; 68 | } 69 | 70 | /** 71 | * Creates a class method from this function and add it to the given 72 | * class definition. 73 | * 74 | * @param \Pharborist\Objects\ClassNode $class 75 | * The class to add the new method to. 76 | * 77 | * @return \Pharborist\Objects\ClassMethodNode 78 | * The newly created method. 79 | */ 80 | public function cloneAsMethodOf(ClassNode $class) { 81 | $clone = ClassMethodNode::fromFunction($this); 82 | $class->appendMethod($clone); 83 | return $clone; 84 | } 85 | 86 | public static function fromReflector(\ReflectionFunction $reflector) { 87 | $node = static::create($reflector->getName()); 88 | $node->matchReflector($reflector); 89 | return $node; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/TokenIterator.php: -------------------------------------------------------------------------------- 1 | tokens = $tokens; 31 | $this->length = count($tokens); 32 | $this->position = 0; 33 | } 34 | 35 | /** 36 | * Return the current token. 37 | * @return TokenNode 38 | */ 39 | public function current() { 40 | if ($this->position >= $this->length) { 41 | return NULL; 42 | } 43 | return $this->tokens[$this->position]; 44 | } 45 | 46 | /** 47 | * Peek ahead. 48 | * @param int $offset Offset from current position. 49 | * @return TokenNode 50 | */ 51 | public function peek($offset) { 52 | if ($this->position + $offset >= $this->length) { 53 | return NULL; 54 | } 55 | return $this->tokens[$this->position + $offset]; 56 | } 57 | 58 | /** 59 | * Move to the next token and return it. 60 | * @return TokenNode 61 | */ 62 | public function next() { 63 | $this->position++; 64 | if ($this->position >= $this->length) { 65 | $this->position = $this->length; 66 | return NULL; 67 | } 68 | return $this->tokens[$this->position]; 69 | } 70 | 71 | /** 72 | * Return TRUE if there are more tokens. 73 | * @return bool 74 | */ 75 | public function hasNext() { 76 | return $this->position + 1 < $this->length; 77 | } 78 | 79 | /** 80 | * @return int 81 | */ 82 | public function getLineNumber() { 83 | if ($this->length === 0) { 84 | return 1; 85 | } 86 | $token = $this->current(); 87 | if ($token === NULL) { 88 | $token = $this->tokens[$this->length - 1]; 89 | return $token->getLineNumber(); 90 | } 91 | return $token->getLineNumber(); 92 | } 93 | 94 | /** 95 | * @return int 96 | */ 97 | public function getColumnNumber() { 98 | if ($this->length === 0) { 99 | return 1; 100 | } 101 | $token = $this->current(); 102 | if ($token === NULL) { 103 | $token = $this->tokens[$this->length - 1]; 104 | return $token->getColumnNumber() + $token->getByteLength(); 105 | } 106 | return $token->getColumnNumber(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/StatementNodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(7, $node->getLineCount()); 18 | // What, you haven't seen Spaceballs? 19 | $node = Parser::parseSnippet('$combination = 12345;'); 20 | $this->assertEquals(1, $node->getLineCount()); 21 | 22 | 23 | $text = <<<'END' 24 | db_delete('variable') 25 | ->condition('name', 'cron_last') 26 | ->execute(); 27 | END; 28 | $node = Parser::parseSnippet($text); 29 | $this->assertEquals(3, $node->getLineCount()); 30 | } 31 | 32 | public function testToComment() { 33 | $original = <<<'END' 34 | class Foo { 35 | private $larry; 36 | private $moe; 37 | private $curly; 38 | } 39 | END; 40 | 41 | // The expected text needs the extra line, because PHP ignores the new line 42 | // before END for whatever reason. Weird, but the tests pass this way. 43 | $expected = <<<'END' 44 | // class Foo { 45 | // private $larry; 46 | // private $moe; 47 | // private $curly; 48 | // } 49 | 50 | END; 51 | /** @var StatementNode $statement_node */ 52 | $statement_node = Parser::parseSnippet($original); 53 | $comment = $statement_node->toComment(); 54 | $this->assertEquals($expected, $comment->getText()); 55 | $this->assertEquals($original, $comment->uncomment()->getText()); 56 | } 57 | 58 | public function testAddCommentAbove() { 59 | $original = '$value = variable_get("my_variable", NULL);'; 60 | $comment = <<addCommentAbove($comment); 76 | $this->assertEquals($expected, $node->parent()->getText()); 77 | } 78 | 79 | /** 80 | * @expectedException \InvalidArgumentException 81 | */ 82 | public function testInvalidCommentAbove() { 83 | $original = '$value = variable_get("my_variable", NULL);'; 84 | /** @var StatementNode $statement_node */ 85 | $statement_node = Parser::parseSnippet($original); 86 | $statement_node->addCommentAbove(NULL); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/UseDeclarationStatementNodeTest.php: -------------------------------------------------------------------------------- 1 | getDeclarationStatements()[0]; 11 | $this->assertCount(1, $use_declaration_statement->getDeclarations()); 12 | $this->assertEquals($use_declaration_statement->getDeclarations(), $use_declaration_statement->getDeclarationList()->getItems()); 13 | $this->assertTrue($use_declaration_statement->importsClass()); 14 | $this->assertFalse($use_declaration_statement->importsConst()); 15 | $this->assertFalse($use_declaration_statement->importsFunction()); 16 | $this->assertTrue($use_declaration_statement->importsClass('MyNamespace\MyClass')); 17 | $this->assertFalse($use_declaration_statement->importsClass('MyNamespace\NotFound')); 18 | } 19 | 20 | public function testImportConst() { 21 | /** @var UseDeclarationBlockNode $use_block */ 22 | $use_block = Parser::parseSnippet('use const MyNamespace\MY_CONST;'); 23 | $use_declaration_statement = $use_block->getDeclarationStatements()[0]; 24 | $this->assertCount(1, $use_declaration_statement->getDeclarations()); 25 | $this->assertEquals($use_declaration_statement->getDeclarations(), $use_declaration_statement->getDeclarationList()->getItems()); 26 | $this->assertTrue($use_declaration_statement->importsConst()); 27 | $this->assertFalse($use_declaration_statement->importsClass()); 28 | $this->assertFalse($use_declaration_statement->importsFunction()); 29 | $this->assertTrue($use_declaration_statement->importsConst('MyNamespace\MY_CONST')); 30 | $this->assertFalse($use_declaration_statement->importsConst('MyNamespace\NOT_FOUND')); 31 | } 32 | 33 | public function testImportFunction() { 34 | /** @var UseDeclarationBlockNode $use_block */ 35 | $use_block = Parser::parseSnippet('use function MyNamespace\test_func;'); 36 | $use_declaration_statement = $use_block->getDeclarationStatements()[0]; 37 | $this->assertCount(1, $use_declaration_statement->getDeclarations()); 38 | $this->assertEquals($use_declaration_statement->getDeclarations(), $use_declaration_statement->getDeclarationList()->getItems()); 39 | $this->assertTrue($use_declaration_statement->importsFunction()); 40 | $this->assertFalse($use_declaration_statement->importsConst()); 41 | $this->assertFalse($use_declaration_statement->importsClass()); 42 | $this->assertTrue($use_declaration_statement->importsFunction('MyNamespace\test_func')); 43 | $this->assertFalse($use_declaration_statement->importsFunction('MyNamespace\not_found')); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Objects/ObjectMethodCallNode.php: -------------------------------------------------------------------------------- 1 | method()` 13 | */ 14 | class ObjectMethodCallNode extends CallNode implements VariableExpressionNode { 15 | /** 16 | * @var Node 17 | */ 18 | protected $object; 19 | 20 | /** 21 | * @var TokenNode 22 | */ 23 | protected $operator; 24 | 25 | /** 26 | * @var Node 27 | */ 28 | protected $methodName; 29 | 30 | /** 31 | * @return Node 32 | */ 33 | public function getObject() { 34 | return $this->object; 35 | } 36 | 37 | /** 38 | * The object operator '->' token (T_OBJECT_OPERATOR). 39 | * 40 | * @return TokenNode 41 | */ 42 | public function getOperator() { 43 | return $this->operator; 44 | } 45 | 46 | /** 47 | * @return Node 48 | */ 49 | public function getMethodName() { 50 | return $this->methodName; 51 | } 52 | 53 | /** 54 | * @param string|Node $method_name 55 | * @return $this 56 | */ 57 | public function setMethodName($method_name) { 58 | if (is_string($method_name)) { 59 | $method_name = Token::identifier($method_name); 60 | } 61 | $this->methodName->replaceWith($method_name); 62 | $this->methodName = $method_name; 63 | return $this; 64 | } 65 | 66 | /** 67 | * Creates a method call on an object with an empty argument list. 68 | * 69 | * @param Node $object 70 | * The expression that is an object. 71 | * @param string $method_name 72 | * The name of the called method. 73 | * 74 | * @return static 75 | */ 76 | public static function create(Node $object, $method_name) { 77 | /** @var ObjectMethodCallNode $node */ 78 | $node = new static(); 79 | $node->addChild($object, 'object'); 80 | $node->addChild(Token::objectOperator(), 'operator'); 81 | $node->addChild(Token::identifier($method_name), 'methodName'); 82 | $node->addChild(Token::openParen(), 'openParen'); 83 | $node->addChild(new CommaListNode(), 'arguments'); 84 | $node->addChild(Token::closeParen(), 'closeParen'); 85 | return $node; 86 | } 87 | 88 | /** 89 | * If this is a chained method call (e.g., foo()->bar()->baz()), returns 90 | * the previous call in the chain. 91 | * 92 | * @return \Pharborist\Functions\CallNode|NULL 93 | * The previous call in the chain or NULL if there is none. 94 | */ 95 | public function getPreviousCall() { 96 | if ($this->object instanceof CallNode) { 97 | return $this->object; 98 | } 99 | else { 100 | return NULL; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Functions/FunctionTrait.php: -------------------------------------------------------------------------------- 1 | reference; 25 | } 26 | 27 | /** 28 | * @param boolean $is_reference 29 | * @return $this 30 | */ 31 | public function setReference($is_reference) { 32 | if ($is_reference) { 33 | if (!isset($this->reference)) { 34 | /** @var \Pharborist\Functions\FunctionDeclarationNode|\Pharborist\Objects\ClassMethodNode|\Pharborist\Objects\InterfaceMethodNode $this */ 35 | $this->reference = Token::reference(); 36 | $this->name->before($this->reference); 37 | } 38 | } 39 | else { 40 | if (isset($this->reference)) { 41 | $this->reference->remove(); 42 | } 43 | } 44 | return $this; 45 | } 46 | 47 | /** 48 | * Return TRUE if function has phpDoc return type. 49 | * 50 | * @return bool 51 | */ 52 | public function hasReturnTypes() { 53 | $doc_comment = $this->getDocComment(); 54 | if (!$doc_comment) { 55 | return FALSE; 56 | } 57 | $return_tag = $doc_comment->getReturn(); 58 | if (!$return_tag) { 59 | return FALSE; 60 | } 61 | $types = $return_tag->getTypes(); 62 | return !empty($types); 63 | } 64 | 65 | /** 66 | * Get the return type of the function as defined by the doc comment. 67 | * 68 | * @return string[] 69 | * The types as defined by phpdoc standard. Default is ['void']. 70 | */ 71 | public function getReturnTypes() { 72 | $types = ['void']; 73 | $doc_comment = $this->getDocComment(); 74 | if (!$doc_comment) { 75 | return $types; 76 | } 77 | $return_tag = $doc_comment->getReturn(); 78 | if (!$return_tag) { 79 | return $types; 80 | } 81 | $types = Types::normalize($return_tag->getTypes()); 82 | if (empty($types)) { 83 | $types[] = 'void'; 84 | } 85 | return $types; 86 | } 87 | 88 | public function matchReflector(\ReflectionFunctionAbstract $reflector) { 89 | $this->setReference($reflector->returnsReference()); 90 | 91 | foreach ($reflector->getParameters() as $i => $parameter) { 92 | try { 93 | $this->getParameterAtIndex($i)->matchReflector($parameter); 94 | } 95 | catch (\OutOfBoundsException $e) { 96 | $this->appendParameter(ParameterNode::fromReflector($parameter)); 97 | } 98 | } 99 | 100 | return $this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/LineCommentBlockNode.php: -------------------------------------------------------------------------------- 1 | addChild($comment_node); 30 | } 31 | return $block_comment; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getCommentText() { 38 | $comment = ''; 39 | $child = $this->firstChild(); 40 | $first = TRUE; 41 | while ($child) { 42 | if ($child instanceof CommentNode) { 43 | if ($first) { 44 | $first = FALSE; 45 | } 46 | else { 47 | $comment .= "\n"; 48 | } 49 | $comment .= $child->getCommentText(); 50 | } 51 | $child = $child->next; 52 | } 53 | return $comment; 54 | } 55 | 56 | /** 57 | * Set indent for document comment. 58 | * 59 | * @param string $indent 60 | * Whitespace to use as indent. 61 | * @return $this 62 | */ 63 | public function setIndent($indent) { 64 | // Normalize comment block. 65 | $this->removeIndent(); 66 | // Add indent to comment block. 67 | $this->addIndent($indent); 68 | return $this; 69 | } 70 | 71 | /** 72 | * Remove indent from document comment. 73 | * 74 | * @return $this 75 | */ 76 | public function removeIndent() { 77 | $this->children(function (Node $node) { 78 | return !($node instanceof CommentNode); 79 | })->remove(); 80 | return $this; 81 | } 82 | 83 | /** 84 | * Add indent to comment. 85 | * 86 | * @param string $whitespace 87 | * Additional whitespace to add. 88 | * @return $this 89 | */ 90 | public function addIndent($whitespace) { 91 | $has_indent = $this->children(function (Node $node) { 92 | return !($node instanceof CommentNode); 93 | })->count() > 0; 94 | if ($has_indent) { 95 | $this->children(Filter::isInstanceOf('\Pharborist\WhitespaceNode'))->each(function (WhitespaceNode $ws_node) use ($whitespace) { 96 | $ws_node->setText($ws_node->getText() . $whitespace); 97 | }); 98 | } 99 | else { 100 | $this->children()->before(Token::whitespace($whitespace)); 101 | } 102 | return $this; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/FunctionDeclarationNodeTest.php: -------------------------------------------------------------------------------- 1 | cloneAsMethodOf($class); 15 | $this->assertTrue($class->hasMethod('sing_goofy_song')); 16 | } 17 | 18 | public function testName() { 19 | /** @var \Pharborist\Functions\FunctionDeclarationNode $func */ 20 | $func = Parser::parseSnippet('function hello() {}'); 21 | $this->assertEquals('hello', $func->getName()->getText()); 22 | $func->setName('test'); 23 | $this->assertEquals('test', $func->getName()->getText()); 24 | } 25 | 26 | public function testReference() { 27 | /** @var \Pharborist\Functions\FunctionDeclarationNode $func */ 28 | $func = Parser::parseSnippet('function hello() {}'); 29 | $this->assertNull($func->getReference()); 30 | $func->setReference(TRUE); 31 | $this->assertNotNull($func->getReference()); 32 | $this->assertEquals('&', $func->getReference()->getText()); 33 | $this->assertEquals('function &hello() {}', $func->getText()); 34 | $func->setReference(FALSE); 35 | $this->assertNull($func->getReference()); 36 | $this->assertEquals('function hello() {}', $func->getText()); 37 | } 38 | 39 | public function testReturnType() { 40 | /** @var \Pharborist\Functions\FunctionDeclarationNode $func */ 41 | $func = Parser::parseSnippet('function hello() {}'); 42 | $this->assertFalse($func->hasReturnTypes()); 43 | $this->assertEquals(['void'], $func->getReturnTypes()); 44 | 45 | $func = Parser::parseSnippet('/** @return */ function hello() {}'); 46 | $this->assertFalse($func->hasReturnTypes()); 47 | $this->assertEquals(['void'], $func->getReturnTypes()); 48 | 49 | $func = Parser::parseSnippet('/** @return void */ function hello() {}'); 50 | $this->assertTrue($func->hasReturnTypes()); 51 | $this->assertEquals(['void'], $func->getReturnTypes()); 52 | 53 | $func = Parser::parseSnippet('/** @return string */ function hello() {}'); 54 | $this->assertTrue($func->hasReturnTypes()); 55 | $this->assertEquals(['string'], $func->getReturnTypes()); 56 | 57 | $func = Parser::parseSnippet('/** @return string|int */ function hello() {}'); 58 | $this->assertTrue($func->hasReturnTypes()); 59 | $this->assertEquals(['string', 'int'], $func->getReturnTypes()); 60 | } 61 | 62 | public function testMatchReflector() { 63 | $function = FunctionDeclarationNode::create('array_walk'); 64 | $reflector = new \ReflectionFunction('array_walk'); 65 | // $function->getReference() should return a TokenNode or NULL, which will 66 | // loosely evaluate to TRUE or FALSE 67 | $this->assertEquals($function->getReference(), $reflector->returnsReference()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Namespaces/NamespaceNode.php: -------------------------------------------------------------------------------- 1 | name; 71 | } 72 | 73 | /** 74 | * @return StatementBlockNode 75 | */ 76 | public function getBody() { 77 | return $this->body; 78 | } 79 | 80 | /** 81 | * Determine if the node belongs to this namespace. 82 | * 83 | * @param ClassNode|InterfaceNode|TraitNode|FunctionDeclarationNode|ConstantDeclarationNode $node 84 | * Node to test if owned by namespace. 85 | * 86 | * @return bool 87 | */ 88 | public function owns($node) { 89 | return $this === $node->getName()->getNamespace(); 90 | } 91 | 92 | /** 93 | * Get use declarations. 94 | * 95 | * @return UseDeclarationNode[]|\Pharborist\NodeCollection 96 | * Use declarations. 97 | */ 98 | public function getUseDeclarations() { 99 | return $this->body->getUseDeclarations(); 100 | } 101 | 102 | /** 103 | * Return mapping of class names to fully qualified names. 104 | * 105 | * @return array 106 | * Associative array of namespace alias to fully qualified names. 107 | */ 108 | public function getClassAliases() { 109 | return $this->body->getClassAliases(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/TokenNode.php: -------------------------------------------------------------------------------- 1 | type = $type; 55 | $this->text = $text; 56 | $this->filename = $filename; 57 | $this->lineNo = $lineNo; 58 | $this->newlineCount = $newlineCount; 59 | $this->colNo = $colNo; 60 | $this->byteOffset = $byteOffset; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getFilename() { 67 | return $this->filename; 68 | } 69 | 70 | /** 71 | * @return int 72 | */ 73 | public function getLineNumber() { 74 | return $this->lineNo; 75 | } 76 | 77 | /** 78 | * @return int 79 | */ 80 | public function getNewlineCount() { 81 | return $this->newlineCount; 82 | } 83 | 84 | /** 85 | * @return int 86 | */ 87 | public function getColumnNumber() { 88 | return $this->colNo; 89 | } 90 | 91 | /** 92 | * @return int 93 | */ 94 | public function getByteOffset() { 95 | return $this->byteOffset; 96 | } 97 | 98 | /** 99 | * @return int 100 | */ 101 | public function getByteLength() { 102 | return strlen($this->text); 103 | } 104 | 105 | /** 106 | * @return int 107 | */ 108 | public function getType() { 109 | return $this->type; 110 | } 111 | 112 | /** 113 | * @param int $type 114 | * @return string 115 | */ 116 | public static function typeName($type) { 117 | if (is_string($type)) { 118 | return $type; 119 | } 120 | else { 121 | return token_name($type); 122 | } 123 | } 124 | 125 | /** 126 | * @return string 127 | */ 128 | public function getTypeName() { 129 | return self::typeName($this->type); 130 | } 131 | 132 | /** 133 | * @return string 134 | */ 135 | public function getText() { 136 | return $this->text; 137 | } 138 | 139 | /** 140 | * @param string $text 141 | * @return $this 142 | */ 143 | public function setText($text) { 144 | $this->text = $text; 145 | return $this; 146 | } 147 | 148 | /** 149 | * @return string 150 | */ 151 | public function __toString() { 152 | return $this->text; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Functions/ArgumentTrait.php: -------------------------------------------------------------------------------- 1 | arguments; 23 | } 24 | 25 | /** 26 | * @return NodeCollection|ExpressionNode[] 27 | */ 28 | public function getArguments() { 29 | return $this->arguments->getItems(); 30 | } 31 | 32 | /** 33 | * @param mixed $argument 34 | * The argument to prepend. Can be an ExpressionNode or a scalar value, 35 | * which will be converted to an expression. 36 | * 37 | * @return $this 38 | * 39 | * @throws \InvalidArgumentException 40 | */ 41 | public function appendArgument($argument) { 42 | if (is_scalar($argument)) { 43 | $argument = Node::fromValue($argument); 44 | } 45 | 46 | if ($argument instanceof ExpressionNode) { 47 | /** @var Node $argument */ 48 | $this->arguments->appendItem($argument); 49 | } 50 | else { 51 | throw new \InvalidArgumentException(); 52 | } 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * @param mixed $argument 59 | * The argument to prepend. Can be an ExpressionNode or a scalar value, 60 | * which will be converted to an expression. 61 | * 62 | * @return $this 63 | * 64 | * @throws \InvalidArgumentException 65 | */ 66 | public function prependArgument($argument) { 67 | if (is_scalar($argument)) { 68 | $argument = Node::fromValue($argument); 69 | } 70 | 71 | if ($argument instanceof ExpressionNode) { 72 | /** @var Node $argument */ 73 | $this->arguments->prependItem($argument); 74 | } 75 | else { 76 | throw new \InvalidArgumentException(); 77 | } 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Insert argument before argument at index. 84 | * 85 | * @param mixed $argument 86 | * The argument to insert. Can be an ExpressionNode or a scalar value, 87 | * which will be converted to an expression. 88 | * @param int $index 89 | * Position to insert argument at. 90 | * @throws \OutOfBoundsException 91 | * Index out of bounds. 92 | * @throws \InvalidArgumentException 93 | * 94 | * @return $this 95 | */ 96 | public function insertArgument($argument, $index) { 97 | if (is_scalar($argument)) { 98 | $argument = Node::fromValue($argument); 99 | } 100 | 101 | if ($argument instanceof ExpressionNode) { 102 | /** @var Node $argument */ 103 | $this->arguments->insertItem($argument, $index); 104 | } 105 | else { 106 | throw new \InvalidArgumentException(); 107 | } 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * Remove all arguments. 114 | * 115 | * @return $this 116 | */ 117 | public function clearArguments() { 118 | $this->arguments->clear(); 119 | return $this; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /tests/ParameterTraitTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($function->hasParameter('a')); 16 | $this->assertTrue($function->hasParameter('$a')); 17 | $this->assertTrue($function->hasParameter($function->getParameterAtIndex(0))); 18 | $this->assertFalse($function->hasParameter('b')); 19 | $blorf = ParameterNode::create('$blorf'); 20 | $this->assertFalse($function->hasParameter($blorf)); 21 | 22 | $this->assertTrue($function->hasParameter('a', 'stdClass')); 23 | $this->assertFalse($function->hasParameter('a', 'Node')); 24 | 25 | $this->assertFalse($function->hasRequiredParameter('a', 'stdClass')); 26 | $this->assertTrue($function->hasOptionalParameter('a', 'stdClass')); 27 | 28 | $function->getParameterAtIndex(0)->setValue(NULL); 29 | $this->assertTrue($function->hasRequiredParameter('a', 'stdClass')); 30 | $this->assertFalse($function->hasOptionalParameter('a', 'stdClass')); 31 | } 32 | 33 | public function testGetParameters() { 34 | /** @var \Pharborist\Functions\FunctionDeclarationNode $function */ 35 | $function = Parser::parseSnippet('function foo($a, $a = 1, $b, $c) {}'); 36 | $parameters = $function->getParameters(); 37 | $a = $parameters['a']; 38 | $this->assertEquals('$a = 1', $a->getText()); 39 | $this->assertEquals('$a', $parameters[0]); 40 | $this->assertTrue(isset($parameters[2])); 41 | $this->assertTrue(isset($parameters['b'])); 42 | $this->assertEquals('$b', $parameters['b']); 43 | $this->assertEquals('$c', $parameters[3]); 44 | } 45 | 46 | /** 47 | * @depends testHasParameter 48 | */ 49 | public function testAppendParameter() { 50 | /** @var \Pharborist\Functions\FunctionDeclarationNode $function */ 51 | $function = Parser::parseSnippet('function baz() {}'); 52 | 53 | $function->appendParameter(ParameterNode::create('$neo')); 54 | $this->assertTrue($function->hasParameter('neo')); 55 | 56 | $function->appendParameter(function(FunctionDeclarationNode $function) { 57 | return ParameterNode::create('$trinity'); 58 | }); 59 | $this->assertTrue($function->hasParameter('trinity')); 60 | } 61 | 62 | /** 63 | * @expectedException \InvalidArgumentException 64 | */ 65 | public function testHasParamterInvalidArgumentException() { 66 | Parser::parseSnippet('function foo($bar) {}')->hasParameter(1); 67 | } 68 | 69 | /** 70 | * @requires PHP 5.6 71 | */ 72 | public function testIsVariadic() { 73 | $doc = <<<'END' 74 | children(Filter::isFunction('foo'))->get(0); 80 | $this->assertTrue($func->isVariadic()); 81 | 82 | $doc = <<<'END' 83 | children(Filter::isFunction('baz'))->get(0); 88 | $this->assertFalse($func->isVariadic()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/ArgumentTraitTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('\Pharborist\Functions\FunctionCallNode', $call); 13 | $this->assertCount(0, $call->getArguments()); 14 | 15 | $call->appendArgument(1)->appendArgument('hohoho'); 16 | $arguments = $call->getArguments(); 17 | $this->assertCount(2, $arguments); 18 | $this->assertInstanceOf('\Pharborist\Types\IntegerNode', $arguments[0]); 19 | $this->assertInstanceof('\Pharborist\Types\StringNode', $arguments[1]); 20 | 21 | $pi = FloatNode::fromValue(3.141); 22 | $call->appendArgument($pi); 23 | $this->assertSame($pi, $call->getArguments()->get(2)); 24 | } 25 | 26 | public function testPrependArgument() { 27 | /** @var \Pharborist\Functions\FunctionCallNode $call */ 28 | $call = Parser::parseExpression('foo()'); 29 | 30 | $call->prependArgument('wozwoz'); 31 | $arguments = $call->getArguments(); 32 | $this->assertCount(1, $arguments); 33 | $this->assertInstanceOf('\Pharborist\Types\StringNode', $arguments[0]); 34 | 35 | $bazbaz = StringNode::fromValue('bazbaz'); 36 | $call->prependArgument($bazbaz); 37 | $arguments = $call->getArguments(); 38 | $this->assertCount(2, $arguments); 39 | $this->assertSame($bazbaz, $arguments[0]); 40 | } 41 | 42 | /** 43 | * @expectedException \InvalidArgumentException 44 | */ 45 | public function testPrependInvalidArgument() { 46 | /** @var \Pharborist\Functions\FunctionCallNode $call */ 47 | $call = Parser::parseExpression('foo()'); 48 | $call->prependArgument(NULL); 49 | } 50 | 51 | /** 52 | * @expectedException \InvalidArgumentException 53 | */ 54 | public function testAppendInvalidArgument() { 55 | /** @var \Pharborist\Functions\FunctionCallNode $call */ 56 | $call = Parser::parseExpression('foo()'); 57 | $call->appendArgument(NULL); 58 | } 59 | 60 | public function testInsertArgument() { 61 | /** @var \Pharborist\Functions\FunctionCallNode $call */ 62 | $call = Parser::parseExpression('foo()'); 63 | $call->insertArgument('a', 0); 64 | $arguments = $call->getArguments(); 65 | $this->assertCount(1, $arguments); 66 | $this->assertInstanceOf('\Pharborist\Types\StringNode', $arguments[0]); 67 | } 68 | 69 | /** 70 | * @expectedException \InvalidArgumentException 71 | */ 72 | public function testInsertInvalidArgument() { 73 | /** @var \Pharborist\Functions\FunctionCallNode $call */ 74 | $call = Parser::parseExpression('foo()'); 75 | $call->insertArgument(NULL, 0); 76 | } 77 | 78 | /** 79 | * @expectedException \OutOfBoundsException 80 | */ 81 | public function testInsertInvalidIndex() { 82 | /** @var \Pharborist\Functions\FunctionCallNode $call */ 83 | $call = Parser::parseExpression('foo()'); 84 | $call->insertArgument(42, 1); 85 | } 86 | 87 | public function testClearArguments() { 88 | /** @var \Pharborist\Functions\FunctionCallNode $call */ 89 | $call = Parser::parseExpression('foo(42)'); 90 | $this->assertCount(1, $call->getArguments()); 91 | $call->clearArguments(); 92 | $this->assertTrue($call->getArguments()->isEmpty()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/AnonymousFunctionNodeTest.php: -------------------------------------------------------------------------------- 1 | assertNull($func->getReference()); 11 | $func->setReference(TRUE); 12 | $this->assertNotNull($func->getReference()); 13 | $this->assertEquals('&', $func->getReference()->getText()); 14 | $this->assertEquals('function &() {}', $func->getText()); 15 | 16 | $func->setReference(FALSE); 17 | $this->assertNull($func->getReference()); 18 | $this->assertEquals('function () {}', $func->getText()); 19 | } 20 | 21 | public function testLexicalVariables() { 22 | /** @var AnonymousFunctionNode $func */ 23 | $func = Parser::parseExpression('function () {}'); 24 | $func->appendLexicalVariable(Token::variable('$a')); 25 | $this->assertTrue($func->hasLexicalVariables()); 26 | $lexical_variables = $func->getLexicalVariables(); 27 | $this->assertCount(1, $func->getLexicalVariables()); 28 | $this->assertEquals('$a', $lexical_variables[0]->getText()); 29 | $this->assertEquals($lexical_variables, $func->getLexicalVariableList()->getItems()); 30 | $this->assertEquals('function () use ($a) {}', $func->getText()); 31 | $this->assertEquals('use', $func->getLexicalUse()->getText()); 32 | $this->assertEquals('(', $func->getLexicalOpenParen()); 33 | $this->assertNotSame($func->getOpenParen(), $func->getLexicalOpenParen()); 34 | $this->assertEquals(')', $func->getLexicalCloseParen()); 35 | $this->assertNotSame($func->getCloseParen(), $func->getLexicalCloseParen()); 36 | 37 | $func->prependLexicalVariable(Token::variable('$before')); 38 | $lexical_variables = $func->getLexicalVariables(); 39 | $this->assertCount(2, $func->getLexicalVariables()); 40 | $this->assertEquals('$before', $lexical_variables[0]->getText()); 41 | $this->assertEquals('$a', $lexical_variables[1]->getText()); 42 | $this->assertEquals($lexical_variables, $func->getLexicalVariableList()->getItems()); 43 | $this->assertEquals('function () use ($before, $a) {}', $func->getText()); 44 | 45 | $func->insertLexicalVariable(Token::variable('$middle'), 1); 46 | $lexical_variables = $func->getLexicalVariables(); 47 | $this->assertCount(3, $func->getLexicalVariables()); 48 | $this->assertEquals('$before', $lexical_variables[0]->getText()); 49 | $this->assertEquals('$middle', $lexical_variables[1]->getText()); 50 | $this->assertEquals('$a', $lexical_variables[2]->getText()); 51 | $this->assertEquals($lexical_variables, $func->getLexicalVariableList()->getItems()); 52 | $this->assertEquals('function () use ($before, $middle, $a) {}', $func->getText()); 53 | 54 | $func->clearLexicalVariables(); 55 | $this->assertFalse($func->hasLexicalVariables()); 56 | $this->assertEquals('function () {}', $func->getText()); 57 | } 58 | 59 | /** 60 | * @expectedException \OutOfBoundsException 61 | */ 62 | public function testInsertNegativeIndex() { 63 | /** @var AnonymousFunctionNode $func */ 64 | $func = Parser::parseExpression('function () use ($b) {}'); 65 | $func->insertLexicalVariable(Token::variable('$a'), -1); 66 | } 67 | 68 | /** 69 | * @expectedException \OutOfBoundsException 70 | */ 71 | public function testInsertOutOfBoundsEmpty() { 72 | /** @var AnonymousFunctionNode $func */ 73 | $func = Parser::parseExpression('function () {}'); 74 | $func->insertLexicalVariable(Token::variable('$a'), 1); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/CommentNode.php: -------------------------------------------------------------------------------- 1 | commentType = self::DOC; 35 | } 36 | else { 37 | $prefix = substr($prefix, 0, 2); 38 | if ($prefix === self::BLOCK) { 39 | $this->commentType = self::BLOCK; 40 | } 41 | elseif ($prefix === self::SINGLE) { 42 | $this->commentType = self::SINGLE; 43 | } 44 | elseif ($prefix[0] === self::HASH) { 45 | $this->commentType = self::HASH; 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Create line comment. 52 | * 53 | * @param string $comment 54 | * Comment without leading prefix. 55 | * @return CommentNode|LineCommentBlockNode 56 | */ 57 | public static function create($comment) { 58 | $comment = trim($comment); 59 | $nl_count = substr_count($comment, "\n"); 60 | if ($nl_count > 1) { 61 | return LineCommentBlockNode::create($comment); 62 | } 63 | else { 64 | return new CommentNode(T_COMMENT, '// ' . $comment . "\n"); 65 | } 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function getCommentType() { 72 | return $this->commentType; 73 | } 74 | 75 | /** 76 | * @return bool 77 | */ 78 | public function isLineComment() { 79 | return $this->commentType === self::SINGLE || $this->commentType === self::HASH; 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function getCommentText() { 86 | switch ($this->commentType) { 87 | case self::SINGLE: 88 | case self::HASH: 89 | $comment_text = rtrim(substr($this->text, strlen($this->commentType))); 90 | if ($comment_text[0] === ' ') { 91 | $comment_text = substr($comment_text, 1); 92 | } 93 | return $comment_text; 94 | case self::DOC: 95 | $lines = explode("\n", $this->text); 96 | if (count($lines) === 1) { 97 | return trim(substr($this->text, 3, -2)); 98 | } 99 | else { 100 | $last_index = count($lines) - 1; 101 | unset($lines[0]); // ignore first line 102 | unset($lines[$last_index]); // ignore last line 103 | $comment = ''; 104 | $first = TRUE; 105 | foreach ($lines as $line) { 106 | $line = trim($line); 107 | if ($line !== '' && $line[0] === '*') { 108 | if (!$first) { 109 | $comment .= "\n"; 110 | } 111 | else { 112 | $first = FALSE; 113 | } 114 | $comment .= substr($line, 2); 115 | } 116 | } 117 | return $comment; 118 | } 119 | case self::BLOCK: 120 | return trim(substr($this->text, 2, -2)); 121 | default: 122 | throw new \LogicException("Unhandled comment type in CommentNode::getCommentText()"); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Namespaces/UseDeclarationStatementNode.php: -------------------------------------------------------------------------------- 1 | useFunction || $this->useConst) { 50 | return FALSE; 51 | } 52 | if ($class_name) { 53 | foreach ($this->getDeclarations() as $declaration) { 54 | if ($declaration->getName()->getPath() === $class_name) { 55 | return TRUE; 56 | } 57 | } 58 | return FALSE; 59 | } 60 | else { 61 | return TRUE; 62 | } 63 | } 64 | 65 | /** 66 | * Test whether use declaration imports a function. 67 | * 68 | * @param string|NULL $function_name 69 | * (Optional) Function name to check if being imported by use statement. 70 | * 71 | * @return bool 72 | * TRUE if this use declaration imports function. 73 | */ 74 | public function importsFunction($function_name = NULL) { 75 | if (!$this->useFunction) { 76 | return FALSE; 77 | } 78 | if ($function_name) { 79 | foreach ($this->getDeclarations() as $declaration) { 80 | if ($declaration->getName()->getPath() === $function_name) { 81 | return TRUE; 82 | } 83 | } 84 | return FALSE; 85 | } 86 | else { 87 | return TRUE; 88 | } 89 | } 90 | 91 | /** 92 | * Test whether use declaration imports a constant. 93 | * 94 | * @param string|NULL $const_name 95 | * (Optional) Constant name to check if being imported by use statement. 96 | * 97 | * @return bool 98 | * TRUE if this use declaration imports constant. 99 | */ 100 | public function importsConst($const_name = NULL) { 101 | if (!$this->useConst) { 102 | return FALSE; 103 | } 104 | if ($const_name) { 105 | foreach ($this->getDeclarations() as $declaration) { 106 | if ($declaration->getName()->getPath() === $const_name) { 107 | return TRUE; 108 | } 109 | } 110 | return FALSE; 111 | } 112 | else { 113 | return TRUE; 114 | } 115 | } 116 | 117 | /** 118 | * @var CommaListNode 119 | */ 120 | protected $declarations; 121 | 122 | /** 123 | * @return CommaListNode 124 | */ 125 | public function getDeclarationList() { 126 | return $this->declarations; 127 | } 128 | 129 | /** 130 | * @return NodeCollection|UseDeclarationNode[] 131 | */ 132 | public function getDeclarations() { 133 | return $this->declarations->getItems(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/ArrayLookupNode.php: -------------------------------------------------------------------------------- 1 | addChild($array, 'array'); 37 | $node->addChild(Token::openBracket()); 38 | /** @var Node $key */ 39 | $node->addChild($key, 'key'); 40 | $node->addChild(Token::closeBracket()); 41 | return $node; 42 | } 43 | 44 | /** 45 | * @return Node 46 | */ 47 | public function getArray() { 48 | return $this->array; 49 | } 50 | 51 | /** 52 | * @return \Pharborist\Node[] 53 | */ 54 | public function getKeys() { 55 | $keys = []; 56 | 57 | if ($this->key) { 58 | $keys[] = clone $this->key; 59 | } 60 | if ($this->array instanceof ArrayLookupNode) { 61 | $keys = array_merge($this->array->getKeys(), $keys); 62 | } 63 | 64 | return $keys; 65 | } 66 | 67 | /** 68 | * Returns a specific key in the lookup. 69 | * 70 | * @param integer $index 71 | * The index of the key to return. 72 | * 73 | * @return \Pharborist\Node 74 | * 75 | * @throws 76 | * \InvalidArgumentException if $index is not an integer. 77 | * \OutOfBoundsException if $index is less than zero or greater than the 78 | * number of keys in the lookup. 79 | */ 80 | public function getKey($index = 0) { 81 | $keys = $this->getKeys(); 82 | 83 | if (!is_integer($index)) { 84 | throw new \InvalidArgumentException(); 85 | } 86 | if ($index < 0 || $index >= count($keys)) { 87 | throw new \OutOfBoundsException(); 88 | } 89 | return $keys[$index]; 90 | } 91 | 92 | /** 93 | * Returns TRUE if all keys in the lookup are scalar. So a lookup like 94 | * $foo['bar']['baz'][0] will be TRUE, but $foo[$bar]['baz'][0] won't. 95 | * 96 | * @return boolean 97 | */ 98 | public function hasScalarKeys() { 99 | if ($this->key instanceof ScalarNode) { 100 | return $this->array instanceof ArrayLookupNode ? $this->array->hasScalarKeys() : TRUE; 101 | } 102 | else { 103 | return FALSE; 104 | } 105 | } 106 | 107 | /** 108 | * Returns every key in the lookup. For example, $foo['bar']['baz'][5] will 109 | * return ['bar', 'baz', 5]. 110 | * 111 | * @return mixed[] 112 | * 113 | * @throws \DomainException if the lookup contains any non-scalar keys. 114 | */ 115 | public function extractKeys() { 116 | if (!$this->hasScalarKeys()) { 117 | throw new \DomainException('Cannot extract non-scalar keys from array lookup ' . $this); 118 | } 119 | return array_map(function(ScalarNode $key) { return $key->toValue(); }, $this->getKeys()); 120 | } 121 | 122 | /** 123 | * Returns the root of the lookup. 124 | * 125 | * For example, given an expression like $foo['bar']['baz'], 126 | * this method will return a VariableNode for $foo. 127 | * 128 | * @return Node 129 | */ 130 | public function getRootArray() { 131 | return $this->array instanceof ArrayLookupNode ? $this->array->getRootArray() : $this->array; 132 | } 133 | } 134 | --------------------------------------------------------------------------------