@@ -90,6 +90,16 @@ public ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) {
9090
9191 final PropertyScope propertyScope = tokenToPropertyScope .get (currentToken );
9292 if (propertyScope != null ) {
93+
94+ if (scopes .isEmpty ()) {
95+ if (currentToken .tokenType == JSGraphQLTokenTypes .RBRACE || currentToken .tokenType == JSGraphQLTokenTypes .RBRACKET || currentToken .tokenType == JSGraphQLTokenTypes .RPAREN ) {
96+ // closing scope without an open scope, so skip ahead to continue parsing
97+ builder .advanceLexer ();
98+ tokenIndex .set (tokenIndex .get ()+1 );
99+ continue ;
100+ }
101+ }
102+
93103 if (currentToken .tokenType == JSGraphQLTokenTypes .PROPERTY || currentToken .tokenType == JSGraphQLTokenTypes .KEYWORD /* query etc.*/ ) {
94104 if (propertyScope .lbrace != null ) {
95105 // Field property token with selection set is considered a scope
@@ -107,6 +117,9 @@ public ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) {
107117 if (JSGraphQLElementType .OBJECT_VALUE_KIND .equals (propertyScope .lbrace .sourceToken .getKind ())) {
108118 // close object value
109119 endScope (builder , tokenIndex , scopes , true );
120+ if (propertyScope .parentToClose != null ) {
121+ endScope (builder , tokenIndex , scopes , false );
122+ }
110123 continue ;
111124 } else {
112125 // close selection set
@@ -128,6 +141,14 @@ public ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) {
128141 }
129142 } else if (currentToken .tokenType == JSGraphQLTokenTypes .RBRACKET ) {
130143 endScope (builder , tokenIndex , scopes , true );
144+ if (propertyScope .parentToClose != null ) {
145+ endScope (builder , tokenIndex , scopes , false );
146+ }
147+ continue ;
148+ } else if (currentToken .tokenType == JSGraphQLTokenTypes .ATTRIBUTE ) {
149+ // atribute with list/object value, so it's a scope for indentation, folding etc.
150+ startScope (builder , scopes , currentToken , false );
151+ markCurrentToken (builder , tokenIndex , JSGraphQLElementType .ATTRIBUTE_KIND );
131152 continue ;
132153 }
133154 } else if (currentToken .tokenType == JSGraphQLTokenTypes .PROPERTY ) {
@@ -139,6 +160,10 @@ public ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) {
139160 } else if (currentToken .tokenType == JSGraphQLTokenTypes .DEF ) {
140161 markCurrentToken (builder , tokenIndex , JSGraphQLElementType .DEFINITION_KIND );
141162 continue ;
163+ } else if (currentToken .tokenType == JSGraphQLTokenTypes .ATTRIBUTE ) {
164+ // attribute with literal value (not a scope)
165+ markCurrentToken (builder , tokenIndex , JSGraphQLElementType .ATTRIBUTE_KIND );
166+ continue ;
142167 }
143168
144169 // ---- template fragment ----
@@ -197,15 +222,11 @@ private List<PropertyScope> getPropertyScopes(List<JSGraphQLToken> tokens) {
197222 .filter ((token ) -> token .tokenType != JSGraphQLTokenTypes .WHITESPACE && token .tokenType != JSGraphQLTokenTypes .COMMENT )
198223 .collect (Collectors .toList ());
199224
200- final Set <String > closeScopeKinds = Sets .newHashSet (
201- JSGraphQLElementType .SELECTION_SET_KIND ,
202- JSGraphQLElementType .DOCUMENT_KIND ,
203- JSGraphQLElementType .OBJECT_VALUE_KIND ,
204- JSGraphQLElementType .ARGUMENTS_KIND
205- );
206-
207225 final List <PropertyScope > ret = Lists .newArrayList ();
208226 final Stack <PropertyScope > scopes = new Stack <>();
227+
228+ boolean parseArguments = false ;
229+
209230 for (int i = 0 ; i < astTokens .size (); i ++) {
210231 JSGraphQLToken token = astTokens .get (i );
211232 if (token .tokenType == JSGraphQLTokenTypes .KEYWORD ) {
@@ -224,54 +245,111 @@ private List<PropertyScope> getPropertyScopes(List<JSGraphQLToken> tokens) {
224245 }
225246 } else if (token .tokenType == JSGraphQLTokenTypes .RBRACE ) {
226247 if (!scopes .isEmpty ()) {
227- final String kind = token .sourceToken .getKind ();
228- if (closeScopeKinds .contains (kind ) || isSchemaDefWithLBrace (kind )) {
229- PropertyScope propertyScope = scopes .pop ();
230- if (propertyScope .lbrace == null ) {
231- // closing the parent scope
232- if (!scopes .isEmpty ()) {
233- propertyScope = scopes .pop ();
248+ if (parseArguments ) {
249+ scopes .pop ().rbrace = token ;
250+ } else {
251+ final String kind = token .sourceToken .getKind ();
252+ if (JSGraphQLElementType .SELECTION_SET_KIND .equals (kind ) || JSGraphQLElementType .DOCUMENT_KIND .equals (kind ) || isSchemaDefWithLBrace (kind )) {
253+ PropertyScope propertyScope = scopes .pop ();
254+ if (propertyScope .lbrace == null ) {
255+ // closing the parent scope
256+ if (!scopes .isEmpty ()) {
257+ propertyScope = scopes .pop ();
258+ }
234259 }
260+ propertyScope .rbrace = token ;
235261 }
236- propertyScope .rbrace = token ;
237262 }
238263 }
239264 } else if (token .tokenType == JSGraphQLTokenTypes .LBRACE ) {
240- // if top level scope, it's shorthand for a query
241- if (scopes .isEmpty ()) {
265+ if (parseArguments ) {
242266 PropertyScope propertyScope = new PropertyScope (token , token );
243267 scopes .add (propertyScope );
244268 ret .add (propertyScope );
245269 } else {
246- final String kind = token . sourceToken . getKind ();
247- if ( JSGraphQLElementType . OBJECT_VALUE_KIND . equals ( kind )) {
270+ // if top level scope, it's shorthand for a query
271+ if ( scopes . isEmpty ( )) {
248272 PropertyScope propertyScope = new PropertyScope (token , token );
249273 scopes .add (propertyScope );
250274 ret .add (propertyScope );
251275 }
252276 }
253277 } else if (token .tokenType == JSGraphQLTokenTypes .LPAREN ) {
254- PropertyScope propertyScope = new PropertyScope (token , token );
255- scopes .add (propertyScope );
256- ret .add (propertyScope );
278+ if (!schema ) {
279+ parseArguments = true ;
280+ PropertyScope propertyScope = new PropertyScope (token , token );
281+ scopes .add (propertyScope );
282+ ret .add (propertyScope );
283+ }
257284 } else if (token .tokenType == JSGraphQLTokenTypes .RPAREN ) {
258- if (!scopes .isEmpty ()) {
259- scopes .pop ().rbrace = token ;
285+ if (!schema ) {
286+ if (!scopes .isEmpty ()) {
287+ scopes .pop ().rbrace = token ;
288+ }
289+ parseArguments = false ;
260290 }
261291 } else if (token .tokenType == JSGraphQLTokenTypes .LBRACKET ) {
262- PropertyScope propertyScope = new PropertyScope (token , token );
263- scopes .add (propertyScope );
264- ret .add (propertyScope );
292+ if (!schema && parseArguments ) {
293+ PropertyScope propertyScope = new PropertyScope (token , token );
294+ scopes .add (propertyScope );
295+ ret .add (propertyScope );
296+ }
265297 } else if (token .tokenType == JSGraphQLTokenTypes .RBRACKET ) {
266- if (!scopes .isEmpty ()) {
298+ if (!schema && parseArguments && ! scopes .isEmpty ()) {
267299 scopes .pop ().rbrace = token ;
268300 }
301+ } else if (token .tokenType == JSGraphQLTokenTypes .ATTRIBUTE ) {
302+ if (!schema && parseArguments ) {
303+ PropertyScope propertyScope = new PropertyScope (token , null );
304+ propertyScope .astTokenStartIndex = i ;
305+ ret .add (propertyScope );
306+ }
307+ }
308+ }
309+
310+ // associate the attribute scopes with their corresponding list/object values
311+ final Set <PropertyScope > literalAttributeValues = Sets .newHashSet ();
312+ for (int i = 0 ; i < ret .size (); i ++) {
313+ final PropertyScope attributeNameScope = ret .get (i );
314+ if (attributeNameScope .closedBy == null && attributeNameScope .propertyOrOperation .tokenType == JSGraphQLTokenTypes .ATTRIBUTE ) {
315+ if (i + 1 < ret .size ()) {
316+ final PropertyScope attributeValueScope = ret .get (i + 1 );
317+ if (!isValueForAttribute (astTokens , attributeNameScope , attributeValueScope )) {
318+ literalAttributeValues .add (attributeNameScope );
319+ continue ;
320+ }
321+ final JSGraphQLToken valueToken = attributeValueScope .lbrace ;
322+ if (valueToken != null && (valueToken .tokenType == JSGraphQLTokenTypes .LBRACE || valueToken .tokenType == JSGraphQLTokenTypes .LBRACKET )) {
323+ attributeValueScope .parentToClose = attributeNameScope ;
324+ attributeNameScope .closedBy = attributeValueScope ;
325+ } else {
326+ literalAttributeValues .add (attributeNameScope );
327+ }
328+ } else {
329+ literalAttributeValues .add (attributeNameScope );
330+ }
269331 }
270332 }
333+ ret .removeAll (literalAttributeValues );
271334
272335 return ret ;
273336 }
274337
338+ private boolean isValueForAttribute (List <JSGraphQLToken > astTokens , PropertyScope attributeNameScope , PropertyScope attributeValue ) {
339+ for (int i = attributeNameScope .astTokenStartIndex + 1 ; i < astTokens .size (); i ++) {
340+ final JSGraphQLToken token = astTokens .get (i );
341+ if (token == attributeValue .lbrace ) {
342+ return true ;
343+ }
344+ if (token .tokenType == JSGraphQLTokenTypes .PUNCTUATION ) {
345+ // expecting colon before the attribute value
346+ continue ;
347+ }
348+ break ;
349+ }
350+ return false ;
351+ }
352+
275353 private boolean isPropertyScopeDefinition (String text , int tokenIndex , List <JSGraphQLToken > astTokens , Stack <PropertyScope > scopes ) {
276354 switch (text ) {
277355 case JSGraphQLKeywords .TYPE :
@@ -370,6 +448,11 @@ private static class PropertyScope {
370448 JSGraphQLToken lbrace ;
371449 JSGraphQLToken rbrace ;
372450
451+ PropertyScope closedBy ;
452+ PropertyScope parentToClose ;
453+
454+ Integer astTokenStartIndex ;
455+
373456 public PropertyScope (JSGraphQLToken propertyOrOperation , JSGraphQLToken lbrace ) {
374457 this .propertyOrOperation = propertyOrOperation ;
375458 this .lbrace = lbrace ;
0 commit comments