protected override Task FixAllAsync( Document document, ImmutableArray <Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) { var syntaxRoot = editor.OriginalRoot; foreach (var diagnostic in diagnostics) { var firstUnreachableStatementLocation = diagnostic.AdditionalLocations.Single(); var firstUnreachableStatement = (StatementSyntax)firstUnreachableStatementLocation.FindNode(cancellationToken); editor.RemoveNode(firstUnreachableStatement, SyntaxRemoveOptions.KeepUnbalancedDirectives); var sections = RemoveUnreachableCodeHelpers.GetSubsequentUnreachableSections(firstUnreachableStatement); foreach (var section in sections) { foreach (var statement in section) { editor.RemoveNode(statement, SyntaxRemoveOptions.KeepUnbalancedDirectives); } } } return(Task.CompletedTask); }
private void ProcessUnreachableDiagnostic( SemanticModelAnalysisContext context, SyntaxNode root, TextSpan sourceSpan, bool fadeOutCode) { var node = root.FindNode(sourceSpan); // Note: this approach works as the language only supports the concept of // unreachable statements. If we ever get unreachable subexpressions, then // we'll need to revise this code accordingly. var firstUnreachableStatement = node.FirstAncestorOrSelf <StatementSyntax>(); if (firstUnreachableStatement == null || firstUnreachableStatement.SpanStart != sourceSpan.Start) { return; } // At a high level, we can think about us wanting to fade out a "section" of unreachable // statements. However, the compiler only reports the first statement in that "section". // We want to figure out what other statements are in that section and fade them all out // along with the first statement. This is made somewhat tricky due to the fact that // subsequent sibling statements possibly being reachable due to explicit gotos+labels. // // On top of this, an unreachable section might not be contiguous. This is possible // when there is unreachable code that contains a local function declaration in-situ. // This is legal, and the local function declaration may be called from other reachable code. // // As such, it's not possible to just get first unreachable statement, and the last, and // then report that whole region as unreachable. Instead, when we are told about an // unreachable statement, we simply determine which other statements are also unreachable // and bucket them into contiguous chunks. // // We then fade each of these contiguous chunks, while also having each diagnostic we // report point back to the first unreachable statement so that we can easily determine // what to remove if the user fixes the issue. (The fix itself has to go recompute this // as the total set of statements to remove may be larger than the actual faded code // that that diagnostic corresponds to). // Get the location of this first unreachable statement. It will be given to all // the diagnostics we create off of this single compiler diagnostic so that we always // know how to find it regardless of which of our diagnostics the user invokes the // fix off of. var firstStatementLocation = root.SyntaxTree.GetLocation(firstUnreachableStatement.FullSpan); if (!firstUnreachableStatement.IsParentKind(SyntaxKind.Block) && !firstUnreachableStatement.IsParentKind(SyntaxKind.SwitchSection)) { // Can't actually remove this statement (it's an embedded statement in something // like an 'if-statement'). Just fade the code out, but don't offer to remove it. if (fadeOutCode) { context.ReportDiagnostic( Diagnostic.Create(UnnecessaryWithoutSuggestionDescriptor, firstStatementLocation)); } return; } // 'additionalLocations' is how we always pass along the locaiton of the first unreachable // statement in this group. var additionalLocations = SpecializedCollections.SingletonEnumerable(firstStatementLocation); var descriptor = fadeOutCode ? UnnecessaryWithSuggestionDescriptor : Descriptor; context.ReportDiagnostic( Diagnostic.Create(descriptor, firstStatementLocation, additionalLocations)); var sections = RemoveUnreachableCodeHelpers.GetSubsequentUnreachableSections(firstUnreachableStatement); foreach (var section in sections) { var span = TextSpan.FromBounds(section[0].FullSpan.Start, section.Last().FullSpan.End); var location = root.SyntaxTree.GetLocation(span); // Mark subsequent sections as being 'cascaded'. We don't need to actually process them // when doing a fix-all as they'll be scooped up when we process the fix for the first // section. context.ReportDiagnostic( Diagnostic.Create(descriptor, location, additionalLocations, s_subsequentSectionProperties)); } }