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));
        }
示例#7
0
        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);
            }
示例#15
0
        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);
        }
示例#16
0
            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);
                    }