diff --git a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj index d9892eaf778175bb55943903b732dd5ebefbf77f..93a8f4abd11d61368e062d1f92d09102b022501f 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj @@ -153,7 +153,6 @@ - diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index a8db573e96ccc449b59a2785d7434d607fd990a7..0b31331add1bfb65f18a77a30d40e83df290a343 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -614,8 +614,6 @@ - - diff --git a/src/EditorFeatures/Core/Implementation/TodoComment/AbstractTodoCommentIncrementalAnalyzer.cs b/src/EditorFeatures/Core/Implementation/TodoComment/AbstractTodoCommentIncrementalAnalyzer.cs index e6c1af57a47eefe7dc6bccd44164c57db29539b8..f3000702c7f5977c7c37c8b96877d4feee2663a4 100644 --- a/src/EditorFeatures/Core/Implementation/TodoComment/AbstractTodoCommentIncrementalAnalyzer.cs +++ b/src/EditorFeatures/Core/Implementation/TodoComment/AbstractTodoCommentIncrementalAnalyzer.cs @@ -8,9 +8,11 @@ using Microsoft.CodeAnalysis.Common; using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.TodoComments; using Microsoft.CodeAnalysis.Versions; using Roslyn.Utilities; @@ -70,14 +72,8 @@ public async Task AnalyzeSyntaxAsync(Document document, InvocationReasons reason } } - var service = document.GetLanguageService(); - if (service == null) - { - return; - } - var tokens = _todoCommentTokens.GetTokens(document, cancellationToken); - var comments = await service.GetTodoCommentsAsync(document, tokens, cancellationToken).ConfigureAwait(false); + var comments = await GetTodoCommentsAsync(document, tokens, cancellationToken).ConfigureAwait(false); var items = await CreateItemsAsync(document, comments, cancellationToken).ConfigureAwait(false); var data = new Data(textVersion, syntaxVersion, items); @@ -91,9 +87,31 @@ public async Task AnalyzeSyntaxAsync(Document document, InvocationReasons reason } } + private async Task> GetTodoCommentsAsync(Document document, ImmutableArray tokens, CancellationToken cancellationToken) + { + var client = await _workspace.GetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false); + if (client != null && !document.IsOpen()) + { + // run todo scanner on remote host + return await client.RunCodeAnalysisServiceOnRemoteHostAsync>( + document.Project.Solution, nameof(IRemoteTodoCommentService.GetTodoCommentsAsync), + new object[] { document.Id, tokens }, cancellationToken).ConfigureAwait(false); + } + + // No remote host support, use inproc service + var service = document.GetLanguageService(); + if (service == null) + { + // no inproc support + return SpecializedCollections.EmptyList(); + } + + return await service.GetTodoCommentsAsync(document, tokens, cancellationToken).ConfigureAwait(false); + } + private async Task> CreateItemsAsync(Document document, IList comments, CancellationToken cancellationToken) { - var items = ArrayBuilder.GetInstance(); + var items = ImmutableArray.CreateBuilder(); if (comments != null) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -105,7 +123,7 @@ private async Task> CreateItemsAsync(Document document, } } - return items.ToImmutableAndFree(); + return items.ToImmutable(); } private TodoItem CreateItem(Document document, SourceText text, SyntaxTree tree, TodoComment comment) diff --git a/src/EditorFeatures/Core/Implementation/TodoComment/TodoCommentTokens.cs b/src/EditorFeatures/Core/Implementation/TodoComment/TodoCommentTokens.cs index 655dc35b5f19daf60a969f0243c7069be124961a..2a26c0495e73aec4a79409ece1c80163d26753a0 100644 --- a/src/EditorFeatures/Core/Implementation/TodoComment/TodoCommentTokens.cs +++ b/src/EditorFeatures/Core/Implementation/TodoComment/TodoCommentTokens.cs @@ -5,8 +5,7 @@ using System.ComponentModel.Composition; using System.Globalization; using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.TodoComments; namespace Microsoft.CodeAnalysis.Editor.Implementation.TodoComments { diff --git a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj index 9dc4dbbdf5aa089e3fc0b7c40f4496e51b8f7578..5eea970d188be5dcbdcaeaab5f93790bb0aa9a45 100644 --- a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj +++ b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj @@ -160,7 +160,6 @@ - diff --git a/src/Features/CSharp/Portable/CSharpFeatures.csproj b/src/Features/CSharp/Portable/CSharpFeatures.csproj index 488f4839abb7b280e069476da62bdede4c1b1207..d78f6f8ab6bc45a63ca47c5c17c1fd07e522144f 100644 --- a/src/Features/CSharp/Portable/CSharpFeatures.csproj +++ b/src/Features/CSharp/Portable/CSharpFeatures.csproj @@ -79,6 +79,7 @@ + diff --git a/src/EditorFeatures/CSharp/TodoComment/CSharpTodoCommentIncrementalAnalyzerProvider.cs b/src/Features/CSharp/Portable/TodoComments/CSharpTodoCommentIncrementalAnalyzerProvider.cs similarity index 95% rename from src/EditorFeatures/CSharp/TodoComment/CSharpTodoCommentIncrementalAnalyzerProvider.cs rename to src/Features/CSharp/Portable/TodoComments/CSharpTodoCommentIncrementalAnalyzerProvider.cs index e206bf4c827ef18a3fd9bb696f768f670e8d604f..fb303e71df7a76577da65765c4229544b049cb09 100644 --- a/src/EditorFeatures/CSharp/TodoComment/CSharpTodoCommentIncrementalAnalyzerProvider.cs +++ b/src/Features/CSharp/Portable/TodoComments/CSharpTodoCommentIncrementalAnalyzerProvider.cs @@ -4,13 +4,12 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.Editor.Implementation.TodoComments; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.TodoComments; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.CSharp.TodoComments +namespace Microsoft.CodeAnalysis.CSharp.TodoComments { [ExportLanguageService(typeof(ITodoCommentService), LanguageNames.CSharp), Shared] internal class CSharpTodoCommentService : AbstractTodoCommentService diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 0554390b4b5583aabdd240d395ed2b26deb6ccb1..5890983de9a271e752764b66a7837c023333e48c 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -157,6 +157,9 @@ + + + diff --git a/src/EditorFeatures/Core/Implementation/TodoComment/AbstractTodoCommentService.cs b/src/Features/Core/Portable/TodoComments/AbstractTodoCommentService.cs similarity index 98% rename from src/EditorFeatures/Core/Implementation/TodoComment/AbstractTodoCommentService.cs rename to src/Features/Core/Portable/TodoComments/AbstractTodoCommentService.cs index 013759ec9ee0e3c7a29b4f90dc8f47803f79e412..14a19b9b11fef3f31b60db9d3832ba173b0a49b6 100644 --- a/src/EditorFeatures/Core/Implementation/TodoComment/AbstractTodoCommentService.cs +++ b/src/Features/Core/Portable/TodoComments/AbstractTodoCommentService.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; -namespace Microsoft.CodeAnalysis.Editor.Implementation.TodoComments +namespace Microsoft.CodeAnalysis.TodoComments { internal abstract class AbstractTodoCommentService : ITodoCommentService { diff --git a/src/Features/Core/Portable/TodoComments/IRemoteTodoCommentService.cs b/src/Features/Core/Portable/TodoComments/IRemoteTodoCommentService.cs new file mode 100644 index 0000000000000000000000000000000000000000..3a940b4ed89c0ccdffb0655ef4442d9fb9d1f782 --- /dev/null +++ b/src/Features/Core/Portable/TodoComments/IRemoteTodoCommentService.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.TodoComments +{ + /// + /// interface exist to strongly type todo comment remote service + /// + internal interface IRemoteTodoCommentService + { + Task> GetTodoCommentsAsync(DocumentId documentId, ImmutableArray commentDescriptors); + } +} diff --git a/src/EditorFeatures/Core/Implementation/TodoComment/ITodoCommentService.cs b/src/Features/Core/Portable/TodoComments/ITodoCommentService.cs similarity index 97% rename from src/EditorFeatures/Core/Implementation/TodoComment/ITodoCommentService.cs rename to src/Features/Core/Portable/TodoComments/ITodoCommentService.cs index 8588bdd791eadc50b0e5a66365d79df54d237b23..42b830a88132547c8cb54efc4350cf3159cc4bee 100644 --- a/src/EditorFeatures/Core/Implementation/TodoComment/ITodoCommentService.cs +++ b/src/Features/Core/Portable/TodoComments/ITodoCommentService.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; -namespace Microsoft.CodeAnalysis.Editor +namespace Microsoft.CodeAnalysis.TodoComments { /// /// Description of a TODO comment type to find in a user's comments. diff --git a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj index c6e67780071ac273f8fcaecc2cce9f788c942410..4cd06241128ceceda7464782c6ded7c0b3f6c6ef 100644 --- a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj +++ b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj @@ -418,6 +418,7 @@ + diff --git a/src/EditorFeatures/VisualBasic/TodoComment/BasicTodoCommentIncrementalAnalyzerProvider.vb b/src/Features/VisualBasic/Portable/TodoComments/BasicTodoCommentIncrementalAnalyzerProvider.vb similarity index 97% rename from src/EditorFeatures/VisualBasic/TodoComment/BasicTodoCommentIncrementalAnalyzerProvider.vb rename to src/Features/VisualBasic/Portable/TodoComments/BasicTodoCommentIncrementalAnalyzerProvider.vb index b848a17378cddff40b0196da8307c9c0ed1e6746..9624ec46e2f39c73bdab74773f4c5bfdb1883c2a 100644 --- a/src/EditorFeatures/VisualBasic/TodoComment/BasicTodoCommentIncrementalAnalyzerProvider.vb +++ b/src/Features/VisualBasic/Portable/TodoComments/BasicTodoCommentIncrementalAnalyzerProvider.vb @@ -3,10 +3,10 @@ Imports System.Collections.Immutable Imports System.Composition Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Editor.Implementation.TodoComments Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.TodoComments -Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.TodoComments +Namespace Microsoft.CodeAnalysis.VisualBasic.TodoComments Friend Class VisualBasicTodoCommentService Inherits AbstractTodoCommentService diff --git a/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj b/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj index d60383b68a0fff51bbfa6d8b47c671328d230c0b..ecc7824c3207deb0e9174eae85a4245060b92ef1 100644 --- a/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj +++ b/src/VisualStudio/Core/Next/ServicesVisualStudio.Next.csproj @@ -79,6 +79,9 @@ Shared\RoslynJsonConverter.cs + + Shared\RoslynJsonConverter.RoslynOnly.cs + Shared\RoslynJsonConverter.SolutionIdConverters.cs diff --git a/src/VisualStudio/Core/Test.Next/Remote/JsonConverterTests.cs b/src/VisualStudio/Core/Test.Next/Remote/JsonConverterTests.cs index ea51a391b655492a55af44f2d438e82d00246c2e..c9c7a49fc11a0f26466d40bc9d3299f4b1167ab8 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/JsonConverterTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/JsonConverterTests.cs @@ -3,12 +3,14 @@ extern alias hub; using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.TodoComments; using Newtonsoft.Json; using Roslyn.Test.Utilities; using Xunit; @@ -82,6 +84,38 @@ public void TestSymbolKey() VerifyJsonSerialization(new SymbolKey("TEST")); } + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public void TestTodoCommentDescriptor() + { + VerifyJsonSerialization(new TodoCommentDescriptor("Test", 0)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public void TestTodoComment() + { + VerifyJsonSerialization(new TodoComment(new TodoCommentDescriptor("Test", 1), "Message", 10)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public void TestTodoCommentDescriptorImmutableArray() + { + VerifyJsonSerialization(ImmutableArray.Create(new TodoCommentDescriptor("Test", 0), new TodoCommentDescriptor("Test1", 1)), (x, y) => + { + return x.SequenceEqual(y) ? 0 : 1; + }); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public void TestTodoCommentList() + { + VerifyJsonSerialization(new[] { + new TodoComment(new TodoCommentDescriptor("Test1", 1), "Message1", 10), + new TodoComment(new TodoCommentDescriptor("Test2", 2), "Message2", 20)}.ToList(), (x, y) => + { + return x.SequenceEqual(y) ? 0 : 1; + }); + } + private static void VerifyJsonSerialization(T value, Comparison equality = null) { var serializer = new JsonSerializer(); diff --git a/src/Workspaces/Core/Portable/Log/FunctionId.cs b/src/Workspaces/Core/Portable/Log/FunctionId.cs index ff0e167914b4ac35c8f4aa3be4e8df199540d587..4110d11d04dea51c88c6c26290fdd861429bfe8e 100644 --- a/src/Workspaces/Core/Portable/Log/FunctionId.cs +++ b/src/Workspaces/Core/Portable/Log/FunctionId.cs @@ -364,5 +364,6 @@ internal enum FunctionId FileTextLoader_FileLengthThresholdExceeded, RemoteHost_Connect, RemoteHost_Disconnect, + CodeAnalysisService_GetTodoCommentsAsync, } } diff --git a/src/Workspaces/Remote/ServiceHub/ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/ServiceHub.csproj index 5fef77051717c3b590659ec3126d2d25132b5417..e89708d8936ef5e5f5c4414f5ad051df18e44bef 100644 --- a/src/Workspaces/Remote/ServiceHub/ServiceHub.csproj +++ b/src/Workspaces/Remote/ServiceHub/ServiceHub.csproj @@ -56,6 +56,7 @@ + @@ -63,6 +64,7 @@ + diff --git a/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_TodoComments.cs b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_TodoComments.cs new file mode 100644 index 0000000000000000000000000000000000000000..9261c248f9f8d06200371a7dcf51987b9e5a2db4 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/CodeAnalysisService_TodoComments.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.TodoComments; +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 : IRemoteTodoCommentService + { + /// + /// This is top level entry point for TodoComments service from client (VS). + /// + /// This will be called by ServiceHub/JsonRpc framework + /// + public async Task> GetTodoCommentsAsync(DocumentId documentId, ImmutableArray tokens) + { + using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_GetTodoCommentsAsync, documentId.ProjectId.DebugName, CancellationToken)) + { + try + { + var solution = await GetSolutionAsync().ConfigureAwait(false); + var document = solution.GetDocument(documentId); + + var service = document.GetLanguageService(); + if (service != null) + { + // todo comment service supported + return await service.GetTodoCommentsAsync(document, tokens, CancellationToken).ConfigureAwait(false); + } + } + catch (IOException) + { + // stream to send over result has closed before we + // had chance to check cancellation + } + catch (OperationCanceledException) + { + // rpc connection has closed. + // this can happen if client side cancelled the + // operation + } + + return SpecializedCollections.EmptyList(); + } + } + } +} \ No newline at end of file diff --git a/src/Workspaces/Remote/ServiceHub/Shared/RoslynJsonConverter.RoslynOnly.cs b/src/Workspaces/Remote/ServiceHub/Shared/RoslynJsonConverter.RoslynOnly.cs new file mode 100644 index 0000000000000000000000000000000000000000..a87bfc1da61737a2850e85c7ca37f11c51e987cb --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Shared/RoslynJsonConverter.RoslynOnly.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.TodoComments; +using Newtonsoft.Json; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal partial class AggregateJsonConverter : JsonConverter + { + partial void AppendRoslynSpecificJsonConverters(ImmutableDictionary.Builder builder) + { + builder.Add(typeof(TodoCommentDescriptor), new TodoCommentDescriptorJsonConverter()); + builder.Add(typeof(TodoComment), new TodoCommentJsonConverter()); + } + + private class TodoCommentDescriptorJsonConverter : BaseJsonConverter + { + public override bool CanConvert(Type objectType) => typeof(TodoCommentDescriptor) == objectType; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + Contract.ThrowIfFalse(reader.TokenType == JsonToken.StartObject); + + // all integer is long + var text = ReadProperty(reader); + var priority = ReadProperty(reader); + + Contract.ThrowIfFalse(reader.Read()); + Contract.ThrowIfFalse(reader.TokenType == JsonToken.EndObject); + + return new TodoCommentDescriptor(text, (int)priority); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var descriptor = (TodoCommentDescriptor)value; + + writer.WriteStartObject(); + + writer.WritePropertyName("text"); + writer.WriteValue(descriptor.Text); + + writer.WritePropertyName("priority"); + writer.WriteValue(descriptor.Priority); + + writer.WriteEndObject(); + } + } + + private class TodoCommentJsonConverter : BaseJsonConverter + { + public override bool CanConvert(Type objectType) => typeof(TodoComment) == objectType; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + Contract.ThrowIfFalse(reader.TokenType == JsonToken.StartObject); + + // all integer is long + var descriptor = ReadProperty(serializer, reader); + var message = ReadProperty(reader); + var position = ReadProperty(reader); + + Contract.ThrowIfFalse(reader.Read()); + Contract.ThrowIfFalse(reader.TokenType == JsonToken.EndObject); + + return new TodoComment(descriptor, message, (int)position); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var todoComment = (TodoComment)value; + + writer.WriteStartObject(); + + writer.WritePropertyName("descriptor"); + serializer.Serialize(writer, todoComment.Descriptor); + + writer.WritePropertyName("message"); + writer.WriteValue(todoComment.Message); + + writer.WritePropertyName("position"); + writer.WriteValue(todoComment.Position); + + writer.WriteEndObject(); + } + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Shared/RoslynJsonConverter.cs b/src/Workspaces/Remote/ServiceHub/Shared/RoslynJsonConverter.cs index f42d493e0c3082da2ef7ae55335caa3525217fb8..372cc037f2766aeaa6e8f9ba7175d260a07033f6 100644 --- a/src/Workspaces/Remote/ServiceHub/Shared/RoslynJsonConverter.cs +++ b/src/Workspaces/Remote/ServiceHub/Shared/RoslynJsonConverter.cs @@ -34,6 +34,11 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s _map[value.GetType()].WriteJson(writer, value, serializer); } + // this type is shared by multiple teams such as Razor, LUT and etc which have either separated/shared/shim repo + // so some types might not available to those context. this partial method let us add Roslyn specific types + // without breaking them + partial void AppendRoslynSpecificJsonConverters(ImmutableDictionary.Builder builder); + private ImmutableDictionary CreateConverterMap() { var builder = ImmutableDictionary.CreateBuilder(); @@ -45,6 +50,8 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s builder.Add(typeof(TextSpan), new TextSpanJsonConverter()); builder.Add(typeof(SymbolKey), new SymbolKeyJsonConverter()); + AppendRoslynSpecificJsonConverters(builder); + return builder.ToImmutable(); }