diff --git a/docs/ide/test-plans/source-generators.md b/docs/ide/test-plans/source-generators.md new file mode 100644 index 0000000000000000000000000000000000000000..969370755a3b3d119f1c399993302ff10b36dc0a --- /dev/null +++ b/docs/ide/test-plans/source-generators.md @@ -0,0 +1,34 @@ +## Features Opening New Windows + +1. Locate a use of a generated symbol in the project, and invoke Go to Definition. + - [ ] It works. +2. Locate a use of a generated symbol in the project, and invoke Go to Implementation. + - [ ] It works. +3. Locate a use of a generated symbol in the project, and invoke Find References. + - [ ] References to the generated symbol in regular code are located. + - [ ] References to the generated symbol in generated code are located. + +## Features That Are Blocked + +1. Locate a use of a generated method in a project, and invoke Rename with Ctrl+R Ctrl+R. + - [ ] It should be blocked. +2. Locate a use of a generated method in a project, change the name at the use site. + - [ ] **[NOT WORKING YET]** Rename tracking should not trigger. + +## Navigating within a Generated Document + +1. Invoke Go to Definition on a generated symbol to open a source generated file. + - [ ] **[NOT WORKING YET]** The navigation bar on the top of the file shows the project that contains the generated source +2. Invoke Find References on the symbol we navigated to. + - [ ] **[NOT WORKING YET]** The original reference should appear in find references. +3. Click on some symbol used more than once in a generated file. + - [ ] Highlight references should highlight all uses in the file. + +## Window > New Window Support + +1. Invoke Go to Definition to open a source-generated file. Then do Window > New Window to ensure that the new window is set up correctly. + - [ ] It's read only. + - [ ] The title and caption are correct. +2. Remove the source generator from the project. + - [ ] Confirm the first window correctly gets an info bar showing the file is no longer valid. + - [ ] Confirm the second window correctly gets an info bar showing the file is no longer valid. diff --git a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs index a4e82f2b8da87664ffc8a375f2f348e77a82b45f..9928a48d8afeb8446932d0db1004002c4cdfdc2a 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs @@ -169,6 +169,17 @@ public static IReadOnlyCollection ToCollection(this IEnumerable sequenc return source.Cast().LastOrDefault(); } + public static T? SingleOrNull(this IEnumerable source, Func predicate) + where T : struct + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return source.Cast().SingleOrDefault(v => predicate(v!.Value)); + } + public static bool IsSingle(this IEnumerable list) { using var enumerator = list.GetEnumerator(); diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 3b6ca9de3d82401773f6045a0d60730a2cf98511..f8727cf83a28c0be583c8cc1a9cbffd3e63f0a2f 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.FindUsages @@ -146,6 +147,22 @@ internal static class DefinitionItemExtensions sourceLocations.Add(documentLocation); } + else + { + // Was this a source generated tree? If so, we don't have a document representaion (yet) so + // we'll create a metadata symbol which will later be handled by the symbol navigation service + // that way. Once we represent generated source trees as propery documents, we'll update the code above + // to correctly make this item. + var project = solution.GetOriginatingProject(definition); + var generatorRunResult = await project.GetGeneratorDriverRunResultAsync(cancellationToken).ConfigureAwait(false); + + if (generatorRunResult.TryGetGeneratorAndHint(location.SourceTree, out _, out _)) + { + return DefinitionItem.CreateMetadataDefinition( + tags, displayParts, nameDisplayParts, solution, + definition, properties, displayIfNoReferences); + } + } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..cfde05f4b6a91a051c3cb1671e2409dbdc5d056a --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -0,0 +1,423 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Threading; +using Roslyn.Utilities; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation +{ + /// + /// Provides the support for opening files pointing to source generated documents, and keeping the content updated accordingly. + /// + [Export(typeof(SourceGeneratedFileManager))] + internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventListener + { + private readonly IServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + private readonly IVsRunningDocumentTable _runningDocumentTable; + private readonly ITextDocumentFactoryService _textDocumentFactoryService; + + private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; + + /// + /// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use. + /// + private readonly string _temporaryDirectory; + + /// + /// Map of currently open generated files; the key is the generated full file path. + /// + private readonly Dictionary _openFiles = new Dictionary(); + private readonly VisualStudioWorkspace _visualStudioWorkspace; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public SourceGeneratedFileManager( + [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, + IThreadingContext threadingContext, + IVsEditorAdaptersFactoryService editorAdaptersFactoryService, + ITextDocumentFactoryService textDocumentFactoryService, + VisualStudioWorkspace visualStudioWorkspace) + { + _serviceProvider = serviceProvider; + _threadingContext = threadingContext; + _textDocumentFactoryService = textDocumentFactoryService; + _temporaryDirectory = Path.Combine(Path.GetTempPath(), "VisualStudioSourceGeneratedDocuments"); + _visualStudioWorkspace = visualStudioWorkspace; + + Directory.CreateDirectory(_temporaryDirectory); + + // The IVsRunningDocumentTable is a free-threaded VS service that allows fetching of the service and advising events + // to be done without implicitly marshalling to the UI thread. + _runningDocumentTable = _serviceProvider.GetService(); + _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker( + threadingContext, + editorAdaptersFactoryService, + _runningDocumentTable, + this); + } + + public void NavigateToSourceGeneratedFile(Project project, ISourceGenerator generator, string generatedSourceHintName, TextSpan sourceSpan) + { + // We will create an file name to represent this generated file; the Visual Studio shell APIs imply you can use a URI, + // but most URIs are blocked other than file:// and http://; they also get extra handling to attempt to download the file so + // those aren't really usable anyways. + + // We put all the files related to the same project in a single directory + var generatorType = generator.GetType(); + var projectDirectory = Path.Combine(_temporaryDirectory, project.Id.Id.ToString()); + Directory.CreateDirectory(projectDirectory); + + // The file name we generate here is chosen to match the compiler's choice, so the debugger can recognize the files should match. + // This can only be changed if the compiler changes the algorithm as well. + var temporaryFilePath = Path.Combine(projectDirectory, $"{generatorType.Module.ModuleVersionId}_{generatorType.FullName}_{generatedSourceHintName}"); + File.WriteAllText(temporaryFilePath, ""); + + var openDocumentService = _serviceProvider.GetService(); + var hr = openDocumentService.OpenDocumentViaProject( + temporaryFilePath, + VSConstants.LOGVIEWID.TextView_guid, + out _, + out _, + out _, + out var windowFrame); + + if (ErrorHandler.Succeeded(hr) && windowFrame != null) + { + windowFrame.Show(); + } + } + + public bool TryParseGeneratedFilePath( + string filePath, + [NotNullWhen(true)] out ProjectId? projectId, + [NotNullWhen(true)] out string? generatorTypeName, + [NotNullWhen(true)] out string? generatedSourceHintName) + { + if (!filePath.StartsWith(_temporaryDirectory)) + { + projectId = null; + generatorTypeName = null; + generatedSourceHintName = null; + return false; + } + + string fileName = Path.GetFileName(filePath); + string[] parts = fileName.Split(new[] { '_' }, count: 3); + + generatorTypeName = parts[1]; + generatedSourceHintName = parts[2]; + + projectId = ProjectId.CreateFromSerialized(Guid.Parse(new FileInfo(filePath).Directory.Name)); + + return true; + } + + void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy, IVsWindowFrame? windowFrame) + { + if (TryParseGeneratedFilePath(moniker, out var projectId, out var generatorTypeName, out var generatedSourceHintName)) + { + // Attach to the text buffer if we haven't already + if (!_openFiles.TryGetValue(moniker, out OpenSourceGeneratedFile openFile)) + { + openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, projectId, generatorTypeName, generatedSourceHintName, _threadingContext); + _openFiles.Add(moniker, openFile); + _threadingContext.JoinableTaskFactory.Run(() => openFile.UpdateBufferContentsAsync(CancellationToken.None)); + + // Update the RDT flags to ensure the file can't be saved or appears in any MRUs as it's a temporary generated file name. + var cookie = ((IVsRunningDocumentTable4)_runningDocumentTable).GetDocumentCookie(moniker); + ErrorHandler.ThrowOnFailure(_runningDocumentTable.ModifyDocumentFlags(cookie, (uint)(_VSRDTFLAGS.RDT_CantSave | _VSRDTFLAGS.RDT_DontAddToMRU), fSet: 1)); + } + + if (windowFrame != null) + { + openFile.SetWindowFrame(windowFrame); + } + } + } + + void IRunningDocumentTableEventListener.OnCloseDocument(string moniker) + { + if (_openFiles.TryGetValue(moniker, out var openFile)) + { + openFile.Dispose(); + _openFiles.Remove(moniker); + } + } + + void IRunningDocumentTableEventListener.OnRefreshDocumentContext(string moniker, IVsHierarchy hierarchy) + { + } + + void IRunningDocumentTableEventListener.OnRenameDocument(string newMoniker, string oldMoniker, ITextBuffer textBuffer) + { + } + + private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisposable + { + private readonly SourceGeneratedFileManager _fileManager; + private readonly ITextBuffer _textBuffer; + private readonly Workspace _workspace; + private readonly ProjectId _projectId; + private readonly string _generatorTypeName; + private readonly string _generatedSourceHintName; + + /// + /// A read-only region that we create across the entire file to prevent edits unless we are the one making them. + /// It's a dynamic read-only region that will allow edits if is set. + /// + private readonly IReadOnlyRegion _readOnlyRegion; + private bool _updatingBuffer = false; + + /// + /// A cancellation token used for any background updating of this file; this is cancelled on the UI thread + /// when the file is closed. + /// + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + /// + /// A queue used to batch updates to the file. + /// + private readonly AsyncBatchingDelay _batchingWorkQueue; + + /// + /// The of the active window. This may be null if we're in the middle of construction and + /// we haven't been given it yet. + /// + private IVsWindowFrame? _windowFrame; + + private string? _windowFrameMessageToShow = null; + private ImageMoniker _windowFrameImageMonikerToShow = default; + private string? _currentWindowFrameMessage = null; + private ImageMoniker _currentWindowFrameImageMoniker = default; + private IVsInfoBarUIElement? _currentWindowFrameInfoBarElement = null; + + public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, Workspace workspace, ProjectId projectId, string generatorTypeName, string generatedSourceHintName, IThreadingContext threadingContext) + : base(threadingContext, assertIsForeground: true) + { + _fileManager = fileManager; + _textBuffer = textBuffer; + _workspace = workspace; + _projectId = projectId; + _generatorTypeName = generatorTypeName; + _generatedSourceHintName = generatedSourceHintName; + + // We'll create a read-only region for the file, but it'll be a dynamic region we can temporarily suspend + // while we're doing edits. + using (var readOnlyRegionEdit = _textBuffer.CreateReadOnlyRegionEdit()) + { + _readOnlyRegion = readOnlyRegionEdit.CreateDynamicReadOnlyRegion( + _textBuffer.CurrentSnapshot.GetFullSpan(), + SpanTrackingMode.EdgeInclusive, + EdgeInsertionMode.Deny, + callback: _ => !_updatingBuffer); + + readOnlyRegionEdit.Apply(); + } + + _workspace.WorkspaceChanged += OnWorkspaceChanged; + + _batchingWorkQueue = new AsyncBatchingDelay( + TimeSpan.FromSeconds(1), + UpdateBufferContentsAsync, + asyncListener: null, + _cancellationTokenSource.Token); + } + + public void Dispose() + { + using (var readOnlyRegionEdit = _textBuffer.CreateReadOnlyRegionEdit()) + { + readOnlyRegionEdit.RemoveReadOnlyRegion(_readOnlyRegion); + readOnlyRegionEdit.Apply(); + } + + _workspace.WorkspaceChanged -= OnWorkspaceChanged; + + // Cancel any remaining asynchronous work we may have had to update this file + _cancellationTokenSource.Cancel(); + } + + public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken) + { + GeneratedSourceResult? generatedSource = null; + var project = _workspace.CurrentSolution.GetProject(_projectId); + + // Locals correspond to the equivalently-named fields; we'll assign these and then assign to the fields while on the + // UI thread to avoid any potential race where we update the InfoBar while this is running. + string? windowFrameMessageToShow; + ImageMoniker windowFrameImageMonikerToShow; + + if (project == null) + { + windowFrameMessageToShow = "The project no longer exists."; + windowFrameImageMonikerToShow = KnownMonikers.StatusError; + } + else + { + + var generatorDriverRunResults = await project.GetGeneratorDriverRunResultAsync(cancellationToken).ConfigureAwait(false); + var generatorRunResult = generatorDriverRunResults?.Results.SingleOrNull(r => r.Generator.GetType().FullName == _generatorTypeName); + + if (generatorRunResult == null) + { + windowFrameMessageToShow = string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_been_removed_from_the_project, _generatorTypeName); + windowFrameImageMonikerToShow = KnownMonikers.StatusError; + } + else + { + generatedSource = generatorRunResult.Value.GeneratedSources.SingleOrNull(r => r.HintName == _generatedSourceHintName); + + if (generatedSource == null) + { + windowFrameMessageToShow = string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_stopped_generating_this_file, _generatorTypeName); + windowFrameImageMonikerToShow = KnownMonikers.StatusError; + } + else + { + windowFrameMessageToShow = string.Format(ServicesVSResources.This_file_is_autogenerated_by_0_and_cannot_be_edited, _generatorTypeName); + windowFrameImageMonikerToShow = default; + } + } + } + + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + _windowFrameMessageToShow = windowFrameMessageToShow; + _windowFrameImageMonikerToShow = windowFrameImageMonikerToShow; + + // Update the text if we have new text + if (generatedSource.HasValue) + { + try + { + // Allow us to do our own edits + _updatingBuffer = true; + + // Ensure the encoding matches; this is necessary for debugger checksums to match what is in the PDB. + if (_fileManager._textDocumentFactoryService.TryGetTextDocument(_textBuffer, out var textDocument)) + { + textDocument.Encoding = generatedSource.Value.SourceText.Encoding; + } + + // We'll ask the editor to do the diffing for us so updates don't refresh the entire buffer + using (var edit = _textBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) + { + // TODO: make the edit in some nicer way than creating a massive string + edit.Replace(startPosition: 0, _textBuffer.CurrentSnapshot.Length, generatedSource.Value.SourceText.ToString()); + edit.Apply(); + } + } + finally + { + _updatingBuffer = false; + } + } + + // Update the InfoBar either way + EnsureWindowFrameInfoBarUpdated(); + } + + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + { + var oldProject = e.OldSolution.GetProject(_projectId); + var newProject = e.NewSolution.GetProject(_projectId); + + if (oldProject != null && newProject != null) + { + // We'll start this work asynchronously to figure out if we need to change; if the file is closed the cancellationToken + // is triggered and this will no-op. + Task.Run(async () => + { + if (await oldProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false) != + await newProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false)) + { + _batchingWorkQueue.RequeueWork(); + } + }, _cancellationTokenSource.Token); + } + } + + internal void SetWindowFrame(IVsWindowFrame windowFrame) + { + AssertIsForeground(); + + if (_windowFrame != null) + { + // We already have a window frame, and we don't expect to get a second one + return; + } + + _windowFrame = windowFrame; + + // We'll override the window frame and never show it as dirty, even if there's an underlying edit + windowFrame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, false); + windowFrame.SetProperty((int)__VSFPROPID5.VSFPROPID_OverrideCaption, _generatedSourceHintName + " " + ServicesVSResources.generated_suffix); + windowFrame.SetProperty((int)__VSFPROPID5.VSFPROPID_OverrideToolTip, _generatedSourceHintName + " " + string.Format(ServicesVSResources.generated_by_0_suffix, _generatorTypeName)); + + EnsureWindowFrameInfoBarUpdated(); + } + + private void EnsureWindowFrameInfoBarUpdated() + { + AssertIsForeground(); + + if (_windowFrameMessageToShow == null || + _windowFrame == null || + _currentWindowFrameMessage == _windowFrameMessageToShow && + !_currentWindowFrameImageMoniker.Equals(_windowFrameImageMonikerToShow)) + { + // We don't have anything to do, or anything to do yet. + return; + } + + var infoBarFactory = (IVsInfoBarUIFactory)_fileManager._serviceProvider.GetService(typeof(SVsInfoBarUIFactory)); + Assumes.Present(infoBarFactory); + + if (ErrorHandler.Failed(_windowFrame.GetProperty((int)__VSFPROPID7.VSFPROPID_InfoBarHost, out var infoBarHostObject)) || + infoBarHostObject is not IVsInfoBarHost infoBarHost) + { + return; + } + + // Remove the existing bar + if (_currentWindowFrameInfoBarElement != null) + { + infoBarHost.RemoveInfoBar(_currentWindowFrameInfoBarElement); + } + + var infoBar = new InfoBarModel(_windowFrameMessageToShow, _windowFrameImageMonikerToShow, isCloseButtonVisible: false); + var infoBarUI = infoBarFactory.CreateInfoBar(infoBar); + + infoBarHost.AddInfoBar(infoBarUI); + + _currentWindowFrameMessage = _windowFrameMessageToShow; + _currentWindowFrameImageMoniker = _windowFrameImageMonikerToShow; + _currentWindowFrameInfoBarElement = infoBarUI; + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index 88bcafeda8e4e7f29567edb302321dbabd95bb1b..56fec42a0d824cfe346676f4f9456c83dd7f51c3 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -22,6 +22,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Utilities; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.Library; @@ -39,6 +40,7 @@ internal partial class VisualStudioSymbolNavigationService : ForegroundThreadAff private readonly IServiceProvider _serviceProvider; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; + private readonly SourceGeneratedFileManager _sourceGeneratedFileManager; public VisualStudioSymbolNavigationService( SVsServiceProvider serviceProvider, @@ -50,6 +52,7 @@ internal partial class VisualStudioSymbolNavigationService : ForegroundThreadAff var componentModel = _serviceProvider.GetService(); _editorAdaptersFactory = componentModel.GetService(); _metadataAsSourceFileService = componentModel.GetService(); + _sourceGeneratedFileManager = componentModel.GetService(); } public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options, CancellationToken cancellationToken) @@ -76,6 +79,17 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet optio var navigationService = editorWorkspace.Services.GetRequiredService(); return navigationService.TryNavigateToSpan(editorWorkspace, targetDocument.Id, sourceLocation.SourceSpan, options); } + else + { + // This may be a file from a source generator; if so let's go to it instead + var generatorRunResult = project.GetGeneratorDriverRunResultAsync(cancellationToken).WaitAndGetResult(cancellationToken); + + if (generatorRunResult.TryGetGeneratorAndHint(sourceLocation.SourceTree!, out var generator, out var hintName)) + { + _sourceGeneratedFileManager.NavigateToSourceGeneratedFile(project, generator, hintName, sourceLocation.SourceSpan); + return true; + } + } } // We don't have a source document, so show the Metadata as Source view in a preview tab. diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 4213e6275f72b401ccb0a25c63a775c64be076af..2cfd1136a4ff4700be98ba2d6c17c6b4342c60b5 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1538,4 +1538,20 @@ I agree to all of the foregoing: Extract Base Class + + This file is auto-generated by the generator '{0}' and cannot be edited. + + + [generated by {0}] + {0} is the name of a generator. + + + [generated] + + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 414ce43824770f929ec1bb2ada7b437766f146b3..27fa6d82c1d10c1c84eab7254afc73f73cd93c16 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -777,6 +777,21 @@ Cílový obor názvů: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Toto je neplatný obor názvů. @@ -1017,6 +1032,16 @@ {0} se změní na veřejný. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo Daný pracovní prostor nepodporuje vrácení akce zpátky. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index b5173720f2baadded7410dae348a42b376bafab2..82a392bcb98e07e413211e4600c934b1c3975b99 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -777,6 +777,21 @@ Zielnamespace: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Dies ist ein ungültiger Namespace. @@ -1017,6 +1032,16 @@ "{0}" wird in öffentlichen Wert geändert. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo Der angegebene Arbeitsbereich unterstützt die Funktion "Rückgängig" nicht. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index b96ba3e025f80e6d91173428a14eae19a4f552e4..2af5a6fa9d84319fc56cebf95fae3ef667119b34 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -777,6 +777,21 @@ Espacio de nombres de destino: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Este espacio de nombres no es válido @@ -1017,6 +1032,16 @@ "{0}" se cambiará a público. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo el área de trabajo determinada no permite deshacer diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index 1fa458067c506bba32fcb3e66aa99e2ababdb053..0b9e344700d236f489e80920c0488a8a2840915c 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -777,6 +777,21 @@ Espace de noms cible : + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Ceci est un espace de noms non valide @@ -1017,6 +1032,16 @@ '{0}' va être changé en valeur publique. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo l'espace de travail donné ne prend pas en charge la fonction Annuler diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index 594c2e762200c1709024fa21abc055b7a964a8fa..8a167fa48a10a2e4f59d5654184979e8e1c74df0 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -777,6 +777,21 @@ Spazio dei nomi di destinazione: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Questo è uno spazio dei nomi non valido @@ -1017,6 +1032,16 @@ '{0}' verrà modificato in pubblico. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo l'area di lavoro specificata non supporta l'annullamento di operazioni diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index dd22c780161a0b44c9f0cb050487d6d5d773ac4f..677df295548581f5ec273f91182abedfc345ffe2 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -777,6 +777,21 @@ ターゲット名前空間: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace これは無効な名前空間です @@ -1017,6 +1032,16 @@ '{0}' はパブリックに変更されます。 + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 指定されたワークスペースは元に戻す操作をサポートしていません diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 2402269309bbe81b4edeb527aad7dde0a73c1f7d..937ba4cd451d3b8562e9be445376b512fa30d092 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -777,6 +777,21 @@ 대상 네임스페이스: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace 잘못된 네임스페이스입니다. @@ -1017,6 +1032,16 @@ '{0}'이(가) 공용으로 변경됩니다. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 지정한 작업 영역에서 실행을 취소할 수 없습니다. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 81361c69ef85688501e068d88829e0f5d5138733..82f18388671935b819f5ef1626aa40eaf722a946 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -777,6 +777,21 @@ Docelowa przestrzeń nazw: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace To jest nieprawidłowa przestrzeń nazw @@ -1017,6 +1032,16 @@ Element „{0}” zostanie zmieniony na publiczny. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo dany obszar roboczy nie obsługuje operacji cofania diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index 3e6da477dcbc0e76d7448e1686d040fc490764ce..7d3309b8588852a76f21264aeab836da59136655 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -777,6 +777,21 @@ Namespace de Destino: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Este é um namespace inválido @@ -1017,6 +1032,16 @@ '{0}' será alterado para público. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo o workspace fornecido não dá suporte a desfazer diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 9967f8929fb6d96aa435074bd5c9761486d79f88..4d8a9e8552acf30a0e0569a43b537571a12ad5f7 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -777,6 +777,21 @@ Целевое пространство имен: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Это недопустимое пространство имен. @@ -1017,6 +1032,16 @@ Элемент "{0}" будет изменен на открытый. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo заданная рабочая область не поддерживает отмену. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 1cd54f7ef7ef6d510d0200a6cd7ac462043f2090..78ca67678d848dd003ab8057b2c940c3aa3b6ff1 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -777,6 +777,21 @@ Hedef Ad Alanı: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Bu geçersiz bir ad alanı @@ -1017,6 +1032,16 @@ '{0}' ortak olacak şekilde değiştirildi. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo sağlanan çalışma alanı, geri almayı desteklemiyor diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 4bc37389e47c00ebe3dc934a6f1cfd8fc5b25e8e..4eb77eec1b76889de30808dce7658d5d7918f8fb 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -777,6 +777,21 @@ 目标命名空间: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace 这是一个无效的命名空间 @@ -1017,6 +1032,16 @@ “{0}”将更改为“公共”。 + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 给定的工作区不支持撤消 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 492c8ddefa99659e349dbc6f973480c8c6d77ebe..3829ad22d7d2c6fb6b8282a121dfb62d205ee056 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -777,6 +777,21 @@ 目標命名空間: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace 這是無效的命名空間 @@ -1017,6 +1032,16 @@ '{0}' 會變更為公用。 + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 指定的工作區不支援復原 diff --git a/src/Workspaces/Core/Portable/Utilities/GeneratorDriverRunResultExtensions.cs b/src/Workspaces/Core/Portable/Utilities/GeneratorDriverRunResultExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..a3035a0792df355c74922a66bafcf0cb2be7d3e5 --- /dev/null +++ b/src/Workspaces/Core/Portable/Utilities/GeneratorDriverRunResultExtensions.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Utilities +{ + internal static class GeneratorDriverRunResultExtensions + { + public static bool TryGetGeneratorAndHint( + this GeneratorDriverRunResult? generatorRunResult, + SyntaxTree tree, + [NotNullWhen(true)] out ISourceGenerator? generator, + [NotNullWhen(true)] out string? generatedSourceHintName) + { + if (generatorRunResult != null) + { + foreach (var generatorResult in generatorRunResult.Results) + { + foreach (var generatedSource in generatorResult.GeneratedSources) + { + if (generatedSource.SyntaxTree == tree) + { + generator = generatorResult.Generator; + generatedSourceHintName = generatedSource.HintName; + + return true; + } + } + } + } + + generator = null; + generatedSourceHintName = null; + return false; + } + } +}