| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- commit da935bfa95a8d6112cb892744d62dc8c5e5da88d
- Author: N. Taylor Mullen <[email protected]>
- Date: Thu Sep 6 18:12:24 2018 -0700
- Generated document output persists result to generated code container.
-
- - Prior to this we had a `BackgroundDocumentGenerator` that would constantly be updating generated code containers. With this changes we've changed the details in how a `GeneratedCodeContainer` can be mutated. It can now be touched from any thread and is updated when an underlying `DocumentSnapshot` has available content. However, if a generated output comes through that's older then the last seen output we no-op.
- - Removed `BackgroundDocumentGenerator` SetOutput logic.
- - Updated tests to react to new behavior of `GetGeneratedOutputAsync`.
- - Added new test to verify getting generated output results in the setting of the host documents output.
- diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs
- index a3e11da04fa..e84cd5fd0b2 100644
- --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs
- +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs
- @@ -111,7 +111,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-
- var projectEngine = project.GetProjectEngine();
-
- - return projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers);
- + var codeDocument = projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers);
- + var csharpDocument = codeDocument.GetCSharpDocument();
- + if (document is DefaultDocumentSnapshot defaultDocument)
- + {
- + defaultDocument.State.HostDocument.GeneratedCodeContainer.SetOutput(csharpDocument, defaultDocument);
- + }
- +
- + return codeDocument;
- }
-
- private async Task<RazorSourceDocument> GetRazorSourceDocumentAsync(DocumentSnapshot document)
- diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
- index 342da76b09a..548a6733a6f 100644
- --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
- +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
- @@ -2,32 +2,89 @@
- // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
- using System;
- -using Microsoft.CodeAnalysis.Text;
- -using Microsoft.CodeAnalysis.Experiment;
- -using Microsoft.AspNetCore.Razor.Language;
- +using System.Collections.Generic;
- using System.Collections.Immutable;
- -using System.Threading.Tasks;
- +using System.Diagnostics;
- using System.Threading;
- -using System.Collections.Generic;
- +using System.Threading.Tasks;
- +using Microsoft.AspNetCore.Razor.Language;
- +using Microsoft.CodeAnalysis.Experiment;
- +using Microsoft.CodeAnalysis.Text;
-
- namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- {
- internal class GeneratedCodeContainer : IDocumentServiceFactory, ISpanMapper
- {
- + public event EventHandler<TextChangeEventArgs> GeneratedCodeChanged;
- +
- + private SourceText _source;
- + private VersionStamp _sourceVersion;
- + private RazorCSharpDocument _output;
- + private DocumentSnapshot _latestDocument;
- +
- + private readonly object _setOutputLock = new object();
- private readonly TextContainer _textContainer;
-
- public GeneratedCodeContainer()
- {
- - _textContainer = new TextContainer();
- + _textContainer = new TextContainer(_setOutputLock);
- + _textContainer.TextChanged += TextContainer_TextChanged;
- }
-
- - public SourceText Source { get; private set; }
- + public SourceText Source
- + {
- + get
- + {
- + lock (_setOutputLock)
- + {
- + return _source;
- + }
- + }
- + }
-
- - public VersionStamp SourceVersion { get; private set; }
- + public VersionStamp SourceVersion
- + {
- + get
- + {
- + lock (_setOutputLock)
- + {
- + return _sourceVersion;
- + }
- + }
- + }
-
- - public RazorCSharpDocument Output { get; private set; }
- + public RazorCSharpDocument Output
- + {
- + get
- + {
- + lock (_setOutputLock)
- + {
- + return _output;
- + }
- + }
- + }
-
- - public SourceTextContainer SourceTextContainer => _textContainer;
- + public DocumentSnapshot LatestDocument
- + {
- + get
- + {
- + lock (_setOutputLock)
- + {
- + return _latestDocument;
- + }
- + }
- + }
- +
- + public SourceTextContainer SourceTextContainer
- + {
- + get
- + {
- + lock (_setOutputLock)
- + {
- + return _textContainer;
- + }
- + }
- + }
-
- public TService GetService<TService>()
- {
- @@ -39,28 +96,59 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- return default(TService);
- }
-
- - public void SetOutput(SourceText source, RazorCodeDocument codeDocument)
- + public void SetOutput(RazorCSharpDocument csharpDocument, DefaultDocumentSnapshot document)
- {
- - Source = source;
- - Output = codeDocument.GetCSharpDocument();
- + lock (_setOutputLock)
- + {
- + if (!document.TryGetTextVersion(out var version))
- + {
- + Debug.Fail("The text version should have already been evaluated.");
- + return;
- + }
- +
- + var newerVersion = SourceVersion.GetNewerVersion(version);
- + if (newerVersion == SourceVersion)
- + {
- + // Latest document is newer than the provided document.
- + return;
- + }
-
- - _textContainer.SetText(SourceText.From(Output.GeneratedCode));
- + if (!document.TryGetText(out var source))
- + {
- + Debug.Fail("The text should have already been evaluated.");
- + return;
- + }
- +
- + _source = source;
- + _sourceVersion = version;
- + _output = csharpDocument;
- + _latestDocument = document;
- + _textContainer.SetText(SourceText.From(Output.GeneratedCode));
- + }
- }
-
- public Task<ImmutableArray<SpanMapResult>> MapSpansAsync(
- - Document document,
- - IEnumerable<TextSpan> spans,
- - CancellationToken cancellationToken)
- + Document document,
- + IEnumerable<TextSpan> spans,
- + CancellationToken cancellationToken)
- {
- - if (Output == null)
- + RazorCSharpDocument output;
- + SourceText source;
- + lock (_setOutputLock)
- {
- - return Task.FromResult(ImmutableArray<SpanMapResult>.Empty);
- + if (Output == null)
- + {
- + return Task.FromResult(ImmutableArray<SpanMapResult>.Empty);
- + }
- +
- + output = Output;
- + source = Source;
- }
-
- var results = ImmutableArray.CreateBuilder<SpanMapResult>();
- foreach (var span in spans)
- {
- - if (TryGetLinePositionSpan(span, out var linePositionSpan))
- + if (TryGetLinePositionSpan(span, source, output, out var linePositionSpan))
- {
- results.Add(new SpanMapResult(document, linePositionSpan));
- }
- @@ -70,11 +158,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- }
-
- // Internal for testing.
- - internal bool TryGetLinePositionSpan(TextSpan span, out LinePositionSpan linePositionSpan)
- + internal static bool TryGetLinePositionSpan(TextSpan span, SourceText source, RazorCSharpDocument output, out LinePositionSpan linePositionSpan)
- {
- - for (var i = 0; i < Output.SourceMappings.Count; i++)
- + for (var i = 0; i < output.SourceMappings.Count; i++)
- {
- - var mapping = Output.SourceMappings[i];
- + var mapping = output.SourceMappings[i];
- if (span.Length > mapping.GeneratedSpan.Length)
- {
- // If the length of the generated span is smaller they can't match. A C# expression
- @@ -94,7 +182,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- {
- // This span mapping contains the span.
- var adjusted = new TextSpan(original.Start + leftOffset, (original.End + rightOffset) - (original.Start + leftOffset));
- - linePositionSpan = Source.Lines.GetLinePositionSpan(adjusted);
- + linePositionSpan = source.Lines.GetLinePositionSpan(adjusted);
- return true;
- }
- }
- @@ -103,15 +191,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- return false;
- }
-
- + private void TextContainer_TextChanged(object sender, TextChangeEventArgs args)
- + {
- + GeneratedCodeChanged?.Invoke(this, args);
- + }
- +
- private class TextContainer : SourceTextContainer
- {
- public override event EventHandler<TextChangeEventArgs> TextChanged;
-
- + private readonly object _outerLock;
- private SourceText _currentText;
-
- - public TextContainer()
- + public TextContainer(object outerLock)
- : this(SourceText.From(string.Empty))
- {
- + _outerLock = outerLock;
- }
-
- public TextContainer(SourceText sourceText)
- @@ -124,7 +219,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- _currentText = sourceText;
- }
-
- - public override SourceText CurrentText => _currentText;
- + public override SourceText CurrentText
- + {
- + get
- + {
- + lock (_outerLock)
- + {
- + return _currentText;
- + }
- + }
- + }
-
- public void SetText(SourceText sourceText)
- {
- @@ -133,10 +237,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- throw new ArgumentNullException(nameof(sourceText));
- }
-
- - var e = new TextChangeEventArgs(_currentText, sourceText);
- - _currentText = sourceText;
- + lock (_outerLock)
- + {
-
- - TextChanged?.Invoke(this, e);
- + var e = new TextChangeEventArgs(_currentText, sourceText);
- + _currentText = sourceText;
- +
- + TextChanged?.Invoke(this, e);
- + }
- }
- }
- }
- diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
- index b0a99e8e244..e06a26fe6ad 100644
- --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
- +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
- @@ -187,12 +187,6 @@ namespace Microsoft.CodeAnalysis.Razor
-
- OnCompletingBackgroundWork();
-
- - await Task.Factory.StartNew(
- - () => ReportUpdates(work),
- - CancellationToken.None,
- - TaskCreationOptions.None,
- - _foregroundDispatcher.ForegroundScheduler);
- -
- lock (_work)
- {
- // Resetting the timer allows another batch of work to start.
- @@ -219,22 +213,6 @@ namespace Microsoft.CodeAnalysis.Razor
- }
- }
-
- - private void ReportUpdates(KeyValuePair<DocumentKey, DocumentSnapshot>[] work)
- - {
- - for (var i = 0; i < work.Length; i++)
- - {
- - var key = work[i].Key;
- - var document = work[i].Value;
- -
- - if (document.TryGetText(out var source) &&
- - document.TryGetGeneratedOutput(out var output))
- - {
- - var container = ((DefaultDocumentSnapshot)document).State.GeneratedCodeContainer;
- - container.SetOutput(source, output);
- - }
- - }
- - }
- -
- private void ReportError(DocumentSnapshot document, Exception ex)
- {
- GC.KeepAlive(Task.Factory.StartNew(
- diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs
- new file mode 100644
- index 00000000000..860334c66a2
- --- /dev/null
- +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs
- @@ -0,0 +1,72 @@
- +// Copyright (c) .NET Foundation. All rights reserved.
- +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
- +
- +using System.Threading.Tasks;
- +using Microsoft.AspNetCore.Razor.Language;
- +using Microsoft.CodeAnalysis.Host;
- +using Microsoft.CodeAnalysis.Text;
- +using Xunit;
- +
- +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- +{
- + public class DefaultDocumentSnapshotTest
- + {
- + public DefaultDocumentSnapshotTest()
- + {
- + var services = TestServices.Create(
- + new[] { new TestProjectSnapshotProjectEngineFactory() },
- + new[] { new TestTagHelperResolver() });
- + Workspace = TestWorkspace.Create(services);
- + var hostProject = new HostProject("C:/some/path/project.csproj", RazorConfiguration.Default);
- + var projectState = ProjectState.Create(Workspace.Services, hostProject);
- + var project = new DefaultProjectSnapshot(projectState);
- + HostDocument = new HostDocument("C:/some/path/file.cshtml", "C:/some/path/file.cshtml");
- + SourceText = Text.SourceText.From("<p>Hello World</p>");
- + Version = VersionStamp.Default.GetNewerVersion();
- + var textAndVersion = TextAndVersion.Create(SourceText, Version);
- + var documentState = DocumentState.Create(Workspace.Services, HostDocument, () => Task.FromResult(textAndVersion));
- + Document = new DefaultDocumentSnapshot(project, documentState);
- +
- + }
- +
- + private Workspace Workspace { get; }
- +
- + private SourceText SourceText { get; }
- +
- + private VersionStamp Version { get; }
- +
- + private HostDocument HostDocument { get; }
- +
- + private DefaultDocumentSnapshot Document { get; }
- +
- + [Fact]
- + public async Task GetGeneratedOutputAsync_SetsHostDocumentOutput()
- + {
- + // Act
- + await Document.GetGeneratedOutputAsync();
- +
- + // Assert
- + Assert.NotNull(HostDocument.GeneratedCodeContainer.Output);
- + Assert.Same(SourceText, HostDocument.GeneratedCodeContainer.Source);
- + }
- +
- + [Fact]
- + public async Task GetGeneratedOutputAsync_OnlySetsOutputIfDocumentNewer()
- + {
- + // Arrange
- + var newSourceText = SourceText.From("NEW!");
- + var newDocumentState = Document.State.WithText(newSourceText, Version.GetNewerVersion());
- + var newDocument = new DefaultDocumentSnapshot(Document.Project, newDocumentState);
- +
- + // Force the output to be the new output
- + await newDocument.GetGeneratedOutputAsync();
- +
- + // Act
- + await Document.GetGeneratedOutputAsync();
- +
- + // Assert
- + Assert.NotNull(HostDocument.GeneratedCodeContainer.Output);
- + Assert.Same(newSourceText, HostDocument.GeneratedCodeContainer.Source);
- + }
- + }
- +}
- diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs
- index 0f2c03c02ea..ede9b52d30f 100644
- --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs
- +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs
- @@ -20,10 +20,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- ";
- var sourceText = SourceText.From(content);
- var codeDocument = GetCodeDocument(content);
- - var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode;
- -
- - var container = new GeneratedCodeContainer();
- - container.SetOutput(sourceText, codeDocument);
- + var csharpDocument = codeDocument.GetCSharpDocument();
- + var generatedCode = csharpDocument.GeneratedCode;
-
- // TODO: Make writing these tests a little less manual.
- // Position of `SomeProperty` in the generated code.
- @@ -34,7 +32,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- var expectedLineSpan = new LinePositionSpan(new LinePosition(2, 22), new LinePosition(2, 34));
-
- // Act
- - var result = container.TryGetLinePositionSpan(span, out var lineSpan);
- + var result = GeneratedCodeContainer.TryGetLinePositionSpan(span, sourceText, csharpDocument, out var lineSpan);
-
- // Assert
- Assert.True(result);
- @@ -52,17 +50,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
- ";
- var sourceText = SourceText.From(content);
- var codeDocument = GetCodeDocument(content);
- - var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode;
- -
- - var container = new GeneratedCodeContainer();
- - container.SetOutput(sourceText, codeDocument);
- + var csharpDocument = codeDocument.GetCSharpDocument();
- + var generatedCode = csharpDocument.GeneratedCode;
-
- // Position of `ExecuteAsync` in the generated code.
- var symbol = "ExecuteAsync";
- var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length);
-
- // Act
- - var result = container.TryGetLinePositionSpan(span, out var lineSpan);
- + var result = GeneratedCodeContainer.TryGetLinePositionSpan(span, sourceText, csharpDocument, out var lineSpan);
-
- // Assert
- Assert.False(result);
|