public bool TrySetOutput( DefaultDocumentSnapshot document, RazorCodeDocument codeDocument, VersionStamp inputVersion, VersionStamp outputCSharpVersion, VersionStamp outputHtmlVersion) { lock (_setOutputLock) { if (_inputVersion.HasValue && _inputVersion.Value != inputVersion && _inputVersion == _inputVersion.Value.GetNewerVersion(inputVersion)) { // Latest document is newer than the provided document. return(false); } if (!document.TryGetText(out var source)) { Debug.Fail("The text should have already been evaluated."); return(false); } _source = source; _inputVersion = inputVersion; _outputCSharpVersion = outputCSharpVersion; _outputHtmlVersion = outputHtmlVersion; _outputCSharp = codeDocument.GetCSharpDocument(); _outputHtml = codeDocument.GetHtmlDocument(); _latestDocument = document; var csharpSourceText = codeDocument.GetCSharpSourceText(); _csharpTextContainer.SetText(csharpSourceText); var htmlSourceText = codeDocument.GetHtmlSourceText(); _htmlTextContainer.SetText(htmlSourceText); return(true); } }
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 tagHelperDifference = new HashSet <TagHelperDescriptor>(TagHelperDescriptorComparer.Default); tagHelperDifference.UnionWith(_older._tagHelpers); tagHelperDifference.SymmetricExceptWith(tagHelpers); 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; // Drop reference so it can be GC'ed _older = null; // Cache the tag helpers and imports so the next version can use them _tagHelpers = tagHelpers; _imports = imports; return(result); } } // Drop reference so it can be GC'ed _older = null; // 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(); return(projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers)); }
public Task <RazorCodeDocument> GetGeneratedOutputInitializationTask(ProjectSnapshot project, DocumentSnapshot document) { if (project == null) { throw new ArgumentNullException(nameof(project)); } if (document == null) { throw new ArgumentNullException(nameof(document)); } if (_task == null) { lock (_lock) { if (_task == null) { _task = GetGeneratedOutputInitializationTaskCore(project, document); } } } return(_task); }
public ImportItem(string filePath, VersionStamp versionStamp, DocumentSnapshot import) { FilePath = filePath; VersionStamp = versionStamp; Import = import; }
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 async Task <RazorSourceDocument> GetRazorSourceDocumentAsync(DocumentSnapshot document) { var sourceText = await document.GetTextAsync(); return(sourceText.GetRazorSourceDocument(document.FilePath)); }
private async Task <RazorCodeDocument> GetGeneratedOutputInitializationTaskCore(ProjectSnapshot project, DocumentSnapshot document) { var tagHelpers = await project.GetTagHelpersAsync().ConfigureAwait(false); if (_older != null && _older.IsResultAvailable) { var difference = new HashSet <TagHelperDescriptor>(TagHelperDescriptorComparer.Default); difference.UnionWith(_older._tagHelpers); difference.SymmetricExceptWith(tagHelpers); if (difference.Count == 0) { // We can use the cached result. var result = _older._task.Result; // Drop reference so it can be GC'ed _older = null; // Cache the tag helpers so the next version can use them _tagHelpers = tagHelpers; return(result); } } // Drop reference so it can be GC'ed _older = null; // Cache the tag helpers so the next version can use them _tagHelpers = tagHelpers; var projectEngine = project.GetProjectEngine(); var projectItem = projectEngine.FileSystem.GetItem(document.FilePath); return(projectItem == null ? null : projectEngine.ProcessDesignTime(projectItem)); }
public ImportItem(string filePath, VersionStamp version, DocumentSnapshot document) { FilePath = filePath; Version = version; Document = document; }
private async Task <RazorSourceDocument> GetRazorSourceDocumentAsync(DocumentSnapshot document, RazorProjectItem projectItem) { var sourceText = await document.GetTextAsync(); return(sourceText.GetRazorSourceDocument(document.FilePath, projectItem?.RelativePhysicalPath)); }
private async Task <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)> GetGeneratedOutputAndVersionCoreAsync(DefaultProjectSnapshot project, DocumentSnapshot document) { // We only need to produce the generated code if any of our inputs is newer than the // previously cached output. // // First find the versions that are the inputs: // - The project + computed state // - The imports // - This document // // All of these things are cached, so no work is wasted if we do need to generate the code. var configurationVersion = project.State.ConfigurationVersion; var projectWorkspaceStateVersion = project.State.ProjectWorkspaceStateVersion; var documentCollectionVersion = project.State.DocumentCollectionVersion; var imports = await GetImportsAsync(project, document).ConfigureAwait(false); var documentVersion = await document.GetTextVersionAsync().ConfigureAwait(false); // OK now that have the previous output and all of the versions, we can see if anything // has changed that would require regenerating the code. var inputVersion = documentVersion; if (inputVersion.GetNewerVersion(configurationVersion) == configurationVersion) { inputVersion = configurationVersion; } if (inputVersion.GetNewerVersion(projectWorkspaceStateVersion) == projectWorkspaceStateVersion) { inputVersion = projectWorkspaceStateVersion; } if (inputVersion.GetNewerVersion(documentCollectionVersion) == documentCollectionVersion) { inputVersion = documentCollectionVersion; } for (var i = 0; i < imports.Count; i++) { var importVersion = imports[i].Version; if (inputVersion.GetNewerVersion(importVersion) == importVersion) { inputVersion = importVersion; } } RazorCodeDocument olderOutput = null; var olderInputVersion = default(VersionStamp); var olderCSharpOutputVersion = default(VersionStamp); var olderHtmlOutputVersion = default(VersionStamp); if (_older?.TaskUnsafeReference != null && _older.TaskUnsafeReference.TryGetTarget(out var taskUnsafe)) { (olderOutput, olderInputVersion, olderCSharpOutputVersion, olderHtmlOutputVersion) = await taskUnsafe.ConfigureAwait(false); if (inputVersion.GetNewerVersion(olderInputVersion) == olderInputVersion) { // Nothing has changed, we can use the cached result. lock (_lock) { TaskUnsafeReference = _older.TaskUnsafeReference; _older = null; return(olderOutput, olderInputVersion, olderCSharpOutputVersion, olderHtmlOutputVersion); } } } // OK we have to generate the code. var importSources = new List <RazorSourceDocument>(); var projectEngine = project.GetProjectEngine(); foreach (var item in imports) { var importProjectItem = item.FilePath == null ? null : projectEngine.FileSystem.GetItem(item.FilePath, item.FileKind); var sourceDocument = await GetRazorSourceDocumentAsync(item.Document, importProjectItem).ConfigureAwait(false); importSources.Add(sourceDocument); } var projectItem = document.FilePath == null ? null : projectEngine.FileSystem.GetItem(document.FilePath, document.FileKind); var documentSource = await GetRazorSourceDocumentAsync(document, projectItem).ConfigureAwait(false); var codeDocument = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); var csharpDocument = codeDocument.GetCSharpDocument(); var htmlDocument = codeDocument.GetHtmlDocument(); // OK now we've generated the code. Let's check if the output is actually different. This is // a valuable optimization for our use cases because lots of changes you could make require // us to run code generation, but don't change the result. // // Note that we're talking about the effect on the generated C#/HTML here (not the other artifacts). // This is the reason why we have three versions associated with the document. // // The INPUT version is related the .cshtml files and tag helpers // The CSHARPOUTPUT version is related to the generated C# // The HTMLOUTPUT version is related to the generated HTML // // Examples: // // A change to a tag helper not used by this document - updates the INPUT version, but not // the OUTPUT version. // // // Razor IDE features should always retrieve the output and party on it regardless. Depending // on the use cases we may or may not need to synchronize the output. var outputCSharpVersion = inputVersion; var outputHtmlVersion = inputVersion; if (olderOutput != null) { if (string.Equals( olderOutput.GetCSharpDocument().GeneratedCode, csharpDocument.GeneratedCode, StringComparison.Ordinal)) { outputCSharpVersion = olderCSharpOutputVersion; } if (string.Equals( olderOutput.GetHtmlDocument().GeneratedHtml, htmlDocument.GeneratedHtml, StringComparison.Ordinal)) { outputHtmlVersion = olderHtmlOutputVersion; } } if (document is DefaultDocumentSnapshot defaultDocument) { defaultDocument.State.HostDocument.GeneratedDocumentContainer.SetOutput( defaultDocument, csharpDocument, htmlDocument, inputVersion, outputCSharpVersion, outputHtmlVersion); } return(codeDocument, inputVersion, outputCSharpVersion, outputHtmlVersion); }
public Task <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)> GetGeneratedOutputAndVersionAsync(DefaultProjectSnapshot project, DocumentSnapshot document) { if (project == null) { throw new ArgumentNullException(nameof(project)); } if (document == null) { throw new ArgumentNullException(nameof(document)); } if (TaskUnsafeReference == null || !TaskUnsafeReference.TryGetTarget(out var taskUnsafe)) { lock (_lock) { if (TaskUnsafeReference == null || !TaskUnsafeReference.TryGetTarget(out taskUnsafe)) { taskUnsafe = GetGeneratedOutputAndVersionCoreAsync(project, document); TaskUnsafeReference = new WeakReference <Task <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)> >(taskUnsafe); } } } return(taskUnsafe); }
/// <summary> /// If the provided document is an import document, gets the other documents in the project /// that include directives specified by the provided document. Otherwise returns an empty /// list. /// </summary> /// <param name="document">The document.</param> /// <returns>A list of related documents.</returns> public abstract IEnumerable <DocumentSnapshot> GetRelatedDocuments(DocumentSnapshot document);
public abstract bool IsImportDocument(DocumentSnapshot document);
public Task <(RazorCodeDocument, VersionStamp, VersionStamp)> GetGeneratedOutputAndVersionAsync(DefaultProjectSnapshot project, DocumentSnapshot document) { if (project == null) { throw new ArgumentNullException(nameof(project)); } if (document == null) { throw new ArgumentNullException(nameof(document)); } if (TaskUnsafe == null) { lock (_lock) { if (TaskUnsafe == null) { TaskUnsafe = GetGeneratedOutputAndVersionCoreAsync(project, document); } } } return(TaskUnsafe); }
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); }
public Task <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)> GetGeneratedOutputAndVersionAsync(DefaultProjectSnapshot project, DocumentSnapshot document) { if (project is null) { throw new ArgumentNullException(nameof(project)); } if (document is null) { throw new ArgumentNullException(nameof(document)); } if (_taskUnsafeReference is null || !_taskUnsafeReference.TryGetTarget(out var taskUnsafe)) { TaskCompletionSource <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)> tcs = null; lock (_lock) { if (_taskUnsafeReference is null || !_taskUnsafeReference.TryGetTarget(out taskUnsafe)) { // So this is a bit confusing. Instead of directly calling the Razor parser inside of this lock we create an indirect TaskCompletionSource // to represent when it completes. The reason behind this is that there are several scenarios in which the Razor parser will run synchronously // (mostly all in VS) resulting in this lock being held for significantly longer than expected. To avoid threads queuing up repeatedly on the // above lock and blocking we can allow those threads to await asynchronously for the completion of the original parse. tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); taskUnsafe = tcs.Task; _taskUnsafeReference = new WeakReference <Task <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)> >(taskUnsafe); } } if (tcs is null) { // There's no task completion source created meaning a value was retrieved from cache, just return it. return(taskUnsafe); } // Typically in VS scenarios this will run synchronously because all resources are readily available. var outputTask = GetGeneratedOutputAndVersionCoreAsync(project, document); if (outputTask.IsCompleted) { // Compiling ran synchronously, lets just immediately propagate to the TCS PropagateToTaskCompletionSource(outputTask, tcs); } else { // Task didn't run synchronously (most likely outside of VS), lets allocate a bit more but utilize ContinueWith // to properly connect the output task and TCS _ = outputTask.ContinueWith( static (task, state) => { var tcs = (TaskCompletionSource <(RazorCodeDocument, VersionStamp, VersionStamp, VersionStamp)>)state; PropagateToTaskCompletionSource(task, tcs); }, tcs, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); }