public Task <object> CreateChangedDocumentPreviewViewAsync(Document oldDocument, Document newDocument, double zoomLevel, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            // Note: We don't use the original buffer that is associated with oldDocument
            // (and currently open in the editor) for oldBuffer below. This is because oldBuffer
            // will be used inside a projection buffer inside our inline diff preview below
            // and platform's implementation currently has a bug where projection buffers
            // are being leaked. This leak means that if we use the original buffer that is
            // currently visible in the editor here, the projection buffer span calculation
            // would be triggered every time user changes some code in this buffer (even though
            // the diff view would long have been dismissed by the time user edits the code)
            // resulting in crashes. Instead we create a new buffer from the same content.
            // TODO: We could use ITextBufferCloneService instead here to clone the original buffer.
            var oldBuffer = CreateNewBuffer(oldDocument, cancellationToken);
            var newBuffer = CreateNewBuffer(newDocument, cancellationToken);

            // Convert the diffs to be line based.
            // Compute the diffs between the old text and the new.
            var diffResult = ComputeEditDifferences(oldDocument, newDocument, cancellationToken);

            // Need to show the spans in the right that are different.
            // We also need to show the spans that are in conflict.
            var originalSpans = GetOriginalSpans(diffResult, cancellationToken);
            var changedSpans  = GetChangedSpans(diffResult, cancellationToken);

            var newRoot              = newDocument.GetSyntaxRootAsync(cancellationToken).WaitAndGetResult(cancellationToken);
            var conflictNodes        = newRoot.GetAnnotatedNodesAndTokens(ConflictAnnotation.Kind);
            var conflictSpans        = conflictNodes.Select(n => n.Span.ToSpan()).ToList();
            var conflictDescriptions = conflictNodes.SelectMany(n => n.GetAnnotations(ConflictAnnotation.Kind))
                                       .Select(a => ConflictAnnotation.GetDescription(a))
                                       .Distinct();

            var warningNodes        = newRoot.GetAnnotatedNodesAndTokens(WarningAnnotation.Kind);
            var warningSpans        = warningNodes.Select(n => n.Span.ToSpan()).ToList();
            var warningDescriptions = warningNodes.SelectMany(n => n.GetAnnotations(WarningAnnotation.Kind))
                                      .Select(a => WarningAnnotation.GetDescription(a))
                                      .Distinct();

            AttachConflictAndWarningAnnotationToBuffer(newBuffer, conflictSpans, warningSpans);

            var description = conflictSpans.Count == 0 && warningSpans.Count == 0
                ? null
                : string.Join(Environment.NewLine, conflictDescriptions.Concat(warningDescriptions));

            var allSpans = new NormalizedSpanCollection(conflictSpans.Concat(warningSpans).Concat(changedSpans));

            var originalLineSpans = CreateLineSpans(oldBuffer.CurrentSnapshot, originalSpans, cancellationToken);
            var changedLineSpans  = CreateLineSpans(newBuffer.CurrentSnapshot, allSpans, cancellationToken);

            if (!originalLineSpans.Any())
            {
                // This means that we have no differences (likely because of conflicts).
                // In such cases, use the same spans for the left (old) buffer as the right (new) buffer.
                originalLineSpans = changedLineSpans;
            }

            // Create PreviewWorkspaces around the buffers to be displayed on the left and right
            // so that all IDE services (colorizer, squiggles etc.) light up in these buffers.
            var leftDocument = oldDocument.Project
                               .RemoveDocument(oldDocument.Id)
                               .AddDocument(oldDocument.Name, oldBuffer.AsTextContainer().CurrentText);
            var leftWorkspace = new PreviewWorkspace(leftDocument.Project.Solution);

            leftWorkspace.OpenDocument(leftDocument.Id);

            var rightWorkspace = new PreviewWorkspace(
                oldDocument.WithText(newBuffer.AsTextContainer().CurrentText).Project.Solution);

            rightWorkspace.OpenDocument(newDocument.Id);

            return(CreateChangedDocumentViewAsync(
                       oldBuffer, newBuffer, description, originalLineSpans, changedLineSpans,
                       leftWorkspace, rightWorkspace, zoomLevel, cancellationToken));
        }
        public async Task <DifferenceViewerPreview?> CreateChangedDocumentPreviewViewAsync(Document oldDocument, Document newDocument, double zoomLevel, CancellationToken cancellationToken)
        {
            // CreateNewBufferAsync must be called from the main thread
            await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            // Note: We don't use the original buffer that is associated with oldDocument
            // (and currently open in the editor) for oldBuffer below. This is because oldBuffer
            // will be used inside a projection buffer inside our inline diff preview below
            // and platform's implementation currently has a bug where projection buffers
            // are being leaked. This leak means that if we use the original buffer that is
            // currently visible in the editor here, the projection buffer span calculation
            // would be triggered every time user changes some code in this buffer (even though
            // the diff view would long have been dismissed by the time user edits the code)
            // resulting in crashes. Instead we create a new buffer from the same content.
            // TODO: We could use ITextBufferCloneService instead here to clone the original buffer.
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF)
            var oldBuffer = await CreateNewBufferAsync(oldDocument, cancellationToken);

            var newBuffer = await CreateNewBufferAsync(newDocument, cancellationToken);

#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task

            // Convert the diffs to be line based.
            // Compute the diffs between the old text and the new.
            var diffResult = ComputeEditDifferences(oldDocument, newDocument, cancellationToken);

            // Need to show the spans in the right that are different.
            // We also need to show the spans that are in conflict.
            var    originalSpans = GetOriginalSpans(diffResult, cancellationToken);
            var    changedSpans  = GetChangedSpans(diffResult, cancellationToken);
            string?description   = null;
            NormalizedSpanCollection allSpans;

            if (newDocument.SupportsSyntaxTree)
            {
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF)
                var newRoot = await newDocument.GetRequiredSyntaxRootAsync(cancellationToken);

#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
                var conflictNodes        = newRoot.GetAnnotatedNodesAndTokens(ConflictAnnotation.Kind);
                var conflictSpans        = conflictNodes.Select(n => n.Span.ToSpan()).ToList();
                var conflictDescriptions = conflictNodes.SelectMany(n => n.GetAnnotations(ConflictAnnotation.Kind))
                                           .Select(a => $"❌ {ConflictAnnotation.GetDescription(a)}")
                                           .Distinct();

                var warningNodes        = newRoot.GetAnnotatedNodesAndTokens(WarningAnnotation.Kind);
                var warningSpans        = warningNodes.Select(n => n.Span.ToSpan()).ToList();
                var warningDescriptions = warningNodes.SelectMany(n => n.GetAnnotations(WarningAnnotation.Kind))
                                          .Select(a => $"⚠ {WarningAnnotation.GetDescription(a)}")
                                          .Distinct();

                var suppressDiagnosticsNodes = newRoot.GetAnnotatedNodesAndTokens(SuppressDiagnosticsAnnotation.Kind);
                var suppressDiagnosticsSpans = suppressDiagnosticsNodes.Select(n => n.Span.ToSpan()).ToList();
                AttachAnnotationsToBuffer(newBuffer, conflictSpans, warningSpans, suppressDiagnosticsSpans);

                description = conflictSpans.Count == 0 && warningSpans.Count == 0
                    ? null
                    : string.Join(Environment.NewLine, conflictDescriptions.Concat(warningDescriptions));
                allSpans = new NormalizedSpanCollection(conflictSpans.Concat(warningSpans).Concat(changedSpans));
            }
            else
            {
                allSpans = new NormalizedSpanCollection(changedSpans);
            }

            var originalLineSpans = CreateLineSpans(oldBuffer.CurrentSnapshot, originalSpans, cancellationToken);
            var changedLineSpans  = CreateLineSpans(newBuffer.CurrentSnapshot, allSpans, cancellationToken);
            if (!originalLineSpans.Any())
            {
                // This means that we have no differences (likely because of conflicts).
                // In such cases, use the same spans for the left (old) buffer as the right (new) buffer.
                originalLineSpans = changedLineSpans;
            }

            // Create PreviewWorkspaces around the buffers to be displayed on the left and right
            // so that all IDE services (colorizer, squiggles etc.) light up in these buffers.
            var leftWorkspace = new PreviewWorkspace(oldDocument.Project.Solution);
            leftWorkspace.OpenDocument(oldDocument.Id, oldBuffer.AsTextContainer());

            var rightWorkspace = new PreviewWorkspace(newDocument.Project.Solution);
            rightWorkspace.OpenDocument(newDocument.Id, newBuffer.AsTextContainer());

#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF)
            return(await CreateChangedDocumentViewAsync(
                       oldBuffer, newBuffer, description, originalLineSpans, changedLineSpans,
                       leftWorkspace, rightWorkspace, zoomLevel, cancellationToken));

#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
        }
Exemple #3
0
        public async Task TestWarnsWithMatchingExtensionMethodUsedAsDelegate(bool useSymbolAnnotations)
        {
            var source = @"using System;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}";

            await TestAsync(
                source,
                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}",

                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(C1 c1) => 42.M;
}", safe : true, useSymbolAnnotations);

            var doc = await GetDocument(source, useSymbolAnnotations);

            OptionSet options = await doc.GetOptionsAsync();

            var imported = await ImportAdder.AddImportsFromSyntaxesAsync(doc, true, options);

            var root = await imported.GetSyntaxRootAsync();

            var nodeWithWarning = root.GetAnnotatedNodes(WarningAnnotation.Kind).Single();

            Assert.Equal("42.M", nodeWithWarning.ToFullString());

            var warning = nodeWithWarning.GetAnnotations(WarningAnnotation.Kind).Single();
            var expectedWarningMessage = string.Format(WorkspacesResources.Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access, "M");

            Assert.Equal(expectedWarningMessage, WarningAnnotation.GetDescription(warning));
        }
Exemple #4
0
        protected async Task TestChangeNamespaceAsync(
            string initialMarkUp,
            string expectedSourceOriginal,
            string expectedSourceReference = null)
        {
            var testOptions = new TestParameters();

            using (var workspace = CreateWorkspaceFromOptions(initialMarkUp, testOptions))
            {
                if (workspace.Projects.Count == 2)
                {
                    var project          = workspace.Documents.Single(doc => !doc.SelectedSpans.IsEmpty()).Project;
                    var dependentProject = workspace.Projects.Single(proj => proj.Id != project.Id);
                    var references       = dependentProject.ProjectReferences.ToList();
                    references.Add(new ProjectReference(project.Id));
                    dependentProject.ProjectReferences = references;
                    workspace.OnProjectReferenceAdded(dependentProject.Id, new ProjectReference(project.Id));
                }

                if (expectedSourceOriginal != null)
                {
                    var originalDocument   = workspace.Documents.Single(doc => !doc.SelectedSpans.IsEmpty());
                    var originalDocumentId = originalDocument.Id;

                    var refDocument   = workspace.Documents.Where(doc => doc.Id != originalDocumentId).SingleOrDefault();
                    var refDocumentId = refDocument?.Id;

                    var oldAndNewSolution = await TestOperationAsync(testOptions, workspace);

                    var oldSolution = oldAndNewSolution.Item1;
                    var newSolution = oldAndNewSolution.Item2;

                    var changedDocumentIds = SolutionUtilities.GetChangedDocuments(oldSolution, newSolution);

                    Assert.True(changedDocumentIds.Contains(originalDocumentId), "original document was not changed.");

                    var modifiedOriginalDocument = newSolution.GetDocument(originalDocumentId);
                    var modifiedOringinalRoot    = await modifiedOriginalDocument.GetSyntaxRootAsync();

                    // One node/token will contain the warning we attached for change namespace action.
                    Assert.Single(modifiedOringinalRoot.DescendantNodesAndTokensAndSelf().Where(n =>
                    {
                        IEnumerable <SyntaxAnnotation> annotations;
                        if (n.IsNode)
                        {
                            annotations = n.AsNode().GetAnnotations(WarningAnnotation.Kind);
                        }
                        else
                        {
                            annotations = n.AsToken().GetAnnotations(WarningAnnotation.Kind);
                        }

                        return(annotations.Any(annotation =>
                                               WarningAnnotation.GetDescription(annotation) == FeaturesResources.Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning));
                    }));


                    var actualText = (await modifiedOriginalDocument.GetTextAsync()).ToString();
                    Assert.Equal(expectedSourceOriginal, actualText);

                    if (expectedSourceReference == null)
                    {
                        // there shouldn't be any textual change
                        if (changedDocumentIds.Contains(refDocumentId))
                        {
                            var oldRefText = (await oldSolution.GetDocument(refDocumentId).GetTextAsync()).ToString();
                            var newRefText = (await newSolution.GetDocument(refDocumentId).GetTextAsync()).ToString();
                            Assert.Equal(oldRefText, newRefText);
                        }
                    }
                    else
                    {
                        Assert.True(changedDocumentIds.Contains(refDocumentId));
                        var actualRefText = (await newSolution.GetDocument(refDocumentId).GetTextAsync()).ToString();
                        Assert.Equal(expectedSourceReference, actualRefText);
                    }
                }
                else
                {
                    var(actions, _) = await GetCodeActionsAsync(workspace, testOptions);

                    if (actions.Length > 0)
                    {
                        var hasChangeNamespaceAction = actions.Any(action => action is CodeAction.SolutionChangeAction);
                        Assert.False(hasChangeNamespaceAction, "Change namespace to match folder action was not expected, but shows up.");
                    }
                }
            }

            async Task <Tuple <Solution, Solution> > TestOperationAsync(TestParameters parameters, TestWorkspace workspace)
            {
                var(actions, _) = await GetCodeActionsAsync(workspace, parameters);

                var changeNamespaceAction = actions.Single(a => a is CodeAction.SolutionChangeAction);
                var operations            = await changeNamespaceAction.GetOperationsAsync(CancellationToken.None);

                return(ApplyOperationsAndGetSolution(workspace, operations));
            }
        }
Exemple #5
0
        public async Task TestWarnsWithMatchingExtensionMethodUsedAsDelegate(bool useSymbolAnnotations)
        {
            var source = @"using System;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}";

            await TestAsync(
                source,
                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(A.C1 c1) => 42.M;
}",

                @"using System;
using A;
using B;

namespace A
{
    static class AExtensions
    {
        public static void M(this int a){}
    }
    public class C1 {}
}

namespace B
{
    static class BExtensions
    {
        public static void M(this object a){}
    }
}

class C
{
    Action M(C1 c1) => 42.M;
}", safe : true, useSymbolAnnotations);

            var doc = await GetDocument(source, useSymbolAnnotations);

            OptionSet options = await doc.GetOptionsAsync();

            var imported = await ImportAdder.AddImportsFromSyntaxesAsync(doc, true, options);

            var root = await imported.GetSyntaxRootAsync();

            var nodeWithWarning = root.GetAnnotatedNodes(WarningAnnotation.Kind).Single();

            Assert.Equal("42.M", nodeWithWarning.ToFullString());

            var warning = nodeWithWarning.GetAnnotations(WarningAnnotation.Kind).Single();

            Assert.Equal("Adding imports will bring an extension method into scope with the same name as 'M'", WarningAnnotation.GetDescription(warning));
        }