66
77namespace Magento2 \Sniffs \GraphQL ;
88
9+ use GraphQL \Error \SyntaxError ;
10+ use GraphQL \Language \AST \DocumentNode ;
911use PHP_CodeSniffer \Files \File ;
1012
1113/**
1214 * Detects argument names that are not specified in <kbd>cameCase</kbd>.
1315 */
1416class ValidArgumentNameSniff extends AbstractGraphQLSniff
1517{
18+
1619 /**
1720 * @inheritDoc
1821 */
1922 public function register ()
2023 {
21- return [T_OPEN_PARENTHESIS ];
24+ return [T_VARIABLE ];
2225 }
2326
2427 /**
2528 * @inheritDoc
2629 */
2730 public function process (File $ phpcsFile , $ stackPtr )
2831 {
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+ }
3139
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 ) {
3443 $ error = 'Possible parse error: Missing closing parenthesis for argument list in line %d ' ;
3544 $ data = [
3645 $ tokens [$ stackPtr ]['line ' ],
@@ -39,18 +48,15 @@ public function process(File $phpcsFile, $stackPtr)
3948 return ;
4049 }
4150
42- $ arguments = $ this ->getArguments ($ stackPtr , $ closeParenthesisPointer , $ tokens );
51+ $ arguments = $ this ->getArguments ($ openArgumentListPointer , $ closeArgumentListPointer , $ tokens );
4352
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 )) {
4955 $ type = 'Argument ' ;
5056 $ error = '%s name "%s" is not in CamelCase format ' ;
5157 $ data = [
5258 $ type ,
53- $ name ,
59+ $ argument ,
5460 ];
5561
5662 $ phpcsFile ->addError ($ error , $ pointer , 'NotCamelCase ' , $ data );
@@ -61,56 +67,131 @@ public function process(File $phpcsFile, $stackPtr)
6167 }
6268
6369 //return stack pointer of closing parenthesis
64- return $ closeParenthesisPointer ;
70+ return $ closeArgumentListPointer ;
6571 }
6672
6773 /**
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.
7075 *
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
7382 * @param array $tokens
74- * @return array[]
83+ * @return int
7584 */
76- private function getArguments ( $ startPointer , $ endPointer , array $ tokens )
85+ private function getArgumentDefinitionEndPointer ( $ argumentDefinitionStartPointer , array $ tokens )
7786 {
78- $ argumentTokenPointer = null ;
79- $ argument = '' ;
80- $ names = [];
81- $ skipTypes = [T_COMMENT , T_WHITESPACE ];
87+ $ colonPointer = $ this ->seekToken (T_COLON , $ tokens , $ argumentDefinitionStartPointer );
8288
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 ;
8991
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 ;
93101
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 ' ];
99105 }
100106 }
101107
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 ' ];
103122 }
104123
105124 /**
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.
107127 *
108128 * @param int $stackPointer
109129 * @param array $tokens
110130 * @return bool|int
111131 */
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 )
113161 {
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 ;
115196 }
116197}
0 commit comments