private bool TryGetSyntaxTreeAndSource(DocumentSnapshot document, out RazorSyntaxTree syntaxTree, out SourceText sourceText)
        {
            if (!document.TryGetText(out sourceText))
            {
                // Can't get the source text synchronously
                syntaxTree = null;
                return(false);
            }

            if (document.TryGetGeneratedOutput(out var codeDocument))
            {
                syntaxTree = codeDocument.GetSyntaxTree();
                return(true);
            }

            syntaxTree = ParseSourceText(document.FilePath, sourceText);
            return(true);
        }
        // The goal of this method is to decide if the older and newer snapshots have diverged in a way that could result in
        // impactful changes to other Razor files. Detecting divergence is an optimization which allows us to not always re-parse
        // dependent files and therefore not notify anyone else listening (Roslyn).
        //
        // Here's how we calculate "divergence":
        //
        // 1. Get the SyntaxTree for each document. If one has not been calculated yet, create a one-off syntax tree that only cares about
        //    capturing Razor directives.
        // 2. Extract @code and @functions directive blocks
        // 3. Map those directive blocks back to the original source document and extract their inner content.
        //    Aka @code { private int _foo; } => private int _foo;
        // 4. Do a light-weight C# parse on the content of each captured @code/@functions content in order to build a C# SyntaxTree. The
        //    SyntaxTree is not meant to be full-fidelity or error free, it's just meant to represent the key pieces of the content like
        //    methods, fields, properties etc.
        // 5. Extract all properties for the new and old documents.
        // 6. Compare the old documents properties to the new documents properties, if they've changed structurally then we've diverged;
        //    otherwise no divergence!
        //
        // At any point in this flow if we're unable to calculate one of the requirements such as the original Razor documents source text,
        // assume divergence.
        public override bool PossibleDivergence(DocumentSnapshot old, DocumentSnapshot @new)
        {
            if (old is null)
            {
                throw new ArgumentNullException(nameof(old));
            }

            if (@new is null)
            {
                throw new ArgumentNullException(nameof(@new));
            }

            if (!string.Equals(@new.FileKind, FileKinds.Component, StringComparison.OrdinalIgnoreCase))
            {
                // Component import or ordinary cshtml file.
                return(true);
            }

            if (!TryGetSyntaxTreeAndSource(old, out var oldSyntaxTree, out var oldText))
            {
                return(true);
            }

            if (!TryGetSyntaxTreeAndSource(@new, out var newSyntaxTree, out var newText))
            {
                return(true);
            }

            var newWalker = new CodeFunctionsExtractor();

            newWalker.Visit(newSyntaxTree.Root);

            var oldWalker = new CodeFunctionsExtractor();

            oldWalker.Visit(oldSyntaxTree.Root);

            if (newWalker.CodeBlocks.Count == 0 && oldWalker.CodeBlocks.Count == 0)
            {
                // No directive code blocks, therefore no properties to analyze.
                return(false);
            }

            var newProperties = ExtractCSharpProperties(newText, newWalker.CodeBlocks);
            var oldProperties = ExtractCSharpProperties(oldText, oldWalker.CodeBlocks);

            if (newProperties.Count != oldProperties.Count)
            {
                return(true);
            }

            for (var i = 0; i < newProperties.Count; i++)
            {
                var newProperty = newProperties[i];
                var oldProperty = oldProperties[i];
                if (!CSharpPropertiesEqual(newProperty, oldProperty))
                {
                    return(true);
                }
            }

            // Properties before and after document change are equivalent
            return(false);
        }