Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 73 additions & 65 deletions src/Features/Core/SolutionCrawler/AbstractDocumentDifferenceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,99 +7,107 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.SolutionCrawler
{
internal abstract class AbstractDocumentDifferenceService : IDocumentDifferenceService
{
public async Task<DocumentDifferenceResult> GetDifferenceAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken)
{
var syntaxFactsService = newDocument.Project.LanguageServices.GetService<ISyntaxFactsService>();
if (syntaxFactsService == null)
try
{
// somehow, we can't get the service. without it, there is nothing we can do.
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}
var syntaxFactsService = newDocument.Project.LanguageServices.GetService<ISyntaxFactsService>();
if (syntaxFactsService == null)
{
// somehow, we can't get the service. without it, there is nothing we can do.
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}

// this is based on the implementation detail where opened documents use strong references
// to tree and text rather than recoverable versions.
SourceText oldText;
SourceText newText;
if (!oldDocument.TryGetText(out oldText) ||
!newDocument.TryGetText(out newText))
{
// no cheap way to determine top level changes. assumes top level has changed
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}
// this is based on the implementation detail where opened documents use strong references
// to tree and text rather than recoverable versions.
SourceText oldText;
SourceText newText;
if (!oldDocument.TryGetText(out oldText) ||
!newDocument.TryGetText(out newText))
{
// no cheap way to determine top level changes. assumes top level has changed
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}

// quick check whether two tree versions are same
VersionStamp oldVersion;
VersionStamp newVersion;
if (oldDocument.TryGetSyntaxVersion(out oldVersion) &&
newDocument.TryGetSyntaxVersion(out newVersion) &&
oldVersion.Equals(newVersion))
{
// nothing has changed. don't do anything.
// this could happen if a document is opened/closed without any buffer change
return null;
}
// quick check whether two tree versions are same
VersionStamp oldVersion;
VersionStamp newVersion;
if (oldDocument.TryGetSyntaxVersion(out oldVersion) &&
newDocument.TryGetSyntaxVersion(out newVersion) &&
oldVersion.Equals(newVersion))
{
// nothing has changed. don't do anything.
// this could happen if a document is opened/closed without any buffer change
return null;
}

var range = newText.GetEncompassingTextChangeRange(oldText);
if (range == default(TextChangeRange))
{
// nothing has changed. don't do anything
return null;
}
var range = newText.GetEncompassingTextChangeRange(oldText);
if (range == default(TextChangeRange))
{
// nothing has changed. don't do anything
return null;
}

var incrementalParsingCandidate = range.NewLength != newText.Length;
var incrementalParsingCandidate = range.NewLength != newText.Length;

// see whether we can get it without explicit parsing
SyntaxNode oldRoot;
SyntaxNode newRoot;
if (!oldDocument.TryGetSyntaxRoot(out oldRoot) ||
!newDocument.TryGetSyntaxRoot(out newRoot))
{
if (!incrementalParsingCandidate)
// see whether we can get it without explicit parsing
SyntaxNode oldRoot;
SyntaxNode newRoot;
if (!oldDocument.TryGetSyntaxRoot(out oldRoot) ||
!newDocument.TryGetSyntaxRoot(out newRoot))
{
// no cheap way to determine top level changes. assumes top level has changed
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
if (!incrementalParsingCandidate)
{
// no cheap way to determine top level changes. assumes top level has changed
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}

// explicitly parse them
oldRoot = await oldDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
}

// explicitly parse them
oldRoot = await oldDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
}
// at this point, we must have these version already calculated
VersionStamp oldTopLevelChangeVersion;
VersionStamp newTopLevelChangeVersion;
if (!oldDocument.TryGetTopLevelChangeTextVersion(out oldTopLevelChangeVersion) ||
!newDocument.TryGetTopLevelChangeTextVersion(out newTopLevelChangeVersion))
{
throw ExceptionUtilities.Unreachable;
}

// at this point, we must have these version already calculated
VersionStamp oldTopLevelChangeVersion;
VersionStamp newTopLevelChangeVersion;
if (!oldDocument.TryGetTopLevelChangeTextVersion(out oldTopLevelChangeVersion) ||
!newDocument.TryGetTopLevelChangeTextVersion(out newTopLevelChangeVersion))
{
Debug.Fail("How?");
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}
// quicker common case
if (incrementalParsingCandidate)
{
if (oldTopLevelChangeVersion.Equals(newTopLevelChangeVersion))
{
return new DocumentDifferenceResult(InvocationReasons.SyntaxChanged, GetChangedMember(syntaxFactsService, oldRoot, newRoot, range));
}

return new DocumentDifferenceResult(InvocationReasons.DocumentChanged, GetBestGuessChangedMember(syntaxFactsService, oldRoot, newRoot, range));
}

// quicker common case
if (incrementalParsingCandidate)
{
if (oldTopLevelChangeVersion.Equals(newTopLevelChangeVersion))
{
return new DocumentDifferenceResult(InvocationReasons.SyntaxChanged, GetChangedMember(syntaxFactsService, oldRoot, newRoot, range));
return new DocumentDifferenceResult(InvocationReasons.SyntaxChanged);
}

return new DocumentDifferenceResult(InvocationReasons.DocumentChanged, GetBestGuessChangedMember(syntaxFactsService, oldRoot, newRoot, range));
return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}

if (oldTopLevelChangeVersion.Equals(newTopLevelChangeVersion))
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
return new DocumentDifferenceResult(InvocationReasons.SyntaxChanged);
throw ExceptionUtilities.Unreachable;
}

return new DocumentDifferenceResult(InvocationReasons.DocumentChanged);
}

private static SyntaxNode GetChangedMember(
Expand Down