private IReadOnlyList <DocumentSnapshot> GetImportsCore(DefaultProjectSnapshot project) { var projectEngine = project.GetProjectEngine(); var importFeatures = projectEngine.ProjectFeatures.OfType <IImportProjectFeature>(); var projectItem = projectEngine.FileSystem.GetItem(HostDocument.FilePath, HostDocument.FileKind); var importItems = importFeatures.SelectMany(f => f.GetImports(projectItem)); 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 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); }