未验证 提交 6895112f 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #42346 from CyrusNajmabadi/designerOOP

Move designer attribute scanning out-of-process
......@@ -9,6 +9,7 @@
<ServiceHubService Include="roslynCodeAnalysis" ClassName="Microsoft.CodeAnalysis.Remote.CodeAnalysisService" />
<ServiceHubService Include="roslynRemoteHost" ClassName="Microsoft.CodeAnalysis.Remote.RemoteHostService" />
<ServiceHubService Include="roslynSnapshot" ClassName="Microsoft.CodeAnalysis.Remote.SnapshotService" />
<ServiceHubService Include="roslynRemoteDesignerAttributeService" ClassName="Microsoft.CodeAnalysis.Remote.RemoteDesignerAttributeService" />
<ServiceHubService Include="roslynRemoteSymbolSearchUpdateEngine" ClassName="Microsoft.CodeAnalysis.Remote.RemoteSymbolSearchUpdateEngine" />
<ServiceHubService Include="roslynLanguageServer" ClassName="Microsoft.CodeAnalysis.Remote.LanguageServer" />
</ItemGroup>
......
......@@ -167,6 +167,7 @@ public InProcRemoteServices(bool runCacheCleanup)
RegisterService(WellKnownServiceHubServices.CodeAnalysisService, (s, p) => new CodeAnalysisService(s, p));
RegisterService(WellKnownServiceHubServices.SnapshotService, (s, p) => new SnapshotService(s, p));
RegisterService(WellKnownServiceHubServices.RemoteSymbolSearchUpdateEngine, (s, p) => new RemoteSymbolSearchUpdateEngine(s, p));
RegisterService(WellKnownServiceHubServices.RemoteDesignerAttributeService, (s, p) => new RemoteDesignerAttributeService(s, p));
RegisterService(WellKnownServiceHubServices.LanguageServer, (s, p) => new LanguageServer(s, p));
}
......
// 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.
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DesignerAttributes;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.DesignerAttributes
{
[ExportLanguageServiceFactory(typeof(IDesignerAttributeService), LanguageNames.CSharp), Shared]
internal class CSharpDesignerAttributeServiceFactory : ILanguageServiceFactory
{
[ImportingConstructor]
public CSharpDesignerAttributeServiceFactory()
{
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
=> new CSharpDesignerAttributeService(languageServices.WorkspaceServices.Workspace);
}
internal class CSharpDesignerAttributeService : AbstractDesignerAttributeService
{
public CSharpDesignerAttributeService(Workspace workspace) : base(workspace)
{
}
protected override IEnumerable<SyntaxNode> GetAllTopLevelTypeDefined(SyntaxNode node)
{
if (!(node is CompilationUnitSyntax compilationUnit))
{
return SpecializedCollections.EmptyEnumerable<SyntaxNode>();
}
return compilationUnit.Members.SelectMany(GetAllTopLevelTypeDefined);
}
private IEnumerable<SyntaxNode> GetAllTopLevelTypeDefined(MemberDeclarationSyntax member)
{
switch (member)
{
case NamespaceDeclarationSyntax namespaceMember:
return namespaceMember.Members.SelectMany(GetAllTopLevelTypeDefined);
case ClassDeclarationSyntax type:
return SpecializedCollections.SingletonEnumerable<SyntaxNode>(type);
}
return SpecializedCollections.EmptyEnumerable<SyntaxNode>();
}
protected override bool ProcessOnlyFirstTypeDefined()
{
return true;
}
protected override bool HasAttributesOrBaseTypeOrIsPartial(SyntaxNode typeNode)
{
if (typeNode is ClassDeclarationSyntax classNode)
{
return classNode.AttributeLists.Count > 0 ||
classNode.BaseList != null ||
classNode.Modifiers.Any(SyntaxKind.PartialKeyword);
}
return false;
}
}
}
// 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.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.DesignerAttributes
{
internal abstract class AbstractDesignerAttributeService : IDesignerAttributeService
{
// we hold onto workspace to make sure given input (Document) belong to right workspace.
// since remote host is from workspace service, different workspace can have different expectation
// on remote host, so we need to make sure given input always belong to right workspace where
// the session belong to.
private readonly Workspace _workspace;
protected AbstractDesignerAttributeService(Workspace workspace)
{
_workspace = workspace;
}
protected abstract bool ProcessOnlyFirstTypeDefined();
protected abstract IEnumerable<SyntaxNode> GetAllTopLevelTypeDefined(SyntaxNode root);
protected abstract bool HasAttributesOrBaseTypeOrIsPartial(SyntaxNode typeNode);
public async Task<DesignerAttributeResult> ScanDesignerAttributesAsync(Document document, CancellationToken cancellationToken)
{
// make sure given input is right one
Contract.ThrowIfFalse(_workspace == document.Project.Solution.Workspace);
// run designer attributes scanner on remote host
// we only run closed files to make open document to have better responsiveness.
// also we cache everything related to open files anyway, no saving by running
// them in remote host
if (!document.IsOpen())
{
var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var result = await client.TryRunRemoteAsync<DesignerAttributeResult>(
WellKnownServiceHubServices.CodeAnalysisService,
nameof(IRemoteDesignerAttributeService.ScanDesignerAttributesAsync),
document.Project.Solution,
new[] { document.Id },
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (result.HasValue)
{
return result.Value;
}
}
}
return await ScanDesignerAttributesInCurrentProcessAsync(document, cancellationToken).ConfigureAwait(false);
}
private async Task<DesignerAttributeResult> ScanDesignerAttributesInCurrentProcessAsync(Document document, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
// Delay getting any of these until we need them, but hold on to them once we have them.
Compilation compilation = null;
INamedTypeSymbol designerAttribute = null;
SemanticModel model = null;
string designerAttributeArgument = null;
var documentHasError = false;
// get type defined in current tree
foreach (var typeNode in GetAllTopLevelTypeDefined(root))
{
cancellationToken.ThrowIfCancellationRequested();
if (HasAttributesOrBaseTypeOrIsPartial(typeNode))
{
if (designerAttribute == null)
{
compilation ??= await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
designerAttribute = compilation.DesignerCategoryAttributeType();
if (designerAttribute == null)
{
// The DesignerCategoryAttribute doesn't exist. either not applicable or
// no idea on design attribute status, just leave things as it is.
return new DesignerAttributeResult(designerAttributeArgument, documentHasError, applicable: false);
}
}
if (model == null)
{
model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
}
if (!(model.GetDeclaredSymbol(typeNode, cancellationToken) is INamedTypeSymbol definedType))
{
continue;
}
// walk up type chain
foreach (var type in definedType.GetBaseTypesAndThis())
{
if (type.IsErrorType())
{
documentHasError = true;
continue;
}
cancellationToken.ThrowIfCancellationRequested();
// if it has designer attribute, set it
var attribute = type.GetAttributes().Where(d => designerAttribute.Equals(d.AttributeClass)).FirstOrDefault();
if (attribute != null && attribute.ConstructorArguments.Length == 1)
{
designerAttributeArgument = GetArgumentString(attribute.ConstructorArguments[0]);
return new DesignerAttributeResult(designerAttributeArgument, documentHasError, applicable: true);
}
}
}
// check only first type
if (ProcessOnlyFirstTypeDefined())
{
break;
}
}
return new DesignerAttributeResult(designerAttributeArgument, documentHasError, applicable: true);
}
private static string GetArgumentString(TypedConstant argument)
{
if (argument.Type == null ||
argument.Type.SpecialType != SpecialType.System_String ||
argument.IsNull)
{
return null;
}
return ((string)argument.Value).Trim();
}
}
}
' 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.
Imports System.Composition
Imports Microsoft.CodeAnalysis.DesignerAttributes
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.DesignerAttributes
<ExportLanguageServiceFactory(GetType(IDesignerAttributeService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicDesignerAttributeServiceFactory
Implements ILanguageServiceFactory
<ImportingConstructor>
Public Sub New()
End Sub
Public Function CreateLanguageService(languageServices As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService
Return New BasicDesignerAttributeService(languageServices.WorkspaceServices.Workspace)
End Function
End Class
Friend Class BasicDesignerAttributeService
Inherits AbstractDesignerAttributeService
Public Sub New(workspace As Workspace)
MyBase.New(workspace)
End Sub
Protected Overrides Function GetAllTopLevelTypeDefined(node As SyntaxNode) As IEnumerable(Of SyntaxNode)
Dim compilationUnit = TryCast(node, CompilationUnitSyntax)
If compilationUnit Is Nothing Then
Return SpecializedCollections.EmptyEnumerable(Of SyntaxNode)()
End If
Return compilationUnit.Members.SelectMany(AddressOf GetAllTopLevelTypeDefined)
End Function
Private Overloads Function GetAllTopLevelTypeDefined(member As StatementSyntax) As IEnumerable(Of SyntaxNode)
Dim namespaceMember = TryCast(member, NamespaceBlockSyntax)
If namespaceMember IsNot Nothing Then
Return namespaceMember.Members.SelectMany(AddressOf GetAllTopLevelTypeDefined)
End If
Dim type = TryCast(member, ClassBlockSyntax)
If type IsNot Nothing Then
Return SpecializedCollections.SingletonEnumerable(Of SyntaxNode)(type)
End If
Return SpecializedCollections.EmptyEnumerable(Of SyntaxNode)()
End Function
Protected Overrides Function ProcessOnlyFirstTypeDefined() As Boolean
Return True
End Function
Protected Overrides Function HasAttributesOrBaseTypeOrIsPartial(typeNode As SyntaxNode) As Boolean
Dim type = TryCast(typeNode, ClassBlockSyntax)
If type IsNot Nothing Then
' VB can't actually use any syntactic tricks to limit the types we need to look at.
' VB allows up to one partial declaration omit the 'Partial' keyword; so the presence
' or absence of attributes, base types, or the 'Partial' keyword doesn't mean anything.
' If this is a ClassBlockSyntax node, we're going to have to bind.
Return True
End If
Return False
End Function
End Class
End Namespace
......@@ -5,7 +5,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.DesignerAttributes;
using Microsoft.CodeAnalysis.DesignerAttribute;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
......@@ -19,43 +19,79 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.DesignerAttri
public class DesignerAttributeServiceTests
{
[Fact]
public async Task NoDesignerTest()
public async Task NoDesignerTest1()
{
var code = @"class Test { }";
await TestAsync(code, designer: false);
await TestAsync(code, category: null);
}
[Fact]
public async Task NoDesignerOnSecondClass()
{
await TestAsync(
@"class Test1 { }
[System.ComponentModel.DesignerCategory(""Form"")]
class Test2 { }", category: null);
}
[Fact]
public async Task NoDesignerOnStruct()
{
await TestAsync(
@"
[System.ComponentModel.DesignerCategory(""Form"")]
struct Test1 { }", category: null);
}
[Fact]
public async Task NoDesignerOnNestedClass()
{
await TestAsync(
@"class Test1
{
[System.ComponentModel.DesignerCategory(""Form"")]
class Test2 { }
}", category: null);
}
[Fact]
public async Task SimpleDesignerTest()
{
var code = @"[System.ComponentModel.DesignerCategory(""Form"")]
class Test { }";
await TestAsync(code, designer: true);
await TestAsync(
@"[System.ComponentModel.DesignerCategory(""Form"")]
class Test { }", "Form");
}
private static async Task TestAsync(string codeWithMarker, bool designer)
[Fact]
public async Task SimpleDesignerTest2()
{
await TestAsync(codeWithMarker, designer, remote: false);
await TestAsync(codeWithMarker, designer, remote: true);
await TestAsync(
@"using System.ComponentModel;
[DesignerCategory(""Form"")]
class Test { }", "Form");
}
private static async Task TestAsync(string codeWithMarker, bool designer, bool remote)
private static async Task TestAsync(string codeWithMarker, string category)
{
using var workspace = TestWorkspace.CreateCSharp(codeWithMarker, openDocuments: false);
workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options
.WithChangedOption(RemoteHostOptions.RemoteHostTest, remote)));
var hostDocument = workspace.Documents.First();
var documentId = hostDocument.Id;
var document = workspace.CurrentSolution.GetDocument(documentId);
var service = document.GetLanguageService<IDesignerAttributeService>();
var result = await service.ScanDesignerAttributesAsync(document, CancellationToken.None);
var compilation = await document.Project.GetCompilationAsync();
var actual = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync(
compilation.DesignerCategoryAttributeType(), document, CancellationToken.None);
var argumentIsNull = result.DesignerAttributeArgument == null;
Assert.Equal(designer, !argumentIsNull);
Assert.Equal(category, actual);
}
}
}
// 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.
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.DesignerAttributes;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Versions;
using Microsoft.VisualStudio.Designer.Interfaces;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.Services;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute
{
internal partial class DesignerAttributeIncrementalAnalyzer : ForegroundThreadAffinitizedObject, IIncrementalAnalyzer
{
private readonly IForegroundNotificationService _notificationService;
private readonly IServiceProvider _serviceProvider;
private readonly DesignerAttributeState _state;
private readonly IAsynchronousOperationListener _listener;
// cache whether a project is cps project or not
private readonly ConcurrentDictionary<ProjectId, IProjectItemDesignerTypeUpdateService> _cpsProjects;
/// <summary>
/// cache designer from UI thread
///
/// access this field through <see cref="GetDesignerFromForegroundThread"/>
/// </summary>
private IVSMDDesignerService _dotNotAccessDirectlyDesigner;
public DesignerAttributeIncrementalAnalyzer(
IThreadingContext threadingContext,
IServiceProvider serviceProvider,
IForegroundNotificationService notificationService,
IAsynchronousOperationListenerProvider listenerProvider)
: base(threadingContext)
{
_serviceProvider = serviceProvider;
Contract.ThrowIfNull(_serviceProvider);
_notificationService = notificationService;
_cpsProjects = new ConcurrentDictionary<ProjectId, IProjectItemDesignerTypeUpdateService>(concurrencyLevel: 2, capacity: 10);
_listener = listenerProvider.GetListener(FeatureAttribute.DesignerAttribute);
_state = new DesignerAttributeState();
}
public Task DocumentResetAsync(Document document, CancellationToken cancellationToken)
{
_state.Remove(document.Id);
return _state.PersistAsync(document, new Data(VersionStamp.Default, VersionStamp.Default, designerAttributeArgument: null), cancellationToken);
}
public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e)
{
return false;
}
public async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken)
{
Contract.ThrowIfFalse(document.IsFromPrimaryBranch());
cancellationToken.ThrowIfCancellationRequested();
if (!document.Project.Solution.Workspace.Options.GetOption(InternalFeatureOnOffOptions.DesignerAttributes))
{
return;
}
// use text and project dependent versions so that we can detect changes on this file and its dependent files
// in current project or projects this depends on transitively
var textVersion = await document.GetTextVersionAsync(cancellationToken).ConfigureAwait(false);
var projectVersion = await document.Project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false);
var existingData = await _state.TryGetExistingDataAsync(document, cancellationToken).ConfigureAwait(false);
if (existingData != null)
{
// check whether we can use the data as it is (can happen when re-using persisted data from previous VS session)
if (CheckVersions(document, textVersion, projectVersion, existingData))
{
await RegisterDesignerAttributeAsync(document, existingData.DesignerAttributeArgument, cancellationToken).ConfigureAwait(false);
return;
}
}
var result = await ScanDesignerAttributesOnRemoteHostIfPossibleAsync(document, cancellationToken).ConfigureAwait(false);
if (!result.Applicable)
{
_state.Remove(document.Id);
return;
}
// we checked all types in the document, but couldn't find designer attribute, but we can't say this document doesn't have designer attribute
// if the document also contains some errors.
var designerAttributeArgumentOpt = result.ContainsErrors ? new Optional<string>() : new Optional<string>(result.DesignerAttributeArgument);
await RegisterDesignerAttributeAndSaveStateAsync(document, textVersion, projectVersion, designerAttributeArgumentOpt, cancellationToken).ConfigureAwait(false);
}
public void RemoveDocument(DocumentId documentId)
{
_state.Remove(documentId);
}
public void RemoveProject(ProjectId projectId)
{
_cpsProjects.TryRemove(projectId, out _);
}
private async Task<DesignerAttributeResult> ScanDesignerAttributesOnRemoteHostIfPossibleAsync(Document document, CancellationToken cancellationToken)
{
// No remote host support, use inproc service
var service = document.GetLanguageService<IDesignerAttributeService>();
if (service == null)
{
return new DesignerAttributeResult(designerAttributeArgument: null, containsErrors: true, applicable: false);
}
return await service.ScanDesignerAttributesAsync(document, cancellationToken).ConfigureAwait(false);
}
private async Task<IProjectItemDesignerTypeUpdateService> GetUpdateServiceIfCpsProjectAsync(Project project, CancellationToken cancellationToken)
{
if (_cpsProjects.TryGetValue(project.Id, out var value))
{
return value;
}
var vsWorkspace = project.Solution.Workspace as VisualStudioWorkspaceImpl;
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
if (!vsWorkspace.IsCPSProject(project))
{
_cpsProjects.TryAdd(project.Id, null);
return null;
}
var vsProject = (IVsProject)vsWorkspace.GetHierarchy(project.Id);
if (ErrorHandler.Failed(vsProject.GetItemContext((uint)VSConstants.VSITEMID.Root, out var projectServiceProvider)))
{
_cpsProjects.TryAdd(project.Id, null);
return null;
}
var serviceProvider = new Shell.ServiceProvider(projectServiceProvider);
var updateService = serviceProvider.GetService(typeof(IProjectItemDesignerTypeUpdateService)) as IProjectItemDesignerTypeUpdateService;
if (updateService == null)
{
_cpsProjects.TryAdd(project.Id, null);
return null;
}
_cpsProjects[project.Id] = updateService;
return updateService;
}
private bool CheckVersions(Document document, VersionStamp textVersion, VersionStamp semanticVersion, Data existingData)
{
// first check full version to see whether we can reuse data in same session, if we can't, check timestamp only version to see whether
// we can use it cross-session.
return document.CanReusePersistedTextVersion(textVersion, existingData.TextVersion) &&
document.Project.CanReusePersistedDependentProjectVersion(semanticVersion, existingData.SemanticVersion);
}
private async Task RegisterDesignerAttributeAndSaveStateAsync(
Document document, VersionStamp textVersion, VersionStamp semanticVersion, Optional<string> designerAttributeArgumentOpt, CancellationToken cancellationToken)
{
if (!designerAttributeArgumentOpt.HasValue)
{
// no value means it couldn't determine whether this document has designer attribute or not.
// one of such case is when base type is error type.
return;
}
var data = new Data(textVersion, semanticVersion, designerAttributeArgumentOpt.Value);
await _state.PersistAsync(document, data, cancellationToken).ConfigureAwait(false);
await RegisterDesignerAttributeAsync(document, designerAttributeArgumentOpt.Value, cancellationToken).ConfigureAwait(false);
}
private async Task RegisterDesignerAttributeAsync(Document document, string designerAttributeArgument, CancellationToken cancellationToken)
{
if (!_state.Update(document.Id, designerAttributeArgument))
{
// value is not updated, meaning we are trying to report same value as before.
// we don't need to do anything.
//
// I kept this since existing code had this, but since we check what platform has before
// reporting it, this might be redundant.
return;
}
if (!(document.Project.Solution.Workspace is VisualStudioWorkspaceImpl workspace))
{
return;
}
var updateService = await GetUpdateServiceIfCpsProjectAsync(document.Project, cancellationToken).ConfigureAwait(false);
if (updateService != null)
{
// fire and forget designer attribute notification to CPS
_ = NotifyCpsDesignerAttributeAsync(document, designerAttributeArgument, updateService).ReportNonFatalErrorAsync();
}
else
{
var documentId = document.Id;
_notificationService.RegisterNotification(() =>
{
var hierarchy = workspace.GetHierarchy(documentId.ProjectId);
if (hierarchy == null)
{
return;
}
var itemId = hierarchy.TryGetItemId(document.FilePath);
if (itemId == VSConstants.VSITEMID_NIL)
{
return;
}
if (ErrorHandler.Succeeded(hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ItemSubType, out var currentValue)))
{
var currentStringValue = string.IsNullOrEmpty(currentValue as string) ? null : (string)currentValue;
if (string.Equals(currentStringValue, designerAttributeArgument, StringComparison.OrdinalIgnoreCase))
{
// PERF: Avoid sending the message if the project system already has the current value.
return;
}
}
try
{
var designer = GetDesignerFromForegroundThread();
if (designer != null)
{
designer.RegisterDesignViewAttribute(hierarchy, (int)itemId, dwClass: 0, pwszAttributeValue: designerAttributeArgument);
}
}
catch
{
// DevDiv # 933717
// turns out RegisterDesignViewAttribute can throw in certain cases such as a file failed to be checked out by source control
// or IVSHierarchy failed to set a property for this project
//
// just swallow it. don't crash VS.
}
}, _listener.BeginAsyncOperation("RegisterDesignerAttribute"));
}
}
private async Task NotifyCpsDesignerAttributeAsync(Document document, string designerAttributeArgument, IProjectItemDesignerTypeUpdateService updateService)
{
using (_listener.BeginAsyncOperation("RegisterDesignerAttribute"))
{
try
{
await updateService.SetProjectItemDesignerTypeAsync(document.FilePath, designerAttributeArgument).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
// we might call update service after project is already removed and get object disposed exception.
// we will catch the exception and ignore.
// see this PR for more detail - https://github.com/dotnet/roslyn/pull/35383
}
}
}
private IVSMDDesignerService GetDesignerFromForegroundThread()
{
if (_dotNotAccessDirectlyDesigner != null)
{
return _dotNotAccessDirectlyDesigner;
}
AssertIsForeground();
_dotNotAccessDirectlyDesigner = _serviceProvider.GetService(typeof(SVSMDDesignerService)) as IVSMDDesignerService;
return _dotNotAccessDirectlyDesigner;
}
private class Data
{
public readonly VersionStamp TextVersion;
public readonly VersionStamp SemanticVersion;
public readonly string DesignerAttributeArgument;
public Data(VersionStamp textVersion, VersionStamp semanticVersion, string designerAttributeArgument)
{
this.TextVersion = textVersion;
this.SemanticVersion = semanticVersion;
this.DesignerAttributeArgument = designerAttributeArgument;
}
}
#region unused
public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
#endregion
}
}
// 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.
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.SolutionCrawler.State;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute
{
internal partial class DesignerAttributeIncrementalAnalyzer : IIncrementalAnalyzer
{
private class DesignerAttributeState : AbstractDocumentAnalyzerState<Data>
{
private const string FormatVersion = "2";
/// <summary>
/// remember last time what we reported
/// </summary>
private readonly ConcurrentDictionary<DocumentId, string> _lastReported = new ConcurrentDictionary<DocumentId, string>(concurrencyLevel: 2, capacity: 10);
public bool Update(DocumentId id, string designerAttributeArgument)
{
if (_lastReported.TryGetValue(id, out var lastReported) &&
lastReported == designerAttributeArgument)
{
// nothing is actually updated
return false;
}
// value updated
_lastReported[id] = designerAttributeArgument;
return true;
}
protected override string StateName
{
get
{
return "<DesignerAttribute>";
}
}
protected override int GetCount(Data data)
{
return 1;
}
protected override Data TryGetExistingData(Stream stream, Document value, CancellationToken cancellationToken)
{
using var reader = ObjectReader.TryGetReader(stream, leaveOpen: true, cancellationToken);
if (reader != null)
{
var format = reader.ReadString();
if (string.Equals(format, FormatVersion, StringComparison.InvariantCulture))
{
var textVersion = VersionStamp.ReadFrom(reader);
var dataVersion = VersionStamp.ReadFrom(reader);
var designerAttributeArgument = reader.ReadString();
return new Data(textVersion, dataVersion, designerAttributeArgument);
}
}
return null;
}
protected override void WriteTo(Stream stream, Data data, CancellationToken cancellationToken)
{
using var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken);
writer.WriteString(FormatVersion);
data.TextVersion.WriteTo(writer);
data.SemanticVersion.WriteTo(writer);
writer.WriteString(data.DesignerAttributeArgument);
}
public override bool Remove(DocumentId id)
{
// forget what I have reported
_lastReported.TryRemove(id, out _);
return base.Remove(id);
}
}
}
}
......@@ -3,13 +3,20 @@
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.DesignerAttributes
namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute
{
internal interface IDesignerAttributeService : ILanguageService
/// <summary>
/// In process service responsible for listening to OOP notifications whether or not a file is
/// designable and then notifying the respective project systems about that information.
/// </summary>
internal interface IDesignerAttributeService : IWorkspaceService
{
Task<DesignerAttributeResult> ScanDesignerAttributesAsync(Document document, CancellationToken cancellationToken);
/// <summary>
/// Called by a host to let this service know that it should start background
/// analysis of the workspace to determine which classes are designable.
/// </summary>
void Start(CancellationToken cancellationToken);
}
}
// 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.DesignerAttribute;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Designer.Interfaces;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.Services;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute
{
internal class VisualStudioDesignerAttributeService
: ForegroundThreadAffinitizedObject, IDesignerAttributeService, IDesignerAttributeListener
{
private readonly VisualStudioWorkspaceImpl _workspace;
/// <summary>
/// Used so we can switch over to the UI thread for communicating with legacy projects that
/// require that.
/// </summary>
private readonly IThreadingContext _threadingContext;
/// <summary>
/// Used to acquire the legacy project designer service.
/// </summary>
private readonly IServiceProvider _serviceProvider;
/// <summary>
/// Our connections to the remote OOP server. Created on demand when we startup and then
/// kept around for the lifetime of this service.
/// </summary>
private KeepAliveSession? _keepAliveSession;
/// <summary>
/// Cache from project to the CPS designer service for it. Computed on demand (which
/// requires using the UI thread), but then cached for all subsequent notifications about
/// that project.
/// </summary>
private readonly ConcurrentDictionary<ProjectId, IProjectItemDesignerTypeUpdateService?> _cpsProjects
= new ConcurrentDictionary<ProjectId, IProjectItemDesignerTypeUpdateService?>();
/// <summary>
/// Cached designer service for notifying legacy projects about designer atttributes.
/// </summary>
private IVSMDDesignerService? _legacyDesignerService;
// We'll get notifications from the OOP server about new attribute arguments. Batch those
// notifications up and deliver them to VS every second.
#region protected by lock
/// <summary>
/// Lock we will use to ensure the remainder of these fields can be accessed in a threadsafe
/// manner. When OOP calls back into us, we'll place the data it produced into
/// <see cref="_updatedInfos"/>. We'll then kick of a task to process this in the future if
/// we don't already have an existing task in flight for that.
/// </summary>
private readonly object _gate = new object();
/// <summary>
/// Data produced by OOP that we want to process in our next update task.
/// </summary>
private readonly List<DesignerInfo> _updatedInfos = new List<DesignerInfo>();
/// <summary>
/// Task kicked off to do the next batch of processing of <see cref="_updatedInfos"/>. These
/// tasks form a chain so that the next task only processes when the previous one completes.
/// </summary>
private Task _updateTask = Task.CompletedTask;
/// <summary>
/// Whether or not there is an existing task in flight that will process the current batch
/// of <see cref="_updatedInfos"/>. If there is an existing in flight task, we don't need
/// to kick off a new one if we receive more notifications before it runs.
/// </summary>
private bool _taskInFlight = false;
#endregion
public VisualStudioDesignerAttributeService(
VisualStudioWorkspaceImpl workspace,
IThreadingContext threadingContext,
IServiceProvider serviceProvider)
: base(threadingContext)
{
_workspace = workspace;
_threadingContext = threadingContext;
_serviceProvider = serviceProvider;
_workspace.WorkspaceChanged += OnWorkspaceChanged;
}
private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
if (e.Kind == WorkspaceChangeKind.ProjectRemoved)
_cpsProjects.TryRemove(e.ProjectId, out _);
}
void IDesignerAttributeService.Start(CancellationToken cancellationToken)
=> _ = StartAsync(cancellationToken);
private async Task StartAsync(CancellationToken cancellationToken)
{
// Have to catch all exceptions coming through here as this is called from a
// fire-and-forget method and we want to make sure nothing leaks out.
try
{
await StartWorkerAsync(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Cancellation is normal (during VS closing). Just ignore.
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
// Otherwise report a watson for any other exception. Don't bring down VS. This is
// a BG service we don't want impacting the user experience.
}
}
private async Task StartWorkerAsync(CancellationToken cancellationToken)
{
var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false);
if (client == null)
return;
// Pass ourselves in as the callback target for the OOP service. As it discovers
// designer attributes it will call back into us to notify VS about it.
_keepAliveSession = await client.TryCreateKeepAliveSessionAsync(
WellKnownServiceHubServices.RemoteDesignerAttributeService,
callbackTarget: this, cancellationToken).ConfigureAwait(false);
if (_keepAliveSession == null)
return;
// Now kick off scanning in the OOP process.
var success = await _keepAliveSession.TryInvokeAsync(
nameof(IRemoteDesignerAttributeService.StartScanningForDesignerAttributesAsync),
solution: null,
arguments: Array.Empty<object>(),
cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Callback from the OOP service back into us.
/// </summary>
public Task RegisterDesignerAttributesAsync(
IList<DesignerInfo> attributeInfos, CancellationToken cancellationToken)
{
lock (_gate)
{
// add our work to the set we'll process in the next batch.
_updatedInfos.AddRange(attributeInfos);
if (!_taskInFlight)
{
// No in-flight task. Kick one off to process these messages a second from now.
// We always attach the task to the previous one so that notifications to the ui
// follow the same order as the notification the OOP server sent to us.
_updateTask = _updateTask.ContinueWithAfterDelayFromAsync(
_ => NotifyProjectSystemAsync(cancellationToken),
cancellationToken,
1000/*ms*/,
TaskContinuationOptions.RunContinuationsAsynchronously,
TaskScheduler.Default);
_taskInFlight = true;
}
}
return Task.CompletedTask;
}
private async Task NotifyProjectSystemAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using var _1 = ArrayBuilder<DesignerInfo>.GetInstance(out var attributeInfos);
AddInfosAndResetQueue(attributeInfos);
// Now, group all the notifications by project and update all the projects in parallel.
using var _2 = ArrayBuilder<Task>.GetInstance(out var tasks);
foreach (var group in attributeInfos.GroupBy(a => a.DocumentId.ProjectId))
{
cancellationToken.ThrowIfCancellationRequested();
tasks.Add(NotifyProjectSystemAsync(group.Key, group, cancellationToken));
}
// Wait until all project updates have happened before processing the next batch.
await Task.WhenAll(tasks).ConfigureAwait(false);
}
private void AddInfosAndResetQueue(ArrayBuilder<DesignerInfo> attributeInfos)
{
using var _ = PooledHashSet<DocumentId>.GetInstance(out var seenDocumentIds);
lock (_gate)
{
// walk the set of updates in reverse, and ignore documents if we see them a second
// time. This ensures that if we're batching up multiple notifications for the same
// document, that we only bother processing the last one since it should beat out
// all the prior ones.
for (var i = _updatedInfos.Count - 1; i >= 0; i--)
{
var designerArg = _updatedInfos[i];
if (seenDocumentIds.Add(designerArg.DocumentId))
attributeInfos.Add(designerArg);
}
// mark there being no existing update task so that the next OOP notification will
// kick one off.
_updatedInfos.Clear();
_taskInFlight = false;
}
}
private async Task NotifyProjectSystemAsync(
ProjectId projectId,
IEnumerable<DesignerInfo> attributeInfos,
CancellationToken cancellationToken)
{
// Delegate to the CPS or legacy notification services as necessary.
var cpsUpdateService = await GetUpdateServiceIfCpsProjectAsync(projectId, cancellationToken).ConfigureAwait(false);
var task = cpsUpdateService == null
? NotifyLegacyProjectSystemAsync(projectId, attributeInfos, cancellationToken)
: NotifyCpsProjectSystemAsync(cpsUpdateService, attributeInfos, cancellationToken);
await task.ConfigureAwait(false);
}
private async Task NotifyLegacyProjectSystemAsync(
ProjectId projectId,
IEnumerable<DesignerInfo> attributeInfos,
CancellationToken cancellationToken)
{
// legacy project system can only be talked to on the UI thread.
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
AssertIsForeground();
var designerService = _legacyDesignerService ??= (IVSMDDesignerService)_serviceProvider.GetService(typeof(SVSMDDesignerService));
if (designerService == null)
return;
var hierarchy = _workspace.GetHierarchy(projectId);
if (hierarchy == null)
return;
foreach (var info in attributeInfos)
{
cancellationToken.ThrowIfCancellationRequested();
NotifyLegacyProjectSystemOnUIThread(designerService, hierarchy, info);
}
}
private void NotifyLegacyProjectSystemOnUIThread(
IVSMDDesignerService designerService,
IVsHierarchy hierarchy,
DesignerInfo info)
{
this.AssertIsForeground();
var itemId = hierarchy.TryGetItemId(info.FilePath);
if (itemId == VSConstants.VSITEMID_NIL)
return;
// PERF: Avoid sending the message if the project system already has the current value.
if (ErrorHandler.Succeeded(hierarchy.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_ItemSubType, out var currentValue)))
{
var currentStringValue = string.IsNullOrEmpty(currentValue as string) ? null : (string)currentValue;
if (string.Equals(currentStringValue, info.Category, StringComparison.OrdinalIgnoreCase))
return;
}
try
{
designerService.RegisterDesignViewAttribute(
hierarchy, (int)itemId, dwClass: 0,
pwszAttributeValue: info.Category);
}
catch
{
// DevDiv # 933717
// turns out RegisterDesignViewAttribute can throw in certain cases such as a file failed to be checked out by source control
// or IVSHierarchy failed to set a property for this project
//
// just swallow it. don't crash VS.
}
}
private async Task NotifyCpsProjectSystemAsync(
IProjectItemDesignerTypeUpdateService updateService,
IEnumerable<DesignerInfo> attributeInfos,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
// Broadcast all the information about all the documents in parallel to CPS.
using var _ = ArrayBuilder<Task>.GetInstance(out var tasks);
foreach (var info in attributeInfos)
{
cancellationToken.ThrowIfCancellationRequested();
tasks.Add(NotifyCpsProjectSystemAsync(updateService, info, cancellationToken));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
}
private async Task NotifyCpsProjectSystemAsync(
IProjectItemDesignerTypeUpdateService updateService,
DesignerInfo info,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
await updateService.SetProjectItemDesignerTypeAsync(info.FilePath, info.Category).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
// we might call update service after project is already removed and get object disposed exception.
// we will catch the exception and ignore.
// see this PR for more detail - https://github.com/dotnet/roslyn/pull/35383
}
}
private async Task<IProjectItemDesignerTypeUpdateService?> GetUpdateServiceIfCpsProjectAsync(
ProjectId projectId, CancellationToken cancellationToken)
{
if (!_cpsProjects.TryGetValue(projectId, out var updateService))
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
this.AssertIsForeground();
updateService = ComputeUpdateService();
_cpsProjects.TryAdd(projectId, updateService);
}
return updateService;
IProjectItemDesignerTypeUpdateService? ComputeUpdateService()
{
if (!_workspace.IsCPSProject(projectId))
return null;
var vsProject = (IVsProject?)_workspace.GetHierarchy(projectId);
if (vsProject == null)
return null;
if (ErrorHandler.Failed(vsProject.GetItemContext((uint)VSConstants.VSITEMID.Root, out var projectServiceProvider)))
return null;
var serviceProvider = new Shell.ServiceProvider(projectServiceProvider);
return serviceProvider.GetService(typeof(IProjectItemDesignerTypeUpdateService)) as IProjectItemDesignerTypeUpdateService;
}
}
}
}
......@@ -4,44 +4,36 @@
using System;
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute
{
[ExportIncrementalAnalyzerProvider(Name, new[] { WorkspaceKind.Host }), Shared]
internal class DesignerAttributeIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider
[ExportWorkspaceServiceFactory(typeof(IDesignerAttributeService), ServiceLayer.Host), Shared]
internal class VisualStudioDesignerAttributeServiceFactory : IWorkspaceServiceFactory
{
public const string Name = nameof(DesignerAttributeIncrementalAnalyzerProvider);
private readonly IThreadingContext _threadingContext;
private readonly IServiceProvider _serviceProvider;
private readonly IForegroundNotificationService _notificationService;
private readonly IAsynchronousOperationListenerProvider _listenerProvider;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public DesignerAttributeIncrementalAnalyzerProvider(
public VisualStudioDesignerAttributeServiceFactory(
IThreadingContext threadingContext,
SVsServiceProvider serviceProvider,
IForegroundNotificationService notificationService,
IAsynchronousOperationListenerProvider listenerProvider)
Shell.SVsServiceProvider serviceProvider)
{
_threadingContext = threadingContext;
_serviceProvider = serviceProvider;
_notificationService = notificationService;
_listenerProvider = listenerProvider;
}
public IIncrementalAnalyzer CreateIncrementalAnalyzer(CodeAnalysis.Workspace workspace)
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
return new DesignerAttributeIncrementalAnalyzer(
_threadingContext, _serviceProvider, _notificationService, _listenerProvider);
if (!(workspaceServices.Workspace is VisualStudioWorkspaceImpl workspace))
return null;
return new VisualStudioDesignerAttributeService(
workspace, _threadingContext, _serviceProvider);
}
}
}
......@@ -423,10 +423,13 @@ internal override bool CanRenameFilesDuringCodeActions(CodeAnalysis.Project proj
=> !IsCPSProject(project);
internal bool IsCPSProject(CodeAnalysis.Project project)
=> IsCPSProject(project.Id);
internal bool IsCPSProject(ProjectId projectId)
{
_foregroundObject.AssertIsForeground();
if (this.TryGetHierarchy(project.Id, out var hierarchy))
if (this.TryGetHierarchy(projectId, out var hierarchy))
{
// Currently renaming files in CPS projects (i.e. .NET Core) doesn't work proprey.
// This is because the remove/add of the documents in CPS is not synchronous
......
......@@ -22,6 +22,7 @@
using Microsoft.VisualStudio.LanguageServices.ColorSchemes;
using Microsoft.VisualStudio.LanguageServices.Experimentation;
using Microsoft.VisualStudio.LanguageServices.Implementation;
using Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute;
using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics;
using Microsoft.VisualStudio.LanguageServices.Implementation.Interactive;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
......@@ -154,6 +155,11 @@ private async Task LoadComponentsBackgroundAsync(CancellationToken cancellationT
{
await experiment.InitializeAsync().ConfigureAwait(true);
}
// Load the designer attribute service and tell it to start watching the solution for
// designable files.
var designerAttributeService = _workspace.Services.GetService<IDesignerAttributeService>();
designerAttributeService.Start(this.DisposalToken);
}
private async Task LoadInteractiveMenusAsync(CancellationToken cancellationToken)
......
......@@ -9,7 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.DesignerAttributes;
using Microsoft.CodeAnalysis.DesignerAttribute;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
......@@ -17,10 +17,14 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.DebugUtil;
using Microsoft.CodeAnalysis.Remote.Shared;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.TodoComments;
using Microsoft.CodeAnalysis.UnitTests;
using Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute;
using Nerdbank;
using Roslyn.Test.Utilities.Remote;
using Roslyn.Utilities;
......@@ -152,27 +156,74 @@ public async Task TestTodoComments()
}
}
private static async Task<SolutionService> GetSolutionServiceAsync(Solution solution, Dictionary<Checksum, object> map = null)
{
// make sure checksum is calculated
await solution.State.GetChecksumAsync(CancellationToken.None);
map ??= new Dictionary<Checksum, object>();
await solution.AppendAssetMapAsync(map, CancellationToken.None);
var sessionId = 0;
var storage = new AssetStorage();
_ = new SimpleAssetSource(storage, map);
var remoteWorkspace = new RemoteWorkspace(applyStartupOptions: false);
return new SolutionService(new AssetProvider(sessionId, storage, remoteWorkspace.Services.GetService<ISerializerService>()));
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public async Task TestDesignerAttributes()
{
var code = @"[System.ComponentModel.DesignerCategory(""Form"")]
class Test { }";
using (var workspace = TestWorkspace.CreateCSharp(code))
{
var client = (InProcRemoteHostClient)await InProcRemoteHostClient.CreateAsync(workspace, runCacheCleanup: false);
using var workspace = TestWorkspace.CreateCSharp(
@"[System.ComponentModel.DesignerCategory(""Form"")]
class Test { }");
var solution = workspace.CurrentSolution;
var cancellationTokenSource = new CancellationTokenSource();
var result = await client.TryRunRemoteAsync<DesignerAttributeResult>(
WellKnownServiceHubServices.CodeAnalysisService,
nameof(IRemoteDesignerAttributeService.ScanDesignerAttributesAsync),
solution,
new[] { solution.Projects.First().DocumentIds.First() },
callbackTarget: null,
CancellationToken.None);
var solution = workspace.CurrentSolution;
Assert.Equal("Form", result.Value.DesignerAttributeArgument);
// Ensure remote workspace is in sync with normal workspace.
var solutionService = await GetSolutionServiceAsync(solution);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
await solutionService.UpdatePrimaryWorkspaceAsync(solutionChecksum, solution.WorkspaceVersion, CancellationToken.None);
var callback = new DesignerListener();
using var client = await InProcRemoteHostClient.CreateAsync(workspace, runCacheCleanup: false);
var session = await client.TryCreateKeepAliveSessionAsync(
WellKnownServiceHubServices.RemoteDesignerAttributeService,
callback,
cancellationTokenSource.Token);
var invokeTask = session.TryInvokeAsync(
nameof(IRemoteDesignerAttributeService.StartScanningForDesignerAttributesAsync),
solution: null,
arguments: Array.Empty<object>(),
cancellationTokenSource.Token);
var infos = await callback.Infos;
Assert.Equal(1, infos.Count);
var info = infos[0];
Assert.Equal("Form", info.Category);
Assert.Equal(solution.Projects.Single().Documents.Single().Id, info.DocumentId);
cancellationTokenSource.Cancel();
await invokeTask;
}
private class DesignerListener : IDesignerAttributeListener
{
private readonly TaskCompletionSource<IList<DesignerInfo>> _infosSource = new TaskCompletionSource<IList<DesignerInfo>>();
public Task<IList<DesignerInfo>> Infos => _infosSource.Task;
public Task RegisterDesignerAttributesAsync(IList<DesignerInfo> infos, CancellationToken cancellationToken)
{
_infosSource.SetResult(infos);
return Task.CompletedTask;
}
}
......
......@@ -10,7 +10,6 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.DebugUtil;
using Microsoft.CodeAnalysis.Remote.Shared;
......@@ -19,7 +18,6 @@
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using Roslyn.VisualStudio.Next.UnitTests.Mocks;
using Xunit;
namespace Roslyn.VisualStudio.Next.UnitTests.Remote
......
......@@ -34,11 +34,13 @@
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteHost.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynSnapshot.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynCodeAnalysis.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteDesignerAttributeService.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteSymbolSearchUpdateEngine.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynLanguageServer.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteHost64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynSnapshot64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynCodeAnalysis64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteDesignerAttributeService64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynRemoteSymbolSearchUpdateEngine64.servicehub.service.json" />
<Asset Type="Microsoft.ServiceHub.Service" d:Source="File" Path="roslynLanguageServer64.servicehub.service.json" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="BasicVisualStudio" Path="|BasicVisualStudio|" />
......
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.DesignerAttribute
{
internal static class DesignerAttributeHelpers
{
public static async Task<string?> ComputeDesignerAttributeCategoryAsync(
INamedTypeSymbol? designerCategoryType,
Document document,
CancellationToken cancellationToken)
{
// simple case. If there's no DesignerCategory type in this compilation, then there's
// definitely no designable types. Just immediately bail out.
if (designerCategoryType == null)
return null;
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
// Legacy behavior. We only register the designer info for the first non-nested class
// in the file.
var firstClass = FindFirstNonNestedClass(
syntaxFacts, syntaxFacts.GetMembersOfCompilationUnit(root), cancellationToken);
if (firstClass == null)
return null;
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var firstClassType = (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(firstClass, cancellationToken);
return TryGetDesignerCategory(firstClassType, designerCategoryType, cancellationToken);
}
private static string? TryGetDesignerCategory(
INamedTypeSymbol classType,
INamedTypeSymbol designerCategoryType,
CancellationToken cancellationToken)
{
foreach (var type in classType.GetBaseTypesAndThis())
{
cancellationToken.ThrowIfCancellationRequested();
// if it has designer attribute, set it
var attribute = type.GetAttributes().FirstOrDefault(d => designerCategoryType.Equals(d.AttributeClass));
if (attribute?.ConstructorArguments.Length == 1)
return GetArgumentString(attribute.ConstructorArguments[0]);
}
return null;
}
private static SyntaxNode? FindFirstNonNestedClass(
ISyntaxFactsService syntaxFacts, SyntaxList<SyntaxNode> members, CancellationToken cancellationToken)
{
foreach (var member in members)
{
cancellationToken.ThrowIfCancellationRequested();
if (syntaxFacts.IsNamespaceDeclaration(member))
{
var firstClass = FindFirstNonNestedClass(
syntaxFacts, syntaxFacts.GetMembersOfNamespaceDeclaration(member), cancellationToken);
if (firstClass != null)
return firstClass;
}
else if (syntaxFacts.IsClassDeclaration(member))
{
return member;
}
}
return null;
}
private static string? GetArgumentString(TypedConstant argument)
{
if (argument.Type == null ||
argument.Type.SpecialType != SpecialType.System_String ||
argument.IsNull ||
!(argument.Value is string stringValue))
{
return null;
}
return stringValue.Trim();
}
}
}
......@@ -2,30 +2,28 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CodeAnalysis.DesignerAttributes
#nullable enable
namespace Microsoft.CodeAnalysis.DesignerAttribute
{
internal readonly struct DesignerAttributeResult
/// <summary>
/// Serialization typed used to pass information to/from OOP and VS.
/// </summary>
internal struct DesignerInfo
{
/// <summary>
/// Designer attribute string
/// The category specified in a <c>[DesignerCategory("...")]</c> attribute.
/// </summary>
public string DesignerAttributeArgument { get; }
public string? Category;
/// <summary>
/// No designer attribute due to errors in the document
/// The document this <see cref="Category"/> applies to.
/// </summary>
public bool ContainsErrors { get; }
public DocumentId DocumentId;
/// <summary>
/// The document asked is applicable for the designer attribute
/// Path for this <see cref="DocumentId"/>.
/// </summary>
public bool Applicable { get; }
public DesignerAttributeResult(string designerAttributeArgument, bool containsErrors, bool applicable)
{
DesignerAttributeArgument = designerAttributeArgument;
ContainsErrors = containsErrors;
Applicable = applicable;
}
public string FilePath;
}
}
// 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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.DesignerAttribute
{
/// <summary>
/// Callback the host (VS) passes to the OOP service to allow it to send batch notifications
/// about designer attribute info. There is no guarantee that the host will have done anything
/// with this data when the callback returns, only that it will try to inform the project system
/// about the designer attribute info in the future.
/// </summary>
internal interface IDesignerAttributeListener
{
Task RegisterDesignerAttributesAsync(IList<DesignerInfo> infos, CancellationToken cancellationToken);
}
}
......@@ -2,14 +2,23 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.ErrorReporting;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.DesignerAttributes
namespace Microsoft.CodeAnalysis.DesignerAttribute
{
/// <summary>
/// Interface to allow host (VS) to inform the OOP service to start incrementally analyzing and
/// reporting results back to the host.
/// </summary>
internal interface IRemoteDesignerAttributeService
{
Task<DesignerAttributeResult> ScanDesignerAttributesAsync(PinnedSolutionInfo solutionInfo, DocumentId documentId, CancellationToken cancellationToken);
Task StartScanningForDesignerAttributesAsync(CancellationToken cancellation);
}
}
......@@ -12,6 +12,7 @@ public static void Set64bit(bool x64)
SnapshotService = "roslynSnapshot" + bit;
CodeAnalysisService = "roslynCodeAnalysis" + bit;
RemoteDesignerAttributeService = "roslynRemoteDesignerAttributeService" + bit;
RemoteSymbolSearchUpdateEngine = "roslynRemoteSymbolSearchUpdateEngine" + bit;
LanguageServer = "roslynLanguageServer" + bit;
}
......@@ -19,6 +20,7 @@ public static void Set64bit(bool x64)
public static string SnapshotService { get; private set; } = "roslynSnapshot";
public static string CodeAnalysisService { get; private set; } = "roslynCodeAnalysis";
public static string RemoteSymbolSearchUpdateEngine { get; private set; } = "roslynRemoteSymbolSearchUpdateEngine";
public static string RemoteDesignerAttributeService { get; private set; } = "roslynRemoteDesignerAttributeService";
public static string LanguageServer { get; private set; } = "roslynLanguageServer";
// these are OOP implementation itself should care. not features that consume OOP care
......
......@@ -13,7 +13,6 @@ internal partial class FeatureAttribute
public const string Classification = nameof(Classification);
public const string CodeModel = nameof(CodeModel);
public const string CompletionSet = nameof(CompletionSet);
public const string DesignerAttribute = nameof(DesignerAttribute);
public const string DiagnosticService = nameof(DiagnosticService);
public const string EncapsulateField = nameof(EncapsulateField);
public const string ErrorList = nameof(ErrorList);
......
// 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.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.DesignerAttributes;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger;
namespace Microsoft.CodeAnalysis.Remote
{
// root level service for all Roslyn services
internal partial class CodeAnalysisService : IRemoteDesignerAttributeService
{
/// <summary>
/// This is top level entry point for DesignerAttribute service from client (VS).
///
/// This will be called by ServiceHub/JsonRpc framework
/// </summary>
public Task<DesignerAttributeResult> ScanDesignerAttributesAsync(PinnedSolutionInfo solutionInfo, DocumentId documentId, CancellationToken cancellationToken)
{
return RunServiceAsync(async () =>
{
using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_GetDesignerAttributesAsync, documentId.DebugName, cancellationToken))
{
var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false);
var document = solution.GetDocument(documentId);
var service = document.GetLanguageService<IDesignerAttributeService>();
if (service != null)
{
// todo comment service supported
return await service.ScanDesignerAttributesAsync(document, cancellationToken).ConfigureAwait(false);
}
return new DesignerAttributeResult(designerAttributeArgument: null, containsErrors: true, applicable: false);
}
}, cancellationToken);
}
}
}
// 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.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.DesignerAttribute;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
internal sealed partial class RemoteDesignerAttributeIncrementalAnalyzer : IncrementalAnalyzerBase
{
private const string DataKey = "DesignerAttributeData";
/// <summary>
/// Channel back to VS to inform it of the designer attributes we discover.
/// </summary>
private readonly RemoteEndPoint _endPoint;
private readonly IPersistentStorageService _storageService;
public RemoteDesignerAttributeIncrementalAnalyzer(Workspace workspace, RemoteEndPoint endPoint)
{
_endPoint = endPoint;
_storageService = workspace.Services.GetRequiredService<IPersistentStorageService>();
}
public override Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken)
=> AnalyzeProjectAsync(project, specificDocument: null, cancellationToken);
public override Task AnalyzeDocumentAsync(Document document, SyntaxNode? body, InvocationReasons reasons, CancellationToken cancellationToken)
{
// don't need to reanalyze file if just a method body was edited. That can't
// affect designer attributes.
if (body != null)
return Task.CompletedTask;
// When we register our analyzer we will get called into for every document to
// 'reanalyze' them all. Ignore those as we would prefer to analyze the project
// en-mass.
if (reasons.Contains(PredefinedInvocationReasons.Reanalyze))
return Task.CompletedTask;
return AnalyzeProjectAsync(document.Project, document, cancellationToken);
}
private async Task AnalyzeProjectAsync(Project project, Document? specificDocument, CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
return;
// We need to reanalyze the project whenever it (or any of its dependencies) have
// changed. We need to know about dependencies since if a downstream project adds the
// DesignerCategory attribute to a class, that can affect us when we examine the classes
// in this project.
var projectVersion = await project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
var latestInfos = await ComputeLatestInfosAsync(
project, projectVersion, specificDocument, cancellationToken).ConfigureAwait(false);
// Now get all the values that actually changed and notify VS about them. We don't need
// to tell it about the ones that didn't change since that will have no effect on the
// user experience.
//
// ! is safe here as `i.changed` implies `i.info` is non-null.
var changedInfos = latestInfos.Where(i => i.changed).Select(i => i.info!.Value).ToList();
if (changedInfos.Count > 0)
{
await _endPoint.InvokeAsync(
nameof(IDesignerAttributeListener.RegisterDesignerAttributesAsync),
new object[] { changedInfos },
cancellationToken).ConfigureAwait(false);
}
// now that we've notified VS, persist all the infos we have (changed or otherwise) back
// to disk. We want to do this even when the data is unchanged so that our version
// stamps will be correct for the next time we come around to analyze this project.
//
// Note: we have a potential race condition here. Specifically, for simplicity, the VS
// side will return immediately, without actually notifying the project system. That
// means that we could persist the data to local storage that isn't in sync with what
// the project system knows about. i.e. if VS is closed or crashes before that
// information is persisted, then these two systems will be in disagreement. this is
// believed to not be a big issue given how small a time window this would be and how
// easy it would be to get out of that state (just edit the file).
await PersistLatestInfosAsync(project.Solution, projectVersion, latestInfos, cancellationToken).ConfigureAwait(false);
}
private async Task PersistLatestInfosAsync(
Solution solution, VersionStamp projectVersion, (Document, DesignerInfo? info, bool changed)[] latestInfos, CancellationToken cancellationToken)
{
using var storage = _storageService.GetStorage(solution);
foreach (var (doc, info, _) in latestInfos)
{
// Skip documents that didn't change contents/version at all. No point in writing
// back out the exact same data as before.
if (info == null)
continue;
using var memoryStream = new MemoryStream();
using var writer = new ObjectWriter(memoryStream);
PersistInfoTo(writer, info.Value, projectVersion);
memoryStream.Position = 0;
await storage.WriteStreamAsync(
doc, DataKey, memoryStream, cancellationToken).ConfigureAwait(false);
}
}
private async Task<(Document, DesignerInfo? info, bool changed)[]> ComputeLatestInfosAsync(
Project project, VersionStamp projectVersion,
Document? specificDocument, CancellationToken cancellationToken)
{
using var storage = _storageService.GetStorage(project.Solution);
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
var designerCategoryType = compilation.DesignerCategoryAttributeType();
using var _ = ArrayBuilder<Task<(Document, DesignerInfo?, bool changed)>>.GetInstance(out var tasks);
foreach (var document in project.Documents)
{
// If we're only analyzing a specific document, then skip the rest.
if (specificDocument != null && document != specificDocument)
continue;
tasks.Add(ComputeDesignerAttributeInfoAsync(
storage, projectVersion, designerCategoryType, document, cancellationToken));
}
return await Task.WhenAll(tasks).ConfigureAwait(false);
}
private async Task<(Document, DesignerInfo?, bool changed)> ComputeDesignerAttributeInfoAsync(
IPersistentStorage storage, VersionStamp projectVersion, INamedTypeSymbol? designerCategoryType,
Document document, CancellationToken cancellationToken)
{
try
{
// If we don't have a path for this document, we cant proceed with it.
// We need that path to inform the project system which file we're referring to.
if (document.FilePath == null)
return default;
// First check and see if we have stored information for this doc and if that
// information is up to date.
using var stream = await storage.ReadStreamAsync(document, DataKey, cancellationToken).ConfigureAwait(false);
using var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken);
var persisted = TryReadPersistedInfo(reader);
if (persisted.category != null && persisted.projectVersion == projectVersion)
{
// We were able to read out the old data, and it matches our current project
// version. Just return back that nothing changed here. We won't tell VS about
// this, and we won't re-persist this later.
return default;
}
// We either haven't computed the designer info, or our data was out of date. We need
// So recompute here. Figure out what the current category is, and if that's different
// from what we previously stored.
var category = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync(
designerCategoryType, document, cancellationToken).ConfigureAwait(false);
var info = new DesignerInfo
{
Category = category,
DocumentId = document.Id,
FilePath = document.FilePath,
};
return (document, info, changed: category != persisted.category);
}
catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e))
{
return default;
}
}
}
}
// 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 Microsoft.CodeAnalysis.SolutionCrawler;
namespace Microsoft.CodeAnalysis.Remote
{
/// <remarks>Note: this is explicitly <b>not</b> exported. We don't want the <see
/// cref="RemoteWorkspace"/> to automatically load this. Instead, VS waits until it is ready
/// and then calls into OOP to tell it to start analyzing the solution. At that point we'll get
/// created and added to the solution crawler.
/// </remarks>
internal sealed class RemoteDesignerAttributeIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider
{
private readonly RemoteEndPoint _endPoint;
public RemoteDesignerAttributeIncrementalAnalyzerProvider(RemoteEndPoint endPoint)
{
_endPoint = endPoint;
}
public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
=> new RemoteDesignerAttributeIncrementalAnalyzer(workspace, _endPoint);
}
}
// 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 Microsoft.CodeAnalysis.DesignerAttribute;
using Microsoft.CodeAnalysis.ErrorReporting;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
internal partial class RemoteDesignerAttributeIncrementalAnalyzer
{
/// <summary>
/// Our current serialization format. Increment whenever it changes so that we don't read
/// bogus data when reading older persisted data.
/// </summary>
private const string SerializationFormat = "1";
private static (string? category, VersionStamp projectVersion) TryReadPersistedInfo(ObjectReader reader)
{
try
{
// if we couldn't get a reader then we have no persisted category/version to read out.
if (reader == null)
return default;
// check to make sure whatever we persisted matches our current format for this info
// type. If not, then we can't read this out.
var format = reader.ReadString();
if (format != SerializationFormat)
return default;
// Looks good, pull out the stored data.
var category = reader.ReadString();
var projectVersion = VersionStamp.ReadFrom(reader);
return (category, projectVersion);
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
// can happen if the DB got edited outside of our control.
return default;
}
}
private static void PersistInfoTo(ObjectWriter writer, DesignerInfo info, VersionStamp projectVersion)
{
writer.WriteString(SerializationFormat);
writer.WriteString(info.Category);
projectVersion.WriteTo(writer);
}
}
}
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.DesignerAttribute;
using Microsoft.CodeAnalysis.SolutionCrawler;
namespace Microsoft.CodeAnalysis.Remote
{
internal sealed class RemoteDesignerAttributeService : ServiceBase, IRemoteDesignerAttributeService
{
public RemoteDesignerAttributeService(
Stream stream, IServiceProvider serviceProvider)
: base(serviceProvider, stream)
{
StartService();
}
public Task StartScanningForDesignerAttributesAsync(CancellationToken cancellation)
{
return RunServiceAsync(() =>
{
var registrationService = SolutionService.PrimaryWorkspace.Services.GetRequiredService<ISolutionCrawlerRegistrationService>();
var analyzerProvider = new RemoteDesignerAttributeIncrementalAnalyzerProvider(this.EndPoint);
registrationService.AddAnalyzerProvider(
analyzerProvider,
new IncrementalAnalyzerProviderMetadata(
nameof(RemoteDesignerAttributeIncrementalAnalyzerProvider),
highPriorityForActiveFile: false,
workspaceKinds: WorkspaceKind.RemoteWorkspace));
return Task.CompletedTask;
}, cancellation);
}
}
}
......@@ -5,10 +5,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.DesignerAttributes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.DocumentHighlighting;
using Microsoft.CodeAnalysis.Packaging;
......@@ -29,7 +27,6 @@ internal partial class AggregateJsonConverter : JsonConverter
Add(builder, new TodoCommentDescriptorJsonConverter());
Add(builder, new TodoCommentJsonConverter());
Add(builder, new DesignerAttributeResultJsonConverter());
Add(builder, new PackageSourceJsonConverter());
Add(builder, new PackageWithTypeResultJsonConverter());
......@@ -105,39 +102,6 @@ protected override void WriteValue(JsonWriter writer, TodoComment todoComment, J
}
}
private class DesignerAttributeResultJsonConverter : BaseJsonConverter<DesignerAttributeResult>
{
protected override DesignerAttributeResult ReadValue(JsonReader reader, JsonSerializer serializer)
{
Contract.ThrowIfFalse(reader.TokenType == JsonToken.StartObject);
var designerAttributeArgument = ReadProperty<string>(reader);
var containsErrors = ReadProperty<bool>(reader);
var applicable = ReadProperty<bool>(reader);
Contract.ThrowIfFalse(reader.Read());
Contract.ThrowIfFalse(reader.TokenType == JsonToken.EndObject);
return new DesignerAttributeResult(designerAttributeArgument, containsErrors, applicable);
}
protected override void WriteValue(JsonWriter writer, DesignerAttributeResult result, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName(nameof(DesignerAttributeResult.DesignerAttributeArgument));
writer.WriteValue(result.DesignerAttributeArgument);
writer.WritePropertyName(nameof(DesignerAttributeResult.ContainsErrors));
writer.WriteValue(result.ContainsErrors);
writer.WritePropertyName(nameof(DesignerAttributeResult.Applicable));
writer.WriteValue(result.Applicable);
writer.WriteEndObject();
}
}
private class PackageSourceJsonConverter : BaseJsonConverter<PackageSource>
{
protected override PackageSource ReadValue(JsonReader reader, JsonSerializer serializer)
......
......@@ -870,6 +870,12 @@ public List<SyntaxNode> GetMethodLevelMembers(SyntaxNode root)
return list;
}
public bool IsClassDeclaration(SyntaxNode node)
=> node?.Kind() == SyntaxKind.ClassDeclaration;
public bool IsNamespaceDeclaration(SyntaxNode node)
=> node?.Kind() == SyntaxKind.NamespaceDeclaration;
public SyntaxList<SyntaxNode> GetMembersOfTypeDeclaration(SyntaxNode typeDeclaration)
=> ((TypeDeclarationSyntax)typeDeclaration).Members;
......
......@@ -347,6 +347,8 @@ internal partial interface ISyntaxFacts
SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false);
bool IsClassDeclaration(SyntaxNode node);
bool IsNamespaceDeclaration(SyntaxNode node);
List<SyntaxNode> GetMethodLevelMembers(SyntaxNode root);
SyntaxList<SyntaxNode> GetMembersOfTypeDeclaration(SyntaxNode typeDeclaration);
SyntaxList<SyntaxNode> GetMembersOfNamespaceDeclaration(SyntaxNode namespaceDeclaration);
......
......@@ -868,6 +868,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices
Return list
End Function
Public Function IsClassDeclaration(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsClassDeclaration
Return node.IsKind(SyntaxKind.ClassBlock)
End Function
Public Function IsNamespaceDeclaration(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsNamespaceDeclaration
Return node.IsKind(SyntaxKind.NamespaceBlock)
End Function
Public Function GetMembersOfTypeDeclaration(typeDeclaration As SyntaxNode) As SyntaxList(Of SyntaxNode) Implements ISyntaxFacts.GetMembersOfTypeDeclaration
Return DirectCast(typeDeclaration, TypeBlockSyntax).Members
End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册