Skip to content
2 changes: 1 addition & 1 deletion src/EmbeddedSonarAnalyzer.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<EmbeddedSonarSecretsJarVersion>2.27.0.7705</EmbeddedSonarSecretsJarVersion>
<EmbeddedSonarSqvsRoslynJarVersion>1.0.0.0</EmbeddedSonarSqvsRoslynJarVersion>
<!-- SLOOP: Binaries for SonarLint Out Of Process -->
<EmbeddedSloopVersion>10.28.0.82098</EmbeddedSloopVersion>
<EmbeddedSloopVersion>10.29.0.82181</EmbeddedSloopVersion>
<EmbeddedEsLintBridgeVersion>1.0.0</EmbeddedEsLintBridgeVersion>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@

using System.IO;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.CSharpVB;
using SonarLint.VisualStudio.Core.SystemAbstractions;
using SonarLint.VisualStudio.Integration.Vsix.EmbeddedAnalyzers;
using SonarLint.VisualStudio.Integration.Vsix.Helpers;
using SonarLint.VisualStudio.RoslynAnalyzerServer.Analysis.Configuration;
using SonarLint.VisualStudio.RoslynAnalyzerServer.Http.Models;

namespace SonarLint.VisualStudio.Integration.UnitTests.EmbeddedAnalyzers;

Expand All @@ -37,36 +37,27 @@ public class EmbeddedDotnetAnalyzersLocatorTests
private static readonly string VbRegularAnalyzer = GetAnalyzerFullPath(PathInsideVsix, "SonarAnalyzer.VisualBasic.dll");
private static readonly string CSharpEnterpriseAnalyzer = GetAnalyzerFullPath(PathInsideVsix, "SonarAnalyzer.Enterprise.CSharp.dll");
private static readonly string VbEnterpriseAnalyzer = GetAnalyzerFullPath(PathInsideVsix, "SonarAnalyzer.Enterprise.VisualBasic.dll");
private IFileSystemService fileSystem;

private EmbeddedDotnetAnalyzersLocator testSubject;
private IVsixRootLocator vsixRootLocator;
private IFileSystemService fileSystem;
private ILanguageProvider languageProvider;

[TestInitialize]
public void TestInitialize()
{
vsixRootLocator = Substitute.For<IVsixRootLocator>();
languageProvider = Substitute.For<ILanguageProvider>();
languageProvider.RoslynLanguages.Returns([Language.CSharp, Language.VBNET]);
fileSystem = Substitute.For<IFileSystemService>();
testSubject = new EmbeddedDotnetAnalyzersLocator(vsixRootLocator, languageProvider, fileSystem);
testSubject = new EmbeddedDotnetAnalyzersLocator(vsixRootLocator, fileSystem);
}

[TestMethod]
public void MefCtor_CheckIsExported()
{
public void MefCtor_CheckIsExported() =>
MefTestHelpers.CheckTypeCanBeImported<EmbeddedDotnetAnalyzersLocator, IEmbeddedDotnetAnalyzersLocator>(
MefTestHelpers.CreateExport<IVsixRootLocator>(),
MefTestHelpers.CreateExport<ILanguageProvider>(),
MefTestHelpers.CreateExport<IFileSystemService>());
}

[TestMethod]
public void MefCtor_IsSingleton()
{
MefTestHelpers.CheckIsSingletonMefComponent<EmbeddedDotnetAnalyzersLocator>();
}
public void MefCtor_IsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<EmbeddedDotnetAnalyzersLocator>();

[TestMethod]
public void GetBasicAnalyzerFullPaths_AnalyzersExists_ReturnsFullPathsToAnalyzers()
Expand Down Expand Up @@ -149,30 +140,21 @@ public void GetAnalyzerFullPaths_SearchesForFilesInsideVsix()
}

[TestMethod]
public void GetBasicAnalyzerFullPathsByLanguage_GroupsDllsByLanguageAndFiltersEnterprise()
public void GetAnalyzerFullPathsByLanguage_BothEnterprise_GroupsEnterpriseDllsByLanguage()
{
fileSystem.Directory.GetFiles(Arg.Any<string>(), Arg.Any<string>()).Returns([
CSharpRegularAnalyzer,
VbRegularAnalyzer,
CSharpEnterpriseAnalyzer
CSharpEnterpriseAnalyzer,
VbEnterpriseAnalyzer
]);

testSubject.GetBasicAnalyzerFullPathsByLanguage().Should().BeEquivalentTo(new Dictionary<RoslynLanguage, List<string>>
{
[Language.CSharp] = [CSharpRegularAnalyzer], [Language.VBNET] = [VbRegularAnalyzer]
});
testSubject.GetAnalyzerFullPathsByLanguage(new AnalyzerInfoDto(true, true)).Should().BeEquivalentTo(
new Dictionary<RoslynLanguage, List<string>> { [Language.CSharp] = [CSharpRegularAnalyzer, CSharpEnterpriseAnalyzer], [Language.VBNET] = [VbRegularAnalyzer, VbEnterpriseAnalyzer] });
}

[TestMethod]
public void GetBasicAnalyzerFullPathsByLanguage_IncludesAllLanguagesEvenWithNoAnalyzers()
{
fileSystem.Directory.GetFiles(Arg.Any<string>(), Arg.Any<string>()).Returns([CSharpRegularAnalyzer]);

testSubject.GetBasicAnalyzerFullPathsByLanguage().Should().BeEquivalentTo(new Dictionary<RoslynLanguage, List<string>> { [Language.CSharp] = [CSharpRegularAnalyzer], [Language.VBNET] = [] });
}

[TestMethod]
public void GetEnterpriseAnalyzerFullPathsByLanguage_GroupsDllsByLanguageIncludingEnterprise()
public void GetAnalyzerFullPathsByLanguage_BothBasic_GroupsBasicDllsByLanguage()
{
fileSystem.Directory.GetFiles(Arg.Any<string>(), Arg.Any<string>()).Returns([
CSharpRegularAnalyzer,
Expand All @@ -181,40 +163,37 @@ public void GetEnterpriseAnalyzerFullPathsByLanguage_GroupsDllsByLanguageIncludi
VbEnterpriseAnalyzer
]);

testSubject.GetEnterpriseAnalyzerFullPathsByLanguage().Should().BeEquivalentTo(new Dictionary<RoslynLanguage, List<string>>
{
[Language.CSharp] = [CSharpRegularAnalyzer, CSharpEnterpriseAnalyzer], [Language.VBNET] = [VbRegularAnalyzer, VbEnterpriseAnalyzer]
});
testSubject.GetAnalyzerFullPathsByLanguage(new AnalyzerInfoDto(false, false)).Should().BeEquivalentTo(
new Dictionary<RoslynLanguage, List<string>> { [Language.CSharp] = [CSharpRegularAnalyzer], [Language.VBNET] = [VbRegularAnalyzer] });
}

[TestMethod]
public void GetEnterpriseAnalyzerFullPathsByLanguage_IncludesAllLanguagesEvenWithNoAnalyzers()
public void GetAnalyzerFullPathsByLanguage_OnlyCsharpEnterprise_GroupsDllsByLanguage()
{
fileSystem.Directory.GetFiles(Arg.Any<string>(), Arg.Any<string>()).Returns([VbEnterpriseAnalyzer]);
fileSystem.Directory.GetFiles(Arg.Any<string>(), Arg.Any<string>()).Returns([
CSharpRegularAnalyzer,
VbRegularAnalyzer,
CSharpEnterpriseAnalyzer,
VbEnterpriseAnalyzer
]);

testSubject.GetEnterpriseAnalyzerFullPathsByLanguage().Should().BeEquivalentTo(new Dictionary<RoslynLanguage, List<string>> { [Language.CSharp] = [], [Language.VBNET] = [VbEnterpriseAnalyzer] });
testSubject.GetAnalyzerFullPathsByLanguage(new AnalyzerInfoDto(true, false)).Should().BeEquivalentTo(
new Dictionary<RoslynLanguage, List<string>> { [Language.CSharp] = [CSharpRegularAnalyzer, CSharpEnterpriseAnalyzer], [Language.VBNET] = [VbRegularAnalyzer] });
}

[TestMethod]
public void GetEnterpriseAnalyzerFullPathsByLanguage_ExcludesLanguagesNotInRoslynLanguages()
public void GetAnalyzerFullPathsByLanguage_OnlyVbEnterprise_GroupsDllsByLanguage()
{
fileSystem.Directory.GetFiles(Arg.Any<string>(), Arg.Any<string>()).Returns([
CSharpRegularAnalyzer,
VbRegularAnalyzer,
CSharpEnterpriseAnalyzer,
VbEnterpriseAnalyzer
]);
// Only C# is in the Roslyn languages, VB.NET is not
languageProvider.RoslynLanguages.Returns([Language.CSharp]);

testSubject.GetEnterpriseAnalyzerFullPathsByLanguage().Should().BeEquivalentTo(new Dictionary<RoslynLanguage, List<string>>
{
[Language.CSharp] = [CSharpRegularAnalyzer, CSharpEnterpriseAnalyzer]
});
testSubject.GetAnalyzerFullPathsByLanguage(new AnalyzerInfoDto(false, true)).Should().BeEquivalentTo(
new Dictionary<RoslynLanguage, List<string>> { [Language.CSharp] = [CSharpRegularAnalyzer], [Language.VBNET] = [VbRegularAnalyzer, VbEnterpriseAnalyzer] });
}

private static string GetAnalyzerFullPath(string pathInsideVsix, string analyzerFile)
{
return Path.Combine(pathInsideVsix, analyzerFile);
}
private static string GetAnalyzerFullPath(string pathInsideVsix, string analyzerFile) => Path.Combine(pathInsideVsix, analyzerFile);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@
using System.IO;
using System.IO.Abstractions;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.CSharpVB;
using SonarLint.VisualStudio.Core.SystemAbstractions;
using SonarLint.VisualStudio.Infrastructure.VS.Roslyn;
using SonarLint.VisualStudio.Integration.Vsix.Helpers;
using SonarLint.VisualStudio.RoslynAnalyzerServer.Analysis.Configuration;
using SonarLint.VisualStudio.RoslynAnalyzerServer.Http.Models;

namespace SonarLint.VisualStudio.Integration.Vsix.EmbeddedAnalyzers;

[Export(typeof(IEmbeddedDotnetAnalyzersLocator))]
[Export(typeof(IObsoleteDotnetAnalyzersLocator))]
[PartCreationPolicy(CreationPolicy.Shared)]
[method: ImportingConstructor]
internal class EmbeddedDotnetAnalyzersLocator(IVsixRootLocator vsixRootLocator, ILanguageProvider languageProvider, IFileSystemService fileSystem) : IEmbeddedDotnetAnalyzersLocator, IObsoleteDotnetAnalyzersLocator
internal class EmbeddedDotnetAnalyzersLocator(IVsixRootLocator vsixRootLocator, IFileSystemService fileSystem)
: IEmbeddedDotnetAnalyzersLocator, IObsoleteDotnetAnalyzersLocator
{
private const string PathInsideVsix = "EmbeddedDotnetAnalyzerDLLs";
private const string DllsSearchPattern = "SonarAnalyzer.*.dll"; // starting from 10.0, the analyzer assemblies are merged and all of the dll names start with SonarAnalyzer
Expand All @@ -44,32 +45,28 @@ internal class EmbeddedDotnetAnalyzersLocator(IVsixRootLocator vsixRootLocator,

public List<string> GetBasicAnalyzerFullPaths() => GetBasicAnalyzerDlls().ToList();

public Dictionary<RoslynLanguage, List<string>> GetBasicAnalyzerFullPathsByLanguage() => GroupByLanguage(GetBasicAnalyzerDlls());
public Dictionary<RoslynLanguage, List<string>> GetAnalyzerFullPathsByLanguage(AnalyzerInfoDto analyzerInfoDto)
{
var languageToDllsMap = new Dictionary<RoslynLanguage, List<string>>
{
{ RoslynLanguage.CSharp, GetAnalyzerFullPathsByLanguage(RoslynLanguage.CSharp, analyzerInfoDto.ShouldUseCsharpEnterprise) },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded mapping :-/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do need a mapping, because of the bool values. Either I do it here, either I do it in another model, I still need to map each language to the corresponding bool

{ RoslynLanguage.VBNET, GetAnalyzerFullPathsByLanguage(RoslynLanguage.VBNET, analyzerInfoDto.ShouldUseVbEnterprise) }
};
return languageToDllsMap;
}

private List<string> GetAnalyzerFullPathsByLanguage(RoslynLanguage language, bool shouldUseEnterprise)
{
var dlls = shouldUseEnterprise ? GetEnterpriseAnalyzerFullPaths() : GetBasicAnalyzerDlls();

return dlls.Where(dll => dll.Contains(language.RoslynDllIdentifier)).ToList();
}

private IEnumerable<string> GetBasicAnalyzerDlls() => GetAllAnalyzerDlls().Where(x => !x.Contains(EnterpriseInfix));

public List<string> GetEnterpriseAnalyzerFullPaths() => GetAllAnalyzerDlls().ToList();

public Dictionary<RoslynLanguage, List<string>> GetEnterpriseAnalyzerFullPathsByLanguage() => GroupByLanguage(GetAllAnalyzerDlls());

private string[] GetAllAnalyzerDlls() => fileSystem.Directory.GetFiles(GetPathToParentFolder(), DllsSearchPattern);

private string GetPathToParentFolder() => Path.Combine(vsixRootLocator.GetVsixRoot(), PathInsideVsix);


private Dictionary<RoslynLanguage, List<string>> GroupByLanguage(IEnumerable<string> analyzerDlls)
{
var languageToAnalyzerLocations = languageProvider.RoslynLanguages.ToDictionary(x => x, _ => new List<string>());

foreach (var analyzerDll in analyzerDlls)
{
var dllLanguage = languageToAnalyzerLocations.Keys.FirstOrDefault(x => analyzerDll.Contains(x.RoslynDllIdentifier));
if (dllLanguage != null)
{
languageToAnalyzerLocations[dllLanguage].Add(analyzerDll);
}
}

return languageToAnalyzerLocations;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
using SonarLint.VisualStudio.RoslynAnalyzerServer.Analysis;
using SonarLint.VisualStudio.RoslynAnalyzerServer.Http;
using SonarLint.VisualStudio.RoslynAnalyzerServer.Http.Models;
using SonarLint.VisualStudio.SLCore.Common.Models;

namespace SonarLint.VisualStudio.RoslynAnalyzerServer.IntegrationTests.Http.Helper;

Expand Down Expand Up @@ -76,7 +75,7 @@ private static ILogger CreateMockedLogger()
private static IRoslynAnalysisService CreateMockedAnalysisEngine()
{
var analysisEngine = Substitute.For<IRoslynAnalysisService>();
analysisEngine.AnalyzeAsync(Arg.Any<List<FileUri>>(), Arg.Any<List<ActiveRuleDto>>(), Arg.Any<Dictionary<string, string>>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(Enumerable.Empty<RoslynIssue>()));
analysisEngine.AnalyzeAsync(Arg.Any<AnalysisRequest>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(Enumerable.Empty<RoslynIssue>()));
return analysisEngine;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
using SonarLint.VisualStudio.RoslynAnalyzerServer.Http;
using SonarLint.VisualStudio.RoslynAnalyzerServer.Http.Models;
using SonarLint.VisualStudio.RoslynAnalyzerServer.IntegrationTests.Http.Helper;
using SonarLint.VisualStudio.SLCore.Common.Models;

namespace SonarLint.VisualStudio.RoslynAnalyzerServer.IntegrationTests.Http;

Expand Down Expand Up @@ -197,7 +196,7 @@ public async Task StartListenAsync_AnalysisThrowsException_ReturnsInternalServer
var exceptionMessage = "Simulated exception";
using var serverStarter2 = new HttpServerStarter();
serverStarter2.MockedRoslynAnalysisService
.When(x => x.AnalyzeAsync(Arg.Any<List<FileUri>>(), Arg.Any<List<ActiveRuleDto>>(), Arg.Any<Dictionary<string, string>>(), Arg.Any<CancellationToken>()))
.When(x => x.AnalyzeAsync(Arg.Any<AnalysisRequest>(), Arg.Any<CancellationToken>()))
.Do(_ => throw new InvalidOperationException(exceptionMessage));
serverStarter2.StartListeningOnBackgroundThread();

Expand Down Expand Up @@ -288,6 +287,6 @@ private static void MockServerSettings(

private static void SimulateLongAnalysis(IRoslynAnalysisService roslynAnalysisService, int milliseconds) =>
roslynAnalysisService
.When(x => x.AnalyzeAsync(Arg.Any<List<FileUri>>(), Arg.Any<List<ActiveRuleDto>>(), Arg.Any<Dictionary<string, string>>(), Arg.Any<CancellationToken>()))
.When(x => x.AnalyzeAsync(Arg.Any<AnalysisRequest>(), Arg.Any<CancellationToken>()))
.Do(_ => Task.Delay(milliseconds).GetAwaiter().GetResult());
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ private static readonly ImmutableDictionary<RoslynLanguage, AnalyzerAssemblyCont
= new Dictionary<RoslynLanguage, AnalyzerAssemblyContents> { { Language.CSharp, new AnalyzerAssemblyContents() } }.ToImmutableDictionary();
private static readonly List<ActiveRuleDto> DefaultActiveRules = new();
private static readonly Dictionary<string, string> DefaultAnalysisProperties = new();
private static readonly AnalyzerInfoDto DefaultAnalyzerInfoDto = new(false, false);

private ISonarLintXmlProvider sonarLintXmlProvider = null!;
private IRoslynAnalyzerProvider roslynAnalyzerProvider = null!;
Expand All @@ -47,7 +48,7 @@ public void TestInitialize()
{
sonarLintXmlProvider = Substitute.For<ISonarLintXmlProvider>();
roslynAnalyzerProvider = Substitute.For<IRoslynAnalyzerProvider>();
roslynAnalyzerProvider.LoadAndProcessAnalyzerAssemblies().Returns(DefaultAnalyzers);
roslynAnalyzerProvider.LoadAndProcessAnalyzerAssemblies(DefaultAnalyzerInfoDto).Returns(DefaultAnalyzers);

analyzerProfilesProvider = Substitute.For<IRoslynAnalysisProfilesProvider>();
testLogger = Substitute.ForPartsOf<TestLogger>();
Expand Down Expand Up @@ -101,7 +102,7 @@ public void GetConfiguration_CreatesConfigurationForEachLanguage()
analyzerProfilesProvider.GetAnalysisProfilesByLanguage(DefaultAnalyzers, DefaultActiveRules, DefaultAnalysisProperties)
.Returns(roslynAnalysisProfiles);

var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties);
var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties, DefaultAnalyzerInfoDto);

result.Keys.Should().BeEquivalentTo(roslynAnalysisProfiles.Keys);
foreach (var language in roslynAnalysisProfiles.Keys)
Expand Down Expand Up @@ -132,7 +133,7 @@ public void GetConfiguration_NoAnalyzers_LogsAndExcludesLanguage()
analyzerProfilesProvider.GetAnalysisProfilesByLanguage(DefaultAnalyzers, DefaultActiveRules, DefaultAnalysisProperties)
.Returns(roslynAnalysisProfiles);

var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties);
var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties, DefaultAnalyzerInfoDto);

result.Should().BeEmpty();
testLogger.AssertPartialOutputStringExists(string.Format(Resources.RoslynAnalysisConfigurationNoAnalyzers, language.Name));
Expand All @@ -156,7 +157,7 @@ public void GetConfiguration_NoActiveRules_LogsAndExcludesLanguage()
analyzerProfilesProvider.GetAnalysisProfilesByLanguage(DefaultAnalyzers, DefaultActiveRules, DefaultAnalysisProperties)
.Returns(roslynAnalysisProfiles);

var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties);
var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties, DefaultAnalyzerInfoDto);

result.Should().BeEmpty();
testLogger.AssertPartialOutputStringExists(string.Format(Resources.RoslynAnalysisConfigurationNoActiveRules, language.Name));
Expand All @@ -168,7 +169,7 @@ public void GetConfiguration_NoAnalysisProfiles_ReturnsEmptyDictionary()
analyzerProfilesProvider.GetAnalysisProfilesByLanguage(DefaultAnalyzers, DefaultActiveRules, DefaultAnalysisProperties)
.Returns(new Dictionary<RoslynLanguage, RoslynAnalysisProfile>());

var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties);
var result = testSubject.GetConfiguration(DefaultActiveRules, DefaultAnalysisProperties, DefaultAnalyzerInfoDto);

result.Should().BeEmpty();
}
Expand Down
Loading