public void ProjectSnapshot_CachesTagHelperTask()
        {
            // Arrange
            TagHelperResolver.CompletionSource = new TaskCompletionSource <TagHelperResolutionResult>();

            try
            {
                var state    = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject);
                var snapshot = new DefaultProjectSnapshot(state);

                // Act
                var task1 = snapshot.GetTagHelpersAsync();
                var task2 = snapshot.GetTagHelpersAsync();

                // Assert
                Assert.Same(task1, task2);
            }
            finally
            {
                TagHelperResolver.CompletionSource.SetCanceled();
            }
        }
Example #2
0
            private async Task <(RazorCodeDocument, 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 computedStateVersion = await project.State.GetComputedStateVersionAsync(project).ConfigureAwait(false);

                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(computedStateVersion) == computedStateVersion)
                {
                    inputVersion = computedStateVersion;
                }

                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 olderOutputVersion        = default(VersionStamp);

                if (_older?.TaskUnsafe != null)
                {
                    (olderOutput, olderInputVersion, olderOutputVersion) = await _older.TaskUnsafe.ConfigureAwait(false);

                    if (inputVersion.GetNewerVersion(olderInputVersion) == olderInputVersion)
                    {
                        // Nothing has changed, we can use the cached result.
                        lock (_lock)
                        {
                            TaskUnsafe = _older.TaskUnsafe;
                            _older     = null;
                            return(olderOutput, olderInputVersion, olderOutputVersion);
                        }
                    }
                }

                // OK we have to generate the code.
                var tagHelpers = await project.GetTagHelpersAsync().ConfigureAwait(false);

                var importSources = new List <RazorSourceDocument>();

                foreach (var item in imports)
                {
                    var sourceDocument = await GetRazorSourceDocumentAsync(item.Document).ConfigureAwait(false);

                    importSources.Add(sourceDocument);
                }

                var documentSource = await GetRazorSourceDocumentAsync(document).ConfigureAwait(false);

                var projectEngine = project.GetProjectEngine();

                var codeDocument   = projectEngine.ProcessDesignTime(documentSource, importSources, tagHelpers);
                var csharpDocument = codeDocument.GetCSharpDocument();

                // 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# code here (not the other artifacts).
                // This is the reason why we have two versions associated with the output.
                //
                // The INPUT version is related the .cshtml files and tag helpers
                // The OUTPUT version is related to the generated C#.
                //
                // Examples:
                //
                // A change to a tag helper not used by this document - updates the INPUT version, but not
                // the OUTPUT version.
                //
                // A change in the HTML - 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 outputVersion = inputVersion;

                if (olderOutput != null)
                {
                    if (string.Equals(
                            olderOutput.GetCSharpDocument().GeneratedCode,
                            csharpDocument.GeneratedCode,
                            StringComparison.Ordinal))
                    {
                        outputVersion = olderOutputVersion;
                    }
                }

                if (document is DefaultDocumentSnapshot defaultDocument)
                {
                    defaultDocument.State.HostDocument.GeneratedCodeContainer.SetOutput(
                        defaultDocument,
                        csharpDocument,
                        inputVersion,
                        outputVersion);
                }

                return(codeDocument, inputVersion, outputVersion);
            }