439 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			439 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| 
								 | 
							
								<?php
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * This file is part of the Peast package
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * (c) Marco Marchiò <marco.mm89@gmail.com>
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * For the full copyright and license information refer to the LICENSE file
							 | 
						||
| 
								 | 
							
								 * distributed with this source code
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								namespace Peast\Syntax\JSX;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Peast\Syntax\Token;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * JSX parser trait
							 | 
						||
| 
								 | 
							
								 * 
							 | 
						||
| 
								 | 
							
								 * @author Marco Marchiò <marco.mm89@gmail.com>
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								trait Parser
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Creates a JSX node
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @param string $nodeType Node's type
							 | 
						||
| 
								 | 
							
								     * @param mixed  $position Node's start position
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\Node
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function createJSXNode($nodeType, $position)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        return $this->createNode("JSX\\$nodeType", $position);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx fragment
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\JSX\JSXFragment|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXFragment()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $startOpeningToken = $this->scanner->getToken();
							 | 
						||
| 
								 | 
							
								        if (!$startOpeningToken || $startOpeningToken->value !== "<") {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $endOpeningToken = $this->scanner->getNextToken();
							 | 
						||
| 
								 | 
							
								        if (!$endOpeningToken || $endOpeningToken->value !== ">") {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								        $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $children = $this->parseJSXChildren();
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        if (!($startClosingToken = $this->scanner->consume("<")) ||
							 | 
						||
| 
								 | 
							
								            !$this->scanner->consume("/") ||
							 | 
						||
| 
								 | 
							
								            !$this->scanner->reconsumeCurrentTokenInJSXMode() ||
							 | 
						||
| 
								 | 
							
								            $endOpeningToken->value !== ">") {
							 | 
						||
| 
								 | 
							
								            $this->error();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Opening tag
							 | 
						||
| 
								 | 
							
								        $openingNode = $this->createJSXNode(
							 | 
						||
| 
								 | 
							
								            "JSXOpeningFragment",
							 | 
						||
| 
								 | 
							
								            $startOpeningToken
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        $this->completeNode(
							 | 
						||
| 
								 | 
							
								            $openingNode,
							 | 
						||
| 
								 | 
							
								            $endOpeningToken->location->end
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Closing tag
							 | 
						||
| 
								 | 
							
								        $closingNode = $this->createJSXNode(
							 | 
						||
| 
								 | 
							
								            "JSXClosingFragment",
							 | 
						||
| 
								 | 
							
								            $startClosingToken
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        $this->completeNode($closingNode);
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Fragment
							 | 
						||
| 
								 | 
							
								        $node = $this->createJSXNode("JSXFragment", $startOpeningToken);
							 | 
						||
| 
								 | 
							
								        $node->setOpeningFragment($openingNode);
							 | 
						||
| 
								 | 
							
								        $node->setClosingFragment($closingNode);
							 | 
						||
| 
								 | 
							
								        if ($children) {
							 | 
						||
| 
								 | 
							
								            $node->setChildren($children);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return $this->completeNode($node);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a group of jsx children
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\Node[]|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXChildren()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $children = array();
							 | 
						||
| 
								 | 
							
								        while ($child = $this->parseJSXChild()) {
							 | 
						||
| 
								 | 
							
								            $children[] = $child;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return count($children) ? $children : null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx child
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\Node|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXChild()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        if ($node = $this->parseJSXText()) {
							 | 
						||
| 
								 | 
							
								            return $node;
							 | 
						||
| 
								 | 
							
								        } elseif ($node = $this->parseJSXFragment()) {
							 | 
						||
| 
								 | 
							
								            return $node;
							 | 
						||
| 
								 | 
							
								        } elseif($node = $this->parseJSXElement()) {
							 | 
						||
| 
								 | 
							
								            return $node;
							 | 
						||
| 
								 | 
							
								        } elseif ($startToken = $this->scanner->consume("{")) {
							 | 
						||
| 
								 | 
							
								            $spread = $this->scanner->consume("...");
							 | 
						||
| 
								 | 
							
								            $exp = $this->parseAssignmentExpression();
							 | 
						||
| 
								 | 
							
								            $midPos = $this->scanner->getPosition();
							 | 
						||
| 
								 | 
							
								            if (($spread && !$exp) || !$this->scanner->consume("}")) {
							 | 
						||
| 
								 | 
							
								                $this->error();
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            $node = $this->createJSXNode(
							 | 
						||
| 
								 | 
							
								                $spread ? "JSXSpreadChild" : "JSXExpressionContainer",
							 | 
						||
| 
								 | 
							
								                $startToken
							 | 
						||
| 
								 | 
							
								            );
							 | 
						||
| 
								 | 
							
								            if (!$exp) {
							 | 
						||
| 
								 | 
							
								                $exp = $this->createJSXNode("JSXEmptyExpression", $midPos);
							 | 
						||
| 
								 | 
							
								                $this->completeNode($exp, $midPos);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            $node->setExpression($exp);
							 | 
						||
| 
								 | 
							
								            return $this->completeNode($node);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx text
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\JSX\JSXText|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXText()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        if (!($token = $this->scanner->reconsumeCurrentTokenAsJSXText())) {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								        $node = $this->createJSXNode("JSXText", $token);
							 | 
						||
| 
								 | 
							
								        $node->setRaw($token->value);
							 | 
						||
| 
								 | 
							
								        return $this->completeNode($node, $token->location->end);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx element
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\JSX\JSXElement|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXElement()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $startOpeningToken = $this->scanner->getToken();
							 | 
						||
| 
								 | 
							
								        if (!$startOpeningToken || $startOpeningToken->value !== "<") {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $nextToken = $this->scanner->getNextToken();
							 | 
						||
| 
								 | 
							
								        if ($nextToken && $nextToken->value === "/") {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        if (!($name = $this->parseJSXIdentifierOrMemberExpression())) {
							 | 
						||
| 
								 | 
							
								            $this->error();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $attributes = $this->parseJSXAttributes();
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $selfClosing = $this->scanner->consume("/");
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $endOpeningToken = $this->scanner->reconsumeCurrentTokenInJSXMode();
							 | 
						||
| 
								 | 
							
								        if (!$endOpeningToken || $endOpeningToken->value !== ">") {
							 | 
						||
| 
								 | 
							
								            $this->error();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        if (!$selfClosing) {
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            $children = $this->parseJSXChildren();
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            if (
							 | 
						||
| 
								 | 
							
								                ($startClosingToken = $this->scanner->consume("<")) &&
							 | 
						||
| 
								 | 
							
								                $this->scanner->consume("/") &&
							 | 
						||
| 
								 | 
							
								                ($closingName = $this->parseJSXIdentifierOrMemberExpression()) &&
							 | 
						||
| 
								 | 
							
								                ($endClosingToken = $this->scanner->reconsumeCurrentTokenInJSXMode()) &&
							 | 
						||
| 
								 | 
							
								                ($endClosingToken->value === ">")
							 | 
						||
| 
								 | 
							
								            ) {
							 | 
						||
| 
								 | 
							
								                $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								                if (!$this->isSameJSXElementName($name, $closingName)) {
							 | 
						||
| 
								 | 
							
								                    $this->error("Closing tag does not match opening tag");
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                $this->error();
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Opening tag
							 | 
						||
| 
								 | 
							
								        $openingNode = $this->createJSXNode(
							 | 
						||
| 
								 | 
							
								            "JSXOpeningElement",
							 | 
						||
| 
								 | 
							
								            $startOpeningToken
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        $openingNode->setName($name);
							 | 
						||
| 
								 | 
							
								        $openingNode->setSelfClosing($selfClosing);
							 | 
						||
| 
								 | 
							
								        if ($attributes) {
							 | 
						||
| 
								 | 
							
								            $openingNode->setAttributes($attributes);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        $this->completeNode(
							 | 
						||
| 
								 | 
							
								            $openingNode,
							 | 
						||
| 
								 | 
							
								            $endOpeningToken->location->end
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Closing tag
							 | 
						||
| 
								 | 
							
								        $closingNode = null;
							 | 
						||
| 
								 | 
							
								        if (!$selfClosing) {
							 | 
						||
| 
								 | 
							
								            $closingNode = $this->createJSXNode(
							 | 
						||
| 
								 | 
							
								                "JSXClosingElement",
							 | 
						||
| 
								 | 
							
								                $startClosingToken
							 | 
						||
| 
								 | 
							
								            );
							 | 
						||
| 
								 | 
							
								            $closingNode->setName($closingName);
							 | 
						||
| 
								 | 
							
								            $this->completeNode($closingNode);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Element
							 | 
						||
| 
								 | 
							
								        $node = $this->createJSXNode("JSXElement", $startOpeningToken);
							 | 
						||
| 
								 | 
							
								        $node->setOpeningElement($openingNode);
							 | 
						||
| 
								 | 
							
								        if ($closingNode) {
							 | 
						||
| 
								 | 
							
								            $node->setClosingElement($closingNode);
							 | 
						||
| 
								 | 
							
								            if ($children) {
							 | 
						||
| 
								 | 
							
								                $node->setChildren($children);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return $this->completeNode($node);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx identifier, namespaced identifier or member expression
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @param bool $allowMember True to allow member expressions
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\Node|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXIdentifierOrMemberExpression($allowMember = true)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $idToken = $this->scanner->reconsumeCurrentTokenInJSXMode();
							 | 
						||
| 
								 | 
							
								        if (!$idToken || $idToken->type !== Token::TYPE_JSX_IDENTIFIER) {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $idNode = $this->createJSXNode("JSXIdentifier", $idToken);
							 | 
						||
| 
								 | 
							
								        $idNode->setName($idToken->value);
							 | 
						||
| 
								 | 
							
								        $idNode = $this->completeNode($idNode);
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Namespaced identifier
							 | 
						||
| 
								 | 
							
								        if ($this->scanner->consume(":")) {
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            $idToken2 = $this->scanner->reconsumeCurrentTokenInJSXMode();
							 | 
						||
| 
								 | 
							
								            if (!$idToken2 || $idToken2->type !== Token::TYPE_JSX_IDENTIFIER) {
							 | 
						||
| 
								 | 
							
								                $this->error();
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            $idNode2 = $this->createJSXNode("JSXIdentifier", $idToken2);
							 | 
						||
| 
								 | 
							
								            $idNode2->setName($idToken2->value);
							 | 
						||
| 
								 | 
							
								            $idNode2 = $this->completeNode($idNode2);
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            $node = $this->createJSXNode("JSXNamespacedName", $idToken);
							 | 
						||
| 
								 | 
							
								            $node->setNamespace($idNode);
							 | 
						||
| 
								 | 
							
								            $node->setName($idNode2);
							 | 
						||
| 
								 | 
							
								            return $this->completeNode($node);
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Get following identifiers
							 | 
						||
| 
								 | 
							
								        $nextIds = array();
							 | 
						||
| 
								 | 
							
								        if ($allowMember) {
							 | 
						||
| 
								 | 
							
								            while ($this->scanner->consume(".")) {
							 | 
						||
| 
								 | 
							
								                $nextId = $this->scanner->reconsumeCurrentTokenInJSXMode();
							 | 
						||
| 
								 | 
							
								                if (!$nextId || $nextId->type !== Token::TYPE_JSX_IDENTIFIER) {
							 | 
						||
| 
								 | 
							
								                    $this->error();
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								                $nextIds[] = $nextId;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        //Create the member expression if required
							 | 
						||
| 
								 | 
							
								        $objectNode = $idNode;
							 | 
						||
| 
								 | 
							
								        foreach ($nextIds as $nid) {
							 | 
						||
| 
								 | 
							
								            $propEnd = $nid->location->end;
							 | 
						||
| 
								 | 
							
								            $propNode = $this->createJSXNode("JSXIdentifier", $nid);
							 | 
						||
| 
								 | 
							
								            $propNode->setName($nid->value);
							 | 
						||
| 
								 | 
							
								            $propNode = $this->completeNode($propNode, $propEnd);
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            $node = $this->createJSXNode("JSXMemberExpression", $objectNode);
							 | 
						||
| 
								 | 
							
								            $node->setObject($objectNode);
							 | 
						||
| 
								 | 
							
								            $node->setProperty($propNode);
							 | 
						||
| 
								 | 
							
								            $objectNode = $this->completeNode($node, $propEnd);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        return $objectNode;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx attributes list
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\Node[]|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXAttributes()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $attributes = array();
							 | 
						||
| 
								 | 
							
								        while (
							 | 
						||
| 
								 | 
							
								            ($attr = $this->parseJSXSpreadAttribute()) ||
							 | 
						||
| 
								 | 
							
								            ($attr = $this->parseJSXAttribute())
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								            $attributes[] = $attr;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return count($attributes) ? $attributes : null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx spread attribute
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\JSX\JSXSpreadAttribute|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXSpreadAttribute()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        if (!($openToken = $this->scanner->consume("{"))) {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								            $this->scanner->consume("...") &&
							 | 
						||
| 
								 | 
							
								            ($exp = $this->parseAssignmentExpression()) &&
							 | 
						||
| 
								 | 
							
								            $this->scanner->consume("}")
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								            $node = $this->createJSXNode("JSXSpreadAttribute", $openToken);
							 | 
						||
| 
								 | 
							
								            $node->setArgument($exp);
							 | 
						||
| 
								 | 
							
								            return $this->completeNode($node);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $this->error();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Parses a jsx spread attribute
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return \Peast\Syntax\Node\JSX\JSXSpreadAttribute|null
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function parseJSXAttribute()
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        if (!($name = $this->parseJSXIdentifierOrMemberExpression(false))) {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $value = null;
							 | 
						||
| 
								 | 
							
								        if ($this->scanner->consume("=")) {
							 | 
						||
| 
								 | 
							
								            $strToken = $this->scanner->reconsumeCurrentTokenInJSXMode();
							 | 
						||
| 
								 | 
							
								            if ($strToken && $strToken->type === Token::TYPE_STRING_LITERAL) {
							 | 
						||
| 
								 | 
							
								                $this->scanner->consumeToken();
							 | 
						||
| 
								 | 
							
								                $value = $this->createNode("StringLiteral", $strToken);
							 | 
						||
| 
								 | 
							
								                $value->setRaw($strToken->value);
							 | 
						||
| 
								 | 
							
								                $value = $this->completeNode($value);
							 | 
						||
| 
								 | 
							
								            } elseif ($startExp = $this->scanner->consume("{")) {
							 | 
						||
| 
								 | 
							
								                
							 | 
						||
| 
								 | 
							
								                if (
							 | 
						||
| 
								 | 
							
								                    ($exp = $this->parseAssignmentExpression()) &&
							 | 
						||
| 
								 | 
							
								                    $this->scanner->consume("}")
							 | 
						||
| 
								 | 
							
								                ) {
							 | 
						||
| 
								 | 
							
								                    
							 | 
						||
| 
								 | 
							
								                    $value = $this->createJSXNode(
							 | 
						||
| 
								 | 
							
								                        "JSXExpressionContainer",
							 | 
						||
| 
								 | 
							
								                        $startExp
							 | 
						||
| 
								 | 
							
								                    );
							 | 
						||
| 
								 | 
							
								                    $value->setExpression($exp);
							 | 
						||
| 
								 | 
							
								                    $value = $this->completeNode($value);
							 | 
						||
| 
								 | 
							
								                    
							 | 
						||
| 
								 | 
							
								                } else {
							 | 
						||
| 
								 | 
							
								                    $this->error();
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                
							 | 
						||
| 
								 | 
							
								            } elseif (
							 | 
						||
| 
								 | 
							
								                !($value = $this->parseJSXFragment()) &&
							 | 
						||
| 
								 | 
							
								                !($value = $this->parseJSXElement())
							 | 
						||
| 
								 | 
							
								            ) {
							 | 
						||
| 
								 | 
							
								                $this->error();
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        $node = $this->createJSXNode("JSXAttribute", $name);
							 | 
						||
| 
								 | 
							
								        $node->setName($name);
							 | 
						||
| 
								 | 
							
								        if ($value) {
							 | 
						||
| 
								 | 
							
								            $node->setValue($value);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return $this->completeNode($node);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Checks that 2 tag names are equal
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @param \Peast\Syntax\Node\Node   $n1 First name
							 | 
						||
| 
								 | 
							
								     * @param \Peast\Syntax\Node\Node   $n2 Second name
							 | 
						||
| 
								 | 
							
								     * 
							 | 
						||
| 
								 | 
							
								     * @return bool
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    protected function isSameJSXElementName($n1, $n2)
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        $type = $n1->getType();
							 | 
						||
| 
								 | 
							
								        if ($type !== $n2->getType()) {
							 | 
						||
| 
								 | 
							
								            return false;
							 | 
						||
| 
								 | 
							
								        } elseif ($type === "JSXNamespacedName") {
							 | 
						||
| 
								 | 
							
								            return $this->isSameJSXElementName(
							 | 
						||
| 
								 | 
							
								                $n1->getNamespace(), $n2->getNamespace()
							 | 
						||
| 
								 | 
							
								            ) && $this->isSameJSXElementName(
							 | 
						||
| 
								 | 
							
								                $n1->getName(), $n2->getName()
							 | 
						||
| 
								 | 
							
								            );
							 | 
						||
| 
								 | 
							
								        } elseif ($type === "JSXMemberExpression") {
							 | 
						||
| 
								 | 
							
								            return $this->isSameJSXElementName(
							 | 
						||
| 
								 | 
							
								                $n1->getObject(), $n2->getObject()
							 | 
						||
| 
								 | 
							
								            ) && $this->isSameJSXElementName(
							 | 
						||
| 
								 | 
							
								                $n1->getProperty(), $n2->getProperty()
							 | 
						||
| 
								 | 
							
								            );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return $type === "JSXIdentifier" && $n1->getName() === $n2->getName();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |