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