Skip to content

Commit

Permalink
Add JsonRpcClassAttribute & RpcMethodNameTransformer (#5143)
Browse files Browse the repository at this point in the history
Fixes #5140
  • Loading branch information
1 parent f3a8b01 commit 403220e
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 10 deletions.
14 changes: 10 additions & 4 deletions src/SLCore.UnitTests/Core/SLCoreJsonRpcTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System;
using System.Threading.Tasks;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Protocol;
using SonarLint.VisualStudio.SLCore.UnitTests.Helpers;
using StreamJsonRpc;

Expand Down Expand Up @@ -80,7 +81,10 @@ public void IsAlive_TaskThrows_NoException()
[TestMethod]
public void CreateService_CallsAttachWithCorrectOptions()
{
var testSubject = CreateTestSubject(out var clientMock, out _);
var methodNameTransformerMock = new Mock<IRpcMethodNameTransformer>();
Func<string, string> transformer = s => s;
methodNameTransformerMock.Setup(x => x.Create<ITestSLCoreService>()).Returns(transformer);
var testSubject = CreateTestSubject(out var clientMock, out _, methodNameTransformerMock.Object);
var service = Mock.Of<ITestSLCoreService>();
clientMock.Setup(x => x.Attach<ITestSLCoreService>(It.IsAny<JsonRpcProxyOptions>())).Returns(service);

Expand All @@ -89,7 +93,7 @@ public void CreateService_CallsAttachWithCorrectOptions()
createdService.Should().BeSameAs(service);
clientMock.Verify(x =>
x.Attach<ITestSLCoreService>(It.Is<JsonRpcProxyOptions>(options =>
options.MethodNameTransform == CommonMethodNameTransforms.CamelCase)), // todo: https://github.com/SonarSource/sonarlint-visualstudio/issues/5140
options.MethodNameTransform == transformer)),
Times.Once);
clientMock.VerifyNoOtherCalls();
}
Expand All @@ -109,10 +113,12 @@ public void AttachListener_CallsAddLocalRpcTargetWithCorrectOptions()
clientMock.VerifyNoOtherCalls();
}

private static SLCoreJsonRpc CreateTestSubject(out Mock<IJsonRpc> clientMock, out TaskCompletionSource<bool> clientCompletionSource)
private static SLCoreJsonRpc CreateTestSubject(out Mock<IJsonRpc> clientMock,
out TaskCompletionSource<bool> clientCompletionSource,
IRpcMethodNameTransformer methodNameTransformer = null)
{
(clientMock, clientCompletionSource) = TestJsonRpcFactory.Create();
return new SLCoreJsonRpc(clientMock.Object);
return new SLCoreJsonRpc(clientMock.Object, methodNameTransformer);
}

public interface ITestSLCoreService : ISLCoreService {}
Expand Down
79 changes: 79 additions & 0 deletions src/SLCore.UnitTests/Protocol/RpcMethodNameTransformerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Protocol;
using SonarLint.VisualStudio.TestInfrastructure;
using StreamJsonRpc;

namespace SonarLint.VisualStudio.SLCore.UnitTests.Protocol;

[TestClass]
public class RpcMethodNameTransformerTests
{
private const string Prefix = "prefix";

[TestMethod]
public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<RpcMethodNameTransformer, IRpcMethodNameTransformer>();
}

[TestMethod]
public void Mef_CheckIsSingleton()
{
MefTestHelpers.CheckIsSingletonMefComponent<RpcMethodNameTransformer>();
}

[TestMethod]
public void Create_NoAttribute_CreatesCamelCaseTransformer()
{
var testSubject = CreateTestSubject();

var transformer = testSubject.Create<ITestSLCoreServiceNoAttribute>();

transformer.Should().BeSameAs(CommonMethodNameTransforms.CamelCase);
}

[DataTestMethod]
[DataRow("Method", $"{Prefix}/method")]
[DataRow("MyMethod", $"{Prefix}/myMethod")]
[DataRow("MyLovelyMethod", $"{Prefix}/myLovelyMethod")]
[DataRow("myLovelyMethod", $"{Prefix}/myLovelyMethod")]
[DataRow("mylovelymethod", $"{Prefix}/mylovelymethod")]
public void Create_NoAttribute_CreatesCompositeTransformer(string methodName, string expectedMethodName)
{
var testSubject = CreateTestSubject();

var transformer = testSubject.Create<ITestSLCoreServiceWithAttribute>();

transformer(methodName).Should().Be(expectedMethodName);
}

private RpcMethodNameTransformer CreateTestSubject()
{
return new RpcMethodNameTransformer();
}

public interface ITestSLCoreServiceNoAttribute : ISLCoreService {}

[JsonRpcClass(Prefix)]
public interface ITestSLCoreServiceWithAttribute : ISLCoreService {}
}
12 changes: 6 additions & 6 deletions src/SLCore/Core/ISLCoreJsonRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using SonarLint.VisualStudio.SLCore.Protocol;
using StreamJsonRpc;

namespace SonarLint.VisualStudio.SLCore.Core
Expand All @@ -40,17 +38,19 @@ public interface ISLCoreJsonRpc
internal class SLCoreJsonRpc : ISLCoreJsonRpc
{
private readonly IJsonRpc rpc;
private readonly IRpcMethodNameTransformer methodNameTransformer;

public SLCoreJsonRpc(IJsonRpc jsonRpc)
public SLCoreJsonRpc(IJsonRpc jsonRpc, IRpcMethodNameTransformer methodNameTransformer)
{
rpc = jsonRpc;
this.methodNameTransformer = methodNameTransformer;
}

public TService CreateService<TService>() where TService : class, ISLCoreService =>
rpc.Attach<TService>(new JsonRpcProxyOptions
{
MethodNameTransform = CommonMethodNameTransforms.CamelCase
}); // todo: https://github.com/SonarSource/sonarlint-visualstudio/issues/5140
MethodNameTransform = methodNameTransformer.Create<TService>()
});

public void AttachListener(ISLCoreListener listener) =>
rpc.AddLocalRpcTarget(listener,
Expand Down
39 changes: 39 additions & 0 deletions src/SLCore/Protocol/JsonRpcClassAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;

namespace SonarLint.VisualStudio.SLCore.Protocol
{
/// <summary>
/// This attribute is used to tell <see cref="IRpcMethodNameTransformer"/> which prefix to use
/// for this type's methods when registering them with the RPC.
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class JsonRpcClassAttribute : Attribute
{
public JsonRpcClassAttribute(string prefix)
{
Prefix = prefix;
}

public string Prefix { get; }
}
}
61 changes: 61 additions & 0 deletions src/SLCore/Protocol/RpcMethodNameTransformer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.ComponentModel.Composition;
using System.Linq;
using StreamJsonRpc;

namespace SonarLint.VisualStudio.SLCore.Protocol
{
/// <summary>
/// Method name transformer for JsonRPC types.
/// </summary>
public interface IRpcMethodNameTransformer
{
/// <summary>
/// Creates method name transformer (<see cref="JsonRpcProxyOptions.MethodNameTransform"/>) for the type.
/// </summary>
/// <returns>
/// <typeparam name="TRpcType">Type of the RPC entity</typeparam>
/// If <typeparamref name="TRpcType"/> is marked with <see cref="JsonRpcClassAttribute"/>, returns a composition of
/// <see cref="CommonMethodNameTransforms.Prepend"/> with parameter <see cref="JsonRpcClassAttribute.Prefix"/> and <see cref="CommonMethodNameTransforms.CamelCase"/>.
/// If not marked, returns <see cref="CommonMethodNameTransforms.CamelCase"/>
/// </returns>
Func<string, string> Create<TRpcType>();
}

[Export(typeof(IRpcMethodNameTransformer))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class RpcMethodNameTransformer : IRpcMethodNameTransformer
{
public Func<string, string> Create<T>()
{
if (!(typeof(T).GetCustomAttributes(typeof(JsonRpcClassAttribute), false).FirstOrDefault() is JsonRpcClassAttribute attribute))
{
return CommonMethodNameTransforms.CamelCase;
}

var prependTransform = CommonMethodNameTransforms.Prepend($"{attribute.Prefix}/");

return name => prependTransform(CommonMethodNameTransforms.CamelCase(name));
}
}
}

0 comments on commit 403220e

Please sign in to comment.