Razor 953 B

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. commit da935bfa95a8d6112cb892744d62dc8c5e5da88d
  2. Author: N. Taylor Mullen <[email protected]>
  3. Date: Thu Sep 6 18:12:24 2018 -0700
  4. Generated document output persists result to generated code container.
  5. - 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.
  6. - Removed `BackgroundDocumentGenerator` SetOutput logic.
  7. - Updated tests to react to new behavior of `GetGeneratedOutputAsync`.
  8. - Added new test to verify getting generated output results in the setting of the host documents output.
  9. diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs
  10. index a3e11da04fa..e84cd5fd0b2 100644
  11. --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs
  12. +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentGeneratedOutputTracker.cs
  13. @@ -111,7 +111,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  14. var projectEngine = project.GetProjectEngine();
  15. - return projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers);
  16. + var codeDocument = projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers);
  17. + var csharpDocument = codeDocument.GetCSharpDocument();
  18. + if (document is DefaultDocumentSnapshot defaultDocument)
  19. + {
  20. + defaultDocument.State.HostDocument.GeneratedCodeContainer.SetOutput(csharpDocument, defaultDocument);
  21. + }
  22. +
  23. + return codeDocument;
  24. }
  25. private async Task<RazorSourceDocument> GetRazorSourceDocumentAsync(DocumentSnapshot document)
  26. diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
  27. index 342da76b09a..548a6733a6f 100644
  28. --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
  29. +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/GeneratedCodeContainer.cs
  30. @@ -2,32 +2,89 @@
  31. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  32. using System;
  33. -using Microsoft.CodeAnalysis.Text;
  34. -using Microsoft.CodeAnalysis.Experiment;
  35. -using Microsoft.AspNetCore.Razor.Language;
  36. +using System.Collections.Generic;
  37. using System.Collections.Immutable;
  38. -using System.Threading.Tasks;
  39. +using System.Diagnostics;
  40. using System.Threading;
  41. -using System.Collections.Generic;
  42. +using System.Threading.Tasks;
  43. +using Microsoft.AspNetCore.Razor.Language;
  44. +using Microsoft.CodeAnalysis.Experiment;
  45. +using Microsoft.CodeAnalysis.Text;
  46. namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  47. {
  48. internal class GeneratedCodeContainer : IDocumentServiceFactory, ISpanMapper
  49. {
  50. + public event EventHandler<TextChangeEventArgs> GeneratedCodeChanged;
  51. +
  52. + private SourceText _source;
  53. + private VersionStamp _sourceVersion;
  54. + private RazorCSharpDocument _output;
  55. + private DocumentSnapshot _latestDocument;
  56. +
  57. + private readonly object _setOutputLock = new object();
  58. private readonly TextContainer _textContainer;
  59. public GeneratedCodeContainer()
  60. {
  61. - _textContainer = new TextContainer();
  62. + _textContainer = new TextContainer(_setOutputLock);
  63. + _textContainer.TextChanged += TextContainer_TextChanged;
  64. }
  65. - public SourceText Source { get; private set; }
  66. + public SourceText Source
  67. + {
  68. + get
  69. + {
  70. + lock (_setOutputLock)
  71. + {
  72. + return _source;
  73. + }
  74. + }
  75. + }
  76. - public VersionStamp SourceVersion { get; private set; }
  77. + public VersionStamp SourceVersion
  78. + {
  79. + get
  80. + {
  81. + lock (_setOutputLock)
  82. + {
  83. + return _sourceVersion;
  84. + }
  85. + }
  86. + }
  87. - public RazorCSharpDocument Output { get; private set; }
  88. + public RazorCSharpDocument Output
  89. + {
  90. + get
  91. + {
  92. + lock (_setOutputLock)
  93. + {
  94. + return _output;
  95. + }
  96. + }
  97. + }
  98. - public SourceTextContainer SourceTextContainer => _textContainer;
  99. + public DocumentSnapshot LatestDocument
  100. + {
  101. + get
  102. + {
  103. + lock (_setOutputLock)
  104. + {
  105. + return _latestDocument;
  106. + }
  107. + }
  108. + }
  109. +
  110. + public SourceTextContainer SourceTextContainer
  111. + {
  112. + get
  113. + {
  114. + lock (_setOutputLock)
  115. + {
  116. + return _textContainer;
  117. + }
  118. + }
  119. + }
  120. public TService GetService<TService>()
  121. {
  122. @@ -39,28 +96,59 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  123. return default(TService);
  124. }
  125. - public void SetOutput(SourceText source, RazorCodeDocument codeDocument)
  126. + public void SetOutput(RazorCSharpDocument csharpDocument, DefaultDocumentSnapshot document)
  127. {
  128. - Source = source;
  129. - Output = codeDocument.GetCSharpDocument();
  130. + lock (_setOutputLock)
  131. + {
  132. + if (!document.TryGetTextVersion(out var version))
  133. + {
  134. + Debug.Fail("The text version should have already been evaluated.");
  135. + return;
  136. + }
  137. +
  138. + var newerVersion = SourceVersion.GetNewerVersion(version);
  139. + if (newerVersion == SourceVersion)
  140. + {
  141. + // Latest document is newer than the provided document.
  142. + return;
  143. + }
  144. - _textContainer.SetText(SourceText.From(Output.GeneratedCode));
  145. + if (!document.TryGetText(out var source))
  146. + {
  147. + Debug.Fail("The text should have already been evaluated.");
  148. + return;
  149. + }
  150. +
  151. + _source = source;
  152. + _sourceVersion = version;
  153. + _output = csharpDocument;
  154. + _latestDocument = document;
  155. + _textContainer.SetText(SourceText.From(Output.GeneratedCode));
  156. + }
  157. }
  158. public Task<ImmutableArray<SpanMapResult>> MapSpansAsync(
  159. - Document document,
  160. - IEnumerable<TextSpan> spans,
  161. - CancellationToken cancellationToken)
  162. + Document document,
  163. + IEnumerable<TextSpan> spans,
  164. + CancellationToken cancellationToken)
  165. {
  166. - if (Output == null)
  167. + RazorCSharpDocument output;
  168. + SourceText source;
  169. + lock (_setOutputLock)
  170. {
  171. - return Task.FromResult(ImmutableArray<SpanMapResult>.Empty);
  172. + if (Output == null)
  173. + {
  174. + return Task.FromResult(ImmutableArray<SpanMapResult>.Empty);
  175. + }
  176. +
  177. + output = Output;
  178. + source = Source;
  179. }
  180. var results = ImmutableArray.CreateBuilder<SpanMapResult>();
  181. foreach (var span in spans)
  182. {
  183. - if (TryGetLinePositionSpan(span, out var linePositionSpan))
  184. + if (TryGetLinePositionSpan(span, source, output, out var linePositionSpan))
  185. {
  186. results.Add(new SpanMapResult(document, linePositionSpan));
  187. }
  188. @@ -70,11 +158,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  189. }
  190. // Internal for testing.
  191. - internal bool TryGetLinePositionSpan(TextSpan span, out LinePositionSpan linePositionSpan)
  192. + internal static bool TryGetLinePositionSpan(TextSpan span, SourceText source, RazorCSharpDocument output, out LinePositionSpan linePositionSpan)
  193. {
  194. - for (var i = 0; i < Output.SourceMappings.Count; i++)
  195. + for (var i = 0; i < output.SourceMappings.Count; i++)
  196. {
  197. - var mapping = Output.SourceMappings[i];
  198. + var mapping = output.SourceMappings[i];
  199. if (span.Length > mapping.GeneratedSpan.Length)
  200. {
  201. // If the length of the generated span is smaller they can't match. A C# expression
  202. @@ -94,7 +182,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  203. {
  204. // This span mapping contains the span.
  205. var adjusted = new TextSpan(original.Start + leftOffset, (original.End + rightOffset) - (original.Start + leftOffset));
  206. - linePositionSpan = Source.Lines.GetLinePositionSpan(adjusted);
  207. + linePositionSpan = source.Lines.GetLinePositionSpan(adjusted);
  208. return true;
  209. }
  210. }
  211. @@ -103,15 +191,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  212. return false;
  213. }
  214. + private void TextContainer_TextChanged(object sender, TextChangeEventArgs args)
  215. + {
  216. + GeneratedCodeChanged?.Invoke(this, args);
  217. + }
  218. +
  219. private class TextContainer : SourceTextContainer
  220. {
  221. public override event EventHandler<TextChangeEventArgs> TextChanged;
  222. + private readonly object _outerLock;
  223. private SourceText _currentText;
  224. - public TextContainer()
  225. + public TextContainer(object outerLock)
  226. : this(SourceText.From(string.Empty))
  227. {
  228. + _outerLock = outerLock;
  229. }
  230. public TextContainer(SourceText sourceText)
  231. @@ -124,7 +219,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  232. _currentText = sourceText;
  233. }
  234. - public override SourceText CurrentText => _currentText;
  235. + public override SourceText CurrentText
  236. + {
  237. + get
  238. + {
  239. + lock (_outerLock)
  240. + {
  241. + return _currentText;
  242. + }
  243. + }
  244. + }
  245. public void SetText(SourceText sourceText)
  246. {
  247. @@ -133,10 +237,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  248. throw new ArgumentNullException(nameof(sourceText));
  249. }
  250. - var e = new TextChangeEventArgs(_currentText, sourceText);
  251. - _currentText = sourceText;
  252. + lock (_outerLock)
  253. + {
  254. - TextChanged?.Invoke(this, e);
  255. + var e = new TextChangeEventArgs(_currentText, sourceText);
  256. + _currentText = sourceText;
  257. +
  258. + TextChanged?.Invoke(this, e);
  259. + }
  260. }
  261. }
  262. }
  263. diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
  264. index b0a99e8e244..e06a26fe6ad 100644
  265. --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
  266. +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs
  267. @@ -187,12 +187,6 @@ namespace Microsoft.CodeAnalysis.Razor
  268. OnCompletingBackgroundWork();
  269. - await Task.Factory.StartNew(
  270. - () => ReportUpdates(work),
  271. - CancellationToken.None,
  272. - TaskCreationOptions.None,
  273. - _foregroundDispatcher.ForegroundScheduler);
  274. -
  275. lock (_work)
  276. {
  277. // Resetting the timer allows another batch of work to start.
  278. @@ -219,22 +213,6 @@ namespace Microsoft.CodeAnalysis.Razor
  279. }
  280. }
  281. - private void ReportUpdates(KeyValuePair<DocumentKey, DocumentSnapshot>[] work)
  282. - {
  283. - for (var i = 0; i < work.Length; i++)
  284. - {
  285. - var key = work[i].Key;
  286. - var document = work[i].Value;
  287. -
  288. - if (document.TryGetText(out var source) &&
  289. - document.TryGetGeneratedOutput(out var output))
  290. - {
  291. - var container = ((DefaultDocumentSnapshot)document).State.GeneratedCodeContainer;
  292. - container.SetOutput(source, output);
  293. - }
  294. - }
  295. - }
  296. -
  297. private void ReportError(DocumentSnapshot document, Exception ex)
  298. {
  299. GC.KeepAlive(Task.Factory.StartNew(
  300. diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs
  301. new file mode 100644
  302. index 00000000000..860334c66a2
  303. --- /dev/null
  304. +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs
  305. @@ -0,0 +1,72 @@
  306. +// Copyright (c) .NET Foundation. All rights reserved.
  307. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  308. +
  309. +using System.Threading.Tasks;
  310. +using Microsoft.AspNetCore.Razor.Language;
  311. +using Microsoft.CodeAnalysis.Host;
  312. +using Microsoft.CodeAnalysis.Text;
  313. +using Xunit;
  314. +
  315. +namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  316. +{
  317. + public class DefaultDocumentSnapshotTest
  318. + {
  319. + public DefaultDocumentSnapshotTest()
  320. + {
  321. + var services = TestServices.Create(
  322. + new[] { new TestProjectSnapshotProjectEngineFactory() },
  323. + new[] { new TestTagHelperResolver() });
  324. + Workspace = TestWorkspace.Create(services);
  325. + var hostProject = new HostProject("C:/some/path/project.csproj", RazorConfiguration.Default);
  326. + var projectState = ProjectState.Create(Workspace.Services, hostProject);
  327. + var project = new DefaultProjectSnapshot(projectState);
  328. + HostDocument = new HostDocument("C:/some/path/file.cshtml", "C:/some/path/file.cshtml");
  329. + SourceText = Text.SourceText.From("<p>Hello World</p>");
  330. + Version = VersionStamp.Default.GetNewerVersion();
  331. + var textAndVersion = TextAndVersion.Create(SourceText, Version);
  332. + var documentState = DocumentState.Create(Workspace.Services, HostDocument, () => Task.FromResult(textAndVersion));
  333. + Document = new DefaultDocumentSnapshot(project, documentState);
  334. +
  335. + }
  336. +
  337. + private Workspace Workspace { get; }
  338. +
  339. + private SourceText SourceText { get; }
  340. +
  341. + private VersionStamp Version { get; }
  342. +
  343. + private HostDocument HostDocument { get; }
  344. +
  345. + private DefaultDocumentSnapshot Document { get; }
  346. +
  347. + [Fact]
  348. + public async Task GetGeneratedOutputAsync_SetsHostDocumentOutput()
  349. + {
  350. + // Act
  351. + await Document.GetGeneratedOutputAsync();
  352. +
  353. + // Assert
  354. + Assert.NotNull(HostDocument.GeneratedCodeContainer.Output);
  355. + Assert.Same(SourceText, HostDocument.GeneratedCodeContainer.Source);
  356. + }
  357. +
  358. + [Fact]
  359. + public async Task GetGeneratedOutputAsync_OnlySetsOutputIfDocumentNewer()
  360. + {
  361. + // Arrange
  362. + var newSourceText = SourceText.From("NEW!");
  363. + var newDocumentState = Document.State.WithText(newSourceText, Version.GetNewerVersion());
  364. + var newDocument = new DefaultDocumentSnapshot(Document.Project, newDocumentState);
  365. +
  366. + // Force the output to be the new output
  367. + await newDocument.GetGeneratedOutputAsync();
  368. +
  369. + // Act
  370. + await Document.GetGeneratedOutputAsync();
  371. +
  372. + // Assert
  373. + Assert.NotNull(HostDocument.GeneratedCodeContainer.Output);
  374. + Assert.Same(newSourceText, HostDocument.GeneratedCodeContainer.Source);
  375. + }
  376. + }
  377. +}
  378. diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs
  379. index 0f2c03c02ea..ede9b52d30f 100644
  380. --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs
  381. +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/GeneratedCodeContainerTest.cs
  382. @@ -20,10 +20,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  383. ";
  384. var sourceText = SourceText.From(content);
  385. var codeDocument = GetCodeDocument(content);
  386. - var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode;
  387. -
  388. - var container = new GeneratedCodeContainer();
  389. - container.SetOutput(sourceText, codeDocument);
  390. + var csharpDocument = codeDocument.GetCSharpDocument();
  391. + var generatedCode = csharpDocument.GeneratedCode;
  392. // TODO: Make writing these tests a little less manual.
  393. // Position of `SomeProperty` in the generated code.
  394. @@ -34,7 +32,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  395. var expectedLineSpan = new LinePositionSpan(new LinePosition(2, 22), new LinePosition(2, 34));
  396. // Act
  397. - var result = container.TryGetLinePositionSpan(span, out var lineSpan);
  398. + var result = GeneratedCodeContainer.TryGetLinePositionSpan(span, sourceText, csharpDocument, out var lineSpan);
  399. // Assert
  400. Assert.True(result);
  401. @@ -52,17 +50,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
  402. ";
  403. var sourceText = SourceText.From(content);
  404. var codeDocument = GetCodeDocument(content);
  405. - var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode;
  406. -
  407. - var container = new GeneratedCodeContainer();
  408. - container.SetOutput(sourceText, codeDocument);
  409. + var csharpDocument = codeDocument.GetCSharpDocument();
  410. + var generatedCode = csharpDocument.GeneratedCode;
  411. // Position of `ExecuteAsync` in the generated code.
  412. var symbol = "ExecuteAsync";
  413. var span = new TextSpan(generatedCode.IndexOf(symbol), symbol.Length);
  414. // Act
  415. - var result = container.TryGetLinePositionSpan(span, out var lineSpan);
  416. + var result = GeneratedCodeContainer.TryGetLinePositionSpan(span, sourceText, csharpDocument, out var lineSpan);
  417. // Assert
  418. Assert.False(result);