Initial Drupal 11 with DDEV setup
This commit is contained in:
438
vendor/mck89/peast/lib/Peast/Syntax/JSX/Parser.php
vendored
Normal file
438
vendor/mck89/peast/lib/Peast/Syntax/JSX/Parser.php
vendored
Normal file
@ -0,0 +1,438 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user