-
Notifications
You must be signed in to change notification settings - Fork 158
/
Copy pathValidArgumentNameSniff.php
206 lines (178 loc) · 6.97 KB
/
ValidArgumentNameSniff.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<?php
/**
* Copyright 2019 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);
namespace Magento2\Sniffs\GraphQL;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\AST\DocumentNode;
use PHP_CodeSniffer\Files\File;
/**
* Detects argument names that are not specified in <kbd>cameCase</kbd>.
*/
class ValidArgumentNameSniff extends AbstractGraphQLSniff
{
/**
* @inheritDoc
*/
public function register()
{
return [T_VARIABLE];
}
/**
* @inheritDoc
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
//get the pointer to the argument list opener or bail out if none was found
//since then the field does not have arguments
$openArgumentListPointer = $this->getArgumentListOpenPointer($stackPtr, $tokens);
if ($openArgumentListPointer === false) {
return;
}
//get the pointer to the argument list closer or add a warning and terminate as we have an unbalanced file
$closeArgumentListPointer = $this->getArgumentListClosePointer($openArgumentListPointer, $tokens);
if ($closeArgumentListPointer === false) {
$error = 'Possible parse error: Missing closing parenthesis for argument list in line %d';
$data = [
$tokens[$stackPtr]['line'],
];
$phpcsFile->addWarning($error, $stackPtr, 'UnclosedArgumentList', $data);
return;
}
$arguments = $this->getArguments($openArgumentListPointer, $closeArgumentListPointer, $tokens);
foreach ($arguments as $pointer => $argument) {
if (!$this->isCamelCase($argument)) {
$type = 'Argument';
$error = '%s name "%s" is not in CamelCase format';
$data = [
$type,
$argument,
];
$phpcsFile->addError($error, $pointer, 'NotCamelCase', $data);
$phpcsFile->recordMetric($pointer, 'CamelCase argument name', 'no');
} else {
$phpcsFile->recordMetric($pointer, 'CamelCase argument name', 'yes');
}
}
//return stack pointer of closing parenthesis
return $closeArgumentListPointer;
}
/**
* Seeks the last token of an argument definition and returns its pointer.
*
* Arguments are defined as follows:
* <pre>
* {ArgumentName}: {ArgumentType}[ = {DefaultValue}][{Directive}]*
* </pre>
*
* @param int $argumentDefinitionStartPointer
* @param array $tokens
* @return int
*/
private function getArgumentDefinitionEndPointer($argumentDefinitionStartPointer, array $tokens)
{
$endPointer = $this->seekToken(T_COLON, $tokens, $argumentDefinitionStartPointer);
//the colon is always followed by the type, which we can consume. it could be a list type though, thus we check
if ($tokens[$endPointer + 1]['code'] === T_OPEN_SQUARE_BRACKET) {
//consume everything up to closing bracket
$endPointer = $tokens[$endPointer + 1]['bracket_closer'];
} else {
//consume everything up to type
++$endPointer;
}
//the type may be non null, meaning that it is followed by an exclamation mark, which we consume
if ($tokens[$endPointer + 1]['code'] === T_BOOLEAN_NOT) {
++$endPointer;
}
//if argument has a default value, we advance to the default definition end
if ($tokens[$endPointer + 1]['code'] === T_EQUAL) {
$endPointer += 2;
}
//while next token starts a directive, we advance to the end of the directive
while ($tokens[$endPointer + 1]['code'] === T_DOC_COMMENT_TAG) {
$endPointer = $this->seekEndOfDirective($tokens, $endPointer + 1);
}
return $endPointer;
}
/**
* Returns the closing parenthesis for the token found at <var>$openParenthesisPointer</var> in <var>$tokens</var>.
*
* @param int $openParenthesisPointer
* @param array $tokens
* @return bool|int
*/
private function getArgumentListClosePointer($openParenthesisPointer, array $tokens)
{
$openParenthesisToken = $tokens[$openParenthesisPointer];
return $openParenthesisToken['parenthesis_closer'];
}
/**
* Find the argument list open pointer
*
* Seeks the next available {@link T_OPEN_PARENTHESIS} token
* that comes directly after <var>$stackPointer</var> token.
*
* @param int $stackPointer
* @param array $tokens
* @return bool|int
*/
private function getArgumentListOpenPointer($stackPointer, array $tokens)
{
//get next open parenthesis pointer or bail out if none was found
$openParenthesisPointer = $this->seekToken(T_OPEN_PARENTHESIS, $tokens, $stackPointer);
if ($openParenthesisPointer === false) {
return false;
}
//bail out if open parenthesis does not directly come after current stack pointer
if ($openParenthesisPointer !== $stackPointer + 1) {
return false;
}
//we have found the appropriate opening parenthesis
return $openParenthesisPointer;
}
/**
* Finds all argument names contained in <var>$tokens</var> in range <var>$startPointer</var> to
* <var>$endPointer</var>.
*
* The returned array uses token pointers as keys and argument names as values.
*
* @param int $startPointer
* @param int $endPointer
* @param array $tokens
* @return array<int, string>
*/
private function getArguments($startPointer, $endPointer, array $tokens)
{
$argumentTokenPointer = null;
$argument = '';
$names = [];
$skipTypes = [T_COMMENT, T_WHITESPACE];
for ($i = $startPointer + 1; $i < $endPointer; ++$i) {
$tokenCode = $tokens[$i]['code'];
switch (true) {
case in_array($tokenCode, $skipTypes):
//NOP This is a token that we have to skip
break;
case $tokenCode === T_COLON:
//we have reached the end of the argument name, thus we store its pointer and value
$names[$argumentTokenPointer] = $argument;
//advance to end of argument definition
$i = $this->getArgumentDefinitionEndPointer($argumentTokenPointer, $tokens);
//and reset temporary variables
$argument = '';
$argumentTokenPointer = null;
break;
default:
//this seems to be part of the argument name
$argument .= $tokens[$i]['content'];
if ($argumentTokenPointer === null) {
$argumentTokenPointer = $i;
}
}
}
return $names;
}
}