public static SyntaxTreeState Create(bool defaultCompleted, NullableContextOptions compilationOptions, SyntaxTree tree, CancellationToken cancellationToken) { var root = tree.GetCompilationUnitRoot(cancellationToken); // This analyzer only needs to process syntax trees that contain at least one #nullable directive that // reduces the nullable analysis scope. int?positionOfFirstReducingNullableDirective = null; NullableContextOptions?currentOptions = compilationOptions; for (var directive = root.GetFirstDirective(); directive is not null; directive = directive.GetNextDirective()) { cancellationToken.ThrowIfCancellationRequested(); if (directive.IsKind(SyntaxKind.NullableDirectiveTrivia, out NullableDirectiveTriviaSyntax? nullableDirectiveTrivia)) { var newOptions = CSharpRemoveRedundantNullableDirectiveDiagnosticAnalyzer.GetNullableContextOptions(compilationOptions, currentOptions, nullableDirectiveTrivia); if (IsReducing(currentOptions, newOptions)) { positionOfFirstReducingNullableDirective = directive.SpanStart; break; } currentOptions = newOptions; } } return(new SyntaxTreeState(completed: defaultCompleted || positionOfFirstReducingNullableDirective is null, positionOfFirstReducingNullableDirective)); }
private ImmutableArray <Diagnostic> AnalyzeSemanticModel(SemanticModelAnalysisContext context, int positionOfFirstReducingNullableDirective, SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>?codeBlockIntervalTree, SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>?possibleNullableImpactIntervalTree) { var root = context.SemanticModel.SyntaxTree.GetCompilationUnitRoot(context.CancellationToken); using (var simplifier = new NullableImpactingSpanWalker(context.SemanticModel, positionOfFirstReducingNullableDirective, ignoredSpans: codeBlockIntervalTree, context.CancellationToken)) { simplifier.Visit(root); possibleNullableImpactIntervalTree ??= new SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>(new TextSpanIntervalIntrospector(), values: null); foreach (var interval in simplifier.Spans) { possibleNullableImpactIntervalTree.AddIntervalInPlace(interval); } } using var diagnostics = TemporaryArray <Diagnostic> .Empty; var compilationOptions = ((CSharpCompilationOptions)context.SemanticModel.Compilation.Options).NullableContextOptions; DirectiveTriviaSyntax? previousRetainedDirective = null; NullableContextOptions?retainedOptions = compilationOptions; DirectiveTriviaSyntax?currentOptionsDirective = null; var currentOptions = retainedOptions; for (var directive = root.GetFirstDirective(); directive is not null; directive = directive.GetNextDirective()) { context.CancellationToken.ThrowIfCancellationRequested(); if (directive.IsKind(SyntaxKind.NullableDirectiveTrivia, out NullableDirectiveTriviaSyntax? nullableDirectiveTrivia)) { // Once we reach a new directive, check to see if we can remove the previous directive var removedCurrent = false; if (IsReducing(retainedOptions, currentOptions)) { // We can't have found a reducing directive and not know which directive it was Contract.ThrowIfNull(currentOptionsDirective); if (possibleNullableImpactIntervalTree is null || !possibleNullableImpactIntervalTree.HasIntervalThatOverlapsWith(currentOptionsDirective.Span.End, nullableDirectiveTrivia.SpanStart - currentOptionsDirective.Span.End)) { diagnostics.Add(Diagnostic.Create(Descriptor, currentOptionsDirective.GetLocation())); } } if (!removedCurrent) { previousRetainedDirective = currentOptionsDirective; retainedOptions = currentOptions; } currentOptionsDirective = nullableDirectiveTrivia; currentOptions = CSharpRemoveRedundantNullableDirectiveDiagnosticAnalyzer.GetNullableContextOptions(compilationOptions, currentOptions, nullableDirectiveTrivia); } else if (directive.IsKind(SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia)) { possibleNullableImpactIntervalTree ??= new SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>(new TextSpanIntervalIntrospector(), values: null); possibleNullableImpactIntervalTree.AddIntervalInPlace(directive.Span); } } // Once we reach the end of the file, check to see if we can remove the last directive if (IsReducing(retainedOptions, currentOptions)) { // We can't have found a reducing directive and not know which directive it was Contract.ThrowIfNull(currentOptionsDirective); if (possibleNullableImpactIntervalTree is null || !possibleNullableImpactIntervalTree.HasIntervalThatOverlapsWith(currentOptionsDirective.Span.End, root.Span.End - currentOptionsDirective.Span.End)) { diagnostics.Add(Diagnostic.Create(Descriptor, currentOptionsDirective.GetLocation())); } } return(diagnostics.ToImmutableAndClear()); }