@@ -103,7 +103,8 @@ internal class SA1003SymbolsMustBeSpacedCorrectly : DiagnosticAnalyzer
103103 SyntaxKind . LogicalNotExpression ,
104104 SyntaxKind . PreIncrementExpression ,
105105 SyntaxKind . PreDecrementExpression ,
106- SyntaxKind . AddressOfExpression ) ;
106+ SyntaxKind . AddressOfExpression ,
107+ SyntaxKindEx . IndexExpression ) ;
107108
108109 private static readonly ImmutableArray < SyntaxKind > PostfixUnaryExpressionKinds =
109110 ImmutableArray . Create (
@@ -138,6 +139,7 @@ internal class SA1003SymbolsMustBeSpacedCorrectly : DiagnosticAnalyzer
138139 private static readonly Action < SyntaxNodeAnalysisContext > EqualsValueClauseAction = HandleEqualsValueClause ;
139140 private static readonly Action < SyntaxNodeAnalysisContext > LambdaExpressionAction = HandleLambdaExpression ;
140141 private static readonly Action < SyntaxNodeAnalysisContext > ArrowExpressionClauseAction = HandleArrowExpressionClause ;
142+ private static readonly Action < SyntaxNodeAnalysisContext > RangeExpressionAction = HandleRangeExpression ;
141143
142144 /// <summary>
143145 /// Gets the descriptor for prefix unary expression that may not be followed by a comment.
@@ -214,6 +216,7 @@ public override void Initialize(AnalysisContext context)
214216 context . RegisterSyntaxNodeAction ( EqualsValueClauseAction , SyntaxKind . EqualsValueClause ) ;
215217 context . RegisterSyntaxNodeAction ( LambdaExpressionAction , SyntaxKinds . LambdaExpression ) ;
216218 context . RegisterSyntaxNodeAction ( ArrowExpressionClauseAction , SyntaxKind . ArrowExpressionClause ) ;
219+ context . RegisterSyntaxNodeAction ( RangeExpressionAction , SyntaxKindEx . RangeExpression ) ;
217220 }
218221
219222 private static void HandleConstructorDeclaration ( SyntaxNodeAnalysisContext context )
@@ -249,6 +252,41 @@ private static void HandleBinaryExpression(SyntaxNodeAnalysisContext context)
249252 CheckToken ( context , binaryExpression . OperatorToken , true , true , true ) ;
250253 }
251254
255+ private static void HandleRangeExpression ( SyntaxNodeAnalysisContext context )
256+ {
257+ if ( ! RangeExpressionSyntaxWrapper . IsInstance ( context . Node ) )
258+ {
259+ return ;
260+ }
261+
262+ var rangeExpression = ( RangeExpressionSyntaxWrapper ) context . Node ;
263+ var hasLeftOperand = rangeExpression . LeftOperand != null ;
264+ var hasRightOperand = rangeExpression . RightOperand != null ;
265+
266+ if ( hasLeftOperand && hasRightOperand )
267+ {
268+ // Both operands present: no whitespace around the operator.
269+ CheckToken ( context , rangeExpression . OperatorToken , withLeadingWhitespace : false , allowAtEndOfLine : true , withTrailingWhitespace : false ) ;
270+ return ;
271+ }
272+
273+ if ( ! hasLeftOperand && hasRightOperand )
274+ {
275+ // Left operand omitted: preceding spacing is governed by surrounding context; operator must be adjacent to right operand.
276+ CheckTokenTrailingWhitespace ( context , rangeExpression . OperatorToken , allowAtEndOfLine : false , withTrailingWhitespace : false ) ;
277+ return ;
278+ }
279+
280+ if ( hasLeftOperand && ! hasRightOperand )
281+ {
282+ // Right operand omitted: operator must be adjacent to left operand; trailing spacing is governed by surrounding context.
283+ CheckTokenLeadingWhitespace ( context , rangeExpression . OperatorToken , withLeadingWhitespace : false ) ;
284+ return ;
285+ }
286+
287+ // Both operands omitted: spacing governed by surrounding context.
288+ }
289+
252290 private static void HandlePrefixUnaryExpression ( SyntaxNodeAnalysisContext context )
253291 {
254292 var unaryExpression = ( PrefixUnaryExpressionSyntax ) context . Node ;
@@ -265,6 +303,7 @@ private static void HandlePrefixUnaryExpression(SyntaxNodeAnalysisContext contex
265303 && ! ( unaryExpression . Parent is CastExpressionSyntax )
266304 && ! precedingToken . IsKind ( SyntaxKind . OpenParenToken )
267305 && ! precedingToken . IsKind ( SyntaxKind . OpenBracketToken )
306+ && ! precedingToken . IsKind ( SyntaxKindEx . DotDotToken )
268307 && ! ( precedingToken . IsKind ( SyntaxKind . OpenBraceToken ) && ( precedingToken . Parent is InterpolationSyntax ) ) ;
269308
270309 bool analyze ;
@@ -291,7 +330,16 @@ private static void HandlePrefixUnaryExpression(SyntaxNodeAnalysisContext contex
291330 }
292331 else
293332 {
294- CheckToken ( context , unaryExpression . OperatorToken , mustHaveLeadingWhitespace , false , false ) ;
333+ if ( precedingToken . IsKind ( SyntaxKindEx . DotDotToken ) )
334+ {
335+ // The preceding whitespace will be checked as part of the range operator '..' so only check
336+ // trailing whitespace for the current unary prefix operator.
337+ CheckTokenTrailingWhitespace ( context , unaryExpression . OperatorToken , allowAtEndOfLine : false , false ) ;
338+ }
339+ else
340+ {
341+ CheckToken ( context , unaryExpression . OperatorToken , mustHaveLeadingWhitespace , allowAtEndOfLine : false , withTrailingWhitespace : false ) ;
342+ }
295343 }
296344 }
297345 }
@@ -385,12 +433,17 @@ private static void CheckToken(SyntaxNodeAnalysisContext context, SyntaxToken to
385433 {
386434 tokenText = tokenText ?? token . Text ;
387435
436+ CheckTokenLeadingWhitespace ( context , token , withLeadingWhitespace , tokenText ) ;
437+ CheckTokenTrailingWhitespace ( context , token , allowAtEndOfLine , withTrailingWhitespace , tokenText ) ;
438+ }
439+
440+ private static void CheckTokenLeadingWhitespace ( SyntaxNodeAnalysisContext context , SyntaxToken token , bool withLeadingWhitespace , string tokenText = null )
441+ {
442+ tokenText = tokenText ?? token . Text ;
443+
388444 var precedingToken = token . GetPreviousToken ( ) ;
389445 var precedingTriviaList = TriviaHelper . MergeTriviaLists ( precedingToken . TrailingTrivia , token . LeadingTrivia ) ;
390446
391- var followingToken = token . GetNextToken ( ) ;
392- var followingTriviaList = TriviaHelper . MergeTriviaLists ( token . TrailingTrivia , followingToken . LeadingTrivia ) ;
393-
394447 if ( withLeadingWhitespace )
395448 {
396449 // Don't report missing leading whitespace when the token is the first token on a text line.
@@ -413,6 +466,14 @@ private static void CheckToken(SyntaxNodeAnalysisContext context, SyntaxToken to
413466 context . ReportDiagnostic ( Diagnostic . Create ( DescriptorNotPrecededByWhitespace , token . GetLocation ( ) , properties , tokenText ) ) ;
414467 }
415468 }
469+ }
470+
471+ private static void CheckTokenTrailingWhitespace ( SyntaxNodeAnalysisContext context , SyntaxToken token , bool allowAtEndOfLine , bool withTrailingWhitespace , string tokenText = null )
472+ {
473+ tokenText = tokenText ?? token . Text ;
474+
475+ var followingToken = token . GetNextToken ( ) ;
476+ var followingTriviaList = TriviaHelper . MergeTriviaLists ( token . TrailingTrivia , followingToken . LeadingTrivia ) ;
416477
417478 if ( ! allowAtEndOfLine && token . TrailingTrivia . Any ( SyntaxKind . EndOfLineTrivia ) )
418479 {
@@ -437,14 +498,11 @@ private static void CheckToken(SyntaxNodeAnalysisContext context, SyntaxToken to
437498 context . ReportDiagnostic ( Diagnostic . Create ( DescriptorFollowedByWhitespace , token . GetLocation ( ) , properties , tokenText ) ) ;
438499 }
439500 }
440- else
501+ else if ( ( followingTriviaList . Count > 0 ) && followingTriviaList . First ( ) . IsKind ( SyntaxKind . WhitespaceTrivia ) )
441502 {
442- if ( ( followingTriviaList . Count > 0 ) && followingTriviaList . First ( ) . IsKind ( SyntaxKind . WhitespaceTrivia ) )
443- {
444- var properties = ImmutableDictionary . Create < string , string > ( )
445- . Add ( CodeFixAction , RemoveAfterTag ) ;
446- context . ReportDiagnostic ( Diagnostic . Create ( DescriptorNotFollowedByWhitespace , token . GetLocation ( ) , properties , tokenText ) ) ;
447- }
503+ var properties = ImmutableDictionary . Create < string , string > ( )
504+ . Add ( CodeFixAction , RemoveAfterTag ) ;
505+ context . ReportDiagnostic ( Diagnostic . Create ( DescriptorNotFollowedByWhitespace , token . GetLocation ( ) , properties , tokenText ) ) ;
448506 }
449507 }
450508 }
0 commit comments