Added a taghelpers and imports overload to Process and ProcessDesignTime

We want to have a way to specify the taghelper descriptors and imports to use while
processing a specific document.
- Added an overload to Process and ProcessDesignTime to take in a list
TagHelperDescriptors and a list of imports
- Added the corresponding CreateCodeDocumentCore overload
- Added GetTagHelpers and SetTagHelpers extension methods for
CodeDocument
- Added the necessary plumbing to use the taghelpers from the
CodeDocument when available and fallback logic.
- Added DocumentImportsTracker and updated background code generation
logic to use the new overload
- Added/updated tests
This commit is contained in:
Ajay Bhargav Baaskaran 2018-05-11 11:18:09 -07:00
parent a20a88fa3f
commit f9d4fba39d
19 changed files with 971 additions and 76 deletions

View File

@ -68,10 +68,23 @@ namespace Microsoft.AspNetCore.Razor.Language
var importItems = importFeature.GetImports(projectItem);
var importSourceDocuments = GetImportSourceDocuments(importItems);
return CreateCodeDocumentCore(sourceDocument, importSourceDocuments, tagHelpers: null);
}
internal override RazorCodeDocument CreateCodeDocumentCore(RazorSourceDocument sourceDocument, IReadOnlyList<RazorSourceDocument> importSourceDocuments, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (sourceDocument == null)
{
throw new ArgumentNullException(nameof(sourceDocument));
}
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(ConfigureParserOptions);
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(ConfigureCodeGenerationOptions);
return RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
var codeDocument = RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
codeDocument.SetTagHelpers(tagHelpers);
return codeDocument;
}
protected override RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorProjectItem projectItem)
@ -87,10 +100,23 @@ namespace Microsoft.AspNetCore.Razor.Language
var importItems = importFeature.GetImports(projectItem);
var importSourceDocuments = GetImportSourceDocuments(importItems, suppressExceptions: true);
return CreateCodeDocumentDesignTimeCore(sourceDocument, importSourceDocuments, tagHelpers: null);
}
internal override RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorSourceDocument sourceDocument, IReadOnlyList<RazorSourceDocument> importSourceDocuments, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (sourceDocument == null)
{
throw new ArgumentNullException(nameof(sourceDocument));
}
var parserOptions = GetRequiredFeature<IRazorParserOptionsFactoryProjectFeature>().Create(ConfigureDesignTimeParserOptions);
var codeGenerationOptions = GetRequiredFeature<IRazorCodeGenerationOptionsFactoryProjectFeature>().Create(ConfigureDesignTimeCodeGenerationOptions);
return RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
var codeDocument = RazorCodeDocument.Create(sourceDocument, importSourceDocuments, parserOptions, codeGenerationOptions);
codeDocument.SetTagHelpers(tagHelpers);
return codeDocument;
}
protected override void ProcessCore(RazorCodeDocument codeDocument)

View File

@ -15,18 +15,23 @@ namespace Microsoft.AspNetCore.Razor.Language
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDocumentDependency(syntaxTree);
var feature = Engine.Features.OfType<ITagHelperFeature>().FirstOrDefault();
if (feature == null)
var descriptors = codeDocument.GetTagHelpers();
if (descriptors == null)
{
// No feature, nothing to do.
return;
var feature = Engine.Features.OfType<ITagHelperFeature>().FirstOrDefault();
if (feature == null)
{
// No feature, nothing to do.
return;
}
descriptors = feature.GetDescriptors();
}
// We need to find directives in all of the *imports* as well as in the main razor file
//
// The imports come logically before the main razor file and are in the order they
// should be processed.
var descriptors = feature.GetDescriptors();
var visitor = new DirectiveVisitor(descriptors);
var imports = codeDocument.GetImportSyntaxTrees();
if (imports != null)

View File

@ -29,6 +29,26 @@ namespace Microsoft.AspNetCore.Razor.Language
document.Items[typeof(TagHelperDocumentContext)] = context;
}
internal static IReadOnlyList<TagHelperDescriptor> GetTagHelpers(this RazorCodeDocument document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
return (document.Items[typeof(TagHelpersHolder)] as TagHelpersHolder)?.TagHelpers;
}
internal static void SetTagHelpers(this RazorCodeDocument document, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
document.Items[typeof(TagHelpersHolder)] = new TagHelpersHolder(tagHelpers);
}
public static RazorSyntaxTree GetSyntaxTree(this RazorCodeDocument document)
{
if (document == null)
@ -168,5 +188,15 @@ namespace Microsoft.AspNetCore.Razor.Language
public IReadOnlyList<RazorSyntaxTree> SyntaxTrees { get; }
}
private class TagHelpersHolder
{
public TagHelpersHolder(IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
TagHelpers = tagHelpers;
}
public IReadOnlyList<TagHelperDescriptor> TagHelpers { get; }
}
}
}

View File

@ -35,6 +35,18 @@ namespace Microsoft.AspNetCore.Razor.Language
return codeDocument;
}
internal virtual RazorCodeDocument Process(RazorSourceDocument source, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = CreateCodeDocumentCore(source, importSources, tagHelpers);
ProcessCore(codeDocument);
return codeDocument;
}
public virtual RazorCodeDocument ProcessDesignTime(RazorProjectItem projectItem)
{
if (projectItem == null)
@ -47,10 +59,32 @@ namespace Microsoft.AspNetCore.Razor.Language
return codeDocument;
}
internal virtual RazorCodeDocument ProcessDesignTime(RazorSourceDocument source, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var codeDocument = CreateCodeDocumentDesignTimeCore(source, importSources, tagHelpers);
ProcessCore(codeDocument);
return codeDocument;
}
protected abstract RazorCodeDocument CreateCodeDocumentCore(RazorProjectItem projectItem);
internal virtual RazorCodeDocument CreateCodeDocumentCore(RazorSourceDocument source, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
return RazorCodeDocument.Create(source, importSources);
}
protected abstract RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorProjectItem projectItem);
internal virtual RazorCodeDocument CreateCodeDocumentDesignTimeCore(RazorSourceDocument source, IReadOnlyList<RazorSourceDocument> importSources, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
return RazorCodeDocument.Create(source, importSources);
}
protected abstract void ProcessCore(RazorCodeDocument codeDocument);
internal static RazorProjectEngine CreateEmpty(Action<RazorProjectEngineBuilder> configure = null)

View File

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Text;
@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class DefaultDocumentSnapshot : DocumentSnapshot
{
public DefaultDocumentSnapshot(ProjectSnapshot project, DocumentState state)
public DefaultDocumentSnapshot(DefaultProjectSnapshot project, DocumentState state)
{
if (project == null)
{
@ -27,7 +27,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
State = state;
}
public ProjectSnapshot Project { get; }
public DefaultProjectSnapshot Project { get; }
public DocumentState State { get; }
@ -35,6 +35,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public override string TargetPath => State.HostDocument.TargetPath;
public override IReadOnlyList<DocumentSnapshot> GetImports()
{
return State.Imports.GetImports(Project, this);
}
public override Task<SourceText> GetTextAsync()
{
return State.GetTextAsync();

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Internal;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
@ -16,6 +18,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private Task<RazorCodeDocument> _task;
private IReadOnlyList<TagHelperDescriptor> _tagHelpers;
private IReadOnlyList<ImportItem> _imports;
public DocumentGeneratedOutputTracker(DocumentGeneratedOutputTracker older)
{
@ -62,13 +65,19 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private async Task<RazorCodeDocument> GetGeneratedOutputInitializationTaskCore(ProjectSnapshot project, DocumentSnapshot document)
{
var tagHelpers = await project.GetTagHelpersAsync().ConfigureAwait(false);
var imports = await GetImportsAsync(project, document);
if (_older != null && _older.IsResultAvailable)
{
var difference = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
difference.UnionWith(_older._tagHelpers);
difference.SymmetricExceptWith(tagHelpers);
var tagHelperDifference = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
tagHelperDifference.UnionWith(_older._tagHelpers);
tagHelperDifference.SymmetricExceptWith(tagHelpers);
if (difference.Count == 0)
var importDifference = new HashSet<ImportItem>();
importDifference.UnionWith(_older._imports);
importDifference.SymmetricExceptWith(imports);
if (tagHelperDifference.Count == 0 && importDifference.Count == 0)
{
// We can use the cached result.
var result = _older._task.Result;
@ -76,8 +85,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Drop reference so it can be GC'ed
_older = null;
// Cache the tag helpers so the next version can use them
// Cache the tag helpers and imports so the next version can use them
_tagHelpers = tagHelpers;
_imports = imports;
return result;
}
@ -86,13 +96,77 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Drop reference so it can be GC'ed
_older = null;
// Cache the tag helpers so the next version can use them
// Cache the tag helpers and imports so the next version can use them
_tagHelpers = tagHelpers;
_imports = imports;
var importSources = new List<RazorSourceDocument>();
foreach (var item in imports)
{
var sourceDocument = await GetRazorSourceDocumentAsync(item.Import);
importSources.Add(sourceDocument);
}
var documentSource = await GetRazorSourceDocumentAsync(document);
var projectEngine = project.GetProjectEngine();
var projectItem = projectEngine.FileSystem.GetItem(document.FilePath);
return projectItem == null ? null : projectEngine.ProcessDesignTime(projectItem);
return projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers);
}
private async Task<RazorSourceDocument> GetRazorSourceDocumentAsync(DocumentSnapshot document)
{
var sourceText = await document.GetTextAsync();
return sourceText.GetRazorSourceDocument(document.FilePath);
}
private async Task<IReadOnlyList<ImportItem>> GetImportsAsync(ProjectSnapshot project, DocumentSnapshot document)
{
var imports = new List<ImportItem>();
foreach (var snapshot in document.GetImports())
{
var versionStamp = await snapshot.GetTextVersionAsync();
imports.Add(new ImportItem(snapshot.FilePath, versionStamp, snapshot));
}
return imports;
}
private struct ImportItem : IEquatable<ImportItem>
{
public ImportItem(string filePath, VersionStamp versionStamp, DocumentSnapshot import)
{
FilePath = filePath;
VersionStamp = versionStamp;
Import = import;
}
public string FilePath { get; }
public VersionStamp VersionStamp { get; }
public DocumentSnapshot Import { get; }
public bool Equals(ImportItem other)
{
return
FilePathComparer.Instance.Equals(FilePath, other.FilePath) &&
VersionStamp == other.VersionStamp;
}
public override bool Equals(object obj)
{
return obj is ImportItem item ? Equals(item) : false;
}
public override int GetHashCode()
{
var hash = new HashCodeCombiner();
hash.Add(FilePath, FilePathComparer.Instance);
hash.Add(VersionStamp);
return hash;
}
}
}
}

View File

@ -0,0 +1,165 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class DocumentImportsTracker
{
private readonly object _lock;
private IReadOnlyList<DocumentSnapshot> _imports;
public DocumentImportsTracker()
{
_lock = new object();
}
public IReadOnlyList<DocumentSnapshot> GetImports(ProjectSnapshot project, DocumentSnapshot document)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
if (_imports == null)
{
lock (_lock)
{
if (_imports == null)
{
_imports = GetImportsCore(project, document);
}
}
}
return _imports;
}
private IReadOnlyList<DocumentSnapshot> GetImportsCore(ProjectSnapshot project, DocumentSnapshot document)
{
var projectEngine = project.GetProjectEngine();
var importFeature = projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault();
var projectItem = projectEngine.FileSystem.GetItem(document.FilePath);
var importItems = importFeature?.GetImports(projectItem).Where(i => i.Exists);
if (importItems == null)
{
return Array.Empty<DocumentSnapshot>();
}
var imports = new List<DocumentSnapshot>();
foreach (var item in importItems)
{
if (item.PhysicalPath == null)
{
// This is a default import.
var defaultImport = new DefaultImportDocumentSnapshot(project, item);
imports.Add(defaultImport);
}
else
{
var import = project.GetDocument(item.PhysicalPath);
if (import == null)
{
// We are not tracking this document in this project. So do nothing.
continue;
}
imports.Add(import);
}
}
return imports;
}
private class DefaultImportDocumentSnapshot : DocumentSnapshot
{
private ProjectSnapshot _project;
private RazorProjectItem _importItem;
private SourceText _sourceText;
private VersionStamp _version;
private DocumentGeneratedOutputTracker _generatedOutput;
public DefaultImportDocumentSnapshot(ProjectSnapshot project, RazorProjectItem item)
{
_project = project;
_importItem = item;
_version = VersionStamp.Default;
_generatedOutput = new DocumentGeneratedOutputTracker(null);
}
public override string FilePath => null;
public override string TargetPath => null;
public override Task<RazorCodeDocument> GetGeneratedOutputAsync()
{
return _generatedOutput.GetGeneratedOutputInitializationTask(_project, this);
}
public override IReadOnlyList<DocumentSnapshot> GetImports()
{
return Array.Empty<DocumentSnapshot>();
}
public async override Task<SourceText> GetTextAsync()
{
using (var stream = _importItem.Read())
using (var reader = new StreamReader(stream))
{
var content = await reader.ReadToEndAsync();
_sourceText = SourceText.From(content);
}
return _sourceText;
}
public override Task<VersionStamp> GetTextVersionAsync()
{
return Task.FromResult(_version);
}
public override bool TryGetText(out SourceText result)
{
if (_sourceText != null)
{
result = _sourceText;
return true;
}
result = null;
return false;
}
public override bool TryGetTextVersion(out VersionStamp result)
{
result = _version;
return true;
}
public override bool TryGetGeneratedOutput(out RazorCodeDocument result)
{
if (_generatedOutput.IsResultAvailable)
{
result = GetGeneratedOutputAsync().Result;
return true;
}
result = null;
return false;
}
}
}
}

View File

@ -1,7 +1,7 @@
// 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;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Text;
@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public abstract string TargetPath { get; }
public abstract IReadOnlyList<DocumentSnapshot> GetImports();
public abstract Task<SourceText> GetTextAsync();
public abstract Task<VersionStamp> GetTextVersionAsync();

View File

@ -24,6 +24,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private VersionStamp? _version;
private DocumentGeneratedOutputTracker _generatedOutput;
private DocumentImportsTracker _imports;
public static DocumentState Create(
HostWorkspaceServices services,
@ -44,7 +45,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return new DocumentState(services, hostDocument, null, null, loader);
}
private DocumentState(
// Internal for testing
internal DocumentState(
HostWorkspaceServices services,
HostDocument hostDocument,
SourceText text,
@ -82,6 +84,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
}
public DocumentImportsTracker Imports
{
get
{
if (_imports == null)
{
lock (_lock)
{
if (_imports == null)
{
_imports = new DocumentImportsTracker();
}
}
}
return _imports;
}
}
public async Task<SourceText> GetTextAsync()
{
if (TryGetText(out var text))
@ -148,7 +169,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return false;
}
public DocumentState WithConfigurationChange()
public virtual DocumentState WithConfigurationChange()
{
var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader);
@ -160,7 +181,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return state;
}
public DocumentState WithWorkspaceProjectChange()
public virtual DocumentState WithWorkspaceProjectChange()
{
var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader);
@ -175,7 +196,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return state;
}
public DocumentState WithText(SourceText sourceText, VersionStamp version)
public virtual DocumentState WithText(SourceText sourceText, VersionStamp version)
{
if (sourceText == null)
{
@ -185,8 +206,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return new DocumentState(Services, HostDocument, sourceText, version, null);
}
public DocumentState WithTextLoader(Func<Task<TextAndVersion>> loader)
public virtual DocumentState WithTextLoader(Func<Task<TextAndVersion>> loader)
{
if (loader == null)
{

View File

@ -83,7 +83,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_tagHelpers = older._tagHelpers?.ForkFor(this, difference);
}
public IReadOnlyDictionary<string, DocumentState> Documents { get; }
// Internal set for testing.
public IReadOnlyDictionary<string, DocumentState> Documents { get; internal set; }
public HostProject HostProject { get; }
@ -293,7 +294,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
var documents = new Dictionary<string, DocumentState>(FilePathComparer.Instance);
foreach (var kvp in Documents)
{
documents.Add(kvp.Key, kvp.Value.WithConfigurationChange());
documents.Add(kvp.Key, kvp.Value.WithWorkspaceProjectChange());
}
var state = new ProjectState(this, difference, HostProject, workspaceProject, documents);

View File

@ -0,0 +1,23 @@
// 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;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Text
{
internal static class SourceTextExtensions
{
public static RazorSourceDocument GetRazorSourceDocument(this SourceText sourceText, string fileName)
{
if (sourceText == null)
{
throw new ArgumentNullException(nameof(sourceText));
}
var content = sourceText.ToString();
return RazorSourceDocument.Create(content, fileName);
}
}
}

View File

@ -32,6 +32,16 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
_onClosed = Document_Closed;
}
// For testing purposes only.
internal EditorDocumentManagerListener(EditorDocumentManager documentManager, EventHandler onChangedOnDisk, EventHandler onChangedInEditor, EventHandler onOpened, EventHandler onClosed)
{
_documentManager = documentManager;
_onChangedOnDisk = onChangedOnDisk;
_onChangedInEditor = onChangedInEditor;
_onOpened = onOpened;
_onClosed = onClosed;
}
public override void Initialize(ProjectSnapshotManagerBase projectManager)
{
if (projectManager == null)
@ -45,17 +55,18 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
_projectManager.Changed += ProjectManager_Changed;
}
private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
// Internal for testing.
internal void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
{
switch (e.Kind)
{
case ProjectChangeKind.DocumentAdded:
{
var key = new DocumentKey(e.ProjectFilePath, e.DocumentFilePath);
var document = _documentManager.GetOrCreateDocument(key, _onChangedOnDisk, _onChangedOnDisk, _onOpened, _onClosed);
var document = _documentManager.GetOrCreateDocument(key, _onChangedOnDisk, _onChangedInEditor, _onOpened, _onClosed);
if (document.IsOpenInEditor)
{
Document_Opened(document, EventArgs.Empty);
_onOpened(document, EventArgs.Empty);
}
break;

View File

@ -1,6 +1,8 @@
// 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;
using System.Collections.Generic;
using System.IO;
using Moq;
using Xunit;
@ -91,5 +93,147 @@ namespace Microsoft.AspNetCore.Razor.Language
Assert.NotNull(csharpDocument);
Assert.Empty(csharpDocument.Diagnostics);
}
[Fact]
public void Process_WithImportsAndTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var importItem = new TestRazorProjectItem("_import.cshtml");
var expectedImports = new[] { RazorSourceDocument.ReadFrom(importItem) };
var expectedTagHelpers = new[]
{
TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build(),
TagHelperDescriptorBuilder.Create("Test2TagHelper", "TestAssembly").Build(),
};
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), expectedImports, expectedTagHelpers);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Same(expectedTagHelpers, tagHelpers);
Assert.Equal(expectedImports, codeDocument.Imports);
}
[Fact]
public void Process_WithNullTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), Array.Empty<RazorSourceDocument>(), tagHelpers: null);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void Process_SetsNullTagHelpersOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void Process_WithNullImports_SetsEmptyListOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), importSources: null, tagHelpers: null);
// Assert
Assert.Empty(codeDocument.Imports);
}
[Fact]
public void ProcessDesignTime_WithImportsAndTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var importItem = new TestRazorProjectItem("_import.cshtml");
var expectedImports = new[] { RazorSourceDocument.ReadFrom(importItem) };
var expectedTagHelpers = new[]
{
TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build(),
TagHelperDescriptorBuilder.Create("Test2TagHelper", "TestAssembly").Build(),
};
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(RazorSourceDocument.ReadFrom(projectItem), expectedImports, expectedTagHelpers);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Same(expectedTagHelpers, tagHelpers);
Assert.Equal(expectedImports, codeDocument.Imports);
}
[Fact]
public void ProcessDesignTime_WithNullTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(RazorSourceDocument.ReadFrom(projectItem), Array.Empty<RazorSourceDocument>(), tagHelpers: null);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void ProcessDesignTime_SetsNullTagHelpersOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(projectItem);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void ProcessDesignTime_WithNullImports_SetsEmptyListOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(RazorSourceDocument.ReadFrom(projectItem), importSources: null, tagHelpers: null);
// Assert
Assert.Empty(codeDocument.Imports);
}
}
}

View File

@ -171,6 +171,129 @@ namespace Microsoft.AspNetCore.Razor.Language
Assert.Equal("input", inputTagHelper.TagName);
}
[Fact]
public void Execute_WithTagHelperDescriptorsFromCodeDocument_RewritesTagHelpers()
{
// Arrange
var projectEngine = RazorProjectEngine.Create();
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "form",
typeName: "TestFormTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestInputTagHelper",
assemblyName: "TestAssembly"),
};
var phase = new DefaultRazorTagHelperBinderPhase()
{
Engine = projectEngine.Engine,
};
var sourceDocument = CreateTestSourceDocument();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
codeDocument.SetSyntaxTree(originalTree);
codeDocument.SetTagHelpers(tagHelpers);
// Act
phase.Execute(codeDocument);
// Assert
var rewrittenTree = codeDocument.GetSyntaxTree();
Assert.Empty(rewrittenTree.Diagnostics);
Assert.Equal(3, rewrittenTree.Root.Children.Count);
var formTagHelper = Assert.IsType<TagHelperBlock>(rewrittenTree.Root.Children[2]);
Assert.Equal("form", formTagHelper.TagName);
Assert.Equal(3, formTagHelper.Children.Count);
var inputTagHelper = Assert.IsType<TagHelperBlock>(formTagHelper.Children[1]);
Assert.Equal("input", inputTagHelper.TagName);
}
[Fact]
public void Execute_NullTagHelperDescriptorsFromCodeDocument_FallsBackToTagHelperFeature()
{
// Arrange
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "form",
typeName: "TestFormTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestInputTagHelper",
assemblyName: "TestAssembly"),
};
var projectEngine = RazorProjectEngine.Create(builder => builder.AddTagHelpers(tagHelpers));
var phase = new DefaultRazorTagHelperBinderPhase()
{
Engine = projectEngine.Engine,
};
var sourceDocument = CreateTestSourceDocument();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
codeDocument.SetSyntaxTree(originalTree);
codeDocument.SetTagHelpers(tagHelpers: null);
// Act
phase.Execute(codeDocument);
// Assert
var rewrittenTree = codeDocument.GetSyntaxTree();
Assert.Empty(rewrittenTree.Diagnostics);
Assert.Equal(3, rewrittenTree.Root.Children.Count);
var formTagHelper = Assert.IsType<TagHelperBlock>(rewrittenTree.Root.Children[2]);
Assert.Equal("form", formTagHelper.TagName);
Assert.Equal(3, formTagHelper.Children.Count);
var inputTagHelper = Assert.IsType<TagHelperBlock>(formTagHelper.Children[1]);
Assert.Equal("input", inputTagHelper.TagName);
}
[Fact]
public void Execute_EmptyTagHelperDescriptorsFromCodeDocument_DoesNotFallbackToTagHelperFeature()
{
// Arrange
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "form",
typeName: "TestFormTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestInputTagHelper",
assemblyName: "TestAssembly"),
};
var projectEngine = RazorProjectEngine.Create(builder => builder.AddTagHelpers(tagHelpers));
var phase = new DefaultRazorTagHelperBinderPhase()
{
Engine = projectEngine.Engine,
};
var sourceDocument = CreateTestSourceDocument();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
codeDocument.SetSyntaxTree(originalTree);
codeDocument.SetTagHelpers(tagHelpers: Array.Empty<TagHelperDescriptor>());
// Act
phase.Execute(codeDocument);
// Assert
var rewrittenTree = codeDocument.GetSyntaxTree();
Assert.Empty(rewrittenTree.Diagnostics);
Assert.Equal(7, rewrittenTree.Root.Children.Count);
var rewrittenNodes = rewrittenTree.Root.Children.OfType<TagHelperBlock>();
Assert.Empty(rewrittenNodes);
}
[Fact]
public void Execute_DirectiveWithoutQuotes_RewritesTagHelpers_TagHelperMatchesElementTwice()
{
@ -278,35 +401,58 @@ namespace Microsoft.AspNetCore.Razor.Language
}
[Fact]
public void Execute_NoopsWhenNoTagHelperFeature()
public void Execute_TagHelpersFromCodeDocumentAndFeature_PrefersCodeDocument()
{
// Arrange
var projectEngine = RazorProjectEngine.Create();
var featureTagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestInputTagHelper",
assemblyName: "TestAssembly"),
};
var projectEngine = RazorProjectEngine.Create(builder => builder.AddTagHelpers(featureTagHelpers));
var phase = new DefaultRazorTagHelperBinderPhase()
{
Engine = projectEngine.Engine,
};
var sourceDocument = CreateTestSourceDocument();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
codeDocument.SetSyntaxTree(originalTree);
var codeDocumentTagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "form",
typeName: "TestFormTagHelper",
assemblyName: "TestAssembly"),
};
codeDocument.SetTagHelpers(codeDocumentTagHelpers);
// Act
phase.Execute(codeDocument);
// Assert
var outputTree = codeDocument.GetSyntaxTree();
Assert.Empty(outputTree.Diagnostics);
Assert.Same(originalTree, outputTree);
var rewrittenTree = codeDocument.GetSyntaxTree();
Assert.Empty(rewrittenTree.Diagnostics);
Assert.Equal(3, rewrittenTree.Root.Children.Count);
var formTagHelper = Assert.IsType<TagHelperBlock>(rewrittenTree.Root.Children[2]);
Assert.Equal("form", formTagHelper.TagName);
Assert.Collection(
formTagHelper.Children,
node => Assert.IsNotType<TagHelperBlock>(node),
node => Assert.IsNotType<TagHelperBlock>(node),
node => Assert.IsNotType<TagHelperBlock>(node));
}
[Fact]
public void Execute_NoopsWhenNoFeature()
public void Execute_NoopsWhenNoTagHelpersFromCodeDocumentOrFeature()
{
// Arrange
var projectEngine = RazorProjectEngine.Create(builder =>
{
});
var projectEngine = RazorProjectEngine.Create();
var phase = new DefaultRazorTagHelperBinderPhase()
{
Engine = projectEngine.Engine,

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Xunit;
@ -937,7 +938,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
var projectEngine = CreateProjectEngine(builder =>
{
builder.ConfigureDocumentClassifier();
builder.AddTagHelpers(descriptors);
// Some of these tests use templates
builder.AddTargetExtension(new TemplateTargetExtension());
@ -948,9 +948,10 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
});
var projectItem = CreateProjectItem();
var imports = GetImports(projectEngine, projectItem);
// Act
var codeDocument = projectEngine.Process(projectItem);
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), imports, descriptors.ToList());
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
@ -963,7 +964,6 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
var projectEngine = CreateProjectEngine(builder =>
{
builder.ConfigureDocumentClassifier();
builder.AddTagHelpers(descriptors);
// Some of these tests use templates
builder.AddTargetExtension(new TemplateTargetExtension());
@ -974,14 +974,24 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
});
var projectItem = CreateProjectItem();
var imports = GetImports(projectEngine, projectItem);
// Act
var codeDocument = projectEngine.ProcessDesignTime(projectItem);
var codeDocument = projectEngine.ProcessDesignTime(RazorSourceDocument.ReadFrom(projectItem), imports, descriptors.ToList());
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
AssertCSharpDocumentMatchesBaseline(codeDocument.GetCSharpDocument());
AssertSourceMappingsMatchBaseline(codeDocument);
}
private static IReadOnlyList<RazorSourceDocument> GetImports(RazorProjectEngine projectEngine, RazorProjectItem projectItem)
{
var importFeature = projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault();
var importItems = importFeature.GetImports(projectItem);
var importSourceDocuments = importItems.Where(i => i.Exists).Select(i => RazorSourceDocument.ReadFrom(i)).ToList();
return importSourceDocuments;
}
}
}

View File

@ -56,6 +56,22 @@ namespace Microsoft.AspNetCore.Razor.Language
Assert.Same(expected, actual);
}
[Fact]
public void GetAndSetTagHelpers_ReturnsTagHelpers()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var expected = new[] { TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build() };
codeDocument.SetTagHelpers(expected);
// Act
var actual = codeDocument.GetTagHelpers();
// Assert
Assert.Same(expected, actual);
}
[Fact]
public void GetIRDocument_ReturnsIRDocument()
{

View File

@ -4,12 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
using Moq;
using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
@ -373,7 +371,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.NotSame(original.TagHelpers, state.TagHelpers);
Assert.NotSame(original.Documents[Documents[1].FilePath], state.Documents[Documents[1].FilePath]);
Assert.NotSame(original.Documents[Documents[1].FilePath], state.Documents[Documents[1].FilePath]);
Assert.NotSame(original.Documents[Documents[2].FilePath], state.Documents[Documents[2].FilePath]);
}
[Fact]
@ -395,6 +393,30 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.Same(original, state);
}
[Fact]
public void ProjectState_WithHostProject_CallsConfigurationChangeOnDocumentState()
{
// Arrange
var callCount = 0;
var documents = new Dictionary<string, DocumentState>();
documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onConfigurationChange: () => callCount++);
documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onConfigurationChange: () => callCount++);
var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject);
original.Documents = documents;
var changed = WorkspaceProject.WithAssemblyName("Test1");
// Act
var state = original.WithHostProject(HostProjectWithConfigurationChange);
// Assert
Assert.NotEqual(original.Version, state.Version);
Assert.Same(HostProjectWithConfigurationChange, state.HostProject);
Assert.Equal(2, callCount);
}
[Fact]
public void ProjectState_WithWorkspaceProject_Removed()
{
@ -418,7 +440,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.NotSame(original.TagHelpers, state.TagHelpers);
Assert.NotSame(original.Documents[Documents[1].FilePath], state.Documents[Documents[1].FilePath]);
Assert.NotSame(original.Documents[Documents[1].FilePath], state.Documents[Documents[1].FilePath]);
Assert.NotSame(original.Documents[Documents[2].FilePath], state.Documents[Documents[2].FilePath]);
}
[Fact]
@ -472,7 +494,92 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.NotSame(original.TagHelpers, state.TagHelpers);
Assert.NotSame(original.Documents[Documents[1].FilePath], state.Documents[Documents[1].FilePath]);
Assert.NotSame(original.Documents[Documents[1].FilePath], state.Documents[Documents[1].FilePath]);
Assert.NotSame(original.Documents[Documents[2].FilePath], state.Documents[Documents[2].FilePath]);
}
[Fact]
public void ProjectState_WithWorkspaceProject_CallsWorkspaceProjectChangeOnDocumentState()
{
// Arrange
var callCount = 0;
var documents = new Dictionary<string, DocumentState>();
documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onWorkspaceProjectChange: () => callCount++);
documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onWorkspaceProjectChange: () => callCount++);
var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject);
original.Documents = documents;
var changed = WorkspaceProject.WithAssemblyName("Test1");
// Act
var state = original.WithWorkspaceProject(changed);
// Assert
Assert.NotEqual(original.Version, state.Version);
Assert.Equal(2, callCount);
}
private class TestDocumentState : DocumentState
{
public static TestDocumentState Create(
HostWorkspaceServices services,
HostDocument hostDocument,
Func<Task<TextAndVersion>> loader = null,
Action onTextChange = null,
Action onTextLoaderChange = null,
Action onConfigurationChange = null,
Action onWorkspaceProjectChange = null)
{
return new TestDocumentState(services, hostDocument, null, null, loader, onTextChange, onTextLoaderChange, onConfigurationChange, onWorkspaceProjectChange);
}
Action _onTextChange;
Action _onTextLoaderChange;
Action _onConfigurationChange;
Action _onWorkspaceProjectChange;
private TestDocumentState(
HostWorkspaceServices services,
HostDocument hostDocument,
SourceText text,
VersionStamp? version,
Func<Task<TextAndVersion>> loader,
Action onTextChange,
Action onTextLoaderChange,
Action onConfigurationChange,
Action onWorkspaceProjectChange)
: base(services, hostDocument, text, version, loader)
{
_onTextChange = onTextChange;
_onTextLoaderChange = onTextLoaderChange;
_onConfigurationChange = onConfigurationChange;
_onWorkspaceProjectChange = onWorkspaceProjectChange;
}
public override DocumentState WithText(SourceText sourceText, VersionStamp version)
{
_onTextChange?.Invoke();
return base.WithText(sourceText, version);
}
public override DocumentState WithTextLoader(Func<Task<TextAndVersion>> loader)
{
_onTextLoaderChange?.Invoke();
return base.WithTextLoader(loader);
}
public override DocumentState WithConfigurationChange()
{
_onConfigurationChange?.Invoke();
return base.WithConfigurationChange();
}
public override DocumentState WithWorkspaceProjectChange()
{
_onWorkspaceProjectChange?.Invoke();
return base.WithWorkspaceProjectChange();
}
}
}
}

View File

@ -0,0 +1,103 @@
// 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;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Test;
using Microsoft.VisualStudio.Text;
using Moq;
using Xunit;
namespace Microsoft.VisualStudio.Editor.Razor.Documents
{
public class EditorDocumentManagerListenerTest
{
public EditorDocumentManagerListenerTest()
{
ProjectFilePath = "C:\\project1\\project.csproj";
DocumentFilePath = "c:\\project1\\file1.cshtml";
TextLoader = TextLoader.From(TextAndVersion.Create(SourceText.From("FILE"), VersionStamp.Default));
FileChangeTracker = new DefaultFileChangeTracker(DocumentFilePath);
TextBuffer = new TestTextBuffer(new StringTextSnapshot("Hello"));
}
private string ProjectFilePath { get; }
private string DocumentFilePath { get; }
private TextLoader TextLoader { get; }
private FileChangeTracker FileChangeTracker { get; }
private TestTextBuffer TextBuffer { get; }
[Fact]
public void ProjectManager_Changed_DocumentAdded_InvokesGetOrCreateDocument()
{
// Arrange
var changedOnDisk = new EventHandler((o, args) => { });
var changedInEditor = new EventHandler((o, args) => { });
var opened = new EventHandler((o, args) => { });
var closed = new EventHandler((o, args) => { });
var editorDocumentManger = new Mock<EditorDocumentManager>(MockBehavior.Strict);
editorDocumentManger
.Setup(e => e.GetOrCreateDocument(It.IsAny<DocumentKey>(), It.IsAny<EventHandler>(), It.IsAny<EventHandler>(), It.IsAny<EventHandler>(), It.IsAny<EventHandler>()))
.Returns(GetEditorDocument())
.Callback<DocumentKey, EventHandler, EventHandler, EventHandler, EventHandler>((key, onChangedOnDisk, onChangedInEditor, onOpened, onClosed) =>
{
Assert.Same(changedOnDisk, onChangedOnDisk);
Assert.Same(changedInEditor, onChangedInEditor);
Assert.Same(opened, onOpened);
Assert.Same(closed, onClosed);
});
var listener = new EditorDocumentManagerListener(editorDocumentManger.Object, changedOnDisk, changedInEditor, opened, closed);
// Act & Assert
listener.ProjectManager_Changed(null, new ProjectChangeEventArgs("/Path/to/project.csproj", ProjectChangeKind.DocumentAdded));
}
[Fact]
public void ProjectManager_Changed_OpenDocumentAdded_InvokesOnOpened()
{
// Arrange
var called = false;
var opened = new EventHandler((o, args) => { called = true; });
var editorDocumentManger = new Mock<EditorDocumentManager>(MockBehavior.Strict);
editorDocumentManger
.Setup(e => e.GetOrCreateDocument(It.IsAny<DocumentKey>(), It.IsAny<EventHandler>(), It.IsAny<EventHandler>(), It.IsAny<EventHandler>(), It.IsAny<EventHandler>()))
.Returns(GetEditorDocument(isOpen: true));
var listener = new EditorDocumentManagerListener(editorDocumentManger.Object, onChangedOnDisk: null, onChangedInEditor: null, onOpened: opened, onClosed: null);
// Act
listener.ProjectManager_Changed(null, new ProjectChangeEventArgs("/Path/to/project.csproj", ProjectChangeKind.DocumentAdded));
// Assert
Assert.True(called);
}
private EditorDocument GetEditorDocument(bool isOpen = false)
{
var document = new EditorDocument(
Mock.Of<EditorDocumentManager>(),
ProjectFilePath,
DocumentFilePath,
TextLoader,
FileChangeTracker,
isOpen ? TextBuffer : null,
changedOnDisk: null,
changedInEditor: null,
opened: null,
closed: null);
return document;
}
}
}

View File

@ -81,32 +81,5 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
Assert.Null(document.EditorTextContainer);
}
}
private class TestSourceTextContainer : SourceTextContainer
{
public override event EventHandler<TextChangeEventArgs> TextChanged;
private SourceText _currentText;
public TestSourceTextContainer()
: this(SourceText.From(string.Empty))
{
}
public TestSourceTextContainer(SourceText text)
{
_currentText = text;
}
public override SourceText CurrentText => _currentText;
public void PushChange(SourceText text)
{
var args = new TextChangeEventArgs(_currentText, text);
_currentText = text;
TextChanged?.Invoke(this, args);
}
}
}
}