@@ -245,5 +245,165 @@ public Ast GetAstPosition(Token token)
245245 return findAstVisitor . AstPosition ;
246246 }
247247
248+ /// <summary>
249+ /// Returns a list of non-overlapping ranges (startOffset,endOffset) representing the start
250+ /// and end of braced member access expressions. These are member accesses where the name is
251+ /// enclosed in braces. The contents of such braces are treated literally as a member name.
252+ /// Altering the contents of these braces by formatting is likely to break code.
253+ /// </summary>
254+ public List < Tuple < int , int > > GetBracedMemberAccessRanges ( )
255+ {
256+ // A list of (startOffset, endOffset) pairs representing the start
257+ // and end braces of braced member access expressions.
258+ var ranges = new List < Tuple < int , int > > ( ) ;
259+
260+ var node = tokensLL . Value . First ;
261+ while ( node != null )
262+ {
263+ switch ( node . Value . Kind )
264+ {
265+ #if CORECLR
266+ // TokenKind added in PS7
267+ case TokenKind . QuestionDot:
268+ #endif
269+ case TokenKind . Dot:
270+ break ;
271+ default :
272+ node = node . Next ;
273+ continue ;
274+ }
275+
276+ // Note: We don't check if the dot is part of an existing range. When we find
277+ // a valid range, we skip all tokens inside it - so we won't ever evaluate a token
278+ // which already part of a previously found range.
279+
280+ // Backward scan:
281+ // Determine if this 'dot' is part of a member access.
282+ // Walk left over contiguous comment tokens that are 'touching'.
283+ // After skipping comments, the preceding non-comment token must also be 'touching'
284+ // and one of the expected TokenKinds.
285+ var leftToken = node . Previous ;
286+ var rightToken = node ;
287+ while ( leftToken != null && leftToken . Value . Kind == TokenKind . Comment )
288+ {
289+ if ( leftToken . Value . Extent . EndOffset != rightToken . Value . Extent . StartOffset )
290+ {
291+ leftToken = null ;
292+ break ;
293+ }
294+ rightToken = leftToken ;
295+ leftToken = leftToken . Previous ;
296+ }
297+ if ( leftToken == null )
298+ {
299+ // We ran out of tokens before finding a non-comment token to the left or there
300+ // was intervening whitespace.
301+ node = node . Next ;
302+ continue ;
303+ }
304+
305+ if ( leftToken . Value . Extent . EndOffset != rightToken . Value . Extent . StartOffset )
306+ {
307+ // There's whitespace between the two tokens
308+ node = node . Next ;
309+ continue ;
310+ }
311+
312+ // Limit to valid token kinds that can precede a 'dot' in a member access.
313+ switch ( leftToken . Value . Kind )
314+ {
315+ // Note: TokenKind.Number isn't in the list as 5.{Prop} is a syntax error
316+ // (Unexpected token). Numbers also have no properties - only methods.
317+ case TokenKind . Variable :
318+ case TokenKind . Identifier :
319+ case TokenKind . StringLiteral :
320+ case TokenKind . StringExpandable :
321+ case TokenKind . HereStringLiteral :
322+ case TokenKind . HereStringExpandable :
323+ case TokenKind . RParen :
324+ case TokenKind . RCurly :
325+ case TokenKind . RBracket :
326+ // allowed
327+ break ;
328+ default :
329+ // not allowed
330+ node = node . Next ;
331+ continue ;
332+ }
333+
334+ // Forward Scan:
335+ // Check that the next significant token is an LCurly
336+ // Starting from the token after the 'dot', walk right skipping trivia tokens:
337+ // - Comment
338+ // - NewLine
339+ // - LineContinuation (`)
340+ // These may be multi-line and need not be 'touching' the dot.
341+ // The first non-trivia token encountered must be an opening curly brace (LCurly) for
342+ // this dot to begin a braced member access. If it is not LCurly or we run out
343+ // of tokens, this dot is ignored.
344+ var scan = node . Next ;
345+ while ( scan != null )
346+ {
347+ if (
348+ scan . Value . Kind == TokenKind . Comment ||
349+ scan . Value . Kind == TokenKind . NewLine ||
350+ scan . Value . Kind == TokenKind . LineContinuation
351+ )
352+ {
353+ scan = scan . Next ;
354+ continue ;
355+ }
356+ break ;
357+ }
358+
359+ // If we reached the end without finding a significant token, or if the found token
360+ // is not LCurly, continue.
361+ if ( scan == null || scan . Value . Kind != TokenKind . LCurly )
362+ {
363+ node = node . Next ;
364+ continue ;
365+ }
366+
367+ // We have a valid token, followed by a dot, followed by an LCurly.
368+ // Find the matching RCurly and create the range.
369+ var lCurlyNode = scan ;
370+
371+ // Depth count braces to find the RCurly which closes the LCurly.
372+ int depth = 0 ;
373+ LinkedListNode < Token > rcurlyNode = null ;
374+ while ( scan != null )
375+ {
376+ if ( scan . Value . Kind == TokenKind . LCurly ) depth ++ ;
377+ else if ( scan . Value . Kind == TokenKind . RCurly )
378+ {
379+ depth -- ;
380+ if ( depth == 0 )
381+ {
382+ rcurlyNode = scan ;
383+ break ;
384+ }
385+ }
386+ scan = scan . Next ;
387+ }
388+
389+ // If we didn't find a matching RCurly, something has gone wrong.
390+ // Should an unmatched pair be caught by the parser as a parse error?
391+ if ( rcurlyNode == null )
392+ {
393+ node = node . Next ;
394+ continue ;
395+ }
396+
397+ ranges . Add ( new Tuple < int , int > (
398+ lCurlyNode . Value . Extent . StartOffset ,
399+ rcurlyNode . Value . Extent . EndOffset
400+ ) ) ;
401+
402+ // Skip all tokens inside the excluded range.
403+ node = rcurlyNode . Next ;
404+ }
405+
406+ return ranges ;
407+ }
248408 }
249409}
0 commit comments