| Current Path : /home/rtorresani/www/vendor/magento/magento-coding-standard/PHP_CodeSniffer/Tokenizers/ |
| Current File : //home/rtorresani/www/vendor/magento/magento-coding-standard/PHP_CodeSniffer/Tokenizers/GRAPHQL.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace PHP_CodeSniffer\Tokenizers;
use GraphQL\Language\Lexer;
use GraphQL\Language\Source;
use GraphQL\Language\Token;
/**
* Implements a tokenizer for GraphQL files.
*/
class GRAPHQL extends Tokenizer
{
/**
* Defines how GraphQL token types are mapped to PHP token types.
*
* This is a complete list of all token types supported by <kbd>webonyx/graphql-php</var>. <knd>null</kbd> values
* are automatically mapped to <kbd>T_STRING</kbd> but are noted as <kbd>null</kbd> in this list to improve
* maintenance at a glance.
*
* @var array
*/
private $tokenTypeMap = [
Token::AT => 'T_DOC_COMMENT_TAG',
Token::BANG => 'T_BOOLEAN_NOT',
Token::BLOCK_STRING => 'T_COMMENT',
Token::BRACE_L => 'T_OPEN_CURLY_BRACKET',
Token::BRACE_R => 'T_CLOSE_CURLY_BRACKET',
Token::BRACKET_L => 'T_OPEN_SQUARE_BRACKET',
Token::BRACKET_R => 'T_CLOSE_SQUARE_BRACKET',
Token::COLON => 'T_COLON',
Token::COMMENT => 'T_COMMENT',
Token::DOLLAR => 'T_DOLLAR',
Token::EOF => 'T_CLOSE_TAG',
Token::EQUALS => 'T_EQUAL',
Token::FLOAT => null,
Token::INT => null,
Token::NAME => 'T_STRING',
Token::PAREN_L => 'T_OPEN_PARENTHESIS',
Token::PAREN_R => 'T_CLOSE_PARENTHESIS',
Token::PIPE => null,
Token::SPREAD => 'T_ELLIPSIS',
Token::SOF => 'T_OPEN_TAG',
Token::STRING => 'T_STRING',
];
/**
* Defines how special keywords are mapped to PHP token types
*
* @var array
*/
private $keywordTokenTypeMap = [
'enum' => 'T_CLASS',
'extend' => 'T_EXTENDS',
'interface' => 'T_INTERFACE',
'implements' => 'T_IMPLEMENTS',
'type' => 'T_CLASS',
'union' => 'T_CLASS',
'query' => 'T_FUNCTION',
'mutation' => 'T_FUNCTION',
];
/**
* @inheritDoc
*/
public function processAdditional()
{
$this->logVerbose('*** START ADDITIONAL GRAPHQL PROCESSING ***');
$this->fixErroneousKeywordTokens();
$this->processFields();
$this->logVerbose('*** END ADDITIONAL GRAPHQL PROCESSING ***');
}
/**
* @inheritDoc
*/
protected function tokenize($string)
{
$this->logVerbose('*** START GRAPHQL TOKENIZING ***');
$string = str_replace($this->eolChar, "\n", $string);
$tokens = [];
$source = new Source($string);
$lexer = new Lexer($source);
do {
$kind = $lexer->token->kind;
$value = $lexer->token->value ?: '';
//if we have encountered a keyword, we convert it
//otherwise we translate the token or default it to T_STRING
if ($kind === Token::NAME && isset($this->keywordTokenTypeMap[$value])) {
$tokenType = $this->keywordTokenTypeMap[$value];
} elseif (isset($this->tokenTypeMap[$kind])) {
$tokenType = $this->tokenTypeMap[$kind];
} else {
$tokenType = 'T_STRING';
}
//some GraphQL tokens need special handling
switch ($kind) {
case Token::AT:
case Token::BRACE_L:
case Token::BRACE_R:
case Token::PAREN_L:
case Token::PAREN_R:
case Token::COLON:
case Token::BRACKET_L:
case Token::BRACKET_R:
case Token::BANG:
$value = $kind;
break;
default:
//NOP
}
//finally we create the PHP token
$token = [
'code' => constant($tokenType),
'type' => $tokenType,
'content' => $value,
];
$line = $lexer->token->line;
$lexer->advance();
//if line has changed (and we're not on start of file) we have to append at least one line break to current
//token's content otherwise PHP_CodeSniffer will screw up line numbers
if ($lexer->token->line !== $line && $kind !== Token::SOF) {
$token['content'] .= $this->eolChar;
}
$tokens[] = $token;
$tokens = array_merge(
$tokens,
$this->getNewLineTokens($line, $lexer->token->line)
);
} while ($lexer->token->kind !== Token::EOF);
$this->logVerbose('*** END GRAPHQL TOKENIZING ***');
return $tokens;
}
/**
* Fixes that keywords may be used as field, argument etc. names and could thus have been marked as special tokens
* while tokenizing.
*/
private function fixErroneousKeywordTokens()
{
$processingCodeBlock = false;
$numTokens = count($this->tokens);
for ($i = 0; $i < $numTokens; ++$i) {
$tokenCode = $this->tokens[$i]['code'];
$tokenContent = $this->tokens[$i]['content'];
switch (true) {
case $tokenCode === T_OPEN_CURLY_BRACKET:
//we have hit the beginning of a code block
$processingCodeBlock = true;
break;
case $tokenCode === T_CLOSE_CURLY_BRACKET:
//we have hit the end of a code block
$processingCodeBlock = false;
break;
case $processingCodeBlock
&& $tokenCode !== T_STRING
&& isset($this->keywordTokenTypeMap[$tokenContent]):
//we have hit a keyword within a code block that is of wrong token type
$this->tokens[$i]['code'] = T_STRING;
$this->tokens[$i]['type'] = 'T_STRING';
break;
default:
//NOP All operations have already been executed
}
}
}
/**
* Returns tokens of empty new lines for the range <var>$lineStart</var> to <var>$lineEnd</var>
*
* @param int $lineStart
* @param int $lineEnd
* @return array
*/
private function getNewLineTokens($lineStart, $lineEnd)
{
$amount = ($lineEnd - $lineStart) - 1;
$tokens = [];
for ($i = 0; $i < $amount; ++$i) {
$tokens[] = [
'code' => T_WHITESPACE,
'type' => 'T_WHITESPACE',
'content' => $this->eolChar,
];
}
return $tokens;
}
/**
* Returns whether the token under <var>$stackPointer</var> is a field.
*
* We consider a token to be a field if:
* <ul>
* <li>its direct successor is of type {@link T_COLON}</li>
* <li>it has a list of arguments followed by a {@link T_COLON}</li>
* </ul>
*
* @param int $stackPointer
* @return bool
*/
private function isFieldToken($stackPointer)
{
//bail out if current token is nested in a parenthesis, since fields cannot be contained in parenthesises
if (isset($this->tokens[$stackPointer]['nested_parenthesis'])) {
return false;
}
$nextToken = $this->tokens[$stackPointer + 1];
//if next token is an opening parenthesis, we advance to the token after the closing parenthesis
if ($nextToken['code'] === T_OPEN_PARENTHESIS) {
$nextPointer = $nextToken['parenthesis_closer'] + 1;
$nextToken = $this->tokens[$nextPointer];
}
//return whether current token is a string and next token is a colon
return $this->tokens[$stackPointer]['code'] === T_STRING && $nextToken['code'] === T_COLON;
}
/**
* Logs <var>$message</var> if {@link PHP_CODESNIFFER_VERBOSITY} is greater than <var>$level</var>.
*
* @param string $message
* @param int $level
*/
private function logVerbose($message, $level = 1)
{
if (PHP_CODESNIFFER_VERBOSITY > $level) {
printf("\t%s" . PHP_EOL, $message);
}
}
/**
* Processes field tokens, setting their type to {@link T_VARIABLE}.
*/
private function processFields()
{
$processingEntity = false;
$numTokens = count($this->tokens);
$entityTypes = [T_CLASS, T_INTERFACE];
$skipTypes = [T_COMMENT, T_WHITESPACE];
for ($i = 0; $i < $numTokens; ++$i) {
$tokenCode = $this->tokens[$i]['code'];
//process current token
switch (true) {
case in_array($tokenCode, $skipTypes):
//we have hit a token that needs to be skipped -> NOP
break;
case in_array($tokenCode, $entityTypes) && $this->tokens[$i]['content'] !== 'enum':
//we have hit an entity declaration
$processingEntity = true;
break;
case $tokenCode === T_CLOSE_CURLY_BRACKET:
//we have hit the end of an entity declaration
$processingEntity = false;
break;
case $processingEntity && $this->isFieldToken($i):
//we have hit a field
$this->tokens[$i]['code'] = T_VARIABLE;
$this->tokens[$i]['type'] = 'T_VARIABLE';
break;
default:
//NOP All operations have already been executed
}
}
}
}