12
12
using System . Reflection ;
13
13
using System . Runtime . CompilerServices ;
14
14
using System . Text ;
15
+ using System . Text . RegularExpressions ;
15
16
using System . Threading ;
16
17
using System . Threading . Tasks ;
17
18
using DiffPlex ;
@@ -34,6 +35,7 @@ public abstract class AnalyzerTest<TVerifier>
34
35
where TVerifier : IVerifier , new ( )
35
36
{
36
37
private static readonly ConditionalWeakTable < Diagnostic , object > NonLocalDiagnostics = new ConditionalWeakTable < Diagnostic , object > ( ) ;
38
+ private static readonly Regex EncodedIndicesSyntax = new Regex ( @"^\s*\[\s*((?<Index>[0-9]+)\s*(,\s*(?<Index>[0-9]+)\s*)*)?\]\s*$" , RegexOptions . ExplicitCapture | RegexOptions . Compiled ) ;
37
39
38
40
/// <summary>
39
41
/// Gets the default verifier for the test.
@@ -558,16 +560,43 @@ private void VerifyDiagnosticResults(IEnumerable<(Project project, Diagnostic di
558
560
else
559
561
{
560
562
VerifyDiagnosticLocation ( analyzers , actual . diagnostic , expected , actual . diagnostic . Location , expected . Spans [ 0 ] , verifier ) ;
563
+ int [ ] unnecessaryIndices = { } ;
564
+ if ( actual . diagnostic . Properties . TryGetValue ( WellKnownDiagnosticTags . Unnecessary , out var encodedUnnecessaryLocations ) )
565
+ {
566
+ verifier . True ( actual . diagnostic . Descriptor . CustomTags . Contains ( WellKnownDiagnosticTags . Unnecessary ) , "Diagnostic reported extended unnecessary locations, but the descriptor is not marked as unnecessary code." ) ;
567
+ var match = EncodedIndicesSyntax . Match ( encodedUnnecessaryLocations ) ;
568
+ verifier . True ( match . Success , $ "Expected encoded unnecessary locations to be a valid JSON array of non-negative integers: { encodedUnnecessaryLocations } ") ;
569
+ unnecessaryIndices = match . Groups [ "Index" ] . Captures . OfType < Capture > ( ) . Select ( capture => int . Parse ( capture . Value ) ) . ToArray ( ) ;
570
+ verifier . NotEmpty ( nameof ( unnecessaryIndices ) , unnecessaryIndices ) ;
571
+ foreach ( var index in unnecessaryIndices )
572
+ {
573
+ if ( index < 0 || index >= actual . diagnostic . AdditionalLocations . Count )
574
+ {
575
+ verifier . Fail ( $ "All unnecessary indices in the diagnostic must be valid indices in AdditionalLocations [0-{ actual . diagnostic . AdditionalLocations . Count } ): { encodedUnnecessaryLocations } ") ;
576
+ }
577
+ }
578
+ }
579
+
561
580
if ( ! expected . Options . HasFlag ( DiagnosticOptions . IgnoreAdditionalLocations ) )
562
581
{
563
582
var additionalLocations = actual . diagnostic . AdditionalLocations . ToArray ( ) ;
564
583
565
584
message = FormatVerifierMessage ( analyzers , actual . diagnostic , expected , $ "Expected { expected . Spans . Length - 1 } additional locations but got { additionalLocations . Length } for Diagnostic:") ;
566
585
verifier . Equal ( expected . Spans . Length - 1 , additionalLocations . Length , message ) ;
567
586
568
- for ( var j = 0 ; j < additionalLocations . Length ; ++ j )
587
+ for ( var j = 0 ; j < additionalLocations . Length ; j ++ )
569
588
{
570
589
VerifyDiagnosticLocation ( analyzers , actual . diagnostic , expected , additionalLocations [ j ] , expected . Spans [ j + 1 ] , verifier ) ;
590
+ var isActualUnnecessary = unnecessaryIndices . Contains ( j ) ;
591
+ var isExpectedUnnecessary = expected . Spans [ j + 1 ] . Options . HasFlag ( DiagnosticLocationOptions . UnnecessaryCode ) ;
592
+ if ( isExpectedUnnecessary )
593
+ {
594
+ verifier . True ( isActualUnnecessary , $ "Expected diagnostic additional location index \" { j } \" to be marked unnecessary, but was not.") ;
595
+ }
596
+ else
597
+ {
598
+ verifier . False ( isActualUnnecessary , $ "Expected diagnostic additional location index \" { j } \" to not be marked unnecessary, but was instead marked unnecessary.") ;
599
+ }
571
600
}
572
601
}
573
602
}
@@ -815,18 +844,17 @@ private void VerifyLinePosition(ImmutableArray<DiagnosticAnalyzer> analyzers, Di
815
844
private static string FormatDiagnostics ( ImmutableArray < DiagnosticAnalyzer > analyzers , string defaultFilePath , params Diagnostic [ ] diagnostics )
816
845
{
817
846
var builder = new StringBuilder ( ) ;
818
- for ( var i = 0 ; i < diagnostics . Length ; ++ i )
847
+ foreach ( var diagnostic in diagnostics )
819
848
{
820
- var diagnosticsId = diagnostics [ i ] . Id ;
821
- var location = diagnostics [ i ] . Location ;
849
+ var location = diagnostic . Location ;
822
850
823
- builder . Append ( "// " ) . AppendLine ( diagnostics [ i ] . ToString ( ) ) ;
851
+ builder . Append ( "// " ) . AppendLine ( diagnostic . ToString ( ) ) ;
824
852
825
- var applicableAnalyzer = analyzers . FirstOrDefault ( a => a . SupportedDiagnostics . Any ( dd => dd . Id == diagnosticsId ) ) ;
853
+ var applicableAnalyzer = analyzers . FirstOrDefault ( a => a . SupportedDiagnostics . Any ( dd => dd . Id == diagnostic . Id ) ) ;
826
854
if ( applicableAnalyzer != null )
827
855
{
828
856
var analyzerType = applicableAnalyzer . GetType ( ) ;
829
- var rule = location != Location . None && location . IsInSource && applicableAnalyzer . SupportedDiagnostics . Length == 1 ? string . Empty : $ "{ analyzerType . Name } .{ diagnosticsId } ";
857
+ var rule = location != Location . None && location . IsInSource && applicableAnalyzer . SupportedDiagnostics . Length == 1 ? string . Empty : $ "{ analyzerType . Name } .{ diagnostic . Id } ";
830
858
831
859
if ( location == Location . None || ! location . IsInSource )
832
860
{
@@ -841,11 +869,11 @@ private static string FormatDiagnostics(ImmutableArray<DiagnosticAnalyzer> analy
841
869
else
842
870
{
843
871
builder . Append (
844
- diagnostics [ i ] . Severity switch
872
+ diagnostic . Severity switch
845
873
{
846
- DiagnosticSeverity . Error => $ "{ nameof ( DiagnosticResult ) } .{ nameof ( DiagnosticResult . CompilerError ) } (\" { diagnostics [ i ] . Id } \" )",
847
- DiagnosticSeverity . Warning => $ "{ nameof ( DiagnosticResult ) } .{ nameof ( DiagnosticResult . CompilerWarning ) } (\" { diagnostics [ i ] . Id } \" )",
848
- var severity => $ "new { nameof ( DiagnosticResult ) } (\" { diagnostics [ i ] . Id } \" , { nameof ( DiagnosticSeverity ) } .{ severity } )",
874
+ DiagnosticSeverity . Error => $ "{ nameof ( DiagnosticResult ) } .{ nameof ( DiagnosticResult . CompilerError ) } (\" { diagnostic . Id } \" )",
875
+ DiagnosticSeverity . Warning => $ "{ nameof ( DiagnosticResult ) } .{ nameof ( DiagnosticResult . CompilerWarning ) } (\" { diagnostic . Id } \" )",
876
+ var severity => $ "new { nameof ( DiagnosticResult ) } (\" { diagnostic . Id } \" , { nameof ( DiagnosticSeverity ) } .{ severity } )",
849
877
} ) ;
850
878
}
851
879
@@ -855,22 +883,35 @@ private static string FormatDiagnostics(ImmutableArray<DiagnosticAnalyzer> analy
855
883
}
856
884
else
857
885
{
858
- AppendLocation ( diagnostics [ i ] . Location ) ;
859
- foreach ( var additionalLocation in diagnostics [ i ] . AdditionalLocations )
886
+ // The unnecessary code designator is ignored for the primary diagnostic location.
887
+ AppendLocation ( diagnostic . Location , isUnnecessary : false ) ;
888
+
889
+ int [ ] unnecessaryIndices = { } ;
890
+ if ( diagnostic . Properties . TryGetValue ( WellKnownDiagnosticTags . Unnecessary , out var encodedUnnecessaryLocations ) )
891
+ {
892
+ var match = EncodedIndicesSyntax . Match ( encodedUnnecessaryLocations ) ;
893
+ if ( match . Success )
894
+ {
895
+ unnecessaryIndices = match . Groups [ "Index" ] . Captures . OfType < Capture > ( ) . Select ( capture => int . Parse ( capture . Value ) ) . ToArray ( ) ;
896
+ }
897
+ }
898
+
899
+ for ( var i = 0 ; i < diagnostic . AdditionalLocations . Count ; i ++ )
860
900
{
861
- AppendLocation ( additionalLocation ) ;
901
+ var additionalLocation = diagnostic . AdditionalLocations [ i ] ;
902
+ AppendLocation ( additionalLocation , isUnnecessary : unnecessaryIndices . Contains ( i ) ) ;
862
903
}
863
904
}
864
905
865
- var arguments = diagnostics [ i ] . Arguments ( ) ;
906
+ var arguments = diagnostic . Arguments ( ) ;
866
907
if ( arguments . Count > 0 )
867
908
{
868
909
builder . Append ( $ ".{ nameof ( DiagnosticResult . WithArguments ) } (") ;
869
910
builder . Append ( string . Join ( ", " , arguments . Select ( a => "\" " + a ? . ToString ( ) + "\" " ) ) ) ;
870
911
builder . Append ( ")" ) ;
871
912
}
872
913
873
- if ( diagnostics [ i ] . IsSuppressed ( ) )
914
+ if ( diagnostic . IsSuppressed ( ) )
874
915
{
875
916
builder . Append ( $ ".{ nameof ( DiagnosticResult . WithIsSuppressed ) } (true)") ;
876
917
}
@@ -881,13 +922,16 @@ private static string FormatDiagnostics(ImmutableArray<DiagnosticAnalyzer> analy
881
922
return builder . ToString ( ) ;
882
923
883
924
// Local functions
884
- void AppendLocation ( Location location )
925
+ void AppendLocation ( Location location , bool isUnnecessary )
885
926
{
886
927
var lineSpan = location . GetLineSpan ( ) ;
887
928
var pathString = location . IsInSource && lineSpan . Path == defaultFilePath ? string . Empty : $ "\" { lineSpan . Path } \" , ";
888
929
var linePosition = lineSpan . StartLinePosition ;
889
930
var endLinePosition = lineSpan . EndLinePosition ;
890
- builder . Append ( $ ".WithSpan({ pathString } { linePosition . Line + 1 } , { linePosition . Character + 1 } , { endLinePosition . Line + 1 } , { endLinePosition . Character + 1 } )") ;
931
+ var unnecessaryArgument = isUnnecessary
932
+ ? $ ", { nameof ( DiagnosticLocationOptions ) } .{ nameof ( DiagnosticLocationOptions . UnnecessaryCode ) } "
933
+ : string . Empty ;
934
+ builder . Append ( $ ".WithSpan({ pathString } { linePosition . Line + 1 } , { linePosition . Character + 1 } , { endLinePosition . Line + 1 } , { endLinePosition . Character + 1 } { unnecessaryArgument } )") ;
891
935
}
892
936
}
893
937
0 commit comments