From 34beb7959675b99c0a7923a9070030eb3cc0b696 Mon Sep 17 00:00:00 2001 From: Vasileios Naskos Date: Wed, 27 Nov 2024 13:47:11 +0100 Subject: [PATCH 01/21] SLVS-1638 Migrate CMake analysis to CFamily java plugin via SLCore --- .../Analysis/CFamily_CallAnalyzerTests.cs | 319 -------------- .../Analysis/CLangAnalyzerTests.cs | 390 ------------------ .../Analysis/RequestFactoryAggregateTests.cs | 116 ------ .../CFamily_CLangAnalyzer_IntegrationTests.cs | 157 ------- src/CFamily/Analysis/CLangAnalyzer.cs | 211 +--------- .../SLCore/SLCoreConstantsProviderTests.cs | 2 + ...egration.Vsix_Baseline_WithStrongNames.txt | 3 +- ...ation.Vsix_Baseline_WithoutStrongNames.txt | 3 +- .../SLCore/SLCoreConstantsProvider.cs | 2 + .../Analysis/SLCoreAnalyzerTests.cs | 114 +++-- src/SLCore/Analysis/SLCoreAnalyzer.cs | 47 ++- 11 files changed, 132 insertions(+), 1232 deletions(-) delete mode 100644 src/CFamily.UnitTests/Analysis/CFamily_CallAnalyzerTests.cs delete mode 100644 src/CFamily.UnitTests/Analysis/CLangAnalyzerTests.cs delete mode 100644 src/CFamily.UnitTests/Analysis/RequestFactoryAggregateTests.cs delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CFamily_CLangAnalyzer_IntegrationTests.cs diff --git a/src/CFamily.UnitTests/Analysis/CFamily_CallAnalyzerTests.cs b/src/CFamily.UnitTests/Analysis/CFamily_CallAnalyzerTests.cs deleted file mode 100644 index bd45b8f77f..0000000000 --- a/src/CFamily.UnitTests/Analysis/CFamily_CallAnalyzerTests.cs +++ /dev/null @@ -1,319 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; -using System.IO.Abstractions; -using System.IO.Abstractions.TestingHelpers; -using System.Linq; -using System.Threading; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.Analysis.UnitTests -{ - [TestClass] - public class CFamily_CLangAnalyzerTests - { - [TestMethod] - public void CallAnalyzer_Succeeds_ReturnsMessages() - { - // Arrange - var dummyProcessRunner = new DummyProcessRunner(MockResponse()); - - // Act - var response = GetResponse(dummyProcessRunner, CreateRequest(), new TestLogger()); - - // Assert - dummyProcessRunner.ExecuteCalled.Should().BeTrue(); - - response.Count.Should().Be(1); - response[0].Filename.Should().Be("file.cpp"); - } - - [TestMethod] - public void CallAnalyzer_Fails_ReturnsZeroMessages() - { - // Arrange - var dummyProcessRunner = new DummyProcessRunner(MockEmptyResponse()); - - // Act - var response = GetResponse(dummyProcessRunner, CreateRequest(), new TestLogger()); - - // Assert - dummyProcessRunner.ExecuteCalled.Should().BeTrue(); - - response.Should().BeEmpty(); - } - - [TestMethod] - public void CallAnalyzer_RequestWithEnvironmentVariables_EnvVarsArePassed() - { - // Arrange - var envVars = new Dictionary { { "aaa", "bbb" } }; - - var request = CreateRequestMock(); - request.SetupGet(x => x.EnvironmentVariables).Returns(envVars); - - var dummyProcessRunner = new DummyProcessRunner(MockResponse()); - var result = GetResponse(dummyProcessRunner, request.Object, new TestLogger()); - - // Act and Assert - dummyProcessRunner.ExecuteCalled.Should().BeTrue(); - dummyProcessRunner.SuppliedProcessRunnerArguments.EnvironmentVariables.Should().BeSameAs(envVars); - } - - [TestMethod] - public void CallAnalyzer_RequestWithReproducer_ReturnsZeroMessages() - { - // Arrange - var request = CreateRequest(new CFamilyAnalyzerOptions { CreateReproducer = true }); - var dummyProcessRunner = new DummyProcessRunner(MockBadEndResponse()); - var result = GetResponse(dummyProcessRunner, request, new TestLogger()); - - // Act and Assert - result.Should().BeEmpty(); - dummyProcessRunner.ExecuteCalled.Should().BeTrue(); - } - - [TestMethod] - public void CallAnalyzer_RequestWithReproducer_DiagnosticsFileIsSaved() - { - // Arrange - var logger = new TestLogger(); - var fileSystem = CreateInitializedFileSystem(); - var requestMock = CreateRequestMock(new CFamilyAnalyzerOptions { CreateReproducer = true }); - var dummyProcessRunner = new DummyProcessRunner(MockResponse()); - GetResponse(dummyProcessRunner, requestMock.Object, logger, fileSystem); - - // Act and Assert - dummyProcessRunner.ExecuteCalled.Should().BeTrue(); - - fileSystem.AllFiles.Should().BeEquivalentTo(SubProcessFilePaths.RequestConfigFilePath); - requestMock.Verify(x => x.WriteRequestDiagnostics(It.IsAny()), Times.Once); - - logger.AssertPartialOutputStringExists(SubProcessFilePaths.RequestConfigFilePath); - logger.AssertPartialOutputStringExists(SubProcessFilePaths.ReproducerFilePath); - } - - [TestMethod] - public void CallAnalyzer_RequestWithoutReproducer_DiagnosticsFileIsNotSaved() - { - // Arrange - var logger = new TestLogger(); - var fileSystem = CreateInitializedFileSystem(); - var requestMock = CreateRequestMock(new CFamilyAnalyzerOptions { CreateReproducer = false }); - var dummyProcessRunner = new DummyProcessRunner(MockResponse()); - GetResponse(dummyProcessRunner, requestMock.Object, logger, fileSystem); - - // Act and Assert - dummyProcessRunner.ExecuteCalled.Should().BeTrue(); - - fileSystem.AllFiles.Should().BeEmpty(); - requestMock.Verify(x => x.WriteRequestDiagnostics(It.IsAny()), Times.Never); - - logger.AssertPartialOutputStringDoesNotExist(SubProcessFilePaths.RequestConfigFilePath); - logger.AssertPartialOutputStringDoesNotExist(SubProcessFilePaths.ReproducerFilePath); - } - - [TestMethod] - public void CallAnalyzer_BadResponse_ThrowsException() - { - // Arrange - var logger = new TestLogger(); - var dummyProcessRunner = new DummyProcessRunner(MockBadEndResponse()); - - Action act = () => GetResponse(dummyProcessRunner, CreateRequest(), logger); - - // Act and Assert - act.Should().Throw().And.Message.Should().Be("Communication issue with the C/C++ analyzer"); - dummyProcessRunner.ExecuteCalled.Should().BeTrue(); - } - - private static IRequest CreateRequest(CFamilyAnalyzerOptions analyzerOptions = null) => - CreateRequestMock(analyzerOptions).Object; - - private static Mock CreateRequestMock(CFamilyAnalyzerOptions analyzerOptions = null) - { - var context = new RequestContext(null, null, null, null, analyzerOptions, false); - var request = new Mock(); - request.Setup(x => x.Context).Returns(context); - return request; - } - - private static MockFileSystem CreateInitializedFileSystem() - { - var fileSystem = new MockFileSystem(); - - // Make sure the expected working directory exists - fileSystem.Directory.CreateDirectory(SubProcessFilePaths.WorkingDirectory); - return fileSystem; - } - - private static List GetResponse(DummyProcessRunner dummyProcessRunner, IRequest request, ILogger logger, - IFileSystem fileSystem = null) - { - return GetResponse(dummyProcessRunner, request, logger, CancellationToken.None, fileSystem ?? new FileSystem()); - } - - private static List GetResponse(DummyProcessRunner dummyProcessRunner, IRequest request, ILogger logger, CancellationToken cancellationToken, - IFileSystem fileSystem) - { - var messages = new List(); - - CLangAnalyzer.ExecuteSubProcess(messages.Add, request, dummyProcessRunner, logger, cancellationToken, fileSystem); - - return messages; - } - - private class DummyProcessRunner : IProcessRunner - { - private readonly byte[] responseToReturn; - - public DummyProcessRunner(byte[] responseToReturn) - { - this.responseToReturn = responseToReturn; - } - - public bool ExecuteCalled { get; private set; } - - public ProcessRunnerArguments SuppliedProcessRunnerArguments { get; private set; } - - public void Execute(ProcessRunnerArguments runnerArgs) - { - ExecuteCalled = true; - - runnerArgs.Should().NotBeNull(); - SuppliedProcessRunnerArguments = runnerArgs; - - // Expecting a single file name as input - runnerArgs.CmdLineArgs.Count().Should().Be(1); - - using (var stream = new MemoryStream(responseToReturn)) - using (var streamReader = new StreamReader(stream)) - { - runnerArgs.HandleOutputStream(streamReader); - } - } - } - - private byte[] MockEmptyResponse() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - - // 0 issues - - // 0 measures - Protocol.WriteUTF(writer, "measures"); - Protocol.WriteInt(writer, 0); - - // 0 symbols - Protocol.WriteUTF(writer, "symbols"); - Protocol.WriteInt(writer, 0); - - Protocol.WriteUTF(writer, "END"); - return stream.ToArray(); - } - } - - private byte[] MockResponse() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - - // 1 issue - Protocol.WriteUTF(writer, "message"); - - Protocol.WriteUTF(writer, "ruleKey"); - Protocol.WriteUTF(writer, "file.cpp"); - Protocol.WriteInt(writer, 10); - Protocol.WriteInt(writer, 11); - Protocol.WriteInt(writer, 12); - Protocol.WriteInt(writer, 13); - Protocol.WriteInt(writer, 100); - Protocol.WriteUTF(writer, "Issue message"); - writer.Write(true); - - // 1 flow - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "another.cpp"); - Protocol.WriteInt(writer, 14); - Protocol.WriteInt(writer, 15); - Protocol.WriteInt(writer, 16); - Protocol.WriteInt(writer, 17); - Protocol.WriteUTF(writer, "Flow message"); - - // 0 Data Flow - Protocol.WriteInt(writer, 0); - - // 0 fixes - writer.Write(false); - Protocol.WriteInt(writer, 0); - - // 1 measure - Protocol.WriteUTF(writer, "measures"); - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "file.cpp"); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - byte[] execLines = new byte[] { 1, 2, 3, 4 }; - Protocol.WriteInt(writer, execLines.Length); - writer.Write(execLines); - - // 1 symbol - Protocol.WriteUTF(writer, "symbols"); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - Protocol.WriteUTF(writer, "END"); - return stream.ToArray(); - } - } - - private byte[] MockBadEndResponse() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - Protocol.WriteUTF(writer, "FOO"); - return stream.ToArray(); - } - } - } -} diff --git a/src/CFamily.UnitTests/Analysis/CLangAnalyzerTests.cs b/src/CFamily.UnitTests/Analysis/CLangAnalyzerTests.cs deleted file mode 100644 index 1820337fde..0000000000 --- a/src/CFamily.UnitTests/Analysis/CLangAnalyzerTests.cs +++ /dev/null @@ -1,390 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO.Abstractions; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Telemetry; -using SonarLint.VisualStudio.Integration; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.Analysis.UnitTests -{ - [TestClass] - public class CLangAnalyzerTests - { - private static readonly IIssueConsumer ValidIssueConsumer = Mock.Of(); - private static readonly IAnalysisStatusNotifier AnyStatusNotifier = Mock.Of(); - - [TestMethod] - public void IsSupported() - { - var testSubject = new CLangAnalyzer(Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); - - testSubject.IsAnalysisSupported(new[] { AnalysisLanguage.CFamily }).Should().BeTrue(); - testSubject.IsAnalysisSupported(new[] { AnalysisLanguage.Javascript }).Should().BeFalse(); - testSubject.IsAnalysisSupported(new[] { AnalysisLanguage.CascadingStyleSheets }).Should().BeFalse(); - testSubject.IsAnalysisSupported(new[] { AnalysisLanguage.Javascript, AnalysisLanguage.CFamily }).Should().BeTrue(); - } - - [TestMethod] - public async Task ExecuteAnalysis_RequestCannotBeCreated_NoAnalysis() - { - var analysisOptions = new CFamilyAnalyzerOptions(); - var requestFactory = CreateRequestFactory("path", analysisOptions, null); - - var testSubject = CreateTestableAnalyzer(requestFactory: requestFactory.Object); - await testSubject.TriggerAnalysisAsync("path", new[] { AnalysisLanguage.CFamily }, ValidIssueConsumer, analysisOptions, AnyStatusNotifier, CancellationToken.None); - - requestFactory.Verify(x => x.TryCreateAsync("path", analysisOptions), Times.Once); - - // TODO - modify check to be more reliable - Thread.Sleep(400); // delay in case the background thread has gone on to call the subprocess - testSubject.SubProcessExecutedCount.Should().Be(0); - } - - [TestMethod] - [DataRow(true)] - [DataRow(false)] - public async Task ExecuteAnalysis_RequestCannotBeCreated_NotPCH_LogOutput(bool isNullOptions) - { - var analysisOptions = isNullOptions ? null : new CFamilyAnalyzerOptions { CreatePreCompiledHeaders = false }; - var requestFactory = CreateRequestFactory("path", analysisOptions, null); - var testLogger = new TestLogger(); - - var testSubject = CreateTestableAnalyzer( - requestFactory: requestFactory.Object, - logger: testLogger); - - await testSubject.TriggerAnalysisAsync("path", new[] { AnalysisLanguage.CFamily }, ValidIssueConsumer, analysisOptions, AnyStatusNotifier, CancellationToken.None); - - testLogger.AssertOutputStringExists(string.Format(CFamilyStrings.MSG_UnableToCreateConfig, "path")); - } - - [TestMethod] - public async Task ExecuteAnalysis_RequestCannotBeCreated_PCH_NoLogOutput() - { - var analysisOptions = new CFamilyAnalyzerOptions {CreatePreCompiledHeaders = true}; - var requestFactory = CreateRequestFactory("path", analysisOptions, null); - var testLogger = new TestLogger(); - - var testSubject = CreateTestableAnalyzer( - requestFactory: requestFactory.Object, - logger: testLogger); - - await testSubject.TriggerAnalysisAsync("path", new[] { AnalysisLanguage.CFamily }, ValidIssueConsumer, analysisOptions, AnyStatusNotifier, CancellationToken.None); - - testLogger.AssertNoOutputMessages(); - } - - [TestMethod] - public async Task ExecuteAnalysis_RequestCanBeCreated_AnalysisIsTriggered() - { - var analysisOptions = new CFamilyAnalyzerOptions(); - var request = CreateRequest("path"); - var requestFactory = CreateRequestFactory("path", analysisOptions, request); - - - var testSubject = CreateTestableAnalyzer( - requestFactory: requestFactory.Object); - - await testSubject.TriggerAnalysisAsync("path", new[] { AnalysisLanguage.CFamily }, ValidIssueConsumer, analysisOptions, AnyStatusNotifier, CancellationToken.None); - - testSubject.SubProcessExecutedCount.Should().Be(1); - } - - [TestMethod] - public async Task TriggerAnalysisAsync_StreamsIssuesFromSubProcessToConsumer() - { - const string fileName = "c:\\data\\aaa\\bbb\\file.txt"; - var rulesConfig = new DummyCFamilyRulesConfig("c") - .AddRule("rule1", isActive: true) - .AddRule("rule2", isActive: true); - - var request = CreateRequest - ( - file: fileName, - rulesConfiguration: rulesConfig, - language: rulesConfig.LanguageKey - ); - var requestFactory = CreateRequestFactory(fileName, ValidAnalyzerOptions, request); - - var message1 = new Message("rule1", fileName, 1, 1, 1, 1, "message one", false, Array.Empty(), Array.Empty()); - var message2 = new Message("rule2", fileName, 2, 2, 2, 2, "message two", false, Array.Empty(), Array.Empty()); - - var convertedMessage1 = Mock.Of(); - var convertedMessage2 = Mock.Of(); - - var issueConverter = new Mock(); - issueConverter - .Setup(x => x.Convert(message1, request.Context.CFamilyLanguage, rulesConfig)) - .Returns(convertedMessage1); - - issueConverter - .Setup(x => x.Convert(message2, request.Context.CFamilyLanguage, rulesConfig)) - .Returns(convertedMessage2); - - var issueConverterFactory = new Mock(); - issueConverterFactory.Setup(x => x.Create()).Returns(issueConverter.Object); - - var mockConsumer = new Mock(); - var statusNotifier = new Mock(); - - var testSubject = CreateTestableAnalyzer(issueConverterFactory: issueConverterFactory.Object, - requestFactory: requestFactory.Object); - - TestableCLangAnalyzer.HandleCallSubProcess subProcessOp = (handleMessage, _, _, _, _) => - { - // NOTE: on a background thread so the assertions might be handled by the product code. - // Must check testSubject.SubProcessCompleted on the "main" test thread. - - // Stream the first message to the analyzer - handleMessage(message1); - - mockConsumer.Verify(x => x.Accept(fileName, It.IsAny>()), Times.Once); - var suppliedIssues = (IEnumerable)mockConsumer.Invocations[0].Arguments[1]; - suppliedIssues.Count().Should().Be(1); - suppliedIssues.First().Should().Be(convertedMessage1); - - // Stream the second message to the analyzer - handleMessage(message2); - - mockConsumer.Verify(x => x.Accept(fileName, It.IsAny>()), Times.Exactly(2)); - suppliedIssues = (IEnumerable)mockConsumer.Invocations[1].Arguments[1]; - suppliedIssues.Count().Should().Be(1); - suppliedIssues.First().Should().Be(convertedMessage2); - }; - testSubject.SetCallSubProcessBehaviour(subProcessOp); - - await testSubject.TriggerAnalysisAsync(fileName, ValidDetectedLanguages, mockConsumer.Object, ValidAnalyzerOptions, statusNotifier.Object, CancellationToken.None); - - testSubject.SubProcessCompleted.Should().BeTrue(); - - statusNotifier.Verify(x => x.AnalysisStarted(), Times.Once); - statusNotifier.Verify(x => x.AnalysisFinished(2, It.IsAny()), Times.Once); - statusNotifier.VerifyNoOtherCalls(); - } - - [TestMethod] - public async Task TriggerAnalysisAsync_AnalysisIsCancelled_NotifiesOfCancellation() - { - var mockConsumer = new Mock(); - var originalStatusNotifier = new Mock(); - - // Call the CLangAnalyzer on another thread (that thread is blocked by subprocess wrapper) - var filePath = "c:\\test.cpp"; - var request = CreateRequest(filePath); - var requestFactory = CreateRequestFactory(filePath, ValidAnalyzerOptions, request); - - var testSubject = CreateTestableAnalyzer(requestFactory: requestFactory.Object); - - using var cts = new CancellationTokenSource(); - - TestableCLangAnalyzer.HandleCallSubProcess subProcessAction = (_, _, _, _, _) => - { - cts.Cancel(); - }; - testSubject.SetCallSubProcessBehaviour(subProcessAction); - - // Expecting to use this status notifier, not the one supplied in the constructor - var statusNotifier = new Mock(); - - await testSubject.TriggerAnalysisAsync(filePath, ValidDetectedLanguages, mockConsumer.Object, ValidAnalyzerOptions, statusNotifier.Object, cts.Token); - - testSubject.SubProcessCompleted.Should().BeTrue(); - - statusNotifier.Verify(x => x.AnalysisStarted(), Times.Once); - statusNotifier.Verify(x => x.AnalysisCancelled(), Times.Once); - statusNotifier.VerifyNoOtherCalls(); - originalStatusNotifier.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public async Task TriggerAnalysisAsync_AnalysisFailsDueToException_NotifiesOfFailure() - { - void MockSubProcessCall(Action message, IRequest request, ISonarLintSettings settings, ILogger logger, CancellationToken token) - { - throw new NullReferenceException("test"); - } - - var statusNotifier = new Mock(); - - var filePath = "c:\\test.cpp"; - var request = CreateRequest(filePath); - var requestFactory = CreateRequestFactory(filePath, ValidAnalyzerOptions, request); - - var testSubject = CreateTestableAnalyzer(requestFactory: requestFactory.Object); - testSubject.SetCallSubProcessBehaviour(MockSubProcessCall); - - await testSubject.TriggerAnalysisAsync(filePath, ValidDetectedLanguages, ValidIssueConsumer, ValidAnalyzerOptions, statusNotifier.Object, CancellationToken.None); - - statusNotifier.Verify(x => x.AnalysisStarted(), Times.Once); - statusNotifier.Verify(x => x.AnalysisFailed(It.Is(e => e.Message == "test")), Times.Once); - statusNotifier.VerifyNoOtherCalls(); - } - - [TestMethod] - public async Task TriggerAnalysisAsync_AnalysisFailsDueToInternalMessage_NotifiesOfFailure() - { - const string fileName = "c:\\data\\aaa\\bbb\\file.txt"; - var request = CreateRequest(fileName); - var requestFactory = CreateRequestFactory(fileName, ValidAnalyzerOptions, request); - - var internalErrorMessage = new Message("internal.UnexpectedFailure", "", 1, 1, 1, 1, "XXX Error in subprocess XXX", false, Array.Empty(), Array.Empty()); - - var issueConverterFactory = Mock.Of(); - var mockConsumer = new Mock(); - var statusNotifier = new Mock(); - - var testSubject = CreateTestableAnalyzer(issueConverterFactory: issueConverterFactory, - requestFactory: requestFactory.Object); - - TestableCLangAnalyzer.HandleCallSubProcess subProcessOp = (handleMessage, _, _, _, _) => - { - handleMessage(internalErrorMessage); - }; - testSubject.SetCallSubProcessBehaviour(subProcessOp); - - await testSubject.TriggerAnalysisAsync(fileName, ValidDetectedLanguages, mockConsumer.Object, ValidAnalyzerOptions, statusNotifier.Object, CancellationToken.None); - - testSubject.SubProcessCompleted.Should().BeTrue(); - - statusNotifier.Verify(x => x.AnalysisStarted(), Times.Once); - statusNotifier.Verify(x => x.AnalysisFailed(CFamilyStrings.MSG_GenericAnalysisFailed), Times.Once); - statusNotifier.VerifyNoOtherCalls(); - } - - [TestMethod] - public async Task TriggerAnalysisAsync_SwitchesToBackgroundThreadBeforeProcessing() - { - var callOrder = new List(); - - var threadHandling = new Mock(); - threadHandling.Setup(x => x.SwitchToBackgroundThread()) - .Returns(() => new NoOpThreadHandler.NoOpAwaitable()) - .Callback(() => callOrder.Add("SwitchToBackgroundThread")); - - var requestFactory = new Mock(); - requestFactory.Setup(x => x.TryCreateAsync(It.IsAny(), It.IsAny())) - .Callback(() => callOrder.Add("TryCreateAsync")); - - var testSubject = CreateTestableAnalyzer(requestFactory: requestFactory.Object, threadHandling: threadHandling.Object); - await testSubject.TriggerAnalysisAsync("path", ValidDetectedLanguages, Mock.Of(), - Mock.Of(), Mock.Of(), CancellationToken.None); - - callOrder.Should().Equal("SwitchToBackgroundThread", "TryCreateAsync"); - } - - private readonly AnalysisLanguage[] ValidDetectedLanguages = new[] { AnalysisLanguage.CFamily }; - private readonly CFamilyAnalyzerOptions ValidAnalyzerOptions = null; - - private static IRequest CreateRequest(string file = null, string language = null, ICFamilyRulesConfig rulesConfiguration = null) - { - var request = new Mock(); - var context = new RequestContext(language, rulesConfiguration, file, null, null, false); - request.SetupGet(x => x.Context).Returns(context); - return request.Object; - } - - private static Mock CreateRequestFactory(string filePath, CFamilyAnalyzerOptions analysisOptions, IRequest request) - { - var factory = new Mock(); - factory.Setup(x => x.TryCreateAsync(filePath, analysisOptions)) - .Returns(Task.FromResult(request)); - return factory; - } - - private static TestableCLangAnalyzer CreateTestableAnalyzer(ITelemetryManager telemetryManager = null, - ISonarLintSettings settings = null, - ICFamilyIssueConverterFactory issueConverterFactory = null, - IRequestFactoryAggregate requestFactory = null, - ILogger logger = null, - IFileSystem fileSystem = null, - IThreadHandling threadHandling = null) - { - telemetryManager ??= Mock.Of(); - settings ??= new ConfigurableSonarLintSettings(); - issueConverterFactory ??= Mock.Of(); - requestFactory ??= Mock.Of(); - logger ??= new TestLogger(); - fileSystem ??= Mock.Of(); - threadHandling ??= new NoOpThreadHandler(); - - return new TestableCLangAnalyzer(telemetryManager, settings, logger, issueConverterFactory, requestFactory, fileSystem, threadHandling); - } - - private class TestableCLangAnalyzer : CLangAnalyzer - { - public delegate void HandleCallSubProcess(Action handleMessage, IRequest request, - ISonarLintSettings settings, ILogger logger, CancellationToken cancellationToken); - - private HandleCallSubProcess onCallSubProcess; - - public void SetCallSubProcessBehaviour(HandleCallSubProcess onCallSubProcess) => - this.onCallSubProcess = onCallSubProcess; - - public bool SubProcessCompleted { get; private set; } - - public int SubProcessExecutedCount { get; private set; } - - public TestableCLangAnalyzer(ITelemetryManager telemetryManager, ISonarLintSettings settings, - ILogger logger, - ICFamilyIssueConverterFactory cFamilyIssueConverterFactory, IRequestFactoryAggregate requestFactory, IFileSystem fileSystem, - IThreadHandling threadHandling) - : base(telemetryManager, settings, Mock.Of(), cFamilyIssueConverterFactory, requestFactory, logger, fileSystem, threadHandling) - { } - - protected override void CallSubProcess(Action handleMessage, IRequest request, - ISonarLintSettings settings, ILogger logger, CancellationToken cancellationToken) - { - SubProcessExecutedCount++; - if (onCallSubProcess == null) - { - base.CallSubProcess(handleMessage, request, settings, logger, cancellationToken); - } - else - { - onCallSubProcess(handleMessage, request, settings, logger, cancellationToken); - - // The sub process is executed on a separate thread, so any exceptions might be - // squashed by the product code. So, we'll set a flag to indicate whether it - // ran to completion. - SubProcessCompleted = true; - } - } - } - } -} diff --git a/src/CFamily.UnitTests/Analysis/RequestFactoryAggregateTests.cs b/src/CFamily.UnitTests/Analysis/RequestFactoryAggregateTests.cs deleted file mode 100644 index fd084a10d4..0000000000 --- a/src/CFamily.UnitTests/Analysis/RequestFactoryAggregateTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.ComponentModel.Composition.Hosting; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.Analysis.UnitTests -{ - [TestClass] - public class RequestFactoryAggregateTests - { - [TestMethod] - public void MefCtor_CheckExports() - { - var batch = new CompositionBatch(); - - batch.AddExport(MefTestHelpers.CreateExport(Mock.Of())); - batch.AddExport(MefTestHelpers.CreateExport(Mock.Of())); - batch.AddExport(MefTestHelpers.CreateExport(Mock.Of())); - - var aggregateImport = new SingleObjectImporter(); - batch.AddPart(aggregateImport); - - using var catalog = new TypeCatalog(typeof(RequestFactoryAggregate)); - using var container = new CompositionContainer(catalog); - container.Compose(batch); - - aggregateImport.Import.Should().NotBeNull(); - } - - [TestMethod] - public void TryGet_NullFilePath_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Func act = () => testSubject.TryCreateAsync(null, new CFamilyAnalyzerOptions()); - - act.Should().ThrowExactly().And.ParamName.Should().Be("analyzedFilePath"); - } - - [TestMethod] - public async Task TryGet_NoFactories_Null() - { - var testSubject = CreateTestSubject(); - - var result = await testSubject.TryCreateAsync("path", new CFamilyAnalyzerOptions()); - - result.Should().BeNull(); - } - - [TestMethod] - public async Task TryGet_NoMatchingFactory_Null() - { - var factory1 = new Mock(); - var factory2 = new Mock(); - - var testSubject = CreateTestSubject(factory1.Object, factory2.Object); - - var options = new CFamilyAnalyzerOptions(); - var result = await testSubject.TryCreateAsync("path", options); - - result.Should().BeNull(); - - factory1.Verify(x=> x.TryCreateAsync("path", options), Times.Once); - factory2.Verify(x=> x.TryCreateAsync("path", options), Times.Once); - } - - [TestMethod] - public async Task TryGet_HasMatchingFactory_OtherFactoriesNotChecked() - { - var factory1 = new Mock(); - var factory2 = new Mock(); - var factory3 = new Mock(); - - var requestToReturn = Mock.Of(); - var options = new CFamilyAnalyzerOptions(); - factory2.Setup(x => x.TryCreateAsync("path", options)).Returns(Task.FromResult(requestToReturn)); - - var testSubject = CreateTestSubject(factory1.Object, factory2.Object, factory3.Object); - - var result = await testSubject.TryCreateAsync("path", options); - - result.Should().Be(requestToReturn); - - factory1.Verify(x => x.TryCreateAsync("path", options), Times.Once); - factory2.Verify(x => x.TryCreateAsync("path", options), Times.Once); - factory3.Invocations.Count.Should().Be(0); - } - - private RequestFactoryAggregate CreateTestSubject(params IRequestFactory[] requestFactories) => - new RequestFactoryAggregate(requestFactories); - } -} diff --git a/src/CFamily.UnitTests/IntegrationTests/CFamily_CLangAnalyzer_IntegrationTests.cs b/src/CFamily.UnitTests/IntegrationTests/CFamily_CLangAnalyzer_IntegrationTests.cs deleted file mode 100644 index be98c6589e..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CFamily_CLangAnalyzer_IntegrationTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.IO.Abstractions; -using System.Linq; -using System.Threading; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.CMake; -using SonarLint.VisualStudio.CFamily.CompilationDatabase; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.IntegrationTests -{ - [TestClass] - public class CFamily_CLangAnalyzer_IntegrationTests - { - private string testsDataDirectory; - private string clExe; - - [TestInitialize] - public void TestInitialize() - { - // Uri absolute path is used to make issues filename slashes consistent between expected and actual - testsDataDirectory = new Uri(Path.Combine( - Path.GetDirectoryName(typeof(CFamily_CLangAnalyzer_IntegrationTests).Assembly.Location), - "IntegrationTests\\")).AbsolutePath; - - // Subprocess.exe requires a valid path to an executable named cl.exe that prints something similar to the real compiler - const string code = "Console.Error.WriteLine(\"Microsoft(R) C / C++ Optimizing Compiler Version 19.32.31114.2 for x64\");"; - clExe = DummyExeHelper.CreateDummyExe(testsDataDirectory, "cl.exe", 0, code); - } - - [TestMethod] - [DataRow("CLangAnalyzerTestFile_NoIssues_EmptyFile")] - [DataRow("CLangAnalyzerTestFile_OneIssue")] - [DataRow("CLangAnalyzerTestFile_TwoIssues")] - [DataRow("CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations")] - public void CallAnalyzer_IntegrationTest(string testCaseFileName) - { - var testedFile = Path.Combine(testsDataDirectory, testCaseFileName + ".txt").Replace('/', '\\'); - - // Sanity checks to help with debugging on the CI machine - CheckFileExists(testedFile); - CheckRulesMetadataFilesExist(); - - var request = GetRequest(testedFile); - var expectedMessages = GetExpectedMessages(testCaseFileName, testedFile); - - var messages = InvokeAnalyzer(request); - - messages.Where(x => !string.IsNullOrEmpty(x.Filename)).Should().BeEquivalentTo(expectedMessages, e => e.WithStrictOrdering()); - } - - private static void CheckRulesMetadataFilesExist() - { - var libDirectory = CFamilyShared.CFamilyFilesDirectory; - Console.WriteLine($"[TEST SETUP] Checking CFamily lib directory exists: {libDirectory}"); - Directory.Exists(libDirectory).Should().BeTrue($"[TEST SETUP ERROR] CFamily lib directory could not be found: {libDirectory}"); - - CheckFileExists(Path.Combine(libDirectory, "Sonar_way_profile.json")); - CheckFileExists(Path.Combine(libDirectory, "RulesList.json")); - } - - private static void CheckFileExists(string fileName) - { - Console.WriteLine($"[TEST SETUP] Checking for required file: {fileName}"); - File.Exists(fileName).Should().BeTrue($"[TEST SETUP ERROR] Could not find required test input file: {fileName}"); - } - - private CompilationDatabaseRequest GetRequest(string testedFile) - { - var command = "\"" + clExe + "\" /TP " + testedFile; - var compilationDatabaseEntry = new CompilationDatabaseEntry - { - Directory = testsDataDirectory, - Command = command, - File = testedFile - }; - - var envVars = new ReadOnlyDictionary(new Dictionary { - { "INCLUDE", "" } - }); - - var languageKey = SonarLanguageKeys.CPlusPlus; - - var config = new CFamilySonarWayRulesConfigProvider(CFamilyShared.CFamilyFilesDirectory).GetRulesConfiguration("cpp"); - var context = new RequestContext( - languageKey, - config, - testedFile, - "", - new CFamilyAnalyzerOptions(), - false); - - var request = new CompilationDatabaseRequest(compilationDatabaseEntry, context, envVars); - - return request; - } - - private Message[] GetExpectedMessages(string testFileName, string testedFile) - { - var expectedResponseJson = File.ReadAllText(Path.Combine(testsDataDirectory, testFileName + "_response.json")); - var expectedResponse = JsonConvert.DeserializeObject(expectedResponseJson); - var messages = expectedResponse.Messages; - foreach (var expectedResponseMessage in messages) - { - expectedResponseMessage.Filename = testedFile; - - foreach (var messagePart in expectedResponseMessage.Parts) - { - messagePart.Filename = testedFile; - } - } - - return messages; - } - - private static List InvokeAnalyzer(CompilationDatabaseRequest request) - { - var testLogger = new TestLogger(true); - var processRunner = new ProcessRunner(new ConfigurableSonarLintSettings(), testLogger); - - var messages = new List(); - CLangAnalyzer.ExecuteSubProcess(messages.Add, request, processRunner, testLogger, CancellationToken.None, new FileSystem()); - messages = messages.Where(m => !m.RuleKey.StartsWith("internal.")).ToList(); - return messages; - } - } -} diff --git a/src/CFamily/Analysis/CLangAnalyzer.cs b/src/CFamily/Analysis/CLangAnalyzer.cs index 2cd7d3e9a2..3b3f5dee1b 100644 --- a/src/CFamily/Analysis/CLangAnalyzer.cs +++ b/src/CFamily/Analysis/CLangAnalyzer.cs @@ -19,16 +19,8 @@ */ using System.ComponentModel.Composition; -using System.IO; -using System.IO.Abstractions; -using Microsoft.VisualStudio.Threading; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; +using System.Diagnostics.CodeAnalysis; using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Telemetry; -using SonarLint.VisualStudio.Infrastructure.VS; -using SonarLint.VisualStudio.Integration; -using Task = System.Threading.Tasks.Task; namespace SonarLint.VisualStudio.CFamily.Analysis { @@ -42,49 +34,14 @@ void ExecuteAnalysis(string path, CancellationToken cancellationToken); } - [Export(typeof(IAnalyzer))] + [ExcludeFromCodeCoverage] [Export(typeof(ICFamilyAnalyzer))] [PartCreationPolicy(CreationPolicy.Shared)] internal class CLangAnalyzer : ICFamilyAnalyzer { - private readonly ITelemetryManager telemetryManager; - private readonly ISonarLintSettings settings; - private readonly IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; - private readonly ILogger logger; - private readonly ICFamilyIssueConverterFactory issueConverterFactory; - private readonly IRequestFactoryAggregate requestFactory; - private readonly IFileSystem fileSystem; - private readonly IThreadHandling threadHandling; - [ImportingConstructor] - public CLangAnalyzer(ITelemetryManager telemetryManager, - ISonarLintSettings settings, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, - ICFamilyIssueConverterFactory issueConverterFactory, - IRequestFactoryAggregate requestFactory, - ILogger logger) - : this(telemetryManager, settings, analysisStatusNotifierFactory, issueConverterFactory, requestFactory, logger, new FileSystem(), ThreadHandling.Instance) - { - } - - internal /* for testing */ CLangAnalyzer(ITelemetryManager telemetryManager, - ISonarLintSettings settings, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, - ICFamilyIssueConverterFactory issueConverterFactory, - IRequestFactoryAggregate requestFactory, - ILogger logger, - IFileSystem fileSystem, - IThreadHandling threadHandling) - + public CLangAnalyzer() { - this.telemetryManager = telemetryManager; - this.settings = settings; - this.analysisStatusNotifierFactory = analysisStatusNotifierFactory; - this.logger = logger; - this.issueConverterFactory = issueConverterFactory; - this.requestFactory = requestFactory; - this.fileSystem = fileSystem; - this.threadHandling = threadHandling; } public bool IsAnalysisSupported(IEnumerable languages) @@ -92,170 +49,24 @@ public bool IsAnalysisSupported(IEnumerable languages) return languages.Contains(AnalysisLanguage.CFamily); } - public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, - IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, - CancellationToken cancellationToken) - { - var analysisStatusNotifier = analysisStatusNotifierFactory.Create(nameof(CLangAnalyzer), path); - - ExecuteAnalysis(path, detectedLanguages, consumer, analyzerOptions, analysisStatusNotifier, cancellationToken); - } - - public void ExecuteAnalysis(string path, + public void ExecuteAnalysis( + string path, + Guid analysisId, IEnumerable detectedLanguages, IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, - IAnalysisStatusNotifier statusNotifier, - CancellationToken cancellationToken) => - TriggerAnalysisAsync(path, detectedLanguages, consumer, analyzerOptions, statusNotifier, cancellationToken) - .Forget(); // fire and forget + CancellationToken cancellationToken) + { + } - internal /* for testing */ async Task TriggerAnalysisAsync(string path, + public void ExecuteAnalysis( + string path, IEnumerable detectedLanguages, IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, IAnalysisStatusNotifier statusNotifier, CancellationToken cancellationToken) { - Debug.Assert(IsAnalysisSupported(detectedLanguages)); - - // Switch to a background thread - await threadHandling.SwitchToBackgroundThread(); - - var request = await TryCreateRequestAsync(path, analyzerOptions); - - if (request != null) - { - RunAnalysis(request, consumer, statusNotifier, cancellationToken); - } - } - - private async Task TryCreateRequestAsync(string path, IAnalyzerOptions analyzerOptions) - { - var cFamilyAnalyzerOptions = analyzerOptions as CFamilyAnalyzerOptions; - var request = await requestFactory.TryCreateAsync(path, cFamilyAnalyzerOptions); - - if (request == null) - { - // Logging for PCH is too noisy: #2553 - if (cFamilyAnalyzerOptions == null || !cFamilyAnalyzerOptions.CreatePreCompiledHeaders) - { - logger.WriteLine(CFamilyStrings.MSG_UnableToCreateConfig, path); - } - return null; - } - - return request; - } - - protected /* for testing */ virtual void CallSubProcess(Action handleMessage, IRequest request, ISonarLintSettings settings, ILogger logger, CancellationToken cancellationToken) - { - ExecuteSubProcess(handleMessage, request, new ProcessRunner(settings, logger), logger, cancellationToken, fileSystem); - } - - private void RunAnalysis(IRequest request, IIssueConsumer consumer, IAnalysisStatusNotifier statusNotifier, CancellationToken cancellationToken) - { - var analysisStopwatch = Stopwatch.StartNew(); - statusNotifier?.AnalysisStarted(); - - var messageHandler = consumer == null - ? NoOpMessageHandler.Instance - : new MessageHandler(request, consumer, issueConverterFactory.Create(), logger); - - try - { - // We're tying up a background thread waiting for out-of-process analysis. We could - // change the process runner so it works asynchronously. Alternatively, we could change the - // RequestAnalysis method to be asynchronous, rather than fire-and-forget. - CallSubProcess(messageHandler.HandleMessage, request, settings, logger, cancellationToken); - - if (cancellationToken.IsCancellationRequested) - { - statusNotifier?.AnalysisCancelled(); - } - else - { - if (messageHandler.AnalysisSucceeded) - { - statusNotifier?.AnalysisFinished(messageHandler.IssueCount, analysisStopwatch.Elapsed); - } - else - { - statusNotifier?.AnalysisFailed(CFamilyStrings.MSG_GenericAnalysisFailed); - } - } - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - statusNotifier?.AnalysisFailed(ex); - } - - telemetryManager.LanguageAnalyzed(request.Context.CFamilyLanguage, analysisStopwatch.Elapsed); // different keys for C and C++ - } - - internal /* for testing */ static void ExecuteSubProcess(Action handleMessage, IRequest request, IProcessRunner runner, ILogger logger, CancellationToken cancellationToken, IFileSystem fileSystem) - { - if (SubProcessFilePaths.AnalyzerExeFilePath == null) - { - logger.WriteLine(CFamilyStrings.MSG_UnableToLocateSubProcessExe); - return; - } - - var createReproducer = request.Context.AnalyzerOptions?.CreateReproducer ?? false; - if (createReproducer) - { - SaveRequestDiagnostics(request, logger, fileSystem); - } - - const string communicateViaStreaming = "-"; // signal the subprocess we want to communicate via standard IO streams. - - var args = new ProcessRunnerArguments(SubProcessFilePaths.AnalyzerExeFilePath, false) - { - CmdLineArgs = new[] { communicateViaStreaming }, - CancellationToken = cancellationToken, - WorkingDirectory = SubProcessFilePaths.WorkingDirectory, - EnvironmentVariables = request.EnvironmentVariables, - HandleInputStream = writer => - { - using (var binaryWriter = new BinaryWriter(writer.BaseStream)) - { - request.WriteRequest(binaryWriter); - } - }, - HandleOutputStream = reader => - { - if (createReproducer) - { - reader.ReadToEnd(); - logger.WriteLine(CFamilyStrings.MSG_ReproducerSaved, SubProcessFilePaths.ReproducerFilePath); - } - else if (request.Context.AnalyzerOptions?.CreatePreCompiledHeaders ?? false) - { - reader.ReadToEnd(); - logger.WriteLine(CFamilyStrings.MSG_PchSaved, request.Context.File, request.Context.PchFile); - } - else - { - using (var binaryReader = new BinaryReader(reader.BaseStream)) - { - Protocol.Read(binaryReader, handleMessage); - } - } - } - }; - - runner.Execute(args); - } - - private static void SaveRequestDiagnostics(IRequest request, ILogger logger, IFileSystem fileSystem) - { - using (var stream = fileSystem.FileStream.Create(SubProcessFilePaths.RequestConfigFilePath, FileMode.Create, FileAccess.Write)) - using (var writer = new StreamWriter(stream)) - { - request.WriteRequestDiagnostics(writer); - } - - logger.WriteLine(CFamilyStrings.MSG_RequestConfigSaved, SubProcessFilePaths.RequestConfigFilePath); } } } diff --git a/src/Integration.UnitTests/SLCore/SLCoreConstantsProviderTests.cs b/src/Integration.UnitTests/SLCore/SLCoreConstantsProviderTests.cs index 0ee28cef34..c1a57476b6 100644 --- a/src/Integration.UnitTests/SLCore/SLCoreConstantsProviderTests.cs +++ b/src/Integration.UnitTests/SLCore/SLCoreConstantsProviderTests.cs @@ -149,6 +149,8 @@ public void AnalyzableLanguages_ShouldBeExpected() Language.JS, Language.TS, Language.CSS, + Language.C, + Language.CPP, Language.SECRETS }; diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt index 42d3c0aa9f..1593f47ea9 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt @@ -70,7 +70,6 @@ Referenced assemblies: - 'Microsoft.VisualStudio.CoreUtility, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.Shell.Framework, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.Text.Data, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' -- 'Microsoft.VisualStudio.Threading, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'SonarLint.VisualStudio.Core, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' @@ -79,7 +78,7 @@ Referenced assemblies: - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' -# Number of references: 12 +# Number of references: 11 --- Assembly: 'SonarLint.VisualStudio.ConnectedMode, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt index 04c7332972..ac331b3f07 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt @@ -70,7 +70,6 @@ Referenced assemblies: - 'Microsoft.VisualStudio.CoreUtility, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.Shell.Framework, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.Text.Data, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' -- 'Microsoft.VisualStudio.Threading, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'SonarLint.VisualStudio.Core, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' @@ -79,7 +78,7 @@ Referenced assemblies: - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' -# Number of references: 12 +# Number of references: 11 --- Assembly: 'SonarLint.VisualStudio.ConnectedMode, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' diff --git a/src/Integration/SLCore/SLCoreConstantsProvider.cs b/src/Integration/SLCore/SLCoreConstantsProvider.cs index 5cdd32e317..37600dc6df 100644 --- a/src/Integration/SLCore/SLCoreConstantsProvider.cs +++ b/src/Integration/SLCore/SLCoreConstantsProvider.cs @@ -66,6 +66,8 @@ public SLCoreConstantsProvider(IVsInfoProvider vsInfoProvider) Language.JS, Language.TS, Language.CSS, + Language.C, + Language.CPP, Language.SECRETS ]; diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index a13d1ed1ca..0060faa55b 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -20,6 +20,7 @@ using NSubstitute.ExceptionExtensions; using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Core.ConfigurationScope; using SonarLint.VisualStudio.Core.SystemAbstractions; using SonarLint.VisualStudio.SLCore.Analysis; @@ -38,8 +39,9 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); } [TestMethod] @@ -47,7 +49,7 @@ public void MefCtor_CheckIsSingleton() { MefTestHelpers.CheckIsSingletonMefComponent(); } - + [TestMethod] public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() { @@ -55,7 +57,7 @@ public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() testSubject.IsAnalysisSupported([]).Should().BeTrue(); } - + [DataTestMethod] [DataRow(AnalysisLanguage.Javascript)] [DataRow(AnalysisLanguage.TypeScript)] @@ -74,54 +76,54 @@ public void ExecuteAnalysis_CreatesNotifierAndStarts() { var analysisStatusNotifierFactory = CreateDefaultAnalysisStatusNotifier(out var notifier); var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), @"C:\file\path"); notifier.Received().AnalysisStarted(); } - + [TestMethod] public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() { var activeConfigScopeTracker = Substitute.For(); activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, CreateDefaultAnalysisStatusNotifier(out var notifier)); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); } - + [TestMethod] public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() { var activeConfigScopeTracker = Substitute.For(); activeConfigScopeTracker.Current.Returns(new ConfigurationScope("someconfigscopeid", IsReadyForAnalysis: false)); var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, CreateDefaultAnalysisStatusNotifier(out var notifier)); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); } - + [TestMethod] public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() { var slCoreServiceProvider = CreatServiceProvider(out var analysisService, false); var testSubject = CreateTestSubject(slCoreServiceProvider, CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); analysisService.ReceivedCalls().Should().BeEmpty(); notifier.Received().AnalysisFailed(SLCoreStrings.ServiceProviderNotInitialized); } - + [TestMethod] public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() { @@ -131,15 +133,15 @@ public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() testSubject.ExecuteAnalysis(@"C:\file\path", analysisId, default, default, default, default); - analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => - a.analysisId == analysisId + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.analysisId == analysisId && a.configurationScopeId == "someconfigscopeid" && a.filesToAnalyze.Single() == new FileUri(@"C:\file\path") - && a.extraProperties != null + && a.extraProperties.Count == 0 && a.startTime == expectedTimeStamp.ToUnixTimeMilliseconds()), Arg.Any()); } - + [DataTestMethod] [DataRow(null, false)] [DataRow(false, false)] @@ -150,12 +152,43 @@ public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysis var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid")); testSubject.ExecuteAnalysis(@"C:\file\path", default, default, default, options, default); - - analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.shouldFetchServerIssues == expected), Arg.Any()); } + [TestMethod] + public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraProperties() + { + const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; + var compilationDatabaseLocator = WithCompilationDatabase(compilationDatabasePath); + var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); + var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); + + testSubject.ExecuteAnalysis(@"C:\file\path\myclass.cpp", Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.extraProperties != null + && a.extraProperties["sonar.cfamily.compile-commands"] == compilationDatabasePath), + Arg.Any()); + } + + [TestMethod] + public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExtraProperty() + { + var compilationDatabaseLocator = WithCompilationDatabase(null); + var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); + var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); + + testSubject.ExecuteAnalysis(@"C:\file\path\myclass.cpp", Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.extraProperties != null + && !a.extraProperties.ContainsKey("sonar.cfamily.compile-commands")), + Arg.Any()); + } + [TestMethod] public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() { @@ -164,57 +197,57 @@ public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid")); testSubject.ExecuteAnalysis(@"C:\file\path", analysisId, default, default, default, cancellationTokenSource.Token); - + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), cancellationTokenSource.Token); } - + [TestMethod] public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysis() { var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); - + notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(string)); notifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default, default); } - + [TestMethod] public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() { var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet{new(@"C:\file\path")}, [])); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); - + notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } - + [TestMethod] public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() { var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); var operationCanceledException = new OperationCanceledException(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); - + notifier.Received().AnalysisCancelled(); } - + [TestMethod] public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() { var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); var exception = new Exception(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); - + testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); - + notifier.Received().AnalysisFailed(exception); } @@ -255,19 +288,28 @@ private static ICurrentTimeProvider CreatCurrentTimeProvider(DateTimeOffset nowT return currentTimeProvider; } - private static SLCoreAnalyzer CreateTestSubject(ISLCoreServiceProvider slCoreServiceProvider = null, IActiveConfigScopeTracker activeConfigScopeTracker = null, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, - ICurrentTimeProvider currentTimeProvider = null) + IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, + ICurrentTimeProvider currentTimeProvider = null, + ICompilationDatabaseLocator compilationDatabaseLocator = null) { slCoreServiceProvider ??= Substitute.For(); activeConfigScopeTracker ??= Substitute.For(); analysisStatusNotifierFactory ??= Substitute.For(); currentTimeProvider ??= Substitute.For(); + compilationDatabaseLocator ??= Substitute.For(); return new SLCoreAnalyzer(slCoreServiceProvider, activeConfigScopeTracker, - analysisStatusNotifierFactory, - currentTimeProvider); + analysisStatusNotifierFactory, + currentTimeProvider, + compilationDatabaseLocator); + } + + private static ICompilationDatabaseLocator WithCompilationDatabase(string compilationDatabasePath) + { + var compilationDatabaseLocator = Substitute.For(); + compilationDatabaseLocator.Locate().Returns(compilationDatabasePath); + return compilationDatabaseLocator; } } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index 57959ea221..3eeb323634 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -21,6 +21,7 @@ using System.ComponentModel.Composition; using Microsoft.VisualStudio.Threading; using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Core.ConfigurationScope; using SonarLint.VisualStudio.Core.SystemAbstractions; using SonarLint.VisualStudio.SLCore.Common.Models; @@ -33,21 +34,26 @@ namespace SonarLint.VisualStudio.SLCore.Analysis; [PartCreationPolicy(CreationPolicy.Shared)] public class SLCoreAnalyzer : IAnalyzer { + private const string CFamilyCompileCommandsProperty = "sonar.cfamily.compile-commands"; + private readonly ISLCoreServiceProvider serviceProvider; private readonly IActiveConfigScopeTracker activeConfigScopeTracker; private readonly IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; private readonly ICurrentTimeProvider currentTimeProvider; + private readonly ICompilationDatabaseLocator compilationDatabaseLocator; [ImportingConstructor] - public SLCoreAnalyzer(ISLCoreServiceProvider serviceProvider, + public SLCoreAnalyzer(ISLCoreServiceProvider serviceProvider, IActiveConfigScopeTracker activeConfigScopeTracker, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, - ICurrentTimeProvider currentTimeProvider) + IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, + ICurrentTimeProvider currentTimeProvider, + ICompilationDatabaseLocator compilationDatabaseLocator) { this.serviceProvider = serviceProvider; this.activeConfigScopeTracker = activeConfigScopeTracker; this.analysisStatusNotifierFactory = analysisStatusNotifierFactory; this.currentTimeProvider = currentTimeProvider; + this.compilationDatabaseLocator = compilationDatabaseLocator; } public bool IsAnalysisSupported(IEnumerable languages) @@ -57,32 +63,35 @@ public bool IsAnalysisSupported(IEnumerable languages) public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) - { + { var analysisStatusNotifier = analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), path); analysisStatusNotifier.AnalysisStarted(); - + var configurationScope = activeConfigScopeTracker.Current; if (configurationScope is not { IsReadyForAnalysis: true }) { analysisStatusNotifier.AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); return; } - + if (!serviceProvider.TryGetTransientService(out IAnalysisSLCoreService analysisService)) { analysisStatusNotifier.AnalysisFailed(SLCoreStrings.ServiceProviderNotInitialized); return; } - - ExecuteAnalysisInternalAsync(path, configurationScope.Id, analysisId, analyzerOptions, analysisService, analysisStatusNotifier, cancellationToken).Forget(); + + var extraProperties = GetExtraProperties(detectedLanguages); + + ExecuteAnalysisInternalAsync(path, configurationScope.Id, analysisId, analyzerOptions, analysisService, analysisStatusNotifier, extraProperties, cancellationToken).Forget(); } private async Task ExecuteAnalysisInternalAsync(string path, string configScopeId, - Guid analysisId, + Guid analysisId, IAnalyzerOptions analyzerOptions, IAnalysisSLCoreService analysisService, IAnalysisStatusNotifier analysisStatusNotifier, + Dictionary extraProperties, CancellationToken cancellationToken) { try @@ -92,7 +101,7 @@ private async Task ExecuteAnalysisInternalAsync(string path, configScopeId, analysisId, [new FileUri(path)], - [], + extraProperties, analyzerOptions?.IsOnOpen ?? false, currentTimeProvider.Now.ToUnixTimeMilliseconds()), cancellationToken); @@ -112,4 +121,22 @@ [new FileUri(path)], analysisStatusNotifier.AnalysisFailed(e); } } + + private Dictionary GetExtraProperties(IEnumerable detectedLanguages) + { + Dictionary extraProperties = []; + if (!IsCFamily(detectedLanguages)) + { + return extraProperties; + } + + var compilationDatabasePath = compilationDatabaseLocator.Locate(); + if (compilationDatabasePath != null) + { + extraProperties[CFamilyCompileCommandsProperty] = compilationDatabasePath; + } + return extraProperties; + } + + private static bool IsCFamily(IEnumerable detectedLanguages) => detectedLanguages != null && detectedLanguages.Contains(AnalysisLanguage.CFamily); } From 25d7350e89b8f6675722944cd1f2caf643f1b9c9 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:54:03 +0100 Subject: [PATCH 02/21] SLVS-1664 Add mef-exportable IFileSystemService (#5859) [SLVS-1664](https://sonarsource.atlassian.net/browse/SLVS-1664) Part of SLVS-1637 [SLVS-1664]: https://sonarsource.atlassian.net/browse/SLVS-1664?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../FileSystemServiceTests.cs | 34 +++++++++++++++++++ .../SystemAbstractions/IFileSystemService.cs | 30 ++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/Core.UnitTests/SystemAbstractions/FileSystemServiceTests.cs create mode 100644 src/Core/SystemAbstractions/IFileSystemService.cs diff --git a/src/Core.UnitTests/SystemAbstractions/FileSystemServiceTests.cs b/src/Core.UnitTests/SystemAbstractions/FileSystemServiceTests.cs new file mode 100644 index 0000000000..a85da5c774 --- /dev/null +++ b/src/Core.UnitTests/SystemAbstractions/FileSystemServiceTests.cs @@ -0,0 +1,34 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.Core.SystemAbstractions; +using SonarLint.VisualStudio.TestInfrastructure; + +namespace SonarLint.VisualStudio.Core.UnitTests.SystemAbstractions; + +[TestClass] +public class FileSystemServiceTests +{ + [TestMethod] + public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported(); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); +} diff --git a/src/Core/SystemAbstractions/IFileSystemService.cs b/src/Core/SystemAbstractions/IFileSystemService.cs new file mode 100644 index 0000000000..993783a567 --- /dev/null +++ b/src/Core/SystemAbstractions/IFileSystemService.cs @@ -0,0 +1,30 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ComponentModel.Composition; +using System.IO.Abstractions; + +namespace SonarLint.VisualStudio.Core.SystemAbstractions; + +public interface IFileSystemService : IFileSystem; // separate export interface to avoid conflicts + +[Export(typeof(IFileSystemService))] +[PartCreationPolicy(CreationPolicy.Shared)] +public sealed class FileSystemService : FileSystem, IFileSystemService; // exporting class From d4bf8ee3c02c7f72fbf8fdbb8d0199a58682dc7f Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:21:14 +0100 Subject: [PATCH 03/21] SLVS-1665 Make FileConfigProvider mef-exporting (#5860) [SLVS-1665](https://sonarsource.atlassian.net/browse/SLVS-1665) Part of SLVS-1637 [SLVS-1665]: https://sonarsource.atlassian.net/browse/SLVS-1665?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../CFamily/VcxProject/FileConfigProviderTests.cs | 12 ++++++++++-- .../CFamily/VcxProject/FileConfigProvider.cs | 13 +++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs index 0c031bfd5b..93081da3aa 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs @@ -34,11 +34,19 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject [TestClass] public class FileConfigProviderTests { + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); + [TestMethod] public void Get_FileIsNotInSolution_ReturnsNull() { var projectItemMock = CreateMockProjectItem("c:\\foo\\SingleFileISense\\xxx.vcxproj"); - + var testSubject = CreateTestSubject(); var result = testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); @@ -132,7 +140,7 @@ public void Get_FailsToRetrieveFileConfig_Pch_ExceptionNotLogged() public void Get_SuccessfulConfig_ConfigReturned() { var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj"); - + var testSubject = CreateTestSubject(); var result = testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs index b9de9e12a7..410c47a30f 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs @@ -19,6 +19,7 @@ */ using System; +using System.ComponentModel.Composition; using EnvDTE; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.CFamily.Analysis; @@ -31,16 +32,12 @@ internal interface IFileConfigProvider IFileConfig Get(ProjectItem projectItem, string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions); } - internal class FileConfigProvider : IFileConfigProvider + [Export(typeof(IFileConfigProvider))] + [PartCreationPolicy(CreationPolicy.Shared)] + [method: ImportingConstructor] + internal class FileConfigProvider(ILogger logger) : IFileConfigProvider { private static readonly NoOpLogger noOpLogger = new NoOpLogger(); - private readonly ILogger logger; - - public FileConfigProvider(ILogger logger) - { - this.logger = logger; - } - public IFileConfig Get(ProjectItem projectItem, string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) { From 9c22b6404f792353a3dada5908f92d2c10576856 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:00:56 +0100 Subject: [PATCH 04/21] SLVS-1671 Refactor IFileConfigProvider (#5862) [SLVS-1671](https://sonarsource.atlassian.net/browse/SLVS-1671) Part of SLVS-1637 [SLVS-1671]: https://sonarsource.atlassian.net/browse/SLVS-1671?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../VcxProject/FileConfigProviderTests.cs | 150 ++++--- .../FileInSolutionIndicatorTests.cs | 130 ++++++ .../CFamily/VcxRequestFactoryTests.cs | 394 ------------------ .../CFamily/VcxProject/FileConfig.cs | 2 + .../CFamily/VcxProject/FileConfigProvider.cs | 60 ++- .../VcxProject/IFileInSolutionIndicator.cs | 62 +++ .../CFamily/VcxProject/VcxRequestFactory.cs | 186 --------- 7 files changed, 297 insertions(+), 687 deletions(-) create mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileInSolutionIndicatorTests.cs delete mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxRequestFactoryTests.cs create mode 100644 src/Integration.Vsix/CFamily/VcxProject/IFileInSolutionIndicator.cs delete mode 100644 src/Integration.Vsix/CFamily/VcxProject/VcxRequestFactory.cs diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs index 93081da3aa..ce3e36f637 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs @@ -18,15 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.VisualStudio.VCProjectEngine; -using Moq; +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Shell.Interop; +using NSubstitute.ExceptionExtensions; +using NSubstitute.ReturnsExtensions; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.CFamily.Analysis; +using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; -using SonarLint.VisualStudio.TestInfrastructure; using static SonarLint.VisualStudio.Integration.Vsix.CFamily.UnitTests.CFamilyTestUtility; namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject @@ -34,124 +34,122 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject [TestClass] public class FileConfigProviderTests { + private const string SourceFilePath = "any path"; + private TestLogger logger; + private IFileInSolutionIndicator fileInSolutionIndicator; + private DTE2 dte; + private IVsUIServiceOperation uiServiceOperation; + private FileConfigProvider testSubject; + [TestMethod] public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); [TestMethod] public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - [TestMethod] - public void Get_FileIsNotInSolution_ReturnsNull() + [TestInitialize] + public void TestInitialize() { - var projectItemMock = CreateMockProjectItem("c:\\foo\\SingleFileISense\\xxx.vcxproj"); - - var testSubject = CreateTestSubject(); - var result = testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); + logger = new TestLogger(); + fileInSolutionIndicator = CreateDefaultFileInSolutionIndicator(); + dte = Substitute.For(); + uiServiceOperation = CreateDefaultUiServiceOperation(dte); - result.Should().BeNull(); - projectItemMock.Verify(x => x.ContainingProject, Times.Once); + testSubject = new FileConfigProvider(uiServiceOperation, fileInSolutionIndicator, logger, new NoOpThreadHandler()); } - [TestMethod] - public void Get_FailureToCheckIfFileIsInSolution_NonCriticalException_ExceptionCaughtAndNullReturned() + private static IFileInSolutionIndicator CreateDefaultFileInSolutionIndicator() { - var projectItemMock = CreateMockProjectItem("c:\\foo\\SingleFileISense\\xxx.vcxproj"); - projectItemMock.Setup(x => x.ContainingProject).Throws(); - - var testSubject = CreateTestSubject(); - var result = testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); - - result.Should().BeNull(); - projectItemMock.Verify(x => x.ContainingProject, Times.Once); + var mock = Substitute.For(); + mock.IsFileInSolution(Arg.Any()).Returns(true); + return mock; } - [TestMethod] - public void Get_FailureToCheckIfFileIsInSolution_CriticalException_ExceptionThrown() + private static IVsUIServiceOperation CreateDefaultUiServiceOperation(DTE2 dte2) { - var projectItemMock = CreateMockProjectItem("c:\\foo\\SingleFileISense\\xxx.vcxproj"); - projectItemMock.Setup(x => x.ContainingProject).Throws(); - - var testSubject = CreateTestSubject(); - Action act = () => testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); - - act.Should().Throw(); - projectItemMock.Verify(x => x.ContainingProject, Times.Once); + var mock = Substitute.For(); + mock.Execute(Arg.Any>()).Returns(info => info.Arg>()(dte2)); + return mock; } [TestMethod] - public void Get_FailsToRetrieveFileConfig_NonCriticalException_ExceptionCaughtAndNullReturned() + public void Get_FailsToRetrieveProjectItem_NonCriticalException_ExceptionCaughtAndNullReturned() { - var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj"); - var containingProject = Mock.Get(projectItemMock.Object.ContainingProject.Object as VCProject); - containingProject.Setup(x => x.ActiveConfiguration).Throws(); + dte.Solution.ThrowsForAnyArgs(); - var testSubject = CreateTestSubject(); - var result = testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); + var result = testSubject.Get(SourceFilePath, new CFamilyAnalyzerOptions()); result.Should().BeNull(); - containingProject.Verify(x => x.ActiveConfiguration, Times.Once); + logger.AssertPartialOutputStringExists(nameof(NotImplementedException), SourceFilePath); } [TestMethod] - public void Get_FailsToRetrieveFileConfig_CriticalException_ExceptionThrown() + public void Get_FailsToRetrieveProjectItem_NonCriticalException_Pch_ExceptionCaughtNotLoggedAndNullReturned() { - var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj"); - var containingProject = Mock.Get(projectItemMock.Object.ContainingProject.Object as VCProject); - containingProject.Setup(x => x.ActiveConfiguration).Throws(); + dte.Solution.ThrowsForAnyArgs(); - var testSubject = CreateTestSubject(); - Action act = () => testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); + var result = testSubject.Get(SourceFilePath, new CFamilyAnalyzerOptions{CreatePreCompiledHeaders = true}); - act.Should().Throw(); - containingProject.Verify(x => x.ActiveConfiguration, Times.Once); + result.Should().BeNull(); + logger.AssertNoOutputMessages(); } [TestMethod] - public void Get_FailsToRetrieveFileConfig_NotPch_ExceptionLogged() + public void Get_FailsToRetrieveProjectItem_CriticalException_ExceptionThrown() { - var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj"); - var containingProject = Mock.Get(projectItemMock.Object.ContainingProject.Object as VCProject); - containingProject.Setup(x => x.ActiveConfiguration).Throws(); + dte.Solution.ThrowsForAnyArgs(); - var logger = new TestLogger(); - var testSubject = CreateTestSubject(logger); - testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); + Action act = () => testSubject.Get(SourceFilePath, new CFamilyAnalyzerOptions()); - logger.AssertPartialOutputStringExists(nameof(NotImplementedException), "c:\\dummy"); + act.Should().Throw(); } [TestMethod] - public void Get_FailsToRetrieveFileConfig_Pch_ExceptionNotLogged() + public void Get_NoProjectItem_ReturnsNull() { - var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj"); - var containingProject = Mock.Get(projectItemMock.Object.ContainingProject.Object as VCProject); - containingProject.Setup(x => x.ActiveConfiguration).Throws(); + dte.Solution.FindProjectItem(SourceFilePath).ReturnsNull(); - var logger = new TestLogger(); - var testSubject = CreateTestSubject(logger); - testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions{CreatePreCompiledHeaders = true}); + testSubject.Get(SourceFilePath, null) + .Should().BeNull(); - logger.AssertNoOutputMessages(); + Received.InOrder(() => + { + uiServiceOperation.Execute(Arg.Any>()); + dte.Solution.FindProjectItem(SourceFilePath); + }); } [TestMethod] - public void Get_SuccessfulConfig_ConfigReturned() + public void Get_ProjectItemNotInSolution_ReturnsNull() { - var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj"); - - var testSubject = CreateTestSubject(); - var result = testSubject.Get(projectItemMock.Object, "c:\\dummy", new CFamilyAnalyzerOptions()); - - result.Should().NotBeNull(); + var mockProjectItem = CreateMockProjectItem(SourceFilePath); + dte.Solution.FindProjectItem(SourceFilePath).Returns(mockProjectItem.Object); + fileInSolutionIndicator.IsFileInSolution(mockProjectItem.Object).Returns(false); + + testSubject.Get(SourceFilePath, null) + .Should().BeNull(); + + Received.InOrder(() => + { + uiServiceOperation.Execute(Arg.Any>()); + dte.Solution.FindProjectItem(SourceFilePath); + fileInSolutionIndicator.IsFileInSolution(mockProjectItem.Object); + }); } - private FileConfigProvider CreateTestSubject(ILogger logger = null) + [TestMethod] + public void Get_SuccessfulConfig_ConfigReturned() { - logger ??= Mock.Of(); + var mockProjectItem = CreateMockProjectItem(SourceFilePath); + dte.Solution.FindProjectItem(SourceFilePath).Returns(mockProjectItem.Object); - return new FileConfigProvider(logger); + testSubject.Get(SourceFilePath, null) + .Should().NotBeNull(); } } } diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileInSolutionIndicatorTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileInSolutionIndicatorTests.cs new file mode 100644 index 0000000000..de486ffc17 --- /dev/null +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileInSolutionIndicatorTests.cs @@ -0,0 +1,130 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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 EnvDTE; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; +using static SonarLint.VisualStudio.Integration.Vsix.CFamily.UnitTests.CFamilyTestUtility; + +namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; + +[TestClass] +public class FileInSolutionIndicatorTests +{ + private FileInSolutionIndicator testSubject; + private IThreadHandling threadHandling; + + [TestInitialize] + public void TestInitialize() + { + threadHandling = Substitute.For(); + testSubject = new FileInSolutionIndicator(threadHandling); + } + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => + MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestMethod] + public void Get_FileIsNotInSolution_ReturnsFalse() + { + var projectItemMock = CreateMockProjectItem("c:\\foo\\SingleFileISense\\xxx.vcxproj"); + + testSubject.IsFileInSolution(projectItemMock.Object) + .Should().BeFalse(); + + projectItemMock.Verify(x => x.ContainingProject); + threadHandling.Received().ThrowIfNotOnUIThread(); + } + + [TestMethod] + public void Get_NoConfigurationManager_ReturnsFalse() + { + var projectItemMock = CreateMockProjectItem("c:\\foo\\Any\\xxx.vcxproj"); + projectItemMock.SetupGet(x => x.ConfigurationManager).Returns(null as ConfigurationManager); + + testSubject.IsFileInSolution(projectItemMock.Object) + .Should().BeFalse(); + + projectItemMock.Verify(x => x.ContainingProject); + projectItemMock.Verify(x => x.ConfigurationManager); + threadHandling.Received().ThrowIfNotOnUIThread(); + } + + [TestMethod] + public void Get_NoConfiguration_ReturnsFalse() + { + var projectItemMock = CreateMockProjectItem("c:\\foo\\Any\\xxx.vcxproj"); + var configurationManagerMock = Mock.Get(projectItemMock.Object.ConfigurationManager); + configurationManagerMock.SetupGet(x => x.ActiveConfiguration).Returns(null as Configuration); + + testSubject.IsFileInSolution(projectItemMock.Object) + .Should().BeFalse(); + + projectItemMock.Verify(x => x.ContainingProject); + projectItemMock.Verify(x => x.ConfigurationManager); + configurationManagerMock.Verify(x => x.ActiveConfiguration); + threadHandling.Received().ThrowIfNotOnUIThread(); + } + + [TestMethod] + public void Get_FileInSolution_ReturnsTrue() + { + var projectItemMock = CreateMockProjectItem("c:\\foo\\Any\\xxx.vcxproj"); + + testSubject.IsFileInSolution(projectItemMock.Object) + .Should().BeTrue(); + + projectItemMock.Verify(x => x.ContainingProject); + projectItemMock.Verify(x => x.ConfigurationManager); + threadHandling.Received().ThrowIfNotOnUIThread(); + } + + [TestMethod] + public void Get_FailureToCheckIfFileIsInSolution_NonCriticalException_ExceptionCaughtAndFalseReturned() + { + var projectItemMock = CreateMockProjectItem("c:\\foo\\Any\\xxx.vcxproj"); + projectItemMock.Setup(x => x.ContainingProject).Throws(); + + testSubject.IsFileInSolution(projectItemMock.Object) + .Should().BeFalse(); + + projectItemMock.Verify(x => x.ContainingProject); + threadHandling.Received().ThrowIfNotOnUIThread(); + } + + [TestMethod] + public void Get_FailureToCheckIfFileIsInSolution_CriticalException_ExceptionThrown() + { + var projectItemMock = CreateMockProjectItem("c:\\foo\\Any\\xxx.vcxproj"); + projectItemMock.Setup(x => x.ContainingProject).Throws(); + + Action act = () => testSubject.IsFileInSolution(projectItemMock.Object); + + act.Should().Throw(); + projectItemMock.Verify(x => x.ContainingProject); + threadHandling.Received().ThrowIfNotOnUIThread(); + } +} diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxRequestFactoryTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxRequestFactoryTests.cs deleted file mode 100644 index e610e56184..0000000000 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxRequestFactoryTests.cs +++ /dev/null @@ -1,394 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Threading.Tasks; -using EnvDTE; -using EnvDTE80; -using FluentAssertions; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.CompilationDatabase; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; -using SonarLint.VisualStudio.TestInfrastructure; -using static SonarLint.VisualStudio.Integration.Vsix.CFamily.UnitTests.CFamilyTestUtility; -using VsShell = Microsoft.VisualStudio.Shell; - -namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily -{ - [TestClass] - public class VcxRequestFactoryTests - { - private static ProjectItem DummyProjectItem = Mock.Of(); - private static CFamilyAnalyzerOptions DummyAnalyzerOptions = new CFamilyAnalyzerOptions(); - - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void MefCtor_CheckIsSingleton() - => MefTestHelpers.CheckIsSingletonMefComponent(); - - [TestMethod] - public void MefCtor_DoesNotCallAnyServices() - { - var serviceProvider = new Mock(); - var cFamilyRulesConfigProvider = new Mock(); - var logger = new Mock(); - var threadHandling = new Mock(); - - _ = new VcxRequestFactory(serviceProvider.Object, cFamilyRulesConfigProvider.Object, logger.Object, threadHandling.Object); - - // The MEF constructor should be free-threaded, which it will be if - // it doesn't make any external calls. - serviceProvider.Invocations.Should().BeEmpty(); - cFamilyRulesConfigProvider.Invocations.Should().BeEmpty(); - logger.Invocations.Should().BeEmpty(); - threadHandling.Invocations.Should().BeEmpty(); - } - - [TestMethod] - public async Task TryGet_RunsOnUIThread() - { - var fileConfigProvider = new Mock(); - - var threadHandling = new Mock(); - threadHandling.Setup(x => x.RunOnUIThreadAsync(It.IsAny())) - .Callback(op => - { - // Try to check that the product code is executed inside the "RunOnUIThread" call - fileConfigProvider.Invocations.Count.Should().Be(0); - op(); - fileConfigProvider.Invocations.Count.Should().Be(1); - }); - - var testSubject = CreateTestSubject(projectItem: DummyProjectItem, - threadHandling: threadHandling.Object, - fileConfigProvider: fileConfigProvider.Object); - - - var request = await testSubject.TryCreateAsync("any", new CFamilyAnalyzerOptions()); - - threadHandling.Verify(x => x.RunOnUIThreadAsync(It.IsAny()), Times.Once); - threadHandling.Verify(x => x.ThrowIfNotOnUIThread(), Times.Exactly(2)); - } - - [TestMethod] - public async Task TryGet_NoProjectItem_Null() - { - var testSubject = CreateTestSubject(projectItem: null); - - var request = await testSubject.TryCreateAsync("path", new CFamilyAnalyzerOptions()); - - request.Should().BeNull(); - } - - [TestMethod] - public async Task TryGet_NoFileConfig_Null() - { - const string analyzedFilePath = "path"; - var fileConfigProvider = SetupFileConfigProvider(DummyProjectItem, DummyAnalyzerOptions, analyzedFilePath, null); - - var testSubject = CreateTestSubject(DummyProjectItem, fileConfigProvider: fileConfigProvider.Object); - var request = await testSubject.TryCreateAsync("path", DummyAnalyzerOptions); - - request.Should().BeNull(); - - fileConfigProvider.Verify(x => x.Get(DummyProjectItem, analyzedFilePath, DummyAnalyzerOptions), Times.Once); - } - - [TestMethod] - public async Task TryGet_RequestCreatedWithNoDetectedLanguage_Null() - { - const string analyzedFilePath = "c:\\notCFamilyFile.txt"; - - var fileConfig = CreateDummyFileConfig(analyzedFilePath); - var fileConfigProvider = SetupFileConfigProvider(DummyProjectItem, DummyAnalyzerOptions, analyzedFilePath, fileConfig.Object); - var cFamilyRulesConfigProvider = new Mock(); - - var testSubject = CreateTestSubject(DummyProjectItem, - fileConfigProvider: fileConfigProvider.Object, - cFamilyRulesConfigProvider: cFamilyRulesConfigProvider.Object); - - var request = await testSubject.TryCreateAsync(analyzedFilePath, DummyAnalyzerOptions); - - request.Should().BeNull(); - - fileConfig.VerifyGet(x => x.CDFile, Times.Once); - cFamilyRulesConfigProvider.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public async Task TryGet_FailureParsing_NonCriticialException_Null() - { - const string analyzedFilePath = "c:\\test.cpp"; - - var fileConfig = CreateDummyFileConfig(analyzedFilePath); - var fileConfigProvider = SetupFileConfigProvider(DummyProjectItem, DummyAnalyzerOptions, analyzedFilePath, fileConfig.Object); - - var cFamilyRulesConfigProvider = new Mock(); - cFamilyRulesConfigProvider - .Setup(x => x.GetRulesConfiguration(SonarLanguageKeys.CPlusPlus)) - .Throws(); - - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(DummyProjectItem, - fileConfigProvider: fileConfigProvider.Object, - cFamilyRulesConfigProvider: cFamilyRulesConfigProvider.Object, - logger: logger); - - var request = await testSubject.TryCreateAsync(analyzedFilePath, DummyAnalyzerOptions); - - request.Should().BeNull(); - - logger.AssertPartialOutputStringExists(nameof(NotImplementedException)); - } - - [TestMethod] - public void TryGet_FailureParsing_CriticalException_ExceptionThrown() - { - const string analyzedFilePath = "c:\\test.cpp"; - - var fileConfig = CreateDummyFileConfig(analyzedFilePath); - var fileConfigProvider = SetupFileConfigProvider(DummyProjectItem, DummyAnalyzerOptions, analyzedFilePath, fileConfig.Object); - - var cFamilyRulesConfigProvider = new Mock(); - cFamilyRulesConfigProvider - .Setup(x => x.GetRulesConfiguration(SonarLanguageKeys.CPlusPlus)) - .Throws(); - - var testSubject = CreateTestSubject(DummyProjectItem, - fileConfigProvider: fileConfigProvider.Object, - cFamilyRulesConfigProvider: cFamilyRulesConfigProvider.Object); - - Func act = () => testSubject.TryCreateAsync(analyzedFilePath, DummyAnalyzerOptions); - - act.Should().ThrowExactly(); - } - - [TestMethod] - public async Task TryGet_IRequestPropertiesAreSet() - { - var analyzerOptions = new CFamilyAnalyzerOptions(); - var rulesConfig = Mock.Of(); - - var request = await GetSuccessfulRequest(analyzerOptions, "d:\\xxx\\fileToAnalyze.cpp", rulesConfig); - request.Should().NotBeNull(); - - request.Context.File.Should().Be("d:\\xxx\\fileToAnalyze.cpp"); - request.Context.PchFile.Should().Be(SubProcessFilePaths.PchFilePath); - request.Context.AnalyzerOptions.Should().BeSameAs(analyzerOptions); - request.Context.RulesConfiguration.Should().BeSameAs(rulesConfig); - } - - [TestMethod] - public async Task TryGet_FileConfigIsSet() - { - var request = await GetSuccessfulRequest(); - request.Should().NotBeNull(); - - request.DatabaseEntry.Should().NotBeNull(); - } - - [TestMethod] - public async Task TryGet_NonHeaderFile_IsSupported() - { - var request = await GetSuccessfulRequest(); - - request.Should().NotBeNull(); - request.Context.IsHeaderFile.Should().Be(false); - } - - [TestMethod] - public async Task TryGet_HeaderFile_IsSupported() - { - var projectItemConfig = new ProjectItemConfig { ItemType = "ClInclude" }; - var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); - - var fileConfig = CreateDummyFileConfig("c:\\dummy\\file.h"); - fileConfig.Setup(x => x.HeaderFileLanguage).Returns("cpp"); - - var request = await GetSuccessfulRequest(fileToAnalyze: "c:\\dummy\\file.h", projectItem: projectItemMock.Object, fileConfig: fileConfig); - - request.Should().NotBeNull(); - request.Context.IsHeaderFile.Should().Be(true); - } - - [TestMethod] - public async Task TryGet_NoAnalyzerOptions_RequestCreatedWithoutOptions() - { - var request = await GetSuccessfulRequest(analyzerOptions: null); - request.Should().NotBeNull(); - - (request.Context.AnalyzerOptions).Should().BeNull(); - } - - [TestMethod] - public async Task TryGet_AnalyzerOptionsWithReproducerEnabled_RequestCreatedWithReproducerFlag() - { - var request = await GetSuccessfulRequest(new CFamilyAnalyzerOptions { CreateReproducer = true }); - request.Should().NotBeNull(); - - (request.Context.AnalyzerOptions.CreateReproducer).Should().Be(true); - } - - [TestMethod] - public async Task TryGet_AnalyzerOptionsWithoutReproducerEnabled_RequestCreatedWithoutReproducerFlag() - { - var request = await GetSuccessfulRequest(new CFamilyAnalyzerOptions { CreateReproducer = false }); - request.Should().NotBeNull(); - - (request.Context.AnalyzerOptions.CreateReproducer).Should().Be(false); - } - - [TestMethod] - public async Task TryGet_AnalyzerOptionsWithPCH_RequestCreatedWithPCHFlag() - { - var request = await GetSuccessfulRequest(new CFamilyAnalyzerOptions { CreatePreCompiledHeaders = true }); - request.Should().NotBeNull(); - - (request.Context.AnalyzerOptions.CreatePreCompiledHeaders).Should().Be(true); - } - - [TestMethod] - public async Task TryGet_AnalyzerOptionsWithoutPCH_RequestCreatedWithoutPCHFlag() - { - var request = await GetSuccessfulRequest(new CFamilyAnalyzerOptions { CreatePreCompiledHeaders = false }); - request.Should().NotBeNull(); - - (request.Context.AnalyzerOptions.CreatePreCompiledHeaders).Should().Be(false); - } - - [TestMethod] - public async Task TryGet_AnalyzerOptionsWithPCH_RequestOptionsNotSet() - { - var request = await GetSuccessfulRequest(new CFamilyAnalyzerOptions { CreatePreCompiledHeaders = true }); - request.Should().NotBeNull(); - - request.Context.RulesConfiguration.Should().BeNull(); - (request.Context.AnalyzerOptions.CreateReproducer).Should().Be(false); - (request.Context.AnalyzerOptions.CreatePreCompiledHeaders).Should().Be(true); - } - - private static Mock SetupFileConfigProvider(ProjectItem projectItem, - CFamilyAnalyzerOptions analyzerOptions, - string analyzedFilePath, - IFileConfig fileConfigToReturn) - { - var fileConfigProvider = new Mock(); - fileConfigProvider - .Setup(x => x.Get(projectItem, analyzedFilePath, analyzerOptions)) - .Returns(fileConfigToReturn); - - return fileConfigProvider; - } - - private VcxRequestFactory CreateTestSubject(ProjectItem projectItem, - ICFamilyRulesConfigProvider cFamilyRulesConfigProvider = null, - IFileConfigProvider fileConfigProvider = null, - IThreadHandling threadHandling = null, - ILogger logger = null) - { - var serviceProvider = CreateServiceProviderReturningProjectItem(projectItem); - - cFamilyRulesConfigProvider ??= Mock.Of(); - fileConfigProvider ??= Mock.Of(); - threadHandling ??= new NoOpThreadHandler(); - logger ??= Mock.Of(); - - return new VcxRequestFactory(serviceProvider.Object, - cFamilyRulesConfigProvider, - new Lazy(() => fileConfigProvider), - logger, - threadHandling); - } - - private static Mock CreateServiceProviderReturningProjectItem(ProjectItem projectItemToReturn) - { - var mockSolution = new Mock(); - mockSolution.Setup(s => s.FindProjectItem(It.IsAny())).Returns(projectItemToReturn); - - var mockDTE = new Mock(); - mockDTE.Setup(d => d.Solution).Returns(mockSolution.Object); - - var mockServiceProvider = new Mock(); - mockServiceProvider.Setup(s => s.GetService(typeof(SDTE))).Returns(mockDTE.Object); - - return mockServiceProvider; - } - - private async Task GetSuccessfulRequest(CFamilyAnalyzerOptions analyzerOptions = null, - string fileToAnalyze = "c:\\foo\\file.cpp", - ICFamilyRulesConfig rulesConfig = null, - ProjectItem projectItem = null, - Mock fileConfig = null, - RuleConfigProtocolFormat protocolFormat = null) - { - rulesConfig ??= Mock.Of(); - - var rulesConfigProviderMock = new Mock(); - - rulesConfigProviderMock - .Setup(x => x.GetRulesConfiguration(It.IsAny())) - .Returns(rulesConfig); - - projectItem ??= Mock.Of(); - - fileConfig ??= CreateDummyFileConfig(fileToAnalyze); - var fileConfigProvider = SetupFileConfigProvider(projectItem, analyzerOptions, fileToAnalyze, fileConfig.Object); - - protocolFormat ??= new RuleConfigProtocolFormat("qp", new Dictionary()); - - - var testSubject = CreateTestSubject(projectItem, - rulesConfigProviderMock.Object, - fileConfigProvider.Object); - - return await testSubject.TryCreateAsync(fileToAnalyze, analyzerOptions) as CompilationDatabaseRequest; - } - - private Mock CreateDummyFileConfig(string filePath) - { - var fileConfig = new Mock(); - - fileConfig.SetupGet(x => x.CDDirectory).Returns("c:\\"); - fileConfig.SetupGet(x => x.CDCommand).Returns("cl.exe " + filePath); - fileConfig.SetupGet(x => x.CDFile).Returns(filePath); - fileConfig.SetupGet(x => x.EnvInclude).Returns(""); - fileConfig.SetupGet(x => x.HeaderFileLanguage).Returns(""); - - return fileConfig; - } - } -} diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs index c6a1ae149d..cdb23dbefb 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Diagnostics.CodeAnalysis; using System.IO; using EnvDTE; using Microsoft.VisualStudio.VCProjectEngine; @@ -27,6 +28,7 @@ namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject { internal class FileConfig : IFileConfig { + [ExcludeFromCodeCoverage] public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, string absoluteFilePath) { if (!(dteProjectItem.ContainingProject.Object is VCProject vcProject) || diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs index 410c47a30f..efdafb4959 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs @@ -21,36 +21,57 @@ using System; using System.ComponentModel.Composition; using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Shell.Interop; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.CFamily.Analysis; +using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.Integration.Helpers; namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject { internal interface IFileConfigProvider { - IFileConfig Get(ProjectItem projectItem, string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions); + IFileConfig Get(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions); } [Export(typeof(IFileConfigProvider))] [PartCreationPolicy(CreationPolicy.Shared)] [method: ImportingConstructor] - internal class FileConfigProvider(ILogger logger) : IFileConfigProvider + internal class FileConfigProvider( + IVsUIServiceOperation uiServiceOperation, + IFileInSolutionIndicator fileInSolutionIndicator, + ILogger logger, + IThreadHandling threadHandling) : IFileConfigProvider { private static readonly NoOpLogger noOpLogger = new NoOpLogger(); - public IFileConfig Get(ProjectItem projectItem, string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) + public IFileConfig Get(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) { var analysisLogger = GetAnalysisLogger(analyzerOptions); - if (!IsFileInSolution(projectItem)) - { - analysisLogger.LogVerbose($"[VCX:FileConfigProvider] The file is not part of a VCX project. File: {analyzedFilePath}"); - return null; - } + return uiServiceOperation.Execute(dte => + GetInternal(analyzedFilePath, dte, analysisLogger)); + } + + private FileConfig GetInternal(string analyzedFilePath, DTE2 dte, ILogger analysisLogger) + { + threadHandling.ThrowIfNotOnUIThread(); try { + var projectItem = dte.Solution.FindProjectItem(analyzedFilePath); + + if (projectItem == null) + { + return null; + } + + if (!fileInSolutionIndicator.IsFileInSolution(projectItem)) + { + analysisLogger.LogVerbose($"[VCX:FileConfigProvider] The file is not part of a VCX project. File: {analyzedFilePath}"); + return null; + } // Note: if the C++ tools are not installed then it's likely an exception will be thrown when // the framework tries to JIT-compile the TryGet method (since it won't be able to find the MS.VS.VCProjectEngine // types). @@ -76,29 +97,6 @@ private ILogger GetAnalysisLogger(CFamilyAnalyzerOptions analyzerOptions) return logger; } - internal static bool IsFileInSolution(ProjectItem projectItem) - { - try - { - // Issue 667: https://github.com/SonarSource/sonarlint-visualstudio/issues/667 - // If you open a C++ file that is not part of the current solution then - // VS will cruft up a temporary vcxproj so that it can provide language - // services for the file (e.g. syntax highlighting). This means that - // even though we have what looks like a valid project item, it might - // not actually belong to a real project. - var indexOfSingleFileString = projectItem?.ContainingProject?.FullName.IndexOf("SingleFileISense", StringComparison.OrdinalIgnoreCase); - return indexOfSingleFileString.HasValue && - indexOfSingleFileString.Value <= 0 && - projectItem.ConfigurationManager != null && - // the next line will throw if the file is not part of a solution - projectItem.ConfigurationManager.ActiveConfiguration != null; - } - catch (Exception ex) when (!Microsoft.VisualStudio.ErrorHandler.IsCriticalException(ex)) - { - // Suppress non-critical exceptions - } - return false; - } private class NoOpLogger : ILogger { diff --git a/src/Integration.Vsix/CFamily/VcxProject/IFileInSolutionIndicator.cs b/src/Integration.Vsix/CFamily/VcxProject/IFileInSolutionIndicator.cs new file mode 100644 index 0000000000..9b3ce323a0 --- /dev/null +++ b/src/Integration.Vsix/CFamily/VcxProject/IFileInSolutionIndicator.cs @@ -0,0 +1,62 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ComponentModel.Composition; +using EnvDTE; +using SonarLint.VisualStudio.Core; + +namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +public interface IFileInSolutionIndicator +{ + bool IsFileInSolution(ProjectItem projectItem); +} + +[Export(typeof(IFileInSolutionIndicator))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method:ImportingConstructor] +public class FileInSolutionIndicator(IThreadHandling threadHandling) : IFileInSolutionIndicator +{ + public bool IsFileInSolution(ProjectItem projectItem) + { + threadHandling.ThrowIfNotOnUIThread(); + + try + { + // Issue 667: https://github.com/SonarSource/sonarlint-visualstudio/issues/667 + // If you open a C++ file that is not part of the current solution then + // VS will cruft up a temporary vcxproj so that it can provide language + // services for the file (e.g. syntax highlighting). This means that + // even though we have what looks like a valid project item, it might + // not actually belong to a real project. + var indexOfSingleFileString = projectItem?.ContainingProject?.FullName.IndexOf("SingleFileISense", StringComparison.OrdinalIgnoreCase); + return indexOfSingleFileString.HasValue && + indexOfSingleFileString.Value <= 0 && + projectItem.ConfigurationManager != null && + // the next line will throw if the file is not part of a solution + projectItem.ConfigurationManager.ActiveConfiguration != null; + } + catch (Exception ex) when (!Microsoft.VisualStudio.ErrorHandler.IsCriticalException(ex)) + { + // Suppress non-critical exceptions + } + return false; + } +} diff --git a/src/Integration.Vsix/CFamily/VcxProject/VcxRequestFactory.cs b/src/Integration.Vsix/CFamily/VcxProject/VcxRequestFactory.cs deleted file mode 100644 index 136fda736f..0000000000 --- a/src/Integration.Vsix/CFamily/VcxProject/VcxRequestFactory.cs +++ /dev/null @@ -1,186 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel.Composition; -using System.Threading.Tasks; -using EnvDTE80; -using Microsoft.VisualStudio.Shell.Interop; -using SonarLint.VisualStudio.CFamily; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.CMake; -using SonarLint.VisualStudio.CFamily.CompilationDatabase; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using VsShell = Microsoft.VisualStudio.Shell; - -namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject -{ - [Export(typeof(IRequestFactory))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class VcxRequestFactory : IRequestFactory - { - private readonly ICFamilyRulesConfigProvider cFamilyRulesConfigProvider; - private readonly IThreadHandling threadHandling; - private readonly Lazy fileConfigProvider; - private readonly ILogger logger; - private readonly Lazy dte; - - [ImportingConstructor] - public VcxRequestFactory([Import(typeof(VsShell.SVsServiceProvider))] IServiceProvider serviceProvider, - ICFamilyRulesConfigProvider cFamilyRulesConfigProvider, - ILogger logger, - IThreadHandling threadHandling) -// Suppress FP. The call to threadHandling.ThrowIfNotOnUIThread() inside the Lazy<> causes the -// incorrect identify this constructor as needing to do a threading check. -#pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread - : this(serviceProvider, - cFamilyRulesConfigProvider, - new Lazy(() => new FileConfigProvider(logger)), - logger, - threadHandling) -#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread - { - } - - internal VcxRequestFactory(IServiceProvider serviceProvider, - ICFamilyRulesConfigProvider rulesConfigProvider, - Lazy fileConfigProvider, - ILogger logger, - IThreadHandling threadHandling) - { - this.cFamilyRulesConfigProvider = rulesConfigProvider; - this.threadHandling = threadHandling; - this.fileConfigProvider = fileConfigProvider; - this.logger = logger; - - this.dte = new Lazy(() => - { - threadHandling.ThrowIfNotOnUIThread(); - return serviceProvider.GetService(); - }); - } - - public async Task TryCreateAsync(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) - { - threadHandling.ThrowIfOnUIThread(); - - try - { - LogDebug("Trying to create request for " + analyzedFilePath); - - IFileConfig fileConfig = null; - - await threadHandling.RunOnUIThreadAsync(() => - { - fileConfig = GetFileConfigSync(analyzedFilePath, analyzerOptions); - }); - - if (fileConfig == null) - { - return null; - } - - var request = CreateRequest(analyzedFilePath, analyzerOptions, fileConfig); - - LogDebug("\tCreated request successfully"); - - return request; - } - catch (Exception ex) when (!Microsoft.VisualStudio.ErrorHandler.IsCriticalException(ex)) - { - logger.WriteLine(CFamilyStrings.ERROR_CreatingVcxRequest, analyzedFilePath, ex); - return null; - } - } - - private IFileConfig GetFileConfigSync(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) - { - threadHandling.ThrowIfNotOnUIThread(); - - var projectItem = dte?.Value.Solution?.FindProjectItem(analyzedFilePath); - - if (projectItem == null) - { - LogDebug("\tCould not locate a project item"); - return null; - } - - var fileConfig = fileConfigProvider.Value.Get(projectItem, analyzedFilePath, analyzerOptions); - - if (fileConfig == null) - { - LogDebug("\tCould not get the file configuration"); - return null; - } - - return fileConfig; - } - - private IRequest CreateRequest(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions, IFileConfig fileConfig) - { - threadHandling.ThrowIfOnUIThread(); - - var dbEntry = new CompilationDatabaseEntry { - Directory = fileConfig.CDDirectory, - Command = fileConfig.CDCommand, - File = fileConfig.CDFile, - Arguments = null - }; - - var headerFileLang = fileConfig.HeaderFileLanguage == "cpp" ? SonarLanguageKeys.CPlusPlus : SonarLanguageKeys.C; - var isHeaderFile = !string.IsNullOrEmpty(fileConfig.HeaderFileLanguage); - var languageKey = isHeaderFile ? headerFileLang : CFamilyShared.FindLanguageFromExtension(dbEntry.File); - - if (languageKey == null) - { - return null; - } - ICFamilyRulesConfig rulesConfig = null; - - if (analyzerOptions == null || !analyzerOptions.CreatePreCompiledHeaders) - { - rulesConfig = cFamilyRulesConfigProvider.GetRulesConfiguration(languageKey); - } - - var context = new RequestContext( - languageKey, - rulesConfig, - analyzedFilePath, - SubProcessFilePaths.PchFilePath, - analyzerOptions, - isHeaderFile); - - var envVars = new ReadOnlyDictionary(new Dictionary() { - { "INCLUDE", fileConfig.EnvInclude } - }); - - return new CompilationDatabaseRequest(dbEntry, context, envVars); - } - - private void LogDebug(string message) - { - logger.LogVerbose($"[VCX:VcxRequestFactory] [Thread id: {System.Threading.Thread.CurrentThread.ManagedThreadId}] {message}"); - } - } -} From 8dcca29d4581e1c5d359111dc0879e90dc35914b Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:44:08 +0100 Subject: [PATCH 05/21] SLVS-1663 Implement VCX analysis of C/C++ files with SLCore (#5858) [SLVS-1663](https://sonarsource.atlassian.net/browse/SLVS-1663) Part of SLVS-1637 [SLVS-1663]: https://sonarsource.atlassian.net/browse/SLVS-1663?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- ...> CMakeCompilationDatabaseLocatorTests.cs} | 53 ++- .../CMake/CMakeRequestFactoryTests.cs | 226 ------------ .../CMake/CompilationConfigProviderTests.cs | 345 ------------------ ...egatingCompilationDatabaseProviderTests.cs | 89 +++++ .../PchCacheCleanerTests.cs | 136 ------- .../PreCompiledHeadersEventListenerTests.cs | 201 ---------- src/CFamily/Analysis/CLangAnalyzer.cs | 72 ---- ....cs => CMakeCompilationDatabaseLocator.cs} | 32 +- src/CFamily/CMake/CMakeRequestFactory.cs | 80 ---- .../CMake/CompilationConfigProvider.cs | 220 ----------- .../AggregatingCompilationDatabaseProvider.cs | 44 +++ .../IVCXCompilationDatabaseProvider.cs} | 13 +- .../PreCompiledHeaders/PchCacheCleaner.cs | 68 ---- .../PreCompiledHeadersEventListener.cs | 129 ------- ...IAggregatingCompilationDatabaseProvider.cs | 26 ++ src/Core/Helpers/PathHelper.cs | 14 +- .../VCXCompilationDatabaseProviderTests.cs | 86 +++++ .../VCXCompilationDatabaseStorageTests.cs | 139 +++++++ ...egration.Vsix_Baseline_WithStrongNames.txt | 7 +- ...ation.Vsix_Baseline_WithoutStrongNames.txt | 7 +- .../IVCXCompilationDatabaseStorage.cs | 99 +++++ .../VCXCompilationDatabaseProvider.cs | 42 +++ .../SonarLintDaemonPackage.cs | 16 +- .../Analysis/SLCoreAnalyzerTests.cs | 22 +- src/SLCore/Analysis/SLCoreAnalyzer.cs | 10 +- .../Extensions/FileSystemExtensions.cs | 31 +- 26 files changed, 613 insertions(+), 1594 deletions(-) rename src/CFamily.UnitTests/CMake/{CompilationDatabaseLocatorTests.cs => CMakeCompilationDatabaseLocatorTests.cs} (87%) delete mode 100644 src/CFamily.UnitTests/CMake/CMakeRequestFactoryTests.cs delete mode 100644 src/CFamily.UnitTests/CMake/CompilationConfigProviderTests.cs create mode 100644 src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs delete mode 100644 src/CFamily.UnitTests/PreCompiledHeaders/PchCacheCleanerTests.cs delete mode 100644 src/CFamily.UnitTests/PreCompiledHeaders/PreCompiledHeadersEventListenerTests.cs delete mode 100644 src/CFamily/Analysis/CLangAnalyzer.cs rename src/CFamily/CMake/{CompilationDatabaseLocator.cs => CMakeCompilationDatabaseLocator.cs} (83%) delete mode 100644 src/CFamily/CMake/CMakeRequestFactory.cs delete mode 100644 src/CFamily/CMake/CompilationConfigProvider.cs create mode 100644 src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs rename src/{Core/CFamily/ICompilationDatabaseLocator.cs => CFamily/IVCXCompilationDatabaseProvider.cs} (71%) delete mode 100644 src/CFamily/PreCompiledHeaders/PchCacheCleaner.cs delete mode 100644 src/CFamily/PreCompiledHeaders/PreCompiledHeadersEventListener.cs create mode 100644 src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs create mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs create mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs create mode 100644 src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs create mode 100644 src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs diff --git a/src/CFamily.UnitTests/CMake/CompilationDatabaseLocatorTests.cs b/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs similarity index 87% rename from src/CFamily.UnitTests/CMake/CompilationDatabaseLocatorTests.cs rename to src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs index bd63dfa77e..ccf41187d4 100644 --- a/src/CFamily.UnitTests/CMake/CompilationDatabaseLocatorTests.cs +++ b/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs @@ -18,22 +18,19 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.IO; using System.IO.Abstractions; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SonarLint.VisualStudio.CFamily.CMake; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.CFamily; +using SonarLint.VisualStudio.Core.SystemAbstractions; using SonarLint.VisualStudio.TestInfrastructure; using static SonarLint.VisualStudio.TestInfrastructure.Extensions.FileSystemExtensions; namespace SonarLint.VisualStudio.CFamily.UnitTests.CMake { [TestClass] - public class CompilationDatabaseLocatorTests + public class CMakeCompilationDatabaseLocatorTests { private const string RootDirectory = "dummy root"; @@ -43,8 +40,9 @@ public class CompilationDatabaseLocatorTests [TestMethod] public void MefCtor_CheckIsExported() { - MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -72,10 +70,9 @@ public void Locate_NoCMakeSettings_ReturnsDefaultLocationIfFileExists(bool fileE var defaultLocation = GetDefaultDatabaseFileLocation(activeConfiguration); - var fileSystem = new Mock(); - fileSystem.SetFileExists(defaultLocation, fileExists); + var fileSystem = Substitute.For().SetFileExists(defaultLocation, fileExists); - var testSubject = CreateTestSubject(RootDirectory, configProvider, cmakeSettingsProvider.Object, fileSystem.Object); + var testSubject = CreateTestSubject(RootDirectory, configProvider, cmakeSettingsProvider.Object, fileSystem); var result = testSubject.Locate(); @@ -132,16 +129,15 @@ public void Locate_HasCMakeSettingsFile_ReturnsConfiguredPathIfItExists(bool fil { var configProvider = CreateConfigProvider("my-config"); var cmakeSettings = CreateCMakeSettings("my-config", "folder"); - var cmakeSettingsProvider = CreateCMakeSettingsProvider(RootDirectory, + var cmakeSettingsProvider = CreateCMakeSettingsProvider(RootDirectory, new CMakeSettingsSearchResult(cmakeSettings, "", "")); var compilationDatabaseFullLocation = Path.GetFullPath( - Path.Combine("folder", CompilationDatabaseLocator.CompilationDatabaseFileName)); + Path.Combine("folder", CMakeCompilationDatabaseLocator.CompilationDatabaseFileName)); - var fileSystem = new Mock(); - fileSystem.SetFileExists(compilationDatabaseFullLocation, fileExists); + var fileSystem = Substitute.For().SetFileExists(compilationDatabaseFullLocation, fileExists); - var testSubject = CreateTestSubject(RootDirectory, configProvider, cmakeSettingsProvider.Object, fileSystem.Object, + var testSubject = CreateTestSubject(RootDirectory, configProvider, cmakeSettingsProvider.Object, fileSystem, macroEvaluationService: PassthroughMacroService); var result = testSubject.Locate(); @@ -199,8 +195,8 @@ public void Locate_MacroServiceIsCalled_RelativePath_ExpectedValueIsReturn(strin context.MacroEvalService.Invocations.Count.Should().Be(1); } - private static CMakeSettings CreateCMakeSettings(string activeConfigurationName, - string buildRoot, + private static CMakeSettings CreateCMakeSettings(string activeConfigurationName, + string buildRoot, string generator = "generator") => new() { @@ -217,15 +213,15 @@ private static CMakeSettings CreateCMakeSettings(string activeConfigurationName, private static string GetDefaultDatabaseFileLocation(string activeBuildConfiguration) => Path.GetFullPath(Path.Combine( - string.Format(CompilationDatabaseLocator.DefaultLocationFormat, + string.Format(CMakeCompilationDatabaseLocator.DefaultLocationFormat, RootDirectory, activeBuildConfiguration), - CompilationDatabaseLocator.CompilationDatabaseFileName)); + CMakeCompilationDatabaseLocator.CompilationDatabaseFileName)); - private static CompilationDatabaseLocator CreateTestSubject(string rootDirectory, + private static CMakeCompilationDatabaseLocator CreateTestSubject(string rootDirectory, IBuildConfigProvider buildConfigProvider = null, ICMakeSettingsProvider cMakeSettingsProvider = null, - IFileSystem fileSystem = null, + IFileSystemService fileSystem = null, ILogger logger = null, IMacroEvaluationService macroEvaluationService = null) { @@ -235,17 +231,17 @@ private static CompilationDatabaseLocator CreateTestSubject(string rootDirectory cMakeSettingsProvider ??= Mock.Of(); buildConfigProvider ??= Mock.Of(); logger ??= Mock.Of(); - fileSystem ??= new FileSystem(); + fileSystem ??= new FileSystemService(); macroEvaluationService ??= Mock.Of(); - return new CompilationDatabaseLocator(folderWorkspaceService.Object, buildConfigProvider, cMakeSettingsProvider, macroEvaluationService, fileSystem, logger); + return new CMakeCompilationDatabaseLocator(folderWorkspaceService.Object, buildConfigProvider, cMakeSettingsProvider, macroEvaluationService, fileSystem, logger); } private static IBuildConfigProvider CreateConfigProvider(string activeConfiguration) { var provider = new Mock(); provider.Setup(x => x.GetActiveConfig(It.IsAny())).Returns(activeConfiguration); - + return provider.Object; } @@ -288,10 +284,9 @@ public MacroEvalContext(string macroServiceReturnValue, string unevaluatedBuildR new CMakeSettingsSearchResult(cmakeSettings, cmakeSettingsFilePath, cmakeListsFilePath)); // Treat all files as existing - var fileSystem = new Mock(); - Func nonNullFilesExist = x => x != null; - fileSystem.Setup(x => x.File.Exists(It.IsAny())).Returns(nonNullFilesExist); - + var fileSystem = Substitute.For(); + fileSystem.File.Exists(Arg.Any()).Returns(call => call.Arg() != null); + MacroEvalService = new Mock(); MacroEvalService.Setup(x => x.Evaluate(unevaluatedBuildRoot, @@ -307,10 +302,10 @@ public MacroEvalContext(string macroServiceReturnValue, string unevaluatedBuildR Logger = new TestLogger(logToConsole: true); TestSubject = CreateTestSubject(workspaceRootDir, configProvider, cmakeSettingsProvider.Object, - fileSystem.Object, Logger, MacroEvalService.Object); + fileSystem, Logger, MacroEvalService.Object); } - public CompilationDatabaseLocator TestSubject { get; } + public CMakeCompilationDatabaseLocator TestSubject { get; } public Mock MacroEvalService { get; } public TestLogger Logger { get; } } diff --git a/src/CFamily.UnitTests/CMake/CMakeRequestFactoryTests.cs b/src/CFamily.UnitTests/CMake/CMakeRequestFactoryTests.cs deleted file mode 100644 index 9a81471039..0000000000 --- a/src/CFamily.UnitTests/CMake/CMakeRequestFactoryTests.cs +++ /dev/null @@ -1,226 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.CMake.UnitTests -{ - [TestClass] - public class CMakeRequestFactoryTests - { - private static readonly IEnvironmentVarsProvider ValidEnvVarsProvider = CreateEnvVarsProvider(new Dictionary { { "key", "value" } }).Object; - private static readonly ICFamilyRulesConfigProvider ValidRulesConfigProvider_Cpp = CreateRulesProvider(SonarLanguageKeys.CPlusPlus, new DummyCFamilyRulesConfig((SonarLanguageKeys.CPlusPlus))).Object; - private const string ValidFileName_Cpp = "any.cpp"; - private static readonly ICompilationConfigProvider ValidCompilationConfigProvider = CreateCompilationProvider(ValidFileName_Cpp, CreateCompilationDatabaseEntry(ValidFileName_Cpp)).Object; - private static readonly CFamilyAnalyzerOptions ValidAnalyzerOptions = new CFamilyAnalyzerOptions(); - - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public async Task TryGet_NoConfig_ReturnsNull() - { - const string fileName = "c:\\file.cpp"; - - var compilationConfigProvider = CreateCompilationProvider(fileName, null); - var rulesConfigProvider = new Mock(); - - var testSubject = CreateTestSubject(compilationConfigProvider.Object, rulesConfigProvider.Object, ValidEnvVarsProvider); - - var actual = await testSubject.TryCreateAsync(fileName, new CFamilyAnalyzerOptions()); - - actual.Should().BeNull(); - compilationConfigProvider.VerifyAll(); - rulesConfigProvider.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public async Task TryGet_NoEnvVars_ReturnsNull() - { - var envVarsProvider = CreateEnvVarsProvider(null); - var testSubject = CreateTestSubject(ValidCompilationConfigProvider, ValidRulesConfigProvider_Cpp, envVarsProvider.Object); - - var actual = await testSubject.TryCreateAsync(ValidFileName_Cpp, ValidAnalyzerOptions); - - actual.Should().BeNull(); - envVarsProvider.VerifyAll(); - } - - [TestMethod] - public async Task TryGet_HasEnvVars_ReturnsExpectedValue() - { - var envVarsProvider = CreateEnvVarsProvider(new Dictionary - { - { "key1", "value1"}, - { "INCLUDE", "some paths..." } - }); - var testSubject = CreateTestSubject(ValidCompilationConfigProvider, ValidRulesConfigProvider_Cpp, envVarsProvider.Object); - - var actual = await testSubject.TryCreateAsync(ValidFileName_Cpp, ValidAnalyzerOptions); - - actual.Should().NotBeNull(); - envVarsProvider.VerifyAll(); - - actual.EnvironmentVariables.Should().NotBeNull(); - actual.EnvironmentVariables.Should().HaveCount(2); - actual.EnvironmentVariables["key1"].Should().Be("value1"); - actual.EnvironmentVariables["INCLUDE"].Should().Be("some paths..."); - } - - [TestMethod] - [Description("Check support for header files")] - public async Task TryGet_LanguageCalculatedBasedOnCompilationEntry() - { - const string fileName = "c:\\file.h"; - - var compilationDatabaseEntry = CreateCompilationDatabaseEntry("file.c"); - var compilationConfigProvider = CreateCompilationProvider(fileName, compilationDatabaseEntry); - var rulesConfigProvider = new Mock(); - - var testSubject = CreateTestSubject(compilationConfigProvider.Object, rulesConfigProvider.Object, ValidEnvVarsProvider); - await testSubject.TryCreateAsync(fileName, new CFamilyAnalyzerOptions()); - - compilationConfigProvider.VerifyAll(); - - // When analyzing header files, the analyzed file will be ".h", which is not a known rules' language. - // However, the compilation entry is a ".c" file, so we expect the code to calculate the rules based on the entry. - rulesConfigProvider.Verify(x=> x.GetRulesConfiguration(SonarLanguageKeys.C), Times.Once()); - } - - [TestMethod] - [Description("Check support for header files")] - [DataRow("c:\\file.h", true)] - [DataRow("c:\\file.c", false)] - public async Task TryGet_IsHeaderFileCalculatedCorrectly(string analyzedFilePath, bool expectedIsHeaderFile) - { - var compilationDatabaseEntry = CreateCompilationDatabaseEntry("file.c"); - var compilationConfigProvider = CreateCompilationProvider(analyzedFilePath, compilationDatabaseEntry); - var rulesConfigProvider = new Mock(); - - var testSubject = CreateTestSubject(compilationConfigProvider.Object, rulesConfigProvider.Object, ValidEnvVarsProvider); - var request = await testSubject.TryCreateAsync(analyzedFilePath, new CFamilyAnalyzerOptions()); - - // When analyzing header files, the analyzed file will be ".h" but the compilation entry is a ".c" file. - // We expected the property IsHeaderFile to be calculated based of the analyzed file, and not the compilation entry - request.Context.IsHeaderFile.Should().Be(expectedIsHeaderFile); - } - - [TestMethod] - public async Task TryGet_UnrecognizedLanguage_ReturnsNull() - { - const string fileName = "c:\\file.txt"; - - var compilationDatabaseEntry = CreateCompilationDatabaseEntry(fileName); - var compilationConfigProvider = CreateCompilationProvider(fileName, compilationDatabaseEntry); - var rulesConfigProvider = new Mock(); - - var testSubject = CreateTestSubject(compilationConfigProvider.Object, rulesConfigProvider.Object, ValidEnvVarsProvider); - - var actual = await testSubject.TryCreateAsync(fileName, new CFamilyAnalyzerOptions()); - - actual.Should().BeNull(); - compilationConfigProvider.VerifyAll(); - rulesConfigProvider.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public async Task TryGet_ValidFile_ReturnsExpectedValue() - { - const string fileName = "c:\\file.c"; - - var compilationDatabaseEntry = CreateCompilationDatabaseEntry(fileName); - var compilationConfigProvider = CreateCompilationProvider(fileName, compilationDatabaseEntry); - - var rulesConfig = new DummyCFamilyRulesConfig(SonarLanguageKeys.C); - var rulesConfigProvider = CreateRulesProvider(SonarLanguageKeys.C, rulesConfig); - - var testSubject = CreateTestSubject(compilationConfigProvider.Object, rulesConfigProvider.Object, ValidEnvVarsProvider); - - var analyzerOptions = new CFamilyAnalyzerOptions(); - var actual = await testSubject.TryCreateAsync(fileName, analyzerOptions); - - compilationConfigProvider.VerifyAll(); - rulesConfigProvider.VerifyAll(); - actual.Should().NotBeNull(); - actual.Context.File.Should().Be(fileName); - actual.Context.PchFile.Should().Be(SubProcessFilePaths.PchFilePath); - actual.Context.CFamilyLanguage.Should().Be(SonarLanguageKeys.C); - actual.Context.AnalyzerOptions.Should().BeSameAs(analyzerOptions); - actual.Context.RulesConfiguration.Should().BeSameAs(rulesConfig); - } - - private static CMakeRequestFactory CreateTestSubject( - ICompilationConfigProvider compilationConfigProvider = null, - ICFamilyRulesConfigProvider rulesConfigProvider = null, - IEnvironmentVarsProvider envVarsProvider = null) - { - compilationConfigProvider ??= Mock.Of(); - rulesConfigProvider ??= Mock.Of(); - envVarsProvider ??= Mock.Of(); - - return new CMakeRequestFactory(compilationConfigProvider, rulesConfigProvider, envVarsProvider); - } - - private static Mock CreateCompilationProvider(string fileName, CompilationDatabaseEntry entryToReturn) - { - var compilationConfigProvider = new Mock(); - compilationConfigProvider.Setup(x => x.GetConfig(fileName)).Returns(entryToReturn); - - return compilationConfigProvider; - } - - private static Mock CreateRulesProvider(string languageKey, ICFamilyRulesConfig rulesConfig) - { - var rulesProvider = new Mock(); - rulesProvider.Setup(x => x.GetRulesConfiguration(languageKey)).Returns(rulesConfig); - return rulesProvider; - } - - private static Mock CreateEnvVarsProvider(IReadOnlyDictionary envVars = null) - { - var envVarsProvider = new Mock(); - envVarsProvider.Setup(x => x.GetAsync()).Returns(Task.FromResult(envVars)); - return envVarsProvider; - } - - private static CompilationDatabaseEntry CreateCompilationDatabaseEntry(string filePath) => - new CompilationDatabaseEntry - { - File = filePath, - Command = "cmd" - }; - } -} diff --git a/src/CFamily.UnitTests/CMake/CompilationConfigProviderTests.cs b/src/CFamily.UnitTests/CMake/CompilationConfigProviderTests.cs deleted file mode 100644 index f402dd0eaa..0000000000 --- a/src/CFamily.UnitTests/CMake/CompilationConfigProviderTests.cs +++ /dev/null @@ -1,345 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO.Abstractions; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Newtonsoft.Json; -using SonarLint.VisualStudio.CFamily.CMake; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.CFamily; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.UnitTests.CMake -{ - [TestClass] - public class CompilationConfigProviderTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - public void GetConfig_NullFilePath_ArgumentNullException(string analyzedFilePath) - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.GetConfig(analyzedFilePath); - - act.Should().Throw().And.ParamName.Should().Be("filePath"); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - public void GetConfig_NoCompilationDatabase_Null(string compilationDatabaseFilePath) - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator(compilationDatabaseFilePath); - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object); - - var result = testSubject.GetConfig("some file"); - result.Should().BeNull(); - - compilationDatabaseLocator.Verify(x=> x.Locate(), Times.Once); - } - - [TestMethod] - [DataRow("")] - [DataRow("[]")] - public void GetConfig_EmptyCompilationDatabase_Null(string compilationDatabaseContents) - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", compilationDatabaseContents); - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object, logger); - - var result = testSubject.GetConfig("some file"); - result.Should().BeNull(); - - fileSystem.Verify(x => x.File.ReadAllText("some db"), Times.Once); - - logger.AssertOutputStringExists(string.Format(Resources.EmptyCompilationDatabaseFile, "some db")); - } - - [TestMethod] - public void GetConfig_ProblemReadingDatabaseFile_NonCriticalException_Null() - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", exToThrow: new NotSupportedException("this is a test")); - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object, logger); - - var result = testSubject.GetConfig("some file"); - result.Should().BeNull(); - - fileSystem.Verify(x => x.File.ReadAllText("some db"), Times.Once); - - logger.AssertPartialOutputStringExists("this is a test"); - } - - [TestMethod] - public void GetConfig_ProblemReadingDatabaseFile_CriticalException_ExceptionThrown() - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", exToThrow: new StackOverflowException()); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object); - - Action act = () => testSubject.GetConfig("some file"); - - act.Should().Throw(); - } - - [TestMethod] - public void GetConfig_ProblemParsingDatabaseFile_Null() - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", "not valid json"); - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object, logger); - - var result = testSubject.GetConfig("some file"); - result.Should().BeNull(); - - fileSystem.Verify(x => x.File.ReadAllText("some db"), Times.Once); - - logger.AssertPartialOutputStringExists("JsonReaderException"); - } - - [TestMethod] - [DataRow("[{}]")] // no entries in file - [DataRow("[{\"file\" : \"some file.c\" }]")] // different extension - [DataRow("[{\"file\" : \"sub/some file.cpp\" }]")] // different path - public void GetConfig_CodeFile_EntryNotFoundInDatabase_Null(string databaseFileContents) - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", databaseFileContents); - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object, logger); - - var result = testSubject.GetConfig("some file.cpp"); - result.Should().BeNull(); - - fileSystem.Verify(x => x.File.ReadAllText("some db"), Times.Once); - - logger.AssertPartialOutputStringExists(string.Format(Resources.NoCompilationDatabaseEntry, "some file.cpp")); - } - - [TestMethod] - [DataRow("c:\\some file.cpp")] // exact match - [DataRow("c:/some file.cpp")] // different format - [DataRow("c:/SOME file.cpp")] // case-sensitivity on name - [DataRow("c:/some file.CPP")] // case-sensitivity on extension - public void GetConfig_CodeFile_EntryFound_ReturnsEntry(string entryFilePath) - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var compilationDatabaseContent = new[] - { - new CompilationDatabaseEntry {File = entryFilePath, Command = "some command", Directory = "some dir"} - }; - var fileSystem = SetupDatabaseFileContents("some db", JsonConvert.SerializeObject(compilationDatabaseContent)); - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object, logger); - - var result = testSubject.GetConfig("c:\\some file.cpp"); - result.Should().BeEquivalentTo(compilationDatabaseContent[0]); - } - - [TestMethod] - public void GetConfig_CodeFile_DatabaseContainsMultipleEntriesForSameFile_ReturnsFirstOne() - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var compilationDatabaseContent = new[] - { - new CompilationDatabaseEntry {File = "some other file", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "some file", Command = "cmd2", Directory = "dir2"}, - new CompilationDatabaseEntry {File = "some file", Command = "cmd3", Directory = "dir3"} - }; - - var fileSystem = SetupDatabaseFileContents("some db", JsonConvert.SerializeObject(compilationDatabaseContent)); - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object, logger); - - var result = testSubject.GetConfig("some file"); - - result.Should().BeEquivalentTo(compilationDatabaseContent[1]); - } - - [TestMethod] - public void GetConfig_HeaderFile_NoEntriesInCompilationDatabase_Null() - { - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", "[{}]"); - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object, logger); - - var result = testSubject.GetConfig("some header.h"); - result.Should().BeNull(); - - fileSystem.Verify(x => x.File.ReadAllText("some db"), Times.Once); - - logger.AssertPartialOutputStringExists(string.Format(Resources.NoCompilationDatabaseEntryForHeaderFile, "some header.h")); - } - - [TestMethod] - [DataRow("c:\\a.cpp")] - [DataRow("c:\\some\\folder\\a.c")] - [DataRow("D:\\A.cxx")] - [DataRow("c:\\test\\a.CC")] - public void GetConfig_HeaderFile_CodeWithSameNameDifferentPath_ReturnsCodeFileEntry(string matchingCodeFile) - { - const string headerFilePath = "c:\\test\\a.h"; - - var compilationDatabaseContent = new[] - { - new CompilationDatabaseEntry {File = "c:\\test\\a.b", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = matchingCodeFile, Command = "cmd2", Directory = "dir2"}, - new CompilationDatabaseEntry {File = "c:\\test\\aa.cpp", Command = "cmd3", Directory = "dir3"} - }; - - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", JsonConvert.SerializeObject(compilationDatabaseContent)); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object); - - var result = testSubject.GetConfig(headerFilePath); - result.Should().BeEquivalentTo(compilationDatabaseContent[1]); - } - - [TestMethod] - public void GetConfig_HeaderFile_CodeWithSameNameExactPath_GivenPriorityOverDifferentPath() - { - const string headerFilePath = "c:\\test\\a.hxx"; - - var compilationDatabaseContent = new[] - { - new CompilationDatabaseEntry {File = "c:\\a.c", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\other\\a.c", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\test\\a.c", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\test\\a.b", Command = "cmd1", Directory = "dir1"} - }; - - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", JsonConvert.SerializeObject(compilationDatabaseContent)); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object); - - var result = testSubject.GetConfig(headerFilePath); - result.Should().BeEquivalentTo(compilationDatabaseContent[2]); - } - - [TestMethod] - public void GetConfig_HeaderFile_NoMatchingName_HasCodeFileUnderPath_ReturnsCodeFileEntry() - { - const string headerFilePath = "c:\\a\\b\\test.hh"; - - var compilationDatabaseContent = new[] - { - new CompilationDatabaseEntry {File = "c:\\a\\wrongRoot2.cpp", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\b\\wrongRoot3.cpp", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\a\\b\\c\\correctRoot1.cpp", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\a\\b\\correctRoot2.cpp", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\wrongRoot3.cpp", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\a\\b\\correctRoot3.cpp", Command = "cmd1", Directory = "dir1"} - }; - - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", JsonConvert.SerializeObject(compilationDatabaseContent)); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object); - - var result = testSubject.GetConfig(headerFilePath); - result.Should().BeEquivalentTo(compilationDatabaseContent[2]); - } - - [TestMethod] - public void GetConfig_HeaderFile_NoMatchingName_NoMatchingPath_ReturnsFirstCodeFileEntry() - { - const string headerFilePath = "c:\\a\\b\\test.hpp"; - - var compilationDatabaseContent = new[] - { - new CompilationDatabaseEntry {File = "c:\\wrongRoot.cpp", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\a\\c\\wrongRoot2.cpp", Command = "cmd1", Directory = "dir1"}, - new CompilationDatabaseEntry {File = "c:\\b\\wrongRoot3.cpp", Command = "cmd1", Directory = "dir1"} - }; - - var compilationDatabaseLocator = SetupCompilationDatabaseLocator("some db"); - var fileSystem = SetupDatabaseFileContents("some db", JsonConvert.SerializeObject(compilationDatabaseContent)); - - var testSubject = CreateTestSubject(compilationDatabaseLocator.Object, fileSystem.Object); - - var result = testSubject.GetConfig(headerFilePath); - result.Should().BeEquivalentTo(compilationDatabaseContent[0]); - } - - private static Mock SetupDatabaseFileContents(string databaseFilePath, string content = null, Exception exToThrow = null) - { - var fileSystem = new Mock(); - fileSystem.Setup(x => x.File.Exists(databaseFilePath)).Returns(true); - - if (exToThrow == null) - { - fileSystem.Setup(x => x.File.ReadAllText(databaseFilePath)).Returns(content); - } - else - { - fileSystem.Setup(x => x.File.ReadAllText(databaseFilePath)).Throws(exToThrow); - } - - return fileSystem; - } - - private Mock SetupCompilationDatabaseLocator(string compilationDatabaseFilePath) - { - var compilationDatabaseLocator = new Mock(); - - compilationDatabaseLocator.Setup(x => x.Locate()).Returns(compilationDatabaseFilePath); - - return compilationDatabaseLocator; - } - - private CompilationConfigProvider CreateTestSubject(ICompilationDatabaseLocator compilationDatabaseLocator = null, - IFileSystem fileSystem = null, - ILogger logger = null) - { - compilationDatabaseLocator ??= Mock.Of(); - fileSystem ??= Mock.Of(); - logger ??= Mock.Of(); - - return new CompilationConfigProvider(compilationDatabaseLocator, fileSystem, logger); - } - } -} diff --git a/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs b/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs new file mode 100644 index 0000000000..579ab1bcd4 --- /dev/null +++ b/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs @@ -0,0 +1,89 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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 NSubstitute.ReturnsExtensions; +using SonarLint.VisualStudio.CFamily.CMake; +using SonarLint.VisualStudio.CFamily.CompilationDatabase; +using SonarLint.VisualStudio.Core.CFamily; +using SonarLint.VisualStudio.TestInfrastructure; + +namespace SonarLint.VisualStudio.CFamily.UnitTests.CompilationDatabase; + +[TestClass] +public class AggregatingCompilationDatabaseProviderTests +{ + private ICMakeCompilationDatabaseLocator cmake; + private IVCXCompilationDatabaseProvider vcx; + private AggregatingCompilationDatabaseProvider testSubject; + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestInitialize] + public void TestInitialize() + { + cmake = Substitute.For(); + vcx = Substitute.For(); + testSubject = new AggregatingCompilationDatabaseProvider(cmake, vcx); + } + + [TestMethod] + public void GetOrNull_CmakeAvailable_ReturnsCmakeLocation() + { + var location = "some location"; + cmake.Locate().Returns(location); + + var result = testSubject.GetOrNull("some path"); + + result.Should().Be(location); + vcx.DidNotReceiveWithAnyArgs().CreateOrNull(default); + } + + [TestMethod] + public void GetOrNull_CmakeUnavailable_VcxAvailable_ReturnsVcxLocation() + { + var sourcePath = "some path"; + var location = "some location"; + cmake.Locate().ReturnsNull(); + vcx.CreateOrNull(sourcePath).Returns(location); + + var result = testSubject.GetOrNull(sourcePath); + + result.Should().Be(location); + cmake.Received().Locate(); + } + + [TestMethod] + public void GetOrNull_Unavailable_ReturnsNull() + { + cmake.Locate().ReturnsNull(); + vcx.CreateOrNull(default).ReturnsNullForAnyArgs(); + + var result = testSubject.GetOrNull("some path"); + + result.Should().BeNull(); + } +} diff --git a/src/CFamily.UnitTests/PreCompiledHeaders/PchCacheCleanerTests.cs b/src/CFamily.UnitTests/PreCompiledHeaders/PchCacheCleanerTests.cs deleted file mode 100644 index 19e455dbd4..0000000000 --- a/src/CFamily.UnitTests/PreCompiledHeaders/PchCacheCleanerTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; -using System.IO.Abstractions.TestingHelpers; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace SonarLint.VisualStudio.CFamily.PreCompiledHeaders.UnitTests -{ - [TestClass] - public class PchCacheCleanerTests - { - private MockFileSystem fileSystemMock; - - [TestInitialize] - public void TestInitialize() - { - fileSystemMock = new MockFileSystem(); - fileSystemMock.Directory.CreateDirectory("c:\\test\\pch"); - } - - [TestMethod] - public void Cleanup_NoFilesInDirectory_NoException() - { - var testSubject = new PchCacheCleaner(fileSystemMock, "c:\\test\\pch\\myPch.abc"); - - Action act = () => testSubject.Cleanup(); - act.Should().NotThrow(); - } - - [TestMethod] - public void Cleanup_NoMatchingFilesInDirectory_NonMatchingFilesAreNotDeleted() - { - var nonMatchingFilePaths = new List - { - "c:\\test\\pch\\test.abc", - "c:\\test\\pch\\myPch.ab", - "c:\\test\\pch\\myPch.ab.c", - "c:\\test\\pch\\myPch.abd", - "c:\\test\\pch\\amyPch.abc", - "c:\\test\\pch\\sub\\myPch.abc", - "c:\\test\\myPch.abc" - }; - - foreach (var filePath in nonMatchingFilePaths) - { - fileSystemMock.AddFile(filePath, new MockFileData("")); - } - - var testSubject = new PchCacheCleaner(fileSystemMock, "c:\\test\\pch\\myPch.abc"); - testSubject.Cleanup(); - - fileSystemMock.AllFiles.Should().BeEquivalentTo(nonMatchingFilePaths); - } - - [TestMethod] - public void Cleanup_HasMatchingFilesInDirectory_MatchingFilesAreDeleted() - { - var matchingFilePaths = new List - { - "c:\\test\\pch\\myPch.abc", - "c:\\test\\pch\\MYpch.aBC", - "c:\\test\\pch\\myPch.abcd", - "c:\\test\\pch\\myPch.abcd.e", - "c:\\test\\pch\\myPch.abc.d", - "c:\\test\\pch\\myPch.abc.d.e", - "c:\\test\\pch\\myPch.abc.de.f", - "c:\\test\\pch\\myPCH.ABC.d.E" - }; - - foreach (var filePath in matchingFilePaths) - { - fileSystemMock.AddFile(filePath, new MockFileData("")); - } - - var testSubject = new PchCacheCleaner(fileSystemMock, "c:\\test\\pch\\myPch.abc"); - testSubject.Cleanup(); - - fileSystemMock.AllFiles.Should().BeEmpty(); - } - - [TestMethod] - public void Cleanup_HasMatchingAndNonMatchingFilesInDirectory_OnlyMatchingFilesAreDeleted() - { - var matchingFile = "c:\\test\\pch\\myPch.abc.d"; - var nonMatchingFile = "c:\\test\\pch\\myPch.abd"; - - fileSystemMock.AddFile(matchingFile, new MockFileData("")); - fileSystemMock.AddFile(nonMatchingFile, new MockFileData("")); - - var testSubject = new PchCacheCleaner(fileSystemMock, "c:\\test\\pch\\myPch.abc"); - testSubject.Cleanup(); - - fileSystemMock.AllFiles.Should().BeEquivalentTo(new List{nonMatchingFile}); - } - - [TestMethod] - public void Cleanup_FailsToDeleteSomeFiles_DeletesTheOnesThatSucceed() - { - var matchingFile = "c:\\test\\pch\\myPch.abc.d"; - fileSystemMock.AddFile(matchingFile, new MockFileData("")); - - var matchingFailingFile = "c:\\test\\pch\\myPch.abc.de"; - var failingFileMockData = new MockFileData("") - { - AllowedFileShare = FileShare.None - }; - fileSystemMock.AddFile(matchingFailingFile, failingFileMockData); - - var testSubject = new PchCacheCleaner(fileSystemMock, "c:\\test\\pch\\myPch.abc"); - testSubject.Cleanup(); - - fileSystemMock.AllFiles.Should().BeEquivalentTo(new List { matchingFailingFile }); - } - } -} diff --git a/src/CFamily.UnitTests/PreCompiledHeaders/PreCompiledHeadersEventListenerTests.cs b/src/CFamily.UnitTests/PreCompiledHeaders/PreCompiledHeadersEventListenerTests.cs deleted file mode 100644 index cda4d7d644..0000000000 --- a/src/CFamily.UnitTests/PreCompiledHeaders/PreCompiledHeadersEventListenerTests.cs +++ /dev/null @@ -1,201 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Utilities; -using Moq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Infrastructure.VS.DocumentEvents; -using SonarLint.VisualStudio.IssueVisualization.Editor.LanguageDetection; - -namespace SonarLint.VisualStudio.CFamily.PreCompiledHeaders.UnitTests -{ - [TestClass] - public class PreCompiledHeadersEventListenerTests - { - private const string FocusedDocumentFilePath = "c:\\myfile.cpp"; - private readonly IContentType focusedDocumentContentType = Mock.Of(); - - private Mock cFamilyAnalyzerMock; - private Mock activeDocumentTrackerMock; - private Mock schedulerMock; - private Mock languageRecognizerMock; - private Mock cacheCleanerMock; - - private PreCompiledHeadersEventListener testSubject; - - [TestInitialize] - public void TestInitialize() - { - cFamilyAnalyzerMock = new Mock(); - activeDocumentTrackerMock = new Mock(); - schedulerMock = new Mock(); - languageRecognizerMock = new Mock(); - cacheCleanerMock = new Mock(); - - var environmentSettingsMock = new Mock(); - environmentSettingsMock - .Setup(x => x.PCHGenerationTimeoutInMs(It.IsAny())) - .Returns(1); - - testSubject = new PreCompiledHeadersEventListener(cFamilyAnalyzerMock.Object, activeDocumentTrackerMock.Object, schedulerMock.Object, languageRecognizerMock.Object, environmentSettingsMock.Object, cacheCleanerMock.Object); - } - - [TestMethod] - public void Ctor_RegisterToDocumentFocusedEvent() - { - RaiseActiveDocumentChangedEvent(); - - languageRecognizerMock.Verify(x => x.Detect(FocusedDocumentFilePath, focusedDocumentContentType), Times.Once); - } - - [TestMethod] - public void Dispose_UnregisterFromDocumentFocusedEvent() - { - testSubject.Dispose(); - - RaiseActiveDocumentChangedEvent(); - - cFamilyAnalyzerMock.VerifyNoOtherCalls(); - schedulerMock.VerifyNoOtherCalls(); - languageRecognizerMock.VerifyNoOtherCalls(); - } - - [TestMethod] - public void Dispose_CleanupPchCache() - { - testSubject.Dispose(); - - cacheCleanerMock.Verify(x=> x.Cleanup(), Times.Once); - } - - [TestMethod] - public void Dispose_ExceptionWhenCleaningCache_ExceptionCaught() - { - cacheCleanerMock.Setup(x => x.Cleanup()).Throws(); - - Action act = () => testSubject.Dispose(); - act.Should().NotThrow(); - } - - [TestMethod] - public void Dispose_CriticalExceptionWhenCleaningCache_ExceptionNotCaught() - { - cacheCleanerMock.Setup(x => x.Cleanup()).Throws(); - - Action act = () => testSubject.Dispose(); - act.Should().ThrowExactly(); - } - - [TestMethod] - public void OnDocumentFocused_NoLanguagesDetected_PchGenerationNotScheduled() - { - SetupDetectedLanguages(Enumerable.Empty()); - - RaiseActiveDocumentChangedEvent(); - - schedulerMock.VerifyNoOtherCalls(); - cFamilyAnalyzerMock.VerifyNoOtherCalls(); - } - - [TestMethod] - public void OnDocumentFocused_LanguageIsUnsupported_PchGenerationNotScheduled() - { - var unsupportedLanguages = new List {AnalysisLanguage.Javascript}; - - SetupDetectedLanguages(unsupportedLanguages); - - cFamilyAnalyzerMock.Setup(x => x.IsAnalysisSupported(unsupportedLanguages)).Returns(false).Verifiable(); - - RaiseActiveDocumentChangedEvent(); - - schedulerMock.VerifyNoOtherCalls(); - cFamilyAnalyzerMock.Verify(); - cFamilyAnalyzerMock.VerifyNoOtherCalls(); - } - - [TestMethod] - public void OnDocumentFocused_LanguageIsSupported_SchedulePchGeneration() - { - var supportedLanguages = new List { AnalysisLanguage.CFamily }; - - SetupDetectedLanguages(supportedLanguages); - - cFamilyAnalyzerMock.Setup(x => x.IsAnalysisSupported(supportedLanguages)).Returns(true); - - var cancellationToken = new CancellationTokenSource(); - - schedulerMock - .Setup(x=> x.Schedule(PreCompiledHeadersEventListener.PchJobId, It.IsAny>(), testSubject.pchJobTimeoutInMilliseconds)) - .Callback((string jobId, Action action, int timeout) => action(cancellationToken.Token)); - - RaiseActiveDocumentChangedEvent(); - - cFamilyAnalyzerMock.Verify(x=> - x.ExecuteAnalysis(FocusedDocumentFilePath, - supportedLanguages, - null, - It.Is((IAnalyzerOptions options) => ((CFamilyAnalyzerOptions)options).CreatePreCompiledHeaders), - null, - cancellationToken.Token)); - } - - [TestMethod] - public void OnDocumentFocused_NoActiveDocument_NoError() - { - RaiseActiveDocumentChangedEvent(null); - languageRecognizerMock.Invocations.Count.Should().Be(0); - } - - private void RaiseActiveDocumentChangedEvent() => - RaiseActiveDocumentChangedEvent(CreateMockTextDocument()); - - private void RaiseActiveDocumentChangedEvent(ITextDocument textDocument) => - activeDocumentTrackerMock.Raise(x => x.ActiveDocumentChanged += null, new ActiveDocumentChangedEventArgs(textDocument)); - - private ITextDocument CreateMockTextDocument() - { - var textBufferMock = new Mock(); - textBufferMock.Setup(x => x.ContentType).Returns(focusedDocumentContentType); - - var textDocumentMock = new Mock(); - textDocumentMock.Setup(x => x.TextBuffer).Returns(textBufferMock.Object); - textDocumentMock.Setup(x => x.FilePath).Returns(FocusedDocumentFilePath); - - return textDocumentMock.Object; - } - - private void SetupDetectedLanguages(IEnumerable languages) - { - languageRecognizerMock - .Setup(x => x.Detect(FocusedDocumentFilePath, focusedDocumentContentType)) - .Returns(languages); - } - } -} diff --git a/src/CFamily/Analysis/CLangAnalyzer.cs b/src/CFamily/Analysis/CLangAnalyzer.cs deleted file mode 100644 index 3b3f5dee1b..0000000000 --- a/src/CFamily/Analysis/CLangAnalyzer.cs +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; -using SonarLint.VisualStudio.Core.Analysis; - -namespace SonarLint.VisualStudio.CFamily.Analysis -{ - internal interface ICFamilyAnalyzer : IAnalyzer - { - void ExecuteAnalysis(string path, - IEnumerable detectedLanguages, - IIssueConsumer consumer, - IAnalyzerOptions analyzerOptions, - IAnalysisStatusNotifier statusNotifier, - CancellationToken cancellationToken); - } - - [ExcludeFromCodeCoverage] - [Export(typeof(ICFamilyAnalyzer))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class CLangAnalyzer : ICFamilyAnalyzer - { - [ImportingConstructor] - public CLangAnalyzer() - { - } - - public bool IsAnalysisSupported(IEnumerable languages) - { - return languages.Contains(AnalysisLanguage.CFamily); - } - - public void ExecuteAnalysis( - string path, - Guid analysisId, - IEnumerable detectedLanguages, - IIssueConsumer consumer, - IAnalyzerOptions analyzerOptions, - CancellationToken cancellationToken) - { - } - - public void ExecuteAnalysis( - string path, - IEnumerable detectedLanguages, - IIssueConsumer consumer, - IAnalyzerOptions analyzerOptions, - IAnalysisStatusNotifier statusNotifier, - CancellationToken cancellationToken) - { - } - } -} diff --git a/src/CFamily/CMake/CompilationDatabaseLocator.cs b/src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs similarity index 83% rename from src/CFamily/CMake/CompilationDatabaseLocator.cs rename to src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs index dbed78147f..252d165e27 100644 --- a/src/CFamily/CMake/CompilationDatabaseLocator.cs +++ b/src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs @@ -21,42 +21,46 @@ using System.ComponentModel.Composition; using System.IO; using System.IO.Abstractions; -using System.Linq; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.CFamily; +using SonarLint.VisualStudio.Core.SystemAbstractions; namespace SonarLint.VisualStudio.CFamily.CMake { - [Export(typeof(ICompilationDatabaseLocator))] + internal interface ICMakeCompilationDatabaseLocator + { + string Locate(); + } + + [Export(typeof(ICMakeCompilationDatabaseLocator))] [PartCreationPolicy(CreationPolicy.Shared)] - internal class CompilationDatabaseLocator : ICompilationDatabaseLocator + internal class CMakeCompilationDatabaseLocator : ICMakeCompilationDatabaseLocator { internal const string CompilationDatabaseFileName = "compile_commands.json"; internal const string DefaultLocationFormat = "{0}\\out\\build\\{1}"; private readonly IFolderWorkspaceService folderWorkspaceService; - private readonly IFileSystem fileSystem; + private readonly IFileSystemService fileSystem; private readonly IBuildConfigProvider buildConfigProvider; private readonly ICMakeSettingsProvider cMakeSettingsProvider; private readonly IMacroEvaluationService macroEvaluationService; private readonly ILogger logger; [ImportingConstructor] - public CompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, ILogger logger) + public CMakeCompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, IFileSystemService fileSystem, ILogger logger) : this(folderWorkspaceService, new BuildConfigProvider(logger), - new CMakeSettingsProvider(logger), + new CMakeSettingsProvider(logger), new MacroEvaluationService(logger), - new FileSystem(), + fileSystem, logger) { } - public CompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, + public CMakeCompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, IBuildConfigProvider buildConfigProvider, ICMakeSettingsProvider cMakeSettingsProvider, IMacroEvaluationService macroEvaluationService, - IFileSystem fileSystem, + IFileSystemService fileSystem, ILogger logger) { this.folderWorkspaceService = folderWorkspaceService; @@ -73,7 +77,7 @@ public string Locate() if (string.IsNullOrEmpty(rootDirectory)) { - logger.LogVerbose("[CompilationDatabaseLocator] Could not find project root directory"); + logger.LogVerbose("[CMakeCompilationDatabaseLocator] Could not find project root directory"); return null; } @@ -99,12 +103,12 @@ private string GetDefaultLocation(string rootDirectory, string activeConfigurati var defaultDirectory = Path.GetFullPath(string.Format(DefaultLocationFormat, rootDirectory, activeConfiguration)); var defaultLocation = Path.Combine(defaultDirectory, CompilationDatabaseFileName); - logger.LogVerbose($"[CompilationDatabaseLocator] No CMakeSettings file was found under {rootDirectory}, returning default location: {defaultLocation}"); + logger.LogVerbose($"[CMakeCompilationDatabaseLocator] No CMakeSettings file was found under {rootDirectory}, returning default location: {defaultLocation}"); return defaultLocation; } - private string GetConfiguredLocation(CMakeSettingsSearchResult cMakeSettings, + private string GetConfiguredLocation(CMakeSettingsSearchResult cMakeSettings, string activeConfiguration, string rootDirectory) { @@ -129,7 +133,7 @@ private string GetConfiguredLocation(CMakeSettingsSearchResult cMakeSettings, cMakeSettings.CMakeSettingsFilePath); var evaluatedBuildRoot = macroEvaluationService.Evaluate(buildConfiguration.BuildRoot, evaluationContext); - + if (evaluatedBuildRoot == null) { logger.WriteLine(Resources.UnableToEvaluateBuildRootProperty, buildConfiguration.BuildRoot); diff --git a/src/CFamily/CMake/CMakeRequestFactory.cs b/src/CFamily/CMake/CMakeRequestFactory.cs deleted file mode 100644 index 2a09492bf5..0000000000 --- a/src/CFamily/CMake/CMakeRequestFactory.cs +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.ComponentModel.Composition; -using System.Threading.Tasks; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.CompilationDatabase; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; - -namespace SonarLint.VisualStudio.CFamily.CMake -{ - [Export(typeof(IRequestFactory))] - internal class CMakeRequestFactory : IRequestFactory - { - private readonly ICompilationConfigProvider compilationConfigProvider; - private readonly ICFamilyRulesConfigProvider rulesConfigProvider; - private readonly IEnvironmentVarsProvider envVarsProvider; - - [ImportingConstructor] - public CMakeRequestFactory(ICompilationConfigProvider compilationConfigProvider, - ICFamilyRulesConfigProvider rulesConfigProvider, - IEnvironmentVarsProvider envVarsProvider) - { - this.compilationConfigProvider = compilationConfigProvider; - this.rulesConfigProvider = rulesConfigProvider; - this.envVarsProvider = envVarsProvider; - } - - public async Task TryCreateAsync(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) - { - var dbEntry = compilationConfigProvider.GetConfig(analyzedFilePath); - if (dbEntry == null) - { - return null; - } - - // TODO - handle user specifying the language via a command / argument #2533 - var languageKey = CFamilyShared.FindLanguageFromExtension(dbEntry.File); - if (languageKey == null) - { - return null; - } - - var rulesConfig = rulesConfigProvider.GetRulesConfiguration(languageKey); - var context = new RequestContext( - languageKey, - rulesConfig, - analyzedFilePath, - SubProcessFilePaths.PchFilePath, - analyzerOptions, - CFamilyShared.IsHeaderFileExtension(analyzedFilePath)); - - var envVars = await envVarsProvider.GetAsync(); - if (envVars == null) - { - return null; - } - - return new CompilationDatabaseRequest(dbEntry, context, envVars); - } - } -} diff --git a/src/CFamily/CMake/CompilationConfigProvider.cs b/src/CFamily/CMake/CompilationConfigProvider.cs deleted file mode 100644 index 3b90899881..0000000000 --- a/src/CFamily/CMake/CompilationConfigProvider.cs +++ /dev/null @@ -1,220 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.IO; -using System.IO.Abstractions; -using System.Linq; -using Newtonsoft.Json; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.CFamily; -using SonarLint.VisualStudio.Core.Helpers; - -using ErrorHandler = Microsoft.VisualStudio.ErrorHandler; - -namespace SonarLint.VisualStudio.CFamily.CMake -{ - internal interface ICompilationConfigProvider - { - /// - /// Returns the compilation configuration for the given file, - /// as specified in the compilation database file for the currently active build configuration. - /// Returns null if there is no compilation database or if the file does not exist in the compilation database. - /// - CompilationDatabaseEntry GetConfig(string filePath); - } - - [Export(typeof(ICompilationConfigProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class CompilationConfigProvider : ICompilationConfigProvider - { - private readonly ICompilationDatabaseLocator compilationDatabaseLocator; - private readonly IFileSystem fileSystem; - private readonly ILogger logger; - - [ImportingConstructor] - public CompilationConfigProvider(ICompilationDatabaseLocator compilationDatabaseLocator, ILogger logger) - : this(compilationDatabaseLocator, new FileSystem(), logger) - { - } - - internal CompilationConfigProvider(ICompilationDatabaseLocator compilationDatabaseLocator, - IFileSystem fileSystem, - ILogger logger) - { - this.compilationDatabaseLocator = compilationDatabaseLocator; - this.fileSystem = fileSystem; - this.logger = logger; - } - - public CompilationDatabaseEntry GetConfig(string filePath) - { - if (string.IsNullOrEmpty(filePath)) - { - throw new ArgumentNullException(nameof(filePath)); - } - - var compilationDatabaseLocation = compilationDatabaseLocator.Locate(); - - if (string.IsNullOrEmpty(compilationDatabaseLocation)) - { - return null; - } - - logger.LogVerbose($"[CompilationConfigProvider] Reading compilation database from '{compilationDatabaseLocation}'"); - - try - { - var compilationDatabaseString = fileSystem.File.ReadAllText(compilationDatabaseLocation); - var compilationDatabaseEntries = JsonConvert.DeserializeObject>(compilationDatabaseString); - - if (compilationDatabaseEntries == null || !compilationDatabaseEntries.Any()) - { - logger.WriteLine(Resources.EmptyCompilationDatabaseFile, compilationDatabaseLocation); - return null; - } - - var stopwatch = Stopwatch.StartNew(); - - var entry = CFamilyShared.IsHeaderFileExtension(filePath) - ? LocateMatchingCodeEntry(filePath, compilationDatabaseEntries) - : LocateExactCodeEntry(filePath, compilationDatabaseEntries); - - // todo: remove before release - logger.LogVerbose("***** [CompilationConfigProvider] time (ms) to locate entry: " + stopwatch.ElapsedMilliseconds); - - return entry; - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - logger.WriteLine(Resources.BadCompilationDatabaseFile, ex); - - return null; - } - } - - private CompilationDatabaseEntry LocateExactCodeEntry(string filePath, IEnumerable compilationDatabaseEntries) - { - logger.LogVerbose($"[CompilationConfigProvider] Code file detected, searching for exact match. File: {filePath}"); - - var entry = LocateCodeEntry(filePath, compilationDatabaseEntries); - - if (entry == null) - { - logger.WriteLine(Resources.NoCompilationDatabaseEntry, filePath); - } - - return entry; - } - - private CompilationDatabaseEntry LocateMatchingCodeEntry(string headerFilePath, IEnumerable compilationDatabaseEntries) - { - logger.LogVerbose($"[CompilationConfigProvider] Header file detected, searching for matching code file. File: {headerFilePath}"); - - var codeFilesWithSameNameAndSamePath = - CFamilyShared.KnownExtensions.Select(ext => Path.ChangeExtension(headerFilePath, ext)); - - var matchingCodeEntry = - LocateCodeEntryWithExactNameAndPath(codeFilesWithSameNameAndSamePath, compilationDatabaseEntries) ?? - LocateCodeEntryWithExactName(codeFilesWithSameNameAndSamePath, compilationDatabaseEntries) ?? - LocateFirstCodeEntryUnderRoot(headerFilePath, compilationDatabaseEntries) ?? - LocateFirstCodeEntry(compilationDatabaseEntries); - - if (matchingCodeEntry == null) - { - logger.WriteLine(Resources.NoCompilationDatabaseEntryForHeaderFile, headerFilePath); - } - - return matchingCodeEntry; - } - - private CompilationDatabaseEntry LocateCodeEntryWithExactNameAndPath(IEnumerable codeFilesWithSameNameAndSamePath, IEnumerable compilationDatabaseEntries) - { - foreach (var codeFile in codeFilesWithSameNameAndSamePath) - { - var entry = LocateCodeEntry(codeFile, compilationDatabaseEntries); - - if (entry != null) - { - logger.LogVerbose($"[CompilationConfigProvider] Header file: located matching code file with same name and path: {entry.File}"); - return entry; - } - } - - return null; - } - - private CompilationDatabaseEntry LocateCodeEntryWithExactName(IEnumerable codeFilesWithSameNameAndSamePath, IEnumerable compilationDatabaseEntries) - { - var codeFilesWithSameName = codeFilesWithSameNameAndSamePath.Select(Path.GetFileName); - - foreach (var codeFile in codeFilesWithSameName) - { - var entry = compilationDatabaseEntries.FirstOrDefault(x => - !string.IsNullOrEmpty(x.File) && - Path.GetFileName(x.File).Equals(codeFile, StringComparison.OrdinalIgnoreCase)); - - if (entry != null) - { - logger.LogVerbose($"[CompilationConfigProvider] Header file: located matching code file with same name: {entry.File}"); - return entry; - } - } - - return null; - } - - private CompilationDatabaseEntry LocateFirstCodeEntryUnderRoot(string headerFilePath, IEnumerable compilationDatabaseEntries) - { - var rootDirectory = Path.GetDirectoryName(headerFilePath); - - var entry = compilationDatabaseEntries.FirstOrDefault(x => - !string.IsNullOrEmpty(x.File) && - PathHelper.IsPathRootedUnderRoot(x.File, rootDirectory)); - - if (entry != null) - { - logger.LogVerbose($"[CompilationConfigProvider] Header file: located code file under same root: {entry.File}"); - } - - return entry; - } - - private CompilationDatabaseEntry LocateFirstCodeEntry(IEnumerable compilationDatabaseEntries) - { - var entry = compilationDatabaseEntries.FirstOrDefault(x => !string.IsNullOrEmpty(x.File)); - - if (entry != null) - { - logger.LogVerbose($"[CompilationConfigProvider] Header file: using first entry: {entry.File}"); - } - - return entry; - } - - private static CompilationDatabaseEntry LocateCodeEntry(string filePath, IEnumerable compilationDatabaseEntries) => - compilationDatabaseEntries.FirstOrDefault(x => - !string.IsNullOrEmpty(x.File) && - PathHelper.IsMatchingPath(filePath, x.File)); - } -} diff --git a/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs b/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs new file mode 100644 index 0000000000..dcf86a69cd --- /dev/null +++ b/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs @@ -0,0 +1,44 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ComponentModel.Composition; +using SonarLint.VisualStudio.CFamily.CMake; +using SonarLint.VisualStudio.Core.CFamily; + +namespace SonarLint.VisualStudio.CFamily.CompilationDatabase; + +[Export(typeof(IAggregatingCompilationDatabaseProvider))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method:ImportingConstructor] +internal class AggregatingCompilationDatabaseProvider( + ICMakeCompilationDatabaseLocator cMakeCompilationDatabaseLocator, + IVCXCompilationDatabaseProvider vcxCompilationDatabaseProvider) + : IAggregatingCompilationDatabaseProvider +{ + public string GetOrNull(string sourceFilePath) + { + if (cMakeCompilationDatabaseLocator.Locate() is {} cmakeCompilationDatabasePath) + { + return cmakeCompilationDatabasePath; + } + + return vcxCompilationDatabaseProvider.CreateOrNull(sourceFilePath); + } +} diff --git a/src/Core/CFamily/ICompilationDatabaseLocator.cs b/src/CFamily/IVCXCompilationDatabaseProvider.cs similarity index 71% rename from src/Core/CFamily/ICompilationDatabaseLocator.cs rename to src/CFamily/IVCXCompilationDatabaseProvider.cs index c183e34c34..4b99a37ef1 100644 --- a/src/Core/CFamily/ICompilationDatabaseLocator.cs +++ b/src/CFamily/IVCXCompilationDatabaseProvider.cs @@ -18,14 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarLint.VisualStudio.Core.CFamily +namespace SonarLint.VisualStudio.CFamily; + +public interface IVCXCompilationDatabaseProvider { - public interface ICompilationDatabaseLocator - { - /// - /// Returns absolute path to the compilation database file of the currently active build configuration. - /// Returns null if the file was not found. - /// - string Locate(); - } + string CreateOrNull(string filePath); } diff --git a/src/CFamily/PreCompiledHeaders/PchCacheCleaner.cs b/src/CFamily/PreCompiledHeaders/PchCacheCleaner.cs deleted file mode 100644 index 789cfc8867..0000000000 --- a/src/CFamily/PreCompiledHeaders/PchCacheCleaner.cs +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO; -using System.IO.Abstractions; -using System.Linq; -using SonarLint.VisualStudio.Core; - -namespace SonarLint.VisualStudio.CFamily.PreCompiledHeaders -{ - internal interface IPchCacheCleaner - { - void Cleanup(); - } - - internal class PchCacheCleaner : IPchCacheCleaner - { - private readonly IFileSystem fileSystem; - private readonly string pchFilePath; - - public PchCacheCleaner(IFileSystem fileSystem, string pchFilePath) - { - this.fileSystem = fileSystem; - this.pchFilePath = pchFilePath; - } - - public void Cleanup() => DeletePchFiles(); - - private void DeletePchFiles() - { - var pchDirectory = Path.GetDirectoryName(pchFilePath); - var pchFileName = Path.GetFileName(pchFilePath); - - // CFamily will create PCH files with the same root as the file that we gave them - var filesToDelete = fileSystem.Directory.GetFiles(pchDirectory, $"{pchFileName}*").ToList(); - - foreach (var fileToDelete in filesToDelete) - { - try - { - fileSystem.File.Delete(fileToDelete); - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - // nothing to do if we fail to delete - } - } - } - } -} diff --git a/src/CFamily/PreCompiledHeaders/PreCompiledHeadersEventListener.cs b/src/CFamily/PreCompiledHeaders/PreCompiledHeadersEventListener.cs deleted file mode 100644 index e142ede5b6..0000000000 --- a/src/CFamily/PreCompiledHeaders/PreCompiledHeadersEventListener.cs +++ /dev/null @@ -1,129 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO.Abstractions; -using System.Linq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Infrastructure.VS.DocumentEvents; -using SonarLint.VisualStudio.IssueVisualization.Editor.LanguageDetection; - -namespace SonarLint.VisualStudio.CFamily.PreCompiledHeaders -{ - public interface IPreCompiledHeadersEventListener : IDisposable - { - } - - [Export(typeof(IPreCompiledHeadersEventListener))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal sealed class PreCompiledHeadersEventListener : IPreCompiledHeadersEventListener - { - internal const string PchJobId = "pch-generation"; - internal readonly int pchJobTimeoutInMilliseconds; - - private readonly ICFamilyAnalyzer cFamilyAnalyzer; - private readonly IActiveDocumentTracker activeDocumentTracker; - private readonly IScheduler scheduler; - private readonly ISonarLanguageRecognizer sonarLanguageRecognizer; - private readonly IPchCacheCleaner pchCacheCleaner; - private bool disposed; - - [ImportingConstructor] - public PreCompiledHeadersEventListener(ICFamilyAnalyzer cFamilyAnalyzer, - IActiveDocumentTracker activeDocumentTracker, - IScheduler scheduler, - ISonarLanguageRecognizer sonarLanguageRecognizer) - : this(cFamilyAnalyzer, activeDocumentTracker, scheduler, sonarLanguageRecognizer, new EnvironmentSettings(), new PchCacheCleaner(new FileSystem(), SubProcessFilePaths.PchFilePath)) - { - } - - internal PreCompiledHeadersEventListener(ICFamilyAnalyzer cFamilyAnalyzer, - IActiveDocumentTracker activeDocumentTracker, - IScheduler scheduler, - ISonarLanguageRecognizer sonarLanguageRecognizer, - IEnvironmentSettings environmentSettings, - IPchCacheCleaner pchCacheCleaner) - { - this.cFamilyAnalyzer = cFamilyAnalyzer; - this.activeDocumentTracker = activeDocumentTracker; - this.scheduler = scheduler; - this.sonarLanguageRecognizer = sonarLanguageRecognizer; - this.pchCacheCleaner = pchCacheCleaner; - - pchJobTimeoutInMilliseconds = environmentSettings.PCHGenerationTimeoutInMs(60 * 1000); - - activeDocumentTracker.ActiveDocumentChanged += OnActiveDocumentFocused; - } - - private void OnActiveDocumentFocused(object sender, ActiveDocumentChangedEventArgs e) - { - if (e.ActiveTextDocument == null) - { - return; - } - - var detectedLanguages = sonarLanguageRecognizer.Detect(e.ActiveTextDocument.FilePath, e.ActiveTextDocument.TextBuffer.ContentType); - - if (!detectedLanguages.Any() || !cFamilyAnalyzer.IsAnalysisSupported(detectedLanguages)) - { - return; - } - - var cFamilyAnalyzerOptions = new CFamilyAnalyzerOptions - { - CreatePreCompiledHeaders = true - }; - - scheduler.Schedule(PchJobId, token => - { - cFamilyAnalyzer.ExecuteAnalysis(e.ActiveTextDocument.FilePath, - detectedLanguages, - null, - cFamilyAnalyzerOptions, - null, - token); - }, pchJobTimeoutInMilliseconds); - } - - public void Dispose() - { - if (!disposed) - { - activeDocumentTracker.ActiveDocumentChanged -= OnActiveDocumentFocused; - activeDocumentTracker?.Dispose(); - - try - { - pchCacheCleaner.Cleanup(); - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - // Nothing to do if we failed to clear the cache - } - - disposed = true; - } - } - } -} diff --git a/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs b/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs new file mode 100644 index 0000000000..e118510964 --- /dev/null +++ b/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs @@ -0,0 +1,26 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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. + */ + +namespace SonarLint.VisualStudio.Core.CFamily; + +public interface IAggregatingCompilationDatabaseProvider +{ + string GetOrNull(string sourceFilePath); +} diff --git a/src/Core/Helpers/PathHelper.cs b/src/Core/Helpers/PathHelper.cs index 37562cd657..4cfbe16748 100644 --- a/src/Core/Helpers/PathHelper.cs +++ b/src/Core/Helpers/PathHelper.cs @@ -18,18 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Text; namespace SonarLint.VisualStudio.Core.Helpers { public static class PathHelper { - private static readonly Guid perVSInstanceFolderName = Guid.NewGuid(); + internal static readonly Guid PerVsInstanceFolderName = Guid.NewGuid(); /// /// Replace all invalid file path characters with the underscore ("_"). @@ -129,16 +125,16 @@ public static string GetTempDirForTask(bool perVSInstance, params string[] folde var taskFolders = new List { SLVSTempFolder }; - taskFolders.AddRange(folders); - + taskFolders.AddRange(folders); + var taskPath = Path.Combine(taskFolders.ToArray()); if(perVSInstance) { - return Path.Combine(taskPath, perVSInstanceFolderName.ToString()); + return Path.Combine(taskPath, PerVsInstanceFolderName.ToString()); } - return taskPath; + return taskPath; } public static string CalculateServerRoot(string localFilePath, IList serverPathsWithSameFileName) diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs new file mode 100644 index 0000000000..13e824a7cc --- /dev/null +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs @@ -0,0 +1,86 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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 NSubstitute.ReturnsExtensions; +using SonarLint.VisualStudio.CFamily; +using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; + +[TestClass] +public class VCXCompilationDatabaseProviderTests +{ + private const string SourceFilePath = "some path"; + private IVCXCompilationDatabaseStorage storage; + private IFileConfigProvider fileConfigProvider; + private VCXCompilationDatabaseProvider testSubject; + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestInitialize] + public void TestInitialize() + { + storage = Substitute.For(); + fileConfigProvider = Substitute.For(); + testSubject = new VCXCompilationDatabaseProvider( + storage, + fileConfigProvider); + } + + [TestMethod] + public void CreateOrNull_NoFileConfig_ReturnsNull() + { + fileConfigProvider.Get(SourceFilePath, default).ReturnsNull(); + + testSubject.CreateOrNull(SourceFilePath).Should().BeNull(); + + storage.DidNotReceiveWithAnyArgs().CreateDatabase(default); + } + + [TestMethod] + public void CreateOrNull_FileConfig_CantStore_ReturnsNull() + { + var fileConfig = Substitute.For(); + fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + storage.CreateDatabase(fileConfig).ReturnsNull(); + + testSubject.CreateOrNull(SourceFilePath).Should().BeNull(); + + storage.Received().CreateDatabase(fileConfig); + } + + [TestMethod] + public void CreateOrNull_FileConfig_StoresAndReturnsPath() + { + const string databasePath = "database path"; + var fileConfig = Substitute.For(); + fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + storage.CreateDatabase(fileConfig).Returns(databasePath); + + testSubject.CreateOrNull(SourceFilePath).Should().Be(databasePath); + } +} diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs new file mode 100644 index 0000000000..60292595a6 --- /dev/null +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs @@ -0,0 +1,139 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.IO; +using Newtonsoft.Json; +using NSubstitute.ExceptionExtensions; +using SonarLint.VisualStudio.CFamily.CMake; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Helpers; +using SonarLint.VisualStudio.Core.SystemAbstractions; +using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; + +[TestClass] +public class VCXCompilationDatabaseStorageTests +{ + private const string CompileCommand = "compile"; + private const string SourceDirectory = @"C:\a\b\c"; + private const string SourceFileName = "source.cpp"; + private static readonly string SourceFilePath = Path.Combine(SourceDirectory, SourceFileName); + private IFileSystemService fileSystemService; + private IThreadHandling threadHandling; + private IVCXCompilationDatabaseStorage testSubject; + private TestLogger testLogger; + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestInitialize] + public void TestInitialize() + { + fileSystemService = Substitute.For(); + threadHandling = Substitute.For(); + testLogger = new TestLogger(); + testSubject = new VCXCompilationDatabaseStorage(fileSystemService, threadHandling, testLogger); + } + + [TestMethod] + public void CreateDatabase_NonCriticalException_ReturnsNull() + { + fileSystemService.Directory.CreateDirectory(default).ThrowsForAnyArgs(); + + var database = testSubject.CreateDatabase(Substitute.For()); + + database.Should().BeNull(); + testLogger.AssertPartialOutputStrings(nameof(NotImplementedException)); + } + + [TestMethod] + public void CreateDatabase_CriticalException_Throws() + { + fileSystemService.Directory.CreateDirectory(default).ThrowsForAnyArgs(); + + var act = () => testSubject.CreateDatabase(Substitute.For()); + + act.Should().Throw(); + } + + [TestMethod] + public void CreateDatabase_FileWritten_ReturnsPathToDatabaseWithCorrectContent() + { + var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); + var expectedPath = Path.Combine(expectedDirectory, $"{SourceFileName}.{SourceFilePath.GetHashCode()}.json"); + var fileConfig = SetUpFileConfig(); + + var databasePath = testSubject.CreateDatabase(fileConfig); + + databasePath.Should().Be(expectedPath); + threadHandling.Received().ThrowIfOnUIThread(); + fileSystemService.Directory.Received().CreateDirectory(expectedDirectory); + fileSystemService.File.Received().WriteAllText(expectedPath, Arg.Any()); + VerifyDatabaseContents(); + } + + + [TestMethod] + public void CreateDatabase_Disposed_Throws() + { + testSubject.Dispose(); + + var act = () => testSubject.CreateDatabase(Substitute.For()); + + act.Should().Throw(); + } + + + [TestMethod] + public void Dispose_RemovesDirectory() + { + var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); + + testSubject.Dispose(); + testSubject.Dispose(); + testSubject.Dispose(); + + fileSystemService.Directory.Received(1).Delete(expectedDirectory, true); + } + + private static IFileConfig SetUpFileConfig() + { + var fileConfig = Substitute.For(); + fileConfig.CDFile.Returns(SourceFilePath); + fileConfig.CDDirectory.Returns(SourceDirectory); + fileConfig.CDCommand.Returns(CompileCommand); + return fileConfig; + } + + private void VerifyDatabaseContents() + { + var serializedCompilationDatabase = fileSystemService.File.ReceivedCalls().Single().GetArguments()[1] as string; + var compilationDatabaseEntries = JsonConvert.DeserializeObject(serializedCompilationDatabase); + compilationDatabaseEntries.Should().BeEquivalentTo(new CompilationDatabaseEntry { Directory = SourceDirectory, File = SourceFilePath, Command = CompileCommand, Arguments = null }); + } +} diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt index 1593f47ea9..1f684af00d 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt @@ -41,6 +41,7 @@ Referenced assemblies: - 'Microsoft.VisualStudio.Threading, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.VCProjectEngine, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' +- 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' - 'PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' - 'SonarLint.VisualStudio.CFamily, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' @@ -60,16 +61,14 @@ Referenced assemblies: - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' - 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' -# Number of references: 30 +# Number of references: 31 --- Assembly: 'SonarLint.VisualStudio.CFamily, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' Relative path: 'SonarLint.VisualStudio.CFamily.dll' Referenced assemblies: -- 'Microsoft.VisualStudio.CoreUtility, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.Shell.Framework, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' -- 'Microsoft.VisualStudio.Text.Data, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'SonarLint.VisualStudio.Core, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' @@ -78,7 +77,7 @@ Referenced assemblies: - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' -# Number of references: 11 +# Number of references: 9 --- Assembly: 'SonarLint.VisualStudio.ConnectedMode, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt index ac331b3f07..ada0de3e5a 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt @@ -41,6 +41,7 @@ Referenced assemblies: - 'Microsoft.VisualStudio.Threading, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.VCProjectEngine, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' +- 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' - 'PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' - 'SonarLint.VisualStudio.CFamily, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' @@ -60,16 +61,14 @@ Referenced assemblies: - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' - 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' -# Number of references: 30 +# Number of references: 31 --- Assembly: 'SonarLint.VisualStudio.CFamily, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' Relative path: 'SonarLint.VisualStudio.CFamily.dll' Referenced assemblies: -- 'Microsoft.VisualStudio.CoreUtility, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'Microsoft.VisualStudio.Shell.Framework, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' -- 'Microsoft.VisualStudio.Text.Data, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'SonarLint.VisualStudio.Core, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' @@ -78,7 +77,7 @@ Referenced assemblies: - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' -# Number of references: 11 +# Number of references: 9 --- Assembly: 'SonarLint.VisualStudio.ConnectedMode, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs new file mode 100644 index 0000000000..71c9b1fca5 --- /dev/null +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -0,0 +1,99 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ComponentModel.Composition; +using System.IO; +using System.IO.Abstractions; +using Newtonsoft.Json; +using SonarLint.VisualStudio.CFamily.CMake; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Helpers; +using SonarLint.VisualStudio.Core.SystemAbstractions; + +namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +internal interface IVCXCompilationDatabaseStorage : IDisposable +{ + string CreateDatabase(IFileConfig fileConfig); +} + +[Export(typeof(IVCXCompilationDatabaseStorage))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method: ImportingConstructor] +internal sealed class VCXCompilationDatabaseStorage(IFileSystemService fileSystemService, IThreadHandling threadHandling, ILogger logger) + : IVCXCompilationDatabaseStorage +{ + private bool disposed; + private readonly string compilationDatabaseDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); + + public string CreateDatabase(IFileConfig fileConfig) + { + ThrowIfDisposed(); + threadHandling.ThrowIfOnUIThread(); + + var compilationDatabaseEntry = new CompilationDatabaseEntry + { + Directory = fileConfig.CDDirectory, + Command = fileConfig.CDCommand, + File = fileConfig.CDFile + }; + var compilationDatabase = new[] { compilationDatabaseEntry }; + + var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(compilationDatabaseEntry); + + try + { + fileSystemService.Directory.CreateDirectory(compilationDatabaseDirectoryPath); + fileSystemService.File.WriteAllText(compilationDatabaseFullPath, JsonConvert.SerializeObject(compilationDatabase)); + return compilationDatabaseFullPath; + } + catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) + { + logger.LogVerbose(e.ToString()); + return null; + } + } + + private void ThrowIfDisposed() + { + if (disposed) + { + throw new ObjectDisposedException(nameof(VCXCompilationDatabaseStorage)); + } + } + + private string GetCompilationDatabaseFullPath(CompilationDatabaseEntry compilationDatabaseEntry) + { + var compilationDatabaseFileName = $"{Path.GetFileName(compilationDatabaseEntry.File)}.{compilationDatabaseEntry.File!.GetHashCode()}.json"; + var compilationDatabaseFullPath = Path.Combine(compilationDatabaseDirectoryPath, compilationDatabaseFileName); + return compilationDatabaseFullPath; + } + + public void Dispose() + { + if (disposed) + { + return; + } + + disposed = true; + fileSystemService.Directory.Delete(compilationDatabaseDirectoryPath, true); + } +} diff --git a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs new file mode 100644 index 0000000000..c431045387 --- /dev/null +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -0,0 +1,42 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ComponentModel.Composition; +using EnvDTE80; +using Microsoft.VisualStudio.Shell.Interop; +using SonarLint.VisualStudio.CFamily; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Infrastructure.VS; + +namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +[Export(typeof(IVCXCompilationDatabaseProvider))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method: ImportingConstructor] +internal class VCXCompilationDatabaseProvider( + IVCXCompilationDatabaseStorage storage, + IFileConfigProvider fileConfigProvider) + : IVCXCompilationDatabaseProvider +{ + public string CreateOrNull(string filePath) => + fileConfigProvider.Get(filePath, null) is {} fileConfig + ? storage.CreateDatabase(fileConfig) + : null; +} diff --git a/src/Integration.Vsix/SonarLintDaemonPackage.cs b/src/Integration.Vsix/SonarLintDaemonPackage.cs index 20b331c541..41160ecda2 100644 --- a/src/Integration.Vsix/SonarLintDaemonPackage.cs +++ b/src/Integration.Vsix/SonarLintDaemonPackage.cs @@ -22,12 +22,12 @@ using System.Runtime.InteropServices; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; -using SonarLint.VisualStudio.CFamily.PreCompiledHeaders; using SonarLint.VisualStudio.ConnectedMode.Migration; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Infrastructure.VS.Roslyn; using SonarLint.VisualStudio.Integration.Vsix.Analysis; using SonarLint.VisualStudio.Integration.Vsix.CFamily; +using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; using SonarLint.VisualStudio.Integration.Vsix.Events; using SonarLint.VisualStudio.Integration.Vsix.Resources; using SonarLint.VisualStudio.SLCore; @@ -63,7 +63,7 @@ public sealed class SonarLintDaemonPackage : AsyncPackage public const string CommandSetGuidString = "1F83EA11-3B07-45B3-BF39-307FD4F42194"; private ILogger logger; - private IPreCompiledHeadersEventListener cFamilyPreCompiledHeadersEventListener; + private IVCXCompilationDatabaseStorage vcxCompilationDatabaseStorage; private ISolutionRoslynAnalyzerManager solutionRoslynAnalyzerManager; private IProjectDocumentsEventsListener projectDocumentsEventsListener; private ISLCoreHandler slCoreHandler; @@ -100,15 +100,15 @@ private async Task InitAsync() await DisableRuleCommand.InitializeAsync(this, logger); await CFamilyReproducerCommand.InitializeAsync(this, logger); - cFamilyPreCompiledHeadersEventListener = await this.GetMefServiceAsync(); + vcxCompilationDatabaseStorage = await this.GetMefServiceAsync(); projectDocumentsEventsListener = await this.GetMefServiceAsync(); projectDocumentsEventsListener.Initialize(); solutionRoslynAnalyzerManager = await this.GetMefServiceAsync(); - + LegacyInstallationCleanup.CleanupDaemonFiles(logger); - + slCoreHandler = await this.GetMefServiceAsync(); slCoreHandler.EnableSloop(); } @@ -118,7 +118,7 @@ private async Task InitAsync() } logger?.WriteLine(Strings.Daemon_InitializationComplete); } - + private async Task MigrateBindingsToServerConnectionsIfNeededAsync() { var bindingToConnectionMigration = await this.GetMefServiceAsync(); @@ -131,8 +131,8 @@ protected override void Dispose(bool disposing) if (disposing) { - cFamilyPreCompiledHeadersEventListener?.Dispose(); - cFamilyPreCompiledHeadersEventListener = null; + vcxCompilationDatabaseStorage?.Dispose(); + vcxCompilationDatabaseStorage = null; projectDocumentsEventsListener?.Dispose(); projectDocumentsEventsListener = null; solutionRoslynAnalyzerManager?.Dispose(); diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index 0060faa55b..55a7d87d8f 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -41,7 +41,7 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport()); } [TestMethod] @@ -161,12 +161,13 @@ public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysis [TestMethod] public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraProperties() { + const string filePath = @"C:\file\path\myclass.cpp"; const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; - var compilationDatabaseLocator = WithCompilationDatabase(compilationDatabasePath); + var compilationDatabaseLocator = WithCompilationDatabase(filePath, compilationDatabasePath); var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); - testSubject.ExecuteAnalysis(@"C:\file\path\myclass.cpp", Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null @@ -177,11 +178,12 @@ public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraPropertie [TestMethod] public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExtraProperty() { - var compilationDatabaseLocator = WithCompilationDatabase(null); + const string filePath = @"C:\file\path\myclass.cpp"; + var compilationDatabaseLocator = WithCompilationDatabase(filePath, null); var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); - testSubject.ExecuteAnalysis(@"C:\file\path\myclass.cpp", Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null @@ -292,13 +294,13 @@ private static SLCoreAnalyzer CreateTestSubject(ISLCoreServiceProvider slCoreSer IActiveConfigScopeTracker activeConfigScopeTracker = null, IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, ICurrentTimeProvider currentTimeProvider = null, - ICompilationDatabaseLocator compilationDatabaseLocator = null) + IAggregatingCompilationDatabaseProvider compilationDatabaseLocator = null) { slCoreServiceProvider ??= Substitute.For(); activeConfigScopeTracker ??= Substitute.For(); analysisStatusNotifierFactory ??= Substitute.For(); currentTimeProvider ??= Substitute.For(); - compilationDatabaseLocator ??= Substitute.For(); + compilationDatabaseLocator ??= Substitute.For(); return new SLCoreAnalyzer(slCoreServiceProvider, activeConfigScopeTracker, analysisStatusNotifierFactory, @@ -306,10 +308,10 @@ private static SLCoreAnalyzer CreateTestSubject(ISLCoreServiceProvider slCoreSer compilationDatabaseLocator); } - private static ICompilationDatabaseLocator WithCompilationDatabase(string compilationDatabasePath) + private static IAggregatingCompilationDatabaseProvider WithCompilationDatabase(string filePath, string compilationDatabasePath) { - var compilationDatabaseLocator = Substitute.For(); - compilationDatabaseLocator.Locate().Returns(compilationDatabasePath); + var compilationDatabaseLocator = Substitute.For(); + compilationDatabaseLocator.GetOrNull(filePath).Returns(compilationDatabasePath); return compilationDatabaseLocator; } } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index 3eeb323634..e8b3194497 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -40,14 +40,14 @@ public class SLCoreAnalyzer : IAnalyzer private readonly IActiveConfigScopeTracker activeConfigScopeTracker; private readonly IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; private readonly ICurrentTimeProvider currentTimeProvider; - private readonly ICompilationDatabaseLocator compilationDatabaseLocator; + private readonly IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; [ImportingConstructor] public SLCoreAnalyzer(ISLCoreServiceProvider serviceProvider, IActiveConfigScopeTracker activeConfigScopeTracker, IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, ICurrentTimeProvider currentTimeProvider, - ICompilationDatabaseLocator compilationDatabaseLocator) + IAggregatingCompilationDatabaseProvider compilationDatabaseLocator) { this.serviceProvider = serviceProvider; this.activeConfigScopeTracker = activeConfigScopeTracker; @@ -80,7 +80,7 @@ public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable GetExtraProperties(IEnumerable detectedLanguages) + private Dictionary GetExtraProperties(string path, IEnumerable detectedLanguages) { Dictionary extraProperties = []; if (!IsCFamily(detectedLanguages)) @@ -130,7 +130,7 @@ private Dictionary GetExtraProperties(IEnumerable public static class FileSystemExtensions { - public static Mock SetFileDoesNotExist(this Mock fileSystem, string fullPath) => - fileSystem.SetFileExists(fullPath, false); - - public static Mock SetFileExists(this Mock fileSystem, string fullPath) => - fileSystem.SetFileExists(fullPath, true); - - public static Mock SetFileExists(this Mock fileSystem, string fullPath, bool result) - { - fileSystem.Setup(x => x.File.Exists(fullPath)).Returns(result); - return fileSystem; - } - - public static Mock SetFileReadAllText(this Mock fileSystem, string fullPath, string result) + public static T SetFileExists(this T fileSystem, string fullPath, bool result = true) + where T : class, IFileSystem { - fileSystem - .SetFileExists(fullPath) // saying a file has contents implies it exists - .Setup(x => x.File.ReadAllText(fullPath)).Returns(result); + fileSystem.File.Exists(fullPath).Returns(result); return fileSystem; } - - public static Mock VerifyFileExistsCalledOnce(this Mock fileSystem, string fullPath) - { - fileSystem.Verify(x => x.File.Exists(fullPath), Times.Once); - return fileSystem; - } - - public static Mock VerifyFileReadAllTextCalledOnce(this Mock fileSystem, string fullPath) + public static T VerifyFileExistsCalledOnce(this T fileSystem, string fullPath) + where T : class, IFileSystem { - fileSystem.Verify(x => x.File.ReadAllText(fullPath), Times.Once); + fileSystem.File.Received().Exists(fullPath); return fileSystem; } } From 3abac7a5e3fd1cf3ef22d274a4ac5554837d0cda Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:00:21 +0100 Subject: [PATCH 06/21] SLVS-1679 Enable issue streaming for SLCore analysis (#5876) [SLVS-1679](https://sonarsource.atlassian.net/browse/SLVS-1679) [SLVS-1679]: https://sonarsource.atlassian.net/browse/SLVS-1679?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../Subprocess/MessageHandlerTests.cs | 2 +- src/CFamily/Subprocess/MessageHandler.cs | 7 +-- .../Analysis/AnalysisServiceTests.cs | 54 +++++++++---------- src/Core/Analysis/AnalysisService.cs | 4 +- src/Core/Analysis/IIssueConsumer.cs | 2 +- .../Analysis/IssueConsumerFactoryTests.cs | 2 +- .../ReferencesTests.cs | 2 +- ...ConsumerTests.cs => IssueConsumerTests.cs} | 43 +++++++-------- .../VsAwareAnalysisServiceTests.cs | 2 +- .../Analysis/IssueConsumerFactory.cs | 2 +- ...atingIssueConsumer.cs => IssueConsumer.cs} | 45 ++++++---------- .../SonarLintTagger/VsAwareAnalysisService.cs | 14 ++--- .../Analysis/RaisedFindingProcessorTests.cs | 46 +++++----------- .../Analysis/RaisedFindingProcessor.cs | 14 ++--- 14 files changed, 97 insertions(+), 142 deletions(-) rename src/Integration.Vsix.UnitTests/SonarLintTagger/{AccumulatingIssueConsumerTests.cs => IssueConsumerTests.cs} (82%) rename src/Integration.Vsix/SonarLintTagger/{AccumulatingIssueConsumer.cs => IssueConsumer.cs} (81%) diff --git a/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs b/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs index 0bf4a6629e..a4c7fa3aa7 100644 --- a/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs +++ b/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs @@ -128,7 +128,7 @@ public void HandleMessage_IssuesForAnalyzedFileAreNotIgnored(string fileNameInMe context.IssueConverter.Invocations.Count.Should().Be(1); context.IssueConsumer.Invocations.Count.Should().Be(1); - context.IssueConsumer.Verify(x => x.Accept(analyzedFile, It.IsAny>())); + context.IssueConsumer.Verify(x => x.Set(analyzedFile, It.IsAny>())); } [TestMethod] diff --git a/src/CFamily/Subprocess/MessageHandler.cs b/src/CFamily/Subprocess/MessageHandler.cs index 0f49e6415c..306769203b 100644 --- a/src/CFamily/Subprocess/MessageHandler.cs +++ b/src/CFamily/Subprocess/MessageHandler.cs @@ -109,7 +109,7 @@ public void HandleMessage(Message message) // Rules that start with internal shouldn't be treated as issues. // Some of them should be handled like `internal.fileDependency`. See: https://github.com/SonarSource/sonarlint-visualstudio/issues/2611 - // Others should can simply ignored like `internal.z3RefutationRate`, which is used to log in the scanner how many issues are rejected by the Z3 solver + // Others should can simply ignored like `internal.z3RefutationRate`, which is used to log in the scanner how many issues are rejected by the Z3 solver case string s when s.StartsWith("internal."): break; @@ -137,11 +137,12 @@ private void HandleAnalysisIssue(Message message) IssueCount++; var issue = issueConverter.Convert(message, request.Context.CFamilyLanguage, request.Context.RulesConfiguration); - // Note: the file being analyzed might have been closed by the time the analysis results are + // Note: the file being analyzed might have been closed by the time the analysis results are // returned. This doesn't cause a crash; all active taggers will have been detached from the // TextBufferIssueTracker when the file was closed, but the TextBufferIssueTracker will // still exist and handle the call. - issueConsumer.Accept(request.Context.File, new[] { issue }); + // todo https://sonarsource.atlassian.net/browse/SLVS-1661 + issueConsumer.Set(request.Context.File, new[] { issue }); } internal /* for testing */ static bool IsIssueForActiveRule(Message message, ICFamilyRulesConfig rulesConfiguration) diff --git a/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs b/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs index 56096b6f9b..d543a6cd0c 100644 --- a/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs +++ b/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs @@ -40,7 +40,7 @@ public void MefCtor_CheckIsSingleton() { MefTestHelpers.CheckIsSingletonMefComponent(); } - + [TestMethod] public void ScheduleAnalysis_AnalysisScheduler_CachesIssueConsumer_And_RunsAnalyzerController() { @@ -54,7 +54,7 @@ public void ScheduleAnalysis_AnalysisScheduler_CachesIssueConsumer_And_RunsAnaly var testSubject = CreateTestSubject(analyzerController, issueConsumerStorage, scheduler); testSubject.ScheduleAnalysis("file/path", analysisId, detectedLanguages, issueConsumer, analyzerOptions); - + Received.InOrder(() => { scheduler.Schedule("file/path", Arg.Any>(), Arg.Any()); @@ -62,7 +62,7 @@ public void ScheduleAnalysis_AnalysisScheduler_CachesIssueConsumer_And_RunsAnaly analyzerController.ExecuteAnalysis("file/path", analysisId, detectedLanguages, issueConsumer, analyzerOptions, Arg.Any()); }); } - + [TestMethod] public void ScheduleAnalysis_JobCancelledBeforeStarting_DoesNotExecute() { @@ -72,7 +72,7 @@ public void ScheduleAnalysis_JobCancelledBeforeStarting_DoesNotExecute() var testSubject = CreateTestSubject(analyzerController, issueConsumerStorage, scheduler); testSubject.ScheduleAnalysis("file/path", default, default, default, default); - + scheduler.Received().Schedule("file/path", Arg.Any>(), Arg.Any()); issueConsumerStorage.DidNotReceiveWithAnyArgs().Set(default, default, default); analyzerController.DidNotReceiveWithAnyArgs().ExecuteAnalysis(default, default, default, default, default, default); @@ -90,26 +90,26 @@ public void ScheduleAnalysis_ProvidesCorrectTimeout(int envSettingsResponse, int Environment.SetEnvironmentVariable(EnvironmentSettings.AnalysisTimeoutEnvVar, envSettingsResponse.ToString()); var scheduler = Substitute.For(); var testSubject = CreateTestSubject(scheduler: scheduler); - + testSubject.ScheduleAnalysis("file/path", default, default, default, default); - + scheduler.Received().Schedule("file/path", Arg.Any>(), expectedTimeout); } finally { Environment.SetEnvironmentVariable(EnvironmentSettings.AnalysisTimeoutEnvVar, null); } - + } - + [TestMethod] public void ScheduleAnalysis_NoEnvironmentSettings_DefaultTimeout() { var scheduler = Substitute.For(); var testSubject = CreateTestSubject(scheduler: scheduler); - + testSubject.ScheduleAnalysis("file/path", default, default, default, default); - + scheduler.Received().Schedule("file/path", Arg.Any>(), AnalysisService.DefaultAnalysisTimeoutMs); } @@ -118,12 +118,12 @@ public void PublishIssues_NoConsumerInStorage_DoesNothing() { var issueConsumerStorage = CreateIssueConsumerStorageWithStoredItem(Guid.NewGuid(), null, false); var testSubject = CreateTestSubject(issueConsumerStorage:issueConsumerStorage); - + var act = () => testSubject.PublishIssues("file/path", Guid.NewGuid(), Substitute.For>()); - + act.Should().NotThrow(); } - + [TestMethod] public void PublishIssues_DifferentAnalysisId_DoesNothing() { @@ -131,12 +131,12 @@ public void PublishIssues_DifferentAnalysisId_DoesNothing() var issueConsumer = Substitute.For(); var issueConsumerStorage = CreateIssueConsumerStorageWithStoredItem(Guid.NewGuid(), issueConsumer, true); var testSubject = CreateTestSubject(issueConsumerStorage:issueConsumerStorage); - + testSubject.PublishIssues("file/path", analysisId, Substitute.For>()); - - issueConsumer.DidNotReceiveWithAnyArgs().Accept(default, default); + + issueConsumer.DidNotReceiveWithAnyArgs().Set(default, default); } - + [TestMethod] public void PublishIssues_MatchingConsumer_PublishesIssues() { @@ -147,10 +147,10 @@ public void PublishIssues_MatchingConsumer_PublishesIssues() var analysisIssues = Substitute.For>(); testSubject.PublishIssues("file/path", analysisId, analysisIssues); - - issueConsumer.Received().Accept("file/path", analysisIssues); + + issueConsumer.Received().Set("file/path", analysisIssues); } - + [DataRow(true)] [DataRow(false)] [DataTestMethod] @@ -170,22 +170,22 @@ public void CancelForFile_JobCancelledBeforeStarting_DoesNotExecute() var issueConsumerStorage = Substitute.For(); var scheduler = CreateDefaultScheduler(true); var testSubject = CreateTestSubject(issueConsumerStorage: issueConsumerStorage, scheduler: scheduler); - + testSubject.CancelForFile("file/path"); - + scheduler.Received().Schedule("file/path", Arg.Any>(), -1); issueConsumerStorage.DidNotReceiveWithAnyArgs().Remove(default); } - + [TestMethod] public void CancelForFile_RunsConsumerStorageClearAsScheduledJob() { var issueConsumerStorage = Substitute.For(); var scheduler = CreateDefaultScheduler(); var testSubject = CreateTestSubject(issueConsumerStorage: issueConsumerStorage, scheduler: scheduler); - + testSubject.CancelForFile("file/path"); - + Received.InOrder(() => { scheduler.Schedule("file/path", Arg.Any>(), -1); @@ -202,7 +202,7 @@ private static IAnalysisService CreateTestSubject(IAnalyzerController analyzerCo scheduler ??= Substitute.For(); return new AnalysisService(analyzerController, issueConsumerStorage, scheduler); } - + private static IIssueConsumerStorage CreateIssueConsumerStorageWithStoredItem(Guid analysisId, IIssueConsumer issueConsumer, bool result) { var issueConsumerStorage = Substitute.For(); @@ -214,7 +214,7 @@ private static IIssueConsumerStorage CreateIssueConsumerStorageWithStoredItem(Gu }); return issueConsumerStorage; } - + private static IScheduler CreateDefaultScheduler(bool createCancelled = false) { var cancellationTokenSource = new CancellationTokenSource(); diff --git a/src/Core/Analysis/AnalysisService.cs b/src/Core/Analysis/AnalysisService.cs index bcb54721a9..07b70e93da 100644 --- a/src/Core/Analysis/AnalysisService.cs +++ b/src/Core/Analysis/AnalysisService.cs @@ -50,7 +50,7 @@ public void PublishIssues(string filePath, Guid analysisId, IEnumerable public interface IIssueConsumer { - void Accept(string path, IEnumerable issues); + void Set(string path, IEnumerable issues); } diff --git a/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs b/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs index 77fbd40d4f..d3a12d3d04 100644 --- a/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs +++ b/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs @@ -58,7 +58,7 @@ public void Create_InitializedIssueConsumerReturned() /* The empty issues list is passed as an argument here because it's impossible to verify the actual pipeline due to the fact that mocking ITextSnapshot in a way that then can be used by a SnapshotSpan takes a lot of effort */ - consumer.Accept("analysisfile.txt", []); + consumer.Set("analysisfile.txt", []); publishedIssuesSnapshot.Should().NotBeNull(); publishedIssuesSnapshot.AnalyzedFilePath.Should().Be("updatedfile.txt"); // filename should be updted by this point diff --git a/src/Integration.Vsix.UnitTests/ReferencesTests.cs b/src/Integration.Vsix.UnitTests/ReferencesTests.cs index 498957f87f..336d243fe3 100644 --- a/src/Integration.Vsix.UnitTests/ReferencesTests.cs +++ b/src/Integration.Vsix.UnitTests/ReferencesTests.cs @@ -32,7 +32,7 @@ public class ReferencesTests public void MicrosoftVisualStudioVCProjectEngine_EnsureCorrectVersion() { var codeAnalysisAssemblyVersion = AssemblyHelper.GetVersionOfReferencedAssembly( - typeof(SonarLint.VisualStudio.Integration.Vsix.AccumulatingIssueConsumer), // any type in the VSIX assembly will do + typeof(SonarLint.VisualStudio.Integration.Vsix.IssueConsumer), // any type in the VSIX assembly will do "Microsoft.VisualStudio.VCProjectEngine"); AssertIsCorrectMajorVersion(codeAnalysisAssemblyVersion.Major); diff --git a/src/Integration.Vsix.UnitTests/SonarLintTagger/AccumulatingIssueConsumerTests.cs b/src/Integration.Vsix.UnitTests/SonarLintTagger/IssueConsumerTests.cs similarity index 82% rename from src/Integration.Vsix.UnitTests/SonarLintTagger/AccumulatingIssueConsumerTests.cs rename to src/Integration.Vsix.UnitTests/SonarLintTagger/IssueConsumerTests.cs index cbcc4ae160..268003c867 100644 --- a/src/Integration.Vsix.UnitTests/SonarLintTagger/AccumulatingIssueConsumerTests.cs +++ b/src/Integration.Vsix.UnitTests/SonarLintTagger/IssueConsumerTests.cs @@ -18,22 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.Text; -using Moq; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Integration.Vsix; using SonarLint.VisualStudio.IssueVisualization.Models; -using SonarLint.VisualStudio.TestInfrastructure; namespace SonarLint.VisualStudio.Integration.UnitTests.SonarLintTagger { [TestClass] - public class AccumulatingIssueConsumerTests + public class IssueConsumerTests { private static readonly IAnalysisIssue ValidIssue = CreateIssue(startLine: 1, endLine: 1); private static readonly ITextSnapshot ValidTextSnapshot = CreateSnapshot(lineCount: 10); @@ -44,18 +37,18 @@ public class AccumulatingIssueConsumerTests [TestMethod] public void Ctor_InvalidArgs_Throws() { - AccumulatingIssueConsumer.OnIssuesChanged validCallback = _ => { }; + IssueConsumer.OnIssuesChanged validCallback = _ => { }; - Action act = () => new AccumulatingIssueConsumer(null, ValidFilePath, validCallback, ValidConverter); + Action act = () => new IssueConsumer(null, ValidFilePath, validCallback, ValidConverter); act.Should().ThrowExactly().And.ParamName.Should().Be("analysisSnapshot"); - act = () => new AccumulatingIssueConsumer(ValidTextSnapshot, null, validCallback, ValidConverter); + act = () => new IssueConsumer(ValidTextSnapshot, null, validCallback, ValidConverter); act.Should().ThrowExactly().And.ParamName.Should().Be("analysisFilePath"); - act = () => new AccumulatingIssueConsumer(ValidTextSnapshot, ValidFilePath, null, ValidConverter); + act = () => new IssueConsumer(ValidTextSnapshot, ValidFilePath, null, ValidConverter); act.Should().ThrowExactly().And.ParamName.Should().Be("onIssuesChangedCallback"); - act = () => new AccumulatingIssueConsumer(ValidTextSnapshot, ValidFilePath, validCallback, null); + act = () => new IssueConsumer(ValidTextSnapshot, ValidFilePath, validCallback, null); act.Should().ThrowExactly().And.ParamName.Should().Be("issueToIssueVisualizationConverter"); } @@ -65,11 +58,11 @@ public void Accept_WrongFile_CallbackIsNotCalled() var callbackSpy = new OnIssuesChangedCallbackSpy(); var issues = new IAnalysisIssue[] { ValidIssue }; - var testSubject = new AccumulatingIssueConsumer(ValidTextSnapshot, "c:\\file1.txt", callbackSpy.Callback, ValidConverter); + var testSubject = new IssueConsumer(ValidTextSnapshot, "c:\\file1.txt", callbackSpy.Callback, ValidConverter); using (new AssertIgnoreScope()) { - testSubject.Accept("wrong file", issues); + testSubject.Set("wrong file", issues); } callbackSpy.CallCount.Should().Be(0); @@ -95,11 +88,11 @@ public void Accept_IssuesNotInSnapshotAreIgnored_CallbackIsCalledWithExpectedIss var callbackSpy = new OnIssuesChangedCallbackSpy(); var converter = CreatePassthroughConverter(); - var testSubject = new AccumulatingIssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); + var testSubject = new IssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); using (new AssertIgnoreScope()) { - testSubject.Accept(ValidFilePath, issues); + testSubject.Set(ValidFilePath, issues); } callbackSpy.CallCount.Should().Be(1); @@ -122,16 +115,16 @@ public void Accept_HasFileLevelIssues_NotIgnored() var callbackSpy = new OnIssuesChangedCallbackSpy(); var converter = CreatePassthroughConverter(); - var testSubject = new AccumulatingIssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); + var testSubject = new IssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); - testSubject.Accept(ValidFilePath, issues); + testSubject.Set(ValidFilePath, issues); callbackSpy.CallCount.Should().Be(1); callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(issues); } [TestMethod] - public void Accept_MultipleCallsToAccept_IssuesAreAccumulated() + public void Accept_MultipleCallsToAccept_IssuesAreReplaced() { var callbackSpy = new OnIssuesChangedCallbackSpy(); var firstSetOfIssues = new[] @@ -147,19 +140,19 @@ public void Accept_MultipleCallsToAccept_IssuesAreAccumulated() var snapshot = CreateSnapshot(lineCount: 10); var converter = CreatePassthroughConverter(); - var testSubject = new AccumulatingIssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); + var testSubject = new IssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); // 1. First call - testSubject.Accept(ValidFilePath, firstSetOfIssues); + testSubject.Set(ValidFilePath, firstSetOfIssues); callbackSpy.CallCount.Should().Be(1); callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(firstSetOfIssues); // 2. Second call - testSubject.Accept(ValidFilePath, secondSetOfIssues); + testSubject.Set(ValidFilePath, secondSetOfIssues); callbackSpy.CallCount.Should().Be(2); - callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(firstSetOfIssues.Union(secondSetOfIssues)); + callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(secondSetOfIssues); } private class OnIssuesChangedCallbackSpy @@ -217,7 +210,7 @@ private static IAnalysisIssueVisualizationConverter CreatePassthroughConverter() mockIssueConverter .Setup(x => x.Convert(It.IsAny(), It.IsAny())) .Returns((issue, snapshot) => CreateIssueViz(issue, new SnapshotSpan())); - + return mockIssueConverter.Object; IAnalysisIssueVisualization CreateIssueViz(IAnalysisIssue issue, SnapshotSpan snapshotSpan) diff --git a/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs b/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs index 61eff57691..472ce93daa 100644 --- a/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs +++ b/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs @@ -125,7 +125,7 @@ public void RequestAnalysis_ClearsErrorListAndSchedulesAnalysisOnBackgroundThrea Received.InOrder(() => { threadHandling.RunOnBackgroundThread(Arg.Any>>()); - issueConsumer.Accept(analysisFilePath, []); + issueConsumer.Set(analysisFilePath, []); analysisService.ScheduleAnalysis(analysisFilePath, Arg.Any(), Arg.Any>(), diff --git a/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs b/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs index 86e45cbbd6..7f9b06712c 100644 --- a/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs +++ b/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs @@ -74,7 +74,7 @@ public IIssueConsumer Create(ITextDocument textDocument, SnapshotChangedHandler onSnapshotChanged) { var issueHandler = new IssueHandler(textDocument, projectName, projectGuid, suppressedIssueMatcher, onSnapshotChanged, localHotspotsStore); - var issueConsumer = new AccumulatingIssueConsumer(analysisSnapshot, analysisFilePath, issueHandler.HandleNewIssues, converter); + var issueConsumer = new IssueConsumer(analysisSnapshot, analysisFilePath, issueHandler.HandleNewIssues, converter); return issueConsumer; } diff --git a/src/Integration.Vsix/SonarLintTagger/AccumulatingIssueConsumer.cs b/src/Integration.Vsix/SonarLintTagger/IssueConsumer.cs similarity index 81% rename from src/Integration.Vsix/SonarLintTagger/AccumulatingIssueConsumer.cs rename to src/Integration.Vsix/SonarLintTagger/IssueConsumer.cs index 3259845e70..f7e09675f2 100644 --- a/src/Integration.Vsix/SonarLintTagger/AccumulatingIssueConsumer.cs +++ b/src/Integration.Vsix/SonarLintTagger/IssueConsumer.cs @@ -18,41 +18,37 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using Microsoft.VisualStudio.Text; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; -/* +/* * Instancing: a new instance of this class should be created for each analysis request. - * + * * The class is initialized with the text snapshot representing the state of the text buffer * at the point the analysis was triggered. - * + * * The job of the class is to: - * 1) map the issue start/end positions supplied by the analyzer to spans in the supplied text snapshot, and - * 2) accumulate the list of issues that have return by the analyzer so far - * - * Each time IIssueConsumer.Accept is called, the new issues will be mapped back to the + * 1) map the issue start/end positions supplied by the analyzer to spans in the supplied text snapshot, and + * 2) replace the previous list of issues + * + * Each time IIssueConsumer.Set is called, the new issues will be mapped back to the * supplied snapshot and decorated with the additional data required for filtering and tagging. * Then, all of the issues that have been received so far will be passed to the OnIssuesChanged delegate. - * + * * However, it's possible that the text buffer could have been edited since the analysis * was triggered. It is the responsibility of the callback to translate the supplied IssueVisualizations * to the current text buffer snapshot, if necessary. - * - * + * + * * Mapping from reported issue line/char positions to the analysis snapshot * ------------------------------------------------------------------------ - * Currently, the JS/CFamily analyzers run against the file on disc, not the content of + * Currently, the JS/CFamily analyzers run against the file on disc, not the content of * the snapshot. We're assuming that analysis is triggered on save so the state of the * text snapshot matches the file on disc which is being analyzed. * We expect always to be able to map back from the reported issue to the snapshot. * However, we code defensively and strip out any issues that can't be mapped. - * + * */ namespace SonarLint.VisualStudio.Integration.Vsix @@ -60,28 +56,25 @@ namespace SonarLint.VisualStudio.Integration.Vsix /// /// Handles processing the issues for a single analysis request /// - internal class AccumulatingIssueConsumer : IIssueConsumer + internal class IssueConsumer : IIssueConsumer { // See bug #1487: this text snapshot should match the content of the file being analysed private readonly ITextSnapshot analysisSnapshot; private readonly IAnalysisIssueVisualizationConverter issueToIssueVisualizationConverter; - private readonly List allIssues; private readonly string analysisFilePath; private readonly OnIssuesChanged onIssuesChanged; public delegate void OnIssuesChanged(IEnumerable issues); - public AccumulatingIssueConsumer(ITextSnapshot analysisSnapshot, string analysisFilePath, OnIssuesChanged onIssuesChangedCallback, IAnalysisIssueVisualizationConverter issueToIssueVisualizationConverter) + public IssueConsumer(ITextSnapshot analysisSnapshot, string analysisFilePath, OnIssuesChanged onIssuesChangedCallback, IAnalysisIssueVisualizationConverter issueToIssueVisualizationConverter) { this.analysisSnapshot = analysisSnapshot ?? throw new ArgumentNullException(nameof(analysisSnapshot)); this.analysisFilePath = analysisFilePath ?? throw new ArgumentNullException(nameof(analysisFilePath)); this.onIssuesChanged = onIssuesChangedCallback ?? throw new ArgumentNullException(nameof(onIssuesChangedCallback)); this.issueToIssueVisualizationConverter = issueToIssueVisualizationConverter ?? throw new ArgumentNullException(nameof(issueToIssueVisualizationConverter)); - - allIssues = new List(); } - public void Accept(string path, IEnumerable issues) + public void Set(string path, IEnumerable issues) { // Callback from the daemon when new results are available if (path != analysisFilePath) @@ -92,14 +85,10 @@ public void Accept(string path, IEnumerable issues) Debug.Assert(issues.All(IsIssueFileLevelOrInAnalysisSnapshot), "Not all reported issues could be mapped to the analysis snapshot"); - var newIssues = issues + onIssuesChanged.Invoke(issues .Where(IsIssueFileLevelOrInAnalysisSnapshot) .Select(x => issueToIssueVisualizationConverter.Convert(x, analysisSnapshot)) - .ToArray(); - - allIssues.AddRange(newIssues); - - onIssuesChanged.Invoke(allIssues); + .ToList()); } /// diff --git a/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs b/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs index 2eb634d121..7b831d0a06 100644 --- a/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs +++ b/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs @@ -38,7 +38,7 @@ void RequestAnalysis(ITextDocument document, IAnalyzerOptions options); bool IsAnalysisSupported(IEnumerable detectedLanguages); - + void CancelForFile(string filePath); } @@ -52,7 +52,7 @@ internal class VsAwareAnalysisService : IVsAwareAnalysisService private readonly IAnalysisService analysisService; [ImportingConstructor] - public VsAwareAnalysisService(IVsProjectInfoProvider vsProjectInfoProvider, + public VsAwareAnalysisService(IVsProjectInfoProvider vsProjectInfoProvider, IIssueConsumerFactory issueConsumerFactory, IAnalysisService analysisService, IThreadHandling threadHandling) @@ -76,7 +76,7 @@ public void RequestAnalysis(ITextDocument document, public bool IsAnalysisSupported(IEnumerable detectedLanguages) => analysisService.IsAnalysisSupported(detectedLanguages); - public void CancelForFile(string filePath) => + public void CancelForFile(string filePath) => analysisService.CancelForFile(filePath); private async Task RequestAnalysisAsync(ITextDocument document, @@ -87,7 +87,7 @@ private async Task RequestAnalysisAsync(ITextDocument document, { var (projectName, projectGuid) = await vsProjectInfoProvider.GetDocumentProjectInfoAsync(analysisSnapshot.FilePath); var issueConsumer = issueConsumerFactory.Create(document, analysisSnapshot.FilePath, analysisSnapshot.TextSnapshot, projectName, projectGuid, errorListHandler); - + await ScheduleAnalysisOnBackgroundThreadAsync(analysisSnapshot.FilePath, detectedLanguages, issueConsumer, options); } @@ -99,11 +99,11 @@ private async Task ScheduleAnalysisOnBackgroundThreadAsync(string filePath, await threadHandling.RunOnBackgroundThread(() => { ClearErrorList(filePath, issueConsumer); - + analysisService.ScheduleAnalysis(filePath, Guid.NewGuid(), detectedLanguages, issueConsumer, analyzerOptions); }); } - private static void ClearErrorList(string filePath, IIssueConsumer issueConsumer) => - issueConsumer.Accept(filePath, []); + private static void ClearErrorList(string filePath, IIssueConsumer issueConsumer) => + issueConsumer.Set(filePath, []); } diff --git a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs index e854d379ad..1aea5df67e 100644 --- a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs +++ b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs @@ -38,24 +38,20 @@ namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation.Analy public class RaisedFindingProcessorTests { [TestMethod] - public void MefCtor_CheckIsExported() - { + public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); - } [TestMethod] - public void MefCtor_CheckIsSingleton() - { + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - } - + [TestMethod] - public void RaiseFindings_AnalysisIDisNull_Ignores() + public void RaiseFindings_AnalysisIdIsNull_Ignores() { var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, null); @@ -70,7 +66,7 @@ public void RaiseFindings_AnalysisIDisNull_Ignores() analysisService.DidNotReceive().PublishIssues(Arg.Any(), Arg.Any(), Arg.Any>()); raiseFindingParamsToAnalysisIssueConverter.DidNotReceive().GetAnalysisIssues(Arg.Any(), Arg.Any>()); } - + [TestMethod] public void RaiseFindings_NoFindings_Ignores() { @@ -203,8 +199,10 @@ public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() analysisStatusNotifier.Received(1).AnalysisFinished(2, TimeSpan.Zero); } - [TestMethod] - public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile() + [DataRow(true)] + [DataRow(false)] + [DataTestMethod] + public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isIntermediate) { var analysisId = Guid.NewGuid(); var fileUri1 = new FileUri("file://C:/somefile"); @@ -217,7 +215,7 @@ public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile() var findingsByFileUri = new Dictionary> { { fileUri1, [raisedFinding1] }, { fileUri2, [raisedFinding2] } }; - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediate, analysisId); var analysisService = Substitute.For(); var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); @@ -241,26 +239,6 @@ public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile() analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri2.LocalPath, analysisId); } - [TestMethod] - public void RaiseFindings_HasIssuesIntermediate_DoNotPublishFindings() - { - var raisedFinding1 = CreateTestFinding("csharpsquid:S100"); - var raisedFinding2 = CreateTestFinding("javascript:S101"); - var raisedFinding3 = CreateTestFinding("secrets:S1012"); - var raisedFindings = new List { raisedFinding1, raisedFinding2, raisedFinding3 }; - - var findingsByFileUri = new Dictionary> { { new FileUri("file://C:/somefile"), raisedFindings } }; - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, true, Guid.NewGuid()); - - var analysisService = Substitute.For(); - - var testSubject = CreateTestSubject(analysisService: analysisService); - - testSubject.RaiseFinding(raiseFindingParams); - - analysisService.DidNotReceiveWithAnyArgs().PublishIssues(default, default, default); - } - private RaisedFindingProcessor CreateTestSubject( IAnalysisService analysisService = null, IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = null, @@ -273,7 +251,7 @@ private RaisedFindingProcessor CreateTestSubject( analysisService ?? Substitute.For(), raiseFindingToAnalysisIssueConverter ?? Substitute.For(), analysisStatusNotifierFactory ?? Substitute.For(), logger ?? new TestLogger()); - + private static IRaiseFindingToAnalysisIssueConverter CreateConverter(FileUri fileUri, IReadOnlyCollection raisedFindingDtos, IAnalysisIssue[] findings) { @@ -282,7 +260,7 @@ private static IRaiseFindingToAnalysisIssueConverter CreateConverter(FileUri fil .GetAnalysisIssues(fileUri, Arg.Is>(x => x.SequenceEqual(raisedFindingDtos))).Returns(findings); return raiseFindingParamsToAnalysisIssueConverter; } - + private ISLCoreConstantsProvider CreateConstantsProviderWithLanguages(params SloopLanguage[] languages) { var slCoreConstantsProvider = Substitute.For(); diff --git a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs index 7969af767d..1be9ce63a4 100644 --- a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs +++ b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs @@ -58,7 +58,7 @@ public RaisedFindingProcessor(ISLCoreConstantsProvider slCoreConstantsProvider, analyzableLanguagesRuleKeyPrefixes = CalculateAnalyzableRulePrefixes(slCoreConstantsProvider); } - + public void RaiseFinding(RaiseFindingParams parameters) where T : RaisedFindingDto { if (!IsValid(parameters)) @@ -68,7 +68,7 @@ public void RaiseFinding(RaiseFindingParams parameters) where T : RaisedFi PublishFindings(parameters); } - + private bool IsValid(RaiseFindingParams parameters) where T : RaisedFindingDto { var logContext = $"[{nameof(RaiseFinding)}+{typeof(T).Name}]"; @@ -78,12 +78,6 @@ private bool IsValid(RaiseFindingParams parameters) where T : RaisedFindin return false; } - if (parameters.isIntermediatePublication) - { - logger.LogVerbose($"{logContext} {nameof(parameters.isIntermediatePublication)}=true, ignoring..."); - return false; - } - if (parameters.issuesByFileUri.Count == 0) { logger.LogVerbose($"{logContext} Empty {nameof(parameters.issuesByFileUri)} dictionary, ignoring..."); @@ -92,7 +86,7 @@ private bool IsValid(RaiseFindingParams parameters) where T : RaisedFindin return true; } - + private void PublishFindings(RaiseFindingParams parameters) where T : RaisedFindingDto { foreach (var fileAndIssues in parameters.issuesByFileUri) @@ -112,7 +106,7 @@ private T[] GetSupportedLanguageFindings(IEnumerable findings) where T : R { return findings.Where(i => analyzableLanguagesRuleKeyPrefixes.Exists(languageRepo => i.ruleKey.StartsWith(languageRepo))).ToArray(); } - + private static List CalculateAnalyzableRulePrefixes(ISLCoreConstantsProvider slCoreConstantsProvider) { return slCoreConstantsProvider.SLCoreAnalyzableLanguages? From f7f06219a9ab64aaf73d4aaf488d2125cebbce61 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:54:21 +0100 Subject: [PATCH 07/21] SLVS-1678 Use unique generated compilation database names (#5875) [SLVS-1678](https://sonarsource.atlassian.net/browse/SLVS-1678) [SLVS-1678]: https://sonarsource.atlassian.net/browse/SLVS-1678?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- ...egatingCompilationDatabaseProviderTests.cs | 4 +- .../ExternalCompilationDatabaseHandleTests.cs | 52 +++++ .../AggregatingCompilationDatabaseProvider.cs | 4 +- .../ExternalCompilationDatabaseHandle.cs | 33 +++ .../IVCXCompilationDatabaseProvider.cs | 4 +- ...IAggregatingCompilationDatabaseProvider.cs | 8 +- ...TemporaryCompilationDatabaseHandleTests.cs | 87 ++++++++ .../VCXCompilationDatabaseProviderTests.cs | 9 +- .../VCXCompilationDatabaseStorageTests.cs | 46 +++- .../IVCXCompilationDatabaseStorage.cs | 25 ++- .../TemporaryCompilationDatabaseHandle.cs | 49 +++++ .../VCXCompilationDatabaseProvider.cs | 7 +- .../Analysis/SLCoreAnalyzerTests.cs | 206 +++++++++--------- src/SLCore/Analysis/SLCoreAnalyzer.cs | 47 ++-- 14 files changed, 422 insertions(+), 159 deletions(-) create mode 100644 src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs create mode 100644 src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs create mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs create mode 100644 src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs diff --git a/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs b/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs index 579ab1bcd4..f4924fed0b 100644 --- a/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs +++ b/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs @@ -58,7 +58,7 @@ public void GetOrNull_CmakeAvailable_ReturnsCmakeLocation() var result = testSubject.GetOrNull("some path"); - result.Should().Be(location); + result.Should().BeOfType().And.BeEquivalentTo(new ExternalCompilationDatabaseHandle(location)); vcx.DidNotReceiveWithAnyArgs().CreateOrNull(default); } @@ -66,7 +66,7 @@ public void GetOrNull_CmakeAvailable_ReturnsCmakeLocation() public void GetOrNull_CmakeUnavailable_VcxAvailable_ReturnsVcxLocation() { var sourcePath = "some path"; - var location = "some location"; + var location = Substitute.For(); cmake.Locate().ReturnsNull(); vcx.CreateOrNull(sourcePath).Returns(location); diff --git a/src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs b/src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs new file mode 100644 index 0000000000..5dcf3b4826 --- /dev/null +++ b/src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs @@ -0,0 +1,52 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.CFamily.CompilationDatabase; + +namespace SonarLint.VisualStudio.CFamily.UnitTests.CompilationDatabase; + +[TestClass] +public class ExternalCompilationDatabaseHandleTests +{ + [TestMethod] + public void Ctor_AssignsExpectedValues() + { + const string filePath = "some path"; + var testSubject = new ExternalCompilationDatabaseHandle(filePath); + + testSubject.FilePath.Should().BeSameAs(filePath); + } + + [TestMethod] + public void Ctor_NullPath_Throws() + { + var act = () => new ExternalCompilationDatabaseHandle(null); + + act.Should().Throw().Which.ParamName.Should().Be("filePath"); + } + + [TestMethod] + public void Dispose_NoOp_DoesNotThrow() + { + var act = () => new ExternalCompilationDatabaseHandle("some path").Dispose(); + + act.Should().NotThrow(); + } +} diff --git a/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs b/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs index dcf86a69cd..300f74e86f 100644 --- a/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs +++ b/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs @@ -32,11 +32,11 @@ internal class AggregatingCompilationDatabaseProvider( IVCXCompilationDatabaseProvider vcxCompilationDatabaseProvider) : IAggregatingCompilationDatabaseProvider { - public string GetOrNull(string sourceFilePath) + public ICompilationDatabaseHandle GetOrNull(string sourceFilePath) { if (cMakeCompilationDatabaseLocator.Locate() is {} cmakeCompilationDatabasePath) { - return cmakeCompilationDatabasePath; + return new ExternalCompilationDatabaseHandle(cmakeCompilationDatabasePath); } return vcxCompilationDatabaseProvider.CreateOrNull(sourceFilePath); diff --git a/src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs b/src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs new file mode 100644 index 0000000000..f3c294c950 --- /dev/null +++ b/src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs @@ -0,0 +1,33 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.Core.CFamily; + +namespace SonarLint.VisualStudio.CFamily.CompilationDatabase; + +internal sealed class ExternalCompilationDatabaseHandle(string filePath) : ICompilationDatabaseHandle +{ + public string FilePath { get; } = filePath ?? throw new ArgumentNullException(nameof(filePath)); + + public void Dispose() + { + // do nothing + } +} diff --git a/src/CFamily/IVCXCompilationDatabaseProvider.cs b/src/CFamily/IVCXCompilationDatabaseProvider.cs index 4b99a37ef1..a1636368e2 100644 --- a/src/CFamily/IVCXCompilationDatabaseProvider.cs +++ b/src/CFamily/IVCXCompilationDatabaseProvider.cs @@ -18,9 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using SonarLint.VisualStudio.Core.CFamily; + namespace SonarLint.VisualStudio.CFamily; public interface IVCXCompilationDatabaseProvider { - string CreateOrNull(string filePath); + ICompilationDatabaseHandle CreateOrNull(string filePath); } diff --git a/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs b/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs index e118510964..fb55fe015a 100644 --- a/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs +++ b/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs @@ -22,5 +22,11 @@ namespace SonarLint.VisualStudio.Core.CFamily; public interface IAggregatingCompilationDatabaseProvider { - string GetOrNull(string sourceFilePath); + ICompilationDatabaseHandle GetOrNull(string sourceFilePath); } + +public interface ICompilationDatabaseHandle : IDisposable +{ + string FilePath { get; } +} + diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs new file mode 100644 index 0000000000..eff96d552b --- /dev/null +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs @@ -0,0 +1,87 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.IO.Abstractions; +using NSubstitute.ReceivedExtensions; +using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; + +[TestClass] +public class TemporaryCompilationDatabaseHandleTests +{ + private const string FilePath = "some path"; + private IFile file; + private TestLogger logger; + private TemporaryCompilationDatabaseHandle testSubject; + + [TestInitialize] + public void TestInitialize() + { + file = Substitute.For(); + logger = new TestLogger(); + testSubject = new TemporaryCompilationDatabaseHandle(FilePath, file, logger); + } + + [DataRow("path1")] + [DataRow(@"path1\path2")] + [DataTestMethod] + public void Ctor_AssignsExpectedValues(string path) => + new TemporaryCompilationDatabaseHandle(path, default, default).FilePath.Should().BeSameAs(path); + + [TestMethod] + public void Ctor_NullPath_Throws() + { + var act = () => new TemporaryCompilationDatabaseHandle(null, default, default); + + act.Should().Throw().Which.ParamName.Should().Be("filePath"); + } + + [TestMethod] + public void Dispose_DeletesFile() + { + testSubject.Dispose(); + + file.Received().Delete(FilePath); + logger.AssertNoOutputMessages(); + } + + [TestMethod] + public void Dispose_MultipleTimes_ActsOnlyOnce() + { + testSubject.Dispose(); + testSubject.Dispose(); + testSubject.Dispose(); + + file.ReceivedWithAnyArgs(1).Delete(default); + } + + [TestMethod] + public void Dispose_CatchesAndLogsExceptions() + { + var exception = new Exception("testexc"); + file.When(x => x.Delete(Arg.Any())).Throw(exception); + + var act = () => testSubject.Dispose(); + + act.Should().NotThrow(); + logger.AssertPartialOutputStringExists(exception.ToString()); + } +} diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs index 13e824a7cc..6a8c4100be 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs @@ -20,6 +20,7 @@ using NSubstitute.ReturnsExtensions; using SonarLint.VisualStudio.CFamily; +using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; @@ -74,13 +75,13 @@ public void CreateOrNull_FileConfig_CantStore_ReturnsNull() } [TestMethod] - public void CreateOrNull_FileConfig_StoresAndReturnsPath() + public void CreateOrNull_FileConfig_StoresAndReturnsHandle() { - const string databasePath = "database path"; var fileConfig = Substitute.For(); fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); - storage.CreateDatabase(fileConfig).Returns(databasePath); + var compilationDatabaseHandle = Substitute.For(); + storage.CreateDatabase(fileConfig).Returns(compilationDatabaseHandle); - testSubject.CreateOrNull(SourceFilePath).Should().Be(databasePath); + testSubject.CreateOrNull(SourceFilePath).Should().Be(compilationDatabaseHandle); } } diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs index 60292595a6..07a739e6b0 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs @@ -85,18 +85,32 @@ public void CreateDatabase_CriticalException_Throws() public void CreateDatabase_FileWritten_ReturnsPathToDatabaseWithCorrectContent() { var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); - var expectedPath = Path.Combine(expectedDirectory, $"{SourceFileName}.{SourceFilePath.GetHashCode()}.json"); var fileConfig = SetUpFileConfig(); - var databasePath = testSubject.CreateDatabase(fileConfig); + var databaseHandle = testSubject.CreateDatabase(fileConfig); - databasePath.Should().Be(expectedPath); + var temporaryCompilationDatabaseHandle = databaseHandle.Should().BeOfType().Subject; + Directory.GetParent(temporaryCompilationDatabaseHandle.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); + Path.GetExtension(temporaryCompilationDatabaseHandle.FilePath).Should().Be(".json"); threadHandling.Received().ThrowIfOnUIThread(); fileSystemService.Directory.Received().CreateDirectory(expectedDirectory); - fileSystemService.File.Received().WriteAllText(expectedPath, Arg.Any()); + fileSystemService.File.Received().WriteAllText(temporaryCompilationDatabaseHandle.FilePath, Arg.Any()); VerifyDatabaseContents(); } + [TestMethod] + public void CreateDatabase_CreatesDifferentHandlesForSameFile() + { + var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); + var fileConfig = SetUpFileConfig(); + + var databaseHandle1 = testSubject.CreateDatabase(fileConfig); + var databaseHandle2 = testSubject.CreateDatabase(fileConfig); + + Directory.GetParent(databaseHandle1.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); + Directory.GetParent(databaseHandle2.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); + Path.GetFileNameWithoutExtension(databaseHandle1.FilePath).Should().NotBe(Path.GetFileNameWithoutExtension(databaseHandle2.FilePath)); + } [TestMethod] public void CreateDatabase_Disposed_Throws() @@ -114,11 +128,33 @@ public void Dispose_RemovesDirectory() { var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); + testSubject.Dispose(); + + fileSystemService.Directory.Received().Delete(expectedDirectory, true); + testLogger.AssertNoOutputMessages(); + } + + [TestMethod] + public void Dispose_MultipleTimes_ActsOnlyOnce() + { testSubject.Dispose(); testSubject.Dispose(); testSubject.Dispose(); - fileSystemService.Directory.Received(1).Delete(expectedDirectory, true); + fileSystemService.Directory.ReceivedWithAnyArgs(1).Delete(default, default); + } + + + [TestMethod] + public void Dispose_CatchesAndLogsException() + { + var exception = new Exception("testexc"); + fileSystemService.Directory.When(x => x.Delete(Arg.Any(), Arg.Any())).Throw(exception); + + var act = () => testSubject.Dispose(); + + act.Should().NotThrow(); + testLogger.AssertPartialOutputStringExists(exception.ToString()); } private static IFileConfig SetUpFileConfig() diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index 71c9b1fca5..df2c92c6c5 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -20,10 +20,10 @@ using System.ComponentModel.Composition; using System.IO; -using System.IO.Abstractions; using Newtonsoft.Json; using SonarLint.VisualStudio.CFamily.CMake; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Core.Helpers; using SonarLint.VisualStudio.Core.SystemAbstractions; @@ -31,7 +31,7 @@ namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; internal interface IVCXCompilationDatabaseStorage : IDisposable { - string CreateDatabase(IFileConfig fileConfig); + ICompilationDatabaseHandle CreateDatabase(IFileConfig fileConfig); } [Export(typeof(IVCXCompilationDatabaseStorage))] @@ -43,7 +43,7 @@ internal sealed class VCXCompilationDatabaseStorage(IFileSystemService fileSyste private bool disposed; private readonly string compilationDatabaseDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); - public string CreateDatabase(IFileConfig fileConfig) + public ICompilationDatabaseHandle CreateDatabase(IFileConfig fileConfig) { ThrowIfDisposed(); threadHandling.ThrowIfOnUIThread(); @@ -56,13 +56,13 @@ public string CreateDatabase(IFileConfig fileConfig) }; var compilationDatabase = new[] { compilationDatabaseEntry }; - var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(compilationDatabaseEntry); + var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(); try { fileSystemService.Directory.CreateDirectory(compilationDatabaseDirectoryPath); fileSystemService.File.WriteAllText(compilationDatabaseFullPath, JsonConvert.SerializeObject(compilationDatabase)); - return compilationDatabaseFullPath; + return new TemporaryCompilationDatabaseHandle(compilationDatabaseFullPath, fileSystemService.File, logger); } catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) { @@ -79,9 +79,9 @@ private void ThrowIfDisposed() } } - private string GetCompilationDatabaseFullPath(CompilationDatabaseEntry compilationDatabaseEntry) + private string GetCompilationDatabaseFullPath() { - var compilationDatabaseFileName = $"{Path.GetFileName(compilationDatabaseEntry.File)}.{compilationDatabaseEntry.File!.GetHashCode()}.json"; + var compilationDatabaseFileName = $"{Guid.NewGuid()}.json"; var compilationDatabaseFullPath = Path.Combine(compilationDatabaseDirectoryPath, compilationDatabaseFileName); return compilationDatabaseFullPath; } @@ -92,8 +92,15 @@ public void Dispose() { return; } - disposed = true; - fileSystemService.Directory.Delete(compilationDatabaseDirectoryPath, true); + + try + { + fileSystemService.Directory.Delete(compilationDatabaseDirectoryPath, true); + } + catch (Exception e) + { + logger.LogVerbose(e.ToString()); + } } } diff --git a/src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs b/src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs new file mode 100644 index 0000000000..5853b5ac94 --- /dev/null +++ b/src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs @@ -0,0 +1,49 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.IO.Abstractions; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.CFamily; + +namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +internal sealed class TemporaryCompilationDatabaseHandle(string filePath, IFile file, ILogger logger) : ICompilationDatabaseHandle +{ + private bool disposed; + public string FilePath { get; } = filePath ?? throw new ArgumentNullException(nameof(filePath)); + + public void Dispose() + { + if (disposed) + { + return; + } + disposed = true; + + try + { + file.Delete(FilePath); + } + catch (Exception e) + { + logger.LogVerbose(e.ToString()); + } + } +} diff --git a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs index c431045387..c8e5b7c46d 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -19,11 +19,8 @@ */ using System.ComponentModel.Composition; -using EnvDTE80; -using Microsoft.VisualStudio.Shell.Interop; using SonarLint.VisualStudio.CFamily; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Infrastructure.VS; +using SonarLint.VisualStudio.Core.CFamily; namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; @@ -35,7 +32,7 @@ internal class VCXCompilationDatabaseProvider( IFileConfigProvider fileConfigProvider) : IVCXCompilationDatabaseProvider { - public string CreateOrNull(string filePath) => + public ICompilationDatabaseHandle CreateOrNull(string filePath) => fileConfigProvider.Get(filePath, null) is {} fileConfig ? storage.CreateDatabase(fileConfig) : null; diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index 55a7d87d8f..95b2d145b2 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -33,30 +33,55 @@ namespace SonarLint.VisualStudio.SLCore.UnitTests.Analysis; [TestClass] public class SLCoreAnalyzerTests { - [TestMethod] - public void MefCtor_CheckIsExported() + private const string ConfigScopeId = "ConfigScopeId"; + private const string FilePath = @"C:\file\path"; + private Guid analysisId; + private ISLCoreServiceProvider slCoreServiceProvider; + private IAnalysisSLCoreService analysisService; + private IActiveConfigScopeTracker activeConfigScopeTracker; + private IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; + private ICurrentTimeProvider currentTimeProvider; + private IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; + private IAnalysisStatusNotifier notifier; + private SLCoreAnalyzer testSubject; + + [TestInitialize] + public void TestInitialize() { + analysisId = Guid.NewGuid(); + slCoreServiceProvider = Substitute.For(); + analysisService = Substitute.For(); + SetUpServiceProvider(); + activeConfigScopeTracker = Substitute.For(); + analysisStatusNotifierFactory = Substitute.For(); + notifier = Substitute.For(); + SetUpDefaultNotifier(); + currentTimeProvider = Substitute.For(); + compilationDatabaseLocator = Substitute.For(); + testSubject = new SLCoreAnalyzer(slCoreServiceProvider, + activeConfigScopeTracker, + analysisStatusNotifierFactory, + currentTimeProvider, + compilationDatabaseLocator); + + void SetUpDefaultNotifier() => analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), FilePath, analysisId).Returns(notifier); + } + + [TestMethod] + public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); - } [TestMethod] - public void MefCtor_CheckIsSingleton() - { - MefTestHelpers.CheckIsSingletonMefComponent(); - } + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); [TestMethod] - public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() - { - var testSubject = CreateTestSubject(); - + public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() => testSubject.IsAnalysisSupported([]).Should().BeTrue(); - } [DataTestMethod] [DataRow(AnalysisLanguage.Javascript)] @@ -64,33 +89,24 @@ public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() [DataRow(AnalysisLanguage.CFamily)] [DataRow(AnalysisLanguage.CascadingStyleSheets)] [DataRow(AnalysisLanguage.RoslynFamily)] - public void IsAnalysisSupported_ReturnsTrueForEveryDetectedLanguage(AnalysisLanguage language) - { - var testSubject = CreateTestSubject(); - + public void IsAnalysisSupported_ReturnsTrueForEveryDetectedLanguage(AnalysisLanguage language) => testSubject.IsAnalysisSupported([language]).Should().BeTrue(); - } [TestMethod] public void ExecuteAnalysis_CreatesNotifierAndStarts() { - var analysisStatusNotifierFactory = CreateDefaultAnalysisStatusNotifier(out var notifier); - var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); - - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); - analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), @"C:\file\path"); + analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), FilePath, analysisId); notifier.Received().AnalysisStarted(); } [TestMethod] public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() { - var activeConfigScopeTracker = Substitute.For(); activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, CreateDefaultAnalysisStatusNotifier(out var notifier)); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); @@ -100,11 +116,9 @@ public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() [TestMethod] public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() { - var activeConfigScopeTracker = Substitute.For(); - activeConfigScopeTracker.Current.Returns(new ConfigurationScope("someconfigscopeid", IsReadyForAnalysis: false)); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, CreateDefaultAnalysisStatusNotifier(out var notifier)); + activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: false)); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); @@ -114,10 +128,10 @@ public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() [TestMethod] public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() { - var slCoreServiceProvider = CreatServiceProvider(out var analysisService, false); - var testSubject = CreateTestSubject(slCoreServiceProvider, CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpServiceProvider(false); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); analysisService.ReceivedCalls().Should().BeEmpty(); @@ -128,15 +142,15 @@ public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() { var expectedTimeStamp = DateTimeOffset.Now; - var analysisId = Guid.NewGuid(); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), currentTimeProvider:CreatCurrentTimeProvider(expectedTimeStamp)); + SetUpCurrentTimeProvider(expectedTimeStamp); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.analysisId == analysisId - && a.configurationScopeId == "someconfigscopeid" - && a.filesToAnalyze.Single() == new FileUri(@"C:\file\path") + && a.configurationScopeId == ConfigScopeId + && a.filesToAnalyze.Single() == new FileUri(FilePath) && a.extraProperties.Count == 0 && a.startTime == expectedTimeStamp.ToUnixTimeMilliseconds()), Arg.Any()); @@ -149,9 +163,9 @@ public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysisService(bool? value, bool expected) { IAnalyzerOptions options = value.HasValue ? new AnalyzerOptions { IsOnOpen = value.Value } : null; - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid")); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", default, default, default, options, default); + testSubject.ExecuteAnalysis(FilePath, default, default, default, options, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.shouldFetchServerIssues == expected), @@ -163,27 +177,42 @@ public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraPropertie { const string filePath = @"C:\file\path\myclass.cpp"; const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; - var compilationDatabaseLocator = WithCompilationDatabase(filePath, compilationDatabasePath); - var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); + var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); + SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(filePath, Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null && a.extraProperties["sonar.cfamily.compile-commands"] == compilationDatabasePath), Arg.Any()); + compilationDatabaseHandle.Received().Dispose(); + } + + [TestMethod] + public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDisposed() + { + const string filePath = @"C:\file\path\myclass.cpp"; + const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; + var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); + SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); + SetUpInitializedConfigScope(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(); + + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); + + compilationDatabaseHandle.Received().Dispose(); } [TestMethod] public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExtraProperty() { const string filePath = @"C:\file\path\myclass.cpp"; - var compilationDatabaseLocator = WithCompilationDatabase(filePath, null); - var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); + SetUpCompilationDatabaseLocator(filePath, null); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(filePath, Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null @@ -195,10 +224,9 @@ public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExt public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() { var cancellationTokenSource = new CancellationTokenSource(); - var analysisId = Guid.NewGuid(); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid")); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", analysisId, default, default, default, cancellationTokenSource.Token); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, cancellationTokenSource.Token); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), cancellationTokenSource.Token); @@ -207,10 +235,10 @@ public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() [TestMethod] public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysis() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpInitializedConfigScope(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); @@ -221,10 +249,10 @@ public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysi [TestMethod] public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); - analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet{new(@"C:\file\path")}, [])); + SetUpInitializedConfigScope(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet { new(@"C:\file\path") }, [])); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } @@ -232,11 +260,11 @@ public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() [TestMethod] public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpInitializedConfigScope(); var operationCanceledException = new OperationCanceledException(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.Received().AnalysisCancelled(); } @@ -244,74 +272,36 @@ public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() [TestMethod] public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpInitializedConfigScope(); var exception = new Exception(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.Received().AnalysisFailed(exception); } - private static ISLCoreServiceProvider CreatServiceProvider(out IAnalysisSLCoreService analysisService, bool result = true) - { - var service = Substitute.For(); - analysisService = service; - var slCoreServiceProvider = Substitute.For(); + private void SetUpServiceProvider(bool result = true) => slCoreServiceProvider.TryGetTransientService(out Arg.Any()) .Returns(info => { - info[0] = service; + info[0] = analysisService; return result; }); - return slCoreServiceProvider; - } - private static IActiveConfigScopeTracker CreateInitializedConfigScope(string id) - { - var activeConfigScopeTracker = Substitute.For(); - activeConfigScopeTracker.Current.Returns(new ConfigurationScope(id, IsReadyForAnalysis: true)); - return activeConfigScopeTracker; - } - - private static IAnalysisStatusNotifierFactory CreateDefaultAnalysisStatusNotifier(out IAnalysisStatusNotifier notifier) - { - var analysisStatusNotifierFactory = Substitute.For(); - notifier = Substitute.For(); - analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), Arg.Any()).Returns(notifier); - return analysisStatusNotifierFactory; - } + private void SetUpInitializedConfigScope() => + activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: true)); - private static ICurrentTimeProvider CreatCurrentTimeProvider(DateTimeOffset nowTime) - { - var currentTimeProvider = Substitute.For(); + private void SetUpCurrentTimeProvider(DateTimeOffset nowTime) => currentTimeProvider.Now.Returns(nowTime); - return currentTimeProvider; - } - - private static SLCoreAnalyzer CreateTestSubject(ISLCoreServiceProvider slCoreServiceProvider = null, - IActiveConfigScopeTracker activeConfigScopeTracker = null, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, - ICurrentTimeProvider currentTimeProvider = null, - IAggregatingCompilationDatabaseProvider compilationDatabaseLocator = null) + private static ICompilationDatabaseHandle CreateCompilationDatabaseHandle(string compilationDatabasePath) { - slCoreServiceProvider ??= Substitute.For(); - activeConfigScopeTracker ??= Substitute.For(); - analysisStatusNotifierFactory ??= Substitute.For(); - currentTimeProvider ??= Substitute.For(); - compilationDatabaseLocator ??= Substitute.For(); - return new SLCoreAnalyzer(slCoreServiceProvider, - activeConfigScopeTracker, - analysisStatusNotifierFactory, - currentTimeProvider, - compilationDatabaseLocator); + var handle = Substitute.For(); + handle.FilePath.Returns(compilationDatabasePath); + return handle; } - private static IAggregatingCompilationDatabaseProvider WithCompilationDatabase(string filePath, string compilationDatabasePath) - { - var compilationDatabaseLocator = Substitute.For(); - compilationDatabaseLocator.GetOrNull(filePath).Returns(compilationDatabasePath); - return compilationDatabaseLocator; - } + private void SetUpCompilationDatabaseLocator(string filePath, ICompilationDatabaseHandle handle) => + compilationDatabaseLocator.GetOrNull(filePath).Returns(handle); } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index e8b3194497..d183fb126b 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -43,7 +43,8 @@ public class SLCoreAnalyzer : IAnalyzer private readonly IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; [ImportingConstructor] - public SLCoreAnalyzer(ISLCoreServiceProvider serviceProvider, + public SLCoreAnalyzer( + ISLCoreServiceProvider serviceProvider, IActiveConfigScopeTracker activeConfigScopeTracker, IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, ICurrentTimeProvider currentTimeProvider, @@ -56,15 +57,17 @@ public SLCoreAnalyzer(ISLCoreServiceProvider serviceProvider, this.compilationDatabaseLocator = compilationDatabaseLocator; } - public bool IsAnalysisSupported(IEnumerable languages) - { - return true; - } + public bool IsAnalysisSupported(IEnumerable languages) => true; - public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, IIssueConsumer consumer, - IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) + public void ExecuteAnalysis( + string path, + Guid analysisId, + IEnumerable detectedLanguages, + IIssueConsumer consumer, + IAnalyzerOptions analyzerOptions, + CancellationToken cancellationToken) { - var analysisStatusNotifier = analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), path); + var analysisStatusNotifier = analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), path, analysisId); analysisStatusNotifier.AnalysisStarted(); var configurationScope = activeConfigScopeTracker.Current; @@ -80,28 +83,30 @@ public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, IAnalyzerOptions analyzerOptions, IAnalysisSLCoreService analysisService, IAnalysisStatusNotifier analysisStatusNotifier, - Dictionary extraProperties, CancellationToken cancellationToken) { try { + Dictionary properties = []; + using var temporaryResourcesHandle = EnrichPropertiesForCFamily(properties, path, detectedLanguages); + var (failedAnalysisFiles, _) = await analysisService.AnalyzeFilesAndTrackAsync( new AnalyzeFilesAndTrackParams( configScopeId, analysisId, [new FileUri(path)], - extraProperties, + properties, analyzerOptions?.IsOnOpen ?? false, currentTimeProvider.Now.ToUnixTimeMilliseconds()), cancellationToken); @@ -110,7 +115,6 @@ [new FileUri(path)], { analysisStatusNotifier.AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } - } catch (OperationCanceledException) { @@ -122,20 +126,19 @@ [new FileUri(path)], } } - private Dictionary GetExtraProperties(string path, IEnumerable detectedLanguages) + private IDisposable EnrichPropertiesForCFamily(Dictionary properties, string path, IEnumerable detectedLanguages) { - Dictionary extraProperties = []; if (!IsCFamily(detectedLanguages)) { - return extraProperties; + return null; } - var compilationDatabasePath = compilationDatabaseLocator.GetOrNull(path); - if (compilationDatabasePath != null) + var compilationDatabaseHandle = compilationDatabaseLocator.GetOrNull(path); + if (compilationDatabaseHandle != null) { - extraProperties[CFamilyCompileCommandsProperty] = compilationDatabasePath; + properties[CFamilyCompileCommandsProperty] = compilationDatabaseHandle.FilePath; } - return extraProperties; + return compilationDatabaseHandle; } private static bool IsCFamily(IEnumerable detectedLanguages) => detectedLanguages != null && detectedLanguages.Contains(AnalysisLanguage.CFamily); From d501a42ee9eb4fda3655624d9a7c5966e9d6ef8a Mon Sep 17 00:00:00 2001 From: Michael Jabbour <117195239+michael-jabbour-sonarsource@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:23:54 +0100 Subject: [PATCH 08/21] SLVS-1668 Include header file language in the VCX command (#5861) [SLVS-1668](https://sonarsource.atlassian.net/browse/SLVS-1668) Part of SLVS-1637 See the linked ticket for details about the problems we identified. This PR introduces the following changes: - To communicate the header file language, I am adding the relevant switches to the generated command in the header case as well. This helps analyze C headers as such when they are located inside C VCX projects. - Since `HeaderFileLanguage` is no longer needed, I am removing it from `FileConfig`. [SLVS-1668]: https://sonarsource.atlassian.net/browse/SLVS-1668?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../CFamily/CmdBuilderTests.cs | 13 +++++-------- .../CFamily/VcxProject/FileConfigTests.cs | 17 +++++++++++++---- .../CFamily/VcxProject/CmdBuilder.cs | 10 +--------- .../CFamily/VcxProject/FileConfig.cs | 4 ++-- .../CFamily/VcxProject/IFileConfig.cs | 1 - 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/Integration.Vsix.UnitTests/CFamily/CmdBuilderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/CmdBuilderTests.cs index eaf1bd6a60..f24886bc90 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/CmdBuilderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/CmdBuilderTests.cs @@ -314,14 +314,13 @@ public void AddOptFromProperties(string input, string output, string cmd) cmdBuilder.AddOptFromProperties(settingsMock.Object); cmdBuilder.GetFullCmd().Should().Be(cmd); - cmdBuilder.HeaderFileLang.Should().Be(""); } [TestMethod] - [DataRow("Default", "cpp")] - [DataRow("CompileAsC", "c")] - [DataRow("CompileAsCpp", "cpp")] - public void HeaderFileLang(string compileAs, string lang) + [DataRow("Default", "")] + [DataRow("CompileAsC", "/TC ")] + [DataRow("CompileAsCpp", "/TP ")] + public void HeaderFileLang(string compileAs, string cmd) { var cmdBuilder = new CmdBuilder(true); var settingsMock = new Mock(); @@ -329,8 +328,7 @@ public void HeaderFileLang(string compileAs, string lang) cmdBuilder.AddOptFromProperties(settingsMock.Object); - cmdBuilder.GetFullCmd().Should().Be(""); - cmdBuilder.HeaderFileLang.Should().Be(lang); + cmdBuilder.GetFullCmd().Should().Be(cmd); } [TestMethod] @@ -391,7 +389,6 @@ public void PCHUse() cmdBuilder.AddOptFromProperties(settingsMock.Object); cmdBuilder.GetFullCmd().Should().Be("/Yc\"C:\\pch.h\" "); - cmdBuilder.HeaderFileLang.Should().Be(""); } [TestMethod] diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs index be9ff0e454..7f34345926 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs @@ -139,7 +139,6 @@ public void TryGet_Full_Cmd() // Assert request.Should().NotBeNull(); Assert.AreEqual("\"C:\\path\\cl.exe\" /permissive- /std:c++17 /EHsc /arch:AVX512 /MT /RTCu /Zp8 /TP /DA \"c:\\dummy\\file.cpp\"", request.CDCommand); - Assert.AreEqual("", request.HeaderFileLanguage); Assert.AreEqual("C:\\path\\includeDir1;C:\\path\\includeDir2;C:\\path\\includeDir3;", request.EnvInclude); Assert.AreEqual("c:\\dummy\\file.cpp", request.CDFile); Assert.AreEqual("c:\\foo", request.CDDirectory); @@ -175,7 +174,7 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() // Assert request.Should().NotBeNull(); Assert.AreEqual("\"C:\\path\\cl.exe\" /Yu\"pch.h\" /FI\"pch.h\" /EHsc /RTCu \"c:\\dummy\\file.h\"", request.CDCommand); - Assert.AreEqual("cpp", request.HeaderFileLanguage); + Assert.AreEqual("c:\\dummy\\file.h", request.CDFile); // Arrange projectItemConfig.FileConfigProperties["CompileAs"] = "CompileAsC"; @@ -185,8 +184,18 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h"); // Assert - Assert.AreEqual("\"C:\\path\\cl.exe\" /FI\"FHeader.h\" /Yu\"pch.h\" /EHsc /RTCu \"c:\\dummy\\file.h\"", request.CDCommand); - Assert.AreEqual("c", request.HeaderFileLanguage); + Assert.AreEqual("\"C:\\path\\cl.exe\" /FI\"FHeader.h\" /Yu\"pch.h\" /EHsc /RTCu /TC \"c:\\dummy\\file.h\"", request.CDCommand); + Assert.AreEqual("c:\\dummy\\file.h", request.CDFile); + + // Arrange + projectItemConfig.FileConfigProperties["CompileAs"] = "CompileAsCpp"; + + // Act + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h"); + + // Assert + Assert.AreEqual("\"C:\\path\\cl.exe\" /FI\"FHeader.h\" /Yu\"pch.h\" /EHsc /RTCu /TP \"c:\\dummy\\file.h\"", request.CDCommand); + Assert.AreEqual("c:\\dummy\\file.h", request.CDFile); } [TestMethod] diff --git a/src/Integration.Vsix/CFamily/VcxProject/CmdBuilder.cs b/src/Integration.Vsix/CFamily/VcxProject/CmdBuilder.cs index 5774d6170f..fda75c0b44 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/CmdBuilder.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/CmdBuilder.cs @@ -39,7 +39,6 @@ internal class CmdBuilder StringBuilder Cmd { get; set; } = new StringBuilder(); bool IsHeader { get; set; } - public string HeaderFileLang { get; set; } = ""; public CmdBuilder(bool isHeader) { @@ -167,14 +166,7 @@ internal void AddOptFromProperties(IVCRulePropertyStorage properties) AddCmdOpt(ConvertStructMemberAlignment(structMemberAlignment)); var compileAs = properties.GetEvaluatedPropertyValue("CompileAs"); - if (IsHeader) - { - HeaderFileLang = (compileAs == "CompileAsC") ? "c" : "cpp"; - } - else - { - AddCmdOpt(ConvertCompileAsAndGetLanguage(compileAs)); - } + AddCmdOpt(ConvertCompileAsAndGetLanguage(compileAs)); // Additional options var additionalOptions = properties.GetEvaluatedPropertyValue("AdditionalOptions"); diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs index cdb23dbefb..93e11de617 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs @@ -45,6 +45,7 @@ public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, stri // Not supported return null; } + CmdBuilder cmdBuilder = new CmdBuilder(vcFile.ItemType == "ClInclude"); var compilerPath = vcConfig.GetEvaluatedPropertyValue("ClCompilerPath"); @@ -76,7 +77,6 @@ public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, stri CDCommand = cmdBuilder.GetFullCmd(), CDFile = absoluteFilePath, EnvInclude = envINCLUDE, - HeaderFileLanguage = cmdBuilder.HeaderFileLang, }; } @@ -134,7 +134,7 @@ private static IVCRulePropertyStorage GetVcFileSettings(ILogger logger, string a public string CDCommand { get; set; } public string CDFile { get; set; } public string EnvInclude { get; set; } - public string HeaderFileLanguage { get; set; } + #endregion } diff --git a/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs b/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs index 2f956be6a2..874cfe1c34 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs @@ -26,6 +26,5 @@ internal interface IFileConfig string CDCommand { get; } string CDFile { get; } string EnvInclude { get; } - string HeaderFileLanguage { get; } } } From e40832fbac8c2b87dd2a630ded655a797afceb72 Mon Sep 17 00:00:00 2001 From: Vasileios Naskos <168648790+vnaskos-sonar@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:17:03 +0000 Subject: [PATCH 09/21] SLVS-1669 Handle the case of C/C++ files outside the projects (#5890) --- .../Analysis/SLCoreAnalyzerTests.cs | 14 ++++++++++---- src/SLCore/Analysis/SLCoreAnalyzer.cs | 14 ++++++++++++-- src/SLCore/SLCoreStrings.Designer.cs | 9 +++++++++ src/SLCore/SLCoreStrings.resx | 2 ++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index 95b2d145b2..4a451040b7 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -19,6 +19,7 @@ */ using NSubstitute.ExceptionExtensions; +using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Core.ConfigurationScope; @@ -43,6 +44,7 @@ public class SLCoreAnalyzerTests private ICurrentTimeProvider currentTimeProvider; private IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; private IAnalysisStatusNotifier notifier; + private ILogger logger; private SLCoreAnalyzer testSubject; [TestInitialize] @@ -58,11 +60,13 @@ public void TestInitialize() SetUpDefaultNotifier(); currentTimeProvider = Substitute.For(); compilationDatabaseLocator = Substitute.For(); + logger = new TestLogger(); testSubject = new SLCoreAnalyzer(slCoreServiceProvider, activeConfigScopeTracker, analysisStatusNotifierFactory, currentTimeProvider, - compilationDatabaseLocator); + compilationDatabaseLocator, + logger); void SetUpDefaultNotifier() => analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), FilePath, analysisId).Returns(notifier); } @@ -74,7 +78,8 @@ public void MefCtor_CheckIsExported() => MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); [TestMethod] public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); @@ -206,7 +211,7 @@ public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDispos } [TestMethod] - public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExtraProperty() + public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_PassesEmptyStringAsExtraProperty() { const string filePath = @"C:\file\path\myclass.cpp"; SetUpCompilationDatabaseLocator(filePath, null); @@ -216,7 +221,8 @@ public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExt analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null - && !a.extraProperties.ContainsKey("sonar.cfamily.compile-commands")), + && a.extraProperties.ContainsKey("sonar.cfamily.compile-commands") + && a.extraProperties["sonar.cfamily.compile-commands"] == ""), Arg.Any()); } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index d183fb126b..49b087374d 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -20,6 +20,7 @@ using System.ComponentModel.Composition; using Microsoft.VisualStudio.Threading; +using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Core.ConfigurationScope; @@ -41,6 +42,7 @@ public class SLCoreAnalyzer : IAnalyzer private readonly IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; private readonly ICurrentTimeProvider currentTimeProvider; private readonly IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; + private readonly ILogger logger; [ImportingConstructor] public SLCoreAnalyzer( @@ -48,13 +50,15 @@ public SLCoreAnalyzer( IActiveConfigScopeTracker activeConfigScopeTracker, IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, ICurrentTimeProvider currentTimeProvider, - IAggregatingCompilationDatabaseProvider compilationDatabaseLocator) + IAggregatingCompilationDatabaseProvider compilationDatabaseLocator, + ILogger logger) { this.serviceProvider = serviceProvider; this.activeConfigScopeTracker = activeConfigScopeTracker; this.analysisStatusNotifierFactory = analysisStatusNotifierFactory; this.currentTimeProvider = currentTimeProvider; this.compilationDatabaseLocator = compilationDatabaseLocator; + this.logger = logger; } public bool IsAnalysisSupported(IEnumerable languages) => true; @@ -134,7 +138,13 @@ private IDisposable EnrichPropertiesForCFamily(Dictionary proper } var compilationDatabaseHandle = compilationDatabaseLocator.GetOrNull(path); - if (compilationDatabaseHandle != null) + if (compilationDatabaseHandle == null) + { + logger.WriteLine(SLCoreStrings.CompilationDatabaseNotFound, path); + // Pass empty compilation database path in order to get a more helpful message and not break the analyzer + properties[CFamilyCompileCommandsProperty] = ""; + } + else { properties[CFamilyCompileCommandsProperty] = compilationDatabaseHandle.FilePath; } diff --git a/src/SLCore/SLCoreStrings.Designer.cs b/src/SLCore/SLCoreStrings.Designer.cs index 097d266379..b45c16638a 100644 --- a/src/SLCore/SLCoreStrings.Designer.cs +++ b/src/SLCore/SLCoreStrings.Designer.cs @@ -96,6 +96,15 @@ public static string CertificateValidator_FailureReasonTemplate { } } + /// + /// Looks up a localized string similar to [SLCoreAnalyzer] No compilation database found for file: {0}. Check that the file is part of a supported project type in the current solution.. + /// + public static string CompilationDatabaseNotFound { + get { + return ResourceManager.GetString("CompilationDatabaseNotFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Configuration scope conflict. /// diff --git a/src/SLCore/SLCoreStrings.resx b/src/SLCore/SLCoreStrings.resx index 0ccbd2f31f..a57a56a5c2 100644 --- a/src/SLCore/SLCoreStrings.resx +++ b/src/SLCore/SLCoreStrings.resx @@ -179,5 +179,7 @@ Proxy type can not be determined from scheme '{0}'. Returning HTTP proxy type. + + [SLCoreAnalyzer] No compilation database found for file: {0}. Check that the file is part of a supported project type in the current solution. \ No newline at end of file From cad83bc56dfc9e3a573c72bf7bee4f30c2c171fd Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:57:30 +0100 Subject: [PATCH 10/21] SLVS-1693 Fix: issues set to empty after hotspots event (#5886) [SLVS-1693](https://sonarsource.atlassian.net/browse/SLVS-1693) [SLVS-1693]: https://sonarsource.atlassian.net/browse/SLVS-1693?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../Subprocess/MessageHandlerTests.cs | 2 +- src/CFamily/Subprocess/MessageHandler.cs | 2 +- .../Analysis/AnalysisServiceTests.cs | 38 ---- .../Analysis/HotspotsPublisherTests.cs | 96 ++++++++++ .../Analysis/IssuePublisherTests.cs | 96 ++++++++++ src/Core/Analysis/AnalysisService.cs | 9 - src/Core/Analysis/HotspotPublisher.cs | 38 ++++ src/Core/Analysis/IAnalysisService.cs | 5 - src/Core/Analysis/IFindingsPublisher.cs | 33 ++++ src/Core/Analysis/IIssueConsumer.cs | 3 +- src/Core/Analysis/IssuePublisher.cs | 38 ++++ .../Analysis/IssueConsumerFactoryTests.cs | 2 +- .../Analysis/IssueHandlerTests.cs | 60 +++--- .../SonarLintTagger/IssueConsumerTests.cs | 177 +++++++++++++----- .../VsAwareAnalysisServiceTests.cs | 3 +- .../Analysis/IssueConsumerFactory.cs | 2 +- .../IssueConsumerFactory_IssueHandler.cs | 45 ++--- .../SonarLintTagger/IssueConsumer.cs | 48 +++-- .../SonarLintTagger/VsAwareAnalysisService.cs | 7 +- .../Analysis/AnalysisListenerTests.cs | 24 ++- .../Analysis/RaisedFindingProcessorTests.cs | 69 +++---- .../Analysis/AnalysisListener.cs | 37 ++-- .../Analysis/RaisedFindingProcessor.cs | 51 ++--- 23 files changed, 614 insertions(+), 271 deletions(-) create mode 100644 src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs create mode 100644 src/Core.UnitTests/Analysis/IssuePublisherTests.cs create mode 100644 src/Core/Analysis/HotspotPublisher.cs create mode 100644 src/Core/Analysis/IFindingsPublisher.cs create mode 100644 src/Core/Analysis/IssuePublisher.cs diff --git a/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs b/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs index a4c7fa3aa7..0acf246c84 100644 --- a/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs +++ b/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs @@ -128,7 +128,7 @@ public void HandleMessage_IssuesForAnalyzedFileAreNotIgnored(string fileNameInMe context.IssueConverter.Invocations.Count.Should().Be(1); context.IssueConsumer.Invocations.Count.Should().Be(1); - context.IssueConsumer.Verify(x => x.Set(analyzedFile, It.IsAny>())); + context.IssueConsumer.Verify(x => x.SetIssues(analyzedFile, It.IsAny>())); } [TestMethod] diff --git a/src/CFamily/Subprocess/MessageHandler.cs b/src/CFamily/Subprocess/MessageHandler.cs index 306769203b..c6b18b8ce9 100644 --- a/src/CFamily/Subprocess/MessageHandler.cs +++ b/src/CFamily/Subprocess/MessageHandler.cs @@ -142,7 +142,7 @@ private void HandleAnalysisIssue(Message message) // TextBufferIssueTracker when the file was closed, but the TextBufferIssueTracker will // still exist and handle the call. // todo https://sonarsource.atlassian.net/browse/SLVS-1661 - issueConsumer.Set(request.Context.File, new[] { issue }); + issueConsumer.SetIssues(request.Context.File, new[] { issue }); } internal /* for testing */ static bool IsIssueForActiveRule(Message message, ICFamilyRulesConfig rulesConfiguration) diff --git a/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs b/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs index d543a6cd0c..775a24556b 100644 --- a/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs +++ b/src/Core.UnitTests/Analysis/AnalysisServiceTests.cs @@ -113,44 +113,6 @@ public void ScheduleAnalysis_NoEnvironmentSettings_DefaultTimeout() scheduler.Received().Schedule("file/path", Arg.Any>(), AnalysisService.DefaultAnalysisTimeoutMs); } - [TestMethod] - public void PublishIssues_NoConsumerInStorage_DoesNothing() - { - var issueConsumerStorage = CreateIssueConsumerStorageWithStoredItem(Guid.NewGuid(), null, false); - var testSubject = CreateTestSubject(issueConsumerStorage:issueConsumerStorage); - - var act = () => testSubject.PublishIssues("file/path", Guid.NewGuid(), Substitute.For>()); - - act.Should().NotThrow(); - } - - [TestMethod] - public void PublishIssues_DifferentAnalysisId_DoesNothing() - { - var analysisId = Guid.NewGuid(); - var issueConsumer = Substitute.For(); - var issueConsumerStorage = CreateIssueConsumerStorageWithStoredItem(Guid.NewGuid(), issueConsumer, true); - var testSubject = CreateTestSubject(issueConsumerStorage:issueConsumerStorage); - - testSubject.PublishIssues("file/path", analysisId, Substitute.For>()); - - issueConsumer.DidNotReceiveWithAnyArgs().Set(default, default); - } - - [TestMethod] - public void PublishIssues_MatchingConsumer_PublishesIssues() - { - var analysisId = Guid.NewGuid(); - var issueConsumer = Substitute.For(); - var issueConsumerStorage = CreateIssueConsumerStorageWithStoredItem(analysisId, issueConsumer, true); - var testSubject = CreateTestSubject(issueConsumerStorage:issueConsumerStorage); - var analysisIssues = Substitute.For>(); - - testSubject.PublishIssues("file/path", analysisId, analysisIssues); - - issueConsumer.Received().Set("file/path", analysisIssues); - } - [DataRow(true)] [DataRow(false)] [DataTestMethod] diff --git a/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs b/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs new file mode 100644 index 0000000000..346672f84f --- /dev/null +++ b/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs @@ -0,0 +1,96 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.Core.Analysis; +using SonarLint.VisualStudio.TestInfrastructure; + +namespace SonarLint.VisualStudio.Core.UnitTests.Analysis; + +[TestClass] +public class HotspotPublisherTests +{ + private IIssueConsumerStorage issueConsumerStorage; + private IIssueConsumer issueConsumer; + private HotspotPublisher testSubject; + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported(MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => + MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestInitialize] + public void TestInitialize() + { + issueConsumerStorage = Substitute.For(); + issueConsumer = Substitute.For(); + testSubject = new HotspotPublisher(issueConsumerStorage); + } + + [TestMethod] + public void PublishHotspots_NoConsumerInStorage_DoesNothing() + { + issueConsumerStorage.TryGet(default, out _, out _).ReturnsForAnyArgs(false); + + var act = () => testSubject.Publish("file/path", Guid.NewGuid(), Substitute.For>()); + + act.Should().NotThrow(); + issueConsumer.DidNotReceiveWithAnyArgs().SetIssues(default, default); + issueConsumer.DidNotReceiveWithAnyArgs().SetHotspots(default, default); + } + + [TestMethod] + public void PublishHotspots_DifferentAnalysisId_DoesNothing() + { + issueConsumerStorage.TryGet("file/path", out Arg.Any(), out Arg.Any()) + .Returns(info => + { + info[1] = Guid.NewGuid(); + info[2] = issueConsumer; + return true; + }); + + testSubject.Publish("file/path", Guid.NewGuid(), Substitute.For>()); + + issueConsumer.DidNotReceiveWithAnyArgs().SetIssues(default, default); + issueConsumer.DidNotReceiveWithAnyArgs().SetHotspots(default, default); + } + + [TestMethod] + public void PublishHotspots_MatchingConsumer_PublishesHotspots() + { + var analysisId = Guid.NewGuid(); + var analysisIssues = Substitute.For>(); + issueConsumerStorage.TryGet("file/path", out Arg.Any(), out Arg.Any()) + .Returns(info => + { + info[1] = analysisId; + info[2] = issueConsumer; + return true; + }); + + testSubject.Publish("file/path", analysisId, analysisIssues); + + issueConsumer.Received().SetHotspots("file/path", analysisIssues); + issueConsumer.DidNotReceiveWithAnyArgs().SetIssues(default, default); + } +} diff --git a/src/Core.UnitTests/Analysis/IssuePublisherTests.cs b/src/Core.UnitTests/Analysis/IssuePublisherTests.cs new file mode 100644 index 0000000000..237d187890 --- /dev/null +++ b/src/Core.UnitTests/Analysis/IssuePublisherTests.cs @@ -0,0 +1,96 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.Core.Analysis; +using SonarLint.VisualStudio.TestInfrastructure; + +namespace SonarLint.VisualStudio.Core.UnitTests.Analysis; + +[TestClass] +public class IssuePublisherTests +{ + private IIssueConsumerStorage issueConsumerStorage; + private IIssueConsumer issueConsumer; + private IssuePublisher testSubject; + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported(MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => + MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestInitialize] + public void TestInitialize() + { + issueConsumerStorage = Substitute.For(); + issueConsumer = Substitute.For(); + testSubject = new IssuePublisher(issueConsumerStorage); + } + + [TestMethod] + public void PublishIssues_NoConsumerInStorage_DoesNothing() + { + issueConsumerStorage.TryGet(default, out _, out _).ReturnsForAnyArgs(false); + + var act = () => testSubject.Publish("file/path", Guid.NewGuid(), Substitute.For>()); + + act.Should().NotThrow(); + issueConsumer.DidNotReceiveWithAnyArgs().SetIssues(default, default); + issueConsumer.DidNotReceiveWithAnyArgs().SetHotspots(default, default); + } + + [TestMethod] + public void PublishIssues_DifferentAnalysisId_DoesNothing() + { + issueConsumerStorage.TryGet("file/path", out Arg.Any(), out Arg.Any()) + .Returns(info => + { + info[1] = Guid.NewGuid(); + info[2] = issueConsumer; + return true; + }); + + testSubject.Publish("file/path", Guid.NewGuid(), Substitute.For>()); + + issueConsumer.DidNotReceiveWithAnyArgs().SetIssues(default, default); + issueConsumer.DidNotReceiveWithAnyArgs().SetHotspots(default, default); + } + + [TestMethod] + public void PublishIssues_MatchingConsumer_PublishesIssues() + { + var analysisId = Guid.NewGuid(); + var analysisIssues = Substitute.For>(); + issueConsumerStorage.TryGet("file/path", out Arg.Any(), out Arg.Any()) + .Returns(info => + { + info[1] = analysisId; + info[2] = issueConsumer; + return true; + }); + + testSubject.Publish("file/path", analysisId, analysisIssues); + + issueConsumer.Received().SetIssues("file/path", analysisIssues); + issueConsumer.DidNotReceiveWithAnyArgs().SetHotspots(default, default); + } +} diff --git a/src/Core/Analysis/AnalysisService.cs b/src/Core/Analysis/AnalysisService.cs index 07b70e93da..8258aaec67 100644 --- a/src/Core/Analysis/AnalysisService.cs +++ b/src/Core/Analysis/AnalysisService.cs @@ -45,15 +45,6 @@ public bool IsAnalysisSupported(IEnumerable languages) return analyzerController.IsAnalysisSupported(languages); } - public void PublishIssues(string filePath, Guid analysisId, IEnumerable issues) - { - if (issueConsumerStorage.TryGet(filePath, out var currentAnalysisId, out var issueConsumer) - && analysisId == currentAnalysisId) - { - issueConsumer.Set(filePath, issues); - } - } - public void ScheduleAnalysis(string filePath, Guid analysisId, IEnumerable detectedLanguages, diff --git a/src/Core/Analysis/HotspotPublisher.cs b/src/Core/Analysis/HotspotPublisher.cs new file mode 100644 index 0000000000..9cbe88f6af --- /dev/null +++ b/src/Core/Analysis/HotspotPublisher.cs @@ -0,0 +1,38 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ComponentModel.Composition; + +namespace SonarLint.VisualStudio.Core.Analysis; + +[Export(typeof(IHotspotPublisher))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method:ImportingConstructor] +internal class HotspotPublisher(IIssueConsumerStorage issueConsumerStorage) : IHotspotPublisher +{ + public void Publish(string filePath, Guid analysisId, IEnumerable findings) + { + if (issueConsumerStorage.TryGet(filePath, out var currentAnalysisId, out var issueConsumer) + && analysisId == currentAnalysisId) + { + issueConsumer.SetHotspots(filePath, findings); + } + } +} diff --git a/src/Core/Analysis/IAnalysisService.cs b/src/Core/Analysis/IAnalysisService.cs index 99ac54d4a0..492a24654a 100644 --- a/src/Core/Analysis/IAnalysisService.cs +++ b/src/Core/Analysis/IAnalysisService.cs @@ -30,11 +30,6 @@ public interface IAnalysisService /// bool IsAnalysisSupported(IEnumerable languages); - /// - /// Handles analysis results - /// - void PublishIssues(string filePath, Guid analysisId, IEnumerable issues); - /// /// Starts analysis for /// diff --git a/src/Core/Analysis/IFindingsPublisher.cs b/src/Core/Analysis/IFindingsPublisher.cs new file mode 100644 index 0000000000..89bb3e6203 --- /dev/null +++ b/src/Core/Analysis/IFindingsPublisher.cs @@ -0,0 +1,33 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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. + */ + +namespace SonarLint.VisualStudio.Core.Analysis; + +public interface IFindingsPublisher +{ + /// + /// Handles analysis results + /// + void Publish(string filePath, Guid analysisId, IEnumerable findings); +} + +public interface IIssuePublisher : IFindingsPublisher; + +public interface IHotspotPublisher : IFindingsPublisher; diff --git a/src/Core/Analysis/IIssueConsumer.cs b/src/Core/Analysis/IIssueConsumer.cs index 228de739c1..a10d445af3 100644 --- a/src/Core/Analysis/IIssueConsumer.cs +++ b/src/Core/Analysis/IIssueConsumer.cs @@ -25,5 +25,6 @@ namespace SonarLint.VisualStudio.Core.Analysis; /// public interface IIssueConsumer { - void Set(string path, IEnumerable issues); + void SetIssues(string path, IEnumerable issues); + void SetHotspots(string path, IEnumerable hotspots); } diff --git a/src/Core/Analysis/IssuePublisher.cs b/src/Core/Analysis/IssuePublisher.cs new file mode 100644 index 0000000000..df09fbe228 --- /dev/null +++ b/src/Core/Analysis/IssuePublisher.cs @@ -0,0 +1,38 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ComponentModel.Composition; + +namespace SonarLint.VisualStudio.Core.Analysis; + +[Export(typeof(IIssuePublisher))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method:ImportingConstructor] +internal class IssuePublisher(IIssueConsumerStorage issueConsumerStorage) : IIssuePublisher +{ + public void Publish(string filePath, Guid analysisId, IEnumerable findings) + { + if (issueConsumerStorage.TryGet(filePath, out var currentAnalysisId, out var issueConsumer) + && analysisId == currentAnalysisId) + { + issueConsumer.SetIssues(filePath, findings); + } + } +} diff --git a/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs b/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs index d3a12d3d04..e313367a1c 100644 --- a/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs +++ b/src/Integration.Vsix.UnitTests/Analysis/IssueConsumerFactoryTests.cs @@ -58,7 +58,7 @@ public void Create_InitializedIssueConsumerReturned() /* The empty issues list is passed as an argument here because it's impossible to verify the actual pipeline due to the fact that mocking ITextSnapshot in a way that then can be used by a SnapshotSpan takes a lot of effort */ - consumer.Set("analysisfile.txt", []); + consumer.SetIssues("analysisfile.txt", []); publishedIssuesSnapshot.Should().NotBeNull(); publishedIssuesSnapshot.AnalyzedFilePath.Should().Be("updatedfile.txt"); // filename should be updted by this point diff --git a/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs b/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs index f121f2dfaa..5c67f22dd8 100644 --- a/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs +++ b/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs @@ -18,19 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; using Microsoft.VisualStudio.Shell.TableManager; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.Text; -using Moq; using SonarLint.VisualStudio.ConnectedMode.Suppressions; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Integration.Vsix; using SonarLint.VisualStudio.Integration.Vsix.Analysis; -using SonarLint.VisualStudio.TestInfrastructure; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots; using static SonarLint.VisualStudio.Integration.Vsix.Analysis.IssueConsumerFactory; @@ -42,16 +35,14 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.Analysis public class IssueHandlerTests { [TestMethod] - public void HandleNewIssues_UpdatedSnapshotAndHotspotStoreHaveExpectedValues() + public void HandleNewIssues_UpdatedSnapshot() { var hotspotStoreMock = new Mock(); - var hotspot = CreateIssue("S112", startLine: 1, endLine: 1, isHotspot: true); var issue = CreateIssue("S111", startLine: 1, endLine: 1); var inputIssues = new[] { issue, - hotspot, }; var notificationHandler = new SnapshotChangeHandler(); @@ -59,7 +50,7 @@ public void HandleNewIssues_UpdatedSnapshotAndHotspotStoreHaveExpectedValues() var expectedGuid = Guid.NewGuid(); const string expectedProjectName = "my project name"; const string expectedFilePath = "c:\\aaa\\file.txt"; - + var testSubject = CreateTestSubject(notificationHandler.OnSnapshotChanged, expectedProjectName, expectedGuid, expectedFilePath, localHotspotsStoreUpdater:hotspotStoreMock.Object); @@ -70,10 +61,9 @@ public void HandleNewIssues_UpdatedSnapshotAndHotspotStoreHaveExpectedValues() notificationHandler.InvocationCount.Should().Be(1); // Check the updated issues - VerifyHotspotsAdded(hotspotStoreMock, expectedFilePath, new []{ hotspot }); - + notificationHandler.UpdatedSnapshot.Issues.Count().Should().Be(1); - notificationHandler.UpdatedSnapshot.Issues.Should().BeEquivalentTo(new []{issue}); + notificationHandler.UpdatedSnapshot.Issues.Should().BeEquivalentTo(issue); notificationHandler.UpdatedSnapshot.TryGetValue(0, StandardTableKeyNames.ProjectName, out var actualProjectName).Should().BeTrue(); actualProjectName.Should().Be(expectedProjectName); @@ -85,6 +75,34 @@ public void HandleNewIssues_UpdatedSnapshotAndHotspotStoreHaveExpectedValues() actualFilePath.Should().Be(expectedFilePath); notificationHandler.UpdatedSnapshot.AnalyzedFilePath.Should().Be(expectedFilePath); + hotspotStoreMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void HandleNewHotspots_HotspotStoreHaveExpectedValues() + { + var hotspotStoreMock = new Mock(); + + var hotspot = CreateIssue("S112", startLine: 1, endLine: 1, isHotspot: true); + var inputIssues = new[] + { + hotspot, + }; + + var notificationHandler = new SnapshotChangeHandler(); + + var expectedGuid = Guid.NewGuid(); + const string expectedProjectName = "my project name"; + const string expectedFilePath = "c:\\aaa\\file.txt"; + + var testSubject = CreateTestSubject(notificationHandler.OnSnapshotChanged, + expectedProjectName, expectedGuid, expectedFilePath, localHotspotsStoreUpdater:hotspotStoreMock.Object); + + // Act + testSubject.HandleNewHotspots(inputIssues); + + // Assert + VerifyHotspotsAdded(hotspotStoreMock, expectedFilePath, [hotspot]); } [TestMethod] @@ -125,14 +143,12 @@ public void HandleNewIssues_SpansAreTranslated() var inputIssues = new[] { - CreateIssue("xxx", startLine: 1, endLine: 1), - CreateIssue("xxxx", startLine: 3, endLine: 3, isHotspot: true) + CreateIssue("xxx", startLine: 1, endLine: 1) }; var issuesToReturnFromTranslator = new[] { - CreateIssue("yyy", startLine: 2, endLine: 2), - CreateIssue("yyyy", startLine: 4, endLine: 4, isHotspot: true) + CreateIssue("yyy", startLine: 2, endLine: 2) }; var notificationHandler = new SnapshotChangeHandler(); @@ -155,8 +171,6 @@ public void HandleNewIssues_SpansAreTranslated() translator.VerifyAll(); notificationHandler.InvocationCount.Should().Be(1); notificationHandler.UpdatedSnapshot.Issues.Should().BeEquivalentTo(issuesToReturnFromTranslator.First()); - - VerifyHotspotsAdded(hotspotStoreMock, filePath, new []{ issuesToReturnFromTranslator.Last()}); } [TestMethod] @@ -224,7 +238,7 @@ public void HandleNewIssues_SomeSuppressedIssues_IssuesGetMarkedCorrectly() issue3.IsSuppressed.Should().BeFalse(); issue4.IsSuppressed.Should().BeTrue(); } - + private static void VerifyHotspotsAdded(Mock hotspotStoreMock, string filePath, IAnalysisIssueVisualization[] expectedHotspots) { @@ -304,7 +318,7 @@ private static ITextDocument CreateValidTextDocument(string filePath) } private static IssueHandler CreateTestSubject(SnapshotChangedHandler notificationHandler, - ISuppressedIssueMatcher suppressedIssueMatcher = null, + ISuppressedIssueMatcher suppressedIssueMatcher = null, TranslateSpans translator = null, ITextDocument textDocument = null, ILocalHotspotsStoreUpdater localHotspotsStoreUpdater = null) @@ -332,7 +346,7 @@ private static IssueHandler CreateTestSubject(SnapshotChangedHandler notificatio var testSubject = new IssueHandler( textDocument, projectName, - projectGuid, + projectGuid, suppressedIssueMatcher, notificationHandler, localHotspotsStoreUpdater ?? Mock.Of(), diff --git a/src/Integration.Vsix.UnitTests/SonarLintTagger/IssueConsumerTests.cs b/src/Integration.Vsix.UnitTests/SonarLintTagger/IssueConsumerTests.cs index 268003c867..b20773bd8a 100644 --- a/src/Integration.Vsix.UnitTests/SonarLintTagger/IssueConsumerTests.cs +++ b/src/Integration.Vsix.UnitTests/SonarLintTagger/IssueConsumerTests.cs @@ -21,6 +21,7 @@ using Microsoft.VisualStudio.Text; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Integration.Vsix; +using SonarLint.VisualStudio.Integration.Vsix.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; namespace SonarLint.VisualStudio.Integration.UnitTests.SonarLintTagger @@ -28,44 +29,66 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.SonarLintTagger [TestClass] public class IssueConsumerTests { + private IssueConsumerFactory.IIssueHandler issueHandler; private static readonly IAnalysisIssue ValidIssue = CreateIssue(startLine: 1, endLine: 1); private static readonly ITextSnapshot ValidTextSnapshot = CreateSnapshot(lineCount: 10); private static readonly IAnalysisIssueVisualizationConverter ValidConverter = Mock.Of(); private const string ValidFilePath = "c:\\myfile.txt"; + [TestInitialize] + public void TestInitialize() + { + issueHandler = Substitute.For(); + } + [TestMethod] public void Ctor_InvalidArgs_Throws() { - IssueConsumer.OnIssuesChanged validCallback = _ => { }; - Action act = () => new IssueConsumer(null, ValidFilePath, validCallback, ValidConverter); + Action act = () => new IssueConsumer(null, ValidFilePath, issueHandler, ValidConverter); act.Should().ThrowExactly().And.ParamName.Should().Be("analysisSnapshot"); - act = () => new IssueConsumer(ValidTextSnapshot, null, validCallback, ValidConverter); + act = () => new IssueConsumer(ValidTextSnapshot, null, issueHandler, ValidConverter); act.Should().ThrowExactly().And.ParamName.Should().Be("analysisFilePath"); act = () => new IssueConsumer(ValidTextSnapshot, ValidFilePath, null, ValidConverter); - act.Should().ThrowExactly().And.ParamName.Should().Be("onIssuesChangedCallback"); + act.Should().ThrowExactly().And.ParamName.Should().Be("issueHandler"); - act = () => new IssueConsumer(ValidTextSnapshot, ValidFilePath, validCallback, null); + act = () => new IssueConsumer(ValidTextSnapshot, ValidFilePath, issueHandler, null); act.Should().ThrowExactly().And.ParamName.Should().Be("issueToIssueVisualizationConverter"); } [TestMethod] - public void Accept_WrongFile_CallbackIsNotCalled() + public void SetIssues_WrongFile_CallbackIsNotCalled() + { + var issues = new IAnalysisIssue[] { ValidIssue }; + + var testSubject = new IssueConsumer(ValidTextSnapshot, "c:\\file1.txt", issueHandler, ValidConverter); + + using (new AssertIgnoreScope()) + { + testSubject.SetIssues("wrong file", issues); + } + + issueHandler.DidNotReceiveWithAnyArgs().HandleNewIssues(default); + issueHandler.DidNotReceiveWithAnyArgs().HandleNewHotspots(default); + } + + [TestMethod] + public void SetHotspots_WrongFile_CallbackIsNotCalled() { - var callbackSpy = new OnIssuesChangedCallbackSpy(); var issues = new IAnalysisIssue[] { ValidIssue }; - var testSubject = new IssueConsumer(ValidTextSnapshot, "c:\\file1.txt", callbackSpy.Callback, ValidConverter); + var testSubject = new IssueConsumer(ValidTextSnapshot, "c:\\file1.txt", issueHandler, ValidConverter); using (new AssertIgnoreScope()) { - testSubject.Set("wrong file", issues); + testSubject.SetHotspots("wrong file", issues); } - callbackSpy.CallCount.Should().Be(0); + issueHandler.DidNotReceiveWithAnyArgs().HandleNewIssues(default); + issueHandler.DidNotReceiveWithAnyArgs().HandleNewHotspots(default); } [TestMethod] @@ -77,56 +100,84 @@ public void Accept_WrongFile_CallbackIsNotCalled() [DataRow(10, 10, true)] // end is in last line of snapshot [DataRow(10, 11, false)] // end is outside snapshot [DataRow(11, 11, false)] // end is outside snapshot - public void Accept_IssuesNotInSnapshotAreIgnored_CallbackIsCalledWithExpectedIssues(int issueStartLine, int issueEndLine, bool isMappableToSnapshot) + public void SetIssues_IssuesNotInSnapshotAreIgnored_CallbackIsCalledWithExpectedIssues(int issueStartLine, int issueEndLine, bool isMappableToSnapshot) { // Issues are 1-based. // Snapshots are 0-based so last line = index 9 const int LinesInSnapshot = 10; var snapshot = CreateSnapshot(LinesInSnapshot); var issues = new[] { CreateIssue(issueStartLine, issueEndLine) }; - - var callbackSpy = new OnIssuesChangedCallbackSpy(); var converter = CreatePassthroughConverter(); - var testSubject = new IssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); + var testSubject = new IssueConsumer(snapshot, ValidFilePath, issueHandler, converter); using (new AssertIgnoreScope()) { - testSubject.Set(ValidFilePath, issues); + testSubject.SetIssues(ValidFilePath, issues); } - callbackSpy.CallCount.Should().Be(1); - if (isMappableToSnapshot) - { - callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(issues); - } - else + ValidateReceivedIssues(isMappableToSnapshot ? issues : []); + } + + [TestMethod] + [DataRow(-1, 1, false)] // start line < 1 + [DataRow(0, 0, false)] // file-level issue, can't be mapped to snapshot + [DataRow(0, 1, false)] // illegal i.e. shouldn't happen, but should be ignored if it does + [DataRow(1, 1, true)] // starts in first line of snapshot + [DataRow(9, 10, true)] // in snapshot + [DataRow(10, 10, true)] // end is in last line of snapshot + [DataRow(10, 11, false)] // end is outside snapshot + [DataRow(11, 11, false)] // end is outside snapshot + public void SetHotspots_IssuesNotInSnapshotAreIgnored_CallbackIsCalledWithExpectedIssues(int issueStartLine, int issueEndLine, bool isMappableToSnapshot) + { + // Issues are 1-based. + // Snapshots are 0-based so last line = index 9 + const int LinesInSnapshot = 10; + var snapshot = CreateSnapshot(LinesInSnapshot); + var hotspots = new[] { CreateIssue(issueStartLine, issueEndLine) }; + var converter = CreatePassthroughConverter(); + + var testSubject = new IssueConsumer(snapshot, ValidFilePath, issueHandler, converter); + + using (new AssertIgnoreScope()) { - callbackSpy.LastSuppliedIssueVisualizations.Should().BeEmpty(); + testSubject.SetHotspots(ValidFilePath, hotspots); } + + ValidateReceivedHotspots(isMappableToSnapshot ? hotspots : []); } [TestMethod] - public void Accept_HasFileLevelIssues_NotIgnored() + public void SetIssues_HasFileLevelIssues_NotIgnored() { var snapshot = CreateSnapshot(10); var issues = new[] { CreateFileLevelIssue() }; + var converter = CreatePassthroughConverter(); + + var testSubject = new IssueConsumer(snapshot, ValidFilePath, issueHandler, converter); + + testSubject.SetIssues(ValidFilePath, issues); - var callbackSpy = new OnIssuesChangedCallbackSpy(); + ValidateReceivedIssues(issues); + } + + [TestMethod] + public void SetHotspots_HasFileLevelIssues_NotIgnored() + { + var snapshot = CreateSnapshot(10); + var hotspots = new[] { CreateFileLevelIssue() }; var converter = CreatePassthroughConverter(); - var testSubject = new IssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); + var testSubject = new IssueConsumer(snapshot, ValidFilePath, issueHandler, converter); - testSubject.Set(ValidFilePath, issues); + testSubject.SetHotspots(ValidFilePath, hotspots); - callbackSpy.CallCount.Should().Be(1); - callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(issues); + ValidateReceivedHotspots(hotspots); } [TestMethod] - public void Accept_MultipleCallsToAccept_IssuesAreReplaced() + public void SetIssues_MultipleCallsToAccept_IssuesAreReplaced() { - var callbackSpy = new OnIssuesChangedCallbackSpy(); var firstSetOfIssues = new[] { CreateIssue(1, 1), CreateIssue(2, 2) @@ -140,38 +191,64 @@ public void Accept_MultipleCallsToAccept_IssuesAreReplaced() var snapshot = CreateSnapshot(lineCount: 10); var converter = CreatePassthroughConverter(); - var testSubject = new IssueConsumer(snapshot, ValidFilePath, callbackSpy.Callback, converter); + var testSubject = new IssueConsumer(snapshot, ValidFilePath, issueHandler, converter); // 1. First call - testSubject.Set(ValidFilePath, firstSetOfIssues); + testSubject.SetIssues(ValidFilePath, firstSetOfIssues); - callbackSpy.CallCount.Should().Be(1); - callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(firstSetOfIssues); + ValidateReceivedIssues(firstSetOfIssues); + issueHandler.ClearReceivedCalls(); // 2. Second call - testSubject.Set(ValidFilePath, secondSetOfIssues); + testSubject.SetIssues(ValidFilePath, secondSetOfIssues); - callbackSpy.CallCount.Should().Be(2); - callbackSpy.LastSuppliedIssues.Should().BeEquivalentTo(secondSetOfIssues); + ValidateReceivedIssues(secondSetOfIssues); } - private class OnIssuesChangedCallbackSpy + [TestMethod] + public void SetHotspots_MultipleCallsToAccept_IssuesAreReplaced() { - public int CallCount { get; private set; } - public IList LastSuppliedIssueVisualizations { get; private set; } - public IList LastSuppliedIssues + var firstSetOfHotspots = new[] { - get - { - return LastSuppliedIssueVisualizations?.Select(x => x.Issue).ToList(); - } - } + CreateIssue(1, 1), CreateIssue(2, 2) + }; - public void Callback(IEnumerable issues) + var secondSetOfHotspots = new[] { - CallCount++; - LastSuppliedIssueVisualizations = issues?.ToList(); - } + CreateIssue(3,3), CreateIssue(4,4) + }; + + var snapshot = CreateSnapshot(lineCount: 10); + var converter = CreatePassthroughConverter(); + + var testSubject = new IssueConsumer(snapshot, ValidFilePath, issueHandler, converter); + + // 1. First call + testSubject.SetHotspots(ValidFilePath, firstSetOfHotspots); + + ValidateReceivedHotspots(firstSetOfHotspots); + issueHandler.ClearReceivedCalls(); + + // 2. Second call + testSubject.SetHotspots(ValidFilePath, secondSetOfHotspots); + + ValidateReceivedHotspots(secondSetOfHotspots); + } + + private void ValidateReceivedIssues(IAnalysisIssue[] issues) + { + issueHandler.ReceivedWithAnyArgs(1).HandleNewIssues(default); + issueHandler.DidNotReceiveWithAnyArgs().HandleNewHotspots(default); + var analysisIssues = issueHandler.ReceivedCalls().Single().GetArguments()[0] as IEnumerable; + analysisIssues.Select(x => x.Issue).Should().BeEquivalentTo(issues); + } + + private void ValidateReceivedHotspots(IAnalysisIssue[] hotspots) + { + issueHandler.ReceivedWithAnyArgs(1).HandleNewHotspots(default); + issueHandler.DidNotReceiveWithAnyArgs().HandleNewIssues(default); + var analysisIssues = issueHandler.ReceivedCalls().Single().GetArguments()[0] as IEnumerable; + analysisIssues.Select(x => x.Issue).Should().BeEquivalentTo(hotspots); } private static ITextSnapshot CreateSnapshot(int lineCount) diff --git a/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs b/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs index 472ce93daa..258919b4ce 100644 --- a/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs +++ b/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs @@ -125,7 +125,8 @@ public void RequestAnalysis_ClearsErrorListAndSchedulesAnalysisOnBackgroundThrea Received.InOrder(() => { threadHandling.RunOnBackgroundThread(Arg.Any>>()); - issueConsumer.Set(analysisFilePath, []); + issueConsumer.SetIssues(analysisFilePath, []); + issueConsumer.SetHotspots(analysisFilePath, []); analysisService.ScheduleAnalysis(analysisFilePath, Arg.Any(), Arg.Any>(), diff --git a/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs b/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs index 7f9b06712c..60aa3cbd8b 100644 --- a/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs +++ b/src/Integration.Vsix/Analysis/IssueConsumerFactory.cs @@ -74,7 +74,7 @@ public IIssueConsumer Create(ITextDocument textDocument, SnapshotChangedHandler onSnapshotChanged) { var issueHandler = new IssueHandler(textDocument, projectName, projectGuid, suppressedIssueMatcher, onSnapshotChanged, localHotspotsStore); - var issueConsumer = new IssueConsumer(analysisSnapshot, analysisFilePath, issueHandler.HandleNewIssues, converter); + var issueConsumer = new IssueConsumer(analysisSnapshot, analysisFilePath, issueHandler, converter); return issueConsumer; } diff --git a/src/Integration.Vsix/Analysis/IssueConsumerFactory_IssueHandler.cs b/src/Integration.Vsix/Analysis/IssueConsumerFactory_IssueHandler.cs index e8b41e5fcb..0d85d43ca3 100644 --- a/src/Integration.Vsix/Analysis/IssueConsumerFactory_IssueHandler.cs +++ b/src/Integration.Vsix/Analysis/IssueConsumerFactory_IssueHandler.cs @@ -18,12 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.VisualStudio.Text; using SonarLint.VisualStudio.ConnectedMode.Suppressions; -using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots; @@ -31,7 +27,14 @@ namespace SonarLint.VisualStudio.Integration.Vsix.Analysis { partial class IssueConsumerFactory { - internal class IssueHandler + internal interface IIssueHandler + { + void HandleNewIssues(IEnumerable issues); + + void HandleNewHotspots(IEnumerable hotspots); + } + + internal class IssueHandler : IIssueHandler { // We can't mock the span translation code (to difficult to mock), // so we use this delegate to by-passes it in tests. @@ -71,11 +74,24 @@ public IssueHandler(ITextDocument textDocument, this.suppressedIssueMatcher = suppressedIssueMatcher; this.onSnapshotChanged = onSnapshotChanged; this.localHotspotsStore = localHotspotsStore; - + this.translateSpans = translateSpans; } - internal /* for testing */ void HandleNewIssues(IEnumerable issues) + public void HandleNewIssues(IEnumerable issues) + { + var translatedIssues = PrepareIssues(issues); + var newSnapshot = new IssuesSnapshot(projectName, projectGuid, textDocument.FilePath, translatedIssues); + onSnapshotChanged(newSnapshot); + } + + public void HandleNewHotspots(IEnumerable hotspots) + { + var translatedIssues = PrepareIssues(hotspots); + localHotspotsStore.UpdateForFile(textDocument.FilePath, translatedIssues); + } + + private IAnalysisIssueVisualization[] PrepareIssues(IEnumerable issues) { MarkSuppressedIssues(issues); @@ -83,20 +99,7 @@ public IssueHandler(ITextDocument textDocument, // all issues to the current snapshot. // See bug #1487: https://github.com/SonarSource/sonarlint-visualstudio/issues/1487 var translatedIssues = translateSpans(issues, textDocument.TextBuffer.CurrentSnapshot); - - localHotspotsStore.UpdateForFile(textDocument.FilePath, - translatedIssues - .Where(issue => (issue.Issue as IAnalysisIssue)?.Type == AnalysisIssueType.SecurityHotspot)); - - var newSnapshot = new IssuesSnapshot(projectName, - projectGuid, - textDocument.FilePath, - translatedIssues.Where(issue => - { - var analysisIssueType = (issue.Issue as IAnalysisIssue)?.Type; - return analysisIssueType != AnalysisIssueType.SecurityHotspot; - })); - onSnapshotChanged(newSnapshot); + return translatedIssues; } private void MarkSuppressedIssues(IEnumerable issues) diff --git a/src/Integration.Vsix/SonarLintTagger/IssueConsumer.cs b/src/Integration.Vsix/SonarLintTagger/IssueConsumer.cs index f7e09675f2..83401e9e2b 100644 --- a/src/Integration.Vsix/SonarLintTagger/IssueConsumer.cs +++ b/src/Integration.Vsix/SonarLintTagger/IssueConsumer.cs @@ -20,6 +20,7 @@ using Microsoft.VisualStudio.Text; using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.Integration.Vsix.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; /* @@ -62,33 +63,56 @@ internal class IssueConsumer : IIssueConsumer private readonly ITextSnapshot analysisSnapshot; private readonly IAnalysisIssueVisualizationConverter issueToIssueVisualizationConverter; private readonly string analysisFilePath; - private readonly OnIssuesChanged onIssuesChanged; + private readonly IssueConsumerFactory.IIssueHandler issueHandler; - public delegate void OnIssuesChanged(IEnumerable issues); - - public IssueConsumer(ITextSnapshot analysisSnapshot, string analysisFilePath, OnIssuesChanged onIssuesChangedCallback, IAnalysisIssueVisualizationConverter issueToIssueVisualizationConverter) + public IssueConsumer(ITextSnapshot analysisSnapshot, string analysisFilePath, IssueConsumerFactory.IIssueHandler issueHandler, IAnalysisIssueVisualizationConverter issueToIssueVisualizationConverter) { this.analysisSnapshot = analysisSnapshot ?? throw new ArgumentNullException(nameof(analysisSnapshot)); this.analysisFilePath = analysisFilePath ?? throw new ArgumentNullException(nameof(analysisFilePath)); - this.onIssuesChanged = onIssuesChangedCallback ?? throw new ArgumentNullException(nameof(onIssuesChangedCallback)); + this.issueHandler = issueHandler ?? throw new ArgumentNullException(nameof(issueHandler)); this.issueToIssueVisualizationConverter = issueToIssueVisualizationConverter ?? throw new ArgumentNullException(nameof(issueToIssueVisualizationConverter)); } - public void Set(string path, IEnumerable issues) + public void SetIssues(string path, IEnumerable issues) { - // Callback from the daemon when new results are available - if (path != analysisFilePath) + if (!ValidatePath(path)) + { + return; + } + + issueHandler.HandleNewIssues(PrepareFindings(issues)); + } + + public void SetHotspots(string path, IEnumerable hotspots) + { + if (!ValidatePath(path)) { - Debug.Fail("Issues returned for an unexpected file path"); return; } - Debug.Assert(issues.All(IsIssueFileLevelOrInAnalysisSnapshot), "Not all reported issues could be mapped to the analysis snapshot"); + issueHandler.HandleNewHotspots(PrepareFindings(hotspots)); + } + + private List PrepareFindings(IEnumerable findings) + { + Debug.Assert(findings.All(IsIssueFileLevelOrInAnalysisSnapshot), "Not all reported findings could be mapped to the analysis snapshot"); - onIssuesChanged.Invoke(issues + var analysisIssueVisualizations = findings .Where(IsIssueFileLevelOrInAnalysisSnapshot) .Select(x => issueToIssueVisualizationConverter.Convert(x, analysisSnapshot)) - .ToList()); + .ToList(); + return analysisIssueVisualizations; + } + + private bool ValidatePath(string path) + { + // Callback from the daemon when new results are available + if (path != analysisFilePath) + { + Debug.Fail("Findings returned for an unexpected file path"); + return false; + } + return true; } /// diff --git a/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs b/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs index 7b831d0a06..1a5436f2b9 100644 --- a/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs +++ b/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs @@ -104,6 +104,9 @@ await threadHandling.RunOnBackgroundThread(() => }); } - private static void ClearErrorList(string filePath, IIssueConsumer issueConsumer) => - issueConsumer.Set(filePath, []); + private static void ClearErrorList(string filePath, IIssueConsumer issueConsumer) + { + issueConsumer.SetIssues(filePath, []); + issueConsumer.SetHotspots(filePath, []); + } } diff --git a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/AnalysisListenerTests.cs b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/AnalysisListenerTests.cs index de8867cbf3..8a79ee4513 100644 --- a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/AnalysisListenerTests.cs +++ b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/AnalysisListenerTests.cs @@ -38,6 +38,8 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -88,33 +90,39 @@ public void RaiseIssues_RaisesFinding() { var raiseIssueParams = new RaiseFindingParams(default, default, default, default); var raisedFindingProcessor = Substitute.For(); - var testSubject = CreateTestSubject(raisedFindingProcessor: raisedFindingProcessor); + var issuePublisher = Substitute.For(); + var testSubject = CreateTestSubject(raisedFindingProcessor: raisedFindingProcessor, issuePublisher: issuePublisher); testSubject.RaiseIssues(raiseIssueParams); - - raisedFindingProcessor.Received().RaiseFinding(raiseIssueParams); + + raisedFindingProcessor.Received().RaiseFinding(raiseIssueParams, issuePublisher); } - + [TestMethod] public void RaiseHotspots_RaisesFinding() { var raiseIssueParams = new RaiseHotspotParams(default, default, default, default); var raisedFindingProcessor = Substitute.For(); - var testSubject = CreateTestSubject(raisedFindingProcessor: raisedFindingProcessor); + var hotspotPublisher = Substitute.For(); + var testSubject = CreateTestSubject(raisedFindingProcessor: raisedFindingProcessor, hotspotPublisher: hotspotPublisher); testSubject.RaiseHotspots(raiseIssueParams); - - raisedFindingProcessor.Received().RaiseFinding(raiseIssueParams); + + raisedFindingProcessor.Received().RaiseFinding(raiseIssueParams, hotspotPublisher); } private AnalysisListener CreateTestSubject(IActiveConfigScopeTracker activeConfigScopeTracker = null, IAnalysisRequester analysisRequester = null, IRaisedFindingProcessor raisedFindingProcessor = null, + IIssuePublisher issuePublisher = null, + IHotspotPublisher hotspotPublisher = null, ILogger logger = null) => new(activeConfigScopeTracker ?? Substitute.For(), analysisRequester ?? Substitute.For(), raisedFindingProcessor ?? Substitute.For(), + issuePublisher ?? Substitute.For(), + hotspotPublisher ?? Substitute.For(), logger ?? new TestLogger()); - + } diff --git a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs index 1aea5df67e..8c3f6ebb3f 100644 --- a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs +++ b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs @@ -41,7 +41,6 @@ public class RaisedFindingProcessorTests public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); @@ -54,16 +53,14 @@ public void MefCtor_CheckIsSingleton() => public void RaiseFindings_AnalysisIdIsNull_Ignores() { var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, null); - - var analysisService = Substitute.For(); + var publisher = Substitute.For(); var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); - var testSubject = CreateTestSubject(analysisService: analysisService, - raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); + var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); - testSubject.RaiseFinding(raiseFindingParams); + testSubject.RaiseFinding(raiseFindingParams, publisher); - analysisService.DidNotReceive().PublishIssues(Arg.Any(), Arg.Any(), Arg.Any>()); + publisher.DidNotReceiveWithAnyArgs().Publish(default, default, default); raiseFindingParamsToAnalysisIssueConverter.DidNotReceive().GetAnalysisIssues(Arg.Any(), Arg.Any>()); } @@ -75,17 +72,15 @@ public void RaiseFindings_NoFindings_Ignores() var findingsByFileUri = new Dictionary> { { fileUri, [] } }; var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - - var analysisService = Substitute.For(); + var publisher = Substitute.For(); var raiseFindingParamsToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); - var testSubject = CreateTestSubject(analysisService: analysisService, - raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); + var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); - testSubject.RaiseFinding(raiseFindingParams); + testSubject.RaiseFinding(raiseFindingParams, publisher); raiseFindingParamsToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); - analysisService.Received().PublishIssues(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); + publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); } [TestMethod] @@ -97,21 +92,19 @@ public void RaiseFindings_NoSupportedLanguages_PublishesEmpty() { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - - var analysisService = Substitute.For(); + var publisher = Substitute.For(); IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); - var testSubject = CreateTestSubject(analysisService: analysisService, - raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, + var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, analysisStatusNotifierFactory: analysisStatusNotifierFactory, slCoreConstantsProvider: CreateConstantsProviderWithLanguages([])); - testSubject.RaiseFinding(raiseFindingParams); + testSubject.RaiseFinding(raiseFindingParams, publisher); raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); - analysisService.Received().PublishIssues(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); + publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); } @@ -124,21 +117,20 @@ public void RaiseFindings_NoKnownLanguages_PublishesEmpty() { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - - var analysisService = Substitute.For(); + var publisher = Substitute.For(); IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); - var testSubject = CreateTestSubject(analysisService: analysisService, + var testSubject = CreateTestSubject( raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, analysisStatusNotifierFactory: analysisStatusNotifierFactory, slCoreConstantsProvider: CreateConstantsProviderWithLanguages([SloopLanguage.JAVA])); - testSubject.RaiseFinding(raiseFindingParams); + testSubject.RaiseFinding(raiseFindingParams, publisher); raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); - analysisService.Received().PublishIssues(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); + publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); } @@ -147,15 +139,15 @@ public void RaiseFindings_HasNoFileUri_FinishesAnalysis() { var analysisId = Guid.NewGuid(); var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, null, analysisId); - var analysisService = Substitute.For(); - var testSubject = CreateTestSubject(analysisService: analysisService, analysisStatusNotifierFactory: analysisStatusNotifierFactory); + var publisher = Substitute.For(); + var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); var act = () => testSubject.RaiseFinding(new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, - analysisId)); + analysisId), publisher); act.Should().NotThrow(); - analysisService.ReceivedCalls().Should().BeEmpty(); + publisher.ReceivedCalls().Should().BeEmpty(); analysisStatusNotifier.ReceivedCalls().Should().BeEmpty(); } @@ -177,21 +169,20 @@ public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() var findingsByFileUri = new Dictionary> { { fileUri, raisedFindings } }; var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - - var analysisService = Substitute.For(); + var publisher = Substitute.For(); IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, filteredRaisedFindings, filteredIssues); var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); - var testSubject = CreateTestSubject(analysisService: analysisService, + var testSubject = CreateTestSubject( raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, analysisStatusNotifierFactory: analysisStatusNotifierFactory, slCoreConstantsProvider: CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.CS)); - testSubject.RaiseFinding(raiseFindingParams); + testSubject.RaiseFinding(raiseFindingParams, publisher); - analysisService.Received(1).PublishIssues(fileUri.LocalPath, analysisId, filteredIssues); + publisher.Received(1).Publish(fileUri.LocalPath, analysisId, filteredIssues); raiseFindingToAnalysisIssueConverter.Received(1).GetAnalysisIssues(findingsByFileUri.Single().Key, Arg.Is>( x => x.SequenceEqual(filteredRaisedFindings))); @@ -217,22 +208,22 @@ public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isInterm var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediate, analysisId); - var analysisService = Substitute.For(); + var publisher = Substitute.For(); var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri1, Arg.Any>()).Returns([analysisIssue1]); raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri2, Arg.Any>()).Returns([analysisIssue2]); var analysisStatusNotifierFactory = Substitute.For(); - var testSubject = CreateTestSubject(analysisService: analysisService, + var testSubject = CreateTestSubject( raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter, analysisStatusNotifierFactory: analysisStatusNotifierFactory); - testSubject.RaiseFinding(raiseFindingParams); + testSubject.RaiseFinding(raiseFindingParams, publisher); - analysisService.Received(1).PublishIssues(fileUri1.LocalPath, analysisId, + publisher.Received(1).Publish(fileUri1.LocalPath, analysisId, Arg.Is>(x => x.SequenceEqual(new List { analysisIssue1 }))); - analysisService.Received(1).PublishIssues(fileUri2.LocalPath, analysisId, + publisher.Received(1).Publish(fileUri2.LocalPath, analysisId, Arg.Is>(x => x.SequenceEqual(new List { analysisIssue2 }))); analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri1.LocalPath, analysisId); @@ -240,7 +231,6 @@ public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isInterm } private RaisedFindingProcessor CreateTestSubject( - IAnalysisService analysisService = null, IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = null, IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, ILogger logger = null, @@ -248,7 +238,6 @@ private RaisedFindingProcessor CreateTestSubject( => new( slCoreConstantsProvider ?? CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.JS, SloopLanguage.TS, SloopLanguage.CSS), - analysisService ?? Substitute.For(), raiseFindingToAnalysisIssueConverter ?? Substitute.For(), analysisStatusNotifierFactory ?? Substitute.For(), logger ?? new TestLogger()); diff --git a/src/SLCore.Listeners/Implementation/Analysis/AnalysisListener.cs b/src/SLCore.Listeners/Implementation/Analysis/AnalysisListener.cs index f431dd08cc..c487732c0c 100644 --- a/src/SLCore.Listeners/Implementation/Analysis/AnalysisListener.cs +++ b/src/SLCore.Listeners/Implementation/Analysis/AnalysisListener.cs @@ -31,25 +31,16 @@ namespace SonarLint.VisualStudio.SLCore.Listeners.Implementation.Analysis; [Export(typeof(ISLCoreListener))] [PartCreationPolicy(CreationPolicy.Shared)] -internal class AnalysisListener : IAnalysisListener +[method: ImportingConstructor] +internal class AnalysisListener( + IActiveConfigScopeTracker activeConfigScopeTracker, + IAnalysisRequester analysisRequester, + IRaisedFindingProcessor raisedFindingProcessor, + IIssuePublisher issuePublisher, + IHotspotPublisher hotspotPublisher, + ILogger logger) + : IAnalysisListener { - private readonly IActiveConfigScopeTracker activeConfigScopeTracker; - private readonly IAnalysisRequester analysisRequester; - private readonly IRaisedFindingProcessor raisedFindingProcessor; - private readonly ILogger logger; - - [ImportingConstructor] - public AnalysisListener( - IActiveConfigScopeTracker activeConfigScopeTracker, - IAnalysisRequester analysisRequester, - IRaisedFindingProcessor raisedFindingProcessor, - ILogger logger) - { - this.activeConfigScopeTracker = activeConfigScopeTracker; - this.analysisRequester = analysisRequester; - this.logger = logger; - this.raisedFindingProcessor = raisedFindingProcessor; - } public void DidChangeAnalysisReadiness(DidChangeAnalysisReadinessParams parameters) { @@ -65,13 +56,13 @@ public void DidChangeAnalysisReadiness(DidChangeAnalysisReadinessParams paramete } else { - logger.WriteLine(SLCoreStrings.AnalysisReadinessUpdate, SLCoreStrings.ConfigScopeConflict); + logger.WriteLine(SLCoreStrings.AnalysisReadinessUpdate, SLCoreStrings.ConfigScopeConflict); } } - public void RaiseIssues(RaiseFindingParams parameters) - => raisedFindingProcessor.RaiseFinding(parameters); + public void RaiseIssues(RaiseFindingParams parameters) + => raisedFindingProcessor.RaiseFinding(parameters, issuePublisher); - public void RaiseHotspots(RaiseHotspotParams parameters) - => raisedFindingProcessor.RaiseFinding(parameters); + public void RaiseHotspots(RaiseHotspotParams parameters) + => raisedFindingProcessor.RaiseFinding(parameters, hotspotPublisher); } diff --git a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs index 1be9ce63a4..53e93b6c2e 100644 --- a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs +++ b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs @@ -31,42 +31,29 @@ namespace SonarLint.VisualStudio.SLCore.Listeners.Implementation.Analysis; internal interface IRaisedFindingProcessor { - void RaiseFinding(RaiseFindingParams parameters) where T : RaisedFindingDto; + void RaiseFinding(RaiseFindingParams parameters, IFindingsPublisher findingsPublisher) where T : RaisedFindingDto; } [Export(typeof(IRaisedFindingProcessor))] [PartCreationPolicy(CreationPolicy.Shared)] -internal class RaisedFindingProcessor : IRaisedFindingProcessor +[method: ImportingConstructor] +internal class RaisedFindingProcessor( + ISLCoreConstantsProvider slCoreConstantsProvider, + IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter, + IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, + ILogger logger) + : IRaisedFindingProcessor { - private readonly IAnalysisService analysisService; - private readonly IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; - private readonly List analyzableLanguagesRuleKeyPrefixes; - private readonly ILogger logger; - private readonly IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter; + private readonly List analyzableLanguagesRuleKeyPrefixes = CalculateAnalyzableRulePrefixes(slCoreConstantsProvider); - [ImportingConstructor] - public RaisedFindingProcessor(ISLCoreConstantsProvider slCoreConstantsProvider, - IAnalysisService analysisService, - IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, - ILogger logger) - { - this.analysisService = analysisService; - this.raiseFindingToAnalysisIssueConverter = raiseFindingToAnalysisIssueConverter; - this.analysisStatusNotifierFactory = analysisStatusNotifierFactory; - this.logger = logger; - - analyzableLanguagesRuleKeyPrefixes = CalculateAnalyzableRulePrefixes(slCoreConstantsProvider); - } - - public void RaiseFinding(RaiseFindingParams parameters) where T : RaisedFindingDto + public void RaiseFinding(RaiseFindingParams parameters, IFindingsPublisher findingsPublisher) where T : RaisedFindingDto { if (!IsValid(parameters)) { return; } - PublishFindings(parameters); + PublishFindings(parameters, findingsPublisher); } private bool IsValid(RaiseFindingParams parameters) where T : RaisedFindingDto @@ -87,7 +74,7 @@ private bool IsValid(RaiseFindingParams parameters) where T : RaisedFindin return true; } - private void PublishFindings(RaiseFindingParams parameters) where T : RaisedFindingDto + private void PublishFindings(RaiseFindingParams parameters, IFindingsPublisher findingsPublisher) where T : RaisedFindingDto { foreach (var fileAndIssues in parameters.issuesByFileUri) { @@ -95,24 +82,20 @@ private void PublishFindings(RaiseFindingParams parameters) where T : Rais var localPath = fileUri.LocalPath; var analysisStatusNotifier = analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), localPath, parameters.analysisId); var supportedRaisedIssues = GetSupportedLanguageFindings(fileAndIssues.Value ?? []); - analysisService.PublishIssues(localPath, + findingsPublisher.Publish(localPath, parameters.analysisId!.Value, raiseFindingToAnalysisIssueConverter.GetAnalysisIssues(fileUri, supportedRaisedIssues)); analysisStatusNotifier.AnalysisFinished(supportedRaisedIssues.Length, TimeSpan.Zero); } } - private T[] GetSupportedLanguageFindings(IEnumerable findings) where T : RaisedFindingDto - { - return findings.Where(i => analyzableLanguagesRuleKeyPrefixes.Exists(languageRepo => i.ruleKey.StartsWith(languageRepo))).ToArray(); - } + private T[] GetSupportedLanguageFindings(IEnumerable findings) where T : RaisedFindingDto => + findings.Where(i => analyzableLanguagesRuleKeyPrefixes.Exists(languageRepo => i.ruleKey.StartsWith(languageRepo))).ToArray(); - private static List CalculateAnalyzableRulePrefixes(ISLCoreConstantsProvider slCoreConstantsProvider) - { - return slCoreConstantsProvider.SLCoreAnalyzableLanguages? + private static List CalculateAnalyzableRulePrefixes(ISLCoreConstantsProvider slCoreConstantsProvider) => + slCoreConstantsProvider.SLCoreAnalyzableLanguages? .Select(x => x.ConvertToCoreLanguage()) .Select(Language.GetSonarRepoKeyFromLanguage) .Where(r => r is not null) .ToList() ?? []; - } } From 6867e23f579dc32c13a65ab23957dffd104be637 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:37:39 +0100 Subject: [PATCH 11/21] SLVS-1276 Clean up exclusions and quality profiles configuration (#5888) [SLVS-1276](https://sonarsource.atlassian.net/browse/SLVS-1276) [SLVS-1276]: https://sonarsource.atlassian.net/browse/SLVS-1276?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../Binding/BindingProcessFactoryTests.cs | 5 +- .../Binding/BindingProcessImplTests.cs | 84 +---- .../CompositeBindingConfigProviderTests.cs | 4 +- .../NonRoslynBindingConfigFileTests.cs | 113 ------- .../NonRoslynBindingConfigProviderTests.cs | 286 ------------------ ...onRoslynDummyBindingConfigProviderTests.cs | 72 +++++ .../Binding/BindingProcessImpl.cs | 18 -- .../Binding/CompositeBindingConfigProvider.cs | 11 +- src/ConnectedMode/Binding/IBindingProcess.cs | 2 - .../Binding/IBindingProcessFactory.cs | 4 - .../Binding/IUnintrusiveBindingController.cs | 3 +- .../Binding/NonRoslynBindingConfigFile.cs | 68 ----- .../Binding/NonRoslynBindingConfigProvider.cs | 146 --------- .../NonRoslynDummyBindingConfigProvider.cs | 60 ++++ .../QualityProfileDownloader.cs | 8 +- .../Analysis/AnalysisServiceTests.cs | 17 +- .../Analysis/AnalyzerControllerTests.cs | 116 +------ src/Core/Analysis/AnalysisService.cs | 7 +- src/Core/Analysis/AnalyzerController.cs | 44 +-- src/Core/Analysis/IAnalysisService.cs | 5 - src/Core/Analysis/IAnalyzableFileIndicator.cs | 30 -- src/Core/Analysis/IAnalyzer.cs | 3 - .../Analysis/IExclusionSettingsStorage.cs | 30 -- src/Core/Language.cs | 2 + .../AnalyzableFileIndicatorTests.cs | 233 -------------- .../ExclusionSettingsStorageTests.cs | 233 -------------- .../Analysis/IssueHandlerTests.cs | 27 ++ .../SonarLintTagger/TaggerProviderTests.cs | 32 +- .../VsAwareAnalysisServiceTests.cs | 15 - .../SonarLintTagger/TaggerProvider.cs | 17 +- .../SonarLintTagger/VsAwareAnalysisService.cs | 5 - .../Exclusions/AnalyzableFileIndicator.cs | 106 ------- .../Exclusions/ExclusionSettingsStorage.cs | 113 ------- .../Analysis/SLCoreAnalyzerTests.cs | 41 +-- src/SLCore/Analysis/SLCoreAnalyzer.cs | 3 - 35 files changed, 223 insertions(+), 1740 deletions(-) delete mode 100644 src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigFileTests.cs delete mode 100644 src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigProviderTests.cs create mode 100644 src/ConnectedMode.UnitTests/Binding/NonRoslynDummyBindingConfigProviderTests.cs delete mode 100644 src/ConnectedMode/Binding/NonRoslynBindingConfigFile.cs delete mode 100644 src/ConnectedMode/Binding/NonRoslynBindingConfigProvider.cs create mode 100644 src/ConnectedMode/Binding/NonRoslynDummyBindingConfigProvider.cs delete mode 100644 src/Core/Analysis/IAnalyzableFileIndicator.cs delete mode 100644 src/Core/Analysis/IExclusionSettingsStorage.cs delete mode 100644 src/Integration.UnitTests/Exclusions/AnalyzableFileIndicatorTests.cs delete mode 100644 src/Integration.UnitTests/Exclusions/ExclusionSettingsStorageTests.cs delete mode 100644 src/Integration/Exclusions/AnalyzableFileIndicator.cs delete mode 100644 src/Integration/Exclusions/ExclusionSettingsStorage.cs diff --git a/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs b/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs index a3c8894821..80bcdf2bcb 100644 --- a/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs @@ -38,7 +38,6 @@ public void MefCtor_CheckIsExported() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -57,16 +56,14 @@ public void Create_ReturnsProcessImpl() private static BindingProcessFactory CreateTestSubject( ISonarQubeService service = null, - IExclusionSettingsStorage exclusionSettingsStorage = null, IQualityProfileDownloader qualityProfileDownloader = null, ILogger logger = null) { service ??= Mock.Of(); - exclusionSettingsStorage ??= Mock.Of(); qualityProfileDownloader ??= Mock.Of(); logger ??= new TestLogger(logToConsole: true); - return new BindingProcessFactory(service, exclusionSettingsStorage, qualityProfileDownloader, logger); + return new BindingProcessFactory(service, qualityProfileDownloader, logger); } } diff --git a/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs b/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs index 50af0a34b6..c579792fd3 100644 --- a/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs @@ -44,96 +44,27 @@ public class BindingProcessImplTests public void Ctor_ArgChecks() { var bindingArgs = CreateBindCommandArgs(); - var exclusionSettingsStorage = Mock.Of(); var qpDownloader = Mock.Of(); var sonarQubeService = Mock.Of(); var logger = Mock.Of(); // 1. Null binding args - Action act = () => new BindingProcessImpl(null, exclusionSettingsStorage, sonarQubeService, qpDownloader, logger); + Action act = () => new BindingProcessImpl(null, sonarQubeService, qpDownloader, logger); act.Should().ThrowExactly().And.ParamName.Should().Be("bindingArgs"); - // 2. Null exclusion settings storage - act = () => new BindingProcessImpl(bindingArgs, null, sonarQubeService, qpDownloader, logger); - act.Should().ThrowExactly().And.ParamName.Should().Be("exclusionSettingsStorage"); - // 3. Null SonarQube service - act = () => new BindingProcessImpl(bindingArgs, exclusionSettingsStorage, null, qpDownloader, logger); + act = () => new BindingProcessImpl(bindingArgs, null, qpDownloader, logger); act.Should().ThrowExactly().And.ParamName.Should().Be("sonarQubeService"); // 4. Null QP downloader - act = () => new BindingProcessImpl(bindingArgs, exclusionSettingsStorage, sonarQubeService, null, logger); + act = () => new BindingProcessImpl(bindingArgs, sonarQubeService, null, logger); act.Should().ThrowExactly().And.ParamName.Should().Be("qualityProfileDownloader"); // 5. Null logger - act = () => new BindingProcessImpl(bindingArgs, exclusionSettingsStorage, sonarQubeService, qpDownloader, null); + act = () => new BindingProcessImpl(bindingArgs, sonarQubeService, qpDownloader, null); act.Should().ThrowExactly().And.ParamName.Should().Be("logger"); } - [TestMethod] - public async Task SaveServerExclusionsAsync_ReturnsTrue() - { - var bindingArgs = CreateBindCommandArgs(projectKey: "projectKey"); - var logger = new TestLogger(logToConsole: true); - - ServerExclusions settings = CreateSettings(); - - var sonarQubeService = new Mock(); - sonarQubeService.Setup(s => s.GetServerExclusions("projectKey", It.IsAny())).Returns(Task.FromResult(settings)); - - var exclusionSettingsStorage = new Mock(); - - var testSubject = CreateTestSubject(bindingArgs: bindingArgs, - sonarQubeService: sonarQubeService.Object, - exclusionSettingsStorage: exclusionSettingsStorage.Object, - logger: logger); - - await testSubject.SaveServerExclusionsAsync(CancellationToken.None); - - exclusionSettingsStorage.Verify(fs => fs.SaveSettings(settings), Times.Once); - logger.AssertOutputStrings(0); - } - - [TestMethod] - public async Task SaveServerExclusionsAsync_HasError_ReturnsFalse() - { - var logger = new TestLogger(logToConsole: true); - var bindingArgs = CreateBindCommandArgs(projectKey: "projectKey"); - - var sonarQubeService = new Mock(); - sonarQubeService.Setup(s => s.GetServerExclusions("projectKey", It.IsAny())).Throws(new Exception("Expected Error")); - - var testSubject = CreateTestSubject( - bindingArgs: bindingArgs, - sonarQubeService: sonarQubeService.Object, - logger: logger); - - var result = await testSubject.SaveServerExclusionsAsync(CancellationToken.None); - - result.Should().BeFalse(); - logger.AssertOutputStrings(1); - logger.AssertPartialOutputStrings("Expected Error"); - } - - [TestMethod] - public void SaveServerExclusionsAsync_HasCriticalError_Throws() - { - var logger = new TestLogger(logToConsole: true); - var bindingArgs = CreateBindCommandArgs(projectKey: "projectKey"); - - var sonarQubeService = new Mock(); - sonarQubeService.Setup(s => s.GetServerExclusions("projectKey", It.IsAny())).Throws(new StackOverflowException("Critical Error")); - - var testSubject = CreateTestSubject(bindingArgs: bindingArgs, - sonarQubeService: sonarQubeService.Object, - logger: logger); - - Func> act = async () => await testSubject.SaveServerExclusionsAsync(CancellationToken.None); - - act.Should().ThrowExactly().WithMessage("Critical Error"); - logger.AssertOutputStrings(0); - } - private static ServerExclusions CreateSettings() { return new ServerExclusions @@ -159,7 +90,7 @@ public async Task DownloadQualityProfile_CreatesBoundProjectAndCallsQPDownloader var result = await testSubject.DownloadQualityProfileAsync(progress, CancellationToken.None); result.Should().BeTrue(); - + qpDownloader.Verify(x => x.UpdateAsync(It.IsAny(), progress, It.IsAny()), Times.Once); @@ -171,7 +102,7 @@ public async Task DownloadQualityProfile_CreatesBoundProjectAndCallsQPDownloader actualProject.ServerProjectKey.Should().Be("the project key"); } - [TestMethod] + [TestMethod] public async Task DownloadQualityProfile_HandlesInvalidOperationException() { var qpDownloader = new Mock(); @@ -201,19 +132,16 @@ public async Task DownloadQualityProfile_HandlesInvalidOperationException() #region Helpers private BindingProcessImpl CreateTestSubject(BindCommandArgs bindingArgs = null, - IExclusionSettingsStorage exclusionSettingsStorage = null, ISonarQubeService sonarQubeService = null, IQualityProfileDownloader qpDownloader = null, ILogger logger = null) { bindingArgs = bindingArgs ?? CreateBindCommandArgs(); sonarQubeService ??= Mock.Of(); - exclusionSettingsStorage ??= Mock.Of(); qpDownloader ??= Mock.Of(); logger ??= new TestLogger(logToConsole: true); return new BindingProcessImpl(bindingArgs, - exclusionSettingsStorage, sonarQubeService, qpDownloader, logger); diff --git a/src/ConnectedMode.UnitTests/Binding/CompositeBindingConfigProviderTests.cs b/src/ConnectedMode.UnitTests/Binding/CompositeBindingConfigProviderTests.cs index 3548b74c0f..fad6887f5f 100644 --- a/src/ConnectedMode.UnitTests/Binding/CompositeBindingConfigProviderTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/CompositeBindingConfigProviderTests.cs @@ -54,9 +54,7 @@ public void Ctor_PublicConstructor_ContainsExpectedBindingConfigProviders() // Assert testSubject.Providers.Count().Should().Be(2); - testSubject.Providers.Select(x => x.GetType()).Should().BeEquivalentTo( - typeof(NonRoslynBindingConfigProvider), - typeof(CSharpVBBindingConfigProvider)); + testSubject.Providers.Select(x => x.GetType()).Should().BeEquivalentTo(typeof(NonRoslynDummyBindingConfigProvider), typeof(CSharpVBBindingConfigProvider)); } [TestMethod] diff --git a/src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigFileTests.cs b/src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigFileTests.cs deleted file mode 100644 index 44c718ec5f..0000000000 --- a/src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigFileTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO.Abstractions.TestingHelpers; -using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SSonarLint.VisualStudio.ConnectedMode.Binding.UnitTests -{ - [TestClass] - public class NonRoslynBindingConfigFileTests - { - [TestMethod] - public void Ctor_InvalidArgs() - { - Action act = () => new NonRoslynBindingConfigFile(null, "c:\\test"); - act.Should().ThrowExactly().And.ParamName.Should().Be("rulesSettings"); - - act = () => new NonRoslynBindingConfigFile(new RulesSettings(), null); - act.Should().ThrowExactly().And.ParamName.Should().Be("filePath"); - - act = () => new NonRoslynBindingConfigFile(new RulesSettings(), ""); - act.Should().ThrowExactly().And.ParamName.Should().Be("filePath"); - - act = () => new NonRoslynBindingConfigFile(new RulesSettings(), "c:\\test", null); - act.Should().ThrowExactly().And.ParamName.Should().Be("fileSystem"); - } - - [TestMethod] - public void Ctor_ValidArgs() - { - var settings = new RulesSettings(); - var testSubject = new NonRoslynBindingConfigFile(settings, "c:\\test"); - testSubject.RuleSettings.Should().BeEquivalentTo(settings); - testSubject.FilePath.Should().BeEquivalentTo("c:\\test"); - } - - [TestMethod] - public void Save_DirectoryCreatedAndFileSaved() - { - // Arrange - var settings = new RulesSettings - { - Rules = new Dictionary - { - { "key", new RuleConfig - { - Level = RuleLevel.On, - Severity = IssueSeverity.Minor, - Parameters = new Dictionary - { - { "p1", "p2" } - } - } - } - } - }; - - string filePath = "c:\\full\\path\\file.txt"; - - var fileSystem = new MockFileSystem(); - - var testSubject = new NonRoslynBindingConfigFile(settings, filePath, fileSystem); - - // Act - testSubject.Save(); - - // Assert - // Assert - fileSystem.AllDirectories.Should().BeEquivalentTo(new[] - { - "C:\\", // note: the MockFileSystem capitalises the drive - "c:\\full", - "c:\\full\\path", - }); - - fileSystem.AllFiles.Should().BeEquivalentTo(filePath); - - var savedText = fileSystem.File.ReadAllText(filePath); - savedText.Should().Be(@"{ - ""sonarlint.rules"": { - ""key"": { - ""level"": ""On"", - ""parameters"": { - ""p1"": ""p2"" - }, - ""severity"": ""Minor"" - } - } -}"); - } - } -} diff --git a/src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigProviderTests.cs b/src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigProviderTests.cs deleted file mode 100644 index a373b04a37..0000000000 --- a/src/ConnectedMode.UnitTests/Binding/NonRoslynBindingConfigProviderTests.cs +++ /dev/null @@ -1,286 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.TestInfrastructure; -using SonarQube.Client; -using SonarQube.Client.Models; - -namespace SonarLint.VisualStudio.ConnectedMode.Binding.UnitTests -{ - [TestClass] - public class NonRoslynBindingConfigProviderTests - { - private static readonly Language[] SupportedLanguages = { Language.C, Language.Cpp }; - - [TestMethod] - public void Ctor_NullService_ArgumentNullException() - { - Action act = () => new NonRoslynBindingConfigProvider(null, Mock.Of()); - - act.Should().Throw().And.ParamName.Should().Be("sonarQubeService"); - } - - [TestMethod] - public void Ctor_NullLogger_ArgumentNullException() - { - Action act = () => new NonRoslynBindingConfigProvider(Mock.Of(), null); - - act.Should().Throw().And.ParamName.Should().Be("logger"); - } - - [TestMethod] - [DataRow(SonarQubeIssueSeverity.Blocker, IssueSeverity.Blocker)] - [DataRow(SonarQubeIssueSeverity.Critical, IssueSeverity.Critical)] - [DataRow(SonarQubeIssueSeverity.Info, IssueSeverity.Info)] - [DataRow(SonarQubeIssueSeverity.Major, IssueSeverity.Major)] - [DataRow(SonarQubeIssueSeverity.Minor, IssueSeverity.Minor)] - [DataRow(SonarQubeIssueSeverity.Unknown, null)] - public void SeverityEnumConversion_NotUnknown(SonarQubeIssueSeverity sqSeverity, IssueSeverity? expected) - { - NonRoslynBindingConfigProvider.Convert(sqSeverity).Should().Be(expected); - } - - [TestMethod] - public void ConvertRulesToSettings() - { - // Arrange - var qpRules = new List - { - CreateRule("key1", "repo1", false, SonarQubeIssueSeverity.Blocker), - CreateRule("key2", "repo1", true, SonarQubeIssueSeverity.Critical), - CreateRule("key3", "repo1", false, SonarQubeIssueSeverity.Unknown, - new Dictionary - { - { "paramKey1", "paramValue1" }, - { "paramKey2", "paramValue2" }, - { "paramKey3", "" } - } - ), - }; - - // Act - var settings = NonRoslynBindingConfigProvider.CreateRulesSettingsFromQPRules(qpRules); - - // Assert - settings.Rules.Count.Should().Be(3); - settings.Rules.Keys.Should().BeEquivalentTo("repo1:key1", "repo1:key2", "repo1:key3"); - - settings.Rules["repo1:key1"].Level.Should().Be(RuleLevel.Off); - settings.Rules["repo1:key2"].Level.Should().Be(RuleLevel.On); - settings.Rules["repo1:key3"].Level.Should().Be(RuleLevel.Off); - - settings.Rules["repo1:key1"].Severity.Should().Be(IssueSeverity.Blocker); - settings.Rules["repo1:key2"].Severity.Should().Be(IssueSeverity.Critical); - settings.Rules["repo1:key3"].Severity.Should().BeNull(); - - settings.Rules["repo1:key1"].Parameters.Should().BeNull(); - settings.Rules["repo1:key2"].Parameters.Should().BeNull(); - - var rule3Params = settings.Rules["repo1:key3"].Parameters; - rule3Params.Should().NotBeNull(); - rule3Params.Keys.Should().BeEquivalentTo("paramKey1", "paramKey2", "paramKey3"); - rule3Params["paramKey1"].Should().Be("paramValue1"); - rule3Params["paramKey2"].Should().Be("paramValue2"); - rule3Params["paramKey3"].Should().Be(""); - } - - [TestMethod] - public async Task GetRules_Success() - { - // Arrange - var testLogger = new TestLogger(); - var rules = new List - { - CreateRule("key1", "repo1", true, SonarQubeIssueSeverity.Major), - CreateRule("key2", "repo2", false,SonarQubeIssueSeverity.Info, - new Dictionary - { - { "p1", "v1" }, - { "p2", "v2" } - }) - }; - - var serviceMock = new Mock(); - serviceMock.Setup(x => x.GetRulesAsync(true, It.IsAny(), It.IsAny())) - .ReturnsAsync(() => rules); - - var bindingConfiguration = new BindingConfiguration(new BoundServerProject("any", "any", new ServerConnection.SonarCloud("any")), SonarLintMode.Connected, "c:\\"); - - var testSubject = CreateTestSubject(serviceMock, testLogger); - - // Act - var result = await testSubject.GetConfigurationAsync(CreateQp(), Language.Cpp, bindingConfiguration, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.Should().BeOfType(); - - var cfamilyConfigFile = (NonRoslynBindingConfigFile)result; - cfamilyConfigFile.RuleSettings.Should().NotBeNull(); - - var slvsRules = cfamilyConfigFile.RuleSettings.Rules; - slvsRules.Should().NotBeNull(); - slvsRules.Keys.Should().BeEquivalentTo("repo1:key1", "repo2:key2"); - slvsRules["repo1:key1"].Level.Should().Be(RuleLevel.On); - slvsRules["repo2:key2"].Level.Should().Be(RuleLevel.Off); - - slvsRules["repo1:key1"].Severity.Should().Be(IssueSeverity.Major); - slvsRules["repo2:key2"].Severity.Should().Be(IssueSeverity.Info); - - slvsRules["repo1:key1"].Parameters.Should().BeNull(); - - var rule2Params = slvsRules["repo2:key2"].Parameters; - rule2Params.Should().NotBeNull(); - rule2Params.Keys.Should().BeEquivalentTo("p1", "p2"); - rule2Params["p1"].Should().Be("v1"); - rule2Params["p2"].Should().Be("v2"); - - testLogger.AssertNoOutputMessages(); - } - - [DebuggerStepThrough] - private static SonarQubeQualityProfile CreateQp() => - new SonarQubeQualityProfile("key1", "", "", false, DateTime.UtcNow); - - [TestMethod] - public async Task GetRules_NoData_EmptyResultReturned() - { - // Arrange - var testLogger = new TestLogger(); - var serviceMock = new Mock(); - serviceMock.Setup(x => x.GetRulesAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync(() => new List()); - - var bindingConfiguration = new BindingConfiguration(new BoundServerProject("any", "any", new ServerConnection.SonarCloud("any")), SonarLintMode.Connected, "c:\\"); - - var testSubject = CreateTestSubject(serviceMock, testLogger); - - // Act - var result = await testSubject.GetConfigurationAsync(CreateQp(), Language.Cpp, bindingConfiguration, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.Should().BeOfType(); - - var cfamilyConfigFile = (NonRoslynBindingConfigFile)result; - cfamilyConfigFile.RuleSettings.Should().NotBeNull(); - cfamilyConfigFile.RuleSettings.Rules.Should().NotBeNull(); - cfamilyConfigFile.RuleSettings.Rules.Count.Should().Be(0); - - testLogger.AssertNoOutputMessages(); - } - - [TestMethod] - public async Task GetRules_NonCriticalException_IsHandledAndNullResultReturned() - { - // Arrange - var testLogger = new TestLogger(); - - var serviceMock = new Mock(); - serviceMock.Setup(x => x.GetRulesAsync(It.IsAny(), It.IsAny(), CancellationToken.None)) - .ThrowsAsync(new InvalidOperationException("invalid op")); - - var testSubject = CreateTestSubject(serviceMock, testLogger); - - // Act - var result = await testSubject.GetConfigurationAsync(CreateQp(), Language.Cpp, BindingConfiguration.Standalone, CancellationToken.None); - - // Assert - result.Should().BeNull(); - testLogger.AssertPartialOutputStringExists("invalid op"); - } - - [TestMethod] - public void GetRules_UnsupportedLanguage_Throws() - { - // Arrange - var testLogger = new TestLogger(); - - CancellationTokenSource cts = new CancellationTokenSource(); - var serviceMock = new Mock(); - - var testSubject = CreateTestSubject(serviceMock, testLogger); - - // Act - Action act = () => testSubject.GetConfigurationAsync(CreateQp(), Language.VBNET, BindingConfiguration.Standalone, cts.Token).Wait(); - - // Assert - act.Should().ThrowExactly().WithInnerException(); - } - - [TestMethod] - public void IsSupported() - { - // Arrange - var testLogger = new TestLogger(); - var serviceMock = new Mock(); - var testSubject = CreateTestSubject(serviceMock, testLogger); - - // 1. Supported languages - testSubject.IsLanguageSupported(Language.C).Should().BeTrue(); - testSubject.IsLanguageSupported(Language.Cpp).Should().BeTrue(); - - testSubject.IsLanguageSupported(new Language("cpp", "FooXXX", "foo", new SonarQubeLanguage("serverId", "serverName"))); - - // 2. Not supported - testSubject.IsLanguageSupported(Language.CSharp).Should().BeFalse(); - testSubject.IsLanguageSupported(Language.VBNET).Should().BeFalse(); - } - - private static SonarQubeRule CreateRule(string ruleKey, - string repoKey, - bool isActive, - SonarQubeIssueSeverity severity, - IDictionary parameters = null, - string description = null, - IReadOnlyList descriptionSections = null, - IReadOnlyList educationPrinciples = null, - string name = null, - IReadOnlyList tags = null, - string htmlNote = null) => - new SonarQubeRule(ruleKey, - repoKey, - isActive, - severity, - null, - null, - parameters, - SonarQubeIssueType.Unknown, - description, - descriptionSections, - educationPrinciples, - name, - tags, - htmlNote); - - private static NonRoslynBindingConfigProvider CreateTestSubject(Mock serviceMock, TestLogger testLogger) - { - return new NonRoslynBindingConfigProvider(serviceMock.Object, testLogger, SupportedLanguages); - } - } -} diff --git a/src/ConnectedMode.UnitTests/Binding/NonRoslynDummyBindingConfigProviderTests.cs b/src/ConnectedMode.UnitTests/Binding/NonRoslynDummyBindingConfigProviderTests.cs new file mode 100644 index 0000000000..31fde43218 --- /dev/null +++ b/src/ConnectedMode.UnitTests/Binding/NonRoslynDummyBindingConfigProviderTests.cs @@ -0,0 +1,72 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.ConnectedMode.Binding; +using SonarLint.VisualStudio.Core; + +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Binding; + +[TestClass] +public class NonRoslynDummyBindingConfigProviderTests +{ + private static IEnumerable SupportedLanguages { get; } = Language.KnownLanguages.Where(l => l != Language.CSharp && l != Language.VBNET); + private static IEnumerable RoslynLanguages { get; } = [Language.CSharp, Language.VBNET]; + + public static IEnumerable GetSupportedLanguages() => SupportedLanguages.Select(l => new object[] { l }); + public static IEnumerable GetRoslynLanguages() => RoslynLanguages.Select(l => new object[] { l }); + + public static IEnumerable GetLanguagesWithSupport() + { + foreach (var nonRoslynLanguage in SupportedLanguages) + { + yield return [nonRoslynLanguage, true]; + } + + foreach (var roslynLanguage in RoslynLanguages) + { + yield return [roslynLanguage, false]; + } + } + + [DynamicData(nameof(GetLanguagesWithSupport), DynamicDataSourceType.Method)] + [DataTestMethod] + public void IsLanguageSupported_ReturnsTrueForNonRoslynLanguages(Language language, bool isSupported) => + new NonRoslynDummyBindingConfigProvider().IsLanguageSupported(language).Should().Be(isSupported); + + [DynamicData(nameof(GetRoslynLanguages), DynamicDataSourceType.Method)] + [DataTestMethod] + public async Task GetConfigurationAsync_ThrowsForUnsupported(Language language) + { + var act = () => new NonRoslynDummyBindingConfigProvider().GetConfigurationAsync(default, language, default, default); + + await act.Should().ThrowAsync(); + } + + [DynamicData(nameof(GetSupportedLanguages), DynamicDataSourceType.Method)] + [DataTestMethod] + public async Task GetConfigurationAsync_ReturnsDummyConfigForSupported(Language language) + { + var config = await new NonRoslynDummyBindingConfigProvider().GetConfigurationAsync(default, language, default, default); + config.Should().NotBeNull().And.BeOfType(); + + var configSave = () => config.Save(); + configSave.Should().NotThrow(); + } +} diff --git a/src/ConnectedMode/Binding/BindingProcessImpl.cs b/src/ConnectedMode/Binding/BindingProcessImpl.cs index d5b29c4e55..08e6d4c2ee 100644 --- a/src/ConnectedMode/Binding/BindingProcessImpl.cs +++ b/src/ConnectedMode/Binding/BindingProcessImpl.cs @@ -37,20 +37,17 @@ namespace SonarLint.VisualStudio.ConnectedMode.Binding internal class BindingProcessImpl : IBindingProcess { private readonly BindCommandArgs bindingArgs; - private readonly IExclusionSettingsStorage exclusionSettingsStorage; private readonly ISonarQubeService sonarQubeService; private readonly IQualityProfileDownloader qualityProfileDownloader; private readonly ILogger logger; public BindingProcessImpl( BindCommandArgs bindingArgs, - IExclusionSettingsStorage exclusionSettingsStorage, ISonarQubeService sonarQubeService, IQualityProfileDownloader qualityProfileDownloader, ILogger logger) { this.bindingArgs = bindingArgs ?? throw new ArgumentNullException(nameof(bindingArgs)); - this.exclusionSettingsStorage = exclusionSettingsStorage ?? throw new ArgumentNullException(nameof(exclusionSettingsStorage)); this.sonarQubeService = sonarQubeService ?? throw new ArgumentNullException(nameof(sonarQubeService)); this.qualityProfileDownloader = qualityProfileDownloader ?? throw new ArgumentNullException(nameof(qualityProfileDownloader)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -74,21 +71,6 @@ public async Task DownloadQualityProfileAsync(IProgress SaveServerExclusionsAsync(CancellationToken cancellationToken) - { - try - { - var exclusions = await sonarQubeService.GetServerExclusions(bindingArgs.ProjectToBind.ServerProjectKey, cancellationToken); - exclusionSettingsStorage.SaveSettings(exclusions); - } - catch(Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - logger.WriteLine(string.Format(BindingStrings.SaveExclusionsFailed, ex.Message)); - return false; - } - return true; - } - #endregion } } diff --git a/src/ConnectedMode/Binding/CompositeBindingConfigProvider.cs b/src/ConnectedMode/Binding/CompositeBindingConfigProvider.cs index 654f74b873..eb9ea7a9a0 100644 --- a/src/ConnectedMode/Binding/CompositeBindingConfigProvider.cs +++ b/src/ConnectedMode/Binding/CompositeBindingConfigProvider.cs @@ -44,9 +44,7 @@ internal class CompositeBindingConfigProvider : IBindingConfigProvider [ImportingConstructor] public CompositeBindingConfigProvider(ISonarQubeService sonarQubeService, ILogger logger) - : this( - new CSharpVBBindingConfigProvider(sonarQubeService, logger), - new NonRoslynBindingConfigProvider(sonarQubeService, logger)) + : this(new NonRoslynDummyBindingConfigProvider(), new CSharpVBBindingConfigProvider(sonarQubeService, logger)) { } internal /* for testing */ CompositeBindingConfigProvider(params IBindingConfigProvider[] providers) @@ -55,7 +53,7 @@ public CompositeBindingConfigProvider(ISonarQubeService sonarQubeService, ILogge this.providers = new HashSet(providers); } - internal /* for testing */ IEnumerable Providers { get { return this.providers; } } + internal /* for testing */ IEnumerable Providers => providers; #region IBindingConfigProvider methods @@ -72,10 +70,7 @@ public Task GetConfigurationAsync(SonarQubeQualityProfile qualit return provider.GetConfigurationAsync(qualityProfile, language, bindingConfiguration, cancellationToken); } - public bool IsLanguageSupported(Language language) - { - return Providers.Any(p => p.IsLanguageSupported(language)); - } + public bool IsLanguageSupported(Language language) => Providers.Any(p => p.IsLanguageSupported(language)); #endregion IBindingConfigProvider methods } diff --git a/src/ConnectedMode/Binding/IBindingProcess.cs b/src/ConnectedMode/Binding/IBindingProcess.cs index 77cd548fde..5b75167ac6 100644 --- a/src/ConnectedMode/Binding/IBindingProcess.cs +++ b/src/ConnectedMode/Binding/IBindingProcess.cs @@ -35,7 +35,5 @@ namespace SonarLint.VisualStudio.ConnectedMode.Binding internal interface IBindingProcess { Task DownloadQualityProfileAsync(IProgress progress, CancellationToken cancellationToken); - - Task SaveServerExclusionsAsync(CancellationToken cancellationToken); } } diff --git a/src/ConnectedMode/Binding/IBindingProcessFactory.cs b/src/ConnectedMode/Binding/IBindingProcessFactory.cs index 500078cd88..c68bb69b8a 100644 --- a/src/ConnectedMode/Binding/IBindingProcessFactory.cs +++ b/src/ConnectedMode/Binding/IBindingProcessFactory.cs @@ -39,19 +39,16 @@ internal interface IBindingProcessFactory internal class BindingProcessFactory : IBindingProcessFactory { private readonly ISonarQubeService sonarQubeService; - private readonly IExclusionSettingsStorage exclusionSettingsStorage; private readonly IQualityProfileDownloader qualityProfileDownloader; private readonly ILogger logger; [ImportingConstructor] public BindingProcessFactory( ISonarQubeService sonarQubeService, - IExclusionSettingsStorage exclusionSettingsStorage, IQualityProfileDownloader qualityProfileDownloader, ILogger logger) { this.sonarQubeService = sonarQubeService; - this.exclusionSettingsStorage = exclusionSettingsStorage; this.qualityProfileDownloader = qualityProfileDownloader; this.logger = logger; } @@ -59,7 +56,6 @@ public BindingProcessFactory( public IBindingProcess Create(BindCommandArgs bindingArgs) { return new BindingProcessImpl(bindingArgs, - exclusionSettingsStorage, sonarQubeService, qualityProfileDownloader, logger); diff --git a/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs b/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs index 96d2432899..d7d139d094 100644 --- a/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs +++ b/src/ConnectedMode/Binding/IUnintrusiveBindingController.cs @@ -30,7 +30,7 @@ public interface IBindingController Task BindAsync(BoundServerProject project, CancellationToken cancellationToken); bool Unbind(string localBindingKey); } - + internal interface IUnintrusiveBindingController { Task BindAsync(BoundServerProject project, IProgress progress, CancellationToken token); @@ -67,7 +67,6 @@ public async Task BindAsync(BoundServerProject project, IProgress SupportedLanguages = new [] - { - Language.C, - Language.Cpp, - Language.Js, - Language.Ts, - Language.Secrets, - Language.Css - }; - - private readonly IEnumerable supportedLanguages; - private readonly ISonarQubeService sonarQubeService; - private readonly ILogger logger; - - public NonRoslynBindingConfigProvider(ISonarQubeService sonarQubeService, ILogger logger) - : this(sonarQubeService, logger, SupportedLanguages) - { } - - public NonRoslynBindingConfigProvider(ISonarQubeService sonarQubeService, ILogger logger, IEnumerable supportedLanguages) - { - this.supportedLanguages = supportedLanguages ?? throw new ArgumentNullException(nameof(supportedLanguages)); - this.sonarQubeService = sonarQubeService ?? throw new ArgumentNullException(nameof(sonarQubeService)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - #region IBindingConfigProvider implementation - - public bool IsLanguageSupported(Language language) => supportedLanguages.Contains(language); - - public async Task GetConfigurationAsync(SonarQubeQualityProfile qualityProfile, Language language, - BindingConfiguration bindingConfiguration, CancellationToken cancellationToken) - { - if (!IsLanguageSupported(language)) - { - throw new ArgumentOutOfRangeException(nameof(language)); - } - - var result = await WebServiceHelper.SafeServiceCallAsync( - () => sonarQubeService.GetAllRulesAsync(qualityProfile.Key, cancellationToken), logger); - - if (result == null) - { - return null; - } - - cancellationToken.ThrowIfCancellationRequested(); - - var settings = CreateRulesSettingsFromQPRules(result); - var settingsFilePath = bindingConfiguration.BuildPathUnderConfigDirectory(language.FileSuffixAndExtension); - - var configFile = new NonRoslynBindingConfigFile(settings, settingsFilePath); - - return configFile; - } - - #endregion IBindingConfigProvider implementation - - internal /* for testing */ static RulesSettings CreateRulesSettingsFromQPRules(IList rules) - { - var settings = new RulesSettings() - { - Rules = rules.ToDictionary(ToRuleConfigKey, ToRuleConfig) - }; - - return settings; - } - - private static string ToRuleConfigKey(SonarQubeRule sonarQubeRule) - => $"{sonarQubeRule.RepositoryKey}:{sonarQubeRule.Key}"; - - private static RuleConfig ToRuleConfig(SonarQubeRule sonarQubeRule) - { - // Most rules don't have parameters, so to avoid creating objects unnecessarily - // we'll leave the parameters as null unless there really are values. - Dictionary parameters = null; - if ((sonarQubeRule.Parameters?.Count ?? 0) != 0) - { - parameters = sonarQubeRule.Parameters.ToDictionary(p => p.Key, p => p.Value); - } - - var config = new RuleConfig() - { - Level = sonarQubeRule.IsActive ? RuleLevel.On : RuleLevel.Off, - Parameters = parameters, - Severity = Convert(sonarQubeRule.Severity) - }; - - return config; - } - - internal /* for testing */ static IssueSeverity? Convert(SonarQubeIssueSeverity sonarQubeIssueSeverity) - { - switch (sonarQubeIssueSeverity) - { - case SonarQubeIssueSeverity.Blocker: - return IssueSeverity.Blocker; - case SonarQubeIssueSeverity.Critical: - return IssueSeverity.Critical; - case SonarQubeIssueSeverity.Info: - return IssueSeverity.Info; - case SonarQubeIssueSeverity.Major: - return IssueSeverity.Major; - case SonarQubeIssueSeverity.Minor: - return IssueSeverity.Minor; - default: - return null; - } - } - } -} diff --git a/src/ConnectedMode/Binding/NonRoslynDummyBindingConfigProvider.cs b/src/ConnectedMode/Binding/NonRoslynDummyBindingConfigProvider.cs new file mode 100644 index 0000000000..434dbf02b9 --- /dev/null +++ b/src/ConnectedMode/Binding/NonRoslynDummyBindingConfigProvider.cs @@ -0,0 +1,60 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 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.Core; +using SonarLint.VisualStudio.Core.Binding; +using SonarQube.Client.Models; + +namespace SonarLint.VisualStudio.ConnectedMode.Binding; + +internal class NonRoslynDummyBindingConfigProvider : IBindingConfigProvider +{ + // List of languages that use this type of configuration file (all non-Roslyn languages) + private static readonly Language[] SupportedLanguages = + [ + Language.C, + Language.Cpp, + Language.Js, + Language.Ts, + Language.Secrets, + Language.Css + ]; + + public bool IsLanguageSupported(Language language) => SupportedLanguages.Contains(language); + + public Task GetConfigurationAsync(SonarQubeQualityProfile qualityProfile, Language language, + BindingConfiguration bindingConfiguration, CancellationToken cancellationToken) + { + if (!IsLanguageSupported(language)) + { + throw new ArgumentOutOfRangeException(nameof(language)); + } + + return Task.FromResult(new DummyConfig()); + } + + internal sealed class DummyConfig : IBindingConfig + { + public void Save() + { + // do nothing + } + } +} diff --git a/src/ConnectedMode/QualityProfiles/QualityProfileDownloader.cs b/src/ConnectedMode/QualityProfiles/QualityProfileDownloader.cs index 631d9cd1a7..777b3da2a6 100644 --- a/src/ConnectedMode/QualityProfiles/QualityProfileDownloader.cs +++ b/src/ConnectedMode/QualityProfiles/QualityProfileDownloader.cs @@ -39,7 +39,7 @@ internal interface IQualityProfileDownloader /// If binding failed for one of the languages Task UpdateAsync(BoundServerProject boundProject, IProgress progress, CancellationToken cancellationToken); } - + [Export(typeof(IQualityProfileDownloader))] [PartCreationPolicy(CreationPolicy.Shared)] internal class QualityProfileDownloader : IQualityProfileDownloader @@ -61,7 +61,7 @@ public QualityProfileDownloader( this( bindingConfigProvider, configurationPersister, - outOfDateQualityProfileFinder, + outOfDateQualityProfileFinder, logger, Language.KnownLanguages) { } @@ -90,7 +90,7 @@ public async Task UpdateAsync(BoundServerProject boundProject, IProgress UpdateAsync(BoundServerProject boundProject, IProgress>(), Arg.Any()); issueConsumerStorage.Set("file/path", analysisId, issueConsumer); - analyzerController.ExecuteAnalysis("file/path", analysisId, detectedLanguages, issueConsumer, analyzerOptions, Arg.Any()); + analyzerController.ExecuteAnalysis("file/path", analysisId, detectedLanguages, analyzerOptions, Arg.Any()); }); } @@ -75,7 +75,7 @@ public void ScheduleAnalysis_JobCancelledBeforeStarting_DoesNotExecute() scheduler.Received().Schedule("file/path", Arg.Any>(), Arg.Any()); issueConsumerStorage.DidNotReceiveWithAnyArgs().Set(default, default, default); - analyzerController.DidNotReceiveWithAnyArgs().ExecuteAnalysis(default, default, default, default, default, default); + analyzerController.DidNotReceiveWithAnyArgs().ExecuteAnalysis(default, default, default, default, default); } [TestMethod] @@ -113,19 +113,6 @@ public void ScheduleAnalysis_NoEnvironmentSettings_DefaultTimeout() scheduler.Received().Schedule("file/path", Arg.Any>(), AnalysisService.DefaultAnalysisTimeoutMs); } - [DataRow(true)] - [DataRow(false)] - [DataTestMethod] - public void IsAnalysisSupported_CallsAnalyzerController(bool expected) - { - var analyzerController = Substitute.For(); - var detectedLanguages = Substitute.For>(); - analyzerController.IsAnalysisSupported(detectedLanguages).Returns(expected); - var testSubject = CreateTestSubject(analyzerController:analyzerController); - - testSubject.IsAnalysisSupported(detectedLanguages).Should().Be(expected); - } - [TestMethod] public void CancelForFile_JobCancelledBeforeStarting_DoesNotExecute() { diff --git a/src/Core.UnitTests/Analysis/AnalyzerControllerTests.cs b/src/Core.UnitTests/Analysis/AnalyzerControllerTests.cs index f385bdde23..71df9f643d 100644 --- a/src/Core.UnitTests/Analysis/AnalyzerControllerTests.cs +++ b/src/Core.UnitTests/Analysis/AnalyzerControllerTests.cs @@ -26,99 +26,20 @@ namespace SonarLint.VisualStudio.Core.UnitTests.Analysis [TestClass] public class AnalyzerControllerTests { - [TestMethod] - public void IsAnalysisSupported() - { - // Arrange - var analyzers = new[] - { - new DummyAnalyzer(), - new DummyAnalyzer(AnalysisLanguage.CFamily), - new DummyAnalyzer(), - }; - - var controller = CreateTestSubject(analyzers); - - // Act and Assert - controller.IsAnalysisSupported(new[] { AnalysisLanguage.CFamily }).Should().BeTrue(); - controller.IsAnalysisSupported(new[] { AnalysisLanguage.Javascript }).Should().BeFalse(); - } - - [TestMethod] - public void RequestAnalysis_FileIsNotAnalyzable_RequestAnalysisNotCalled() - { - // Arrange - var analyzers = new[] - { - new DummyAnalyzer(AnalysisLanguage.CFamily), - new DummyAnalyzer(AnalysisLanguage.CFamily), - new DummyAnalyzer(AnalysisLanguage.CFamily), - }; - - var analyzableFileIndicator = new Mock(); - analyzableFileIndicator.Setup(x => x.ShouldAnalyze("c:\\file.cpp")).Returns(false); - - var controller = CreateTestSubject(analyzers, analyzableFileIndicator: analyzableFileIndicator.Object); - - // Act - controller.ExecuteAnalysis("c:\\file.cpp", Guid.NewGuid(), - new[] { AnalysisLanguage.CFamily, AnalysisLanguage.Javascript }, null, null, CancellationToken.None); - - analyzers.Any(x => x.RequestAnalysisCalled).Should().BeFalse(); - - // Verify that the file was checked only once, regardless of number of analyzers - analyzableFileIndicator.Verify(x => x.ShouldAnalyze(It.IsAny()), Times.Once); - } - - [TestMethod] - public void RequestAnalysis_FileIsAnalyzable_LanguageIsNotSupported_RequestAnalysisNotCalled() - { - // Arrange - var analyzers = new[] - { - new DummyAnalyzer(), - new DummyAnalyzer(AnalysisLanguage.CFamily), - new DummyAnalyzer(), - }; - - var analyzableFileIndicator = new Mock(); - analyzableFileIndicator.Setup(x => x.ShouldAnalyze("c:\\file.cpp")).Returns(true); - - var controller = CreateTestSubject(analyzers, analyzableFileIndicator: analyzableFileIndicator.Object); - - // Act - controller.ExecuteAnalysis("c:\\file.cpp", Guid.NewGuid(), new[] { AnalysisLanguage.Javascript }, null, null, CancellationToken.None); - - analyzers.Any(x => x.RequestAnalysisCalled).Should().BeFalse(); - } - [TestMethod] public void RequestAnalysis_FileIsAnalyzable_LanguageIsSupported_RequestAnalysisIsCalled() { // Arrange - var analyzers = new[] - { - new DummyAnalyzer(), - new DummyAnalyzer(AnalysisLanguage.CFamily), - new DummyAnalyzer(), - new DummyAnalyzer(AnalysisLanguage.CFamily), - }; - - var analyzableFileIndicator = new Mock(); - analyzableFileIndicator.Setup(x => x.ShouldAnalyze("c:\\file.cpp")).Returns(true); + var analyzer = new DummyAnalyzer(); - var controller = CreateTestSubject(analyzers, analyzableFileIndicator: analyzableFileIndicator.Object); + var controller = CreateTestSubject(analyzer); // Act controller.ExecuteAnalysis("c:\\file.cpp", Guid.NewGuid(), - new[] { AnalysisLanguage.CFamily, AnalysisLanguage.Javascript }, null, null, CancellationToken.None); + new[] { AnalysisLanguage.CFamily, AnalysisLanguage.Javascript }, null, CancellationToken.None); - analyzers[0].RequestAnalysisCalled.Should().BeFalse(); - analyzers[2].RequestAnalysisCalled.Should().BeFalse(); - // Both analyzers that support analysis should be given the chance to handle the request. - analyzers[1].RequestAnalysisCalled.Should().BeTrue(); - analyzers[3].RequestAnalysisCalled.Should().BeTrue(); + analyzer.RequestAnalysisCalled.Should().BeTrue(); } [TestMethod] @@ -127,15 +48,11 @@ public void RequestAnalysis_FileIsAnalyzable_LanguageIsSupported_RequestAnalysis public void CleanUp_MonitorDisposed() { // Arrange - var analyzers = new[] - { - new DummyAnalyzer() - }; var monitorMock = new Mock(); var disposableMock = monitorMock.As(); - var controller = CreateTestSubject(analyzers, monitorMock.Object); + var controller = CreateTestSubject(new DummyAnalyzer(), monitorMock.Object); // Act - Dispose multiple times controller.Dispose(); @@ -146,33 +63,18 @@ public void CleanUp_MonitorDisposed() disposableMock.Verify(x => x.Dispose(), Times.Once); } - private static AnalyzerController CreateTestSubject(IEnumerable analyzers, - IAnalysisConfigMonitor analysisConfigMonitor = null, - IAnalyzableFileIndicator analyzableFileIndicator = null) => - new(Mock.Of(), analyzers, analysisConfigMonitor, analyzableFileIndicator); + private static AnalyzerController CreateTestSubject(IAnalyzer analyzer, + IAnalysisConfigMonitor analysisConfigMonitor = null) => + new(analysisConfigMonitor, analyzer, Mock.Of()); private class DummyAnalyzer : IAnalyzer { - private readonly AnalysisLanguage[] supportedLanguages; - public bool RequestAnalysisCalled { get; private set; } - public DummyAnalyzer(params AnalysisLanguage[] supportedLanguages) - { - this.supportedLanguages = supportedLanguages; - } - - public bool IsAnalysisSupported(IEnumerable languages) - { - return supportedLanguages?.Intersect(languages).Count() > 0; - } - - public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, - IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) + public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) { detectedLanguages.Should().NotBeNull(); detectedLanguages.Any().Should().BeTrue(); - IsAnalysisSupported(detectedLanguages).Should().BeTrue(); RequestAnalysisCalled = true; } diff --git a/src/Core/Analysis/AnalysisService.cs b/src/Core/Analysis/AnalysisService.cs index 8258aaec67..88b6c16886 100644 --- a/src/Core/Analysis/AnalysisService.cs +++ b/src/Core/Analysis/AnalysisService.cs @@ -40,11 +40,6 @@ internal AnalysisService(IAnalyzerController analyzerController, IIssueConsumerS this.scheduler = scheduler; } - public bool IsAnalysisSupported(IEnumerable languages) - { - return analyzerController.IsAnalysisSupported(languages); - } - public void ScheduleAnalysis(string filePath, Guid analysisId, IEnumerable detectedLanguages, @@ -57,7 +52,7 @@ public void ScheduleAnalysis(string filePath, if (!token.IsCancellationRequested) { issueConsumerStorage.Set(filePath, analysisId, issueConsumer); - analyzerController.ExecuteAnalysis(filePath, analysisId, detectedLanguages, issueConsumer, analyzerOptions, token); + analyzerController.ExecuteAnalysis(filePath, analysisId, detectedLanguages, analyzerOptions, token); } }, GetAnalysisTimeoutInMilliseconds()); diff --git a/src/Core/Analysis/AnalyzerController.cs b/src/Core/Analysis/AnalyzerController.cs index 07a3e34787..84851ada51 100644 --- a/src/Core/Analysis/AnalyzerController.cs +++ b/src/Core/Analysis/AnalyzerController.cs @@ -26,56 +26,26 @@ namespace SonarLint.VisualStudio.Core.Analysis [PartCreationPolicy(CreationPolicy.Shared)] internal sealed class AnalyzerController : IAnalyzerController, IDisposable { - private readonly ILogger logger; - private readonly IEnumerable analyzers; + private readonly IAnalyzer analyzer; // The analyzer controller does not use the config monitor. However, something needs to MEF-import // the config monitor so that it is created, and the lifetimes of the analyzer controller and // config monitor should be the same so it is convenient to create it here. private readonly IAnalysisConfigMonitor analysisConfigMonitor; - private readonly IAnalyzableFileIndicator analyzableFileIndicator; [ImportingConstructor] - public AnalyzerController(ILogger logger, - [ImportMany] IEnumerable analyzers, + public AnalyzerController( IAnalysisConfigMonitor analysisConfigMonitor, - IAnalyzableFileIndicator analyzableFileIndicator) + IAnalyzer analyzer, + ILogger logger) { - this.logger = logger; - this.analyzers = analyzers; this.analysisConfigMonitor = analysisConfigMonitor; - this.analyzableFileIndicator = analyzableFileIndicator; + this.analyzer = analyzer; } #region IAnalyzerController implementation - - public bool IsAnalysisSupported(IEnumerable languages) - { - bool isSupported = analyzers.Any(a => a.IsAnalysisSupported(languages)); - return isSupported; - } - - public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, - IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) - { - var supportedAnalyzers = analyzers.Where(x => x.IsAnalysisSupported(detectedLanguages)).ToList(); - var handled = false; - - if (supportedAnalyzers.Any() && analyzableFileIndicator.ShouldAnalyze(path)) - { - handled = true; - - foreach (var analyzer in supportedAnalyzers) - { - analyzer.ExecuteAnalysis(path, analysisId, detectedLanguages, consumer, analyzerOptions, cancellationToken); - } - } - - if (!handled) - { - logger.LogVerbose($"[AnalyzerController] No analyzer supported analysis of {path}"); - } - } + public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) => + analyzer.ExecuteAnalysis(path, analysisId, detectedLanguages, analyzerOptions, cancellationToken); #endregion IAnalyzerController implementation diff --git a/src/Core/Analysis/IAnalysisService.cs b/src/Core/Analysis/IAnalysisService.cs index 492a24654a..8d5ea4a369 100644 --- a/src/Core/Analysis/IAnalysisService.cs +++ b/src/Core/Analysis/IAnalysisService.cs @@ -25,11 +25,6 @@ namespace SonarLint.VisualStudio.Core.Analysis; /// public interface IAnalysisService { - /// - /// Indicates whether at least one language from list is analyzable. - /// - bool IsAnalysisSupported(IEnumerable languages); - /// /// Starts analysis for /// diff --git a/src/Core/Analysis/IAnalyzableFileIndicator.cs b/src/Core/Analysis/IAnalyzableFileIndicator.cs deleted file mode 100644 index ed92fd2c3a..0000000000 --- a/src/Core/Analysis/IAnalyzableFileIndicator.cs +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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. - */ - -namespace SonarLint.VisualStudio.Core.Analysis -{ - /// - /// Returns true/false whether the given file path should be analyzed or not. - /// - public interface IAnalyzableFileIndicator - { - bool ShouldAnalyze(string filePath); - } -} diff --git a/src/Core/Analysis/IAnalyzer.cs b/src/Core/Analysis/IAnalyzer.cs index e18fc939c7..359a62f37f 100644 --- a/src/Core/Analysis/IAnalyzer.cs +++ b/src/Core/Analysis/IAnalyzer.cs @@ -22,12 +22,9 @@ namespace SonarLint.VisualStudio.Core.Analysis { public interface IAnalyzer { - bool IsAnalysisSupported(IEnumerable languages); - void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, - IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken); } diff --git a/src/Core/Analysis/IExclusionSettingsStorage.cs b/src/Core/Analysis/IExclusionSettingsStorage.cs deleted file mode 100644 index 421e21430e..0000000000 --- a/src/Core/Analysis/IExclusionSettingsStorage.cs +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 SonarQube.Client.Models; - -namespace SonarLint.VisualStudio.Core.Analysis -{ - public interface IExclusionSettingsStorage - { - void SaveSettings(ServerExclusions settings); - ServerExclusions GetSettings(); - } -} diff --git a/src/Core/Language.cs b/src/Core/Language.cs index 093a518988..dea84c9be1 100644 --- a/src/Core/Language.cs +++ b/src/Core/Language.cs @@ -196,5 +196,7 @@ public override int GetHashCode() } #endregion + + public override string ToString() => Name; } } diff --git a/src/Integration.UnitTests/Exclusions/AnalyzableFileIndicatorTests.cs b/src/Integration.UnitTests/Exclusions/AnalyzableFileIndicatorTests.cs deleted file mode 100644 index 96e47ee902..0000000000 --- a/src/Integration.UnitTests/Exclusions/AnalyzableFileIndicatorTests.cs +++ /dev/null @@ -1,233 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Integration.Exclusions; -using SonarQube.Client.Models; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.Integration.UnitTests.Exclusions -{ - [TestClass] - public class AnalyzableFileIndicatorTests - { - private const string TestedFilePath = "some path"; - - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void ShouldAnalyze_NoExclusionSettings_True() - { - var patternMatcher = new Mock(); - - var testSubject = CreateTestSubject(null, patternMatcher.Object); - - var result = testSubject.ShouldAnalyze(TestedFilePath); - - result.Should().Be(true); - patternMatcher.Invocations.Count.Should().Be(0); - } - - [TestMethod] - [DataRow(true, true, false)] - [DataRow(true, false, false)] - [DataRow(false, true, false)] - [DataRow(false, false, true)] - public void ShouldAnalyze_NoInclusions_HasExclusions_ReturnsIfExcluded( - bool projectExclusionsApply, - bool globalExclusionsApply, - bool expectedResult) - { - var projectExclusions = new[] { "**/exclusion1", "**/exclusion2" }; - var globalExclusions = new[] { "**/exclusion3", "**/exclusion4" }; - var exclusionConfig = CreateServerExclusions( - inclusions: null, - exclusions: projectExclusions, - globalExclusions: globalExclusions); - - var patternMatcher = new Mock(); - patternMatcher.Setup(x => x.IsMatch(projectExclusions[0], TestedFilePath)).Returns(false); - patternMatcher.Setup(x => x.IsMatch(projectExclusions[1], TestedFilePath)).Returns(projectExclusionsApply); - patternMatcher.Setup(x => x.IsMatch(globalExclusions[0], TestedFilePath)).Returns(false); - patternMatcher.Setup(x => x.IsMatch(globalExclusions[1], TestedFilePath)).Returns(globalExclusionsApply); - - var testSubject = CreateTestSubject(exclusionConfig, patternMatcher.Object); - - var result = testSubject.ShouldAnalyze(TestedFilePath); - - result.Should().Be(expectedResult); - } - - [TestMethod] - [DataRow(true, true)] - [DataRow(false, false)] - public void ShouldAnalyze_HasInclusions_NoExclusions_ReturnsIfIncluded(bool inclusionsApply, bool expectedResult) - { - var inclusions = new[] { "**/inclusion1", "**/inclusion2" }; - var exclusionConfig = CreateServerExclusions( - inclusions: inclusions, - exclusions: null, - globalExclusions: null); - - var patternMatcher = new Mock(); - patternMatcher.Setup(x => x.IsMatch(inclusions[0], TestedFilePath)).Returns(false); - patternMatcher.Setup(x => x.IsMatch(inclusions[1], TestedFilePath)).Returns(inclusionsApply); - - var testSubject = CreateTestSubject(exclusionConfig, patternMatcher.Object); - - var result = testSubject.ShouldAnalyze(TestedFilePath); - - result.Should().Be(expectedResult); - patternMatcher.VerifyAll(); - patternMatcher.VerifyNoOtherCalls(); - } - - [TestMethod] - [DataRow(true, true, false)] - [DataRow(true, false, false)] - [DataRow(false, true, false)] - [DataRow(false, false, true)] - public void ShouldAnalyze_FileIncluded_ReturnsIfExcluded( - bool projectExclusionsApply, - bool globalExclusionsApply, - bool expectedResult) - { - var inclusions = new[] { "**/inclusion1", "**/inclusion2" }; - var projectExclusions = new[] { "**/exclusion1", "**/exclusion2" }; - var globalExclusions = new[] { "**/exclusion3", "**/exclusion4" }; - var exclusionConfig = CreateServerExclusions( - inclusions: inclusions, - exclusions: projectExclusions, - globalExclusions: globalExclusions); - - var patternMatcher = new Mock(); - patternMatcher.Setup(x => x.IsMatch(inclusions[0], TestedFilePath)).Returns(false); - patternMatcher.Setup(x => x.IsMatch(inclusions[1], TestedFilePath)).Returns(true); - patternMatcher.Setup(x => x.IsMatch(projectExclusions[0], TestedFilePath)).Returns(false); - patternMatcher.Setup(x => x.IsMatch(projectExclusions[1], TestedFilePath)).Returns(projectExclusionsApply); - patternMatcher.Setup(x => x.IsMatch(globalExclusions[0], TestedFilePath)).Returns(false); - patternMatcher.Setup(x => x.IsMatch(globalExclusions[1], TestedFilePath)).Returns(globalExclusionsApply); - - var testSubject = CreateTestSubject(exclusionConfig, patternMatcher.Object); - - var result = testSubject.ShouldAnalyze(TestedFilePath); - - result.Should().Be(expectedResult); - } - - [TestMethod, Description("Regression test for #3075")] - public void ShouldAnalyze_HasWindowsPathWithBackSlash_ReplacesWithForwardSlash() - { - var filePath = "C:\\FooBar\\foo.bar"; - - var projectExclusions = new[] { "**/exclusion" }; - var exclusionConfig = CreateServerExclusions( - inclusions: null, - exclusions: projectExclusions, - globalExclusions: null); - - var patternMatcher = new Mock(); - - var testSubject = CreateTestSubject(exclusionConfig, patternMatcher.Object); - - _ = testSubject.ShouldAnalyze(filePath); - - patternMatcher.Verify(x => x.IsMatch(projectExclusions[0], "C:\\FooBar\\foo.bar"), Times.Never); - patternMatcher.Verify(x => x.IsMatch(projectExclusions[0], "C:/FooBar/foo.bar"), Times.Once); - - } - - [TestMethod] - public void Perf_ShouldAnalyze_HasInclusions_FileNotIncluded_ExclusionsAreNotChecked() - { - var inclusions = new[] { "**/inclusion1" }; - var exclusions = new[] { "**/exclusion1" }; - var exclusionConfig = CreateServerExclusions( - inclusions: inclusions, - exclusions: exclusions, - globalExclusions: exclusions); - - var patternMatcher = new Mock(); - patternMatcher.Setup(x => x.IsMatch(inclusions[0], TestedFilePath)).Returns(false); - patternMatcher.Setup(x => x.IsMatch(exclusions[0], TestedFilePath)).Returns(false); - - var testSubject = CreateTestSubject(exclusionConfig, patternMatcher.Object); - - var result = testSubject.ShouldAnalyze(TestedFilePath); - - result.Should().Be(false); - patternMatcher.Verify(x=> x.IsMatch(inclusions[0], TestedFilePath), Times.Once); - patternMatcher.Verify(x=> x.IsMatch(exclusions[0], TestedFilePath), Times.Never); - patternMatcher.VerifyNoOtherCalls(); - } - - [TestMethod] - public void Perf_ShouldAnalyze_FileExcludedInProjectSettings_GlobalExclusionsAreNotChecked() - { - var projectExclusions = new[] { "**/exclusion1" }; - var globalExclusions = new[] { "**/exclusion2" }; - var exclusionConfig = CreateServerExclusions( - inclusions: null, - exclusions: projectExclusions, - globalExclusions: globalExclusions); - - var patternMatcher = new Mock(); - patternMatcher.Setup(x => x.IsMatch(projectExclusions[0], TestedFilePath)).Returns(true); - patternMatcher.Setup(x => x.IsMatch(globalExclusions[0], TestedFilePath)).Returns(true); - - var testSubject = CreateTestSubject(exclusionConfig, patternMatcher.Object); - - var result = testSubject.ShouldAnalyze(TestedFilePath); - - result.Should().Be(false); - patternMatcher.Verify(x => x.IsMatch(projectExclusions[0], TestedFilePath), Times.Once); - patternMatcher.Verify(x => x.IsMatch(globalExclusions[0], TestedFilePath), Times.Never); - patternMatcher.VerifyNoOtherCalls(); - } - - private AnalyzableFileIndicator CreateTestSubject(ServerExclusions exclusions, IGlobPatternMatcher patternMatcher) - { - var exclusionsSettingsStorage = new Mock(); - exclusionsSettingsStorage.Setup(x => x.GetSettings()).Returns(exclusions); - - return new AnalyzableFileIndicator(exclusionsSettingsStorage.Object, patternMatcher, Mock.Of()); - } - - private ServerExclusions CreateServerExclusions( - string[] inclusions, - string[] exclusions, - string[] globalExclusions) - { - return new ServerExclusions(exclusions: exclusions, - globalExclusions: globalExclusions, - inclusions: inclusions); - } - } -} diff --git a/src/Integration.UnitTests/Exclusions/ExclusionSettingsStorageTests.cs b/src/Integration.UnitTests/Exclusions/ExclusionSettingsStorageTests.cs deleted file mode 100644 index 7f574b6fef..0000000000 --- a/src/Integration.UnitTests/Exclusions/ExclusionSettingsStorageTests.cs +++ /dev/null @@ -1,233 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO.Abstractions; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Integration.Exclusions; -using SonarQube.Client.Models; -using SonarLint.VisualStudio.TestInfrastructure; -using Newtonsoft.Json; - -namespace SonarLint.VisualStudio.Integration.UnitTests.Exclusions -{ - [TestClass] - public class ExclusionSettingsStorageTests - { - private const string BindingFolder = "C:\\SolutionPath"; - private const string ExpectedExclusionsFilePath = "C:\\SolutionPath\\sonar.settings.json"; - private const string SerializedExclusions = "{\"sonar.exclusions\":[\"exclusion\"],\"sonar.global.exclusions\":[\"globalExclusion\"],\"sonar.inclusions\":[\"inclusion1\",\"inclusion2\"]}"; - - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void GetSettings_StandaloneMode_ReturnsNull() - { - var file = new Mock(); - file.Setup(f => f.ReadAllText(ExpectedExclusionsFilePath)).Returns(SerializedExclusions); - file.Setup(f => f.Exists(ExpectedExclusionsFilePath)).Returns(true); - - var bindingConfiguration = BindingConfiguration.Standalone; - - var testSubject = CreateTestSubject(file.Object, bindingConfiguration); - - var settings = testSubject.GetSettings(); - - settings.Should().BeNull(); - file.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public void GetSettings_SettingFileExists_ReadsSettings() - { - var file = new Mock(); - file.Setup(f => f.ReadAllText(ExpectedExclusionsFilePath)).Returns(SerializedExclusions); - file.Setup(f => f.Exists(ExpectedExclusionsFilePath)).Returns(true); - - var testSubject = CreateTestSubject(file.Object); - - var settings = testSubject.GetSettings(); - - settings.Should().NotBeNull(); - settings.Inclusions.Should().BeEquivalentTo("inclusion1", "inclusion2"); - settings.Exclusions.Should().BeEquivalentTo("exclusion"); - settings.GlobalExclusions.Should().BeEquivalentTo("globalExclusion"); - } - - [TestMethod] - public void GetSettings_SettingFileDoesNotExist_ReturnsNull() - { - var file = new Mock(); - file.Setup(f => f.Exists(ExpectedExclusionsFilePath)).Returns(false); - - var testSubject = CreateTestSubject(file.Object); - - var settings = testSubject.GetSettings(); - - settings.Should().BeNull(); - file.VerifyAll(); - file.VerifyNoOtherCalls(); - } - - [TestMethod] - public void GetSettings_FileReadError_ReturnsNull() - { - var file = new Mock(); - file.Setup(f => f.Exists(ExpectedExclusionsFilePath)).Returns(true); - file.Setup(f => f.ReadAllText(ExpectedExclusionsFilePath)).Throws(new Exception("File Read Error")); - - var testSubject = CreateTestSubject(file.Object); - - var settings = testSubject.GetSettings(); - - settings.Should().BeNull(); - file.VerifyAll(); - file.VerifyNoOtherCalls(); - } - - [TestMethod] - public void GetSettings_SerializationError_ReturnsNull() - { - var file = new Mock(); - file.Setup(f => f.Exists(ExpectedExclusionsFilePath)).Returns(true); - file.Setup(f => f.ReadAllText(ExpectedExclusionsFilePath)).Returns("Wrong String"); - - var testSubject = CreateTestSubject(file.Object); - - var settings = testSubject.GetSettings(); - - settings.Should().BeNull(); - file.VerifyAll(); - file.VerifyNoOtherCalls(); - } - - [TestMethod] - public void GetSettings_CriticalException_NotSuppressed() - { - var file = new Mock(); - file.Setup(f => f.Exists(ExpectedExclusionsFilePath)).Returns(true); - file.Setup(f => f.ReadAllText(ExpectedExclusionsFilePath)).Throws(new StackOverflowException()); - - var testSubject = CreateTestSubject(file.Object); - - Action act = () => testSubject.GetSettings(); - - act.Should().Throw(); - } - - [TestMethod] - public void SaveSettings_StandAloneMode_ThrowsInvalidOperationException() - { - var file = new Mock(); - var bindingConfiguration = BindingConfiguration.Standalone; - - var testSubject = CreateTestSubject(file.Object, bindingConfiguration); - - var settings = new ServerExclusions - { - Inclusions = new[] { "inclusion1", "inclusion2" }, - Exclusions = new[] { "exclusion" }, - GlobalExclusions = new[] { "globalExclusion" } - }; - - using var scope = new AssertIgnoreScope(); - - Action act = () => testSubject.SaveSettings(settings); - - act.Should().Throw(); - file.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public void SaveSettings_ErrorWritingSettings_ExceptionIsThrown() - { - var file = new Mock(); - file - .Setup(f => f.WriteAllText(It.IsAny(), It.IsAny())) - .Throws(new NotImplementedException("this is a test")); - - var testSubject = CreateTestSubject(file.Object); - - var settings = new ServerExclusions(); - - Action act = () => testSubject.SaveSettings(settings); - - act.Should().Throw().And.Message.Should().Be("this is a test"); - } - - [TestMethod] - public void SaveSettings_NoError_SavesSettings() - { - var file = new Mock(); - - var testSubject = CreateTestSubject(file.Object); - - var settings = new ServerExclusions - { - Inclusions = new[] { "inclusion1", "inclusion2" }, - Exclusions = new[] { "exclusion" }, - GlobalExclusions = new[] { "globalExclusion" } - }; - - var expectedSerializedSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); - - testSubject.SaveSettings(settings); - - file.Verify(f => f.WriteAllText(ExpectedExclusionsFilePath, expectedSerializedSettings)); - } - - private ExclusionSettingsStorage CreateTestSubject(IFile file, BindingConfiguration bindingConfiguration = null) - { - var fileSystem = CreateFileSystem(file); - - bindingConfiguration ??= new BindingConfiguration(null, SonarLintMode.Connected, BindingFolder); - var configurationProviderService = CreateConfigurationProvider(bindingConfiguration); - - return new ExclusionSettingsStorage(configurationProviderService, Mock.Of(), fileSystem); - } - - private static IConfigurationProvider CreateConfigurationProvider(BindingConfiguration bindingConfiguration) - { - var configurationProviderService = new Mock(); - configurationProviderService.Setup(c => c.GetConfiguration()).Returns(bindingConfiguration); - - return configurationProviderService.Object; - } - - private IFileSystem CreateFileSystem(IFile file) - { - var fileSystem = new Mock(); - fileSystem.SetupGet(fs => fs.File).Returns(file); - - return fileSystem.Object; - } - } -} diff --git a/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs b/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs index 5c67f22dd8..c0f3e0eaf3 100644 --- a/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs +++ b/src/Integration.Vsix.UnitTests/Analysis/IssueHandlerTests.cs @@ -105,6 +105,33 @@ public void HandleNewHotspots_HotspotStoreHaveExpectedValues() VerifyHotspotsAdded(hotspotStoreMock, expectedFilePath, [hotspot]); } + [TestMethod] + public void HandleNewHotspots_UpdatedSnapshotAndHotspotStoreHaveExpectedValues() + { + var hotspotStoreMock = new Mock(); + + var hotspot = CreateIssue("S112", startLine: 1, endLine: 1, isHotspot: true); + var inputIssues = new[] + { + hotspot, + }; + + var notificationHandler = new SnapshotChangeHandler(); + + var expectedGuid = Guid.NewGuid(); + const string expectedProjectName = "my project name"; + const string expectedFilePath = "c:\\aaa\\file.txt"; + + var testSubject = CreateTestSubject(notificationHandler.OnSnapshotChanged, + expectedProjectName, expectedGuid, expectedFilePath, localHotspotsStoreUpdater:hotspotStoreMock.Object); + + // Act + testSubject.HandleNewHotspots(inputIssues); + + // Assert + VerifyHotspotsAdded(hotspotStoreMock, expectedFilePath, [hotspot]); + } + [TestMethod] public void HandleNewIssues_IssuesGetMatchesIsCalled() { diff --git a/src/Integration.Vsix.UnitTests/SonarLintTagger/TaggerProviderTests.cs b/src/Integration.Vsix.UnitTests/SonarLintTagger/TaggerProviderTests.cs index 3ff275c0f7..8ac9075554 100644 --- a/src/Integration.Vsix.UnitTests/SonarLintTagger/TaggerProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/SonarLintTagger/TaggerProviderTests.cs @@ -70,7 +70,7 @@ public void SetUp() var mockAnalysisRequester = new Mock(); var mockFileTracker = new Mock(); - + provider = new TaggerProvider(mockSonarErrorDataSource.Object, dummyDocumentFactoryService, serviceProvider, mockSonarLanguageRecognizer.Object, mockAnalysisService.Object, mockAnalysisRequester.Object, mockTaggableBufferIndicator.Object, mockFileTracker.Object, logger); @@ -110,34 +110,21 @@ private static Export[] GetRequiredExports() => #endregion MEF tests [TestMethod] - public void CreateTagger_should_create_tracker_when_analysis_is_supported() + public void CreateTagger_should_create_tracker_when_analysis_is_requested() { - var doc = CreateMockedDocument("anyname", isDetectable: true); + var doc = CreateMockedDocument("anyname"); var tagger = CreateTaggerForDocument(doc); tagger.Should().NotBeNull(); - VerifyCheckedAnalysisIsSupported(); VerifyAnalysisWasRequested(); mockAnalysisService.VerifyNoOtherCalls(); } - [TestMethod] - public void CreateTagger_should_return_null_when_analysis_is_not_supported() - { - var doc = CreateMockedDocument("anyname", isDetectable: false); - var tagger = CreateTaggerForDocument(doc); - - tagger.Should().BeNull(); - - VerifyCheckedAnalysisIsSupported(); - mockAnalysisService.VerifyNoOtherCalls(); - } - [TestMethod] public void CreateTagger_should_return_null_when_buffer_is_not_taggable() { - var doc = CreateMockedDocument("anyname", isDetectable: true); + var doc = CreateMockedDocument("anyname"); mockTaggableBufferIndicator.Setup(x => x.IsTaggable(doc.TextBuffer)).Returns(false); var tagger = CreateTaggerForDocument(doc); @@ -349,7 +336,7 @@ private ITagger CreateTaggerForDocument(ITextDocument document) return provider.CreateTagger(document.TextBuffer); } - private ITextDocument CreateMockedDocument(string fileName, bool isDetectable = true) + private ITextDocument CreateMockedDocument(string fileName) { var bufferContentType = Mock.Of(); @@ -374,12 +361,10 @@ private ITextDocument CreateMockedDocument(string fileName, bool isDetectable = // Register the buffer-to-doc mapping for the factory service dummyDocumentFactoryService.RegisterDocument(mockTextDocument.Object); - var analysisLanguages = isDetectable ? new[] { AnalysisLanguage.Javascript } : Enumerable.Empty(); + var analysisLanguages = new[] { AnalysisLanguage.Javascript }; SetupDetectedLanguages(fileName, bufferContentType, analysisLanguages); - mockAnalysisService.Setup(x => x.IsAnalysisSupported(analysisLanguages)).Returns(isDetectable); - return mockTextDocument.Object; } @@ -406,11 +391,6 @@ private static SingletonDisposableTaggerManager FindSingletonManagerI return propertyValue; } - private void VerifyCheckedAnalysisIsSupported() - { - mockAnalysisService.Verify(x => x.IsAnalysisSupported(It.IsAny>()), Times.Once); - } - private void VerifyAnalysisWasRequested() { mockAnalysisService.Verify(x => x.CancelForFile(It.IsAny())); diff --git a/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs b/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs index 258919b4ce..76075e1898 100644 --- a/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs +++ b/src/Integration.Vsix.UnitTests/SonarLintTagger/VsAwareAnalysisServiceTests.cs @@ -46,21 +46,6 @@ public void MefCtor_CheckIsSingleton() MefTestHelpers.CheckIsSingletonMefComponent(); } - [DataTestMethod] - [DataRow(true)] - [DataRow(false)] - public void IsAnalysisSupported_UsesAnalyzerService(bool isSupported) - { - var detectedLanguages = Substitute.For>(); - var analysisService = Substitute.For(); - analysisService.IsAnalysisSupported(detectedLanguages).Returns(isSupported); - var testSubject = CreateTestSubject(analysisService: analysisService); - - testSubject.IsAnalysisSupported(detectedLanguages).Should().Be(isSupported); - - analysisService.Received().IsAnalysisSupported(detectedLanguages); - } - [TestMethod] public void CancelForFile_UsesAnalyzerService() { diff --git a/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs b/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs index e42a654574..1254a5fa0e 100644 --- a/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs +++ b/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs @@ -51,7 +51,7 @@ namespace SonarLint.VisualStudio.Integration.Vsix internal sealed class TaggerProvider : ITaggerProvider, IDocumentEvents { internal static readonly Type SingletonManagerPropertyCollectionKey = typeof(SingletonDisposableTaggerManager); - + internal readonly ISonarErrorListDataSource sonarErrorDataSource; internal readonly ITextDocumentFactoryService textDocumentFactoryService; @@ -155,16 +155,13 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag var detectedLanguages = languageRecognizer.Detect(textDocument.FilePath, buffer.ContentType); - if (analysisService.IsAnalysisSupported(detectedLanguages)) - { - // We only want one TBIT per buffer and we don't want it be disposed until - // it is not being used by any tag aggregators, so we're wrapping it in a SingletonDisposableTaggerManager - var singletonTaggerManager = buffer.Properties.GetOrCreateSingletonProperty(SingletonManagerPropertyCollectionKey, - () => new SingletonDisposableTaggerManager(_ => InternalCreateTextBufferIssueTracker(textDocument, detectedLanguages))); + // We only want one TBIT per buffer and we don't want it be disposed until + // it is not being used by any tag aggregators, so we're wrapping it in a SingletonDisposableTaggerManager + var singletonTaggerManager = buffer.Properties.GetOrCreateSingletonProperty(SingletonManagerPropertyCollectionKey, + () => new SingletonDisposableTaggerManager(_ => InternalCreateTextBufferIssueTracker(textDocument, detectedLanguages))); - var tagger = singletonTaggerManager.CreateTagger(buffer); - return tagger as ITagger; - } + var tagger = singletonTaggerManager.CreateTagger(buffer); + return tagger as ITagger; return null; } diff --git a/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs b/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs index 1a5436f2b9..e93b8d1b18 100644 --- a/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs +++ b/src/Integration.Vsix/SonarLintTagger/VsAwareAnalysisService.cs @@ -37,8 +37,6 @@ void RequestAnalysis(ITextDocument document, SnapshotChangedHandler errorListHandler, IAnalyzerOptions options); - bool IsAnalysisSupported(IEnumerable detectedLanguages); - void CancelForFile(string filePath); } @@ -73,9 +71,6 @@ public void RequestAnalysis(ITextDocument document, .Forget(); } - public bool IsAnalysisSupported(IEnumerable detectedLanguages) => - analysisService.IsAnalysisSupported(detectedLanguages); - public void CancelForFile(string filePath) => analysisService.CancelForFile(filePath); diff --git a/src/Integration/Exclusions/AnalyzableFileIndicator.cs b/src/Integration/Exclusions/AnalyzableFileIndicator.cs deleted file mode 100644 index 0fe252cda6..0000000000 --- a/src/Integration/Exclusions/AnalyzableFileIndicator.cs +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.ComponentModel.Composition; -using System.Linq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Integration.Helpers; -using SonarQube.Client.Models; - -namespace SonarLint.VisualStudio.Integration.Exclusions -{ - [Export(typeof(IAnalyzableFileIndicator))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class AnalyzableFileIndicator : IAnalyzableFileIndicator - { - private readonly IExclusionSettingsStorage exclusionSettingsStorage; - private readonly IGlobPatternMatcher globPatternMatcher; - private readonly ILogger logger; - - [ImportingConstructor] - public AnalyzableFileIndicator(IExclusionSettingsStorage exclusionSettingsStorage, ILogger logger) - : this(exclusionSettingsStorage, new GlobPatternMatcher(logger), logger) - { - } - - internal AnalyzableFileIndicator(IExclusionSettingsStorage exclusionSettingsStorage, - IGlobPatternMatcher globPatternMatcher, - ILogger logger) - { - this.exclusionSettingsStorage = exclusionSettingsStorage; - this.globPatternMatcher = globPatternMatcher; - this.logger = logger; - } - - public bool ShouldAnalyze(string filePath) - { - var serverExclusions = exclusionSettingsStorage.GetSettings(); - - if (serverExclusions == null) - { - logger.LogVerbose("[AnalyzableFileIndicator] No server settings were found."); - return true; - } - //Sonarqube uses unix-style directory seperators, so we should replace windows style seperators. - filePath = filePath.Replace("\\", "/"); - var shouldAnalyze = IsIncluded(serverExclusions, filePath) && !IsExcluded(serverExclusions, filePath); - - logger.LogVerbose($"[AnalyzableFileIndicator]" + - $"\n {serverExclusions}" + - $"\n file: '{filePath}'" + - $"\n should analyze: {shouldAnalyze} "); - - return shouldAnalyze; - } - - /// - /// Returns true/false if the file is considered included according to the specified pattern. - /// - /// - /// If there is no defined pattern, it means everything is included. - /// Hence, we check if the array is empty OR if it contains a matching pattern. - /// - private bool IsIncluded(ServerExclusions serverExclusions, string filePath) => - serverExclusions.Inclusions == null || - serverExclusions.Inclusions.Length == 0 || - serverExclusions.Inclusions.Any(x => IsMatch(x, filePath)); - - /// - /// Returns true/false if the file is considered excluded according to the specified patterns. - /// - /// - /// The file is considered excluded only if there is a defined pattern AND the file matches the pattern. - /// Project-level exclusions take precedence over global exclusions. - /// - private bool IsExcluded(ServerExclusions serverExclusions, string filePath) => - IsExcluded(serverExclusions.Exclusions, filePath) || - IsExcluded(serverExclusions.GlobalExclusions, filePath); - - private bool IsExcluded(string[] exclusions, string filePath) => - exclusions != null && - exclusions.Any(x => IsMatch(x, filePath)); - - private bool IsMatch(string pattern, string filePath) - { - return globPatternMatcher.IsMatch(pattern, filePath); - } - } -} diff --git a/src/Integration/Exclusions/ExclusionSettingsStorage.cs b/src/Integration/Exclusions/ExclusionSettingsStorage.cs deleted file mode 100644 index 2d6b200fa1..0000000000 --- a/src/Integration/Exclusions/ExclusionSettingsStorage.cs +++ /dev/null @@ -1,113 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Diagnostics; -using System.IO; -using System.IO.Abstractions; -using Newtonsoft.Json; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Integration.Helpers; -using SonarLint.VisualStudio.Integration.Resources; -using SonarQube.Client.Models; -using static System.String; - -namespace SonarLint.VisualStudio.Integration.Exclusions -{ - [Export(typeof(IExclusionSettingsStorage))] - internal class ExclusionSettingsStorage : IExclusionSettingsStorage - { - private readonly IConfigurationProvider bindingConfigProvider; - private readonly ILogger logger; - private readonly IFileSystem fileSystem; - - [ImportingConstructor] - public ExclusionSettingsStorage(IConfigurationProvider bindingConfigProvider, ILogger logger) - : this(bindingConfigProvider, logger, new FileSystem()) - { - } - - internal ExclusionSettingsStorage(IConfigurationProvider bindingConfigProvider, - ILogger logger, - IFileSystem fileSystem) - { - this.bindingConfigProvider = bindingConfigProvider; - this.logger = logger; - this.fileSystem = fileSystem; - } - - public void SaveSettings(ServerExclusions settings) - { - var bindingConfiguration = bindingConfigProvider.GetConfiguration(); - - if (bindingConfiguration.Mode == SonarLintMode.Standalone) - { - Debug.Fail("Not expecting to be called in Standalone mode"); - throw new InvalidOperationException("Cannot save exclusions in Standalone mode."); - } - - var fileContent = JsonConvert.SerializeObject(settings, Formatting.Indented); - var exclusionsFilePath = GetFilePath(bindingConfiguration); - - fileSystem.File.WriteAllText(exclusionsFilePath, fileContent); - } - - public ServerExclusions GetSettings() - { - try - { - var bindingConfiguration = bindingConfigProvider.GetConfiguration(); - - if (bindingConfiguration.Mode == SonarLintMode.Standalone) - { - logger.LogVerbose("[ExclusionSettingsStorage] Standalone mode, exclusions are not supported."); - return null; - } - - var exclusionsFilePath = GetFilePath(bindingConfiguration); - - if (!fileSystem.File.Exists(exclusionsFilePath)) - { - logger.WriteLine(Strings.ExclusionGetError, Format(Strings.ExclusionFileNotFound, exclusionsFilePath)); - return null; - } - - var fileContent = fileSystem.File.ReadAllText(exclusionsFilePath); - - logger.LogVerbose("[ExclusionSettingsStorage] File content: {0}", fileContent); - - return JsonConvert.DeserializeObject(fileContent); - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - logger.LogVerbose("[ExclusionSettingsStorage] GetSettings error: {0}", ex.ToString()); - logger.WriteLine(Strings.ExclusionGetError, ex.Message); - } - - return null; - } - - private static string GetFilePath(BindingConfiguration bindingConfiguration) => - Path.Combine(bindingConfiguration.BindingConfigDirectory, "sonar.settings.json"); - } -} diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index 4a451040b7..b49d05faf7 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -84,23 +84,10 @@ public void MefCtor_CheckIsExported() => [TestMethod] public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - [TestMethod] - public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() => - testSubject.IsAnalysisSupported([]).Should().BeTrue(); - - [DataTestMethod] - [DataRow(AnalysisLanguage.Javascript)] - [DataRow(AnalysisLanguage.TypeScript)] - [DataRow(AnalysisLanguage.CFamily)] - [DataRow(AnalysisLanguage.CascadingStyleSheets)] - [DataRow(AnalysisLanguage.RoslynFamily)] - public void IsAnalysisSupported_ReturnsTrueForEveryDetectedLanguage(AnalysisLanguage language) => - testSubject.IsAnalysisSupported([language]).Should().BeTrue(); - [TestMethod] public void ExecuteAnalysis_CreatesNotifierAndStarts() { - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), FilePath, analysisId); notifier.Received().AnalysisStarted(); @@ -111,7 +98,7 @@ public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() { activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); @@ -123,7 +110,7 @@ public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() { activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: false)); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); @@ -136,7 +123,7 @@ public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() SetUpServiceProvider(false); SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); analysisService.ReceivedCalls().Should().BeEmpty(); @@ -150,7 +137,7 @@ public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() SetUpCurrentTimeProvider(expectedTimeStamp); SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.analysisId == analysisId @@ -170,7 +157,7 @@ public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysis IAnalyzerOptions options = value.HasValue ? new AnalyzerOptions { IsOnOpen = value.Value } : null; SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(FilePath, default, default, default, options, default); + testSubject.ExecuteAnalysis(FilePath, default, default, options, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.shouldFetchServerIssues == expected), @@ -186,7 +173,7 @@ public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraPropertie SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null @@ -205,7 +192,7 @@ public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDispos SetUpInitializedConfigScope(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(); - testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); compilationDatabaseHandle.Received().Dispose(); } @@ -217,7 +204,7 @@ public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_PassesEmptyStr SetUpCompilationDatabaseLocator(filePath, null); SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null @@ -232,7 +219,7 @@ public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() var cancellationTokenSource = new CancellationTokenSource(); SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, cancellationTokenSource.Token); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, cancellationTokenSource.Token); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), cancellationTokenSource.Token); @@ -244,7 +231,7 @@ public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysi SetUpInitializedConfigScope(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); @@ -258,7 +245,7 @@ public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() SetUpInitializedConfigScope(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet { new(@"C:\file\path") }, [])); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } @@ -270,7 +257,7 @@ public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() var operationCanceledException = new OperationCanceledException(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); notifier.Received().AnalysisCancelled(); } @@ -282,7 +269,7 @@ public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() var exception = new Exception(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); notifier.Received().AnalysisFailed(exception); } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index 49b087374d..d3598dba12 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -61,13 +61,10 @@ public SLCoreAnalyzer( this.logger = logger; } - public bool IsAnalysisSupported(IEnumerable languages) => true; - public void ExecuteAnalysis( string path, Guid analysisId, IEnumerable detectedLanguages, - IIssueConsumer consumer, IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) { From e4c93099934b88854fbe3240c498c8108d2d2f3b Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:05:09 +0100 Subject: [PATCH 12/21] SLVS-1698 Cleanup of custom cfamily implementation (#5889) [SLVS-1698](https://sonarsource.atlassian.net/browse/SLVS-1698) Part of SLVS-1661 [SLVS-1698]: https://sonarsource.atlassian.net/browse/SLVS-1698?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- src/CFamily.UnitTests/CFamilySharedTests.cs | 64 -- .../CMake/CMakeProjectTypeIndicatorTests.cs | 123 --- .../CMake/EnvironmentVarsProviderTests.cs | 173 ---- .../VsDevCmdEnvironmentVarsProviderTests.cs | 382 -------- .../CompilationDatabaseRequestTests.cs | 396 --------- .../RulesConfigProtocolFormatterTests.cs | 141 --- .../Helpers/DummyCFamilyRulesConfig.cs | 86 -- .../Helpers/DummyExeHelper.cs | 165 ---- ...angAnalyzerTestFile_NoIssues_EmptyFile.txt | 1 - ...rTestFile_NoIssues_EmptyFile_response.json | 3 - .../CLangAnalyzerTestFile_OneIssue.txt | 1 - ...estFile_OneIssue_HasSecondaryLocations.txt | 6 - ...eIssue_HasSecondaryLocations_response.json | 49 - ...angAnalyzerTestFile_OneIssue_response.json | 16 - .../CLangAnalyzerTestFile_TwoIssues.txt | 2 - ...ngAnalyzerTestFile_TwoIssues_response.json | 28 - .../Rules/CFamilyRuleConfigProviderTests.cs | 133 --- ...CFamilySonarWayRulesConfigProviderTests.cs | 107 --- .../Rules/DynamicCFamilyRulesConfigTests.cs | 371 -------- ...ffectiveRulesConfigCalculatorCacheTests.cs | 111 --- .../EffectiveRulesConfigCalculatorTests.cs | 181 ---- .../Rules/RulesConfigFixupTests.cs | 214 ----- .../Rules/RulesLoaderTest.cs | 154 ---- .../RulesLoader/ClassComplexity.json | 22 - .../RulesLoader/ClassComplexity_params.json | 8 - .../RulesLoader/MisraRulesList.json | 4 - .../TestResources/RulesLoader/RulesList.json | 412 --------- .../RulesLoader/Sonar_way_profile.json | 260 ------ .../All_ActiveWithParams_1.json | 23 - .../All_ActiveWithParams_1_params.json | 8 - .../RulesMetadataCache/All_Active_1.json | 25 - .../RulesMetadataCache/All_Inactive_1.json | 25 - .../RulesMetadataCache/C_Active_1.json | 33 - .../RulesMetadataCache/C_Inactive_1.json | 33 - .../RulesMetadataCache/C_Inactive_2.json | 33 - .../RulesMetadataCache/C_Misra_Active_1.json | 33 - .../C_Misra_Inactive_1.json | 33 - .../RulesMetadataCache/Cpp_Active_1.json | 33 - .../RulesMetadataCache/Cpp_Active_2.json | 33 - .../RulesMetadataCache/Cpp_Inactive_1.json | 33 - .../Cpp_Misra_Active_1.json | 33 - .../Cpp_Misra_Inactive_1.json | 33 - .../RulesMetadataCache/MisraRulesList.json | 6 - .../RulesMetadataCache/RulesList.json | 11 - .../RulesMetadataCache/Sonar_way_profile.json | 12 - .../Subprocess/MessageHandlerTests.cs | 264 ------ .../Subprocess/NoOpMessageHandlerTests.cs | 60 -- .../Subprocess/ProcessRunnerTests.cs | 528 ----------- .../Subprocess/ProtocolTest.cs | 569 ------------ .../Analysis/ICFamilyIssueConverterFactory.cs | 36 - src/CFamily/Analysis/IRequest.cs | 49 - src/CFamily/Analysis/IRequestFactory.cs | 41 - src/CFamily/Analysis/RequestContext.cs | 65 -- .../Analysis/RequestFactoryAggregate.cs | 65 -- src/CFamily/CFamily.csproj | 9 - src/CFamily/CFamilyShared.cs | 75 -- .../CMake/CMakeProjectTypeIndicator.cs | 65 -- src/CFamily/CMake/EnvironmentVarsProvider.cs | 108 --- .../CMake/VsDevCmdEnvironmentVarsProvider.cs | 237 ----- .../CompilationDatabaseRequest.cs | 152 ---- .../RulesConfigProtocolFormatter.cs | 85 -- .../Rules/CFamilyRulesConfigProvider.cs | 89 -- .../CFamilySonarWayRulesConfigProvider.cs | 106 --- .../Rules/DynamicCFamilyRulesConfig.cs | 187 ---- .../Rules/EffectiveRulesConfigCalculator.cs | 159 ---- src/CFamily/Rules/ICFamilyRulesConfig.cs | 73 -- .../Rules/ICFamilyRulesConfigProvider.cs | 32 - src/CFamily/Rules/Resources.Designer.cs | 126 --- src/CFamily/Rules/Resources.resx | 145 --- src/CFamily/Rules/RulesConfigFixup.cs | 262 ------ src/CFamily/Rules/RulesLoader.cs | 161 ---- .../Subprocess/CFamilyStrings.Designer.cs | 256 ------ src/CFamily/Subprocess/CFamilyStrings.resx | 189 ---- src/CFamily/Subprocess/IProcessRunner.cs | 27 - src/CFamily/Subprocess/MessageHandler.cs | 159 ---- .../Subprocess/PortedFromJava/Analyzer.cs | 117 --- .../Subprocess/PortedFromJava/Protocol.cs | 238 ----- .../Subprocess/PortedFromJava/ReadMe.txt | 7 - src/CFamily/Subprocess/ProcessRunner.cs | 233 ----- .../Subprocess/ProcessRunnerArguments.cs | 244 ----- src/CFamily/Subprocess/SubProcessFilePaths.cs | 59 -- .../RuleSettingsProviderTests.cs | 152 ---- .../UserRuleSettings/IRuleSettingsProvider.cs | 99 --- .../VsInfoServiceTests.cs | 100 --- src/Infrastructure.VS/VsInfoService.cs | 60 -- .../RuleSettingsProviderFactoryTests.cs | 58 -- .../CFamilyEmbeddedSonarWayRulesTests.cs | 110 --- .../CFamilyIssueConverterFactoryTests.cs | 65 -- ...amilyIssueToAnalysisIssueConverterTests.cs | 840 ------------------ .../Analysis/RuleSettingsProviderFactory.cs | 63 -- ...egration.Vsix_Baseline_WithStrongNames.txt | 3 +- ...ation.Vsix_Baseline_WithoutStrongNames.txt | 3 +- .../CFamilyIssueToAnalysisIssueConverter.cs | 326 ------- 93 files changed, 2 insertions(+), 11207 deletions(-) delete mode 100644 src/CFamily.UnitTests/CFamilySharedTests.cs delete mode 100644 src/CFamily.UnitTests/CMake/CMakeProjectTypeIndicatorTests.cs delete mode 100644 src/CFamily.UnitTests/CMake/EnvironmentVarsProviderTests.cs delete mode 100644 src/CFamily.UnitTests/CMake/VsDevCmdEnvironmentVarsProviderTests.cs delete mode 100644 src/CFamily.UnitTests/CompilationDatabase/CompilationDatabaseRequestTests.cs delete mode 100644 src/CFamily.UnitTests/CompilationDatabase/RulesConfigProtocolFormatterTests.cs delete mode 100644 src/CFamily.UnitTests/Helpers/DummyCFamilyRulesConfig.cs delete mode 100644 src/CFamily.UnitTests/Helpers/DummyExeHelper.cs delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile.txt delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile_response.json delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue.txt delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations.txt delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations_response.json delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_response.json delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues.txt delete mode 100644 src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues_response.json delete mode 100644 src/CFamily.UnitTests/Rules/CFamilyRuleConfigProviderTests.cs delete mode 100644 src/CFamily.UnitTests/Rules/CFamilySonarWayRulesConfigProviderTests.cs delete mode 100644 src/CFamily.UnitTests/Rules/DynamicCFamilyRulesConfigTests.cs delete mode 100644 src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorCacheTests.cs delete mode 100644 src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorTests.cs delete mode 100644 src/CFamily.UnitTests/Rules/RulesConfigFixupTests.cs delete mode 100644 src/CFamily.UnitTests/Rules/RulesLoaderTest.cs delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity_params.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesLoader/MisraRulesList.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesLoader/RulesList.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesLoader/Sonar_way_profile.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1_params.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Active_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Inactive_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Active_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_2.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Active_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Inactive_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_2.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Inactive_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Active_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Inactive_1.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/MisraRulesList.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/RulesList.json delete mode 100644 src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Sonar_way_profile.json delete mode 100644 src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs delete mode 100644 src/CFamily.UnitTests/Subprocess/NoOpMessageHandlerTests.cs delete mode 100644 src/CFamily.UnitTests/Subprocess/ProcessRunnerTests.cs delete mode 100644 src/CFamily.UnitTests/Subprocess/ProtocolTest.cs delete mode 100644 src/CFamily/Analysis/ICFamilyIssueConverterFactory.cs delete mode 100644 src/CFamily/Analysis/IRequest.cs delete mode 100644 src/CFamily/Analysis/IRequestFactory.cs delete mode 100644 src/CFamily/Analysis/RequestContext.cs delete mode 100644 src/CFamily/Analysis/RequestFactoryAggregate.cs delete mode 100644 src/CFamily/CFamilyShared.cs delete mode 100644 src/CFamily/CMake/CMakeProjectTypeIndicator.cs delete mode 100644 src/CFamily/CMake/EnvironmentVarsProvider.cs delete mode 100644 src/CFamily/CMake/VsDevCmdEnvironmentVarsProvider.cs delete mode 100644 src/CFamily/CompilationDatabase/CompilationDatabaseRequest.cs delete mode 100644 src/CFamily/CompilationDatabase/RulesConfigProtocolFormatter.cs delete mode 100644 src/CFamily/Rules/CFamilyRulesConfigProvider.cs delete mode 100644 src/CFamily/Rules/CFamilySonarWayRulesConfigProvider.cs delete mode 100644 src/CFamily/Rules/DynamicCFamilyRulesConfig.cs delete mode 100644 src/CFamily/Rules/EffectiveRulesConfigCalculator.cs delete mode 100644 src/CFamily/Rules/ICFamilyRulesConfig.cs delete mode 100644 src/CFamily/Rules/ICFamilyRulesConfigProvider.cs delete mode 100644 src/CFamily/Rules/Resources.Designer.cs delete mode 100644 src/CFamily/Rules/Resources.resx delete mode 100644 src/CFamily/Rules/RulesConfigFixup.cs delete mode 100644 src/CFamily/Rules/RulesLoader.cs delete mode 100644 src/CFamily/Subprocess/CFamilyStrings.Designer.cs delete mode 100644 src/CFamily/Subprocess/CFamilyStrings.resx delete mode 100644 src/CFamily/Subprocess/IProcessRunner.cs delete mode 100644 src/CFamily/Subprocess/MessageHandler.cs delete mode 100644 src/CFamily/Subprocess/PortedFromJava/Analyzer.cs delete mode 100644 src/CFamily/Subprocess/PortedFromJava/Protocol.cs delete mode 100644 src/CFamily/Subprocess/PortedFromJava/ReadMe.txt delete mode 100644 src/CFamily/Subprocess/ProcessRunner.cs delete mode 100644 src/CFamily/Subprocess/ProcessRunnerArguments.cs delete mode 100644 src/CFamily/Subprocess/SubProcessFilePaths.cs delete mode 100644 src/Core.UnitTests/RuleSettingsProviderTests.cs delete mode 100644 src/Core/UserRuleSettings/IRuleSettingsProvider.cs delete mode 100644 src/Infrastructure.VS.UnitTests/VsInfoServiceTests.cs delete mode 100644 src/Infrastructure.VS/VsInfoService.cs delete mode 100644 src/Integration.Vsix.UnitTests/Analysis/RuleSettingsProviderFactoryTests.cs delete mode 100644 src/Integration.Vsix.UnitTests/CFamily/CFamilyEmbeddedSonarWayRulesTests.cs delete mode 100644 src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueConverterFactoryTests.cs delete mode 100644 src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueToAnalysisIssueConverterTests.cs delete mode 100644 src/Integration.Vsix/Analysis/RuleSettingsProviderFactory.cs delete mode 100644 src/Integration.Vsix/CFamily/CFamilyIssueToAnalysisIssueConverter.cs diff --git a/src/CFamily.UnitTests/CFamilySharedTests.cs b/src/CFamily.UnitTests/CFamilySharedTests.cs deleted file mode 100644 index 4a84ac05a3..0000000000 --- a/src/CFamily.UnitTests/CFamilySharedTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using SonarLint.VisualStudio.Core; - -namespace SonarLint.VisualStudio.CFamily.UnitTests -{ - [TestClass] - public class CFamilySharedTests - { - [TestMethod] - [DataRow("c:\\aaa.cpp", SonarLanguageKeys.CPlusPlus)] - [DataRow("c:\\AAA.CPP", SonarLanguageKeys.CPlusPlus)] - [DataRow("c:\\aaa.cxx", SonarLanguageKeys.CPlusPlus)] - [DataRow("d:\\xxx.cc", SonarLanguageKeys.CPlusPlus)] - [DataRow("c:\\aaa\\bbb.c", SonarLanguageKeys.C)] - [DataRow("c:\\aaa.cpp.x", null)] - [DataRow("c:\\aaa.cs", null)] - [DataRow("c:\\aaa.js", null)] - [DataRow("c:\\aaa.x", null)] - public void FindLanguage_ReturnsExpectedValue(string filePath, string expected) - { - CFamilyShared.FindLanguageFromExtension(filePath) - .Should().Be(expected); - } - - [TestMethod] - [DataRow("c:\\test.h", true)] - [DataRow("c:\\test.hpp", true)] - [DataRow("c:\\test.hh", true)] - [DataRow("c:\\test.hxx", true)] - [DataRow("c:\\test.H", true)] - [DataRow("c:\\test.HPP", true)] - [DataRow("c:\\test.HH", true)] - [DataRow("c:\\test.HXX", true)] - [DataRow("c:\\test.Hxx", true)] - [DataRow("c:\\test.h.ha", false)] - [DataRow("c:\\test.cpp", false)] - [DataRow("c:\\test.cpp.h", true)] - public void IsHeaderFileExtension_ReturnsExpectedValue(string filePath, bool expected) - { - CFamilyShared.IsHeaderFileExtension(filePath) - .Should().Be(expected); - } - } -} diff --git a/src/CFamily.UnitTests/CMake/CMakeProjectTypeIndicatorTests.cs b/src/CFamily.UnitTests/CMake/CMakeProjectTypeIndicatorTests.cs deleted file mode 100644 index c3a5ffa80d..0000000000 --- a/src/CFamily.UnitTests/CMake/CMakeProjectTypeIndicatorTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO.Abstractions; -using System.IO.Abstractions.TestingHelpers; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.CFamily; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.CMake.UnitTests -{ - [TestClass] - public class CMakeProjectTypeIndicatorTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void IsCMake_NotOpenAsFolder_False() - { - var folderWorkspaceService = new Mock(); - folderWorkspaceService.Setup(x => x.IsFolderWorkspace()).Returns(false); - - var testSubject = CreateTestSubject(folderWorkspaceService.Object); - - var result = testSubject.IsCMake(); - - result.Should().BeFalse(); - } - - [TestMethod] - public void IsCMake_CouldNotRetrieveRootDirectory_ArgumentNullException() - { - var folderWorkspaceService = SetupOpenAsFolder(rootDirectory: null); - - var testSubject = CreateTestSubject(folderWorkspaceService.Object); - - Action act = () => testSubject.IsCMake(); - - act.Should().Throw().And.ParamName.Should().Be("path"); - } - - [TestMethod] - [DataRow("c:\\some directory\\CMakeLists.txt")] - [DataRow("c:\\some directory\\sub\\CMakeLists.txt")] - [DataRow("c:\\some directory\\sub\\folder\\CMakeLists.txt")] - public void IsCMake_OpenAsFolderProject_HasCmakeFiles_True(string cmakeListsLocation) - { - var folderWorkspaceService = SetupOpenAsFolder("c:\\some directory"); - var fileSystem = new MockFileSystem(); - fileSystem.AddDirectory("c:\\some directory"); - fileSystem.AddFile(cmakeListsLocation, new MockFileData("")); - - var testSubject = CreateTestSubject(folderWorkspaceService.Object, fileSystem); - - var actualLanguage = testSubject.IsCMake(); - - actualLanguage.Should().BeTrue(); - } - - [TestMethod] - public void IsCMake_OpenAsFolderProject_NoCmakeFiles_False() - { - var folderWorkspaceService = SetupOpenAsFolder("c:\\some directory"); - var fileSystem = new MockFileSystem(); - fileSystem.AddDirectory("c:\\some directory"); - fileSystem.AddFile("c:\\anotherRoot\\CMakeLists.txt", new MockFileData("")); - - var testSubject = CreateTestSubject(folderWorkspaceService.Object, fileSystem); - - var actualLanguage = testSubject.IsCMake(); - - actualLanguage.Should().BeFalse(); - } - - private static CMakeProjectTypeIndicator CreateTestSubject(IFolderWorkspaceService folderWorkspaceService = null, IFileSystem fileSystem = null) - { - folderWorkspaceService ??= Mock.Of(); - fileSystem ??= new MockFileSystem(); - - return new CMakeProjectTypeIndicator(folderWorkspaceService, fileSystem); - } - - private static Mock SetupOpenAsFolder(string rootDirectory) - { - var folderWorkspaceService = new Mock(); - - folderWorkspaceService - .Setup(x => x.IsFolderWorkspace()) - .Returns(true); - - folderWorkspaceService.Setup(x => x.FindRootDirectory()) - .Returns(rootDirectory); - - return folderWorkspaceService; - } - } -} diff --git a/src/CFamily.UnitTests/CMake/EnvironmentVarsProviderTests.cs b/src/CFamily.UnitTests/CMake/EnvironmentVarsProviderTests.cs deleted file mode 100644 index 4720b3493c..0000000000 --- a/src/CFamily.UnitTests/CMake/EnvironmentVarsProviderTests.cs +++ /dev/null @@ -1,173 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.CFamily.CMake; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.UnitTests.CMake -{ - [TestClass] - public class EnvironmentVarsProviderTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public async Task TryGet_Caching() - { - var envVars_AAA = new Dictionary { { "AAA", "111" } }; - var envVars_BBB = new Dictionary { { "BBB", "222" } }; - - var vsDevCmdProvider = new Mock() - .SetEnvVars("AAA", envVars_AAA) - .SetEnvVars("BBB", envVars_BBB); - - var testSubject = CreateTestSubject(vsDevCmdProvider.Object); - - // 1. ScriptParams AAA: empty cache => cache miss => provider called - var case1 = await testSubject.GetAsync("AAA"); - - case1.Should().BeSameAs(envVars_AAA); - vsDevCmdProvider.CheckSettingsFetchedOnce("AAA"); - vsDevCmdProvider.CheckSettingsNotFetched("BBB"); - - // 2. ScriptParamsA: cache hit => provider not called - var case2 = await testSubject.GetAsync("AAA"); - - case2.Should().BeSameAs(envVars_AAA); - vsDevCmdProvider.CheckSettingsFetchedOnce("AAA"); - vsDevCmdProvider.CheckSettingsNotFetched("BBB"); - - // 3. ScriptParamsB: different params, cache miss => provider called - var case3 = await testSubject.GetAsync("BBB"); - - case3.Should().BeSameAs(envVars_BBB); - vsDevCmdProvider.CheckSettingsFetchedOnce("AAA"); - vsDevCmdProvider.CheckSettingsFetchedOnce("BBB"); - } - - [TestMethod] - public async Task TryGet_NullVsDevCmdResultsAreCached() - { - var vsDevCmdProvider = new Mock() - .SetEnvVars(string.Empty, null); - - var testSubject = CreateTestSubject(vsDevCmdProvider.Object); - - // 1. empty cache => cache miss => provider called - var actual = await testSubject.GetAsync(string.Empty); - - actual.Should().BeNull(); - vsDevCmdProvider.CheckSettingsFetchedOnce(string.Empty); - - // 2. Call again - null result should have been cached i.e. we don't - // retry calling just because we didn't get a non-null result first time. - actual = await testSubject.GetAsync(string.Empty); - - actual.Should().BeNull(); - vsDevCmdProvider.CheckSettingsFetchedOnce(string.Empty); - } - - [TestMethod] - public void TryGet_SecondCallIsBlockedUntilFirstCallCompletes() - { - const int timeoutMs = 300; - - var firstCallStarted = new ManualResetEvent(false); - var allowFirstCallToComplete = new ManualResetEvent(false); - - void FirstCallToProviderOp() - { - // Let the test know that the provider has been called i.e. we're inside the lock - firstCallStarted.Set(); - - // Wait until the test indicates that it wants the call to finish i.e. to release the lock - allowFirstCallToComplete.WaitOne(); - } - - // Two calls with same script params, so the provider should only be called once - // and return the same result - var provider = new Mock(); - provider.Setup(x => x.GetAsync(string.Empty)) - .Callback(FirstCallToProviderOp) - .ReturnsAsync(new Dictionary()); - - var testSubject = CreateTestSubject(provider.Object); - - // Start first call and wait for it to report it has started - var firstCall = Task.Run(() => testSubject.GetAsync()); - var firstTaskRunning = firstCallStarted.WaitOne(timeoutMs); - firstTaskRunning.Should().BeTrue(); - provider.Invocations.Count.Should().Be(1); - - // Start second call - should be blocked by lock - var secondCall = Task.Run(() => testSubject.GetAsync()); - var secondCallFinished = secondCall.Wait(500); - secondCallFinished.Should().BeFalse(); - - // Check the second call thread is waiting for the lock - secondCall.Status.Should().Be(TaskStatus.WaitingForActivation); - - // Unblock the first call - allowFirstCallToComplete.Set(); - var firstCallCompleted = firstCall.Wait(timeoutMs); - firstCallCompleted.Should().BeTrue(); - - var secondCallCompleted = secondCall.Wait(timeoutMs); - secondCallCompleted.Should().BeTrue(); - - secondCall.Result.Should().BeSameAs(firstCall.Result); - provider.Invocations.Count.Should().Be(1); - } - - private static EnvironmentVarsProvider CreateTestSubject(IVsDevCmdEnvironmentProvider vsDevCmdProvider) - { - var testSubject = new EnvironmentVarsProvider(vsDevCmdProvider, new TestLogger(logToConsole: true)); - return testSubject; - } - } - - internal static class EnvironmentVarsProviderTestsExtensions - { - public static Mock SetEnvVars(this Mock provider, string scriptParams, IReadOnlyDictionary envVars) - { - provider.Setup(x => x.GetAsync(scriptParams)).Returns(Task.FromResult(envVars)); - return provider; - } - - public static void CheckSettingsFetchedOnce(this Mock provider, string scriptParms) => - provider.Verify(x => x.GetAsync(scriptParms), Times.Once); - - public static void CheckSettingsNotFetched(this Mock provider, string scriptParms) => - provider.Verify(x => x.GetAsync(scriptParms), Times.Never); - } -} diff --git a/src/CFamily.UnitTests/CMake/VsDevCmdEnvironmentVarsProviderTests.cs b/src/CFamily.UnitTests/CMake/VsDevCmdEnvironmentVarsProviderTests.cs deleted file mode 100644 index 8c801f6315..0000000000 --- a/src/CFamily.UnitTests/CMake/VsDevCmdEnvironmentVarsProviderTests.cs +++ /dev/null @@ -1,382 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Diagnostics; -using System.IO; -using System.IO.Abstractions; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.CMake; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.SystemAbstractions; -using SonarLint.VisualStudio.Infrastructure.VS; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.UnitTests.CMake -{ - [TestClass] - public class VsDevCmdEnvironmentVarsProviderTests - { - public TestContext TestContext { get; set; } - - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public async Task Get_MissingFile_ReturnsEmptySettings() - { - var context = new ProcessContext(installRootDir: "c:\\aaa\\bbb", batchFileExists: false); - - var actual = await context.TestSubject.GetAsync("any"); - actual.Should().BeNull(); - - context.FileSystem.Verify(x => x.File.Exists("c:\\aaa\\bbb\\Common7\\Tools\\VsDevCmd.bat"), Times.Once); - } - - [TestMethod] - [DataRow("input-script-params")] - [DataRow(null)] - public async Task Get_ExpectedCommandsPassed(string scriptParams) - { - var context = new ProcessContext(installRootDir: "d:\\myinstalldir\\"); - - // Act - await context.TestSubject.GetAsync(scriptParams); - - context.ActualProcessStartInfo.Should().NotBeNull(); - context.ActualProcessStartInfo.FileName.Should().Be(Environment.GetEnvironmentVariable("COMSPEC")); - - context.ActualProcessStartInfo.Arguments.Should().NotBeNull(); - context.TestSubject.UniqueId.Should().NotBeNullOrEmpty(); - - // Split and clean up the command string to make the individual commands easier to check - var args = context.ActualProcessStartInfo.Arguments.Split(new string[] { " && " }, StringSplitOptions.None) - .Select(x => x.Trim()) - .ToArray(); - - args[0].Should().Be("/U /K set VSCMD_SKIP_SENDTELEMETRY=1"); // args to cmd.exe and first command in the sequence - args[1].Should().Be($@"""d:\myinstalldir\Common7\Tools\VsDevCmd.bat"" {scriptParams}".TrimEnd()); // calculated full path to VsDevCmd.bat with additional params - args[2].Should().Be("echo SONARLINT_BEGIN_CAPTURE " + context.TestSubject.UniqueId); - args[3].Should().Be("set"); - args[4].Should().StartWith("echo SONARLINT_END_CAPTURE " + context.TestSubject.UniqueId); - } - - [TestMethod] - public async Task Get_ExpectedTimeoutDurationUsed() - { - var context = new ProcessContext(); - - // Act - await context.TestSubject.GetAsync("any"); - - context.Process.Verify(x => x.WaitForExitAsync(30000), Times.Once); - } - - [TestMethod] - public async Task Get_Lifecycle_RunsToCompletion() - { - var context = new ProcessContext(hasExited: true); - - // Act - await context.TestSubject.GetAsync("any"); - - // Just checking the invocation order - var invokedMembers = context.Process.Invocations.Select(x => x.Method.Name).ToArray(); - invokedMembers.Should().ContainInOrder( - // Initialize and run - "set_HandleOutputDataReceived", - "BeginOutputReadLine", - "WaitForExitAsync", - - // Cleanup - "get_HasExited", - "Dispose" - ); - - invokedMembers.Should().NotContain("Kill"); // should have terminated normally - } - - [TestMethod] - public async Task Get_Lifecycle_TimeoutOccurs_ProcessIsKilled() - { - var context = new ProcessContext(hasExited: false); - - // Act - await context.TestSubject.GetAsync("any"); - - var invokedMembers = context.Process.Invocations.Select(x => x.Method.Name).ToArray(); - invokedMembers.Should().ContainInOrder( - "Kill", - "Dispose" - ); - } - - [TestMethod] - public async Task Get_DataProcessing_DataOutsideMarkersIsIgnored() - { - ProcessContext.SimulateWorkInProcess writeOutputOp = (context) => - { - context.WriteToOutput("before capture -> should be ignored"); - context.WriteToOutput("before_capture=ignore"); - - context.WriteBeginCapture(); - context.WriteToOutput("key1=value1"); - context.WriteToOutput("not an env setting -> should be ignored"); - context.WriteToOutput("key2=value with spaces"); - context.WriteToOutput("anther not an env setting -> should be ignored"); - context.WriteEndCapture(); - - context.WriteToOutput("after capture -> should be ignored"); - context.WriteToOutput("after_capture=ignore"); - - return true; // hasExited - }; - - var context = new ProcessContext(simulatedWorkCallback: writeOutputOp); - - // Act - var actual = await context.TestSubject.GetAsync("any"); - - actual.Should().NotBeNull(); - actual.Count.Should().Be(2); - actual.Keys.Should().BeEquivalentTo("key1", "key2"); - actual["key1"].Should().Be("value1"); - actual["key2"].Should().Be("value with spaces"); - } - - [TestMethod] - public async Task Get_DataProcessing_TimeoutOccurs_NullReturned() - { - ProcessContext.SimulateWorkInProcess writeOutputOp = (context) => - { - context.WriteBeginCapture(); - context.WriteToOutput("key1=value1"); - context.WriteToOutput("key2=value2"); - - return false; // hasExited = false i.e. timeout - }; - - var context = new ProcessContext(simulatedWorkCallback: writeOutputOp); - - // Act - var actual = await context.TestSubject.GetAsync("any"); - - actual.Should().BeNull(); - context.Logger.AssertPartialOutputStringExists(Resources.VsDevCmd_TimedOut); - } - - [TestMethod] - public async Task Get_DataProcessing_NoSettingsCaptured_NullReturned() - { - ProcessContext.SimulateWorkInProcess writeOutputOp = (context) => - { - context.WriteBeginCapture(); - context.WriteToOutput("not a valid setting"); - context.WriteEndCapture(); - return true; // hasExited = true i.e. completed successfully - }; - - var context = new ProcessContext(simulatedWorkCallback: writeOutputOp); - - // Act - var actual = await context.TestSubject.GetAsync("any"); - - actual.Should().BeNull(); - context.Logger.AssertPartialOutputStringExists(Resources.VsDevCmd_NoSettingsFound); - } - - [TestMethod] - public async Task Get_NonCriticalException_IsSuppressed() - { - var context = new ProcessContext(); - - context.FileSystem.Reset(); - context.FileSystem.Setup(x => x.File.Exists(It.IsAny())) - .Throws(new InvalidCastException("thrown from test")); - - var actual = await context.TestSubject.GetAsync("any"); - - actual.Should().BeNull(); - } - - [TestMethod] - public void Get_CriticalException_IsNotSuppressed() - { - var context = new ProcessContext(); - - context.FileSystem.Reset(); - context.FileSystem.Setup(x => x.File.Exists(It.IsAny())) - .Throws(new StackOverflowException("thrown from test")); - - Func act = () => context.TestSubject.GetAsync("any"); - - act.Should().ThrowExactly().And.Message.Should().Be("thrown from test"); - } - - [TestMethod] - public async Task Get_ThrowsIfOnUIThread() - { - var context = new ProcessContext(); - - await context.TestSubject.GetAsync(null); - - context.ThreadHandling.Verify(x => x.ThrowIfOnUIThread(), Times.Once); - } - - private class ProcessContext - { - /// - /// Simulates work done in the process. The return value sets the status of - /// Process.HasExited to indicate whether the process is still running or not. - /// - /// The WriteXXX methods can then be used to send output data to the test subject - public delegate bool SimulateWorkInProcess(ProcessContext context); - - private readonly SimulateWorkInProcess simulatedWorkCallback; - - // Synchronisation objects used to control execution on a separate thread to simulate - // work being done in the process - private ManualResetEvent beginReadOutputCalled = new ManualResetEvent(false); - private ManualResetEvent simulatedWorkCompleted = new ManualResetEvent(false); - - public ProcessContext(string installRootDir = "c:\\any", - SimulateWorkInProcess simulatedWorkCallback = null, - bool hasExited = true, // assume the process exits succesfully i.e. no timeout - bool batchFileExists = true - ) - { - // Set up the basic mocks and properties - Process = new Mock(); - - ProcessFactory = new Mock(); - ProcessFactory.Setup(x => x.Start(It.IsAny())) - .Callback(x => ActualProcessStartInfo = x) - .Returns(Process.Object); - - Logger = new TestLogger(logToConsole: true, logThreadId: true); - - SetHasExitedValue(hasExited); - - var batchFilePath = Path.Combine(installRootDir, "Common7", "Tools", "VsDevCmd.bat"); - FileSystem = new Mock(); - FileSystem.Setup(x => x.File.Exists(batchFilePath)).Returns(batchFileExists); - - // Don't set up the background thread / sync objects unless there is - // actually simulated work to do. - if(simulatedWorkCallback != null) - { - this.simulatedWorkCallback = simulatedWorkCallback; - SetupSimulatedProcessThread(); - } - - ThreadHandling = new Mock(); - - // Create the test subject - TestSubject = new VsDevCmdEnvironmentVarsProvider(CreateVsInfoService(installRootDir), ThreadHandling.Object, Logger, - ProcessFactory.Object, FileSystem.Object); - } - - public VsDevCmdEnvironmentVarsProvider TestSubject { get; } - - public Mock Process { get; } - - public Mock ProcessFactory { get; } - - public Mock FileSystem { get; } - - public Mock ThreadHandling { get; } - - public TestLogger Logger { get; } - - /// - /// Process start info instance created by the test subject - /// - public ProcessStartInfo ActualProcessStartInfo { get; private set; } - - public void WriteToOutput(string message) - { - Action handler = Process.Object.HandleOutputDataReceived; - handler?.Invoke(message); - } - - public void WriteBeginCapture() => - WriteToOutput("SONARLINT_BEGIN_CAPTURE " + TestSubject.UniqueId); - - public void WriteEndCapture() => - WriteToOutput("SONARLINT_END_CAPTURE " + TestSubject.UniqueId); - - private static IVsInfoService CreateVsInfoService(string installRootDir) - { - var infoService = new Mock(); - infoService.SetupGet(x => x.InstallRootDir).Returns(installRootDir); - return infoService.Object; - } - - private void SetHasExitedValue(bool value) => - Process.SetupGet(x => x.HasExited).Returns(value); - - private void SetupSimulatedProcessThread() - { - Process.SetupProperty(x => x.HandleOutputDataReceived); - - // Block the process from completing until the callback has completed - Process.Setup(x => x.WaitForExitAsync(It.IsAny())) - .Callback(() => simulatedWorkCompleted.WaitOne()); - - // Start a new thread to simulate work inside the process - Task.Run(() => CallbackToTestOnSeparateThread()); - - // Unblock the processing thread when the consumer calls BeginOutputReadLine - Process.Setup(x => x.BeginOutputReadLine()) - .Callback(() => beginReadOutputCalled.Set()); - } - - private void CallbackToTestOnSeparateThread() - { - // Don't start the simulated workload until the consumer calls "BeginOutputReadLine". - LogMessage("Waiting for BeginOutputReadLine to be called..."); - beginReadOutputCalled.WaitOne(); - LogMessage("BeginOutputReadLine has been be called. Starting to write output..."); - - bool hasExited = simulatedWorkCallback.Invoke(this); // call back to the test - SetHasExitedValue(hasExited); - - // Signal that we are done to unblock Process.WaitForExit - LogMessage("Simulated work complete"); - simulatedWorkCompleted.Set(); - - void LogMessage(string text) - { - Console.WriteLine($"[Simulated process thread: {Thread.CurrentThread.ManagedThreadId}] {text}"); - } - } - } - } -} diff --git a/src/CFamily.UnitTests/CompilationDatabase/CompilationDatabaseRequestTests.cs b/src/CFamily.UnitTests/CompilationDatabase/CompilationDatabaseRequestTests.cs deleted file mode 100644 index 72b0599776..0000000000 --- a/src/CFamily.UnitTests/CompilationDatabase/CompilationDatabaseRequestTests.cs +++ /dev/null @@ -1,396 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.CMake; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; - -namespace SonarLint.VisualStudio.CFamily.CompilationDatabase.UnitTests -{ - [TestClass] - public class CompilationDatabaseRequestTests - { - private readonly RequestContext ValidContext = new RequestContext("cpp", Mock.Of(), "file.txt", "pchFile.txt", null, false); - private readonly CompilationDatabaseEntry ValidDbEntry = new CompilationDatabaseEntry { File = "file.txt", Directory = "c:\\", Command = "a command" }; - private readonly IReadOnlyDictionary ValidEnvVars = new Dictionary { { "key1", "value1" } }; - - [TestMethod] - public void Ctor_InvalidArguments_Throws() - { - Action act = () => new CompilationDatabaseRequest(null, ValidContext, ValidEnvVars); - act.Should().ThrowExactly().And.ParamName.Should().Be("databaseEntry"); - - act = () => new CompilationDatabaseRequest(ValidDbEntry, null, ValidEnvVars); - act.Should().ThrowExactly().And.ParamName.Should().Be("context"); - } - - [TestMethod] - public void Ctor_NullEnvVars_DoesNotThrow() - { - var testSubject = new CompilationDatabaseRequest(ValidDbEntry, ValidContext, null); - testSubject.EnvironmentVariables.Should().BeNull(); - } - - [TestMethod] - [DataRow(null, "args")] - [DataRow("", "args")] - [DataRow("cmd", null)] - [DataRow("cmd", "")] - public void Ctor_ValidCommandArgsCombination_ShouldNotThrow(string command, string args) - { - var dbEntry = new CompilationDatabaseEntry - { - File = "file", - Directory = "dir", - Command = command, - Arguments = args - }; - - Action act = () => new CompilationDatabaseRequest(dbEntry, ValidContext, ValidEnvVars); - act.Should().NotThrow(); - } - - [TestMethod] - [DataRow(null, null)] - [DataRow("", "")] - [DataRow(null, "")] - [DataRow("", null)] - [DataRow("command", "args")] // can't have both - public void Ctor_InvalidCommandArgsCombination_ShouldThrow(string command, string args) - { - var dbEntry = new CompilationDatabaseEntry - { - File = "file", - Directory = "dir", - Command = command, - Arguments = args - }; - - Action act = () => new CompilationDatabaseRequest(dbEntry, ValidContext, ValidEnvVars); - act.Should().ThrowExactly(); - } - - [TestMethod] - public void WriteRequest_HeaderFile_WritesTheFileFromContext() - { - var dbEntry = new CompilationDatabaseEntry { File = "file.cpp", Directory = "c:\\aaa", Command = "any" }; - var context = new RequestContext("any", Mock.Of(), "file.h", "d:\\preamble.txt", null, true); - - var tokens = WriteRequest(dbEntry, context); - - // File name should be taken from context, to support header files - CheckExpectedSetting(tokens, "File", "file.h"); - } - - [TestMethod] - public void WriteRequest_HeaderFile_WritesHeaderFileLanguage() - { - var dbEntry = new CompilationDatabaseEntry { File = "file.cpp", Directory = "c:\\aaa", Command = "any" }; - var context = new RequestContext("any.language", Mock.Of(), "file.h", "d:\\preamble.txt", null, true); - - var tokens = WriteRequest(dbEntry, context); - - CheckExpectedSetting(tokens, "HeaderFileLanguage", "any.language"); - } - - [TestMethod] - public void WriteRequest_NotHeaderFile_HeaderFileLanguageIsNotWritten() - { - var dbEntry = new CompilationDatabaseEntry { File = "file.cpp", Directory = "c:\\aaa", Command = "any" }; - var context = new RequestContext("any.language", Mock.Of(), "file.cpp", "d:\\preamble.txt", null, false); - - var tokens = WriteRequest(dbEntry, context); - - CheckSettingDoesNotExist(tokens, "HeaderFileLanguage"); - } - - [TestMethod] - public void WriteRequest_ValidRequest_ExpectedHeaderFooterAndSimpleProperties() - { - var dbEntry = new CompilationDatabaseEntry { File = "file.txt", Directory = "c:\\aaa", Command = "any" }; - var context = new RequestContext("any", Mock.Of(), "file.h", "d:\\preamble.txt", null, true); - - var tokens = WriteRequest(dbEntry, context); - - // Header and footer - tokens.First().Should().Be("SL-IN"); - tokens.Last().Should().Be("SL-END"); - - // Simple properties i.e. ones that are just written as-is - CheckExpectedSetting(tokens, "File", "file.h"); - CheckExpectedSetting(tokens, "Directory", "c:\\aaa"); - CheckExpectedSetting(tokens, "PreambleFile", "d:\\preamble.txt"); - } - - [TestMethod] - public void WriteRequest_WithCommand_ExpectedSettingWritten() - { - var dbEntry = new CompilationDatabaseEntry { File = "file.txt", Directory = "c:\\aaa", Command = "cmd1 cmd2" }; - - var tokens = WriteRequest(dbEntry, ValidContext); - - CheckExpectedSetting(tokens, "Command", "cmd1 cmd2"); - CheckSettingDoesNotExist(tokens, "Arguments"); - } - - [TestMethod] - public void WriteRequest_WithArguments_ExpectedSettingWritten() - { - var dbEntry = new CompilationDatabaseEntry { File = "file.txt", Directory = "c:\\aaa", Arguments = "arg1\narg2" }; - - var tokens = WriteRequest(dbEntry, ValidContext); - - CheckExpectedSetting(tokens, "Arguments", "arg1\narg2"); - CheckSettingDoesNotExist(tokens, "Commands"); - } - - [TestMethod] - [DataRow(true, "true")] - [DataRow(false, "false")] - public void WriteRequest_CreateReproducer_ExpectedSettingWritten(bool createReproducer, string expectedValue) - { - var analyzerOptions = new CFamilyAnalyzerOptions { CreateReproducer = createReproducer }; - var context = CreateContext(analyzerOptions: analyzerOptions); - - var tokens = WriteRequest(ValidDbEntry, context); - - CheckExpectedSetting(tokens, "CreateReproducer", expectedValue); - } - - [TestMethod] - [DataRow(true, "true")] - [DataRow(false, "false")] - public void WriteRequest_CreatePreamble_ExpectedSettingWritten(bool createPch, string expectedValue) - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp") - .AddRule("active1", isActive: true); - - var analyzerOptions = new CFamilyAnalyzerOptions { CreatePreCompiledHeaders = createPch }; - var context = CreateContext(rulesConfig: rulesConfig, analyzerOptions: analyzerOptions); - - var tokens = WriteRequest(ValidDbEntry, context); - - CheckExpectedSetting(tokens, "BuildPreamble", expectedValue); - - if (createPch) - { - CheckExpectedSetting(tokens, "QualityProfile", string.Empty); - } - else - { - CheckExpectedSetting(tokens, "QualityProfile", "active1"); - } - } - - [TestMethod] - public void WriteRequest_QualityProfile_ExpectedSettingWritten() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp") - .AddRule("inactive1", isActive: false) - .AddRule("active1", isActive: true) - .AddRule("active2", isActive: true) - .AddRule("inactive2", isActive: false); - - var context = CreateContext(rulesConfig: rulesConfig); - - var tokens = WriteRequest(ValidDbEntry, context); - - CheckExpectedSetting(tokens, "QualityProfile", "active1,active2"); - } - - [TestMethod] - public void WriteRequest_WithRuleParameters_ExpectedSettingWritten() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp") - .AddRule("inactive1", isActive: false, - parameters: new Dictionary - { - { "XXX1", "VVV1"} - }) - - // Active rule with no parameters - .AddRule("active1", isActive: true) - - // Active rule, one parameter - .AddRule("active2", isActive: true, - parameters: new Dictionary - { - { "key2_1", "value2_1" } - }) - - .AddRule("inactive2", isActive: false, - parameters: new Dictionary - { - { "XXX2", "VVV2"}, - { "XXX3", "VVV3"} - }) - - // Active rule, multiple parameters - .AddRule("active3", isActive: true, - parameters: new Dictionary - { - { "key3_1", "value3_1" }, - { "key3_2", "value3_2" } - }); - - var context = CreateContext(rulesConfig: rulesConfig); - - var tokens = WriteRequest(ValidDbEntry, context); - - CheckExpectedSetting(tokens, "active2.key2_1", "value2_1"); - CheckExpectedSetting(tokens, "active3.key3_1", "value3_1"); - CheckExpectedSetting(tokens, "active3.key3_2", "value3_2"); - - CheckSettingDoesNotExist(tokens, "inactive1"); - CheckSettingDoesNotExist(tokens, "inactive2"); - CheckSettingDoesNotExist(tokens, "active1"); - CheckSettingDoesNotExist(tokens, "active4"); - - } - - [TestMethod] - public void WriteRequest_NoRuleParameters_NoErrors() - { - // Active rules with no parameters - var rulesConfig = new DummyCFamilyRulesConfig("cpp") - .AddRule("active1", isActive: true) - .AddRule("active2", isActive: true); - - var context = CreateContext(rulesConfig: rulesConfig); - - var tokens = WriteRequest(ValidDbEntry, context); - - tokens.Where(x => x.StartsWith("active1.")).Should().BeEmpty(); - tokens.Where(x => x.StartsWith("active2.")).Should().BeEmpty(); - } - - [TestMethod] - public void WriteDiagnostics_ExpectedDataWritten() - { - // Expecting the context to be ignored - var context = CreateContext("foo", Mock.Of(), "some file", "some pch file", - new CFamilyAnalyzerOptions { CreatePreCompiledHeaders = true }); - - var dbEntry = new CompilationDatabaseEntry - { - File = "c:\\file.txt", Directory = "d:\\", Command = "1\n2\n" - }; - var expected = @"{ - ""directory"": ""d:\\"", - ""command"": ""1\n2\n"", - ""file"": ""c:\\file.txt"", - ""arguments"": null -}"; - - var testSubject = new CompilationDatabaseRequest(dbEntry, context, ValidEnvVars); - - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb)) - { - testSubject.WriteRequestDiagnostics(writer); - }; - - var actual = sb.ToString(); - actual.Should().Be(expected); - } - - [TestMethod] - public void EnvironmentVariables_ReturnsExpectedValues() - { - var envVars = new Dictionary { { "INCLUDE", "" }, { "PATH", "any"} }; - var testSubject = new CompilationDatabaseRequest(ValidDbEntry, ValidContext, envVars); - - var actual = testSubject.EnvironmentVariables; - - actual.Count.Should().Be(2); - actual.Keys.Should().BeEquivalentTo("INCLUDE", "PATH"); - actual["INCLUDE"].Should().BeEmpty(); - actual["PATH"].Should().Be("any"); - } - - private static RequestContext CreateContext( - string language = "c", - ICFamilyRulesConfig rulesConfig = null, - string file = "file.txt", - string pchFile = "pch.txt", - CFamilyAnalyzerOptions analyzerOptions = null) - { - return new RequestContext( - language: language, - rulesConfig: rulesConfig ?? Mock.Of(), - file: file, - pchFile: pchFile, - analyzerOptions: analyzerOptions, false); - } - - /// - /// Executes the request, and returns the ordered list of strings that were - /// written to the binary stream - /// - private IList WriteRequest(CompilationDatabaseEntry dbEntry, RequestContext context) - { - var tokens = new List(); - - var testSubject = new CompilationDatabaseRequest(dbEntry, context, ValidEnvVars); - - using (var stream = new MemoryStream()) - { - using (var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true)) - { - testSubject.WriteRequest(writer); - } - stream.Flush(); - var endOfStreamPosition = stream.Position; - stream.Position = 0; - - using (var reader = new BinaryReader(stream)) - { - while(stream.Position != endOfStreamPosition) - { - tokens.Add(Protocol.ReadUTF(reader)); - } - } - } - - return tokens; - } - - private void CheckExpectedSetting(IList tokens, string key, string value) - { - var keyIndex = tokens.IndexOf(key); - keyIndex.Should().NotBe(-1); - keyIndex.Should().NotBe(tokens.Count - 2); - tokens[keyIndex + 1].Should().Be(value); - } - - private void CheckSettingDoesNotExist(IList tokens, string key) => - tokens.IndexOf(key).Should().Be(-1); - } -} diff --git a/src/CFamily.UnitTests/CompilationDatabase/RulesConfigProtocolFormatterTests.cs b/src/CFamily.UnitTests/CompilationDatabase/RulesConfigProtocolFormatterTests.cs deleted file mode 100644 index 8d237329fd..0000000000 --- a/src/CFamily.UnitTests/CompilationDatabase/RulesConfigProtocolFormatterTests.cs +++ /dev/null @@ -1,141 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; - -namespace SonarLint.VisualStudio.CFamily.CompilationDatabase.UnitTests -{ - [TestClass] - public class RulesConfigProtocolFormatterTests - { - [TestMethod] - public void Format_NullRulesConfig_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Format(null); - - act.Should().Throw().And.ParamName.Should().Be("rulesConfig"); - } - - [TestMethod] - public void Format_NoRules_EmptyQualityProfile() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp"); - - var testSubject = CreateTestSubject(); - var result = testSubject.Format(rulesConfig); - - result.QualityProfile.Should().BeEmpty(); - } - - [TestMethod] - public void Format_NoActiveRules_EmptyQualityProfile() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp"); - rulesConfig.AddRule("123", false); - - var testSubject = CreateTestSubject(); - var result = testSubject.Format(rulesConfig); - - result.QualityProfile.Should().BeEmpty(); - } - - [TestMethod] - public void Format_OneActiveRule_OneRuleInQualityProfile() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp"); - rulesConfig.AddRule("123", true); - - var testSubject = CreateTestSubject(); - var result = testSubject.Format(rulesConfig); - - result.QualityProfile.Should().Be("123"); - } - - [TestMethod] - public void Format_MultipleActiveRules_CommaSeparatedQualityProfile() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp"); - rulesConfig.AddRule("12", true); - rulesConfig.AddRule("34", false); - rulesConfig.AddRule("56", true); - rulesConfig.AddRule("78", false); - - var testSubject = CreateTestSubject(); - var result = testSubject.Format(rulesConfig); - - result.QualityProfile.Should().Be("12,56"); - } - - [TestMethod] - public void Format_NoRules_EmptyRuleParameters() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp"); - - var testSubject = CreateTestSubject(); - var result = testSubject.Format(rulesConfig); - - result.RuleParameters.Should().BeEmpty(); - } - - [TestMethod] - public void Format_MultipleRules_DotSeparatedParametersForActiveRules() - { - var rulesConfig = new DummyCFamilyRulesConfig("cpp"); - - rulesConfig.AddRule("rule1", true, new Dictionary - { - {"param1", "some value"}, - {"param2", "some other value"} - }); - - // inactive rules should be ignored - rulesConfig.AddRule("inactive", false, new Dictionary - { - {"param3", "value3"}, - {"param4", "value4"} - }); - - rulesConfig.AddRule("rule2", true, new Dictionary - { - {"some param", "value1"}, - {"some other param", "value2"} - }); - - var testSubject = CreateTestSubject(); - var result = testSubject.Format(rulesConfig); - - result.RuleParameters.Should().BeEquivalentTo(new Dictionary - { - {"rule1.param1", "some value"}, - {"rule1.param2", "some other value"}, - {"rule2.some param", "value1"}, - {"rule2.some other param", "value2"} - }); - } - - private RulesConfigProtocolFormatter CreateTestSubject() => new RulesConfigProtocolFormatter(); - } -} diff --git a/src/CFamily.UnitTests/Helpers/DummyCFamilyRulesConfig.cs b/src/CFamily.UnitTests/Helpers/DummyCFamilyRulesConfig.cs deleted file mode 100644 index d5d9824f9e..0000000000 --- a/src/CFamily.UnitTests/Helpers/DummyCFamilyRulesConfig.cs +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Linq; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.CFamily.Helpers.UnitTests -{ - public class DummyCFamilyRulesConfig : ICFamilyRulesConfig - { - private readonly IDictionary ruleKeyToActiveMap; - - public DummyCFamilyRulesConfig(string languageKey) - { - LanguageKey = languageKey; - ruleKeyToActiveMap = new Dictionary(); - } - - public DummyCFamilyRulesConfig AddRule(string partialRuleKey, IssueSeverity issueSeverity, bool isActive, Code code) - { - return AddRule(partialRuleKey, issueSeverity, isActive, null, code); - } - - public DummyCFamilyRulesConfig AddRule(string partialRuleKey, bool isActive) - { - ruleKeyToActiveMap[partialRuleKey] = isActive; - RulesMetadata[partialRuleKey] = new RuleMetadata(); - return this; - } - - public DummyCFamilyRulesConfig AddRule(string partialRuleKey, bool isActive, Dictionary parameters) - { - return AddRule(partialRuleKey, (IssueSeverity)0 /* default enum value */, isActive, parameters, new Code()); - } - - public DummyCFamilyRulesConfig AddRule(string partialRuleKey, IssueSeverity issueSeverity, bool isActive, Dictionary parameters, Code code) - { - ruleKeyToActiveMap[partialRuleKey] = isActive; - RulesMetadata[partialRuleKey] = new RuleMetadata { DefaultSeverity = issueSeverity, Code = code }; - - if (parameters != null) - { - RulesParameters[partialRuleKey] = parameters; - } - return this; - } - - #region IRulesConfiguration interface - - public string LanguageKey { get; set; } - - public IEnumerable AllPartialRuleKeys => ruleKeyToActiveMap.Keys; - - public IEnumerable ActivePartialRuleKeys => ruleKeyToActiveMap.Where(kvp => kvp.Value) - .Select(kvp => kvp.Key) - .ToList(); - - public IDictionary> RulesParameters { get; set; } - = new Dictionary>(); - - public IDictionary RulesMetadata { get; set; } - = new Dictionary(); - - #endregion - } -} diff --git a/src/CFamily.UnitTests/Helpers/DummyExeHelper.cs b/src/CFamily.UnitTests/Helpers/DummyExeHelper.cs deleted file mode 100644 index b07c69f864..0000000000 --- a/src/CFamily.UnitTests/Helpers/DummyExeHelper.cs +++ /dev/null @@ -1,165 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.CodeDom.Compiler; -using System.IO; -using FluentAssertions; -using Microsoft.CSharp; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -// Note: copied from the S4MSB -// https://github.com/SonarSource/sonar-scanner-msbuild/blob/5c23a7da9171e90a1970a31507dce3da3e8ee094/Tests/TestUtilities/DummyExeHelper.cs#L34 - -namespace SonarLint.VisualStudio.CFamily.Helpers.UnitTests -{ - /// - /// Creates dummy executables that log the input parameters and return a specified - /// exit code - /// - internal static class DummyExeHelper - { - private const string DummyExeName = "dummy.exe"; - - #region Public methods - - public static string CreateDummyExe(string outputDir, string exeName, int exitCode) - { - return CreateDummyExe(outputDir, exeName, exitCode, null); - } - - public static string CreateDummyExe(string outputDir, string exeName, int exitCode, string additionalCode) - { - var code = GetDummyExeSource(exitCode, additionalCode); - var asmPath = Path.Combine(outputDir, exeName); - CompileAssembly(code, asmPath); - return asmPath; - } - - public static string CreateDummyExe(string outputDir, int exitCode) - { - return CreateDummyExe(outputDir, DummyExeName, exitCode, null); - } - - #endregion Public methods - - #region Checks - - public static string AssertDummyExeLogExists(string dummyBinDir, TestContext testContext) - { - var logFilePath = GetLogFilePath(dummyBinDir, DummyExeName); - return AssertLogFileExists(logFilePath, testContext); - } - - - public static string GetLogFilePath(string dummyBinDir, string exeName) - { - var logFilePath = Path.Combine(dummyBinDir, exeName); - logFilePath = Path.ChangeExtension(logFilePath, ".log"); - return logFilePath; - } - - public static void AssertExpectedLogContents(string logPath, params string[] expected) - { - File.Exists(logPath).Should().BeTrue("Expected log file does not exist: {0}", logPath); - - var actualLines = File.ReadAllLines(logPath); - - (expected ?? new string[] { }).Should().BeEquivalentTo(actualLines, "Log file does not have the expected content"); - } - - public static string AssertLogFileExists(string logFilePath, TestContext testContext) - { - File.Exists(logFilePath).Should().BeTrue("Expecting the dummy exe log to exist. File: {0}", logFilePath); - testContext.AddResultFile(logFilePath); - return logFilePath; - } - - public static string AssertLogFileDoesNotExist(string dummyBinDir, string exeName) - { - var logFilePath = GetLogFilePath(dummyBinDir, exeName); - - File.Exists(logFilePath).Should().BeFalse("Not expecting the dummy exe log to exist. File: {0}", logFilePath); - return logFilePath; - } - - #endregion Checks - - #region Private methods - - private static string GetDummyExeSource(int returnCode, string additionalCode) - { - string code = @" -using System; -using System.IO; - -namespace SonarQube.Bootstrapper.Tests.Dummy -{ - class Program - { - static int Main(string[] args) - { - string logFile = Path.ChangeExtension(Path.Combine(typeof(Program).Assembly.Location), ""log""); - - File.WriteAllLines(logFile, args); - - int exitCode = EXITCODE_PLACEHOLDER; - - ADDITIONALCODE_PLACEHOLDER - - return exitCode; - } - } -}"; - code = code.Replace("EXITCODE_PLACEHOLDER", returnCode.ToString()); - code = code.Replace("ADDITIONALCODE_PLACEHOLDER", additionalCode); - return code; - } - - /// - /// Compiles the supplied code into a new assembly - /// - private static void CompileAssembly(string code, string outputFilePath) - { - var provider = new CSharpCodeProvider(); - - var options = new CompilerParameters - { - OutputAssembly = outputFilePath, - GenerateExecutable = true, - GenerateInMemory = false - }; - - var result = provider.CompileAssemblyFromSource(options, code); - - if (result.Errors.Count > 0) - { - foreach(var item in result.Output) - { - Console.WriteLine(item); - } - - Assert.Fail("Test setup error: failed to create dynamic assembly. See the test output for compiler output"); - } - } - - #endregion Private methods - } -} diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile.txt b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile.txt deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile_response.json b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile_response.json deleted file mode 100644 index 4643e632d3..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_NoIssues_EmptyFile_response.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Messages": [] -} \ No newline at end of file diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue.txt b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue.txt deleted file mode 100644 index 48340c4e67..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue.txt +++ /dev/null @@ -1 +0,0 @@ -// todo: this line should raise an issue diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations.txt b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations.txt deleted file mode 100644 index d34294e6b0..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations.txt +++ /dev/null @@ -1,6 +0,0 @@ -void a(bool b) { - int* f = nullptr; - if (b) { - *f = 2; - } -} \ No newline at end of file diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations_response.json b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations_response.json deleted file mode 100644 index 069067e5e5..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_HasSecondaryLocations_response.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "Messages": [ - { - "Filename": "", - "Fixes": [], - "RuleKey": "S2259", - "PartsMakeFlow": true, - "Parts": [ - { - "Filename": "", - "Line": 4, - "Column": 12, - "EndLine": 4, - "EndColumn": 13, - "Text": "Dereference of null pointer (loaded from variable 'f')" - }, - { - "Filename": "", - "Line": 3, - "Column": 5, - "EndLine": 3, - "EndColumn": 7, - "Text": "Taking true branch" - }, - { - "Filename": "", - "Line": 3, - "Column": 9, - "EndLine": 3, - "EndColumn": 10, - "Text": "Assuming 'b' is true" - }, - { - "Filename": "", - "Line": 2, - "Column": 5, - "EndLine": 2, - "EndColumn": 11, - "Text": "'f' initialized to a null pointer value" - } - ], - "Line": 4, - "Column": 12, - "EndLine": 4, - "EndColumn": 13, - "Text": "Dereference of null pointer (loaded from variable 'f')" - } - ] -} \ No newline at end of file diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_response.json b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_response.json deleted file mode 100644 index 135b1bf28b..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_OneIssue_response.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Messages": [ - { - "Filename": "", - "Fixes": [], - "RuleKey": "S1135", - "PartsMakeFlow": false, - "Parts": [], - "Line": 1, - "Column": 4, - "EndLine": 1, - "EndColumn": 44, - "Text": "Complete the task associated to this \"todo\" comment." - } - ] -} \ No newline at end of file diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues.txt b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues.txt deleted file mode 100644 index 96fce12726..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues.txt +++ /dev/null @@ -1,2 +0,0 @@ -// todo: this line should raise an issue - // todo: this other line should also raise an issue diff --git a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues_response.json b/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues_response.json deleted file mode 100644 index e19277393f..0000000000 --- a/src/CFamily.UnitTests/IntegrationTests/CLangAnalyzerTestFile_TwoIssues_response.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "Messages": [ - { - "Filename": "", - "Fixes": [], - "RuleKey": "S1135", - "PartsMakeFlow": false, - "Parts": [], - "Line": 1, - "Column": 4, - "EndLine": 1, - "EndColumn": 44, - "Text": "Complete the task associated to this \"todo\" comment." - }, - { - "Filename": "", - "Fixes": [], - "RuleKey": "S1135", - "PartsMakeFlow": false, - "Parts": [], - "Line": 2, - "Column": 4, - "EndLine": 2, - "EndColumn": 55, - "Text": "Complete the task associated to this \"todo\" comment." - } - ] -} \ No newline at end of file diff --git a/src/CFamily.UnitTests/Rules/CFamilyRuleConfigProviderTests.cs b/src/CFamily.UnitTests/Rules/CFamilyRuleConfigProviderTests.cs deleted file mode 100644 index a42895955d..0000000000 --- a/src/CFamily.UnitTests/Rules/CFamilyRuleConfigProviderTests.cs +++ /dev/null @@ -1,133 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.Rules.UnitTests -{ - [TestClass] - public class CFamilyRuleConfigProviderTests - { - [TestMethod] - public void MefCtor_CheckExports() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void Get_NullLanguage_ArgumentNullException() - { - var testSubject = CreateTestSubject(new RulesSettings(), new DummyCFamilyRulesConfig("cpp")); - - Action act = () => testSubject.GetRulesConfiguration(null); - - act.Should().Throw().And.ParamName.Should().Be("languageKey"); - } - - [TestMethod] - public void Get_UnknownLanguageKey_ArgumentNullException() - { - var testSubject = CreateTestSubject(new RulesSettings(), new DummyCFamilyRulesConfig("cpp")); - - Action act = () => testSubject.GetRulesConfiguration("sfdsfdggretert"); - - act.Should().Throw().And.ParamName.Should().Be("language"); - } - - [DataRow(true)] - [DataRow(false)] - [DataTestMethod] - public void Get_EffectiveRulesAreCalculatedDependingOnHotspotConfiguration(bool hotspotsEnabled) - { - var fullHotspotRuleKey = RulesConfigFixup.HotspotRulesKeys[0]; - var hotspotRuleKey = fullHotspotRuleKey.Split(':')[1]; - var standaloneModeSettings = new RulesSettings - { - Rules = new Dictionary - { - {"cpp:rule1", new RuleConfig {Level = RuleLevel.On}}, - {"cpp:rule2", new RuleConfig {Level = RuleLevel.Off}}, - {"cpp:rule4", new RuleConfig {Level = RuleLevel.On}}, - {"XXX:rule3", new RuleConfig {Level = RuleLevel.On}}, - {fullHotspotRuleKey, new RuleConfig {Level = RuleLevel.On}}, - } - }; - - var sonarWayConfig = new DummyCFamilyRulesConfig("cpp") - .AddRule("rule1", IssueSeverity.Info, isActive: false, code: null) - .AddRule("rule2", IssueSeverity.Major, isActive: false, code: null) - .AddRule("rule3", IssueSeverity.Minor, isActive: true, code: null) - .AddRule("rule4", IssueSeverity.Blocker, isActive: false, code: null) - .AddRule(hotspotRuleKey, IssueSeverity.Blocker, isActive: true, code: null); - - var testSubject = CreateTestSubject(standaloneModeSettings, sonarWayConfig, hotspotsEnabled); - - // Act - var result = testSubject.GetRulesConfiguration("cpp"); - - // Assert - var activeKeys = new List { "rule1", "rule3", "rule4" }; - - if (hotspotsEnabled) - { - activeKeys.Add(hotspotRuleKey); - } - - result.ActivePartialRuleKeys.Should().BeEquivalentTo(activeKeys); - result.AllPartialRuleKeys.Should().BeEquivalentTo("rule1", "rule2", "rule3", "rule4", hotspotRuleKey); - } - - private CFamilyRuleConfigProvider CreateTestSubject(RulesSettings ruleSettings, DummyCFamilyRulesConfig sonarWayConfig, bool enableHotspots = false) - { - var ruleSettingsProvider = new Mock(); - ruleSettingsProvider.Setup(x => x.Get()).Returns(ruleSettings); - - var ruleSettingsProviderFactory = new Mock(); - ruleSettingsProviderFactory.Setup(x => x.Get(Language.Cpp)).Returns(ruleSettingsProvider.Object); - - var sonarWayProviderMock = new Mock(); - - sonarWayProviderMock.Setup(x => x.GetRulesConfiguration(It.IsAny())) - .Returns(sonarWayConfig); - - var hotspotAnalysisConfigurationMock = new Mock(); - hotspotAnalysisConfigurationMock.Setup(x => x.IsHotspotsAnalysisEnabled()).Returns(enableHotspots); - - var testSubject = new CFamilyRuleConfigProvider(ruleSettingsProviderFactory.Object, - sonarWayProviderMock.Object, - hotspotAnalysisConfigurationMock.Object, - Mock.Of()); - - return testSubject; - } - } -} diff --git a/src/CFamily.UnitTests/Rules/CFamilySonarWayRulesConfigProviderTests.cs b/src/CFamily.UnitTests/Rules/CFamilySonarWayRulesConfigProviderTests.cs deleted file mode 100644 index 3aec358a71..0000000000 --- a/src/CFamily.UnitTests/Rules/CFamilySonarWayRulesConfigProviderTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO; -using FluentAssertions; -using FluentAssertions.Execution; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.CFamily.Rules.UnitTests -{ - [TestClass] - public class CFamilySonarWayRulesConfigProviderTests - { - // Rule data for files in Rules\TestResources\RulesMetadataCache - private const int Active_C_Rules = 3; - private const int Inactive_C_Rules = 3; - private const int Active_Misra_C_Rules = 1; - private const int Inactive_Misra_C_Rules = 1; - - private const int Active_CPP_Rules = 4; - private const int Inactive_CPP_Rules = 2; - private const int Active_Misra_Cpp_Rules = 1; - private const int Inactive_Misra_Cpp_Rules = 1; - - private CFamilySonarWayRulesConfigProvider sonarWayProvider = CreateTestSubject(); - - [TestMethod] - public void Settings_LanguageKey() - { - sonarWayProvider.GetRulesConfiguration("c").LanguageKey.Should().Be("c"); - sonarWayProvider.GetRulesConfiguration("cpp").LanguageKey.Should().Be("cpp"); - - // We don't currently support ObjC rules in VS - sonarWayProvider.GetRulesConfiguration("objc").Should().BeNull(); - } - - [TestMethod] - public void Read_Rules() - { - sonarWayProvider.GetRulesConfiguration("c").AllPartialRuleKeys.Should().HaveCount(Active_C_Rules + Inactive_C_Rules + Active_Misra_C_Rules + Inactive_Misra_C_Rules); - sonarWayProvider.GetRulesConfiguration("cpp").AllPartialRuleKeys.Should().HaveCount(Active_CPP_Rules + Inactive_CPP_Rules + Active_Misra_Cpp_Rules + Inactive_Misra_Cpp_Rules); - - // We don't currently support ObjC rules in VS - sonarWayProvider.GetRulesConfiguration("objc").Should().BeNull(); - } - - [TestMethod] - public void Read_Active_Rules() - { - sonarWayProvider.GetRulesConfiguration("c").ActivePartialRuleKeys.Should().HaveCount(Active_C_Rules + Active_Misra_C_Rules); - sonarWayProvider.GetRulesConfiguration("cpp").ActivePartialRuleKeys.Should().HaveCount(Active_CPP_Rules + Active_Misra_Cpp_Rules); - - // We don't currently support ObjC rules in VS - sonarWayProvider.GetRulesConfiguration("objc").Should().BeNull(); - } - - [TestMethod] - public void Read_Rules_Params() - { - sonarWayProvider.GetRulesConfiguration("cpp").RulesParameters.TryGetValue("All_ActiveWithParams_1", out var parameters); - parameters.Should() - .Contain(new System.Collections.Generic.KeyValuePair("maximumClassComplexityThreshold", "80")); - } - - [TestMethod] - public void Read_Rules_Metadata() - { - sonarWayProvider.GetRulesConfiguration("cpp").RulesMetadata.TryGetValue("All_ActiveWithParams_1", out var metadata); - using (new AssertionScope()) - { - metadata.Type.Should().Be(IssueType.CodeSmell); - metadata.DefaultSeverity.Should().Be(IssueSeverity.Critical); - } - } - - private static CFamilySonarWayRulesConfigProvider CreateTestSubject() - { - var resourcesPath = Path.Combine( - Path.GetDirectoryName(typeof(CFamilySonarWayRulesConfigProvider).Assembly.Location), - "Rules", "TestResources", "RulesMetadataCache"); - Directory.Exists(resourcesPath).Should().BeTrue($"Test setup error: expected test resources directory does not exist: {resourcesPath}"); - - var testSubject = new CFamilySonarWayRulesConfigProvider(resourcesPath); - return testSubject; - } - - } -} diff --git a/src/CFamily.UnitTests/Rules/DynamicCFamilyRulesConfigTests.cs b/src/CFamily.UnitTests/Rules/DynamicCFamilyRulesConfigTests.cs deleted file mode 100644 index 5fa18fc60a..0000000000 --- a/src/CFamily.UnitTests/Rules/DynamicCFamilyRulesConfigTests.cs +++ /dev/null @@ -1,371 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.Rules.UnitTests -{ - [TestClass] - public class DynamicCFamilyRulesConfigTests - { - [TestMethod] - public void Ctor_NullArguments_Throws() - { - var settings = new RulesSettings(); - - // 1. Default rules config - Action act = () => new DynamicCFamilyRulesConfig(null, - settings, - Mock.Of(), - new TestLogger()); - act.Should().ThrowExactly().And.ParamName.Should().Be("defaultRulesConfig"); - - // 2. Custom settings - act = () => new DynamicCFamilyRulesConfig(new DummyCFamilyRulesConfig("anyLanguage"), - null, - Mock.Of(), - new TestLogger()); - act.Should().ThrowExactly().And.ParamName.Should().Be("customRulesSettings"); - - // 3. Logger - act = () => new DynamicCFamilyRulesConfig(new DummyCFamilyRulesConfig("anyLanguage"), - settings, - Mock.Of(), - null); - act.Should().ThrowExactly().And.ParamName.Should().Be("logger"); - - // 4. Hotspot config - act = () => new DynamicCFamilyRulesConfig(new DummyCFamilyRulesConfig("anyLanguage"), - settings, - null, - new TestLogger()); - act.Should().ThrowExactly().And.ParamName.Should().Be("connectedModeFeaturesConfiguration"); - } - - [TestMethod] - public void NullOrEmptyRulesSettings_DefaultsUsed() - { - // Arrange - var defaultConfig = new DummyCFamilyRulesConfig("123") - .AddRule("rule1", IssueSeverity.Blocker, isActive: true, - parameters: new Dictionary { { "p1", "v1" } }, - code: new Code { Impacts = new Dictionary { { SoftwareQuality.Maintainability, SoftwareQualitySeverity.High } } }) - .AddRule("rule2", IssueSeverity.Major, isActive: true, - parameters: new Dictionary { { "p2", "v2" } }, - code: new Code { Impacts = new Dictionary { { SoftwareQuality.Maintainability, SoftwareQualitySeverity.Medium } } }) - .AddRule("rule3", IssueSeverity.Minor, isActive: false, - parameters: new Dictionary { { "p3", "v3" } }, - code: new Code { Impacts = new Dictionary { { SoftwareQuality.Maintainability, SoftwareQualitySeverity.Low } } }); - - var settings = new RulesSettings(); - - // Act - using (new AssertIgnoreScope()) - { - var testSubject = CreateTestSubject(defaultConfig, settings); - - // Assert - testSubject.AllPartialRuleKeys.Should().BeEquivalentTo("rule1", "rule2", "rule3"); - testSubject.ActivePartialRuleKeys.Should().BeEquivalentTo("rule1", "rule2"); - - testSubject.LanguageKey.Should().Be("123"); - - // Other properties should be pass-throughs - testSubject.AllPartialRuleKeys.Should().BeEquivalentTo(defaultConfig.AllPartialRuleKeys); - testSubject.RulesParameters.Should().BeEquivalentTo(defaultConfig.RulesParameters); - testSubject.RulesMetadata.Should().BeEquivalentTo(defaultConfig.RulesMetadata); - } - } - - [TestMethod] - public void RuleConfigFixup_IsCalled() - { - // Arrange - var defaultConfig = new DummyCFamilyRulesConfig("123") - .AddRule("rule1", isActive: true, null) - .AddRule("rule2", isActive: true, null); - - var inputSettings = new RulesSettings(); - - var hotspotAnalysisConfigurationMock = Mock.Of(); - - // Fixup that should disable rule1 - var fixedUpSettings = new RulesSettings - { - Rules = { { "123:rule1", new RuleConfig { Level = RuleLevel.Off } } } - }; - var fixup = new Mock(); - fixup - .Setup(x => x.Apply(inputSettings, hotspotAnalysisConfigurationMock)) - .Returns(fixedUpSettings); - - // Act - var testSubject = CreateTestSubject(defaultConfig, - inputSettings, - fixup.Object, - hotspotAnalysisConfigurationMock); - - // Assert - fixup.VerifyAll(); - - testSubject.AllPartialRuleKeys.Should().BeEquivalentTo("rule1", "rule2"); - testSubject.ActivePartialRuleKeys.Should().BeEquivalentTo("rule2"); - } - - [TestMethod] - public void ActiveRules_CustomSettingsOverrideDefaults() - { - // Arrange - var defaultConfig = new DummyCFamilyRulesConfig("c") - .AddRule("rule1", isActive: false) - .AddRule("rule2", isActive: true) - .AddRule("rule3", isActive: true); - - var settings = new RulesSettings - { - Rules = new Dictionary - { - // Unknown rules should be ignored - { "x:unknown1", new RuleConfig { Level = RuleLevel.On } }, - - // Turn on a rule that was off (case-insensitive comparison on keys) - { "c:rule1", new RuleConfig { Level = RuleLevel.On } }, - - // Turn off a rule that was on - { "c:rule2", new RuleConfig { Level = RuleLevel.Off} }, - - // Rule key comparison is case-sensitive - { "c:RULE3", new RuleConfig { Level = RuleLevel.Off} }, - - // Settings for other languages should be ignored - { "cpp:rule3", new RuleConfig { Level = RuleLevel.Off } } - } - }; - - // Act - var testSubject = CreateTestSubject(defaultConfig, settings); - - // Assert - testSubject.ActivePartialRuleKeys.Should().BeEquivalentTo("rule1", "rule3"); - } - - [TestMethod] - public void EffectiveSeverity_CustomSettingsOverrideDefaults() - { - // Arrange - var defaultConfig = new DummyCFamilyRulesConfig("c") - .AddRule("rule1", IssueSeverity.Major, isActive: false, null) - .AddRule("rule2", IssueSeverity.Minor, isActive: true, null) - .AddRule("rule3", IssueSeverity.Info, isActive: true, null); - - var settings = new RulesSettings(); - - // Rule 1 - severity not specified -> should use default - settings.Rules["c:rule1"] = new RuleConfig(); - - // Rule 2 - should override default severity - // Rule key comparison should be case-insensitive - settings.Rules["c:RULE2"] = new RuleConfig { Severity = IssueSeverity.Blocker }; - - // Rule 3 for a different language -> should be ignored and the default config used - settings.Rules["cpp:rule3"] = new RuleConfig { Severity = IssueSeverity.Critical }; - - // rule in user settings that isn't in the default config should be ignored - settings.Rules["c:missingRule"] = new RuleConfig { Severity = IssueSeverity.Critical }; - - // Act - var dynamicConfig = CreateTestSubject(defaultConfig, settings); - - // Assert - dynamicConfig.RulesMetadata.Count.Should().Be(3); - dynamicConfig.RulesMetadata["rule1"].DefaultSeverity.Should().Be(IssueSeverity.Major); - dynamicConfig.RulesMetadata["rule2"].DefaultSeverity.Should().Be(IssueSeverity.Blocker); - dynamicConfig.RulesMetadata["rule3"].DefaultSeverity.Should().Be(IssueSeverity.Info); - } - - [TestMethod] - public void Parameters_CustomSettingsOverrideDefaults() - { - // Arrange - var defaultConfig = new DummyCFamilyRulesConfig("c") - .AddRule("rule1", isActive: false, - parameters: new Dictionary - { - { "r1 param1", "r1p1 default" }, - { "r1 param2", "r1p2 default" } - }) - - .AddRule("rule2", isActive: true, - parameters: new Dictionary - { - { "r2 param1", "r2p1 default" }, - { "r2 param2", "r2p2 default" } - }) - .AddRule("rule3", isActive: true, - parameters: new Dictionary - { - { "r3 param1", "r3p1 default" }, - { "r3 param2", "r3p2 default" } - } - ); - - var settings = new RulesSettings - { - Rules = new Dictionary - { - // Rule 1 - no user params -> same as default - - // Rule 2 - all default params overridden - { "c:rule2", new RuleConfig - { - Parameters = new Dictionary - { - { "r2 param1", "r2p1 user"}, - { "r2 param2", "r2p2 user"} - } - } - }, - - // Rule 3 - params merged, with user taking priority - { "c:rule3", new RuleConfig - { - Parameters = new Dictionary - { - { "r3 param1", "r3p1 user"}, - { "r3 param3", "r3p3 user"} - } - } - } - } - }; - - // Act - var dynamicConfig = CreateTestSubject(defaultConfig, settings); - - // Assert - dynamicConfig.RulesParameters.Count.Should().Be(3); - dynamicConfig.RulesParameters["rule1"]["r1 param1"].Should().Be("r1p1 default"); - dynamicConfig.RulesParameters["rule1"]["r1 param2"].Should().Be("r1p2 default"); - dynamicConfig.RulesParameters["rule1"].Count.Should().Be(2); - - dynamicConfig.RulesParameters["rule2"]["r2 param1"].Should().Be("r2p1 user"); - dynamicConfig.RulesParameters["rule2"]["r2 param2"].Should().Be("r2p2 user"); - dynamicConfig.RulesParameters["rule2"].Count.Should().Be(2); - - dynamicConfig.RulesParameters["rule3"]["r3 param1"].Should().Be("r3p1 user"); - dynamicConfig.RulesParameters["rule3"]["r3 param2"].Should().Be("r3p2 default"); - dynamicConfig.RulesParameters["rule3"]["r3 param3"].Should().Be("r3p3 user"); - dynamicConfig.RulesParameters["rule3"].Count.Should().Be(3); - } - - #region Static method tests - - [TestMethod] - public void EffectiveParameters_NullHandling() - { - var nonEmptyParams = new Dictionary { { "p1", "v1" } }; - - // 1. Both null -> null returned - var actual = DynamicCFamilyRulesConfig.GetEffectiveParameters(null, null); - actual.Should().BeNull(); - - // 2. Null default params -> user params returned (not expected in practice, but we don't want to fail if it does) - actual = DynamicCFamilyRulesConfig.GetEffectiveParameters(nonEmptyParams, null); - actual.Should().BeEquivalentTo(nonEmptyParams); - - // 3. Null user params -> default params returned - actual = DynamicCFamilyRulesConfig.GetEffectiveParameters(null, nonEmptyParams); - actual.Should().BeEquivalentTo(nonEmptyParams); - } - - [TestMethod] - public void EffectiveParameters_CustomSettingsOverrideDefaults() - { - // Arrange - var defaultParams = new Dictionary - { - { "param1", "param 1 default" }, - { "param2", "param 2 default" }, - { "param3", "param 3 default" }, // expected - { "param3a", "param 3a default" }, - { "param4", "param 4 default" }, // expected - }; - - var userParams = new Dictionary - { - { "param1", "param 1 user" }, // expected - { "PARAM2", "param 2 user" }, // expected - { "param3a", "param 3a user" }, // expected - not an exact match for param3, should override param3a - // NOTE: params not in the set of default parameters will be included i.e. any arbitrary params set by the user will be included - { "NonDefaultParam", "non-default param value" }, // expected - }; - - // Act - var effectiveParams = DynamicCFamilyRulesConfig.GetEffectiveParameters(defaultParams, userParams); - - // Assert - effectiveParams.Keys.Should().BeEquivalentTo("param1", "param2", "param3", "param3a", "param4", "NonDefaultParam"); - - effectiveParams["param1"].Should().Be("param 1 user"); - effectiveParams["param2"].Should().Be("param 2 user"); - effectiveParams["param3"].Should().Be("param 3 default"); - effectiveParams["param3a"].Should().Be("param 3a user"); - effectiveParams["param4"].Should().Be("param 4 default"); - effectiveParams["NonDefaultParam"].Should().Be("non-default param value"); - } - - #endregion Static method tests - - private static DynamicCFamilyRulesConfig CreateTestSubject(ICFamilyRulesConfig defaultConfig, - RulesSettings customSettings, - IRulesConfigFixup fixup = null, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration = null) - { - fixup ??= new NoOpRulesConfigFixup(); - return new DynamicCFamilyRulesConfig(defaultConfig, customSettings, - connectedModeFeaturesConfiguration ?? Mock.Of(), new TestLogger(), fixup); - } - - private class NoOpRulesConfigFixup : IRulesConfigFixup - { - public RulesSettings Apply(RulesSettings input, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration) => input; - } - } - - internal static class RulesSettingsExtensions - { - public static RulesSettings AddRule(this RulesSettings settings, string ruleKey, RuleLevel level) - { - settings.Rules.Add(ruleKey, new RuleConfig { Level = level }); - return settings; - } - } -} diff --git a/src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorCacheTests.cs b/src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorCacheTests.cs deleted file mode 100644 index 82eec742ee..0000000000 --- a/src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorCacheTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.CFamily.Rules.UnitTests -{ - [TestClass] - public class EffectiveRulesConfigCalculatorCacheTests - { - private EffectiveRulesConfigCalculator.RulesConfigCache testSubject; - - [TestInitialize] - public void TestInitialize() - { - testSubject = new EffectiveRulesConfigCalculator.RulesConfigCache(); - } - - [TestMethod] - public void Cache_DifferentSourceConfig_NotFound_AndEntryCleared() - { - var sourceConfig1 = new Mock().Object; - var sourceSettings1 = new RulesSettings(); - var effectiveConfig1 = new Mock().Object; - - testSubject.Add("key1", sourceConfig1, sourceSettings1, effectiveConfig1); - testSubject.CacheCount.Should().Be(1); - - // 1. Search for added item -> found - testSubject.FindConfig("key1", sourceConfig1, sourceSettings1).Should().BeSameAs(effectiveConfig1); - testSubject.CacheCount.Should().Be(1); - - // 2. Different source config -> not found - testSubject.FindConfig("key1", new Mock().Object, sourceSettings1).Should().BeNull(); - testSubject.CacheCount.Should().Be(0); - } - - [TestMethod] - public void Cache_DifferentSourceSettings_NotFound_AndEntryCleared() - { - var sourceConfig1 = new Mock().Object; - var sourceSettings1 = new RulesSettings(); - var effectiveConfig1 = new Mock().Object; - - testSubject.Add("key1", sourceConfig1, sourceSettings1, effectiveConfig1); - testSubject.CacheCount.Should().Be(1); - - // 1. Search for added item -> found - testSubject.FindConfig("key1", sourceConfig1, sourceSettings1).Should().BeSameAs(effectiveConfig1); - testSubject.CacheCount.Should().Be(1); - - // 2. Different source settings -> not found - testSubject.FindConfig("key1", sourceConfig1, new RulesSettings()).Should().BeNull(); - testSubject.CacheCount.Should().Be(0); - } - - [TestMethod] - public void Cache_MultipleEntries() - { - var sourceConfig1 = new Mock().Object; - var sourceConfig2 = new Mock().Object; - - var sourceSettings1 = new RulesSettings(); - var sourceSettings2 = new RulesSettings(); - - var effectiveConfig1 = new Mock().Object; - var effectiveConfig2 = new Mock().Object; - - var testSubject = new EffectiveRulesConfigCalculator.RulesConfigCache(); - - // 1. Empty cache -> cache miss - testSubject.FindConfig("key1", sourceConfig1, sourceSettings1).Should().BeNull(); - - // 2. Add first entry to cache - testSubject.Add("key1", sourceConfig1, sourceSettings1, effectiveConfig1); - testSubject.CacheCount.Should().Be(1); - - // 3. Find second language - not found - testSubject.FindConfig("key2", sourceConfig2, sourceSettings2).Should().BeNull(); - - // 4. Add second entry to cache - testSubject.Add("key2", sourceConfig2, sourceSettings2, effectiveConfig2); - testSubject.CacheCount.Should().Be(2); - - // 5. Check can find both entries - testSubject.FindConfig("key1", sourceConfig1, sourceSettings1).Should().BeSameAs(effectiveConfig1); - testSubject.FindConfig("key2", sourceConfig2, sourceSettings2).Should().BeSameAs(effectiveConfig2); - } - } -} diff --git a/src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorTests.cs b/src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorTests.cs deleted file mode 100644 index 428c1312f3..0000000000 --- a/src/CFamily.UnitTests/Rules/EffectiveRulesConfigCalculatorTests.cs +++ /dev/null @@ -1,181 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.Rules.UnitTests -{ - [TestClass] - public class EffectiveRulesConfigCalculatorTests - { - private TestLogger testLogger; - private EffectiveRulesConfigCalculator testSubject; - - [TestInitialize] - public void TestInitialize() - { - testLogger = new TestLogger(); - testSubject = new EffectiveRulesConfigCalculator(Mock.Of(), testLogger); - } - - [TestMethod] - public void GetConfig_NullArguments_Throws() - { - var defaultRulesConfig = CreateWellKnownRulesConfig("language1"); - var customSettings = new RulesSettings(); - - // 1. Language - Action act = () => testSubject.GetEffectiveRulesConfig(null, defaultRulesConfig, customSettings); - act.Should().ThrowExactly().And.ParamName.Should().Be("languageKey"); - - // 2. Default rules config - act = () => testSubject.GetEffectiveRulesConfig("x", null, customSettings); - act.Should().ThrowExactly().And.ParamName.Should().Be("defaultRulesConfig"); - - // 3. Custom settings - act = () => testSubject.GetEffectiveRulesConfig("x", defaultRulesConfig, null); - act.Should().ThrowExactly().And.ParamName.Should().Be("customSettings"); - } - - [TestMethod] - public void GetConfig_NoCustomSettings_DefaultsReturned() - { - // Arrange - var defaultRulesConfig = CreateWellKnownRulesConfig("language1"); - var sourcesSettings = new RulesSettings(); - - // Act - var result = testSubject.GetEffectiveRulesConfig("language1", defaultRulesConfig, sourcesSettings); - - // Assert - result.LanguageKey.Should().Be("language1"); - result.AllPartialRuleKeys.Should().BeEquivalentTo(defaultRulesConfig.AllPartialRuleKeys); - - testLogger.AssertOutputStringExists(Resources.NoCustomRulesSettings); - } - - [DataRow(true, 2)] - [DataRow(false, 1)] - [DataTestMethod] - public void GetConfig_NoCustomSettings_RespectsHotspotConfig(bool analyzeHotspots, int expectedActiveCount) - { - // Arrange - var defaultRulesConfig = new DummyCFamilyRulesConfig("cpp") - .AddRule(RulesConfigFixup.HotspotRulesKeys[0].Split(':')[1], true) - .AddRule("12345678", true); - var sourcesSettings = new RulesSettings(); - var hotspotAnalysisConfigurationMock = new Mock(); - hotspotAnalysisConfigurationMock.Setup(x => x.IsHotspotsAnalysisEnabled()).Returns(analyzeHotspots); - - var testSubject1 = - new EffectiveRulesConfigCalculator(hotspotAnalysisConfigurationMock.Object, testLogger); - - // Act - var result = testSubject1.GetEffectiveRulesConfig("cpp", defaultRulesConfig, sourcesSettings); - - // Assert - result.LanguageKey.Should().Be("cpp"); - result.ActivePartialRuleKeys.Should().HaveCount(expectedActiveCount); - - testLogger.AssertOutputStringExists(Resources.NoCustomRulesSettings); - } - - [TestMethod] - public void GetConfig_RulesInCustomSettings_MergedConfigReturned() - { - // Arrange - var defaultRulesConfig = CreateWellKnownRulesConfig("key"); - var sourcesSettings = new RulesSettings - { - Rules = new Dictionary - { - // Turn an active rule off... - { "key:" + WellKnownPartialRuleKey1_Active, new RuleConfig { Level = RuleLevel.Off } }, - // ... and an inactive rule on - { "key:" + WellKnownPartialRuleKey3_Inactive, new RuleConfig { Level = RuleLevel.On } } - } - }; - - var result = testSubject.GetEffectiveRulesConfig("key", defaultRulesConfig, sourcesSettings); - - result.LanguageKey.Should().Be("key"); - result.AllPartialRuleKeys.Should().BeEquivalentTo(defaultRulesConfig.AllPartialRuleKeys); - result.ActivePartialRuleKeys.Should().BeEquivalentTo(WellKnownPartialRuleKey2_Active, WellKnownPartialRuleKey3_Inactive); - } - - [TestMethod] - public void GetConfig_CachedResultsReturnedIfAvailable() - { - // Arrange - var defaultRulesConfig = CreateWellKnownRulesConfig("key"); - var sourcesSettings = new RulesSettings - { - Rules = new Dictionary - { - { "rule1", new RuleConfig() } - } - }; - - // 1. First call -> new config returned - var result1 = testSubject.GetEffectiveRulesConfig("language1", defaultRulesConfig, sourcesSettings); - - result1.Should().NotBeNull(); - result1.Should().NotBeSameAs(defaultRulesConfig); - testLogger.AssertOutputStringExists(Resources.EffectiveRules_CacheMiss); - - // 2. Second call with same settings -> cache hit - testLogger.Reset(); - var result2 = testSubject.GetEffectiveRulesConfig("language1", defaultRulesConfig, sourcesSettings); - - result2.Should().BeSameAs(result1); - testLogger.AssertOutputStringExists(Resources.EffectiveRules_CacheHit); - - // 3. Call with different key -> cache miss - testLogger.Reset(); - var result3 = testSubject.GetEffectiveRulesConfig("another language", defaultRulesConfig, sourcesSettings); - - result3.Should().NotBeSameAs(result2); - testLogger.AssertOutputStringExists(Resources.EffectiveRules_CacheMiss); - } - - internal const string WellKnownPartialRuleKey1_Active = "rule1"; - internal const string WellKnownPartialRuleKey2_Active = "rule2"; - internal const string WellKnownPartialRuleKey3_Inactive = "rule3"; - - private static ICFamilyRulesConfig CreateWellKnownRulesConfig(string languageKey) - { - var defaultRulesConfig = new DummyCFamilyRulesConfig(languageKey) - .AddRule(WellKnownPartialRuleKey1_Active, IssueSeverity.Blocker, isActive: true, parameters: null, code: null) - .AddRule(WellKnownPartialRuleKey2_Active, IssueSeverity.Major, isActive: true, parameters: null, code: null) - .AddRule(WellKnownPartialRuleKey3_Inactive, IssueSeverity.Minor, isActive: false, parameters: null, code: null); - - return defaultRulesConfig; - } - } -} diff --git a/src/CFamily.UnitTests/Rules/RulesConfigFixupTests.cs b/src/CFamily.UnitTests/Rules/RulesConfigFixupTests.cs deleted file mode 100644 index 0c09ae1b77..0000000000 --- a/src/CFamily.UnitTests/Rules/RulesConfigFixupTests.cs +++ /dev/null @@ -1,214 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.Rules.UnitTests -{ - [TestClass] - public class RulesConfigFixupTests - { - [TestMethod] - public void Sanity_NoOverlapBetweenExludedAndLegacyRuleKeys() - { - // The logic in the fixup class assumes that there legacy rules keys and - // excluded rules are disjoint sets. Check this is actually the case. - RulesConfigFixup.fullLegacyToNewKeyMap.Keys.Intersect(RulesConfigFixup.ExcludedRulesKeys) - .Should().BeEmpty(); - - RulesConfigFixup.fullLegacyToNewKeyMap.Values.Intersect(RulesConfigFixup.ExcludedRulesKeys) - .Should().BeEmpty(); - } - - [TestMethod] - [DataRow("c:C99CommentUsage", "c:S787")] - [DataRow("cpp:C99CommentUsage", "cpp:S787")] - [DataRow("c:PPBadIncludeForm", "c:S956")] - [DataRow("cpp:PPBadIncludeForm", "cpp:S956")] - [DataRow("CPP:PPBADINCLUDEFORM", "CPP:PPBADINCLUDEFORM")] // replacement is case-sensitive - public void Apply_LegacyRuleKeys_KeysAreTranslated(string inputRuleKey, string expectedRuleKey) - { - var config1 = new RuleConfig { Level = RuleLevel.On, Severity = IssueSeverity.Major }; - var config2 = new RuleConfig { Level = RuleLevel.Off, Severity = IssueSeverity.Minor }; - var config3 = new RuleConfig { Level = RuleLevel.On, Severity = IssueSeverity.Critical }; - - var originalSettings = new RulesSettings - { - Rules = - { - { "any", config1 }, // arbitrary rule key - { inputRuleKey, config2}, - { "cpp:S123", config3 } // non-legacy - } - }; - - var logger = new TestLogger(); - var testSubject = CreateTestSubject(logger); - - var result = testSubject.Apply(originalSettings, CreateHotspotAnalysisConfig()); - - result.Rules["any"].Should().BeSameAs(config1); - result.Rules["cpp:S123"].Should().BeSameAs(config3); - - result.Rules.TryGetValue(expectedRuleKey, out var outputConfig).Should().BeTrue(); - outputConfig.Should().BeSameAs(config2); - - // Not expecting any messages for the non-legacy rule keys - logger.AssertPartialOutputStringDoesNotExist("any"); - logger.AssertPartialOutputStringDoesNotExist("cpp:S123"); - - CheckInstanceIsDifferent(originalSettings, result); - } - - [TestMethod] - [DataRow("c:C99CommentUsage", "c:S787")] - [DataRow("cpp:PPBadIncludeForm", "cpp:S956")] - public void Apply_BothLegacyAndNewRuleKey_LegacyKeyIsRemoved(string legacyKey, string newKey) - { - var legacyKeyConfig = new RuleConfig { Level = RuleLevel.On, Severity = IssueSeverity.Major }; - var newKeyConfig = new RuleConfig { Level = RuleLevel.Off, Severity = IssueSeverity.Minor }; - - var originalSettings = new RulesSettings - { - Rules = - { - { legacyKey, legacyKeyConfig }, - { newKey, newKeyConfig } - } - }; - - var logger = new TestLogger(logToConsole: true); - - var testSubject = CreateTestSubject(logger); - - var result = testSubject.Apply(originalSettings, CreateHotspotAnalysisConfig()); - - result.Rules[newKey].Should().BeSameAs(newKeyConfig); - result.Rules.TryGetValue(legacyKey, out var _).Should().BeFalse(); - - logger.AssertPartialOutputStringExists(legacyKey, newKey); - CheckInstanceIsDifferent(originalSettings, result); - } - - [DataRow(true)] - [DataRow(false)] - [DataTestMethod] - public void Apply_NoCustomRules_ExcludedRulesAreDisabled(bool hotspotsEnabled) - { - var logger = new TestLogger(); - var emptySettings = new RulesSettings(); - var testSubject = CreateTestSubject(logger); - - var result = testSubject.Apply(emptySettings, CreateHotspotAnalysisConfig(hotspotsEnabled)); - - CheckInstanceIsDifferent(emptySettings, result); - emptySettings.Rules.Count.Should().Be(0); // original settings should not have changed - - var excludedKeys = RulesConfigFixup.ExcludedRulesKeys; - - if (!hotspotsEnabled) - { - excludedKeys = excludedKeys.Concat(RulesConfigFixup.HotspotRulesKeys).ToArray(); - } - - result.Rules.Keys.Should().BeEquivalentTo(excludedKeys); - result.Rules.Values.Select(x => x.Level) - .All(x => x == RuleLevel.Off) - .Should().BeTrue(); - - foreach(string excludedKey in excludedKeys) - { - logger.AssertPartialOutputStringExists(excludedKey); - } - } - - [DataRow(true)] - [DataRow(false)] - [DataTestMethod] - public void Apply_CustomRulesAndExcludedRulesExist_CustomRulesAreUnchangedExcludedRulesAreDisabled(bool hotspotsEnabled) - { - // Arrange - var logger = new TestLogger(); - - string excludedKey1 = RulesConfigFixup.ExcludedRulesKeys[0]; - string excludedKey2 = RulesConfigFixup.ExcludedRulesKeys[1]; - string excludedKey3 = RulesConfigFixup.ExcludedRulesKeys[2]; - - var custom = new RulesSettings() - .AddRule("xxx", RuleLevel.On) - .AddRule(excludedKey1, RuleLevel.On) - .AddRule("yyy", RuleLevel.Off) - .AddRule(excludedKey2, RuleLevel.Off) - .AddRule(excludedKey3, RuleLevel.On); - - var testSubject = CreateTestSubject(logger); - - // Act - var result = testSubject.Apply(custom, CreateHotspotAnalysisConfig(hotspotsEnabled)); - - // Assert - CheckInstanceIsDifferent(custom, result); - custom.Rules.Count.Should().Be(5); - - IEnumerable expectedKeys = RulesConfigFixup.ExcludedRulesKeys; - - if (!hotspotsEnabled) - { - expectedKeys = expectedKeys.Concat(RulesConfigFixup.HotspotRulesKeys); - } - - expectedKeys = expectedKeys.Union(new string[] { "xxx", "yyy"}); - - result.Rules.Keys.Should().BeEquivalentTo(expectedKeys); - - // Non-excluded rules should be unchanged - result.Rules["xxx"].Level.Should().Be(RuleLevel.On); - result.Rules["yyy"].Level.Should().Be(RuleLevel.Off); - - // All excluded rules that were in the custom settings should be "Off" - result.Rules[excludedKey1].Level.Should().Be(RuleLevel.Off); - result.Rules[excludedKey2].Level.Should().Be(RuleLevel.Off); - result.Rules[excludedKey3].Level.Should().Be(RuleLevel.Off); - } - - private static IConnectedModeFeaturesConfiguration CreateHotspotAnalysisConfig(bool isEnabled = false) - { - var mock = new Mock(); - mock.Setup(x => x.IsHotspotsAnalysisEnabled()).Returns(isEnabled); - return mock.Object; - } - - private static RulesConfigFixup CreateTestSubject(ILogger logger = null) - => new RulesConfigFixup(logger ?? new TestLogger()); - - // Checks that the changes have been made to a copy of the settings, - // not the original settings. - private static void CheckInstanceIsDifferent(RulesSettings original, RulesSettings modified) => - modified.Should().NotBeSameAs(original); - } -} diff --git a/src/CFamily.UnitTests/Rules/RulesLoaderTest.cs b/src/CFamily.UnitTests/Rules/RulesLoaderTest.cs deleted file mode 100644 index f54a6f3c3d..0000000000 --- a/src/CFamily.UnitTests/Rules/RulesLoaderTest.cs +++ /dev/null @@ -1,154 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO; -using FluentAssertions; -using FluentAssertions.Execution; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using static SonarLint.VisualStudio.CFamily.Rules.RulesLoader; - -namespace SonarLint.VisualStudio.CFamily.Rules.UnitTests -{ - [TestClass] - public class RulesLoaderTest - { - [TestMethod] - public void Read_Rules() - { - var rulesLoader = CreateTestSubject(); - // 410 in RulesList.json + 2 in MisraRulesList.json - rulesLoader.ReadRulesList().Should().HaveCount(410 + 2); - } - - [TestMethod] - public void Read_Active_Rules() - { - var rulesLoader = CreateTestSubject(); - rulesLoader.ReadActiveRulesList().Should().HaveCount(255); - } - - [TestMethod] - public void Read_Rules_Params() - { - var rulesLoader = CreateTestSubject(); - rulesLoader.ReadRuleParams("ClassComplexity").Should() - .Contain(new System.Collections.Generic.KeyValuePair("maximumClassComplexityThreshold", "80")); - - rulesLoader.ReadRuleParams("Missing").Should().BeEmpty(); - - // Sanity check, ensure we can read all rules params - foreach (string ruleKey in rulesLoader.ReadRulesList()) - { - rulesLoader.ReadRuleParams(ruleKey).Should().NotBeNull(); - } - } - - [TestMethod] - public void Read_Rules_Metadata() - { - var rulesLoader = CreateTestSubject(); - using (new AssertionScope()) - { - rulesLoader.ReadRuleMetadata("ClassComplexity").Type.Should().Be(IssueType.CodeSmell); - rulesLoader.ReadRuleMetadata("ClassComplexity").DefaultSeverity.Should().Be(IssueSeverity.Critical); - } - - Action act = () => rulesLoader.ReadRuleMetadata("Missing"); - act.Should().ThrowExactly(); - } - - [TestMethod] - public void SonarTypeConverter_CodeSmell() - { - var json = @"{ -title: 'title1', -defaultSeverity: 'CRITICAL', -type: 'CODE_SMELL' -}"; - var ruleMetadata = DeserializeJson(json); - - ruleMetadata.Type.Should().Be(IssueType.CodeSmell); - ruleMetadata.DefaultSeverity.Should().Be(IssueSeverity.Critical); - } - - [TestMethod] - public void SonarTypeConverter_Bug() - { - var json = @"{ -title: 'title1', -defaultSeverity: 'BLOCKER', -type: 'BUG' -}"; - var ruleMetadata = DeserializeJson(json); - - ruleMetadata.Type.Should().Be(IssueType.Bug); - ruleMetadata.DefaultSeverity.Should().Be(IssueSeverity.Blocker); - } - - [TestMethod] - public void SonarTypeConverter_Vulnerability () - { - var json = @"{ -title: 'title1', -defaultSeverity: 'INFO', -type: 'VULNERABILITY' -}"; - var ruleMetadata = DeserializeJson(json); - - ruleMetadata.Type.Should().Be(IssueType.Vulnerability); - ruleMetadata.DefaultSeverity.Should().Be(IssueSeverity.Info); - } - - [TestMethod] - public void SonarTypeConverter_UnknownType_Throws() - { - var json = @"{ -title: 'title1', -defaultSeverity: 'CRITICAL', -type: 'xxx bad type' -}"; - Action act = () => DeserializeJson(json); - - act.Should().ThrowExactly().And.Message.Should().Contain("xxx bad type"); - } - - private static RuleMetadata DeserializeJson(string json) - { - var data = JsonConvert.DeserializeObject(json, new SonarTypeConverter()); - return data; - } - - private static RulesLoader CreateTestSubject() - { - var resourcesPath = Path.Combine( - Path.GetDirectoryName(typeof(RulesLoaderTest).Assembly.Location), - "Rules", "TestResources", "RulesLoader"); - Directory.Exists(resourcesPath).Should().BeTrue($"Test setup error: expected test resources directory does not exist: {resourcesPath}"); - - var rulesLoader = new RulesLoader(resourcesPath); - return rulesLoader; - } - - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity.json b/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity.json deleted file mode 100644 index dc71eddba3..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "title": "Classes should not be too complex", - "type": "CODE_SMELL", - "status": "deprecated", - "remediation": { - "func": "Linear with offset", - "linearDesc": "per complexity point over the threshold", - "linearOffset": "10min", - "linearFactor": "1min" - }, - "tags": [ - - ], - "defaultSeverity": "Critical", - "ruleSpecification": "RSPEC-1311", - "sqKey": "ClassComplexity", - "compatibleLanguages": [ - "CPP", - "OBJC" - ], - "scope": "Main" -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity_params.json b/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity_params.json deleted file mode 100644 index 748e2851e9..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/ClassComplexity_params.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "key": "maximumClassComplexityThreshold", - "description": "Maximum complexity allowed.", - "defaultValue": "80", - "type": "INTEGER" - } -] diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/MisraRulesList.json b/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/MisraRulesList.json deleted file mode 100644 index 579ee574b6..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/MisraRulesList.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - "M23_003", - "M23_036" -] diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/RulesList.json b/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/RulesList.json deleted file mode 100644 index 3af1b55fd2..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/RulesList.json +++ /dev/null @@ -1,412 +0,0 @@ -[ - "AssignmentInSubExpression", - "BackJumpWithGoto", - "C99CommentUsage", - "ClassComplexity", - "ClassName", - "CommaAndOrOverloaded", - "CommentMixedStyles", - "CommentRegularExpression", - "CommentedCode", - "ContinueUsage", - "DigraphUsage", - "EllipsisHandlerNotLast", - "ElseIfWithoutElse", - "EmptyCompoundStatement", - "EmptyThrowOutsideHandler", - "EnumPartialInitialization", - "ExceptionInDestructor", - "ExceptionSpecificationUsage", - "FileComplexity", - "FileLoc", - "FunctionComplexity", - "FunctionEllipsis", - "FunctionSinglePointOfExit", - "GlobalMainFunction", - "GlobalNamespaceMembers", - "GotoLabelInNestedBlock", - "GotoUsage", - "IdentifierLongerThan31", - "IncAndDecMixedWithOtherOperators", - "InvalidEscapeSequence", - "LineLength", - "LiteralSuffix", - "LogicalExpressionOperands", - "NamespaceName", - "NarrowAndWideStringConcat", - "NonEmptyCaseWithoutBreak", - "NonReentrantFunction", - "ObsoletePosixFunction", - "OctalConstantAndSequence", - "OneStatementPerLine", - "PPBackslashNotLastCharacter", - "PPBadIncludeForm", - "PPDefineOrUndefFromBlock", - "PPDirectiveIndentation", - "PPErrorDirectiveReached", - "PPIncludeCHeader", - "PPIncludeCstdio", - "PPIncludeCtime", - "PPIncludeNonStandardCharacters", - "PPIncludeNotAtTop", - "PPIncludeSignal", - "PPIncludeStdio", - "PPIncludeTime", - "PPMacroName", - "PPNonStandardInclude", - "PPStringifyAndPastingUsage", - "PPUndefUsage", - "ParsingError", - "S100", - "S1003", - "S1006", - "S1011", - "S1013", - "S1016", - "S1017", - "S1032", - "S1035", - "S1036", - "S1044", - "S1045", - "S106", - "S1065", - "S1066", - "S1067", - "S1068", - "S107", - "S1079", - "S1081", - "S109", - "S110", - "S1103", - "S1110", - "S1116", - "S1117", - "S112", - "S1123", - "S113", - "S1131", - "S1133", - "S1134", - "S1135", - "S1141", - "S1142", - "S1143", - "S1144", - "S1151", - "S116", - "S1163", - "S117", - "S1172", - "S1181", - "S1185", - "S1186", - "S1188", - "S1198", - "S1199", - "S121", - "S1219", - "S1226", - "S1227", - "S1231", - "S1232", - "S1235", - "S1236", - "S1238", - "S1242", - "S1244", - "S1250", - "S1259", - "S1264", - "S1265", - "S127", - "S1270", - "S1271", - "S1291", - "S1301", - "S134", - "S138", - "S139", - "S1448", - "S1451", - "S1479", - "S1481", - "S1578", - "S1642", - "S1656", - "S1669", - "S1679", - "S1699", - "S1704", - "S1705", - "S1706", - "S1707", - "S1708", - "S1709", - "S1712", - "S1749", - "S1750", - "S1751", - "S1760", - "S1761", - "S1762", - "S1763", - "S1764", - "S1767", - "S1768", - "S1771", - "S1772", - "S1773", - "S1774", - "S1820", - "S1821", - "S1831", - "S1836", - "S1854", - "S1862", - "S1871", - "S1874", - "S1878", - "S1986", - "S1990", - "S2095", - "S2123", - "S2156", - "S2190", - "S2193", - "S2209", - "S2216", - "S2234", - "S2259", - "S2275", - "S2323", - "S2324", - "S2335", - "S2342", - "S2343", - "S2387", - "S2393", - "S2479", - "S2486", - "S2583", - "S2589", - "S2637", - "S2665", - "S2668", - "S2681", - "S2737", - "S2738", - "S2753", - "S2754", - "S2757", - "S2761", - "S2806", - "S2807", - "S2808", - "S2813", - "S2815", - "S3222", - "S3229", - "S3230", - "S3231", - "S3252", - "S3261", - "S3358", - "S3400", - "S3432", - "S3457", - "S3458", - "S3468", - "S3469", - "S3470", - "S3471", - "S3485", - "S3486", - "S3490", - "S3491", - "S3516", - "S3518", - "S3519", - "S3520", - "S3522", - "S3529", - "S3539", - "S3540", - "S3541", - "S3542", - "S3543", - "S3548", - "S3549", - "S3562", - "S3574", - "S3576", - "S3584", - "S3588", - "S3590", - "S3608", - "S3609", - "S3624", - "S3626", - "S3628", - "S3630", - "S3636", - "S3642", - "S3646", - "S3654", - "S3656", - "S3657", - "S3659", - "S3685", - "S3687", - "S3689", - "S3691", - "S3692", - "S3696", - "S3698", - "S3708", - "S3715", - "S3719", - "S3726", - "S3728", - "S3729", - "S3730", - "S3731", - "S3732", - "S3743", - "S3744", - "S3776", - "S3805", - "S3806", - "S3807", - "S3923", - "S3935", - "S3936", - "S3949", - "S3972", - "S3973", - "S4143", - "S4144", - "S4263", - "S4334", - "S4962", - "S4963", - "S4997", - "S4998", - "S4999", - "S5000", - "S5008", - "S5018", - "S5019", - "S5020", - "S5025", - "S5028", - "S5180", - "S5184", - "S5205", - "S5213", - "S5259", - "S5261", - "S5262", - "S5263", - "S5265", - "S5266", - "S5267", - "S5269", - "S5270", - "S5271", - "S5272", - "S5273", - "S5274", - "S5275", - "S5277", - "S5278", - "S5279", - "S5280", - "S5281", - "S5283", - "S5293", - "S5297", - "S5298", - "S5302", - "S5303", - "S5305", - "S5306", - "S5307", - "S5308", - "S5309", - "S5318", - "S5319", - "S784", - "S793", - "S802", - "S810", - "S811", - "S812", - "S813", - "S814", - "S819", - "S820", - "S824", - "S831", - "S833", - "S834", - "S835", - "S836", - "S851", - "S853", - "S854", - "S855", - "S856", - "S859", - "S860", - "S864", - "S867", - "S871", - "S872", - "S873", - "S874", - "S876", - "S878", - "S883", - "S886", - "S897", - "S905", - "S920", - "S925", - "S926", - "S929", - "S930", - "S935", - "S936", - "S943", - "S946", - "S950", - "S960", - "S961", - "S966", - "S967", - "S969", - "S977", - "S978", - "S982", - "S984", - "S985", - "S986", - "S989", - "S990", - "S995", - "SideEffectInRightHandSideOfLogical", - "SideEffectInSizeOf", - "SingleDeclarationPerStatement", - "SingleGotoOrBreakPerIteration", - "SizeofSizeof", - "SwitchLabelPlacement", - "SwitchWithoutDefault", - "TabCharacter", - "TrigraphUsage", - "UnaryAndOverloaded", - "Union", - "UnnamedNamespaceInHeader", - "UsingDirective" -] diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/Sonar_way_profile.json b/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/Sonar_way_profile.json deleted file mode 100644 index 47ceb2c1fa..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesLoader/Sonar_way_profile.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "name": "Sonar way", - "ruleKeys": [ - "AssignmentInSubExpression", - "BackJumpWithGoto", - "CommaAndOrOverloaded", - "CommentedCode", - "DigraphUsage", - "EllipsisHandlerNotLast", - "EmptyCompoundStatement", - "EmptyThrowOutsideHandler", - "EnumPartialInitialization", - "ExceptionInDestructor", - "ExceptionSpecificationUsage", - "FunctionEllipsis", - "GlobalMainFunction", - "GotoLabelInNestedBlock", - "InvalidEscapeSequence", - "LiteralSuffix", - "NamespaceName", - "NarrowAndWideStringConcat", - "NonEmptyCaseWithoutBreak", - "NonReentrantFunction", - "ObsoletePosixFunction", - "PPBackslashNotLastCharacter", - "PPBadIncludeForm", - "PPDefineOrUndefFromBlock", - "PPDirectiveIndentation", - "PPIncludeNonStandardCharacters", - "PPIncludeNotAtTop", - "PPNonStandardInclude", - "PPUndefUsage", - "S106", - "S107", - "S110", - "S112", - "S814", - "S819", - "S820", - "S824", - "S836", - "S859", - "S860", - "S872", - "S876", - "S878", - "S897", - "S905", - "S936", - "S961", - "S967", - "S969", - "S977", - "S1003", - "S1006", - "S1011", - "S1017", - "S1035", - "S1036", - "S1044", - "S1045", - "S1065", - "S1066", - "S1068", - "S1079", - "S1081", - "S1103", - "S1110", - "S1116", - "S1117", - "S1123", - "S1133", - "S1134", - "S1135", - "S1141", - "S1143", - "S1144", - "S1163", - "S1172", - "S1181", - "S1185", - "S1186", - "S1198", - "S1199", - "S1219", - "S1231", - "S1232", - "S1235", - "S1236", - "S1238", - "S1242", - "S1250", - "S1264", - "S1265", - "S1301", - "S1448", - "S1479", - "S1481", - "S1656", - "S1669", - "S1679", - "S1699", - "S1709", - "S1751", - "S1760", - "S1761", - "S1763", - "S1764", - "S1767", - "S1768", - "S1771", - "S1820", - "S1831", - "S1836", - "S1854", - "S1862", - "S1871", - "S1874", - "S2095", - "S2123", - "S2190", - "S2193", - "S2209", - "S2216", - "S2234", - "S2259", - "S2275", - "S2323", - "S2387", - "S2479", - "S2486", - "S2583", - "S2589", - "S2637", - "S2665", - "S2668", - "S2681", - "S2737", - "S2738", - "S2753", - "S2754", - "S2757", - "S2761", - "S2808", - "S2813", - "S2815", - "S3229", - "S3230", - "S3231", - "S3252", - "S3261", - "S3358", - "S3432", - "S3457", - "S3458", - "S3468", - "S3469", - "S3470", - "S3485", - "S3486", - "S3491", - "S3516", - "S3518", - "S3519", - "S3520", - "S3522", - "S3529", - "S3539", - "S3540", - "S3541", - "S3543", - "S3548", - "S3574", - "S3576", - "S3584", - "S3588", - "S3590", - "S3608", - "S3624", - "S3626", - "S3636", - "S3646", - "S3656", - "S3657", - "S3659", - "S3689", - "S3691", - "S3692", - "S3696", - "S3698", - "S3708", - "S3726", - "S3728", - "S3729", - "S3730", - "S3731", - "S3732", - "S3743", - "S3744", - "S3776", - "S3805", - "S3806", - "S3807", - "S3923", - "S3935", - "S3936", - "S3949", - "S3972", - "S3973", - "S4143", - "S4144", - "S4263", - "S4963", - "S4997", - "S4998", - "S4999", - "S5000", - "S5008", - "S5019", - "S5020", - "S5028", - "S5180", - "S5184", - "S5205", - "S5213", - "S5259", - "S5261", - "S5262", - "S5263", - "S5265", - "S5266", - "S5267", - "S5269", - "S5271", - "S5272", - "S5273", - "S5274", - "S5275", - "S5277", - "S5278", - "S5279", - "S5280", - "S5281", - "S5283", - "S5293", - "S5297", - "S5308", - "SideEffectInRightHandSideOfLogical", - "SideEffectInSizeOf", - "SingleDeclarationPerStatement", - "SingleGotoOrBreakPerIteration", - "SizeofSizeof", - "SwitchLabelPlacement", - "SwitchWithoutDefault", - "TrigraphUsage", - "UnaryAndOverloaded", - "Union", - "UnnamedNamespaceInHeader" - ] -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1.json deleted file mode 100644 index 6e02ca9c4b..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "title": "All ActiveWithParams 1 title", - "type": "CODE_SMELL", - "status": "deprecated", - "remediation": { - "func": "Linear with offset", - "linearDesc": "per complexity point over the threshold", - "linearOffset": "10min", - "linearFactor": "1min" - }, - "tags": [ - - ], - "defaultSeverity": "Critical", - "ruleSpecification": "RSPEC-1311", - "sqKey": "All_ActiveWithParams_1", - "compatibleLanguages": [ - "C", - "CPP", - "OBJC" - ], - "scope": "Main" -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1_params.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1_params.json deleted file mode 100644 index 748e2851e9..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_ActiveWithParams_1_params.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "key": "maximumClassComplexityThreshold", - "description": "Maximum complexity allowed.", - "defaultValue": "80", - "type": "INTEGER" - } -] diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Active_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Active_1.json deleted file mode 100644 index 45e48aab60..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Active_1.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title": "All_Active_1 title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "convention", - "based-on-misra" - ], - "standards": [ - "MISRA C 2004" - ], - "defaultSeverity": "Minor", - "ruleSpecification": "RSPEC-787", - "sqKey": "C99CommentUsage", - "compatibleLanguages": [ - "C", - "CPP", - "OBJC" - ], - "scope": "Main" -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Inactive_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Inactive_1.json deleted file mode 100644 index ffc4d88d86..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/All_Inactive_1.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title": "All Inactive", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "convention", - "based-on-misra" - ], - "standards": [ - "MISRA C 2004" - ], - "defaultSeverity": "Minor", - "ruleSpecification": "RSPEC-787", - "sqKey": "All_Inactive_1", - "compatibleLanguages": [ - "C", - "CPP", - "OBJC" - ], - "scope": "Main" -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Active_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Active_1.json deleted file mode 100644 index ff99855ff0..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Active_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "C Active 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "C_Active_1", - "compatibleLanguages": [ - "C" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_1.json deleted file mode 100644 index 0b6f437cca..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "C Inactive 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "C_Inactive_1", - "compatibleLanguages": [ - "C" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_2.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_2.json deleted file mode 100644 index b00837cdce..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Inactive_2.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "C Inactive 2 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "C_Inactive_2", - "compatibleLanguages": [ - "C" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Active_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Active_1.json deleted file mode 100644 index 7dea2a6573..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Active_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "C MISRA Active 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "C_Active_1", - "compatibleLanguages": [ - "C" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Inactive_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Inactive_1.json deleted file mode 100644 index e000fd9581..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/C_Misra_Inactive_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "C MISRA Inactive 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "C_Inactive_1", - "compatibleLanguages": [ - "C" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_1.json deleted file mode 100644 index 0f339961b7..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "Cpp Active 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "Cpp_Active_1", - "compatibleLanguages": [ - "Cpp" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_2.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_2.json deleted file mode 100644 index d9ac096a97..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Active_2.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "Cpp Active 2 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "Cpp_Active_2", - "compatibleLanguages": [ - "Cpp" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Inactive_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Inactive_1.json deleted file mode 100644 index b9c501ebfa..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Inactive_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "Cpp Inactive 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "Cpp_Inactive_1", - "compatibleLanguages": [ - "CPP" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Active_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Active_1.json deleted file mode 100644 index d77a1b71ff..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Active_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "Cpp MISRA Active 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "Cpp_Active_1", - "compatibleLanguages": [ - "Cpp" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Inactive_1.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Inactive_1.json deleted file mode 100644 index 32795a7612..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Cpp_Misra_Inactive_1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "title": "Cpp MISRA Inactive 1 Title", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "cwe", - "based-on-misra", - "cert", - "suspicious" - ], - "standards": [ - "MISRA C 2004", - "MISRA C 2012", - "CWE", - "CERT" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-1121", - "sqKey": "Cpp_Inactive_1", - "compatibleLanguages": [ - "CPP" - ], - "scope": "All", - "securityStandards": { - "CWE": [ - 481 - ] - } -} diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/MisraRulesList.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/MisraRulesList.json deleted file mode 100644 index 2af02c7f75..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/MisraRulesList.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - "C_Misra_Active_1", - "C_Misra_Inactive_1", - "Cpp_Misra_Active_1", - "Cpp_Misra_Inactive_1" -] diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/RulesList.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/RulesList.json deleted file mode 100644 index 3a45f26fab..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/RulesList.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - "All_Active_1", - "All_ActiveWithParams_1", - "All_Inactive_1", - "C_Active_1", - "C_Inactive_1", - "C_Inactive_2", - "Cpp_Active_1", - "Cpp_Active_2", - "Cpp_Inactive_1" -] \ No newline at end of file diff --git a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Sonar_way_profile.json b/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Sonar_way_profile.json deleted file mode 100644 index abdf165c3d..0000000000 --- a/src/CFamily.UnitTests/Rules/TestResources/RulesMetadataCache/Sonar_way_profile.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "Sonar way", - "ruleKeys": [ - "All_Active_1", - "All_ActiveWithParams_1", - "C_Active_1", - "Cpp_Active_1", - "Cpp_Active_2", - "C_Misra_Active_1", - "Cpp_Misra_Active_1" - ] -} \ No newline at end of file diff --git a/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs b/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs deleted file mode 100644 index 0acf246c84..0000000000 --- a/src/CFamily.UnitTests/Subprocess/MessageHandlerTests.cs +++ /dev/null @@ -1,264 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.SubProcess.UnitTests -{ - [TestClass] - public class MessageHandlerTests - { - [TestMethod] - public void TestIsIssueForActiveRule() - { - var rulesConfig = new DummyCFamilyRulesConfig("any") - .AddRule("rule1", isActive: true) - .AddRule("rule2", isActive: false); - - // 1. Match - active - var message = new Message("rule1", "filename", 0, 0, 0, 0, "msg", false, null, Array.Empty()); - MessageHandler.IsIssueForActiveRule(message, rulesConfig).Should().BeTrue(); - - // 2. Match - not active - message = new Message("rule2", "filename", 0, 0, 0, 0, "msg", false, null, Array.Empty()); - MessageHandler.IsIssueForActiveRule(message, rulesConfig).Should().BeFalse(); - - // 3. No match - case-sensitivity - message = new Message("RULE1", "filename", 0, 0, 0, 0, "msg", false, null, Array.Empty()); - MessageHandler.IsIssueForActiveRule(message, rulesConfig).Should().BeFalse(); - - // 4. No match - message = new Message("xxx", "filename", 0, 0, 0, 0, "msg", false, null, Array.Empty()); - MessageHandler.IsIssueForActiveRule(message, rulesConfig).Should().BeFalse(); - } - - [TestMethod] - public void HandleMessage_NoMessages_AnalysisSucceeds() - { - var context = new MessageHandlerTestContext(); - - var testSubject = context.CreateTestSubject(); - - testSubject.AnalysisSucceeded.Should().BeTrue(); - } - - [TestMethod] - public void HandleMessage_IssueProcessing_InactiveRulesAreIgnored_ActiveRulesAreNotIgnored() - { - const string fileName = "c:\\data\\aaa\\bbb\\file.txt"; - var inactiveRuleMessage = CreateMessage("inactiveRule", fileName); - var activeRuleMessage = CreateMessage("activeRule", fileName); - - var context = new MessageHandlerTestContext() - .SetRequestFilePath(fileName) - .AddRule("inactiveRule", isActive: false) - .AddRule("activeRule", isActive: true); - - var convertedActiveMessage = Mock.Of(); - context.IssueConverter - .Setup(x => x.Convert(activeRuleMessage, context.AnalysisLanguageKey, context.RulesConfig)) - .Returns(convertedActiveMessage); - - var testSubject = context.CreateTestSubject(); - - // Stream the inactive rule message to the analyzer - // - should not be passed to the consumer or converted - testSubject.HandleMessage(inactiveRuleMessage); - - context.AssertNoIssuesProcessed(); - - // Now stream an active rule message - testSubject.HandleMessage(activeRuleMessage); - - testSubject.AnalysisSucceeded.Should().BeTrue(); - testSubject.IssueCount.Should().Be(1); - context.IssueConverter.VerifyAll(); - - var suppliedIssues = (IEnumerable)context.IssueConsumer.Invocations[0].Arguments[1]; - suppliedIssues.Count().Should().Be(1); - suppliedIssues.First().Should().Be(convertedActiveMessage); - } - - [TestMethod] - [DataRow("c:\\Analyzedfile.txt")] // exact match - [DataRow("C:\\ANALYZEDFILE.TXT")] // different case - [DataRow("C:\\AAA\\..\\ANALYZEDFILE.TXT")] // logically the same file path - public void HandleMessage_IssuesForAnalyzedFileAreNotIgnored(string fileNameInMessage) - { - const string analyzedFile = "c:\\Analyzedfile.txt"; - var analyzedFileMessage = CreateMessage("activeRule", fileNameInMessage); - - var context = new MessageHandlerTestContext() - .SetRequestFilePath(analyzedFile) - .AddRule("activeRule", isActive: true); - - var testSubject = context.CreateTestSubject(); - - // Process the message - testSubject.HandleMessage(analyzedFileMessage); - - testSubject.AnalysisSucceeded.Should().BeTrue(); - context.IssueConverter.Invocations.Count.Should().Be(1); - context.IssueConsumer.Invocations.Count.Should().Be(1); - - context.IssueConsumer.Verify(x => x.SetIssues(analyzedFile, It.IsAny>())); - } - - [TestMethod] - [DataRow("")] - [DataRow("another file")] - [DataRow("D:\\Analyzedfile.txt")] // correct file name, wrong drive - public void HandleMessage_IssuesForOtherFilesAreIgnored(string messageFileName) - { - const string analyzedFile = "c:\\Analyzedfile.txt"; - var otherFileMessage = CreateMessage("activeRule", messageFileName); - - var context = new MessageHandlerTestContext() - .SetRequestFilePath(analyzedFile) - .AddRule("activeRule", isActive: true); - - var testSubject = context.CreateTestSubject(); - - // Process the message - testSubject.HandleMessage(otherFileMessage); - - testSubject.AnalysisSucceeded.Should().BeTrue(); // analysis still succeeded, even though no issues reported. - context.AssertNoIssuesProcessed(); - } - - [TestMethod] - [DataRow("internal.InvalidInput", "MsgHandler_ReportInvalidInput")] - [DataRow("internal.UnexpectedFailure", "MsgHandler_ReportUnexpectedFailure")] - [DataRow("internal.UnsupportedConfig", "MsgHandler_ReportUnsupportedConfiguration")] - public void HandleMessage_InternalErrorMessage_IsReportedAndAnalysisFails(string internalRuleKey, string expectedResourceMessageName) - { - // Note: this test assumes that all of the internal rule error messages have a single placeholder - // into which the message text is inserted. - string logMessageFormat = CFamilyStrings.ResourceManager.GetString(expectedResourceMessageName); - var expectedLogMessage = string.Format(logMessageFormat, "XXX internal error XXX"); - - var internalMessage = CreateMessage(internalRuleKey, text: "XXX internal error XXX"); - - var context = new MessageHandlerTestContext() - .AddRule("S123", isActive: true); - var testSubject = context.CreateTestSubject(); - - // Act - testSubject.HandleMessage(internalMessage); - - testSubject.AnalysisSucceeded.Should().BeFalse(); - context.Logger.AssertOutputStringExists(expectedLogMessage); - context.AssertNoIssuesProcessed(); - } - - [TestMethod] - [DataRow("internal.fileDependency")] // real property - [DataRow("internal.something")] // fake property - [DataRow("internal.InvalidInputtttt")] // testing that it takes any starts with - public void HandleMessage_UnknownInternalRules_IsIgnored(string ruleId) - { - var internalMessage = CreateMessage(ruleId, text: "c:\\file.txt"); - - var context = new MessageHandlerTestContext() - // The message file name matches the file being analyzed, but should be ignored anyway - // because of the rule key - .SetRequestFilePath("c:\\file.txt"); - - var testSubject = context.CreateTestSubject(); - - // Act - testSubject.HandleMessage(internalMessage); - - testSubject.AnalysisSucceeded.Should().BeTrue(); - context.Logger.AssertNoOutputMessages(); - context.AssertNoIssuesProcessed(); - } - - private static Message CreateMessage(string ruleId, string fileName = "any file", string text = "any text") => - new(ruleId, fileName, -1, -1, -1, -1, text, false, null, Array.Empty()); - - - private class MessageHandlerTestContext - { - public Mock IssueConsumer { get; } = new Mock(); - public Mock IssueConverter { get; } = new Mock(); - public TestLogger Logger { get; } = new TestLogger(logToConsole: true); - - private string requestFilePath = "any.txt"; - private const string languageKey = "c"; - - public string AnalysisLanguageKey { get; } = languageKey; - - public DummyCFamilyRulesConfig RulesConfig { get; } = new DummyCFamilyRulesConfig(languageKey); - - public MessageHandler TestSubject { get; set; } - - public MessageHandlerTestContext SetRequestFilePath (string fileToAnalyze) - { - requestFilePath = fileToAnalyze; - return this; - } - - public MessageHandlerTestContext AddRule(string ruleKey, bool isActive) - { - RulesConfig.AddRule(ruleKey, isActive); - return this; - } - - public MessageHandler CreateTestSubject() - { - if (TestSubject != null) - { - throw new InvalidOperationException("Test setup error: TestSubject has already been created"); - } - - var request = CreateRequest(requestFilePath, AnalysisLanguageKey, RulesConfig); - - TestSubject = new MessageHandler(request, IssueConsumer.Object, IssueConverter.Object, Logger); - return TestSubject; - } - - public void AssertNoIssuesProcessed() - { - TestSubject.IssueCount.Should().Be(0); - IssueConverter.Invocations.Count.Should().Be(0); - IssueConsumer.Invocations.Count.Should().Be(0); - } - - private static IRequest CreateRequest(string file = null, string language = null, ICFamilyRulesConfig rulesConfiguration = null) - { - var request = new Mock(); - var context = new RequestContext(language, rulesConfiguration, file, null, null, CFamilyShared.IsHeaderFileExtension(file)); - request.SetupGet(x => x.Context).Returns(context); - return request.Object; - } - } - } -} diff --git a/src/CFamily.UnitTests/Subprocess/NoOpMessageHandlerTests.cs b/src/CFamily.UnitTests/Subprocess/NoOpMessageHandlerTests.cs deleted file mode 100644 index a2511c7e44..0000000000 --- a/src/CFamily.UnitTests/Subprocess/NoOpMessageHandlerTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace SonarLint.VisualStudio.CFamily.SubProcess.UnitTests -{ - [TestClass] - public class NoOpMessageHandlerTests - { - [TestMethod] - public void Singleton() - { - var instance1 = NoOpMessageHandler.Instance; - var instance2 = NoOpMessageHandler.Instance; - - instance1.Should().BeSameAs(instance2); - } - - [TestMethod] - public void AnalysisSucceeded_IsTrue() - { - NoOpMessageHandler.Instance.AnalysisSucceeded.Should().BeTrue(); - } - - [TestMethod] - public void HandleOutput_DoesNothing() - { - var message = new Message("key", "file", 1, 2, 3, 4, "text", false, null, Array.Empty()); - - var testSubject = new NoOpMessageHandler(); - - testSubject.IssueCount.Should().Be(0); // check initial state - - testSubject.HandleMessage(message); - testSubject.HandleMessage(null); - - testSubject.IssueCount.Should().Be(0); - } - } -} diff --git a/src/CFamily.UnitTests/Subprocess/ProcessRunnerTests.cs b/src/CFamily.UnitTests/Subprocess/ProcessRunnerTests.cs deleted file mode 100644 index f005e32dfb..0000000000 --- a/src/CFamily.UnitTests/Subprocess/ProcessRunnerTests.cs +++ /dev/null @@ -1,528 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO; -using SonarLint.VisualStudio.CFamily.Helpers.UnitTests; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.CFamily.SubProcess.UnitTests -{ - [TestClass] - public class ProcessRunnerTests - { - public TestContext TestContext { get; set; } - - [TestMethod] - public void Execute_WhenRunnerArgsIsNull_ThrowsArgumentNullException() - { - // Arrange - Action action = () => new ProcessRunner(new ConfigurableSonarLintSettings(), new TestLogger()).Execute(null); - - // Act & Assert - action.Should().ThrowExactly().And.ParamName.Should().Be("runnerArgs"); - } - - [TestMethod] - public void ProcRunner_ExecutionFailed() - { - // Arrange - var exeName = WriteBatchFileForTest(TestContext, "exit -2"); - - var logger = new TestLogger(); - var args = new ProcessRunnerArguments(exeName, true); - var runner = CreateProcessRunner(logger); - - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(-2, "Unexpected exit code"); - } - - [TestMethod] - public void ProcRunner_ExecutionSucceeded() - { - // Arrange - var exeName = WriteBatchFileForTest(TestContext, -@"@echo Hello world -@echo xxx yyy -@echo Testing 1,2,3...>&2 -"); - - var logger = new TestLogger(); - var args = new ProcessRunnerArguments(exeName, true); - var output = ""; - args.HandleOutputStream = reader => output = reader.ReadToEnd(); - var runner = CreateProcessRunner(logger); - - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(0, "Unexpected exit code"); - - output.Should().Contain("Hello world"); - output.Should().NotContain("Testing 1,2,3..."); - } - - [TestMethod] - public void ProcRunner_PassesEnvVariables() - { - // Arrange - var logger = new TestLogger(); - var runner = CreateProcessRunner(logger); - - var exeName = WriteBatchFileForTest(TestContext, -@"echo %PROCESS_VAR% -@echo %PROCESS_VAR2% -@echo %PROCESS_VAR3% -"); - var envVariables = new Dictionary() { - { "PROCESS_VAR", "PROCESS_VAR value" }, - { "PROCESS_VAR2", "PROCESS_VAR2 value" }, - { "PROCESS_VAR3", "PROCESS_VAR3 value" } }; - - var args = new ProcessRunnerArguments(exeName, true) - { - EnvironmentVariables = envVariables - }; - var output = ""; - args.HandleOutputStream = reader => output = reader.ReadToEnd(); - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(0, "Unexpected exit code"); - - output.Should().Contain("PROCESS_VAR value"); - output.Should().Contain("PROCESS_VAR2 value"); - output.Should().Contain("PROCESS_VAR3 value"); - } - - [TestMethod] - public void ProcRunner_PassesEnvVariables_OverrideExisting() - { - // Tests that existing environment variables will be overwritten successfully - - // Arrange - var logger = new TestLogger(); - var runner = CreateProcessRunner(logger); - var output = ""; - - try - { - // It's possible the user won't be have permissions to set machine level variables - // (e.g. when running on a build agent). Carry on with testing the other variables. - SafeSetEnvironmentVariable("proc.runner.test.machine", "existing machine value", EnvironmentVariableTarget.Machine, logger); - Environment.SetEnvironmentVariable("proc.runner.test.process", "existing process value", EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable("proc.runner.test.user", "existing user value", EnvironmentVariableTarget.User); - - var exeName = WriteBatchFileForTest(TestContext, -@"@echo file: %proc.runner.test.machine% -@echo file: %proc.runner.test.process% -@echo file: %proc.runner.test.user% -"); - - var envVariables = new Dictionary() { - { "proc.runner.test.machine", "machine override" }, - { "proc.runner.test.process", "process override" }, - { "proc.runner.test.user", "user override" } }; - - var args = new ProcessRunnerArguments(exeName, true) - { - EnvironmentVariables = envVariables, - HandleOutputStream = reader => - { - output = reader.ReadToEnd(); - } - }; - - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(0, "Unexpected exit code"); - } - finally - { - SafeSetEnvironmentVariable("proc.runner.test.machine", null, EnvironmentVariableTarget.Machine, logger); - Environment.SetEnvironmentVariable("proc.runner.test.process", null, EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable("proc.runner.test.user", null, EnvironmentVariableTarget.User); - } - - // Check the child process used expected values - output.Should().Contain("file: machine override"); - output.Should().Contain("file: process override"); - output.Should().Contain("file: user override"); - - // Check the runner reported it was overwriting existing variables - // Note: the existing non-process values won't be visible to the child process - // unless they were set *before* the test host launched, which won't be the case. - logger.AssertSingleDebugMessageExists("proc.runner.test.process", "existing process value", "process override"); - } - - [TestMethod] - public void ProcRunner_MissingExe() - { - // Tests attempting to launch a non-existent exe - - // Arrange - var logger = new TestLogger(); - var args = new ProcessRunnerArguments("missingExe.foo", false); - var runner = CreateProcessRunner(logger); - - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(ProcessRunner.ErrorCode, "Unexpected exit code"); - logger.AssertSingleErrorExists("missingExe.foo"); - } - - [TestMethod] - public void ProcRunner_ArgumentQuoting() - { - // Checks arguments passed to the child process are correctly quoted - - // Arrange - var testDir = CreateTestSpecificFolder(TestContext); - // Create a dummy exe that will produce a log file showing any input args - var exeName = DummyExeHelper.CreateDummyExe(testDir, 0); - - var logger = new TestLogger(); - var args = new ProcessRunnerArguments(exeName, false); - - var expected = new[] { - "unquoted", - "\"quoted\"", - "\"quoted with spaces\"", - "/test:\"quoted arg\"", - "unquoted with spaces", - "quote in \"the middle", - "quotes \"& ampersands", - "\"multiple \"\"\" quotes \" ", - "trailing backslash \\", - "all special chars: \\ / : * ? \" < > | %", - "injection \" > foo.txt", - "injection \" & echo haha", - "double escaping \\\" > foo.txt" - }; - - args.CmdLineArgs = expected; - - var runner = CreateProcessRunner(logger); - - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(0, "Unexpected exit code"); - - // Check that the public and private arguments are passed to the child process - var exeLogFile = DummyExeHelper.AssertDummyExeLogExists(testDir, TestContext); - DummyExeHelper.AssertExpectedLogContents(exeLogFile, expected); - } - - [TestMethod] - public void ProcRunner_ArgumentQuotingForwardedByBatchScript() - { - // Checks arguments passed to a batch script which itself passes them on are correctly escaped - - // Arrange - var testDir = CreateTestSpecificFolder(TestContext); - // Create a dummy exe that will produce a log file showing any input args - var exeName = DummyExeHelper.CreateDummyExe(testDir, 0); - - var batchName = WriteBatchFileForTest(TestContext, "\"" + exeName + "\" %*"); - - var logger = new TestLogger(); - var args = new ProcessRunnerArguments(batchName, true); - - var expected = new[] { - "unquoted", - "\"quoted\"", - "\"quoted with spaces\"", - "/test:\"quoted arg\"", - "unquoted with spaces", - "quote in \"the middle", - "quotes \"& ampersands", - "\"multiple \"\"\" quotes \" ", - "trailing backslash \\", - "all special chars: \\ / : * ? \" < > | %", - "injection \" > foo.txt", - "injection \" & echo haha", - "double escaping \\\" > foo.txt" - }; - - args.CmdLineArgs = expected; - - var runner = CreateProcessRunner(logger); - - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(0, "Unexpected exit code"); - - // Check that the public and private arguments are passed to the child process - var exeLogFile = DummyExeHelper.AssertDummyExeLogExists(testDir, TestContext); - DummyExeHelper.AssertExpectedLogContents(exeLogFile, expected); - } - - [TestMethod] - [WorkItem(126)] // Exclude secrets from log data: http://jira.sonarsource.com/browse/SONARMSBRU-126 - public void ProcRunner_DoNotLogSensitiveData() - { - // Arrange - var testDir = CreateTestSpecificFolder(TestContext); - // Create a dummy exe that will produce a log file showing any input args - var exeName = DummyExeHelper.CreateDummyExe(testDir, 0); - - var logger = new TestLogger(); - - // Public args - should appear in the log - var publicArgs = new string[] - { - "public1", - "public2", - "/d:sonar.projectKey=my.key" - }; - - var sensitiveArgs = new string[] { - // Public args - should appear in the log - "public1", "public2", "/dmy.key=value", - - // Sensitive args - should not appear in the log - "/d:sonar.password=secret data password", - "/d:sonar.login=secret data login", - "/d:sonar.jdbc.password=secret data db password", - "/d:sonar.jdbc.username=secret data db user name", - - // Sensitive args - different cases -> exclude to be on the safe side - "/d:SONAR.jdbc.password=secret data db password upper", - "/d:sonar.PASSWORD=secret data password upper", - - // Sensitive args - parameter format is slightly incorrect -> exclude to be on the safe side - "/dsonar.login =secret data key typo", - "sonar.password=secret data password typo" - }; - - var allArgs = sensitiveArgs.Union(publicArgs).ToArray(); - - var runnerArgs = new ProcessRunnerArguments(exeName, false) - { - CmdLineArgs = allArgs, - - // Specify the arguments we consider to be sensitive. - // Note: this is a change from the S4MSB which has a hard-coded set of sensitive keys. - SensitivePropertyKeys = new string[] - { - "sonar.password", "sonar.login", "sonar.jdbc.password", "sonar.jdbc.username" - } - }; - - var runner = CreateProcessRunner(logger); - - // Act - runner.Execute(runnerArgs); - - // Assert - runner.ExitCode.Should().Be(0, "Unexpected exit code"); - - // Check public arguments are logged but private ones are not - foreach (var arg in publicArgs) - { - logger.AssertSingleDebugMessageExists(arg); - } - - logger.AssertSingleDebugMessageExists(""); - AssertTextDoesNotAppearInLog("secret", logger); - - // Check that the public and private arguments are passed to the child process - var exeLogFile = DummyExeHelper.AssertDummyExeLogExists(testDir, TestContext); - DummyExeHelper.AssertExpectedLogContents(exeLogFile, allArgs); - } - - [TestMethod] - public void Execute_CancellationTokenIsAlreadyCancelled_ProcessNotExecuted() - { - var exeName = WriteBatchFileForTest(TestContext, - @"@echo Hello world -xxx yyy -@echo Testing 1,2,3...>&2 -"); - - var logger = new TestLogger(); - var args = new ProcessRunnerArguments(exeName, true); - var runner = CreateProcessRunner(logger); - - args.CancellationToken = new CancellationToken(true); - var output = ""; - args.HandleOutputStream = reader => output = reader.ReadToEnd(); - // Act - runner.Execute(args); - - // Assert - runner.ExitCode.Should().Be(0, "Unexpected exit code"); - - output.Should().NotContain("Hello world"); - } - - [TestMethod] - public void Execute_CancellationTokenCancelledMidway_CancelledDuringWritingRequest_ProcessKilled() - { - var exeName = WriteBatchFileForTest(TestContext, @" -echo started! -:again - set /p arg= - echo %arg% - if %arg% == END (goto finished) - goto again -:finished - echo done!"); - - using var processCancellationTokenSource = new CancellationTokenSource(); - var logger = new TestLogger(true, true); - var args = new ProcessRunnerArguments(exeName, true) - { - CancellationToken = processCancellationTokenSource.Token - }; - var output = ""; - args.HandleOutputStream = reader => output = reader.ReadToEnd(); - args.HandleInputStream = writer => - { - writer.WriteLine("dummy"); - Thread.Sleep(2500); - writer.WriteLine("END"); - }; - - var runner = CreateProcessRunner(logger); - var processTask = Task.Run(() => { runner.Execute(args); }); - - processCancellationTokenSource.CancelAfter(500); - - Task.WaitAll(new[] { processTask }, TimeSpan.FromSeconds(15)); - - runner.ExitCode.Should().Be(-1, "Unexpected exit code"); - processCancellationTokenSource.IsCancellationRequested.Should().BeTrue(); - output.Should().Contain("started!"); - output.Should().Contain("dummy"); - output.Should().NotContain("done!"); - } - - [TestMethod] - public void Execute_CancellationTokenCancelledAfterProcessAlreadyFinished_DoesNotThrow() - { - var exeName = WriteBatchFileForTest(TestContext, - @"@echo Hello world -xxx yyy -@echo Testing 1,2,3...>&2 -"); - - var logger = new TestLogger(); - var args = new ProcessRunnerArguments(exeName, true); - var runner = CreateProcessRunner(logger); - - var cancellationTokenSource = new CancellationTokenSource(); - args.CancellationToken = cancellationTokenSource.Token; - - runner.Execute(args); - - Action act = () => cancellationTokenSource.Cancel(false); - - act.Should().NotThrow(); - } - - #region Private methods - - private static ProcessRunner CreateProcessRunner(ILogger logger) - { - return new ProcessRunner(new ConfigurableSonarLintSettings(), logger); - } - - private static void SafeSetEnvironmentVariable(string key, string value, EnvironmentVariableTarget target, ILogger logger) - { - try - { - Environment.SetEnvironmentVariable(key, value, target); - } - catch (System.Security.SecurityException) - { - logger.WriteLine("TEST SETUP ERROR: user running the test doesn't have the permissions to set the environment variable. Key: {0}, value: {1}, target: {2}", - key, value, target); - } - } - - private static void AssertTextDoesNotAppearInLog(string text, TestLogger logger) - { - var matches = logger.OutputStrings.Where(m => m.Contains(text)); - matches.Should().BeEmpty($"Not expecting the text to appear in the log output: {text}"); - } - - /// - /// Creates a batch file with the name of the current test - /// - /// Returns the full file name of the new file - private static string WriteBatchFileForTest(TestContext context, string content) - { - var testPath = CreateTestSpecificFolder(context); - var fileName = Path.Combine(testPath, "test.bat"); - File.Exists(fileName).Should().BeFalse("Not expecting a batch file to already exist: {0}", fileName); - File.WriteAllText(fileName, content); - return fileName; - } - - private static string CreateTestSpecificFolder(TestContext testContext) - { - var testPath = Path.Combine(testContext.DeploymentDirectory, testContext.TestName); - - if (!Directory.Exists(testPath)) - { - Directory.CreateDirectory(testPath); - } - return testPath; - } - - #endregion Private methods - } - - internal static class LoggerExtensions - { - public static void AssertSingleErrorExists(this TestLogger logger, string expected) - { - AssertSinglePartialMessageExists(logger, CFamilyStrings.MSG_Prefix_ERROR, expected); - } - - public static void AssertSingleDebugMessageExists(this TestLogger logger, params string[] expected) - { - var messageParts = new List(expected); - messageParts.Add(CFamilyStrings.MSG_Prefix_DEBUG); - - AssertSinglePartialMessageExists(logger, messageParts.ToArray()); - } - - private static void AssertSinglePartialMessageExists(this TestLogger logger, params string[] expected) - { - var matches = logger.OutputStrings.Where(m => expected.All(e => m.Contains(e))); - matches.Should().ContainSingle("More than one message contains the expected strings: {0}", string.Join(",", expected)); - } - } -} diff --git a/src/CFamily.UnitTests/Subprocess/ProtocolTest.cs b/src/CFamily.UnitTests/Subprocess/ProtocolTest.cs deleted file mode 100644 index e2c0ce4066..0000000000 --- a/src/CFamily.UnitTests/Subprocess/ProtocolTest.cs +++ /dev/null @@ -1,569 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace SonarLint.VisualStudio.CFamily.SubProcess.UnitTests -{ - [TestClass] - public class ProtocolTest - { - - #region Protocol-level reading/writing tests - - [TestMethod] - public void Read_Empty_Response() - { - var response = CallProtocolRead(MockEmptyResponse()); - - response.Messages.Length.Should().Be(0); - } - - [TestMethod] - public void Read_RequestHasFileName_ReturnedMessageHasNoFileName_MessageIsNotIgnored() - { - const string returnedIssueFileName = ""; - - var response = CallProtocolRead(MockResponse(returnedIssueFileName)); - - response.Messages.Length.Should().Be(1); - response.Messages[0].Filename.Should().Be(string.Empty); - } - - [TestMethod] - public void Read_Response() - { - var response = CallProtocolRead(MockResponse()); - - response.Messages.Length.Should().Be(1); - response.Messages[0].RuleKey.Should().Be("ruleKey"); - response.Messages[0].Line.Should().Be(10); - response.Messages[0].Column.Should().Be(11); - response.Messages[0].EndLine.Should().Be(12); - response.Messages[0].EndColumn.Should().Be(13); - response.Messages[0].Text.Should().Be("Issue message"); - response.Messages[0].PartsMakeFlow.Should().Be(true); - response.Messages[0].Parts.Length.Should().Be(1); - } - - [TestMethod] - public void Response_WithDataFlow_DataFlowIgnored() - { - var response = CallProtocolRead(MockResponseWithDataFlow()); - - response.Messages.Length.Should().Be(1); - response.Messages[0].RuleKey.Should().Be("ruleKey"); - response.Messages[0].Line.Should().Be(10); - response.Messages[0].Column.Should().Be(11); - response.Messages[0].EndLine.Should().Be(12); - response.Messages[0].EndColumn.Should().Be(13); - response.Messages[0].Text.Should().Be("Issue message"); - response.Messages[0].PartsMakeFlow.Should().Be(true); - response.Messages[0].Parts.Length.Should().Be(1); - } - - [TestMethod] - public void Response_WithMultipleMessages_MultipleMessageAreReturned() - { - var response = CallProtocolRead(MockResponseWithIssuesFromMultipleFiles()); - - response.Messages.Length.Should().Be(3); - response.Messages[0].Filename.Should().Be("c:\\data\\file1.cpp"); - response.Messages[1].Filename.Should().Be("e:\\data\\file2.cpp"); - response.Messages[2].Filename.Should().Be("E:\\data\\file2.cpp"); - - response.Messages[0].RuleKey.Should().Be("ruleKey1"); - response.Messages[1].RuleKey.Should().Be("ruleKey2"); - response.Messages[2].RuleKey.Should().Be("ruleKey3"); - } - - [TestMethod] - public void Read_Bad_Response_Throw() - { - Action act = () => CallProtocolRead(MockBadStartResponse()); - act.Should().ThrowExactly(); - - act = () => CallProtocolRead(MockBadEndResponse()); - act.Should().ThrowExactly(); - } - - [TestMethod] - public void Read_ResponseWithQuickFixes_QuickFixesRead() - { - var response = CallProtocolRead(MockResponseWithQuickFixes()); - - response.Messages.Length.Should().Be(1); - - response.Messages[0].Fixes.Length.Should().Be(1); - response.Messages[0].Fixes[0].Message.Should().Be("Fix message"); - response.Messages[0].Fixes[0].Edits.Length.Should().Be(1); - response.Messages[0].Fixes[0].Edits[0].Text.Should().Be("Edit message"); - response.Messages[0].Fixes[0].Edits[0].StartLine.Should().Be(1); - response.Messages[0].Fixes[0].Edits[0].StartColumn.Should().Be(2); - response.Messages[0].Fixes[0].Edits[0].EndLine.Should().Be(3); - response.Messages[0].Fixes[0].Edits[0].EndColumn.Should().Be(4); - } - - private static Response CallProtocolRead(byte[] data) - { - using (MemoryStream stream = new MemoryStream(data)) - { - BinaryReader reader = new BinaryReader(stream); - - var messages = new List(); - Protocol.Read(reader, messages.Add); - - return new Response(messages.ToArray()); - } - } - - #endregion // Protocol-level reading/writing tests - - #region Low-level reading/writing tests - - [TestMethod] - public void Write_UTF8() - { - WriteUtf("").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 0 }); - WriteUtf("a").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 1, 97 }); - WriteUtf("A").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 1, 65 }); - WriteUtf("0").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 1, 48 }); - WriteUtf("\n").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 1, 10 }); - // 3 bytes - WriteUtf("\u0800").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 3, 224, 160, 128 }); - // NUL - WriteUtf("\u0000").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 1, 0 }); - // Supplementary characters - WriteUtf("\U00010400").Should().BeEquivalentTo(new byte[] { 0, 0, 0, 4, 0xF0, 0x90, 0x90, 0x80 }); - } - - [TestMethod] - public void Read_UTF8() - { - ReadUtf(WriteUtf("")).Should().Be(""); - ReadUtf(WriteUtf("a")).Should().Be("a"); - ReadUtf(WriteUtf("A")).Should().Be("A"); - ReadUtf(WriteUtf("0")).Should().Be("0"); - ReadUtf(WriteUtf("\n")).Should().Be("\n"); - // 3 bytes - ReadUtf(WriteUtf("\u0800")).Should().Be("\u0800"); - } - - [TestMethod] - public void Write_Int() - { - WriteInt(0).Should().BeEquivalentTo(new byte[] { 0, 0, 0, 0 }); - WriteInt(1).Should().BeEquivalentTo(new byte[] { 0, 0, 0, 1 }); - WriteInt(int.MaxValue).Should().BeEquivalentTo(new byte[] { 0x7F, 0xFF, 0xFF, 0xFF }); - WriteInt(int.MinValue).Should().BeEquivalentTo(new byte[] { 0x80, 0x00, 0x00, 0x00 }); - } - - [TestMethod] - public void Read_Int() - { - ReadInt(WriteInt(0)).Should().Be(0); - ReadInt(WriteInt(int.MaxValue)).Should().Be(int.MaxValue); - ReadInt(WriteInt(int.MinValue)).Should().Be(int.MinValue); - } - - private byte[] WriteUtf(string s) - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, s); - - return stream.ToArray(); - } - } - - private string ReadUtf(byte[] bytes) - { - using (MemoryStream stream = new MemoryStream(bytes)) - { - BinaryReader reader = new BinaryReader(stream); - return Protocol.ReadUTF(reader); - } - } - - private byte[] WriteInt(int i) - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteInt(writer, i); - - return stream.ToArray(); - } - } - - private int ReadInt(byte[] bytes) - { - using (MemoryStream stream = new MemoryStream(bytes)) - { - BinaryReader reader = new BinaryReader(stream); - return Protocol.ReadInt(reader); - } - } - - #endregion // Low-level reading/writing tests - - private byte[] MockEmptyResponse() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - - // 0 issues - - // 0 measures - Protocol.WriteUTF(writer, "measures"); - Protocol.WriteInt(writer, 0); - - // 0 symbols - Protocol.WriteUTF(writer, "symbols"); - Protocol.WriteInt(writer, 0); - - Protocol.WriteUTF(writer, "END"); - return stream.ToArray(); - } - } - - private byte[] MockBadStartResponse() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "FOO"); - return stream.ToArray(); - } - } - - private byte[] MockBadEndResponse() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - Protocol.WriteUTF(writer, "FOO"); - return stream.ToArray(); - } - } - - private byte[] MockResponse(string fileName = "file.cpp") - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - - // 1 issue - Protocol.WriteUTF(writer, "message"); - - Protocol.WriteUTF(writer, "ruleKey"); - Protocol.WriteUTF(writer, fileName); - Protocol.WriteInt(writer, 10); - Protocol.WriteInt(writer, 11); - Protocol.WriteInt(writer, 12); - Protocol.WriteInt(writer, 13); - Protocol.WriteInt(writer, 100); - Protocol.WriteUTF(writer, "Issue message"); - writer.Write(true); - - // 1 flow - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "another.cpp"); - Protocol.WriteInt(writer, 14); - Protocol.WriteInt(writer, 15); - Protocol.WriteInt(writer, 16); - Protocol.WriteInt(writer, 17); - Protocol.WriteUTF(writer, "Flow message"); - - // 0 Data Flow - Protocol.WriteInt(writer, 0); - - // 0 fixes - writer.Write(false); - Protocol.WriteInt(writer, 0); - - // 1 measure - Protocol.WriteUTF(writer, "measures"); - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, fileName); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - byte[] execLines = new byte[] { 1, 2, 3, 4 }; - Protocol.WriteInt(writer, execLines.Length); - writer.Write(execLines); - - - // 1 symbol - Protocol.WriteUTF(writer, "symbols"); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - Protocol.WriteUTF(writer, "END"); - return stream.ToArray(); - } - } - - private byte[] MockResponseWithIssuesFromMultipleFiles() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - - // Issue 1 - Protocol.WriteUTF(writer, "message"); - Protocol.WriteUTF(writer, "ruleKey1"); - Protocol.WriteUTF(writer, "c:\\data\\file1.cpp"); - Protocol.WriteInt(writer, 10); - Protocol.WriteInt(writer, 11); - Protocol.WriteInt(writer, 12); - Protocol.WriteInt(writer, 13); - Protocol.WriteInt(writer, 100); - Protocol.WriteUTF(writer, "Issue message"); - - writer.Write(false); // no flow - Protocol.WriteInt(writer, 0); - - // 0 Data Flow - Protocol.WriteInt(writer, 0); - - // 0 fixes - writer.Write(false); - Protocol.WriteInt(writer, 0); - - // Issue 2 - Protocol.WriteUTF(writer, "message"); - Protocol.WriteUTF(writer, "ruleKey2"); - Protocol.WriteUTF(writer, "e:\\data\\file2.cpp"); - Protocol.WriteInt(writer, 10); - Protocol.WriteInt(writer, 11); - Protocol.WriteInt(writer, 12); - Protocol.WriteInt(writer, 13); - Protocol.WriteInt(writer, 100); - Protocol.WriteUTF(writer, "Issue message"); - - writer.Write(false); // no flow - Protocol.WriteInt(writer, 0); - - // 0 Data Flow - Protocol.WriteInt(writer, 0); - - // 0 fixes - writer.Write(false); - Protocol.WriteInt(writer, 0); - - // Issue 3 - Protocol.WriteUTF(writer, "message"); - Protocol.WriteUTF(writer, "ruleKey3"); - Protocol.WriteUTF(writer, "E:\\data\\file2.cpp"); - Protocol.WriteInt(writer, 10); - Protocol.WriteInt(writer, 11); - Protocol.WriteInt(writer, 12); - Protocol.WriteInt(writer, 13); - Protocol.WriteInt(writer, 100); - Protocol.WriteUTF(writer, "Issue message"); - - writer.Write(false); // no flow - Protocol.WriteInt(writer, 0); - - // 0 Data Flow - Protocol.WriteInt(writer, 0); - - // 0 fixes - writer.Write(false); - Protocol.WriteInt(writer, 0); - - // 1 measure - Protocol.WriteUTF(writer, "measures"); - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "file.cpp"); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - byte[] execLines = new byte[] { 1, 2, 3, 4 }; - Protocol.WriteInt(writer, execLines.Length); - writer.Write(execLines); - - // 1 symbol - Protocol.WriteUTF(writer, "symbols"); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - Protocol.WriteUTF(writer, "END"); - return stream.ToArray(); - } - } - - private byte[] MockResponseWithQuickFixes() - { - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - - // 1 issue - Protocol.WriteUTF(writer, "message"); - - Protocol.WriteUTF(writer, "ruleKey"); - Protocol.WriteUTF(writer, "cpp1.cpp"); - Protocol.WriteInt(writer, 10); - Protocol.WriteInt(writer, 11); - Protocol.WriteInt(writer, 12); - Protocol.WriteInt(writer, 13); - Protocol.WriteInt(writer, 100); - Protocol.WriteUTF(writer, "Issue message"); - writer.Write(true); - - // 1 flow - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "another.cpp"); - Protocol.WriteInt(writer, 14); - Protocol.WriteInt(writer, 15); - Protocol.WriteInt(writer, 16); - Protocol.WriteInt(writer, 17); - Protocol.WriteUTF(writer, "Flow message"); - - // 0 Data Flow - Protocol.WriteInt(writer, 0); - - // 1 fix - writer.Write(true); - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "Fix message"); - // 1 fix edit - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); // start line - Protocol.WriteInt(writer, 2); // end line - Protocol.WriteInt(writer, 3); // start column - Protocol.WriteInt(writer, 4); // end column - Protocol.WriteUTF(writer, "Edit message"); - - // 0 measures - Protocol.WriteUTF(writer, "measures"); - Protocol.WriteInt(writer, 0); - - // 0 symbols - Protocol.WriteUTF(writer, "symbols"); - Protocol.WriteInt(writer, 0); - - Protocol.WriteUTF(writer, "END"); - return stream.ToArray(); - } - } - - private byte[] MockResponseWithDataFlow() - { - string fileName = "file.cpp"; - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - Protocol.WriteUTF(writer, "OUT"); - - // 1 issue - Protocol.WriteUTF(writer, "message"); - - Protocol.WriteUTF(writer, "ruleKey"); - Protocol.WriteUTF(writer, fileName); - Protocol.WriteInt(writer, 10); - Protocol.WriteInt(writer, 11); - Protocol.WriteInt(writer, 12); - Protocol.WriteInt(writer, 13); - Protocol.WriteInt(writer, 100); - Protocol.WriteUTF(writer, "Issue message"); - writer.Write(true); - - // 1 flow - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "another.cpp"); - Protocol.WriteInt(writer, 14); - Protocol.WriteInt(writer, 15); - Protocol.WriteInt(writer, 16); - Protocol.WriteInt(writer, 17); - Protocol.WriteUTF(writer, "Flow message"); - - // 1 Data Flow - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "DataFlow 1"); - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, "another.cpp"); - Protocol.WriteInt(writer, 24); - Protocol.WriteInt(writer, 25); - Protocol.WriteInt(writer, 26); - Protocol.WriteInt(writer, 27); - Protocol.WriteUTF(writer, "Data Flow message"); - - // 0 fixes - writer.Write(false); - Protocol.WriteInt(writer, 0); - - // 1 measure - Protocol.WriteUTF(writer, "measures"); - Protocol.WriteInt(writer, 1); - Protocol.WriteUTF(writer, fileName); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - byte[] execLines = new byte[] { 1, 2, 3, 4 }; - Protocol.WriteInt(writer, execLines.Length); - writer.Write(execLines); - - - // 1 symbol - Protocol.WriteUTF(writer, "symbols"); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - Protocol.WriteInt(writer, 1); - - Protocol.WriteUTF(writer, "END"); - return stream.ToArray(); - } - } - } -} diff --git a/src/CFamily/Analysis/ICFamilyIssueConverterFactory.cs b/src/CFamily/Analysis/ICFamilyIssueConverterFactory.cs deleted file mode 100644 index a67a2f98ff..0000000000 --- a/src/CFamily/Analysis/ICFamilyIssueConverterFactory.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core.Analysis; - -namespace SonarLint.VisualStudio.CFamily.Analysis -{ - public interface ICFamilyIssueConverterFactory - { - ICFamilyIssueToAnalysisIssueConverter Create(); - } - - public interface ICFamilyIssueToAnalysisIssueConverter - { - IAnalysisIssue Convert(Message cFamilyIssue, string sqLanguage, ICFamilyRulesConfig rulesConfiguration); - } -} diff --git a/src/CFamily/Analysis/IRequest.cs b/src/CFamily/Analysis/IRequest.cs deleted file mode 100644 index 92b91784ff..0000000000 --- a/src/CFamily/Analysis/IRequest.cs +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; - -namespace SonarLint.VisualStudio.CFamily.Analysis -{ - /// - /// Encapsulates a low-level request to be sent to the CFamily subprocess - /// - /// The subprocess supports several different protocols, each of which requires a different set of inputs. - public interface IRequest - { - RequestContext Context { get; } - - /// - /// Serializes the request in the form required by the subprocess - /// - void WriteRequest(BinaryWriter writer); - - /// - /// Any environment variables that need to be passed to the subprocess. Can be null. - /// - IReadOnlyDictionary EnvironmentVariables { get; } - - /// - /// Serializes the request for diagnostic purposes - /// - void WriteRequestDiagnostics(TextWriter writer); - } -} diff --git a/src/CFamily/Analysis/IRequestFactory.cs b/src/CFamily/Analysis/IRequestFactory.cs deleted file mode 100644 index 48578181cb..0000000000 --- a/src/CFamily/Analysis/IRequestFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Threading.Tasks; - -namespace SonarLint.VisualStudio.CFamily.Analysis -{ - public interface IRequestFactory - { - /// - /// Creates for the given . - /// Returns null if request could not be created. - /// - Task TryCreateAsync(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions); - } - - /// - /// Aggregate interface for multiple . - /// will return the first non-nullable request, - /// or null if no factory was able to create one. - /// - internal interface IRequestFactoryAggregate : IRequestFactory - {} -} diff --git a/src/CFamily/Analysis/RequestContext.cs b/src/CFamily/Analysis/RequestContext.cs deleted file mode 100644 index f2f80273a0..0000000000 --- a/src/CFamily/Analysis/RequestContext.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.CFamily.Rules; - -namespace SonarLint.VisualStudio.CFamily.Analysis -{ - /// - /// Data class containing information about an analysis request - /// - public class RequestContext - { - public RequestContext(string language, ICFamilyRulesConfig rulesConfig, string file, string pchFile, CFamilyAnalyzerOptions analyzerOptions, bool isHeaderFile) - { - CFamilyLanguage = language; - RulesConfiguration = rulesConfig; - File = file; - PchFile = pchFile; - AnalyzerOptions = analyzerOptions; - IsHeaderFile = isHeaderFile; - } - - // Note: the language and RulesConfiguration aren't passed as part of the request to the - // CLang analyzer, but it is by SVLS used when filtering the returned issues. - public string CFamilyLanguage { get; } - - public ICFamilyRulesConfig RulesConfiguration { get; } - - /// - /// The full path to the file being analyzed - /// - public string File { get; } - - /// - /// Full path to the precompiled header file (also called the "preamble" file) - /// - /// The file may not exist. If it does, it might be out of date or for a different file. - /// However, it is the responsibility of the CFamily subprocess to handle all of those scenarios. - public string PchFile { get; } - - /// - /// Additional analysis options - /// - public CFamilyAnalyzerOptions AnalyzerOptions { get; } - - public bool IsHeaderFile { get; } - } -} diff --git a/src/CFamily/Analysis/RequestFactoryAggregate.cs b/src/CFamily/Analysis/RequestFactoryAggregate.cs deleted file mode 100644 index 3dca0643a4..0000000000 --- a/src/CFamily/Analysis/RequestFactoryAggregate.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.ComponentModel.Composition; -using System.Threading.Tasks; - -namespace SonarLint.VisualStudio.CFamily.Analysis -{ - [Export(typeof(IRequestFactoryAggregate))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class RequestFactoryAggregate : IRequestFactoryAggregate - { - private readonly IEnumerable requestFactories; - - [ImportingConstructor] - public RequestFactoryAggregate([ImportMany] IEnumerable requestFactories) - { - this.requestFactories = requestFactories; - } - - public Task TryCreateAsync(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) - { - if (string.IsNullOrEmpty(analyzedFilePath)) - { - throw new ArgumentNullException(nameof(analyzedFilePath)); - } - - return TryCreate(analyzedFilePath, analyzerOptions); - } - - private async Task TryCreate(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) - { - foreach (var requestFactory in requestFactories) - { - var request = await requestFactory.TryCreateAsync(analyzedFilePath, analyzerOptions); - - if (request != null) - { - return request; - } - } - - return null; - } - } -} diff --git a/src/CFamily/CFamily.csproj b/src/CFamily/CFamily.csproj index 636664ad6a..19bc56c2f1 100644 --- a/src/CFamily/CFamily.csproj +++ b/src/CFamily/CFamily.csproj @@ -25,11 +25,6 @@ True Resources.resx - - True - True - Resources.resx - True True @@ -42,10 +37,6 @@ ResXFileCodeGenerator Resources.Designer.cs - - ResXFileCodeGenerator - Resources.Designer.cs - ResXFileCodeGenerator CFamilyStrings.Designer.cs diff --git a/src/CFamily/CFamilyShared.cs b/src/CFamily/CFamilyShared.cs deleted file mode 100644 index c5af58b894..0000000000 --- a/src/CFamily/CFamilyShared.cs +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO; -using System.Linq; -using SonarLint.VisualStudio.Core; - -namespace SonarLint.VisualStudio.CFamily -{ - public static class CFamilyShared - { - public static readonly string CFamilyFilesDirectory = Path.Combine( - Path.GetDirectoryName(typeof(CFamilyShared).Assembly.Location), - "lib"); - - public static readonly StringComparer RuleKeyComparer = SonarRuleRepoKeys.RepoKeyComparer; - public static readonly StringComparison RuleKeyComparison = StringComparison.Ordinal; - - public static readonly string[] KnownExtensions = {".cpp", ".cxx", ".cc", ".c"}; - - private static readonly string[] KnownHeaderFileExtensions = {".h", ".hpp", ".hh", ".hxx"}; - - /// - /// Attempts to detect whether the file is C or C++ based on the file extension. - /// Returns null if the extension is not recognised. - /// - public static string FindLanguageFromExtension(string analyzedFilePath) - { - string cfamilyLanguage = null; - - // Compile files with extensions ".cpp", ".cxx" and ".cc" as Cpp and files with extension ".c" as C - if (analyzedFilePath.EndsWith(".cpp", StringComparison.OrdinalIgnoreCase) || - analyzedFilePath.EndsWith(".cxx", StringComparison.OrdinalIgnoreCase) || - analyzedFilePath.EndsWith(".cc", StringComparison.OrdinalIgnoreCase)) - { - cfamilyLanguage = SonarLanguageKeys.CPlusPlus; - } - else if (analyzedFilePath.EndsWith(".c", StringComparison.OrdinalIgnoreCase)) - { - cfamilyLanguage = SonarLanguageKeys.C; - } - - return cfamilyLanguage; - } - - /// - /// Returns true/false if the file's extension is a known header file extension. - /// - public static bool IsHeaderFileExtension(string filePath) - { - var extension = Path.GetExtension(filePath); - - return KnownHeaderFileExtensions.Any(x => - x.Equals(extension, StringComparison.OrdinalIgnoreCase)); - } - } -} diff --git a/src/CFamily/CMake/CMakeProjectTypeIndicator.cs b/src/CFamily/CMake/CMakeProjectTypeIndicator.cs deleted file mode 100644 index 62e0aaacd2..0000000000 --- a/src/CFamily/CMake/CMakeProjectTypeIndicator.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.ComponentModel.Composition; -using System.IO; -using System.IO.Abstractions; -using System.Linq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.CFamily; - -namespace SonarLint.VisualStudio.CFamily.CMake -{ - [Export(typeof(ICMakeProjectTypeIndicator))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class CMakeProjectTypeIndicator : ICMakeProjectTypeIndicator - { - private readonly IFolderWorkspaceService folderWorkspaceService; - private readonly IFileSystem fileSystem; - - [ImportingConstructor] - public CMakeProjectTypeIndicator(IFolderWorkspaceService folderWorkspaceService) - : this(folderWorkspaceService, new FileSystem()) - { - } - - internal CMakeProjectTypeIndicator(IFolderWorkspaceService folderWorkspaceService, IFileSystem fileSystem) - { - this.folderWorkspaceService = folderWorkspaceService; - this.fileSystem = fileSystem; - } - - public bool IsCMake() - { - var isOpenAsFolder = folderWorkspaceService.IsFolderWorkspace(); - - if (!isOpenAsFolder) - { - return false; - } - - var rootDirectory = folderWorkspaceService.FindRootDirectory(); - - var isCMake = fileSystem.Directory.EnumerateFiles(rootDirectory, "CMakeLists.txt", SearchOption.AllDirectories).Any(); - - return isCMake; - } - } -} diff --git a/src/CFamily/CMake/EnvironmentVarsProvider.cs b/src/CFamily/CMake/EnvironmentVarsProvider.cs deleted file mode 100644 index 9776fedccc..0000000000 --- a/src/CFamily/CMake/EnvironmentVarsProvider.cs +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; -using SonarLint.VisualStudio.Core; - -namespace SonarLint.VisualStudio.CFamily.CMake -{ - /// - /// Returns the environment settings to be passed to the subprocess - /// for an analysis request. - /// - internal interface IEnvironmentVarsProvider - { - Task> GetAsync(); - } - - [Export(typeof(IEnvironmentVarsProvider))] - internal class EnvironmentVarsProvider : IEnvironmentVarsProvider - { - private readonly IVsDevCmdEnvironmentProvider vsDevCmdProvider; - private readonly ILogger logger; - private readonly IDictionary> cachedVsDevCmdSettings; - - private readonly SemaphoreSlim fetchLock = new SemaphoreSlim(1, 1); - - [ImportingConstructor] - public EnvironmentVarsProvider(IVsDevCmdEnvironmentProvider vsDevCmdProvider, ILogger logger) - { - this.vsDevCmdProvider = vsDevCmdProvider; - this.logger = logger; - cachedVsDevCmdSettings = new Dictionary>(); - } - - public async Task> GetAsync() - { - // TODO - work out which script parameters to pass - string vsDevCmdScriptParams = string.Empty; - - return await GetAsync(vsDevCmdScriptParams); - } - - internal /* for testing */ async Task> GetAsync(string vsDevCmdScriptParams) - { - // Try to get the cached settings - if (cachedVsDevCmdSettings.TryGetValue(vsDevCmdScriptParams, out var cachedSettings)) - { - LogDebug($"Cache hit. Script params: \"{vsDevCmdScriptParams}\""); - return cachedSettings; - } - - LogDebug($"Cache miss. Script params: \"{vsDevCmdScriptParams}\""); - return await FetchAndCacheVsDevCmdSettingsAsync(vsDevCmdScriptParams); - } - - private async Task> FetchAndCacheVsDevCmdSettingsAsync(string vsDevCmdScriptParams) - { - LogDebug("\tWaiting to acquire lock..."); - - await fetchLock.WaitAsync(); - - try - { - LogDebug("\tAcquired lock."); - // Re-check that the settings weren't fetch while we were waiting to obtain the lock - if (cachedVsDevCmdSettings.TryGetValue(vsDevCmdScriptParams, out var cachedSettings)) - { - LogDebug($"Cache hit (inside lock). Script params: \"{vsDevCmdScriptParams}\""); - return cachedSettings; - } - - var newSettings = await vsDevCmdProvider.GetAsync(vsDevCmdScriptParams); - cachedVsDevCmdSettings[vsDevCmdScriptParams] = newSettings; - - return newSettings; - } - finally - { - fetchLock.Release(); - } - } - - private void LogDebug(string message) - { - logger.LogVerbose($"[CMake:EnvVars] [Thread id: {Thread.CurrentThread.ManagedThreadId}] {message}"); - } - } -} diff --git a/src/CFamily/CMake/VsDevCmdEnvironmentVarsProvider.cs b/src/CFamily/CMake/VsDevCmdEnvironmentVarsProvider.cs deleted file mode 100644 index a94d1eda8c..0000000000 --- a/src/CFamily/CMake/VsDevCmdEnvironmentVarsProvider.cs +++ /dev/null @@ -1,237 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.IO; -using System.IO.Abstractions; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.SystemAbstractions; -using SonarLint.VisualStudio.Infrastructure.VS; -using IThreadHandling = SonarLint.VisualStudio.Core.IThreadHandling; -using ErrorHandler = Microsoft.VisualStudio.ErrorHandler; - -namespace SonarLint.VisualStudio.CFamily.CMake -{ - internal interface IVsDevCmdEnvironmentProvider - { - /// - /// Returns the environment settings set by VsDevCmd.bat - /// - /// Any additional parameters to pass to the batch file. Can be null. - Task> GetAsync(string scriptParams); - } - - [Export(typeof(IVsDevCmdEnvironmentProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class VsDevCmdEnvironmentVarsProvider : IVsDevCmdEnvironmentProvider - { - /// - /// Relative path from the VS installation root to the batch file - /// - /// This path works for VS2015+ - private const string RelativePathToBatchFile = "Common7\\Tools\\VsDevCmd.bat"; - - private const int SCRIPT_TIMEOUT_MS = 30000; - - private readonly IVsInfoService vsInfoService; - private readonly IThreadHandling threadHandling; - private readonly ILogger logger; - - // Interfaces for testing - private readonly IProcessFactory processFactory; - private readonly IFileSystem fileSystem; - - [ImportingConstructor] - public VsDevCmdEnvironmentVarsProvider(IVsInfoService vsInfoService, ILogger logger) - : this(vsInfoService, ThreadHandling.Instance, logger, new ProcessFactory(), new FileSystem()) - { - } - - internal /* for testing */ VsDevCmdEnvironmentVarsProvider(IVsInfoService vsInfoService, IThreadHandling threadHandling, ILogger logger, - IProcessFactory processFactory, IFileSystem fileSystem) - { - this.vsInfoService = vsInfoService; - this.threadHandling = threadHandling; - this.logger = logger; - this.processFactory = processFactory; - this.fileSystem = fileSystem; - } - - public async Task> GetAsync(string scriptParams) - { - threadHandling.ThrowIfOnUIThread(); - - try - { - string filePath = Path.Combine(vsInfoService.InstallRootDir, RelativePathToBatchFile); - LogDebug($"VsDevCmd location: {filePath}"); - - if (!fileSystem.File.Exists(filePath)) - { - logger.WriteLine(Resources.VsDevCmd_FileNotFound, filePath); - return null; - } - - var capturedOutput = await ExecuteVsDevCmd(filePath, scriptParams); - var settings = ParseOutput(capturedOutput); - - if (settings == null || settings.Count == 0) - { - logger.WriteLine(Resources.VsDevCmd_NoSettingsFound); - return null; - } - - LogDebug($"Settings fetched successfully. Count: {settings.Count}"); - return settings; - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - logger.WriteLine(Resources.VsDevCmd_ErrorFetchingSettings, ex); - } - - return null; - } - - internal /* for testing */ string UniqueId { get; private set;} - - private async Task> ExecuteVsDevCmd(string batchFilePath, string scriptParams) - { - UniqueId = Guid.NewGuid().ToString(); - var beginToken = "SONARLINT_BEGIN_CAPTURE " + UniqueId; - var endToken = "SONARLINT_END_CAPTURE " + UniqueId; - - ProcessStartInfo startInfo = new ProcessStartInfo() - { - FileName = Environment.GetEnvironmentVariable("COMSPEC"), - Arguments = "/U " + // Unicode input / output - "/K " + // Execute the command and remain active. Need to do this or we can't fetch the env vars - "set VSCMD_SKIP_SENDTELEMETRY=1 && " + // Disable VS telemetry - "\"" + batchFilePath + "\" " + scriptParams + // Call VsDevCmd with any additional parameters - " && echo " + beginToken + // Output a marker so we known when to start processing the env vars - " && set" + // Write out the env vars - " && echo " + endToken + " \"", // Output a marker to say we are done - - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardInput = false, - RedirectStandardError = false, - StandardOutputEncoding = Encoding.Unicode - }; - - var capturedOutput = new List(); - - var capturingOutput = false; - - using (var process = processFactory.Start(startInfo)) - { - process.HandleOutputDataReceived = data => - { - if (data == null) { return; } - - if (!capturingOutput && data.StartsWith(beginToken)) - { - capturingOutput = true; - return; - } - else if (capturingOutput && data.StartsWith(endToken)) - { - capturingOutput = false; - SafeKillProcess(process); - } - - if (capturingOutput) - { - capturedOutput.Add(data); - } - }; - - LogDebug($"Started process. Id: {process.Id}"); - process.BeginOutputReadLine(); - - // Timeout in case something goes wrong. - await process.WaitForExitAsync(SCRIPT_TIMEOUT_MS); - if (process.HasExited) - { - LogDebug("Process completed successfully"); - } - else - { - logger.WriteLine(Resources.VsDevCmd_TimedOut); - capturedOutput = null; - SafeKillProcess(process); - } - } - - return capturedOutput; - } - - private static Dictionary ParseOutput(IList capturedOutput) - { - if (capturedOutput == null) - { - return null; - } - - var settings = new Dictionary(); - - foreach (var item in capturedOutput) - { - var equalsIndex = item.IndexOf("=", StringComparison.OrdinalIgnoreCase); - if (equalsIndex > -1) - { - var key = item.Substring(0, equalsIndex); - var newValue = item.Substring(equalsIndex + 1); - - settings.Add(key, newValue); - } - } - - return settings; - } - - private void SafeKillProcess(IProcess process) - { - // Ignore any errors when terminating the process - try - { - if (!process.HasExited) - { - process.Kill(); - } - } - catch (Exception ex) - { - LogDebug($"Error terminating VsDevCmd.bat: {ex.Message}"); - } - } - - private void LogDebug(string message) - { - logger.LogVerbose($"[CMake:VsDevCmd] [Thread id: {Thread.CurrentThread.ManagedThreadId}] {message}"); - } - } -} diff --git a/src/CFamily/CompilationDatabase/CompilationDatabaseRequest.cs b/src/CFamily/CompilationDatabase/CompilationDatabaseRequest.cs deleted file mode 100644 index 7d9e8db996..0000000000 --- a/src/CFamily/CompilationDatabase/CompilationDatabaseRequest.cs +++ /dev/null @@ -1,152 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; -using Newtonsoft.Json; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.CMake; -using SonarLint.VisualStudio.CFamily.SubProcess; - -namespace SonarLint.VisualStudio.CFamily.CompilationDatabase -{ - /// - /// Encapsulates a low-level analysis request for the CFamily compilation database entry protocol - /// Used for both CMake and Vcx projects - /// - public class CompilationDatabaseRequest : IRequest - { - private readonly CompilationDatabaseEntry databaseEntry; - private readonly IRulesConfigProtocolFormatter rulesConfigProtocolFormatter; - - public CompilationDatabaseRequest(CompilationDatabaseEntry databaseEntry, RequestContext context, IReadOnlyDictionary environmentVariables) - : this(databaseEntry, context, environmentVariables, new RulesConfigProtocolFormatter()) - { - } - - internal CompilationDatabaseRequest(CompilationDatabaseEntry databaseEntry, - RequestContext context, IReadOnlyDictionary environmentVariables, - IRulesConfigProtocolFormatter rulesConfigProtocolFormatter) - { - this.databaseEntry = databaseEntry ?? throw new ArgumentNullException(nameof(databaseEntry)); - EnvironmentVariables = environmentVariables; - this.rulesConfigProtocolFormatter = rulesConfigProtocolFormatter; - Context = context ?? throw new ArgumentNullException(nameof(context)); - - // Must have a Command or Arguments but not both - var hasArgs = !string.IsNullOrEmpty(databaseEntry.Arguments); - var hasCmd = !string.IsNullOrEmpty(databaseEntry.Command); - - if (!hasArgs && !hasCmd || (hasArgs && hasCmd)) - { - throw new ArgumentException(CFamilyStrings.ERROR_InvalidCompilationEntry); - } - } - - public RequestContext Context { get; } - - public IReadOnlyDictionary EnvironmentVariables { get; } - - public CompilationDatabaseEntry DatabaseEntry => databaseEntry; - - public void WriteRequest(BinaryWriter writer) - { - WriteHeader(writer); - - // Required inputs - WriteSetting(writer, "File", Context.File); - WriteSetting(writer, "Directory", databaseEntry.Directory); - - if(databaseEntry.Arguments == null) - { - WriteSetting(writer, "Command", databaseEntry.Command); - } - else - { - WriteSetting(writer, "Arguments", databaseEntry.Arguments); - } - - WriteRulesConfig(writer); - - // Optional inputs - WriteSetting(writer, "PreambleFile", Context.PchFile); - WriteSetting(writer, "CreateReproducer", Context?.AnalyzerOptions?.CreateReproducer ?? false ? "true" : "false"); - WriteSetting(writer, "BuildPreamble", Context?.AnalyzerOptions?.CreatePreCompiledHeaders ?? false ? "true" : "false"); - - if (Context.IsHeaderFile) - { - WriteSetting(writer, "HeaderFileLanguage", Context.CFamilyLanguage); - } - - WriteFooter(writer); - } - - private void WriteRulesConfig(BinaryWriter writer) - { - // Optimisation - no point in calculating the active rules if we're - // creating a pre-compiled header, as they won't be used. - // However, the QualityProfile is an essential setting so we still - // have to write it. - if (!Context.AnalyzerOptions?.CreatePreCompiledHeaders ?? true) - { - var rulesProtocolFormat = rulesConfigProtocolFormatter.Format(Context.RulesConfiguration); - - WriteQualityProfile(writer, rulesProtocolFormat.QualityProfile); - WriteRuleSettings(writer, rulesProtocolFormat.RuleParameters); - } - else - { - WriteQualityProfile(writer, ""); - } - } - - private static void WriteHeader(BinaryWriter writer) - => Protocol.WriteUTF(writer, "SL-IN"); - - private static void WriteFooter(BinaryWriter writer) - => Protocol.WriteUTF(writer, "SL-END"); - - public void WriteRequestDiagnostics(TextWriter writer) - { - var data = JsonConvert.SerializeObject(databaseEntry, Formatting.Indented); - writer.Write(data); - } - - private void WriteQualityProfile(BinaryWriter writer, string qualityProfile) - { - WriteSetting(writer, "QualityProfile", qualityProfile); - } - - private void WriteRuleSettings(BinaryWriter writer, Dictionary ruleParameters) - { - foreach (var ruleParameter in ruleParameters) - { - WriteSetting(writer, ruleParameter.Key, ruleParameter.Value); - } - } - - private static void WriteSetting(BinaryWriter writer, string key, string value) - { - Protocol.WriteUTF(writer, key); - Protocol.WriteUTF(writer, value); - } - } -} diff --git a/src/CFamily/CompilationDatabase/RulesConfigProtocolFormatter.cs b/src/CFamily/CompilationDatabase/RulesConfigProtocolFormatter.cs deleted file mode 100644 index 132cfd6973..0000000000 --- a/src/CFamily/CompilationDatabase/RulesConfigProtocolFormatter.cs +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using SonarLint.VisualStudio.CFamily.Rules; - -namespace SonarLint.VisualStudio.CFamily.CompilationDatabase -{ - public interface IRulesConfigProtocolFormatter - { - RuleConfigProtocolFormat Format(ICFamilyRulesConfig rulesConfig); - } - - public class RuleConfigProtocolFormat - { - /// - /// Comma-separated list of active rule ids - /// - public string QualityProfile { get; } - - /// - /// The key for each individual setting is in the form {ruleId}.{configname} - /// - public Dictionary RuleParameters { get; } - - public RuleConfigProtocolFormat(string qualityProfile, Dictionary ruleParameters) - { - QualityProfile = qualityProfile; - RuleParameters = ruleParameters; - } - } - - public class RulesConfigProtocolFormatter : IRulesConfigProtocolFormatter - { - public RuleConfigProtocolFormat Format(ICFamilyRulesConfig rulesConfig) - { - if (rulesConfig == null) - { - throw new ArgumentNullException(nameof(rulesConfig)); - } - - var qualityProfile = string.Join(",", rulesConfig.ActivePartialRuleKeys); - var ruleParameters = GetRuleParameters(rulesConfig); - - return new RuleConfigProtocolFormat(qualityProfile, ruleParameters); - } - - private static Dictionary GetRuleParameters(ICFamilyRulesConfig rulesConfiguration) - { - var ruleParameters = new Dictionary(); - - foreach (var ruleKey in rulesConfiguration.ActivePartialRuleKeys) - { - if (rulesConfiguration.RulesParameters.TryGetValue(ruleKey, out var ruleParams)) - { - foreach (var param in ruleParams) - { - var optionKey = ruleKey + "." + param.Key; - ruleParameters.Add(optionKey, param.Value); - } - } - } - - return ruleParameters; - } - } -} diff --git a/src/CFamily/Rules/CFamilyRulesConfigProvider.cs b/src/CFamily/Rules/CFamilyRulesConfigProvider.cs deleted file mode 100644 index 81e567dddc..0000000000 --- a/src/CFamily/Rules/CFamilyRulesConfigProvider.cs +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - [Export(typeof(ICFamilyRulesConfigProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class CFamilyRuleConfigProvider : ICFamilyRulesConfigProvider - { - private readonly EffectiveRulesConfigCalculator effectiveConfigCalculator; - - // Settable in constructor for testing - private readonly IRuleSettingsProviderFactory ruleSettingsProviderFactory; - private readonly ICFamilyRulesConfigProvider sonarWayProvider; - - - [ImportingConstructor] - public CFamilyRuleConfigProvider(IRuleSettingsProviderFactory ruleSettingsProviderFactory, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration, - ILogger logger) - : this(ruleSettingsProviderFactory, - new CFamilySonarWayRulesConfigProvider(CFamilyShared.CFamilyFilesDirectory), - connectedModeFeaturesConfiguration, - logger) - { - } - - public CFamilyRuleConfigProvider(IRuleSettingsProviderFactory ruleSettingsProviderFactory, - ICFamilyRulesConfigProvider sonarWayProvider, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration, - ILogger logger) - { - this.ruleSettingsProviderFactory = ruleSettingsProviderFactory; - this.sonarWayProvider = sonarWayProvider; - this.effectiveConfigCalculator = new EffectiveRulesConfigCalculator(connectedModeFeaturesConfiguration, logger); - } - - #region IRulesConfigurationProvider implementation - - public ICFamilyRulesConfig GetRulesConfiguration(string languageKey) - { - if (languageKey == null) - { - throw new ArgumentNullException(nameof(languageKey)); - } - - var language = Language.GetLanguageFromLanguageKey(languageKey); - - if (language == null) - { - throw new ArgumentNullException(nameof(language)); - } - - var ruleSettingsProvider = ruleSettingsProviderFactory.Get(language); - var settings = ruleSettingsProvider.Get(); - - var sonarWayConfig = sonarWayProvider.GetRulesConfiguration(languageKey); - return CreateConfiguration(languageKey, sonarWayConfig, settings); - } - - #endregion IRulesConfigurationProvider implementation - - protected virtual /* for testing */ ICFamilyRulesConfig CreateConfiguration(string languageKey, ICFamilyRulesConfig sonarWayConfig, RulesSettings settings) - => effectiveConfigCalculator.GetEffectiveRulesConfig(languageKey, sonarWayConfig, settings); - } -} diff --git a/src/CFamily/Rules/CFamilySonarWayRulesConfigProvider.cs b/src/CFamily/Rules/CFamilySonarWayRulesConfigProvider.cs deleted file mode 100644 index b0f604e1e7..0000000000 --- a/src/CFamily/Rules/CFamilySonarWayRulesConfigProvider.cs +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Linq; -using SonarLint.VisualStudio.Core; - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - /// - /// Loads all of the json files shipped with VSIX that contain the metadata for SonarWay - /// and presents it via the interface - /// - public sealed class CFamilySonarWayRulesConfigProvider : ICFamilyRulesConfigProvider - { - private IEnumerable AllLanguagesAllRuleKeys { get; } - private IEnumerable AllLanguagesActiveRuleKeys { get; } - private IDictionary AllLanguagesRulesMetadata { get; } - private IDictionary> AllLanguagesRulesParameters { get; } - - private IDictionary RulesByLanguage { get; } - - public CFamilySonarWayRulesConfigProvider(string rulesDirectoryPath) - { - var rulesLoader = new RulesLoader(rulesDirectoryPath); - - // Read all rules/metadata, irrespective of language. Stored in - // statics so we don't re-read the files for each language - AllLanguagesAllRuleKeys = rulesLoader.ReadRulesList(); - AllLanguagesActiveRuleKeys = rulesLoader.ReadActiveRulesList(); - AllLanguagesRulesParameters = AllLanguagesAllRuleKeys - .ToDictionary(key => key, key => rulesLoader.ReadRuleParams(key)); - AllLanguagesRulesMetadata = AllLanguagesAllRuleKeys - .ToDictionary(key => key, key => rulesLoader.ReadRuleMetadata(key)); - - RulesByLanguage = new Dictionary - { - { SonarLanguageKeys.CPlusPlus, new SingleLanguageRulesConfiguration(this, SonarLanguageKeys.CPlusPlus)}, - { SonarLanguageKeys.C, new SingleLanguageRulesConfiguration(this, SonarLanguageKeys.C)} - }; - } - - #region ICFamilyRulesConfigProvider implementation - - public ICFamilyRulesConfig GetRulesConfiguration(string languageKey) - { - RulesByLanguage.TryGetValue(languageKey, out var rulesConfiguration); - return rulesConfiguration; - } - - #endregion ICFamilyRulesConfigProvider implementation - - private sealed class SingleLanguageRulesConfiguration : ICFamilyRulesConfig - { - private static readonly StringComparer RuleKeyComparer = StringComparer.OrdinalIgnoreCase; - - public SingleLanguageRulesConfiguration(CFamilySonarWayRulesConfigProvider cache, string cFamilyLanguage) - { - LanguageKey = cFamilyLanguage; - - var ruleKeysForLanguage = cache.AllLanguagesRulesMetadata - .Where(kvp => kvp.Value.CompatibleLanguages.Contains(cFamilyLanguage, RuleKeyComparer)) - .Select(kvp => kvp.Key) - .ToArray(); - - AllPartialRuleKeys = ruleKeysForLanguage; - ActivePartialRuleKeys = cache.AllLanguagesActiveRuleKeys - .Intersect(ruleKeysForLanguage, RuleKeyComparer) - .ToArray(); - - RulesParameters = ruleKeysForLanguage - .ToDictionary(key => key, key => cache.AllLanguagesRulesParameters[key]); - RulesMetadata = ruleKeysForLanguage - .ToDictionary(key => key, key => cache.AllLanguagesRulesMetadata[key]); - } - - public string LanguageKey { get; } - - public IEnumerable AllPartialRuleKeys { get; } - - public IEnumerable ActivePartialRuleKeys { get; } - - public IDictionary> RulesParameters { get; } - - public IDictionary RulesMetadata { get; } - } - } -} diff --git a/src/CFamily/Rules/DynamicCFamilyRulesConfig.cs b/src/CFamily/Rules/DynamicCFamilyRulesConfig.cs deleted file mode 100644 index bf4733d34b..0000000000 --- a/src/CFamily/Rules/DynamicCFamilyRulesConfig.cs +++ /dev/null @@ -1,187 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - /// - /// Wrapper that handles applying customised rules settings on top of the default config - /// - /// The customised rules could be user-specified (if in standalone mode) or generated - /// from a QP (if in connected mode) - internal sealed class DynamicCFamilyRulesConfig : ICFamilyRulesConfig - { - private readonly ICFamilyRulesConfig defaultRulesConfig; - - public DynamicCFamilyRulesConfig(ICFamilyRulesConfig defaultRulesConfig, - RulesSettings customRulesSettings, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration, - ILogger logger) - : this(defaultRulesConfig, customRulesSettings, connectedModeFeaturesConfiguration, logger, new RulesConfigFixup(logger)) - { - } - - internal /* for testing */ DynamicCFamilyRulesConfig(ICFamilyRulesConfig defaultRulesConfig, - RulesSettings customRulesSettings, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration, - ILogger logger, - IRulesConfigFixup fixup) - { - this.defaultRulesConfig = defaultRulesConfig ?? throw new ArgumentNullException(nameof(defaultRulesConfig)); - - if (customRulesSettings == null) - { - throw new ArgumentNullException(nameof(customRulesSettings)); - } - - if (connectedModeFeaturesConfiguration == null) - { - throw new ArgumentNullException(nameof(connectedModeFeaturesConfiguration)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - if (customRulesSettings.Rules.Count == 0) - { - logger.WriteLine(Resources.NoCustomRulesSettings); - } - - var modifiedCustomRules = fixup.Apply(customRulesSettings, connectedModeFeaturesConfiguration); - - ActivePartialRuleKeys = CalculateActiveRules(defaultRulesConfig, modifiedCustomRules); - RulesMetadata = new Dictionary(); - RulesParameters = new Dictionary>(); - CalculateEffectiveSettings(defaultRulesConfig, modifiedCustomRules); - } - - #region IRulesConfiguration interface methods - - public string LanguageKey => defaultRulesConfig.LanguageKey; - - public IEnumerable AllPartialRuleKeys => defaultRulesConfig.AllPartialRuleKeys; - - public IEnumerable ActivePartialRuleKeys { get; } - - public IDictionary> RulesParameters { get; } - - public IDictionary RulesMetadata { get; } - - #endregion IRulesConfiguration interface methods - - private static IEnumerable CalculateActiveRules(ICFamilyRulesConfig defaultRulesConfig, RulesSettings customRulesSettings) - { - // We're only interested settings for rules that are for the same language as the supplied rules configuration. - // The rule keys in the custom rules settings include the repo prefix, but the rule keys in the default rules config do not. - var partialKeyToConfigMap = GetFilteredRulesKeyedByPartialKey(customRulesSettings, defaultRulesConfig.LanguageKey); - - var deactivatedByUser = partialKeyToConfigMap.Where(kvp => kvp.Value.Level == RuleLevel.Off).Select(kvp => kvp.Key); - var activatedByUser = partialKeyToConfigMap.Where(kvp => kvp.Value.Level == RuleLevel.On).Select(kvp => kvp.Key); - - return defaultRulesConfig.ActivePartialRuleKeys - .Concat(activatedByUser) - .Except(deactivatedByUser, CFamilyShared.RuleKeyComparer) - .Distinct(CFamilyShared.RuleKeyComparer).ToArray(); - } - - private static IDictionary GetFilteredRulesKeyedByPartialKey(RulesSettings rulesSettings, string language) - { - Debug.Assert(!string.IsNullOrEmpty(language), "language should not be null/empty"); - var languagePrefix = language + ":"; - - return rulesSettings.Rules - .Where(kvp => kvp.Key.StartsWith(languagePrefix, CFamilyShared.RuleKeyComparison)) - .ToDictionary(kvp => kvp.Key.Substring(languagePrefix.Length), kvp => kvp.Value); - } - - private void CalculateEffectiveSettings(ICFamilyRulesConfig defaultRulesConfig, RulesSettings customRulesSettings) - { - Debug.Assert(customRulesSettings?.Rules != null && customRulesSettings.Rules.Count != 0); - - foreach (var partialRuleKey in defaultRulesConfig.AllPartialRuleKeys) - { - // Not all rules have params, but all should have metadata - Debug.Assert(defaultRulesConfig.RulesMetadata[partialRuleKey] != null); - - var defaultMetadata = defaultRulesConfig.RulesMetadata[partialRuleKey]; - defaultRulesConfig.RulesParameters.TryGetValue(partialRuleKey, out var defaultParams); - - var fullRuleKey = GetFullRuleKey(defaultRulesConfig.LanguageKey, partialRuleKey); - customRulesSettings.Rules.TryGetValue(fullRuleKey, out var userRuleConfig); - - RulesMetadata[partialRuleKey] = GetEffectiveMetadata(defaultMetadata, userRuleConfig); - - var effectiveParams = GetEffectiveParameters(defaultParams, userRuleConfig?.Parameters); - if (effectiveParams != null) - { - RulesParameters[partialRuleKey] = effectiveParams; - } - } - } - - private static RuleMetadata GetEffectiveMetadata(RuleMetadata defaultMetadata, RuleConfig userConfig) - { - if (userConfig == null || !userConfig.Severity.HasValue) - { - return defaultMetadata; - } - - return new RuleMetadata - { - DefaultSeverity = userConfig.Severity.Value, - Title = defaultMetadata.Title, - CompatibleLanguages = defaultMetadata.CompatibleLanguages, - Type = defaultMetadata.Type, - Code = defaultMetadata.Code - }; - } - - internal /* for testing */ static IDictionary GetEffectiveParameters(IDictionary defaultParameters, IDictionary userParameters) - { - if (defaultParameters == null) - { - return userParameters; - } - if (userParameters == null) - { - return defaultParameters; - } - - var effectiveParams = new Dictionary(defaultParameters, StringComparer.OrdinalIgnoreCase); - foreach (var userParam in userParameters) - { - effectiveParams[userParam.Key] = userParam.Value; - } - return effectiveParams; - } - - private static string GetFullRuleKey(string language, string partialRuleKey) - => $"{language}:{partialRuleKey}"; - } -} diff --git a/src/CFamily/Rules/EffectiveRulesConfigCalculator.cs b/src/CFamily/Rules/EffectiveRulesConfigCalculator.cs deleted file mode 100644 index 79b8aed21a..0000000000 --- a/src/CFamily/Rules/EffectiveRulesConfigCalculator.cs +++ /dev/null @@ -1,159 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Concurrent; -using System.Collections.Generic; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -/* The calculator implements simple cache to reduce the number of times the effective settings - * are recalculated (and so reduce the pressure on the garbage collector - we are creating - * multiple objects per rule and we have hundreds of rules). - * - * The cache stores one item per language - in effect it stores the last calculated config for - * that language and returns it if the source rules config and source user settings haven't - * changed. - * - * Cache hits/missed are written to the output window. - * - * Limitation: the cache is based on object identity since the source objects don't currently - * have any other mechanism that could be used. - * - * This works ok for standalone mode: the default rules config is static so the root object - * will always be same, and the IUserSettingsProvider only reloads the settings.json file - * when it changes. - * - * However, the caching doesn't work in connected mode since the connected mode settings are - * reloaded automatically every time -> object identities are different -> cache miss. - * - */ - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - /// - /// Returns the effective rules configuration to use i.e. overrides the defaults with - /// values in the user settings. - /// - /// The calculator has an internal cache to reduce unnecessary re-calculations of - /// the effective settings (and the associated object allocations). - internal class EffectiveRulesConfigCalculator - { - private readonly IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration; - private readonly ILogger logger; - private readonly RulesConfigCache configCache; - - public EffectiveRulesConfigCalculator(IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration, - ILogger logger) - { - this.connectedModeFeaturesConfiguration = connectedModeFeaturesConfiguration; - this.logger = logger ?? throw new ArgumentOutOfRangeException(nameof(logger)); - - configCache = new RulesConfigCache(); - } - - /// - /// Calculate the effective rules according to user overrides. - /// - /// - /// GetEffectiveRulesConfig could be called simultaneously from multiple threads. - /// This won't cause a crash because we are using a ConcurrentDictionary (see bug #2783). - /// It could mean that we are doing unnecessary work with multiple threads calculating the same result. - /// - public ICFamilyRulesConfig GetEffectiveRulesConfig(string languageKey, ICFamilyRulesConfig defaultRulesConfig, RulesSettings customSettings) - { - if (languageKey == null) - { - throw new ArgumentNullException(nameof(languageKey)); - } - if (defaultRulesConfig == null) - { - throw new ArgumentNullException(nameof(defaultRulesConfig)); - } - if (customSettings == null) - { - throw new ArgumentNullException(nameof(customSettings)); - } - - var effectiveConfig = configCache.FindConfig(languageKey, defaultRulesConfig, customSettings); - if (effectiveConfig != null) - { - logger.WriteLine(Resources.EffectiveRules_CacheHit); - return effectiveConfig; - } - - logger.WriteLine(Resources.EffectiveRules_CacheMiss); - - effectiveConfig = new DynamicCFamilyRulesConfig(defaultRulesConfig, customSettings, connectedModeFeaturesConfiguration, logger); - - configCache.Add(languageKey, defaultRulesConfig, customSettings, effectiveConfig); - - return effectiveConfig; - } - - /// - /// Simple cache based on object identity - /// - /// The cache holds at most one entry per language. - internal class RulesConfigCache - { - private struct CacheEntry - { - public CacheEntry(ICFamilyRulesConfig sourceConfig, RulesSettings sourceSettings, ICFamilyRulesConfig effectiveConfig) - { - SourceConfig = sourceConfig; - SourceSettings = sourceSettings; - EffectiveConfig = effectiveConfig; - } - - public ICFamilyRulesConfig SourceConfig { get; } - public RulesSettings SourceSettings { get; } - public ICFamilyRulesConfig EffectiveConfig { get; } - } - - private readonly IDictionary languageToConfigMap = new ConcurrentDictionary(); - - internal /* for testing */ int CacheCount { get { return languageToConfigMap.Count; } } - - public ICFamilyRulesConfig FindConfig(string languageKey, ICFamilyRulesConfig sourceConfig, RulesSettings sourceSettings) - { - if (!languageToConfigMap.TryGetValue(languageKey, out var cachedValue)) - { - return null; - } - - if (object.ReferenceEquals(sourceConfig, cachedValue.SourceConfig) && - object.ReferenceEquals(sourceSettings, cachedValue.SourceSettings)) - { - return cachedValue.EffectiveConfig; - } - - languageToConfigMap.Remove(languageKey); // entry doesn't match -> remove it - return null; - } - - public void Add(string languageKey, ICFamilyRulesConfig sourceConfig, RulesSettings sourceSettings, ICFamilyRulesConfig effectiveConfig) - { - languageToConfigMap[languageKey] = new CacheEntry(sourceConfig, sourceSettings, effectiveConfig); - } - } - } -} diff --git a/src/CFamily/Rules/ICFamilyRulesConfig.cs b/src/CFamily/Rules/ICFamilyRulesConfig.cs deleted file mode 100644 index eef9a5bca1..0000000000 --- a/src/CFamily/Rules/ICFamilyRulesConfig.cs +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using Newtonsoft.Json; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - public interface ICFamilyRulesConfig - { - string LanguageKey { get; } - - IEnumerable AllPartialRuleKeys { get; } - - IEnumerable ActivePartialRuleKeys { get; } - - IDictionary> RulesParameters { get; } - - IDictionary RulesMetadata { get; } - } - - public class RuleMetadata - { - [JsonProperty("title")] - public string Title { get; set; } - - [JsonProperty("type")] - public IssueType Type { get; set; } - - [JsonProperty("defaultSeverity")] - public IssueSeverity DefaultSeverity { get; set; } - - [JsonProperty("compatibleLanguages")] - public string[] CompatibleLanguages { get; set; } - - [JsonProperty("code")] - public Code Code { get; set; } - } - - public class Code - { - [JsonProperty("impacts")] - public Dictionary Impacts { get; set; } = new Dictionary(); - } - - public enum IssueType - { - CodeSmell = 0, - Bug = 1, - Vulnerability = 2, - SecurityHotspot = 3, - } -} diff --git a/src/CFamily/Rules/ICFamilyRulesConfigProvider.cs b/src/CFamily/Rules/ICFamilyRulesConfigProvider.cs deleted file mode 100644 index 2d18f99f47..0000000000 --- a/src/CFamily/Rules/ICFamilyRulesConfigProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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. - */ - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - /// - /// Returns the CFamily rules configuration to use - /// - /// The configuration to use will depend on whether we are in standalone or connected mode, and if - /// if standalone mode on whether the user has changed the default configuration - public interface ICFamilyRulesConfigProvider - { - ICFamilyRulesConfig GetRulesConfiguration(string languageKey); - } -} diff --git a/src/CFamily/Rules/Resources.Designer.cs b/src/CFamily/Rules/Resources.Designer.cs deleted file mode 100644 index bfddd362de..0000000000 --- a/src/CFamily/Rules/Resources.Designer.cs +++ /dev/null @@ -1,126 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SonarLint.VisualStudio.CFamily.Rules { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SonarLint.VisualStudio.CFamily.Rules.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to [CFamily] Rule settings contain entries for both legacy rule key and new rule keys. The legacy value will be ignored. Legacy key: {0}, new key: {1}. - /// - internal static string DuplicateLegacyAndNewRuleKey { - get { - return ResourceManager.GetString("DuplicateLegacyAndNewRuleKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily] Using cached rule settings. - /// - internal static string EffectiveRules_CacheHit { - get { - return ResourceManager.GetString("EffectiveRules_CacheHit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily] Calculating effective rule settings.... - /// - internal static string EffectiveRules_CacheMiss { - get { - return ResourceManager.GetString("EffectiveRules_CacheMiss", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily] No custom rules settings - using defaults. - /// - internal static string NoCustomRulesSettings { - get { - return ResourceManager.GetString("NoCustomRulesSettings", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily] Note: the following CFamily rules are not available in SonarQube for Visual Studio: {0}. - /// - internal static string RulesUnavailableInSonarLint { - get { - return ResourceManager.GetString("RulesUnavailableInSonarLint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily] Unable to load/locate connected mode settings. Falling back on standalone mode settings.. - /// - internal static string UnableToLoadConnectedModeSettings { - get { - return ResourceManager.GetString("UnableToLoadConnectedModeSettings", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily] Using connected mode settings. User-specified settings in settings.json will be ignored.. - /// - internal static string UsingConnectedModeSettings { - get { - return ResourceManager.GetString("UsingConnectedModeSettings", resourceCulture); - } - } - } -} diff --git a/src/CFamily/Rules/Resources.resx b/src/CFamily/Rules/Resources.resx deleted file mode 100644 index 877ff72209..0000000000 --- a/src/CFamily/Rules/Resources.resx +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - [CFamily] Rule settings contain entries for both legacy rule key and new rule keys. The legacy value will be ignored. Legacy key: {0}, new key: {1} - Output window message when settings file contains both legacy and new rule keys e.g. c:C99CommentUsage and c:S787 - - - [CFamily] Using cached rule settings - - - [CFamily] Calculating effective rule settings... - - - [CFamily] No custom rules settings - using defaults - - - [CFamily] Note: the following CFamily rules are not available in SonarQube for Visual Studio: {0} - Output window messge - - - [CFamily] Unable to load/locate connected mode settings. Falling back on standalone mode settings. - Output window message if in connected mode but couldn't find or load the connected mode settings file - - - [CFamily] Using connected mode settings. User-specified settings in settings.json will be ignored. - Output window message - - \ No newline at end of file diff --git a/src/CFamily/Rules/RulesConfigFixup.cs b/src/CFamily/Rules/RulesConfigFixup.cs deleted file mode 100644 index e25829dc6d..0000000000 --- a/src/CFamily/Rules/RulesConfigFixup.cs +++ /dev/null @@ -1,262 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Linq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - /// - /// Applies any necessary fix-ups to the rules configuration e.g. translating legacy rule keys - /// - /// - /// Legacy rule keys - /// ---------------- - /// The CFamily analyzer stopped using rule keys in the legacy "friendly name" style in v6.2: - /// all rule keys are now in the "Sxxx" format. - /// - /// There are two scenarios we need to handle: - /// 1) the user is using a version of SonarQube that has a pre-v6.2 version of the CFamily analyzer, - /// so that in Connected Mode the Quality Gate will return legacy keys; and - /// 2) the user's settings.json file contains entries using a legacy key - /// - /// Excluded rule keys - /// ------------------ - /// There are some rules we don't want to run in SonarLint: - /// * rules that need all of the files in the project to produce accurate results, and - /// * security hotspots. - /// These should never be run, even if they are explicitly enabled in custom settings. - /// - internal interface IRulesConfigFixup - { - RulesSettings Apply(RulesSettings input, IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration); - } - - internal class RulesConfigFixup : IRulesConfigFixup - { - internal static readonly string[] ExcludedRulesKeys = new string[] { - // Project-level: - "cpp:S5536", "c:S5536", - "cpp:S4830", "c:S4830", - "cpp:S5527", "c:S5527", - }; - - internal static readonly string[] HotspotRulesKeys = new[] - { - // Security hotspots: - "cpp:S5801", "c:S5801", - "cpp:S5814", "c:S5814", - "cpp:S5815", "c:S5815", - "cpp:S5816", "c:S5816", - "cpp:S5824", "c:S5824", - "cpp:S2612", "c:S2612", - "cpp:S5802", "c:S5802", - "cpp:S5849", "c:S5849", - "cpp:S5982", "c:S5982", - "cpp:S5813", "c:S5813", - "cpp:S5332", "c:S5332", - "cpp:S2068", "c:S2068", - "cpp:S2245", "c:S2245", - "cpp:S5443", "c:S5443", - "cpp:S5042", "c:S5042", - "cpp:S4790", "c:S4790", - "cpp:S1313", "c:S1313", - "cpp:S6069", "c:S6069", - }; - - private static readonly IReadOnlyDictionary partialLegacyToNewKeyMap = new Dictionary - { - // Permalink to v6.32 mapping: https://github.com/SonarSource/sonar-cpp/blob/c51c7ccb23e32f587a543a2e4b08f10e92daf2a7/sonar-cfamily-plugin/src/main/java/com/sonar/cpp/plugin/AbstractRulesDefinition.java#L35 - // This data isn't available as metadata so we have a hard-coded mapping. - { "C99CommentUsage", "S787"}, - { "SideEffectInRightHandSideOfLogical", "S912"}, - { "FunctionEllipsis", "S923"}, - { "SingleGotoOrBreakPerIteration", "S924"}, - { "ExceptionSpecificationUsage", "S2303"}, - { "PPDirectiveIndentation", "S1915"}, - { "NamespaceName", "S2304"}, - { "NonReentrantFunction", "S1912"}, - { "PPMacroName", "S1543" }, - { "ElseIfWithoutElse","S126" }, - { "SideEffectInSizeOf","S922" }, - { "NonEmptyCaseWithoutBreak","S128" }, - { "AssignmentInSubExpression", "S1121" }, - { "OctalConstantAndSequence", "S1314" }, - { "PPNonStandardInclude", "S2305" }, - { "SizeofSizeof", "S1913" }, - { "PPErrorDirectiveReached", "S1914" }, - { "UnnamedNamespaceInHeader", "S1000" }, - { "LogicalExpressionOperands", "S868" }, - { "PPIncludeCtime", "S1052" }, - { "PPIncludeCstdio", "S1055" }, - { "SingleDeclarationPerStatement", "S1659" }, - { "UsingDirective", "S1001" }, - { "EmptyThrowOutsideHandler", "S1039" }, - { "EllipsisHandlerNotLast", "S1046" }, - { "LiteralSuffix", "S818" }, - { "ExceptionInDestructor", "S1048" }, - { "IncAndDecMixedWithOtherOperators", "S881" }, - { "NarrowAndWideStringConcat", "S817" }, - { "Union", "S953" }, - { "GlobalMainFunction","S998" }, - { "GotoLabelInNestedBlock", "S1909" }, - { "PPIncludeNotAtTop","S954" }, - { "PPIncludeTime", "S991" }, - { "TrigraphUsage", "S797" }, - { "ContinueUsage", "S909" }, - { "LineLength", "S103" }, - { "FileLoc", "S104" }, - { "GotoUsage", "S907" }, - { "IdentifierLongerThan31", "S799" }, - { "GlobalNamespaceMembers", "S997" }, - { "PPIncludeNonStandardCharacters","S955" }, - { "BackJumpWithGoto", "S999" }, - { "FileComplexity", "S1908" }, - { "TabCharacter", "S105" }, - { "DigraphUsage", "S798" }, - { "InvalidEscapeSequence", "S796" }, - { "ObsoletePosixFunction", "S1911" }, - { "PPIncludeSignal", "S987" }, - { "PPBackslashNotLastCharacter", "S1916" }, - { "ClassComplexity", "S1311" }, - { "SwitchLabelPlacement", "S916" }, - { "PPIncludeStdio", "S988" }, - { "FunctionComplexity", "S1541" }, - { "CommentMixedStyles", "S1917" }, - { "OneStatementPerLine", "S122" }, - { "CommaAndOrOverloaded", "S919" }, - { "CommentedCode", "S125" }, - { "FunctionSinglePointOfExit", "S1005" }, - { "PPIncludeCHeader","S1051" }, - { "EnumPartialInitialization", "S841" }, - { "UnaryAndOverloaded", "S877" }, - { "ParsingError", "S2260" }, - { "SwitchWithoutDefault", "S131" }, - { "PPStringifyAndPastingUsage", "S968" }, - { "PPUndefUsage", "S959" }, - { "ClassName", "S101" }, - { "EmptyCompoundStatement", "S108" }, - { "PPDefineOrUndefFromBlock","S958" }, - { "PPBadIncludeForm", "S956" } - }; - - internal static readonly IReadOnlyDictionary fullLegacyToNewKeyMap = CalculateFullKeyMap(); - - private static IReadOnlyDictionary CalculateFullKeyMap() - { - var mapWithLanguagePrefixes = new Dictionary(); - foreach (var partial in partialLegacyToNewKeyMap) - { - mapWithLanguagePrefixes[$"{SonarLanguageKeys.C}:{partial.Key}"] = $"{SonarLanguageKeys.C}:{partial.Value}"; - mapWithLanguagePrefixes[$"{SonarLanguageKeys.CPlusPlus}:{partial.Key}"] = $"{SonarLanguageKeys.CPlusPlus}:{partial.Value}"; - } - return mapWithLanguagePrefixes; - } - - private readonly ILogger logger; - - public RulesConfigFixup(ILogger logger) => this.logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); - - /// - /// Translates any legacy rule keys in the input to new Sxxx rule keys - /// - public RulesSettings Apply(RulesSettings input, IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration) - { - /* - - We're making a shallow copy of the list of rules. If we modify the original list, any exclusions we - add could end up be saved in the user settings.json file(if that is where the custom rules - came from). - - Modifying the saved user settings.json would be a problem in the following scenario: - * the file has a legacy key settings e.g.cpp:C99CommentUsage - * the user has multiple VS instances with "old" and "new" SonarLint instances installed - e.g.they still have an instance oF VS2015 / 2017 they need to use - - In that case, we don't want to update the legacy keys in the settings file since it would - re - enable the rules in the "old" version. - - However, _not_ updating the legacy keys in the file has a different issue: the file could - contain both old and new keys e.g. - * settings file has legacy rule key e.g.cpp:C99CommentUsage(set to "On") - * user disables the corresponding "new" rule S787. - In that case, we'll warn in the output window that the legacy setting is being ignored. - - */ - - var modifiedSettings = new RulesSettings - { - Rules = new Dictionary(input.Rules, input.Rules.Comparer) - }; - - TranslateLegacyRuleKeys(modifiedSettings); - DisableExcludedRules(modifiedSettings, connectedModeFeaturesConfiguration.IsHotspotsAnalysisEnabled()); - - return modifiedSettings; - } - - private void TranslateLegacyRuleKeys(RulesSettings settings) - { - foreach (var inputKey in settings.Rules.Keys.ToArray()) - { - if (fullLegacyToNewKeyMap.TryGetValue(inputKey, out var newKey)) - { - var inputConfig = settings.Rules[inputKey]; - settings.Rules.Remove(inputKey); - - // There might already be a setting with the new key. If so, we'll keep it and drop the legacy key setting. - if (settings.Rules.ContainsKey(newKey)) - { - logger.WriteLine(Resources.DuplicateLegacyAndNewRuleKey, inputKey, newKey); - } - else - { - logger.LogVerbose($"[CFamily] Translating legacy rule key: {inputKey} -> {newKey}"); - settings.Rules[newKey] = inputConfig; - } - } - } - } - - /// - /// Marks all excluded rules as disabled, adding them to the settings if necessary - /// - private void DisableExcludedRules(RulesSettings settings, bool hotspotsEnabled) - { - ICollection disabledRules = ExcludedRulesKeys; - - if (!hotspotsEnabled) - { - disabledRules = disabledRules.Concat(HotspotRulesKeys).ToList(); - } - - logger.WriteLine(Resources.RulesUnavailableInSonarLint, string.Join(", ", disabledRules)); - - foreach (var key in disabledRules) - { - settings.Rules[key] = new RuleConfig { Level = RuleLevel.Off }; - } - } - - } -} diff --git a/src/CFamily/Rules/RulesLoader.cs b/src/CFamily/Rules/RulesLoader.cs deleted file mode 100644 index 68d0d5fa29..0000000000 --- a/src/CFamily/Rules/RulesLoader.cs +++ /dev/null @@ -1,161 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using Newtonsoft.Json; - -namespace SonarLint.VisualStudio.CFamily.Rules -{ - internal class RulesLoader - { - private readonly string rulesDirectoryPath; - - public RulesLoader(string rulesDirectoryPath) - { - this.rulesDirectoryPath = rulesDirectoryPath ?? throw new ArgumentNullException(nameof(rulesDirectoryPath)); - } - - public IEnumerable ReadRulesList() - { - var rulesList = LoadCFamilyJsonFile>("RulesList.json"); - Debug.Assert(rulesList != null, "The CFamily RulesList.json should exist and not be empty"); - var misraRulesList = LoadCFamilyJsonFile>("MisraRulesList.json"); - Debug.Assert(rulesList != null, "The CFamily Misra RulesList.json should exist and not be empty"); - - rulesList.AddRange(misraRulesList); - return rulesList; - } - - public IEnumerable ReadActiveRulesList() - { - var rulesProfile = LoadCFamilyJsonFile("Sonar_way_profile.json"); - Debug.Assert(rulesProfile != null, "The CFamily Sonar_way_profile.json should exist and not be empty"); - - return rulesProfile.RuleKeys; - } - - public IDictionary ReadRuleParams(String ruleKey) - { - var ruleParams = LoadCFamilyJsonFile(ruleKey + "_params.json"); - - if (ruleParams == null) - { - return new Dictionary(); - } - - var result = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var param in ruleParams) - { - result.Add(param.Key, param.DefaultValue); - } - return result; - } - - public RuleMetadata ReadRuleMetadata(String ruleKey) - { - var ruleMetadata = LoadCFamilyJsonFile(ruleKey + ".json"); - - if (ruleMetadata == null) - { - throw new FileNotFoundException("Unable to find metadata of rule: " + ruleKey); - } - - return ruleMetadata; - } - - private T LoadCFamilyJsonFile(string fileName) where T : class - { - string path = Path.Combine(this.rulesDirectoryPath, fileName); - if (!File.Exists(path)) - { - return default(T); - } - - var data = JsonConvert.DeserializeObject(File.ReadAllText(path, Encoding.UTF8), new SonarTypeConverter()); - return data; - } - - private class RulesProfile - { - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("ruleKeys")] - public List RuleKeys { get; set; } - } - - private class RuleParameter - { - [JsonProperty("key")] - public string Key { get; set; } - - [JsonProperty("description")] - public string Description { get; set; } - - [JsonProperty("defaultValue")] - public string DefaultValue { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - } - - /// - /// Custom converter to the protobuf issue Type enum - /// - internal class SonarTypeConverter : Newtonsoft.Json.JsonConverter - { - public override bool CanConvert(Type objectType) => - objectType == typeof(IssueType); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var serializedString = (string)reader.Value; - - // The names of the CodeSmell enum doesn't map directly to the serialized string so - // we can't use the default JSON StringEnumSerializer - if (serializedString.Equals("CODE_SMELL", StringComparison.OrdinalIgnoreCase)) - { - return IssueType.CodeSmell; - } - - if (serializedString.Equals("SECURITY_HOTSPOT", StringComparison.OrdinalIgnoreCase)) - { - return IssueType.SecurityHotspot; - } - - if (Enum.TryParse(serializedString, true /* ignore case */, out IssueType data)) - { - return data; - } - - throw new JsonSerializationException($"Unrecognized IssueType value: {serializedString}"); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - } - } -} diff --git a/src/CFamily/Subprocess/CFamilyStrings.Designer.cs b/src/CFamily/Subprocess/CFamilyStrings.Designer.cs deleted file mode 100644 index 2c9a21d6df..0000000000 --- a/src/CFamily/Subprocess/CFamilyStrings.Designer.cs +++ /dev/null @@ -1,256 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SonarLint.VisualStudio.CFamily.SubProcess { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class CFamilyStrings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal CFamilyStrings() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SonarLint.VisualStudio.CFamily.SubProcess.CFamilyStrings", typeof(CFamilyStrings).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Invalid compilation database entry. Either Command or Arguments must be supplied, but not both.. - /// - internal static string ERROR_InvalidCompilationEntry { - get { - return ResourceManager.GetString("ERROR_InvalidCompilationEntry", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Execution failed. The specified executable does not exist: {0}. - /// - internal static string ERROR_ProcessRunner_ExeNotFound { - get { - return ResourceManager.GetString("ERROR_ProcessRunner_ExeNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to <sensitive data removed>. - /// - internal static string MSG_CmdLine_SensitiveCmdLineArgsAlternativeText { - get { - return ResourceManager.GetString("MSG_CmdLine_SensitiveCmdLineArgsAlternativeText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Executing file {0} - /// Args: {1} - /// Working directory: {2} - /// Process id: {3}. - /// - internal static string MSG_ExecutingFile { - get { - return ResourceManager.GetString("MSG_ExecutingFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Execution cancelled.. - /// - internal static string MSG_ExecutionCancelled { - get { - return ResourceManager.GetString("MSG_ExecutionCancelled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Process returned exit code {0}. - /// - internal static string MSG_ExecutionExitCode { - get { - return ResourceManager.GetString("MSG_ExecutionExitCode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to See above for more information.. - /// - internal static string MSG_GenericAnalysisFailed { - get { - return ResourceManager.GetString("MSG_GenericAnalysisFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Refreshing PCH file for {0}. PCH file location: {1}. - /// - internal static string MSG_PchSaved { - get { - return ResourceManager.GetString("MSG_PchSaved", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DEBUG: . - /// - internal static string MSG_Prefix_DEBUG { - get { - return ResourceManager.GetString("MSG_Prefix_DEBUG", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ERROR: . - /// - internal static string MSG_Prefix_ERROR { - get { - return ResourceManager.GetString("MSG_Prefix_ERROR", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to WARNING: . - /// - internal static string MSG_Prefix_WARN { - get { - return ResourceManager.GetString("MSG_Prefix_WARN", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Process {0} was killed.. - /// - internal static string MSG_ProessRunner_ProcessKilled { - get { - return ResourceManager.GetString("MSG_ProessRunner_ProcessKilled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reproducer file saved at: {0} . - /// - internal static string MSG_ReproducerSaved { - get { - return ResourceManager.GetString("MSG_ReproducerSaved", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Request config saved at: {0} . - /// - internal static string MSG_RequestConfigSaved { - get { - return ResourceManager.GetString("MSG_RequestConfigSaved", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Overwriting the value of environment variable '{0}'. Old value: {1}, new value: {2}. - /// - internal static string MSG_Runner_OverwritingEnvVar { - get { - return ResourceManager.GetString("MSG_Runner_OverwritingEnvVar", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Setting environment variable '{0}'. Value: {1}. - /// - internal static string MSG_Runner_SettingEnvVar { - get { - return ResourceManager.GetString("MSG_Runner_SettingEnvVar", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to retrieve the configuration for file '{0}'. - /// Check the file is part of a supported project type in the current solution.. - /// - internal static string MSG_UnableToCreateConfig { - get { - return ResourceManager.GetString("MSG_UnableToCreateConfig", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to locate the CFamily analyzer exe. - /// - internal static string MSG_UnableToLocateSubProcessExe { - get { - return ResourceManager.GetString("MSG_UnableToLocateSubProcessExe", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily Analysis] Internal error calling the analysis subprocess: {0}. - /// - internal static string MsgHandler_ReportInvalidInput { - get { - return ResourceManager.GetString("MsgHandler_ReportInvalidInput", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily Analysis] Internal error in the analysis subprocess: {0}. - /// - internal static string MsgHandler_ReportUnexpectedFailure { - get { - return ResourceManager.GetString("MsgHandler_ReportUnexpectedFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [CFamily Analysis] Unsupported configuration: {0}. - /// - internal static string MsgHandler_ReportUnsupportedConfiguration { - get { - return ResourceManager.GetString("MsgHandler_ReportUnsupportedConfiguration", resourceCulture); - } - } - } -} diff --git a/src/CFamily/Subprocess/CFamilyStrings.resx b/src/CFamily/Subprocess/CFamilyStrings.resx deleted file mode 100644 index d5c01492b6..0000000000 --- a/src/CFamily/Subprocess/CFamilyStrings.resx +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Invalid compilation database entry. Either Command or Arguments must be supplied, but not both. - - - Execution failed. The specified executable does not exist: {0} - - - [CFamily Analysis] Internal error calling the analysis subprocess: {0} - - - [CFamily Analysis] Internal error in the analysis subprocess: {0} - - - [CFamily Analysis] Unsupported configuration: {0} - - - <sensitive data removed> - - - Executing file {0} - Args: {1} - Working directory: {2} - Process id: {3} - - - Execution cancelled. - - - Process returned exit code {0} - - - See above for more information. - Generic failure message logged by the CLangAnalyzer. Assumes more detailed information will already have been logged e.g. by the MessageHandler. -This message is only displayed in the Output Window (not the status bar). - - - Refreshing PCH file for {0}. PCH file location: {1} - - - DEBUG: - - - ERROR: - - - WARNING: - - - Process {0} was killed. - - - Reproducer file saved at: {0} - - - Request config saved at: {0} - - - Overwriting the value of environment variable '{0}'. Old value: {1}, new value: {2} - - - Setting environment variable '{0}'. Value: {1} - - - Unable to retrieve the configuration for file '{0}'. - Check the file is part of a supported project type in the current solution. - - - Unable to locate the CFamily analyzer exe - - \ No newline at end of file diff --git a/src/CFamily/Subprocess/IProcessRunner.cs b/src/CFamily/Subprocess/IProcessRunner.cs deleted file mode 100644 index 5ee2694867..0000000000 --- a/src/CFamily/Subprocess/IProcessRunner.cs +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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. - */ - -namespace SonarLint.VisualStudio.CFamily.SubProcess -{ - internal interface IProcessRunner - { - void Execute(ProcessRunnerArguments runnerArgs); - } -} diff --git a/src/CFamily/Subprocess/MessageHandler.cs b/src/CFamily/Subprocess/MessageHandler.cs deleted file mode 100644 index c6b18b8ce9..0000000000 --- a/src/CFamily/Subprocess/MessageHandler.cs +++ /dev/null @@ -1,159 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Linq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.ETW; -using SonarLint.VisualStudio.Core.Helpers; - -namespace SonarLint.VisualStudio.CFamily.SubProcess -{ - /// - /// Handles messages returned by the CFamily subprocess.exe - /// - internal interface IMessageHandler - { - /// - /// The number of analysis issues processed by the message handler - /// - /// Messages with internal rule keys and message for files other than the - /// file being analyzed are ignored - int IssueCount { get; } - - /// - /// True if the analysis completed successfully, otherwise false - /// - /// The analysis will be treated as having failed if any "error" internal messages are received - bool AnalysisSucceeded { get; } - - void HandleMessage(Message message); - } - - /// - /// No-op implementation - used when there is not a valid issue consumer - /// - internal class NoOpMessageHandler : IMessageHandler - { - /// - /// Singleton no-op message handler - /// - public static readonly IMessageHandler Instance = new NoOpMessageHandler(); - - public int IssueCount { get; } = 0; - - public bool AnalysisSucceeded => true; - - public void HandleMessage(Message message) { /* no-op */ } - } - - internal class MessageHandler : IMessageHandler - { - private readonly IRequest request; - private readonly IIssueConsumer issueConsumer; - private readonly ICFamilyIssueToAnalysisIssueConverter issueConverter; - private readonly ILogger logger; - - public int IssueCount { get; private set; } - - public bool AnalysisSucceeded { get; private set; } = true; - - public MessageHandler(IRequest request, IIssueConsumer issueConsumer, ICFamilyIssueToAnalysisIssueConverter issueConverter, ILogger logger) - { - this.request = request; - this.issueConsumer = issueConsumer; - this.issueConverter = issueConverter; - this.logger = logger; - } - - public void HandleMessage(Message message) - { - CodeMarkers.Instance.CFamilyHandleMessageStart(request.Context.File); - - // Handle known internal rule keys - used to return info/warnings - switch (message.RuleKey) - { - case "internal.UnsupportedConfig": // the user has specified an unsupported configuration option - log it - AnalysisSucceeded = false; - logger.WriteLine(CFamilyStrings.MsgHandler_ReportUnsupportedConfiguration, message.Text); - break; - - case "internal.InvalidInput": // subprocess has been called incorrectly by SonarLint - AnalysisSucceeded = false; - logger.WriteLine(CFamilyStrings.MsgHandler_ReportInvalidInput, message.Text); - break; - - case "internal.UnexpectedFailure": // unexpected failure in the subprocess - AnalysisSucceeded = false; - logger.WriteLine(CFamilyStrings.MsgHandler_ReportUnexpectedFailure, message.Text); - break; - - // Rules that start with internal shouldn't be treated as issues. - // Some of them should be handled like `internal.fileDependency`. See: https://github.com/SonarSource/sonarlint-visualstudio/issues/2611 - // Others should can simply ignored like `internal.z3RefutationRate`, which is used to log in the scanner how many issues are rejected by the Z3 solver - case string s when s.StartsWith("internal."): - break; - - default: // assume anything else is an analysis issue - HandleAnalysisIssue(message); - break; - } - - CodeMarkers.Instance.CFamilyHandleMessageStop(); - } - - private void HandleAnalysisIssue(Message message) - { - if (string.IsNullOrEmpty(message.Filename) // info/error messages might not have a file name - || !PathHelper.IsMatchingPath(message.Filename, request.Context.File)) // Ignore issues for other files (e.g. issues reported against header when analysing a source file) - { - return; - } - - if (!IsIssueForActiveRule(message, request.Context.RulesConfiguration)) - { - return; - } - - IssueCount++; - var issue = issueConverter.Convert(message, request.Context.CFamilyLanguage, request.Context.RulesConfiguration); - - // Note: the file being analyzed might have been closed by the time the analysis results are - // returned. This doesn't cause a crash; all active taggers will have been detached from the - // TextBufferIssueTracker when the file was closed, but the TextBufferIssueTracker will - // still exist and handle the call. - // todo https://sonarsource.atlassian.net/browse/SLVS-1661 - issueConsumer.SetIssues(request.Context.File, new[] { issue }); - } - - internal /* for testing */ static bool IsIssueForActiveRule(Message message, ICFamilyRulesConfig rulesConfiguration) - { - // Currently (v6.3) the subprocess.exe will always run the native CLang rules, so those issues - // could be returned even if they were not activated in the profile. - - // In addition, in v6.4+ there are internal rules that are always enabled and will always return - // issues. Filtering for active rules will also remove those internal issues since the corresponding - // rules will never be active in a quality profile. - return rulesConfiguration.ActivePartialRuleKeys.Contains(message.RuleKey, CFamilyShared.RuleKeyComparer); - } - } -} diff --git a/src/CFamily/Subprocess/PortedFromJava/Analyzer.cs b/src/CFamily/Subprocess/PortedFromJava/Analyzer.cs deleted file mode 100644 index c2a3a0953d..0000000000 --- a/src/CFamily/Subprocess/PortedFromJava/Analyzer.cs +++ /dev/null @@ -1,117 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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. - */ - -/** - * This is a port of the protocol implemented between Java and C++ - * https://github.com/SonarSource/sonar-cpp/blob/master/sonar-cfamily-plugin/src/main/java/com/sonar/cpp/analyzer/Analyzer.java - */ -namespace SonarLint.VisualStudio.CFamily.SubProcess -{ - internal class Response - { - public Message[] Messages { get; } - - public Response(Message[] messages) - { - Messages = messages; - } - } - - public class DataFlow - { - public string Description { get; } - public MessagePart[] Steps { get; } - - public DataFlow(string description, MessagePart[] steps) - { - this.Description = description; - this.Steps = steps; - } - } - - public class MessagePart - { - public string Filename; - public int Line { get; } - public int Column { get; } - public int EndLine { get; } - public int EndColumn { get; } - public string Text { get; } - - public MessagePart(string filename, int line, int column, int endLine, int endColumn, string text) - { - Filename = filename; - Line = line; - Column = column; - EndLine = endLine; - EndColumn = endColumn; - Text = text; - } - } - - public class Message : MessagePart - { - public string RuleKey { get; } - public bool PartsMakeFlow { get; } - public MessagePart[] Parts { get; } - public Fix[] Fixes { get; } - - // SLVS: we expect the class to be JSON-serializable for the ease of testing, and therefore the ctor parameter names must match the property names - // for the default Newtonsoft serializer to work correctly. - public Message(string ruleKey, string filename, int line, int column, int endLine, int endColumn, string text, bool partsMakeFlow, MessagePart[] parts, Fix[] fixes) - : base(filename, line, column, endLine, endColumn, text) - { - RuleKey = ruleKey; - PartsMakeFlow = partsMakeFlow; - Parts = parts; - Fixes = fixes; - } - } - - public class Fix - { - public string Message { get; } - public Edit[] Edits { get; } - - public Fix(string message, Edit[] edits) - { - Message = message; - Edits = edits; - } - } - - public class Edit - { - public int StartLine { get; } - public int StartColumn { get; } - public int EndLine { get; } - public int EndColumn { get; } - public string Text { get; } - - public Edit(int startLine, int startColumn, int endLine, int endColumn, string text) - { - StartLine = startLine; - StartColumn = startColumn; - EndLine = endLine; - EndColumn = endColumn; - Text = text; - } - } -} diff --git a/src/CFamily/Subprocess/PortedFromJava/Protocol.cs b/src/CFamily/Subprocess/PortedFromJava/Protocol.cs deleted file mode 100644 index adab282de7..0000000000 --- a/src/CFamily/Subprocess/PortedFromJava/Protocol.cs +++ /dev/null @@ -1,238 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO; -using System.Text; - -/** - * This is a port of the protocol implemented between Java and C++ - * https://github.com/SonarSource/sonar-cpp/blob/master/sonar-cfamily-plugin/src/main/java/com/sonar/cpp/analyzer/Protocol.java - * - * Note: the ported code also needs to deal with the fact that the Java DataOutputStream/DataInputStreams are big-endian whereas - * the C# BinaryWriter/Reader are little-endian. - */ -namespace SonarLint.VisualStudio.CFamily.SubProcess -{ - internal static class Protocol - { - internal /* for testing */ static void WriteInt(BinaryWriter writer, int i) - { - // Big endian conversion - byte[] temp = BitConverter.GetBytes(i); - if (BitConverter.IsLittleEndian) - { - Array.Reverse(temp); - } - writer.Write(temp); - } - - internal /* for testing */ static void WriteUTF(BinaryWriter writer, string str) - { - byte[] bytes = Encoding.UTF8.GetBytes(str); - WriteInt(writer, bytes.Length); - writer.Write(bytes); - } - - internal /* for testing */ static string ReadUTF(BinaryReader reader) - { - int size = ReadInt(reader); - return Encoding.UTF8.GetString(reader.ReadBytes(size)); - } - - internal /* for testing */ static int ReadInt(BinaryReader reader) - { - // Big endian conversion - byte[] temp = reader.ReadBytes(4); - if (BitConverter.IsLittleEndian) - { - Array.Reverse(temp); - } - return BitConverter.ToInt32(temp, 0); - } - - internal /* for testing */ static ushort ReadUShort(BinaryReader reader) - { - // Big endian conversion - byte[] temp = reader.ReadBytes(2); - if (BitConverter.IsLittleEndian) - { - Array.Reverse(temp); - } - return BitConverter.ToUInt16(temp, 0); - } - - /** - * This method does not close the provided stream. - */ - public static void Read(BinaryReader reader, Action handleIssue) - { - if ("OUT" != ReadUTF(reader)) - { - throw new InvalidDataException("Communication issue with the C/C++ analyzer: OUT expected"); - } - - - while (true) { - switch (ReadUTF(reader)) { - default: - throw new InvalidDataException("Communication issue with the C/C++ analyzer"); - case "message": - var message = readMessage(reader); - handleIssue(message); - break; - case "measures": - // Skip measures - readMeasures(reader); - break; - case "symbols": - // Skip symbols - readSymbols(reader); - break; - case "END": - return; - } - } - } - - private static Message readMessage(BinaryReader reader) { - string ruleKey = ReadUTF(reader); - string filename = ReadUTF(reader); - int line = ReadInt(reader); - int column = ReadInt(reader); - int endLine = ReadInt(reader); - int endColumn = ReadInt(reader); - // Skip remediation cost - ReadInt(reader); - string text = ReadUTF(reader); - bool partsMakeFlow = reader.ReadBoolean(); - MessagePart[] parts = ReadMessageParts(reader); - _ = ReadDataFlows(reader); - reader.ReadBoolean(); - Fix[] fixes = ReadFixes(reader); - return new Message(ruleKey, filename, line, column, endLine, endColumn, text, partsMakeFlow, parts, fixes); - } - - private static Fix[] ReadFixes(BinaryReader reader) { - int fixesCount = ReadInt(reader); - if (fixesCount == 0) - { - return Array.Empty(); - } - Fix[] fixes = new Fix[fixesCount]; - for (int i = 0; i < fixes.Length; i++) - { - fixes[i] = new Fix( - /* message= */ ReadUTF(reader), - ReadEdits(reader)); - } - return fixes; - } - - private static Edit[] ReadEdits(BinaryReader reader) { - int editsCount = ReadInt(reader); - Edit[] edits = new Edit[editsCount]; - for (int i = 0; i < edits.Length; i++) - { - edits[i] = new Edit( - /* startLine= */ ReadInt(reader), - /* startColumn= */ ReadInt(reader), - /* endLine= */ ReadInt(reader), - /* endColumn= */ ReadInt(reader), - /* text= */ ReadUTF(reader)); - } - return edits; - } - - private static MessagePart[] ReadMessageParts(BinaryReader reader) - { - int partsCount = ReadInt(reader); - if (partsCount == 0) - { - return Array.Empty(); - } - MessagePart[] parts = new MessagePart[partsCount]; - for (int j = 0; j < parts.Length; j++) - { - parts[j] = new MessagePart( - /* filename= */ ReadUTF(reader), - /* line= */ ReadInt(reader), - /* column= */ ReadInt(reader), - /* endLine= */ ReadInt(reader), - /* endColumn= */ ReadInt(reader), - /* text= */ ReadUTF(reader)); - } - return parts; - } - - private static void readMeasures(BinaryReader reader) { - int nbMeasures = ReadInt(reader); - for (int i = 0; i < nbMeasures; i++) - { - /* filename */ - ReadUTF(reader); - /* classes */ - ReadInt(reader); - /* functions */ - ReadInt(reader); - /* statements */ - ReadInt(reader); - /* complexity */ - ReadInt(reader); - /* cognitiveComplexity */ - ReadInt(reader); - /* exec lines */ - reader.ReadBytes(ReadInt(reader)); - } - } - - private static void readSymbols(BinaryReader reader) { - int nbSymbols = ReadInt(reader); - for (int i = 0; i < nbSymbols; i++) - { - int nbSymbolRefs = ReadInt(reader); - for (int j = 0; j < nbSymbolRefs; j++) - { - /* line */ - ReadInt(reader); - /* column */ - ReadInt(reader); - /* endLine */ - ReadInt(reader); - /* endColumn */ - ReadInt(reader); - } - } - } - - private static DataFlow[] ReadDataFlows(BinaryReader reader) - { - int flowCount = ReadInt(reader); - if (flowCount == 0) { return null; } - - var dataFlows = new DataFlow[flowCount]; - for (int i = 0; i - /// Helper class to run an executable and capture the output - /// - internal sealed class ProcessRunner : IProcessRunner - { - public const int ErrorCode = 1; - - private readonly ILogger logger; - private readonly ISonarLintSettings settings; - - public ProcessRunner(ISonarLintSettings settings, ILogger logger) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); - } - - public int ExitCode { get; private set; } - - /// - /// Runs the specified executable and communicates with it via Standard IO streams. - /// The method blocks until the handler has read to the end of the output stream, or when the cancellation token is cancelled. - /// - /// - /// Child processes do not inherit the env variables from the parent automatically. - /// The stream reader callbacks are executed on the original calling thread. - /// Errors and timeouts are written to the logger, which in turn writes to the output window. The caller won't see them and has no way of checking the outcome. - /// - public void Execute(ProcessRunnerArguments runnerArgs) - { - if (runnerArgs == null) - { - throw new ArgumentNullException(nameof(runnerArgs)); - } - - Debug.Assert(!string.IsNullOrWhiteSpace(runnerArgs.ExeName), - "Process runner exe name should not be null/empty"); - - if (!File.Exists(runnerArgs.ExeName)) - { - LogError(CFamilyStrings.ERROR_ProcessRunner_ExeNotFound, runnerArgs.ExeName); - ExitCode = ErrorCode; - return; - } - - var psi = new ProcessStartInfo - { - FileName = runnerArgs.ExeName, - RedirectStandardError = true, - RedirectStandardOutput = true, - RedirectStandardInput = true, - UseShellExecute = false, // required if we want to capture the error output - ErrorDialog = false, - CreateNoWindow = true, - Arguments = runnerArgs.GetEscapedArguments(), - WorkingDirectory = runnerArgs.WorkingDirectory - }; - - SetEnvironmentVariables(psi, runnerArgs.EnvironmentVariables); - - var hasProcessStarted = false; - var isRunningProcessCancelled = false; - - using (var process = new Process()) - using (runnerArgs.CancellationToken.Register(() => - { - LogMessage(CFamilyStrings.MSG_ExecutionCancelled); - - lock (process) - { - if (!hasProcessStarted) - { - // Cancellation was requested before process started - do nothing - return; - } - } - // Cancellation was requested after process started - kill it - isRunningProcessCancelled = true; - KillProcess(process); - })) - { - process.ErrorDataReceived += OnErrorDataReceived; - process.StartInfo = psi; - - lock (process) - { - if (!runnerArgs.CancellationToken.IsCancellationRequested) - { - process.Start(); - hasProcessStarted = true; - } - else - { - LogMessage(CFamilyStrings.MSG_ExecutionCancelled); - return; - } - } - - process.BeginErrorReadLine(); - - // Warning: do not log the raw command line args as they - // may contain sensitive data - LogDebug(CFamilyStrings.MSG_ExecutingFile, - runnerArgs.ExeName, - runnerArgs.AsLogText(), - runnerArgs.WorkingDirectory, - process.Id); - - try - { - runnerArgs.HandleInputStream?.Invoke(process.StandardInput); - - // the caller needs to start a blocking read operation, otherwise the method would exit. - runnerArgs.HandleOutputStream?.Invoke(process.StandardOutput); - - // Give any asynchronous events the chance to complete - process.WaitForExit(); - ExitCode = process.ExitCode; - LogDebug(CFamilyStrings.MSG_ExecutionExitCode, process.ExitCode); - } - catch (Exception ex) when (isRunningProcessCancelled && !ErrorHandler.IsCriticalException(ex)) - { - // If a process is cancelled mid-stream, an exception will be thrown. - } - } - } - private void KillProcess(Process process) - { - try - { - if (process != null && !process.HasExited) - { - LogDebug(CFamilyStrings.MSG_ProessRunner_ProcessKilled, process.Id); - process.Kill(); - } - } - catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - { - // It's possible that the process exited just between the IF and the Kill(), in which case an exception is thrown. - } - } - - private void SetEnvironmentVariables(ProcessStartInfo psi, IReadOnlyDictionary envVariables) - { - if (envVariables == null) - { - return; - } - - foreach (var envVariable in envVariables) - { - Debug.Assert(!string.IsNullOrEmpty(envVariable.Key), "Env variable name cannot be null or empty"); - - if (psi.EnvironmentVariables.ContainsKey(envVariable.Key)) - { - LogDebug(CFamilyStrings.MSG_Runner_OverwritingEnvVar, envVariable.Key, psi.EnvironmentVariables[envVariable.Key], envVariable.Value); - } - else - { - LogDebug(CFamilyStrings.MSG_Runner_SettingEnvVar, envVariable.Key, envVariable.Value); - } - psi.EnvironmentVariables[envVariable.Key] = envVariable.Value; - } - } - - private void OnErrorDataReceived(object sender, DataReceivedEventArgs e) - { - if (e.Data != null) - { - LogError(e.Data); - } - } - - private void LogMessage(string message, params object[] args) - { - var formattedMessage = GetFormattedMessage(message, args); - logger.WriteLine(formattedMessage); - } - - private void LogError(string message, params object[] args) - { - LogMessage(CFamilyStrings.MSG_Prefix_ERROR + message, args); - } - - private void LogDebug(string message, params object[] args) - { - if (settings.DaemonLogLevel == DaemonLogLevel.Verbose) - { - LogMessage(CFamilyStrings.MSG_Prefix_DEBUG + message, args); - } - } - - private static string GetFormattedMessage(string message, params object[] args) - { - var finalMessage = message; - if (args != null && args.Length > 0) - { - finalMessage = string.Format(CultureInfo.CurrentCulture, finalMessage ?? string.Empty, args); - } - - return finalMessage; - } - } -} diff --git a/src/CFamily/Subprocess/ProcessRunnerArguments.cs b/src/CFamily/Subprocess/ProcessRunnerArguments.cs deleted file mode 100644 index 4cf267d8ea..0000000000 --- a/src/CFamily/Subprocess/ProcessRunnerArguments.cs +++ /dev/null @@ -1,244 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; - -namespace SonarLint.VisualStudio.CFamily.SubProcess -{ - /// - /// Data class containing parameters required to execute a new process - /// - internal class ProcessRunnerArguments - { - // NOTE: the list of sensitive keys is hard-coded in the S4MSB. - // We've made it a property here to limit the amount of refactoring required to use the - // class here, although currently SLVS doesn't need to pass any sensitive arguments. - - /// - /// Strings that are used to indicate arguments that contain - /// sensitive data that should not be logged - /// - public IEnumerable SensitivePropertyKeys { get; set; } = Enumerable.Empty(); - - public ProcessRunnerArguments(string exeName, bool isBatchScript) - { - if (string.IsNullOrWhiteSpace(exeName)) - { - throw new ArgumentNullException(nameof(exeName)); - } - - ExeName = exeName; - IsBatchScript = isBatchScript; - } - - #region Public properties - - public string ExeName { get; } - - public CancellationToken CancellationToken { get; set; } - - /// - /// Non-sensitive command line arguments (i.e. ones that can safely be logged). Optional. - /// - public IEnumerable CmdLineArgs { get; set; } - - public string WorkingDirectory { get; set; } - - private bool IsBatchScript { get; set; } - - /// - /// Additional environments variables that should be set/overridden for the process. Can be null. - /// - public IReadOnlyDictionary EnvironmentVariables { get; set; } - - public Action HandleInputStream { get; set; } - public Action HandleOutputStream { get; set; } - - public string GetEscapedArguments() - { - if (CmdLineArgs == null) - { return null; } - - var result = string.Join(" ", CmdLineArgs.Select(a => EscapeArgument(a))); - - if (IsBatchScript) - { - result = ShellEscape(result); - } - - return result; - } - - /// - /// Returns the string that should be used when logging command line arguments - /// (sensitive data will have been removed) - /// - public string AsLogText() - { - if (CmdLineArgs == null) - { return null; } - - var hasSensitiveData = false; - - var sb = new StringBuilder(); - - foreach (var arg in CmdLineArgs) - { - if (ContainsSensitiveData(arg)) - { - hasSensitiveData = true; - } - else - { - sb.Append(arg); - sb.Append(" "); - } - } - - if (hasSensitiveData) - { - sb.Append(CFamilyStrings.MSG_CmdLine_SensitiveCmdLineArgsAlternativeText); - } - - return sb.ToString(); - } - - /// - /// Determines whether the text contains sensitive data that - /// should not be logged/written to file - /// - private bool ContainsSensitiveData(string text) - { - Debug.Assert(SensitivePropertyKeys != null, "SensitiveDataMarkers array should not be null"); - - if (text == null) - { - return false; - } - - return SensitivePropertyKeys.Any(marker => text.IndexOf(marker, StringComparison.OrdinalIgnoreCase) > -1); - } - - /// - /// The CreateProcess Win32 API call only takes 1 string for all arguments. - /// Ultimately, it is the responsibility of each program to decide how to split this string into multiple arguments. - /// - /// See: - /// https://blogs.msdn.microsoft.com/oldnewthing/20100917-00/?p=12833/ - /// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ - /// http://www.daviddeley.com/autohotkey/parameters/parameters.htm - /// - private static string EscapeArgument(string arg) - { - Debug.Assert(arg != null, "Not expecting an argument to be null"); - - var sb = new StringBuilder(); - - sb.Append("\""); - for (var i = 0; i < arg.Length; i++) - { - var numberOfBackslashes = 0; - for (; i < arg.Length && arg[i] == '\\'; i++) - { - numberOfBackslashes++; - } - - if (i == arg.Length) - { - // - // Escape all backslashes, but let the terminating - // double quotation mark we add below be interpreted - // as a meta-character. - // - sb.Append('\\', numberOfBackslashes * 2); - } - else if (arg[i] == '"') - { - // - // Escape all backslashes and the following - // double quotation mark. - // - sb.Append('\\', numberOfBackslashes * 2 + 1); - sb.Append(arg[i]); - } - else - { - // - // Backslashes aren't special here. - // - sb.Append('\\', numberOfBackslashes); - sb.Append(arg[i]); - } - } - sb.Append("\""); - - return sb.ToString(); - } - - /// - /// Batch scripts are evil. - /// The escape character in batch is '^'. - /// - /// Example: - /// script.bat : echo %* - /// cmd.exe: script.bat foo^>out.txt - /// - /// This passes the argument "foo >out.txt" to script.bat. - /// Variable expansion happen before execution (i.e. it is preprocessing), so the script becomes: - /// - /// echo foo>out.txt - /// - /// which will write "foo" into the file "out.txt" - /// - /// To avoid this, one must call: - /// cmd.exe: script.bat foo^^^>out.txt - /// - /// which gets rewritten into: echo foo^>out.txt - /// and then executed. - /// - /// Note: Delayed expansion is not available for %*, %1 - /// set foo=%* and set foo="%*" with echo !foo! - /// will only move the command injection away from the "echo" to the "set" itself. - /// - private static string ShellEscape(string argLine) - { - var sb = new StringBuilder(); - foreach (var c in argLine) - { - // This escape is required after %* is expanded to prevent command injections - sb.Append('^'); - sb.Append('^'); - - // This escape is required only to pass the argument line to the batch script - sb.Append('^'); - sb.Append(c); - } - return sb.ToString(); - } - - #endregion Public properties - } -} diff --git a/src/CFamily/Subprocess/SubProcessFilePaths.cs b/src/CFamily/Subprocess/SubProcessFilePaths.cs deleted file mode 100644 index 2d23cbfd93..0000000000 --- a/src/CFamily/Subprocess/SubProcessFilePaths.cs +++ /dev/null @@ -1,59 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Diagnostics; -using System.IO; -using SonarLint.VisualStudio.Core.Helpers; - -namespace SonarLint.VisualStudio.CFamily.SubProcess -{ - /// - /// Returns file paths for various CFamily directories/files - /// - public static class SubProcessFilePaths - { - - static SubProcessFilePaths() - { - try - { - Directory.CreateDirectory(PchFileDirectory); - } - catch (Exception ex) - { - Debug.WriteLine($"PCH directory error: {ex.Message}"); - } - } - - private static string PchFileName = "PCH.preamble"; - private static string PchFileDirectory = PathHelper.GetTempDirForTask(true, "PCH"); - public static string WorkingDirectory => Path.GetTempPath(); - public static string PchFilePath => Path.Combine(PchFileDirectory, PchFileName); - public static string RequestConfigFilePath => Path.Combine(WorkingDirectory, "sonar-cfamily.request.reproducer"); - public static string ReproducerFilePath => Path.Combine(WorkingDirectory, "sonar-cfamily.reproducer"); - - private static readonly string CFamilyFilesDirectory = Path.Combine( - Path.GetDirectoryName(typeof(SubProcessFilePaths).Assembly.Location), - "lib"); - - public static readonly string AnalyzerExeFilePath = Path.Combine(CFamilyFilesDirectory, "subprocess.exe"); - } -} diff --git a/src/Core.UnitTests/RuleSettingsProviderTests.cs b/src/Core.UnitTests/RuleSettingsProviderTests.cs deleted file mode 100644 index 3a867ecdbd..0000000000 --- a/src/Core.UnitTests/RuleSettingsProviderTests.cs +++ /dev/null @@ -1,152 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.Core.UnitTests -{ - [TestClass] - public class RuleSettingsProviderTests - { - private readonly Language validLanguage = Language.C; - - private readonly RulesSettings standaloneSettings = new() - { - Rules = new Dictionary - { - {"standalone", new RuleConfig()} - } - }; - - private readonly RulesSettings connectedModeSettings = new() - { - Rules = new Dictionary - { - {"connected", new RuleConfig()} - } - }; - - [TestMethod] - public void Get_NotInConnectedMode_UserSettings() - { - var userSettingsProvider = CreateUserSettingsProvider(standaloneSettings); - var ruleSettingsSerializer = new Mock(); - - var testSubject = CreateTestSubject(BindingConfiguration.Standalone, userSettingsProvider.Object, ruleSettingsSerializer.Object); - - var result = testSubject.Get(); - - result.Should().Be(standaloneSettings); - - userSettingsProvider.VerifyGet(x=> x.UserSettings, Times.Once); - userSettingsProvider.VerifyNoOtherCalls(); - - ruleSettingsSerializer.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public void Get_InConnectedMode_CantFindConnectModeFile_UserSettings() - { - var userSettingsProvider = CreateUserSettingsProvider(standaloneSettings); - var bindingConfiguration = GetConnectedModeConfiguration(); - var ruleSettingsSerializer = CreateRulesSettingsSerializer(bindingConfiguration, null); - - var testSubject = CreateTestSubject(bindingConfiguration, userSettingsProvider.Object, ruleSettingsSerializer.Object); - - var result = testSubject.Get(); - - result.Should().Be(standaloneSettings); - - userSettingsProvider.VerifyGet(x => x.UserSettings, Times.Once); - userSettingsProvider.VerifyNoOtherCalls(); - - ruleSettingsSerializer.Verify(x => x.SafeLoad(ConnectedModeFilePath(bindingConfiguration)), Times.Once); - ruleSettingsSerializer.VerifyNoOtherCalls(); - } - - [TestMethod] - public void Get_InConnectedMode_FoundConnectModeFile_ConnectedModeSettings() - { - var userSettingsProvider = new Mock(); - var bindingConfiguration = GetConnectedModeConfiguration(); - var ruleSettingsSerializer = CreateRulesSettingsSerializer(bindingConfiguration, connectedModeSettings); - - var testSubject = CreateTestSubject(bindingConfiguration, userSettingsProvider.Object, ruleSettingsSerializer.Object); - - var result = testSubject.Get(); - - result.Should().Be(connectedModeSettings); - - userSettingsProvider.Invocations.Count.Should().Be(0); - - ruleSettingsSerializer.Verify(x => x.SafeLoad(ConnectedModeFilePath(bindingConfiguration)), Times.Once); - ruleSettingsSerializer.VerifyNoOtherCalls(); - } - - private RuleSettingsProvider CreateTestSubject(BindingConfiguration configuration, - IUserSettingsProvider userSettingsProvider, - IRulesSettingsSerializer rulesSettingsSerializer) - { - var activeSolutionBoundTracker = new Mock(); - activeSolutionBoundTracker.Setup(x => x.CurrentConfiguration).Returns(configuration); - - return new RuleSettingsProvider(activeSolutionBoundTracker.Object, - userSettingsProvider, - rulesSettingsSerializer, - validLanguage, - Mock.Of()); - } - - private Mock CreateUserSettingsProvider(RulesSettings ruleSettings) - { - var userSettingsProvider = new Mock(); - userSettingsProvider.Setup(x => x.UserSettings).Returns(new UserSettings(ruleSettings)); - - return userSettingsProvider; - } - - private BindingConfiguration GetConnectedModeConfiguration() - { - return BindingConfiguration.CreateBoundConfiguration( - new BoundServerProject("solution", "projectKey", new ServerConnection.SonarQube(new Uri("http://localhost:2000"))), - SonarLintMode.Connected, - "some directory"); - } - - private Mock CreateRulesSettingsSerializer(BindingConfiguration bindingConfiguration, RulesSettings settings) - { - var serializer = new Mock(); - serializer.Setup(x => x.SafeLoad(ConnectedModeFilePath(bindingConfiguration))).Returns(settings); - - return serializer; - } - - private string ConnectedModeFilePath(BindingConfiguration bindingConfiguration) - { - return bindingConfiguration.BuildPathUnderConfigDirectory(validLanguage.FileSuffixAndExtension); - } - } -} diff --git a/src/Core/UserRuleSettings/IRuleSettingsProvider.cs b/src/Core/UserRuleSettings/IRuleSettingsProvider.cs deleted file mode 100644 index 61cd4f82b0..0000000000 --- a/src/Core/UserRuleSettings/IRuleSettingsProvider.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Core.Binding; - -namespace SonarLint.VisualStudio.Core.UserRuleSettings -{ - public interface IRuleSettingsProviderFactory - { - /// - /// Create for a specific language - /// - /// - /// In connected mode we need to know which file to load and we get it from . - /// - IRuleSettingsProvider Get(Language language); - } - - public interface IRuleSettingsProvider - { - /// - /// Get rule settings specified in connected mode, or in the settings.json file - /// - RulesSettings Get(); - } - - public class RuleSettingsProvider : IRuleSettingsProvider - { - private readonly IActiveSolutionBoundTracker activeSolutionBoundTracker; - private readonly IUserSettingsProvider userSettingsProvider; - private readonly IRulesSettingsSerializer serializer; - private readonly Language language; - private readonly ILogger logger; - - public RuleSettingsProvider(IActiveSolutionBoundTracker activeSolutionBoundTracker, - IUserSettingsProvider userSettingsProvider, - IRulesSettingsSerializer serializer, - Language language, - ILogger logger) - { - this.activeSolutionBoundTracker = activeSolutionBoundTracker; - this.userSettingsProvider = userSettingsProvider; - this.serializer = serializer; - this.language = language; - this.logger = logger; - } - - public RulesSettings Get() - { - RulesSettings settings = null; - - // If in connected mode, look for rule settings in the .sonarlint/sonarqube folder. - var binding = this.activeSolutionBoundTracker.CurrentConfiguration; - - if (binding != null && binding.Mode != SonarLintMode.Standalone) - { - settings = FindConnectedModeSettings(binding); - if (settings == null) - { - logger.WriteLine(CoreStrings.UnableToLoadConnectedModeSettings); - } - else - { - logger.WriteLine(CoreStrings.UsingConnectedModeSettings); - } - } - - // If we are not in connected mode or couldn't find the connected mode settings then fall back on the standalone settings. - settings = settings ?? userSettingsProvider.UserSettings.RulesSettings; - - return settings; - } - - private RulesSettings FindConnectedModeSettings(BindingConfiguration binding) - { - var filePath = binding.BuildPathUnderConfigDirectory(language.FileSuffixAndExtension); - var settings = serializer.SafeLoad(filePath); - - return settings; - } - } -} diff --git a/src/Infrastructure.VS.UnitTests/VsInfoServiceTests.cs b/src/Infrastructure.VS.UnitTests/VsInfoServiceTests.cs deleted file mode 100644 index 816bb28660..0000000000 --- a/src/Infrastructure.VS.UnitTests/VsInfoServiceTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Runtime.InteropServices; -using FluentAssertions; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.Infrastructure.VS.UnitTests -{ - [TestClass] - public class VsInfoServiceTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - => MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport()); - - [TestMethod] - public void MefCtor_CheckIsSingleton() - => MefTestHelpers.CheckIsSingletonMefComponent(); - - [TestMethod] - public void MefCtor_DoesNotCallAnyServices() - { - var serviceOp = new Mock(); - - _ = new VsInfoService(serviceOp.Object); - - // The MEF constructor should be free-threaded, which it will be if - // it doesn't make any external calls. - serviceOp.Invocations.Should().BeEmpty(); - } - - [TestMethod] - public void Create_VsShellCallSucceeds_ReturnsExpectedPath() - { - var serviceOp = CreateConfiguredServiceOperation("c:\\test\\"); - - var testSubject = new VsInfoService(serviceOp); - - testSubject.InstallRootDir.Should().Be("c:\\test\\"); - } - - [TestMethod] - public void Create_VsShellCallFails_ExceptionThrown() - { - var logger = new TestLogger(); - var serviceOp = CreateConfiguredServiceOperation("c:\\test\\", shellHrResult: -123); - - var testSubject = new VsInfoService(serviceOp); - - Action act = () => _ = testSubject.InstallRootDir; - act.Should().ThrowExactly(); - } - - private IVsUIServiceOperation CreateConfiguredServiceOperation(string installDirectory, int shellHrResult = VSConstants.S_OK) - { - object installDir = installDirectory; - var vsShell = new Mock(); - vsShell.Setup(x => x.GetProperty((int)__VSSPROPID2.VSSPROPID_InstallRootDir, out installDir)).Returns(shellHrResult); - - var serviceOp = CreateServiceOperation(vsShell.Object); - - return serviceOp; - } - - private IVsUIServiceOperation CreateServiceOperation(IVsShell svcToPassToCallback) - { - var serviceOp = new Mock(); - - // Set up the mock to invoke the operation with the supplied VS service - serviceOp.Setup(x => x.Execute(It.IsAny>())) - .Returns>(op => op(svcToPassToCallback)); - - return serviceOp.Object; - } - } -} diff --git a/src/Infrastructure.VS/VsInfoService.cs b/src/Infrastructure.VS/VsInfoService.cs deleted file mode 100644 index fc1aadcf09..0000000000 --- a/src/Infrastructure.VS/VsInfoService.cs +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; - -namespace SonarLint.VisualStudio.Infrastructure.VS -{ - /// - /// Returns information about the current VS installation - /// - public interface IVsInfoService - { - /// - /// Returns the root directory for executing VS installation - /// - string InstallRootDir { get; } - } - - [Export(typeof(IVsInfoService))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class VsInfoService : IVsInfoService - { - private readonly Lazy installRootDir; - - [ImportingConstructor] - public VsInfoService(IVsUIServiceOperation vsUIServiceOperation) - { - installRootDir = new Lazy(() => vsUIServiceOperation.Execute(GetInstallRootDir)); - } - - public string InstallRootDir => installRootDir.Value; - - private string GetInstallRootDir(IVsShell shell) - { - object value; - ErrorHandler.ThrowOnFailure(shell.GetProperty((int)__VSSPROPID2.VSSPROPID_InstallRootDir, out value)); - return value as string; - } - } -} diff --git a/src/Integration.Vsix.UnitTests/Analysis/RuleSettingsProviderFactoryTests.cs b/src/Integration.Vsix.UnitTests/Analysis/RuleSettingsProviderFactoryTests.cs deleted file mode 100644 index 207fe12c9d..0000000000 --- a/src/Integration.Vsix.UnitTests/Analysis/RuleSettingsProviderFactoryTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.Integration.Vsix.Analysis; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.Integration.UnitTests.Analysis -{ - [TestClass] - public class RuleSettingsProviderFactoryTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void Get_CreatesRuleSettingsProvider() - { - var testSubject = new RuleSettingsProviderFactory( - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); - - var result = testSubject.Get(Language.C); - - result.Should().NotBeNull(); - } - } -} diff --git a/src/Integration.Vsix.UnitTests/CFamily/CFamilyEmbeddedSonarWayRulesTests.cs b/src/Integration.Vsix.UnitTests/CFamily/CFamilyEmbeddedSonarWayRulesTests.cs deleted file mode 100644 index d4d8558543..0000000000 --- a/src/Integration.Vsix.UnitTests/CFamily/CFamilyEmbeddedSonarWayRulesTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 FluentAssertions.Execution; -using SonarLint.VisualStudio.CFamily; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.UnitTests -{ - [TestClass] - public class CFamilyEmbeddedSonarWayRulesTests - { - // Sanity checks that the rules metadata for the CFamily plugin is present and can be loaded - - // Note: how to find the expected number of active/inactive rules in SonarWay by language: - // 1. Start a local SQ instance with the correct plugin version installed - // 2. Browse to "Rules" e.g. http://localhost:9000/ - // or: - // SonarCloud: C: https://sonarcloud.io/organizations/sonarsource/quality_profiles/show?name=Sonar+way&language=c - // SonarCloud: C++: https://sonarcloud.io/organizations/sonarsource/quality_profiles/show?name=Sonar+way&language=cpp - // note: if you just look at the qp page, then it always shows 6 more inactive rules for c&cpp than available in the ide - // 3. Filter by Repository = SonarAnalyzer C - // 4. Filter by Quality Profile = Sonar way C - // The QP filter has "active/inactive" tabs. The number of rules is shown in the top-right of the screen. - // 5. Repeat for C++. - - // You can check the version of the plugin that is installed on the appropriate web API: - // e.g. https://next.sonarqube.com/sonarqube/api/plugins/installed and https://sonarcloud.io/api/plugins/installed - // Note - you need to be logged in. - - // Rule data for C-Family plugin v6.61.0.77816 - - private const int Active_C_Rules = 211; - private const int Inactive_C_Rules = 130; - - private const int Active_CPP_Rules = 453; - private const int Inactive_CPP_Rules = 219; - - private readonly CFamilySonarWayRulesConfigProvider rulesMetadataCache = new CFamilySonarWayRulesConfigProvider(CFamilyShared.CFamilyFilesDirectory); - - [TestMethod] - public void Read_Rules() - { - rulesMetadataCache.GetRulesConfiguration("c").AllPartialRuleKeys.Should().HaveCount(Active_C_Rules + Inactive_C_Rules); - rulesMetadataCache.GetRulesConfiguration("cpp").AllPartialRuleKeys.Should().HaveCount(Active_CPP_Rules + Inactive_CPP_Rules); - - // We don't currently support ObjC rules in VS - rulesMetadataCache.GetRulesConfiguration("objc").Should().BeNull(); - } - - [TestMethod] - public void Read_Active_Rules() - { - rulesMetadataCache.GetRulesConfiguration("c").ActivePartialRuleKeys.Should().HaveCount(Active_C_Rules); - rulesMetadataCache.GetRulesConfiguration("cpp").ActivePartialRuleKeys.Should().HaveCount(Active_CPP_Rules); - - // We don't currently support ObjC rules in VS - rulesMetadataCache.GetRulesConfiguration("objc").Should().BeNull(); - } - - [TestMethod] - public void Read_Rules_Params() - { - // The choice of rule ID here is arbitrary - any rule that has parameters will do. - rulesMetadataCache.GetRulesConfiguration("cpp").RulesParameters.TryGetValue("S100", out var parameters); - parameters.Should() - .Contain(new KeyValuePair("format", "^[a-z][a-zA-Z0-9]*$")); - } - - [TestMethod] - public void Read_Rules_Metadata() - { - // The choice of rule ID here is arbitrary - any rule will do - rulesMetadataCache.GetRulesConfiguration("cpp").RulesMetadata.TryGetValue("S100", out var metadata); - using (new AssertionScope()) - { - metadata.Type.Should().Be(IssueType.CodeSmell); - metadata.DefaultSeverity.Should().Be(IssueSeverity.Minor); - } - } - - [TestMethod] - [DataRow("S5536", "c")] - [DataRow("S5536", "cpp")] - public void CheckProjectLevelRule_IsDisabledByDefault(string ruleKey, string languageKey) - { - // The choice of rule ID here is arbitrary - any rule will do - rulesMetadataCache.GetRulesConfiguration(languageKey).AllPartialRuleKeys.Contains(ruleKey).Should().BeTrue(); - rulesMetadataCache.GetRulesConfiguration(languageKey).ActivePartialRuleKeys.Contains(ruleKey).Should().BeFalse(); - } - } -} diff --git a/src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueConverterFactoryTests.cs b/src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueConverterFactoryTests.cs deleted file mode 100644 index 49ff9d108e..0000000000 --- a/src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueConverterFactoryTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Utilities; -using Moq; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Integration.Vsix.CFamily; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily -{ - [TestClass] - public class CFamilyIssueConverterFactoryTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported - (MefTestHelpers.CreateExport(Mock.Of()), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void Create_ReturnsNewInstance() - { - var textDocumentFactory = Mock.Of(); - var contentTypeRegistry = Mock.Of(); - var connectedModeFeaturesConfiguration = Mock.Of(); - - var testSubject = new CFamilyIssueConverterFactory(textDocumentFactory, contentTypeRegistry, connectedModeFeaturesConfiguration); - - // Create first item - var result1 = testSubject.Create(); - result1.Should().NotBeNull(); - - // Create second item - var result2 = testSubject.Create(); - result2.Should().NotBeNull(); - - result1.Should().NotBeSameAs(result2); - } - } -} diff --git a/src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueToAnalysisIssueConverterTests.cs b/src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueToAnalysisIssueConverterTests.cs deleted file mode 100644 index 2bd53d3259..0000000000 --- a/src/Integration.Vsix.UnitTests/CFamily/CFamilyIssueToAnalysisIssueConverterTests.cs +++ /dev/null @@ -1,840 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.IO.Abstractions; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Utilities; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.Infrastructure.VS.Editor; -using SonarLint.VisualStudio.Integration.Vsix.CFamily; -using Edit = SonarLint.VisualStudio.CFamily.SubProcess.Edit; - -namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily -{ - [TestClass] - public class CFamilyIssueToAnalysisIssueConverterTests - { - private static readonly IContentType DummyContentType = Mock.Of(); - - [TestMethod] - public void Convert_NoMessageParts_IssueWithoutFlows() - { - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", false, new MessagePart[0], Array.Empty()); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - issue.Flows.Should().BeEmpty(); - } - - [TestMethod] - public void Convert_HasMessageParts_IssueWithSingleFlowAndLocations() - { - var messageParts = new List - { - new MessagePart("c:\\test1.cpp", 1, 2, 3, 4, "this is a test 1"), - new MessagePart("c:\\test2.cpp", 5, 6, 7, 8, "this is a test 2") - }; - - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", false, messageParts.ToArray(), Array.Empty()); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - var expectedLocations = new List - { - new AnalysisIssueLocation("this is a test 1", "c:\\test1.cpp", new TextRange(1, 3, 1, 3, null)), - new AnalysisIssueLocation("this is a test 2", "c:\\test2.cpp", new TextRange(5, 7, 5, 7, null)), - }; - - var expectedFlows = new List - { - new AnalysisIssueFlow(expectedLocations) - }; - - issue.Flows.Count.Should().Be(1); - issue.Flows.Should().BeEquivalentTo(expectedFlows, config => config.WithStrictOrdering()); - } - - [TestMethod] - public void Convert_HasMessagePartsMakeFlow_FlowsAreReversed() - { - var messageParts = new List - { - new MessagePart("c:\\test1.cpp", 1, 2, 3, 4, "this is a test 1"), - new MessagePart("c:\\test2.cpp", 5, 6, 7, 8, "this is a test 2") - }; - - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", true, messageParts.ToArray(), Array.Empty()); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - var expectedLocations = new List - { - new AnalysisIssueLocation("this is a test 2", "c:\\test2.cpp", new TextRange(5, 7, 5, 7, null)), - new AnalysisIssueLocation("this is a test 1", "c:\\test1.cpp", new TextRange(1, 3, 1, 3, null)), - }; - - var expectedFlows = new List - { - new AnalysisIssueFlow(expectedLocations) - }; - - issue.Flows.Count.Should().Be(1); - issue.Flows.Should().BeEquivalentTo(expectedFlows, config => config.WithStrictOrdering()); - } - - [TestMethod] - public void Convert_IssueEndLineIsNotZero_OffsetsAreCalculatedCorrectly() - { - var message = new Message("rule2", "file", 4, 3, 2, 1, "test endline is not zero", false, new MessagePart[0], Array.Empty()); - - // Act - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - // Assert - issue.PrimaryLocation.TextRange.StartLine.Should().Be(4); - issue.PrimaryLocation.TextRange.StartLineOffset.Should().Be(3 - 1); - - issue.PrimaryLocation.TextRange.EndLine.Should().Be(2); - issue.PrimaryLocation.TextRange.EndLineOffset.Should().Be(1 - 1); - } - - [TestMethod] - public void Convert_IssueEndLineIsZero_OffsetsAreIgnored() - { - // Special case: ignore column offsets if EndLine is zero - var message = new Message("rule2", "ff", 101, 1, 0, 3, "test endline is zero", true, new MessagePart[0], Array.Empty()); - - // Act - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - // Assert - issue.PrimaryLocation.TextRange.StartLine.Should().Be(101); - issue.PrimaryLocation.TextRange.StartLineOffset.Should().Be(0); - - issue.PrimaryLocation.TextRange.EndLine.Should().Be(0); - issue.PrimaryLocation.TextRange.EndLineOffset.Should().Be(0); - } - - [TestMethod] - public void Convert_LocationEndLineIsNotZero_OffsetsAreCalculatedCorrectly() - { - var messagePart = new MessagePart("file", 10, 2, 30, 4, "text"); - var message = new Message("rule2", "file", 4, 3, 2, 1, "test endline is not zero", false, new[] { messagePart }, Array.Empty()); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - var convertedLocation = issue.Flows.First().Locations.First(); - - convertedLocation.TextRange.StartLine.Should().Be(10); - convertedLocation.TextRange.StartLineOffset.Should().Be(1); - - convertedLocation.TextRange.EndLine.Should().Be(30); - convertedLocation.TextRange.EndLineOffset.Should().Be(3); - } - - [TestMethod] - public void Convert_LocationEndLineIsZero_OffsetsAreIgnored() - { - var messagePart = new MessagePart("file", 10, 2, 0, 4, "text"); - var message = new Message("rule2", "file", 4, 3, 2, 1, "test endline is not zero", false, new[] { messagePart }, Array.Empty()); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - var convertedLocation = issue.Flows.First().Locations.First(); - - convertedLocation.TextRange.StartLine.Should().Be(10); - convertedLocation.TextRange.StartLineOffset.Should().Be(0); - - convertedLocation.TextRange.EndLine.Should().Be(0); - convertedLocation.TextRange.EndLineOffset.Should().Be(0); - } - - [TestMethod] - public void Convert_NoMessageParts_LineHashCalculatedForIssueOnly() - { - const string filePath = "file1.cpp"; - const int line = 10; - - var fileSystemMock = CreateFileSystemMock(); - var lineHashCalculator = new Mock(); - var textDocumentFactoryService = new Mock(); - - var issueHash = SetupLineHash(fileSystemMock, lineHashCalculator, textDocumentFactoryService, filePath, line); - - var message = new Message("rule2", filePath, line, 3, 2, 1, "this is a test", false, new MessagePart[0], Array.Empty()); - - var testSubject = CreateTestSubject(lineHashCalculator.Object, fileSystemMock.Object, textDocumentFactoryService.Object); - var issue = Convert(testSubject, message); - - issue.PrimaryLocation.TextRange.LineHash.Should().Be(issueHash); - } - - [TestMethod] - public void Convert_HasMessageParts_LineHashCalculatedForIssueAndLocations() - { - var fileSystemMock = CreateFileSystemMock(); - var lineHashCalculator = new Mock(); - var textDocumentFactoryService = new Mock(); - - const string issueFilePath = "file1.cpp"; - const int issueLine = 10; - var issueHash = SetupLineHash(fileSystemMock, lineHashCalculator, textDocumentFactoryService, issueFilePath, issueLine); - - const string firstLocationPath = "file2.cpp"; - const int firstLocationLine = 20; - var firstLocationHash = SetupLineHash(fileSystemMock, lineHashCalculator, textDocumentFactoryService, firstLocationPath, firstLocationLine); - - const string secondLocationPath = "file3.cpp"; - const int secondLocationLine = 30; - var secondLocationHash = SetupLineHash(fileSystemMock, lineHashCalculator, textDocumentFactoryService, secondLocationPath, secondLocationLine); - - var messageParts = new List - { - new MessagePart(firstLocationPath, firstLocationLine, 2, 3, 4, "this is a test 1"), - new MessagePart(secondLocationPath, secondLocationLine, 6, 7, 8, "this is a test 2") - }; - - var message = new Message("rule2", issueFilePath, issueLine, 3, 2, 1, "this is a test", false, messageParts.ToArray(), Array.Empty()); - - var testSubject = CreateTestSubject(lineHashCalculator.Object, fileSystemMock.Object, textDocumentFactoryService.Object); - var issue = Convert(testSubject, message); - - issue.PrimaryLocation.TextRange.LineHash.Should().Be(issueHash); - - var firstLocation = issue.Flows[0].Locations[0]; - var secondLocation = issue.Flows[0].Locations[1]; - - secondLocation.TextRange.LineHash.Should().Be(secondLocationHash); - firstLocation.TextRange.LineHash.Should().Be(firstLocationHash); - } - - [TestMethod] - public void Convert_HasMessageParts_LineHashCalculatedForNonFileLevelLocationsOnly() - { - var fileSystemMock = CreateFileSystemMock(); - var lineHashCalculator = new Mock(); - var textDocumentFactoryService = new Mock(); - - const string nonFileLevelLocationFilePath = "file2.cpp"; - const int nonFileLevelLocationLine = 20; - var nonFileLevelLocationHash = SetupLineHash(fileSystemMock, lineHashCalculator, textDocumentFactoryService, nonFileLevelLocationFilePath, nonFileLevelLocationLine); - - var messageParts = new List - { - new MessagePart(nonFileLevelLocationFilePath, nonFileLevelLocationLine, 2, 3, 4, "this is a test 1"), - new MessagePart("file3.cpp", 1, 1, 0, 0, "this is a test 2") - }; - - var fileLevelIssue = new Message("rule2", "file1.pp", 1, 0, 0, 0, "this is a test", false, messageParts.ToArray(), Array.Empty()); - - var testSubject = CreateTestSubject(lineHashCalculator.Object, fileSystemMock.Object, textDocumentFactoryService.Object); - var issue = Convert(testSubject, fileLevelIssue); - - issue.PrimaryLocation.TextRange.LineHash.Should().BeNull(); - - var nonFileLevelLocation = issue.Flows[0].Locations[0]; - var fileLevelLocation = issue.Flows[0].Locations[1]; - - fileLevelLocation.TextRange.LineHash.Should().BeNull(); - nonFileLevelLocation.TextRange.LineHash.Should().Be(nonFileLevelLocationHash); - } - - [TestMethod] - public void Convert_FileDoesNotExist_NullLineHash() - { - var fileSystemMock = CreateFileSystemMock(); - var lineHashCalculator = new Mock(); - var textDocumentFactoryService = new Mock(); - - const string existingFilePath = "file2.cpp"; - const int line = 20; - var expectedHash = SetupLineHash(fileSystemMock, lineHashCalculator, textDocumentFactoryService, existingFilePath, line); - - var messageParts = new List - { - new MessagePart(existingFilePath, line, 2, 3, 4, "this is a test 1"), - new MessagePart("non existing path", 2, 6, 7, 8, "this is a test 2") - }; - - var message = new Message("rule2", "non existing path", 3, 3, 2, 1, "this is a test", false, messageParts.ToArray(), Array.Empty()); - - var testSubject = CreateTestSubject(lineHashCalculator.Object, fileSystemMock.Object, textDocumentFactoryService.Object); - var issue = Convert(testSubject, message); - issue.PrimaryLocation.TextRange.LineHash.Should().BeNull(); - - var firstLocation = issue.Flows[0].Locations[0]; - var secondLocation = issue.Flows[0].Locations[1]; - - secondLocation.TextRange.LineHash.Should().BeNull(); - firstLocation.TextRange.LineHash.Should().Be(expectedHash); - - // verify that the mock was called only for firstLocationPath - textDocumentFactoryService.Verify(x => x.CreateAndLoadTextDocument(existingFilePath, DummyContentType), Times.Once); - textDocumentFactoryService.Verify(x => x.CreateAndLoadTextDocument(It.IsAny(), It.IsAny()), Times.Once); - } - - [TestMethod] - public void Convert_ExistingFile_NoTextDocument_NullLineHash() - { - var fileSystemMock = CreateFileSystemMock(); - var lineHashCalculator = new Mock(); - var textDocumentFactoryService = new Mock(); - - const string filePath = "test.cpp"; - fileSystemMock.Setup(x => x.File.Exists(filePath)).Returns(true); - - SetupDocumentLoad(textDocumentFactoryService, filePath, textDocument: null); - - var message = new Message("rule2", filePath, 3, 3, 2, 1, "this is a test", false, Array.Empty(), Array.Empty()); - - var testSubject = CreateTestSubject(lineHashCalculator.Object, fileSystemMock.Object, textDocumentFactoryService.Object); - - var issue = Convert(testSubject, message); - issue.PrimaryLocation.TextRange.LineHash.Should().BeNull(); - lineHashCalculator.Invocations.Should().BeEmpty(); - } - - [TestMethod] - public void Convert_ExistingFile_NoTextBuffer_NullLineHash() - { - var fileSystemMock = CreateFileSystemMock(); - var lineHashCalculator = new Mock(); - var textDocumentFactoryService = new Mock(); - - const string filePath = "test.cpp"; - fileSystemMock.Setup(x => x.File.Exists(filePath)).Returns(true); - - var textDocument = CreateTextDocument(null); - SetupDocumentLoad(textDocumentFactoryService, filePath, textDocument); - - var message = new Message("rule2", filePath, 3, 3, 2, 1, "this is a test", false, Array.Empty(), Array.Empty()); - - var testSubject = CreateTestSubject(lineHashCalculator.Object, fileSystemMock.Object, textDocumentFactoryService.Object); - - var issue = Convert(testSubject, message); - issue.PrimaryLocation.TextRange.LineHash.Should().BeNull(); - lineHashCalculator.Invocations.Should().BeEmpty(); - } - - [TestMethod] - public void Convert_ExistingFile_NoTextSnapshot_NullLineHash() - { - var fileSystemMock = CreateFileSystemMock(); - var lineHashCalculator = new Mock(); - var textDocumentFactoryService = new Mock(); - - const string filePath = "test.cpp"; - fileSystemMock.Setup(x => x.File.Exists(filePath)).Returns(true); - - var textBuffer = CreateTextBuffer(null); - var textDocument = CreateTextDocument(textBuffer); - SetupDocumentLoad(textDocumentFactoryService, filePath, textDocument); - - var message = new Message("rule2", filePath, 3, 3, 2, 1, "this is a test", false, Array.Empty(), Array.Empty()); - - var testSubject = CreateTestSubject(lineHashCalculator.Object, fileSystemMock.Object, textDocumentFactoryService.Object); - - var issue = Convert(testSubject, message); - issue.PrimaryLocation.TextRange.LineHash.Should().BeNull(); - lineHashCalculator.Invocations.Should().BeEmpty(); - } - - [TestMethod] - public void Convert_HasMessageParts_EachFileIsLoadedOnlyOnce_PerCall() - { - var messageParts = new List - { - new MessagePart("file1.cpp", 10, 2, 3, 4, "this is a test 1"), - new MessagePart("file1.cpp", 20, 6, 7, 8, "this is a test 2"), - new MessagePart("file2.cpp", 30, 6, 7, 8, "this is a test 2") - }; - - var message = new Message("rule2", "file2.cpp", 40, 3, 2, 1, "this is a test", false, messageParts.ToArray(), Array.Empty()); - - var fileSystemMock = CreateFileSystemMock(fileExists: true); - var textDocFactory = new Mock(); - - var testSubject = CreateTestSubject(fileSystem: fileSystemMock.Object, textDocumentFactoryService: textDocFactory.Object); - - Convert(testSubject, message); - - fileSystemMock.Verify(x => x.File.Exists("file1.cpp"), Times.Once); - fileSystemMock.Verify(x => x.File.Exists("file2.cpp"), Times.Once); - fileSystemMock.VerifyNoOtherCalls(); - - textDocFactory.Verify(x => x.CreateAndLoadTextDocument("file1.cpp", It.IsAny()), Times.Once); - textDocFactory.Verify(x => x.CreateAndLoadTextDocument("file2.cpp", It.IsAny()), Times.Once); - textDocFactory.VerifyNoOtherCalls(); - } - - [TestMethod] - public void Convert_HasMessageParts_EachFileIsLoadedOnlyOnce_AcrossMultipleCalls() - { - var messageParts1 = new List - { - new MessagePart("file1.cpp", 10, 2, 3, 4, "this is a test 1"), - new MessagePart("file2.cpp", 30, 6, 7, 8, "this is a test 2") - }; - var message1 = new Message("rule2", "file2.cpp", 40, 3, 2, 1, "this is a test", false, messageParts1.ToArray(), Array.Empty()); - - var messageParts2 = new List - { - new MessagePart("FILE1.cpp", 100, 2, 3, 4, "this is a test 1"), - new MessagePart("FILE2.cpp", 300, 6, 7, 8, "this is a test 2") - }; - var message2 = new Message("rule2", "file2.cpp", 40, 3, 2, 1, "this is another test", false, messageParts2.ToArray(), Array.Empty()); - - var fileSystemMock = CreateFileSystemMock(fileExists: true); - var textDocFactory = new Mock(); - - var testSubject = CreateTestSubject(fileSystem: fileSystemMock.Object, textDocumentFactoryService: textDocFactory.Object); - - // Convert multiple times - Convert(testSubject, message1); - Convert(testSubject, message2); - - fileSystemMock.Verify(x => x.File.Exists("file1.cpp"), Times.Once); - fileSystemMock.Verify(x => x.File.Exists("file2.cpp"), Times.Once); - fileSystemMock.VerifyNoOtherCalls(); - - textDocFactory.Verify(x => x.CreateAndLoadTextDocument("file1.cpp", It.IsAny()), Times.Once); - textDocFactory.Verify(x => x.CreateAndLoadTextDocument("file2.cpp", It.IsAny()), Times.Once); - textDocFactory.VerifyNoOtherCalls(); - } - - [TestMethod] - [DataRow("rule2", AnalysisIssueSeverity.Info, AnalysisIssueType.CodeSmell)] - [DataRow("rule3", AnalysisIssueSeverity.Critical, AnalysisIssueType.Vulnerability)] - public void Convert_SeverityAndTypeLookup(string ruleKey, AnalysisIssueSeverity severity, AnalysisIssueType type) - { - var message = new Message(ruleKey, "any", 4, 3, 2, 1, "message", false, new MessagePart[0], Array.Empty()); - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - issue.Id.Should().BeNull(); - issue.RuleKey.Should().Be($"lang1:{ruleKey}"); - issue.Severity.Should().Be(severity); - issue.Type.Should().Be(type); - } - - [TestMethod] - [DataRow(true, IssueType.Bug, SoftwareQualitySeverity.High)] - [DataRow(false, IssueType.Bug, null)] - [DataRow(true, IssueType.SecurityHotspot, null)] - public void Convert_NewCCTEnabled_FillsSoftwareQualitySeverity(bool isCCTEnabled, IssueType type, SoftwareQualitySeverity? expectedSoftwareQualitySeverity) - { - var message = new Message("key", "any", 4, 3, 2, 1, "message", false, new MessagePart[0], Array.Empty()); - - var impacts = new Dictionary - { - { SoftwareQuality.Maintainability, SoftwareQualitySeverity.High } - }; - - var ruleMetaData = CreateRuleMetaData(impacts, type); - var rulesMetaData = new Dictionary - { - {"key", ruleMetaData } - }; - - var CFamilyconfig = new Mock(); - CFamilyconfig.Setup(c => c.RulesMetadata).Returns(rulesMetaData); - - var CMConfig = new Mock(); - CMConfig.Setup(c => c.IsNewCctAvailable()).Returns(isCCTEnabled); - - var testSubject = CreateTestSubject(connectedModeFeaturesConfiguration: CMConfig.Object); - var issue = testSubject.Convert(message, "lang", CFamilyconfig.Object); - - var highestSoftwareQualitySeverity = issue.HighestImpact?.Severity; - highestSoftwareQualitySeverity.Should().Be(expectedSoftwareQualitySeverity); - } - - [TestMethod] - [Microsoft.VisualStudio.TestTools.UnitTesting.Description("Regression test for https://github.com/SonarSource/sonarlint-visualstudio/issues/2149")] - [DataRow("", "")] // empty should not throw - [DataRow("a.txt", "a.txt")] // not-rooted should stay the same - [DataRow("c:\\a.txt", "c:\\a.txt")] - [DataRow("c:/a.txt", "c:\\a.txt")] - [DataRow("c:/a/b/c.txt", "c:\\a\\b\\c.txt")] - [DataRow("c:/a\\b/c.txt", "c:\\a\\b\\c.txt")] - public void Convert_HasMessageParts_QualifiedFilePath(string originalPath, string expectedPath) - { - var messageParts = new List - { - new MessagePart(originalPath, 10, 2, 3, 4, "this is a test 1"), - }; - - var message = new Message("rule2", "file2.cpp", 40, 3, 2, 1, "this is a test", false, messageParts.ToArray(), Array.Empty()); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - issue.Flows[0].Locations[0].FilePath.Should().Be(expectedPath); - } - - [TestMethod] - [Microsoft.VisualStudio.TestTools.UnitTesting.Description("Regression test for https://github.com/SonarSource/sonarlint-visualstudio/issues/2557")] - [DataRow("", "")] // empty should not throw - [DataRow("a.txt", "a.txt")] // not-rooted should stay the same - [DataRow("c:\\a.txt", "c:\\a.txt")] - [DataRow("c:/a.txt", "c:\\a.txt")] - [DataRow("c:/a/b/c.txt", "c:\\a\\b\\c.txt")] - [DataRow("c:/a\\b/c.txt", "c:\\a\\b\\c.txt")] - public void Convert_FilePath_QualifiedFilePath(string originalPath, string expectedPath) - { - var message = new Message("rule2", originalPath, 40, 3, 2, 1, "this is a test", false, Array.Empty(), Array.Empty()); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - issue.PrimaryLocation.FilePath.Should().Be(expectedPath); - } - - [TestMethod] - [DataRow(IssueSeverity.Blocker, AnalysisIssueSeverity.Blocker)] - [DataRow(IssueSeverity.Critical, AnalysisIssueSeverity.Critical)] - [DataRow(IssueSeverity.Info, AnalysisIssueSeverity.Info)] - [DataRow(IssueSeverity.Major, AnalysisIssueSeverity.Major)] - [DataRow(IssueSeverity.Minor, AnalysisIssueSeverity.Minor)] - public void ConvertFromIssueSeverity(IssueSeverity cfamilySeverity, AnalysisIssueSeverity analysisIssueSeverity) - { - CFamilyIssueToAnalysisIssueConverter.Convert(cfamilySeverity).Should().Be(analysisIssueSeverity); - } - - [TestMethod] - public void ConvertFromIssueSeverity_InvalidValue_Throws() - { - Action act = () => CFamilyIssueToAnalysisIssueConverter.Convert((IssueSeverity)(-1)); - act.Should().ThrowExactly().And.ParamName.Should().Be("issueSeverity"); - } - - [TestMethod] - [DataRow(IssueType.Bug, AnalysisIssueType.Bug)] - [DataRow(IssueType.CodeSmell, AnalysisIssueType.CodeSmell)] - [DataRow(IssueType.Vulnerability, AnalysisIssueType.Vulnerability)] - [DataRow(IssueType.SecurityHotspot, AnalysisIssueType.SecurityHotspot)] - public void ConvertFromIssueType(IssueType cfamilyIssueType, AnalysisIssueType analysisIssueType) - { - CFamilyIssueToAnalysisIssueConverter.Convert(cfamilyIssueType).Should().Be(analysisIssueType); - } - - [TestMethod] - [DataRow(SoftwareQualitySeverity.High, SoftwareQualitySeverity.High)] - [DataRow(SoftwareQualitySeverity.Medium, SoftwareQualitySeverity.Medium)] - [DataRow(SoftwareQualitySeverity.Low, SoftwareQualitySeverity.Low)] - [DataRow(null, null)] - public void GetHighestImpact_ReturnsImpactWithHighestSeverity(SoftwareQualitySeverity? softwareQualitySeverity, SoftwareQualitySeverity? expectedHighestSoftwareQualitySeverity) - { - var impacts = new Dictionary(); - if (softwareQualitySeverity.HasValue) - { - impacts.Add(SoftwareQuality.Maintainability, softwareQualitySeverity.Value); - } - RuleMetadata ruleMetaData = CreateRuleMetaData(impacts); - - var highestSoftwareQualitySeverity = CFamilyIssueToAnalysisIssueConverter.GetHighestImpact(ruleMetaData)?.Severity; - - highestSoftwareQualitySeverity.Should().Be(expectedHighestSoftwareQualitySeverity); - } - - [TestMethod] - [DataRow(new SoftwareQualitySeverity[] { SoftwareQualitySeverity.Low, SoftwareQualitySeverity.Medium }, SoftwareQualitySeverity.Medium)] - [DataRow(new SoftwareQualitySeverity[] { SoftwareQualitySeverity.Low, SoftwareQualitySeverity.High }, SoftwareQualitySeverity.High)] - [DataRow(new SoftwareQualitySeverity[] { SoftwareQualitySeverity.Medium, SoftwareQualitySeverity.High }, SoftwareQualitySeverity.High)] - public void GetHighestImpact_HasTwoImpacts_GetsTheHighestOne(SoftwareQualitySeverity[] softwareQualitySeverities, SoftwareQualitySeverity? expectedHighestSoftwareQualitySeverity) - { - var impacts = new Dictionary - { - { SoftwareQuality.Maintainability, softwareQualitySeverities[0] }, - { SoftwareQuality.Reliability, softwareQualitySeverities[1] } - }; - RuleMetadata ruleMetaData = CreateRuleMetaData(impacts); - - var highestSoftwareQualitySeverity = CFamilyIssueToAnalysisIssueConverter.GetHighestImpact(ruleMetaData)?.Severity; - - highestSoftwareQualitySeverity.Should().Be(expectedHighestSoftwareQualitySeverity); - } - - [TestMethod] - public void GetHighestImpact_HasThreeImpacts_GetsTheHighestOne() - { - var impacts = new Dictionary - { - { SoftwareQuality.Maintainability, SoftwareQualitySeverity.Low }, - { SoftwareQuality.Reliability, SoftwareQualitySeverity.High }, - { SoftwareQuality.Security, SoftwareQualitySeverity.Medium } - }; - RuleMetadata ruleMetaData = CreateRuleMetaData(impacts); - - var highestImpact = CFamilyIssueToAnalysisIssueConverter.GetHighestImpact(ruleMetaData); - - highestImpact.Severity.Should().Be(SoftwareQualitySeverity.High); - highestImpact.Quality.Should().Be(SoftwareQuality.Reliability); - } - - [TestMethod] - [DataRow(SoftwareQualitySeverity.Blocker)] - [DataRow(SoftwareQualitySeverity.High)] - [DataRow(SoftwareQualitySeverity.Medium)] - [DataRow(SoftwareQualitySeverity.Low)] - [DataRow(SoftwareQualitySeverity.Info)] - public void GetHighestImpact_HasTwoHighImpactsForDifferentQualities_GetsTheHighestSoftwareQuality(SoftwareQualitySeverity softwareQualitySeverity) - { - var impacts = new Dictionary - { - { SoftwareQuality.Maintainability, SoftwareQualitySeverity.Info }, - { SoftwareQuality.Reliability, softwareQualitySeverity }, - { SoftwareQuality.Security, softwareQualitySeverity } - }; - RuleMetadata ruleMetaData = CreateRuleMetaData(impacts); - - var highestImpact = CFamilyIssueToAnalysisIssueConverter.GetHighestImpact(ruleMetaData); - - highestImpact.Severity.Should().Be(softwareQualitySeverity); - highestImpact.Quality.Should().Be(SoftwareQuality.Security); - } - - [TestMethod] - public void ConvertFromIssueType_InvalidValue_Throws() - - { - Action act = () => CFamilyIssueToAnalysisIssueConverter.Convert((IssueType)(-1)); - act.Should().ThrowExactly().And.ParamName.Should().Be("issueType"); - } - - [TestMethod] - public void Convert_Issue_WithSingleFixSingleEdit() - { - var fix1 = new Fix("Fix 1", new Edit[] { new Edit(1, 2, 3, 4, "Edit 1") }); - - var fixes = new Fix[] { fix1 }; - - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", false, new MessagePart[0], fixes); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - issue.Fixes.Count.Should().Be(1); - - CompareFixes(fix1, issue.Fixes[0]); - } - - [TestMethod] - public void Convert_Issue_WithSingleFixMultipleEdits() - { - var fix1 = new Fix("Fix 1", new Edit[] { new Edit(11, 12, 13, 14, "Edit 1"), new Edit(21, 22, 23, 24, "Edit 2"), new Edit(31, 32, 33, 34, "Edit 3"), new Edit(41, 42, 43, 44, "Edit 4") }); - - var fixes = new[] { fix1 }; - - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", false, new MessagePart[0], fixes); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - issue.Fixes.Count.Should().Be(1); - - CompareFixes(fix1, issue.Fixes[0]); - } - - [TestMethod] - public void Convert_Issue_WithMultipleFixesMultipleEdits() - { - var fix1 = new Fix("Fix 1", new Edit[] { new Edit(11, 12, 13, 14, "Edit 1"), new Edit(21, 22, 23, 24, "Edit 2"), new Edit(31, 32, 33, 34, "Edit 3"), new Edit(41, 42, 43, 44, "Edit 4") }); - var fix2 = new Fix("Fix 2", new Edit[] { new Edit(51, 52, 53, 54, "Edit 5"), new Edit(61, 62, 63, 64, "Edit 6"), new Edit(71, 72, 73, 74, "Edit 7"), new Edit(81, 82, 83, 84, "Edit 8") }); - - var fixes = new Fix[] { fix1, fix2 }; - - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", false, new MessagePart[0], fixes); - - var testSubject = CreateTestSubject(); - var issue = Convert(testSubject, message); - - issue.Fixes.Count.Should().Be(2); - - CompareFixes(fix1, issue.Fixes[0]); - CompareFixes(fix2, issue.Fixes[1]); - } - - [TestMethod] - public void Convert_Issue_WithSingleFixNullEdit_Throws() - { - var fix1 = new Fix("Fix 1", null); - - var fixes = new Fix[] { fix1 }; - - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", false, new MessagePart[0], fixes); - - var testSubject = CreateTestSubject(); - Action act = () => Convert(testSubject, message); - - act.Should().ThrowExactly().And.ParamName.Should().Be("edits"); - } - - [TestMethod] - public void Convert_Issue_WithSingleFixEmptyEdit_Throws() - { - var fix1 = new Fix("Fix 1", Array.Empty()); - - var fixes = new Fix[] { fix1 }; - - var message = new Message("rule2", "file", 4, 3, 2, 1, "this is a test", false, new MessagePart[0], fixes); - - var testSubject = CreateTestSubject(); - Action act = () => Convert(testSubject, message); - - act.Should().ThrowExactly().And.ParamName.Should().Be("edits"); - } - - private static void CompareFixes(Fix fix, IQuickFix quickFix) - { - quickFix.Edits.Count.Should().Be(fix.Edits.Length, $"because number of edits were not equal in {fix.Message}"); - - for (int i = 0; i < fix.Edits.Length; i++) - { - fix.Edits[i].StartColumn.Should().Be(quickFix.Edits[i].RangeToReplace.StartLineOffset + 1, $"because StartColumn was not equal in {fix.Message}, edit: {i} "); - fix.Edits[i].EndColumn.Should().Be(quickFix.Edits[i].RangeToReplace.EndLineOffset + 1, $"because EndColumn was not equal in {fix.Message}, edit: {i} "); - fix.Edits[i].StartLine.Should().Be(quickFix.Edits[i].RangeToReplace.StartLine, $"because StartLine was not equal in {fix.Message}, edit: {i} "); - fix.Edits[i].EndLine.Should().Be(quickFix.Edits[i].RangeToReplace.EndLine, $"because EndLine was not equal in {fix.Message}, edit: {i} "); - fix.Edits[i].Text.Should().Be(quickFix.Edits[i].NewText, $"because NewText was not equal in {fix.Message}, edit: {i} "); - } - } - - private static ICFamilyRulesConfig GetDummyRulesConfiguration() - { - var config = new Mock(); - config.Setup(x => x.LanguageKey).Returns("any"); - - var keyToMetadataMap = new Dictionary - { - { "rule1", new RuleMetadata { DefaultSeverity = IssueSeverity.Blocker, Type = IssueType.Bug } }, - { "rule2", new RuleMetadata { DefaultSeverity = IssueSeverity.Info, Type = IssueType.CodeSmell } }, - { "rule3", new RuleMetadata { DefaultSeverity = IssueSeverity.Critical, Type = IssueType.Vulnerability} }, - }; - - config.Setup(x => x.RulesMetadata).Returns(keyToMetadataMap); - - return config.Object; - } - - private static string SetupLineHash(Mock fileSystemMock, - Mock lineHashCalculatorMock, - Mock textDocumentFactoryService, - string filePath, - int line) - { - fileSystemMock.Setup(x => x.File.Exists(filePath)).Returns(true); - - var textSnapshot = Mock.Of(); - var textBuffer = CreateTextBuffer(textSnapshot); - var textDocument = CreateTextDocument(textBuffer); - - SetupDocumentLoad(textDocumentFactoryService, filePath, textDocument); - - var hash = Guid.NewGuid().ToString(); - - lineHashCalculatorMock.Setup(x => x.Calculate(textSnapshot, line)).Returns(hash); - - return hash; - } - - private static IAnalysisIssue Convert(CFamilyIssueToAnalysisIssueConverter testSubject, Message message) => - testSubject.Convert(message, "lang1", GetDummyRulesConfiguration()); - - private static CFamilyIssueToAnalysisIssueConverter CreateTestSubject( - ILineHashCalculator lineHashCalculator = null, - IFileSystem fileSystem = null, - ITextDocumentFactoryService textDocumentFactoryService = null, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration = null) - { - var contentTypeRegistryService = CreateContentTypeRegistryService(); - - lineHashCalculator ??= Mock.Of(); - fileSystem ??= CreateFileSystemMock().Object; - textDocumentFactoryService ??= Mock.Of(); - - if (connectedModeFeaturesConfiguration == null) - { - var connectedModeFeaturesConfigurationMock = new Mock(); - connectedModeFeaturesConfigurationMock.Setup(c => c.IsNewCctAvailable()).Returns(false); - - connectedModeFeaturesConfiguration = connectedModeFeaturesConfigurationMock.Object; - } - - var testSubject = new CFamilyIssueToAnalysisIssueConverter(textDocumentFactoryService, contentTypeRegistryService, connectedModeFeaturesConfiguration, lineHashCalculator, fileSystem); - - return testSubject; - } - - private static Mock CreateFileSystemMock(bool fileExists = false) - { - var fileSystemMock = new Mock(); - fileSystemMock.Setup(x => x.File.Exists(It.IsAny())).Returns(fileExists); - - return fileSystemMock; - } - - private static IContentTypeRegistryService CreateContentTypeRegistryService() - { - var contentTypeRegistryService = new Mock(); - contentTypeRegistryService.Setup(x => x.UnknownContentType).Returns(DummyContentType); - - return contentTypeRegistryService.Object; - } - - private static void SetupDocumentLoad(Mock textDocumentFactoryService, string filePath, ITextDocument textDocument) - { - textDocumentFactoryService - .Setup(x => x.CreateAndLoadTextDocument(filePath, DummyContentType)) - .Returns(textDocument); - } - - private static ITextDocument CreateTextDocument(ITextBuffer textBuffer) - { - var textDocument = new Mock(); - textDocument.Setup(x => x.TextBuffer).Returns(textBuffer); - - return textDocument.Object; - } - - private static ITextBuffer CreateTextBuffer(ITextSnapshot textSnapshot) - { - var textBuffer = new Mock(); - textBuffer.Setup(x => x.CurrentSnapshot).Returns(textSnapshot); - - return textBuffer.Object; - } - - private static RuleMetadata CreateRuleMetaData(Dictionary impacts, IssueType type = default) - { - var code = new Code { Impacts = impacts }; - var ruleMetaData = new RuleMetadata { Code = code, Type = type }; - return ruleMetaData; - } - } -} diff --git a/src/Integration.Vsix/Analysis/RuleSettingsProviderFactory.cs b/src/Integration.Vsix/Analysis/RuleSettingsProviderFactory.cs deleted file mode 100644 index ce4beff15a..0000000000 --- a/src/Integration.Vsix/Analysis/RuleSettingsProviderFactory.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.ComponentModel.Composition; -using System.IO.Abstractions; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.Core.UserRuleSettings; - -namespace SonarLint.VisualStudio.Integration.Vsix.Analysis -{ - [Export(typeof(IRuleSettingsProviderFactory))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class RuleSettingsProviderFactory : IRuleSettingsProviderFactory - { - private readonly IActiveSolutionBoundTracker activeSolutionBoundTracker; - private readonly IUserSettingsProvider userSettingsProvider; - private readonly IRulesSettingsSerializer serializer; - private readonly ILogger logger; - - [ImportingConstructor] - public RuleSettingsProviderFactory(IActiveSolutionBoundTracker activeSolutionBoundTracker, - IUserSettingsProvider userSettingsProvider, - ILogger logger) - : this(activeSolutionBoundTracker, - userSettingsProvider, - new RulesSettingsSerializer(new FileSystem(), logger), - logger) - { - } - - internal RuleSettingsProviderFactory(IActiveSolutionBoundTracker activeSolutionBoundTracker, - IUserSettingsProvider userSettingsProvider, - IRulesSettingsSerializer serializer, - ILogger logger) - { - this.activeSolutionBoundTracker = activeSolutionBoundTracker; - this.userSettingsProvider = userSettingsProvider; - this.serializer = serializer; - this.logger = logger; - } - - public IRuleSettingsProvider Get(Language language) => - new RuleSettingsProvider(activeSolutionBoundTracker, userSettingsProvider, serializer, language, logger); - } -} diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt index 1f684af00d..1b62fd4199 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt @@ -72,12 +72,11 @@ Referenced assemblies: - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'SonarLint.VisualStudio.Core, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' -- 'SonarLint.VisualStudio.Infrastructure.VS, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' -# Number of references: 9 +# Number of references: 8 --- Assembly: 'SonarLint.VisualStudio.ConnectedMode, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt index ada0de3e5a..3634e8129c 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt @@ -72,12 +72,11 @@ Referenced assemblies: - 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' - 'SonarLint.VisualStudio.Core, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' -- 'SonarLint.VisualStudio.Infrastructure.VS, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' -# Number of references: 9 +# Number of references: 8 --- Assembly: 'SonarLint.VisualStudio.ConnectedMode, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' diff --git a/src/Integration.Vsix/CFamily/CFamilyIssueToAnalysisIssueConverter.cs b/src/Integration.Vsix/CFamily/CFamilyIssueToAnalysisIssueConverter.cs deleted file mode 100644 index d5c7852285..0000000000 --- a/src/Integration.Vsix/CFamily/CFamilyIssueToAnalysisIssueConverter.cs +++ /dev/null @@ -1,326 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.IO; -using System.IO.Abstractions; -using System.Linq; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Utilities; -using SonarLint.VisualStudio.CFamily.Analysis; -using SonarLint.VisualStudio.CFamily.Rules; -using SonarLint.VisualStudio.CFamily.SubProcess; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Configuration; -using SonarLint.VisualStudio.Core.ETW; -using SonarLint.VisualStudio.Core.UserRuleSettings; -using SonarLint.VisualStudio.Infrastructure.VS.Editor; - -/* Instancing: a new issue converter should be created for each analysis run. - * - * Overview - * -------- - * Each analysis is for a single file, although the issues returned could be for related - * files (e.g. a header file). The results will generally be a small set of files - the - * analysis file + [zero or more related files]. - * - * Converting an issue entails loading the text document for the file. We don't want to load - * the same document multiple times, so we'll create a separate converter for each analysis, - * so the converter can cache the loaded text documents. - */ - -namespace SonarLint.VisualStudio.Integration.Vsix.CFamily -{ - [Export(typeof(ICFamilyIssueConverterFactory))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class CFamilyIssueConverterFactory : ICFamilyIssueConverterFactory - { - private readonly ITextDocumentFactoryService textDocumentFactoryService; - private readonly IContentTypeRegistryService contentTypeRegistryService; - private readonly IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration; - - [ImportingConstructor] - public CFamilyIssueConverterFactory(ITextDocumentFactoryService textDocumentFactoryService, IContentTypeRegistryService contentTypeRegistryService, IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration) - { - this.textDocumentFactoryService = textDocumentFactoryService; - this.contentTypeRegistryService = contentTypeRegistryService; - this.connectedModeFeaturesConfiguration = connectedModeFeaturesConfiguration; - } - - public ICFamilyIssueToAnalysisIssueConverter Create() - { - return new CFamilyIssueToAnalysisIssueConverter(textDocumentFactoryService, contentTypeRegistryService, connectedModeFeaturesConfiguration); - } - } - - // Short-lived class - one instance per analysis - internal class CFamilyIssueToAnalysisIssueConverter : ICFamilyIssueToAnalysisIssueConverter - { - private readonly ITextDocumentFactoryService textDocumentFactoryService; - private readonly ILineHashCalculator lineHashCalculator; - private readonly IFileSystem fileSystem; - private readonly IContentType filesContentType; - private readonly Dictionary pathToTextDocMap; - private readonly IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration; - - public CFamilyIssueToAnalysisIssueConverter(ITextDocumentFactoryService textDocumentFactoryService, IContentTypeRegistryService contentTypeRegistryService, IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration) - : this(textDocumentFactoryService, contentTypeRegistryService, connectedModeFeaturesConfiguration, new LineHashCalculator(), new FileSystem()) - { - } - - internal CFamilyIssueToAnalysisIssueConverter(ITextDocumentFactoryService textDocumentFactoryService, - IContentTypeRegistryService contentTypeRegistryService, - IConnectedModeFeaturesConfiguration connectedModeFeaturesConfiguration, - ILineHashCalculator lineHashCalculator, - IFileSystem fileSystem) - { - this.textDocumentFactoryService = textDocumentFactoryService; - this.lineHashCalculator = lineHashCalculator; - this.fileSystem = fileSystem; - this.connectedModeFeaturesConfiguration = connectedModeFeaturesConfiguration; - - filesContentType = contentTypeRegistryService.UnknownContentType; - - pathToTextDocMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - public IAnalysisIssue Convert(Message cFamilyIssue, string sqLanguage, ICFamilyRulesConfig rulesConfiguration) - { - CodeMarkers.Instance.CFamilyConvertIssueStart(cFamilyIssue.Filename); - - // Lines and character positions are 1-based - Debug.Assert(cFamilyIssue.Line > 0); - - // BUT special case of EndLine=0, Column=0, EndColumn=0 meaning "select the whole line" - Debug.Assert(cFamilyIssue.EndLine >= 0); - Debug.Assert(cFamilyIssue.Column > 0 || cFamilyIssue.Column == 0); - Debug.Assert(cFamilyIssue.EndColumn > 0 || cFamilyIssue.EndLine == 0); - - var ruleMetaData = rulesConfiguration.RulesMetadata[cFamilyIssue.RuleKey]; - - // Look up default severity and type - var defaultSeverity = ruleMetaData.DefaultSeverity; - var defaultType = ruleMetaData.Type; - Impact highestImpact = null; - - if (ruleMetaData.Type != IssueType.SecurityHotspot && connectedModeFeaturesConfiguration.IsNewCctAvailable()) - { - highestImpact = GetHighestImpact(ruleMetaData); - } - - var fileContents = GetFileContentsOfReportedFiles(cFamilyIssue); - - var locations = cFamilyIssue.Parts - .Select(x => ToAnalysisIssueLocation(x, fileContents)) - .ToArray(); - - // If PartsMakeFlow is set to true the issues are expected to be in the reversed order - if (cFamilyIssue.PartsMakeFlow) - { - Array.Reverse(locations); - } - - var flows = locations.Any() ? new[] { new AnalysisIssueFlow(locations) } : null; - - var result = ToAnalysisIssue(cFamilyIssue, sqLanguage, defaultSeverity, defaultType, flows, fileContents, highestImpact); - - CodeMarkers.Instance.CFamilyConvertIssueStop(); - - return result; - } - - private IReadOnlyDictionary GetFileContentsOfReportedFiles(Message cFamilyIssue) - { - var filePaths = cFamilyIssue.Parts - .Select(x => x.Filename) - .Union(new[] { cFamilyIssue.Filename }) - .Distinct(); - - foreach (var filePath in filePaths) - { - if (pathToTextDocMap.ContainsKey(filePath)) - { - CodeMarkers.Instance.CFamilyConvertIssueFileAlreadyLoaded(filePath); - } - else - { - var doc = GetTextDocument(filePath); - pathToTextDocMap.Add(filePath, doc); - - CodeMarkers.Instance.CFamilyConvertIssueFileLoaded(filePath); - } - } - - return pathToTextDocMap; - } - - private ITextDocument GetTextDocument(string filePath) - { - if (fileSystem.File.Exists(filePath)) - { - // The document is being loaded from disc, so it should match the version that was analyzed by the subprocess - var doc = textDocumentFactoryService.CreateAndLoadTextDocument(filePath, filesContentType); - return doc; - } - - return null; - } - - private IAnalysisIssue ToAnalysisIssue(Message cFamilyIssue, - string sqLanguage, - IssueSeverity defaultSeverity, - IssueType defaultType, - IReadOnlyList flows, - IReadOnlyDictionary fileContents, - Impact highestImpact) - { - return new AnalysisIssue - ( - id: null, // until CFamily is migrated to SlCore, its ID will be null - ruleKey: sqLanguage + ":" + cFamilyIssue.RuleKey, - severity: Convert(defaultSeverity), - type: Convert(defaultType), - highestImpact, - primaryLocation: ToAnalysisIssueLocation(cFamilyIssue, fileContents), - flows: flows, - fixes: ToQuickFixes(cFamilyIssue) - ); - } - - private static List ToQuickFixes(Message cFamilyIssue) - { - return cFamilyIssue.Fixes.Select(f => - new QuickFix( - message: f.Message, - f.Edits?.Select(e => - new Core.Analysis.Edit( - textRange: new TextRange( - startLine: e.StartLine, - startLineOffset: e.StartColumn - 1, - endLine: e.EndLine, - endLineOffset: e.EndColumn - 1, - lineHash: null), - text: e.Text)) - .ToList())) - .ToList(); - } - - private string CalculateLineHash(MessagePart cFamilyIssueLocation, IReadOnlyDictionary fileContents) - { - var isFileLevelLocation = cFamilyIssueLocation.Line == 1 && - cFamilyIssueLocation.Column <= 1 && - cFamilyIssueLocation.EndColumn == 0 && - cFamilyIssueLocation.EndLine == 0; - - if (isFileLevelLocation) - { - return null; - } - - var textSnapshot = fileContents[cFamilyIssueLocation.Filename]?.TextBuffer?.CurrentSnapshot; - - return textSnapshot == null ? null : lineHashCalculator.Calculate(textSnapshot, cFamilyIssueLocation.Line); - } - - private AnalysisIssueLocation ToAnalysisIssueLocation(MessagePart cFamilyIssueLocation, - IReadOnlyDictionary fileContents) => - new AnalysisIssueLocation - ( - filePath: Path.IsPathRooted(cFamilyIssueLocation.Filename) - ? Path.GetFullPath(cFamilyIssueLocation.Filename) - : cFamilyIssueLocation.Filename, - message: cFamilyIssueLocation.Text, - textRange: new TextRange( - lineHash: CalculateLineHash(cFamilyIssueLocation, fileContents), - startLine: cFamilyIssueLocation.Line, - endLine: cFamilyIssueLocation.EndLine, - - // We don't care about the columns in the special case EndLine=0 - startLineOffset: cFamilyIssueLocation.EndLine == 0 ? 0 : cFamilyIssueLocation.Column - 1, - endLineOffset: cFamilyIssueLocation.EndLine == 0 ? 0 : cFamilyIssueLocation.EndColumn - 1 - )); - - /// - /// Converts from the CFamily issue severity enum to the standard AnalysisIssueSeverity - /// - internal /* for testing */ static AnalysisIssueSeverity Convert(IssueSeverity issueSeverity) - { - switch (issueSeverity) - { - case IssueSeverity.Blocker: - return AnalysisIssueSeverity.Blocker; - - case IssueSeverity.Critical: - return AnalysisIssueSeverity.Critical; - - case IssueSeverity.Info: - return AnalysisIssueSeverity.Info; - - case IssueSeverity.Major: - return AnalysisIssueSeverity.Major; - - case IssueSeverity.Minor: - return AnalysisIssueSeverity.Minor; - - default: - throw new ArgumentOutOfRangeException(nameof(issueSeverity)); - } - } - - /// - /// Converts from the CFamily issue type enum to the standard AnalysisIssueType - /// - internal /* for testing */static AnalysisIssueType Convert(IssueType issueType) - { - switch (issueType) - { - case IssueType.Bug: - return AnalysisIssueType.Bug; - - case IssueType.CodeSmell: - return AnalysisIssueType.CodeSmell; - - case IssueType.Vulnerability: - return AnalysisIssueType.Vulnerability; - - case IssueType.SecurityHotspot: - return AnalysisIssueType.SecurityHotspot; - - default: - throw new ArgumentOutOfRangeException(nameof(issueType)); - } - } - - internal /* for testing */ static Impact GetHighestImpact(RuleMetadata ruleMetadata) - { - if (ruleMetadata?.Code?.Impacts == null || ruleMetadata.Code.Impacts.Count == 0) - { - return null; - } - - var highestImpact = ruleMetadata.Code.Impacts.OrderByDescending(kvp => kvp.Value).ThenByDescending(kvp => kvp.Key).First(); - return new Impact(highestImpact.Key, highestImpact.Value); - } - } -} From ea9cdde187b3d5942ab00c60098c6388e606f058 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:40:47 +0100 Subject: [PATCH 13/21] SLVS-1673 Include environment variables in generated compilation db (#5892) [SLVS-1673](https://sonarsource.atlassian.net/browse/SLVS-1673) [SLVS-1673]: https://sonarsource.atlassian.net/browse/SLVS-1673?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../CompilationDatabaseEntry.cs | 29 ++-- .../EnvironmentVariableProviderTests.cs | 20 +++ src/Core/EnvironmentVariableProvider.cs | 26 +++- .../VCXCompilationDatabaseProviderTests.cs | 132 +++++++++++++++--- .../VCXCompilationDatabaseStorageTests.cs | 47 +++---- ...egration.Vsix_Baseline_WithStrongNames.txt | 3 +- ...ation.Vsix_Baseline_WithoutStrongNames.txt | 3 +- .../IVCXCompilationDatabaseStorage.cs | 38 +++-- .../VCXCompilationDatabaseProvider.cs | 41 +++++- 9 files changed, 258 insertions(+), 81 deletions(-) rename src/CFamily/{CMake => CompilationDatabase}/CompilationDatabaseEntry.cs (62%) diff --git a/src/CFamily/CMake/CompilationDatabaseEntry.cs b/src/CFamily/CompilationDatabase/CompilationDatabaseEntry.cs similarity index 62% rename from src/CFamily/CMake/CompilationDatabaseEntry.cs rename to src/CFamily/CompilationDatabase/CompilationDatabaseEntry.cs index 12273db1a8..1332634f64 100644 --- a/src/CFamily/CMake/CompilationDatabaseEntry.cs +++ b/src/CFamily/CompilationDatabase/CompilationDatabaseEntry.cs @@ -20,23 +20,22 @@ using Newtonsoft.Json; -namespace SonarLint.VisualStudio.CFamily.CMake +namespace SonarLint.VisualStudio.CFamily.CompilationDatabase; + +/// +/// Schema based on https://clang.llvm.org/docs/JSONCompilationDatabase.html +/// +public class CompilationDatabaseEntry { - /// - /// Schema based on https://clang.llvm.org/docs/JSONCompilationDatabase.html - /// - public class CompilationDatabaseEntry - { - [JsonProperty("directory")] - public string Directory { get; set; } + [JsonProperty("directory")] + public string Directory { get; set; } - [JsonProperty("command")] - public string Command { get; set; } + [JsonProperty("command")] + public string Command { get; set; } - [JsonProperty("file")] - public string File { get; set; } + [JsonProperty("file")] + public string File { get; set; } - [JsonProperty("arguments")] - public string Arguments { get; set; } - } + [JsonProperty("environment")] + public IEnumerable Environment { get; set; } } diff --git a/src/Core.UnitTests/EnvironmentVariableProviderTests.cs b/src/Core.UnitTests/EnvironmentVariableProviderTests.cs index d3abc138c3..748ce830e7 100644 --- a/src/Core.UnitTests/EnvironmentVariableProviderTests.cs +++ b/src/Core.UnitTests/EnvironmentVariableProviderTests.cs @@ -28,6 +28,26 @@ namespace SonarLint.VisualStudio.Core.UnitTests [TestClass] public class EnvironmentVariableProviderTests { + [TestMethod] + public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported(); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestMethod] + public void GetAll_ReturnsExpectedValues() + { + var testSubject = EnvironmentVariableProvider.Instance; + using var environmentVariableScope = new EnvironmentVariableScope(); + environmentVariableScope.SetVariable("VAR1", "VAL1"); + environmentVariableScope.SetVariable("VAR2", "VAL2"); + + var variables = testSubject.GetAll(); + + variables.Should().HaveCountGreaterThan(2); + variables.Should().Contain([("VAR1", "VAL1"), ("VAR2", "VAL2")]); + } + [TestMethod] [DataRow(null)] [DataRow("")] diff --git a/src/Core/EnvironmentVariableProvider.cs b/src/Core/EnvironmentVariableProvider.cs index 098412c9d3..573d8f62d6 100644 --- a/src/Core/EnvironmentVariableProvider.cs +++ b/src/Core/EnvironmentVariableProvider.cs @@ -19,8 +19,11 @@ */ using System; +using System.Collections; +using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; +using SonarLint.VisualStudio.Core; namespace SonarLint.VisualStudio.Core { @@ -38,12 +41,20 @@ public interface IEnvironmentVariableProvider /// Gets the path to the system special folder that is identified by the specified enumeration. /// string GetFolderPath(Environment.SpecialFolder folder); + + /// + /// Gets the current process environment variables + /// + List<(string name, string value)> GetAll(); } + [Export(typeof(IEnvironmentVariableProvider))] + [PartCreationPolicy(CreationPolicy.Shared)] public class EnvironmentVariableProvider : IEnvironmentVariableProvider { - public static EnvironmentVariableProvider Instance { get; } = new EnvironmentVariableProvider(); + public static EnvironmentVariableProvider Instance { get; } = new(); + [ImportingConstructor] private EnvironmentVariableProvider() { // no-op @@ -60,6 +71,19 @@ public string TryGet(string variableName) } public string GetFolderPath(Environment.SpecialFolder folder) => Environment.GetFolderPath(folder); + + public List<(string name, string value)> GetAll() + { + var variables = new List<(string name, string value)>(); + foreach (DictionaryEntry environmentVariable in Environment.GetEnvironmentVariables()) + { + if (environmentVariable is { Key: string variableName, Value: string variableValue }) + { + variables.Add((variableName, variableValue)); + } + } + return variables; + } } /// diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs index 6a8c4100be..13e95e69b2 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs @@ -20,6 +20,7 @@ using NSubstitute.ReturnsExtensions; using SonarLint.VisualStudio.CFamily; +using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; @@ -28,60 +29,155 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; [TestClass] public class VCXCompilationDatabaseProviderTests { + private const string CDFile = "cdfilevalue"; + private const string CDDirectory = "cddirectoryvalue"; + private const string CDCommand = "cdcommandvalue"; + private const string EnvInclude = "envincludevalue"; private const string SourceFilePath = "some path"; private IVCXCompilationDatabaseStorage storage; private IFileConfigProvider fileConfigProvider; - private VCXCompilationDatabaseProvider testSubject; + private IEnvironmentVariableProvider envVarProvider; + + [TestInitialize] + public void TestInitialize() + { + storage = Substitute.For(); + fileConfigProvider = Substitute.For(); + envVarProvider = Substitute.For(); + envVarProvider.GetAll().Returns([]); + } [TestMethod] - public void MefCtor_CheckIsExported() => + public void MefCtor_CheckIsExported() + { + envVarProvider.GetAll().Returns([]); MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(envVarProvider), MefTestHelpers.CreateExport()); + } [TestMethod] public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - [TestInitialize] - public void TestInitialize() - { - storage = Substitute.For(); - fileConfigProvider = Substitute.For(); - testSubject = new VCXCompilationDatabaseProvider( - storage, - fileConfigProvider); - } - [TestMethod] public void CreateOrNull_NoFileConfig_ReturnsNull() { fileConfigProvider.Get(SourceFilePath, default).ReturnsNull(); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider); testSubject.CreateOrNull(SourceFilePath).Should().BeNull(); - storage.DidNotReceiveWithAnyArgs().CreateDatabase(default); + storage.DidNotReceiveWithAnyArgs().CreateDatabase(default, default, default, default); } [TestMethod] public void CreateOrNull_FileConfig_CantStore_ReturnsNull() { - var fileConfig = Substitute.For(); + var fileConfig = GetFileConfig(); fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); - storage.CreateDatabase(fileConfig).ReturnsNull(); + storage.CreateDatabase(default, default, default, default).ReturnsNullForAnyArgs(); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider); testSubject.CreateOrNull(SourceFilePath).Should().BeNull(); - storage.Received().CreateDatabase(fileConfig); + storage.Received().CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Any>()); } [TestMethod] public void CreateOrNull_FileConfig_StoresAndReturnsHandle() { - var fileConfig = Substitute.For(); + var fileConfig = GetFileConfig(); fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); var compilationDatabaseHandle = Substitute.For(); - storage.CreateDatabase(fileConfig).Returns(compilationDatabaseHandle); + storage.CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Any>()).Returns(compilationDatabaseHandle); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider); testSubject.CreateOrNull(SourceFilePath).Should().Be(compilationDatabaseHandle); } + + [TestMethod] + public void CreateOrNull_NoEnvIncludeInFileConfig_UsesStatic() + { + var fileConfig = GetFileConfig(null); + fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + envVarProvider.GetAll().Returns([("Var1", "Value1"), ("INCLUDE", "static"), ("Var2", "Value2")]); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider); + + testSubject.CreateOrNull(SourceFilePath); + + storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is>(x => x.SequenceEqual(new [] { "Var1=Value1", "INCLUDE=static", "Var2=Value2" }))); + } + + [TestMethod] + public void CreateOrNull_FileConfigHasEnvInclude_UsesDynamic() + { + var fileConfig = GetFileConfig(EnvInclude); + fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + envVarProvider.GetAll().Returns([("Var1", "Value1"), ("INCLUDE", "static"), ("Var2", "Value2")]); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider); + + testSubject.CreateOrNull(SourceFilePath); + + storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is>(x => x.SequenceEqual(new [] { "Var1=Value1", "Var2=Value2", $"INCLUDE={EnvInclude}"}))); + } + + [TestMethod] + public void CreateOrNull_NoStaticInclude_UsesDynamic() + { + var fileConfig = GetFileConfig(EnvInclude); + fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider); + + testSubject.CreateOrNull(SourceFilePath); + + storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is>(x => x.SequenceEqual(new [] { "Var1=Value1", "Var2=Value2", $"INCLUDE={EnvInclude}"}))); + } + + [TestMethod] + public void CreateOrNull_StaticEnvVarsAreCached() + { + var fileConfig = GetFileConfig(); + fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider); + + testSubject.CreateOrNull(SourceFilePath); + testSubject.CreateOrNull(SourceFilePath); + testSubject.CreateOrNull(SourceFilePath); + + envVarProvider.Received(1).GetAll(); + } + + private IFileConfig GetFileConfig(string envInclude = EnvInclude) + { + var fileConfig = Substitute.For(); + fileConfig.CDFile.Returns(CDFile); + fileConfig.CDDirectory.Returns(CDDirectory); + fileConfig.CDCommand.Returns(CDCommand); + fileConfig.EnvInclude.Returns(envInclude); + return fileConfig; + } } diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs index 07a739e6b0..5c9886d678 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs @@ -21,7 +21,7 @@ using System.IO; using Newtonsoft.Json; using NSubstitute.ExceptionExtensions; -using SonarLint.VisualStudio.CFamily.CMake; +using SonarLint.VisualStudio.CFamily.CompilationDatabase; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Helpers; using SonarLint.VisualStudio.Core.SystemAbstractions; @@ -35,12 +35,22 @@ public class VCXCompilationDatabaseStorageTests private const string CompileCommand = "compile"; private const string SourceDirectory = @"C:\a\b\c"; private const string SourceFileName = "source.cpp"; + private static readonly IEnumerable EnvValue = ["envincludevalue"]; private static readonly string SourceFilePath = Path.Combine(SourceDirectory, SourceFileName); private IFileSystemService fileSystemService; private IThreadHandling threadHandling; private IVCXCompilationDatabaseStorage testSubject; private TestLogger testLogger; + [TestInitialize] + public void TestInitialize() + { + fileSystemService = Substitute.For(); + threadHandling = Substitute.For(); + testLogger = new TestLogger(); + testSubject = new VCXCompilationDatabaseStorage(fileSystemService, threadHandling, testLogger); + } + [TestMethod] public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( @@ -51,21 +61,12 @@ public void MefCtor_CheckIsExported() => [TestMethod] public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - [TestInitialize] - public void TestInitialize() - { - fileSystemService = Substitute.For(); - threadHandling = Substitute.For(); - testLogger = new TestLogger(); - testSubject = new VCXCompilationDatabaseStorage(fileSystemService, threadHandling, testLogger); - } - [TestMethod] public void CreateDatabase_NonCriticalException_ReturnsNull() { fileSystemService.Directory.CreateDirectory(default).ThrowsForAnyArgs(); - var database = testSubject.CreateDatabase(Substitute.For()); + var database = testSubject.CreateDatabase(default, default, default, default); database.Should().BeNull(); testLogger.AssertPartialOutputStrings(nameof(NotImplementedException)); @@ -76,7 +77,7 @@ public void CreateDatabase_CriticalException_Throws() { fileSystemService.Directory.CreateDirectory(default).ThrowsForAnyArgs(); - var act = () => testSubject.CreateDatabase(Substitute.For()); + var act = () => testSubject.CreateDatabase(default, default, default, default); act.Should().Throw(); } @@ -85,9 +86,8 @@ public void CreateDatabase_CriticalException_Throws() public void CreateDatabase_FileWritten_ReturnsPathToDatabaseWithCorrectContent() { var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); - var fileConfig = SetUpFileConfig(); - var databaseHandle = testSubject.CreateDatabase(fileConfig); + var databaseHandle = testSubject.CreateDatabase(SourceFilePath, SourceDirectory, CompileCommand, EnvValue); var temporaryCompilationDatabaseHandle = databaseHandle.Should().BeOfType().Subject; Directory.GetParent(temporaryCompilationDatabaseHandle.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); @@ -102,10 +102,9 @@ public void CreateDatabase_FileWritten_ReturnsPathToDatabaseWithCorrectContent() public void CreateDatabase_CreatesDifferentHandlesForSameFile() { var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); - var fileConfig = SetUpFileConfig(); - var databaseHandle1 = testSubject.CreateDatabase(fileConfig); - var databaseHandle2 = testSubject.CreateDatabase(fileConfig); + var databaseHandle1 = testSubject.CreateDatabase(SourceFilePath, SourceDirectory, CompileCommand, EnvValue); + var databaseHandle2 = testSubject.CreateDatabase(SourceFilePath, SourceDirectory, CompileCommand, EnvValue); Directory.GetParent(databaseHandle1.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); Directory.GetParent(databaseHandle2.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); @@ -117,7 +116,7 @@ public void CreateDatabase_Disposed_Throws() { testSubject.Dispose(); - var act = () => testSubject.CreateDatabase(Substitute.For()); + var act = () => testSubject.CreateDatabase(default, default, default, default); act.Should().Throw(); } @@ -157,19 +156,11 @@ public void Dispose_CatchesAndLogsException() testLogger.AssertPartialOutputStringExists(exception.ToString()); } - private static IFileConfig SetUpFileConfig() - { - var fileConfig = Substitute.For(); - fileConfig.CDFile.Returns(SourceFilePath); - fileConfig.CDDirectory.Returns(SourceDirectory); - fileConfig.CDCommand.Returns(CompileCommand); - return fileConfig; - } - private void VerifyDatabaseContents() { var serializedCompilationDatabase = fileSystemService.File.ReceivedCalls().Single().GetArguments()[1] as string; var compilationDatabaseEntries = JsonConvert.DeserializeObject(serializedCompilationDatabase); - compilationDatabaseEntries.Should().BeEquivalentTo(new CompilationDatabaseEntry { Directory = SourceDirectory, File = SourceFilePath, Command = CompileCommand, Arguments = null }); + var compilationDatabaseEntry = compilationDatabaseEntries.Single(); + compilationDatabaseEntry.Should().BeEquivalentTo(new CompilationDatabaseEntry { Directory = SourceDirectory, File = SourceFilePath, Command = CompileCommand, Environment = EnvValue}); } } diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt index 1b62fd4199..dbf76edd16 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt @@ -56,12 +56,13 @@ Referenced assemblies: - 'SonarLint.VisualStudio.SLCore, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' - 'SonarQube.Client, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' +- 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' - 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' -# Number of references: 31 +# Number of references: 32 --- Assembly: 'SonarLint.VisualStudio.CFamily, Version=8.10.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244' diff --git a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt index 3634e8129c..c782ad8848 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt @@ -56,12 +56,13 @@ Referenced assemblies: - 'SonarLint.VisualStudio.SLCore, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' - 'SonarQube.Client, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' - 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' +- 'System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' - 'System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59' - 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' - 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' -# Number of references: 31 +# Number of references: 32 --- Assembly: 'SonarLint.VisualStudio.CFamily, Version=8.10.0.0, Culture=neutral, PublicKeyToken=null' diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index df2c92c6c5..8a68220e98 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -21,7 +21,7 @@ using System.ComponentModel.Composition; using System.IO; using Newtonsoft.Json; -using SonarLint.VisualStudio.CFamily.CMake; +using SonarLint.VisualStudio.CFamily.CompilationDatabase; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Core.Helpers; @@ -31,28 +31,46 @@ namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; internal interface IVCXCompilationDatabaseStorage : IDisposable { - ICompilationDatabaseHandle CreateDatabase(IFileConfig fileConfig); + ICompilationDatabaseHandle CreateDatabase( + string file, + string directory, + string command, + IEnumerable environment); } [Export(typeof(IVCXCompilationDatabaseStorage))] [PartCreationPolicy(CreationPolicy.Shared)] -[method: ImportingConstructor] -internal sealed class VCXCompilationDatabaseStorage(IFileSystemService fileSystemService, IThreadHandling threadHandling, ILogger logger) - : IVCXCompilationDatabaseStorage +internal sealed class VCXCompilationDatabaseStorage : IVCXCompilationDatabaseStorage { - private bool disposed; private readonly string compilationDatabaseDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); + private readonly IFileSystemService fileSystemService; + private readonly IThreadHandling threadHandling; + private readonly ILogger logger; + private bool disposed; + + [ImportingConstructor] + public VCXCompilationDatabaseStorage(IFileSystemService fileSystemService, IThreadHandling threadHandling, ILogger logger) + { + this.fileSystemService = fileSystemService; + this.threadHandling = threadHandling; + this.logger = logger; + } - public ICompilationDatabaseHandle CreateDatabase(IFileConfig fileConfig) + public ICompilationDatabaseHandle CreateDatabase( + string file, + string directory, + string command, + IEnumerable environment) { ThrowIfDisposed(); threadHandling.ThrowIfOnUIThread(); var compilationDatabaseEntry = new CompilationDatabaseEntry { - Directory = fileConfig.CDDirectory, - Command = fileConfig.CDCommand, - File = fileConfig.CDFile + Directory = directory, + Command = command, + File = file, + Environment = environment }; var compilationDatabase = new[] { compilationDatabaseEntry }; diff --git a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs index c8e5b7c46d..8d287fe8c7 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -18,22 +18,49 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.Immutable; using System.ComponentModel.Composition; using SonarLint.VisualStudio.CFamily; +using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.CFamily; namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; [Export(typeof(IVCXCompilationDatabaseProvider))] [PartCreationPolicy(CreationPolicy.Shared)] -[method: ImportingConstructor] -internal class VCXCompilationDatabaseProvider( - IVCXCompilationDatabaseStorage storage, - IFileConfigProvider fileConfigProvider) - : IVCXCompilationDatabaseProvider +internal class VCXCompilationDatabaseProvider : IVCXCompilationDatabaseProvider { + private const string IncludeEntryName = "INCLUDE"; + private readonly ImmutableList staticEnvironmentVariableEntries; + private readonly IVCXCompilationDatabaseStorage storage; + private readonly IFileConfigProvider fileConfigProvider; + + [method: ImportingConstructor] + public VCXCompilationDatabaseProvider( + IVCXCompilationDatabaseStorage storage, + IEnvironmentVariableProvider environmentVariableProvider, + IFileConfigProvider fileConfigProvider) + { + this.storage = storage; + this.fileConfigProvider = fileConfigProvider; + staticEnvironmentVariableEntries = ImmutableList.CreateRange(environmentVariableProvider.GetAll().Select(x => new EnvironmentEntry(x.name, x.value))); + } + public ICompilationDatabaseHandle CreateOrNull(string filePath) => - fileConfigProvider.Get(filePath, null) is {} fileConfig - ? storage.CreateDatabase(fileConfig) + fileConfigProvider.Get(filePath, null) is { } fileConfig + ? storage.CreateDatabase(fileConfig.CDFile, fileConfig.CDDirectory, fileConfig.CDCommand, GetEnvironmentEntries(fileConfig.EnvInclude).Select(x => x.FormattedEntry)) : null; + + private ImmutableList GetEnvironmentEntries(string fileConfigEnvInclude) => + string.IsNullOrEmpty(fileConfigEnvInclude) + ? staticEnvironmentVariableEntries + : staticEnvironmentVariableEntries + .RemoveAll(x => x.Name == IncludeEntryName) + .Add(new EnvironmentEntry(IncludeEntryName, fileConfigEnvInclude)); + + private readonly struct EnvironmentEntry(string name, string value) + { + public string Name { get; } = name; + public string FormattedEntry { get; } = $"{name}={value}"; + } } From 15ca989d0f3b02929189cb9781d809a3691f840f Mon Sep 17 00:00:00 2001 From: Michael Jabbour <117195239+michael-jabbour-sonarsource@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:24:25 +0100 Subject: [PATCH 14/21] SLVS-1690 Analyze CFamily header files as such to avoid FPs (#5895) [SLVS-1690](https://sonarsource.atlassian.net/browse/SLVS-1690) This is done by including a special environment variable in the compilation database entry generated for header files. This environment variable is recognized starting with CFamily analyzer v6.62 (CPP-5898). Additionally, the PR adds some verbose logs to describe which env variables were overwritten/added (similar to what we used to have [here](https://github.com/SonarSource/sonarlint-visualstudio/blob/6498da7a3788467884af7446966029aaab5625ce/src/CFamily/Subprocess/ProcessRunner.cs#L172-L1930)). [SLVS-1690]: https://sonarsource.atlassian.net/browse/SLVS-1690?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../CFamily/VcxProject/FileConfigTests.cs | 4 ++ .../VCXCompilationDatabaseProviderTests.cs | 51 +++++++++++++++---- .../CFamily/VcxProject/FileConfig.cs | 5 +- .../CFamily/VcxProject/IFileConfig.cs | 1 + .../VCXCompilationDatabaseProvider.cs | 43 +++++++++++++--- 5 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs index 7f34345926..9263622f27 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs @@ -142,6 +142,7 @@ public void TryGet_Full_Cmd() Assert.AreEqual("C:\\path\\includeDir1;C:\\path\\includeDir2;C:\\path\\includeDir3;", request.EnvInclude); Assert.AreEqual("c:\\dummy\\file.cpp", request.CDFile); Assert.AreEqual("c:\\foo", request.CDDirectory); + Assert.IsFalse(request.IsHeaderFile); } [TestMethod] @@ -175,6 +176,7 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() request.Should().NotBeNull(); Assert.AreEqual("\"C:\\path\\cl.exe\" /Yu\"pch.h\" /FI\"pch.h\" /EHsc /RTCu \"c:\\dummy\\file.h\"", request.CDCommand); Assert.AreEqual("c:\\dummy\\file.h", request.CDFile); + Assert.IsTrue(request.IsHeaderFile); // Arrange projectItemConfig.FileConfigProperties["CompileAs"] = "CompileAsC"; @@ -186,6 +188,7 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() // Assert Assert.AreEqual("\"C:\\path\\cl.exe\" /FI\"FHeader.h\" /Yu\"pch.h\" /EHsc /RTCu /TC \"c:\\dummy\\file.h\"", request.CDCommand); Assert.AreEqual("c:\\dummy\\file.h", request.CDFile); + Assert.IsTrue(request.IsHeaderFile); // Arrange projectItemConfig.FileConfigProperties["CompileAs"] = "CompileAsCpp"; @@ -196,6 +199,7 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() // Assert Assert.AreEqual("\"C:\\path\\cl.exe\" /FI\"FHeader.h\" /Yu\"pch.h\" /EHsc /RTCu /TP \"c:\\dummy\\file.h\"", request.CDCommand); Assert.AreEqual("c:\\dummy\\file.h", request.CDFile); + Assert.IsTrue(request.IsHeaderFile); } [TestMethod] diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs index 13e95e69b2..025ddb6d11 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs @@ -37,6 +37,7 @@ public class VCXCompilationDatabaseProviderTests private IVCXCompilationDatabaseStorage storage; private IFileConfigProvider fileConfigProvider; private IEnvironmentVariableProvider envVarProvider; + private ILogger logger; [TestInitialize] public void TestInitialize() @@ -45,6 +46,7 @@ public void TestInitialize() fileConfigProvider = Substitute.For(); envVarProvider = Substitute.For(); envVarProvider.GetAll().Returns([]); + logger = Substitute.For(); } [TestMethod] @@ -54,7 +56,8 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(envVarProvider), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); } [TestMethod] @@ -67,7 +70,8 @@ public void CreateOrNull_NoFileConfig_ReturnsNull() var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, - fileConfigProvider); + fileConfigProvider, + logger); testSubject.CreateOrNull(SourceFilePath).Should().BeNull(); @@ -83,7 +87,8 @@ public void CreateOrNull_FileConfig_CantStore_ReturnsNull() var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, - fileConfigProvider); + fileConfigProvider, + logger); testSubject.CreateOrNull(SourceFilePath).Should().BeNull(); @@ -100,7 +105,8 @@ public void CreateOrNull_FileConfig_StoresAndReturnsHandle() var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, - fileConfigProvider); + fileConfigProvider, + logger); testSubject.CreateOrNull(SourceFilePath).Should().Be(compilationDatabaseHandle); } @@ -114,7 +120,8 @@ public void CreateOrNull_NoEnvIncludeInFileConfig_UsesStatic() var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, - fileConfigProvider); + fileConfigProvider, + logger); testSubject.CreateOrNull(SourceFilePath); @@ -130,11 +137,13 @@ public void CreateOrNull_FileConfigHasEnvInclude_UsesDynamic() var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, - fileConfigProvider); + fileConfigProvider, + logger); testSubject.CreateOrNull(SourceFilePath); storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is>(x => x.SequenceEqual(new [] { "Var1=Value1", "Var2=Value2", $"INCLUDE={EnvInclude}"}))); + logger.Received(1).LogVerbose($"[VCXCompilationDatabaseProvider] Overwriting the value of environment variable \"INCLUDE\". Old value: \"static\", new value: \"{EnvInclude}\""); } [TestMethod] @@ -146,11 +155,13 @@ public void CreateOrNull_NoStaticInclude_UsesDynamic() var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, - fileConfigProvider); + fileConfigProvider, + logger); testSubject.CreateOrNull(SourceFilePath); storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is>(x => x.SequenceEqual(new [] { "Var1=Value1", "Var2=Value2", $"INCLUDE={EnvInclude}"}))); + logger.Received(1).LogVerbose($"[VCXCompilationDatabaseProvider] Setting environment variable \"INCLUDE\". Value: \"{EnvInclude}\""); } [TestMethod] @@ -162,7 +173,8 @@ public void CreateOrNull_StaticEnvVarsAreCached() var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, - fileConfigProvider); + fileConfigProvider, + logger); testSubject.CreateOrNull(SourceFilePath); testSubject.CreateOrNull(SourceFilePath); @@ -171,13 +183,34 @@ public void CreateOrNull_StaticEnvVarsAreCached() envVarProvider.Received(1).GetAll(); } - private IFileConfig GetFileConfig(string envInclude = EnvInclude) + [TestMethod] + public void CreateOrNull_EnvVarsContainHeaderPropertyForHeaderFiles() + { + var fileConfig = GetFileConfig(EnvInclude, true); + fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]); + var testSubject = new VCXCompilationDatabaseProvider( + storage, + envVarProvider, + fileConfigProvider, + logger); + + testSubject.CreateOrNull(SourceFilePath); + + var expectedEnv = new[] { "Var1=Value1", "Var2=Value2", $"INCLUDE={EnvInclude}", "SONAR_CFAMILY_CAPTURE_PROPERTY_isHeaderFile=true" }; + storage.Received(1).CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Is>(x => x.SequenceEqual(expectedEnv))); + logger.Received(1).LogVerbose($"[VCXCompilationDatabaseProvider] Setting environment variable \"INCLUDE\". Value: \"{EnvInclude}\""); + logger.Received(1).LogVerbose($"[VCXCompilationDatabaseProvider] Setting environment variable \"SONAR_CFAMILY_CAPTURE_PROPERTY_isHeaderFile\". Value: \"true\""); + } + + private IFileConfig GetFileConfig(string envInclude = EnvInclude, bool isHeader = false) { var fileConfig = Substitute.For(); fileConfig.CDFile.Returns(CDFile); fileConfig.CDDirectory.Returns(CDDirectory); fileConfig.CDCommand.Returns(CDCommand); fileConfig.EnvInclude.Returns(envInclude); + fileConfig.IsHeaderFile.Returns(isHeader); return fileConfig; } } diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs index 93e11de617..8de56fbc91 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs @@ -46,7 +46,8 @@ public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, stri return null; } - CmdBuilder cmdBuilder = new CmdBuilder(vcFile.ItemType == "ClInclude"); + bool isHeaderFile = vcFile.ItemType == "ClInclude"; + CmdBuilder cmdBuilder = new CmdBuilder(isHeaderFile); var compilerPath = vcConfig.GetEvaluatedPropertyValue("ClCompilerPath"); if (string.IsNullOrEmpty(compilerPath)) @@ -77,6 +78,7 @@ public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, stri CDCommand = cmdBuilder.GetFullCmd(), CDFile = absoluteFilePath, EnvInclude = envINCLUDE, + IsHeaderFile = isHeaderFile, }; } @@ -134,6 +136,7 @@ private static IVCRulePropertyStorage GetVcFileSettings(ILogger logger, string a public string CDCommand { get; set; } public string CDFile { get; set; } public string EnvInclude { get; set; } + public bool IsHeaderFile { get; set; } #endregion diff --git a/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs b/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs index 874cfe1c34..94c3816850 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IFileConfig.cs @@ -26,5 +26,6 @@ internal interface IFileConfig string CDCommand { get; } string CDFile { get; } string EnvInclude { get; } + bool IsHeaderFile { get; } } } diff --git a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs index 8d287fe8c7..b4ff05f137 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -31,36 +31,63 @@ namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; internal class VCXCompilationDatabaseProvider : IVCXCompilationDatabaseProvider { private const string IncludeEntryName = "INCLUDE"; + private const string IsHeaderEntryName = "SONAR_CFAMILY_CAPTURE_PROPERTY_isHeaderFile"; private readonly ImmutableList staticEnvironmentVariableEntries; private readonly IVCXCompilationDatabaseStorage storage; private readonly IFileConfigProvider fileConfigProvider; + private readonly ILogger logger; [method: ImportingConstructor] public VCXCompilationDatabaseProvider( IVCXCompilationDatabaseStorage storage, IEnvironmentVariableProvider environmentVariableProvider, - IFileConfigProvider fileConfigProvider) + IFileConfigProvider fileConfigProvider, + ILogger logger) { this.storage = storage; this.fileConfigProvider = fileConfigProvider; + this.logger = logger; staticEnvironmentVariableEntries = ImmutableList.CreateRange(environmentVariableProvider.GetAll().Select(x => new EnvironmentEntry(x.name, x.value))); } public ICompilationDatabaseHandle CreateOrNull(string filePath) => fileConfigProvider.Get(filePath, null) is { } fileConfig - ? storage.CreateDatabase(fileConfig.CDFile, fileConfig.CDDirectory, fileConfig.CDCommand, GetEnvironmentEntries(fileConfig.EnvInclude).Select(x => x.FormattedEntry)) + ? storage.CreateDatabase(fileConfig.CDFile, fileConfig.CDDirectory, fileConfig.CDCommand, GetEnvironmentEntries(fileConfig).Select(x => x.FormattedEntry)) : null; - private ImmutableList GetEnvironmentEntries(string fileConfigEnvInclude) => - string.IsNullOrEmpty(fileConfigEnvInclude) - ? staticEnvironmentVariableEntries - : staticEnvironmentVariableEntries - .RemoveAll(x => x.Name == IncludeEntryName) - .Add(new EnvironmentEntry(IncludeEntryName, fileConfigEnvInclude)); + private ImmutableList GetEnvironmentEntries(IFileConfig fileConfig) + { + ImmutableList environmentEntries = staticEnvironmentVariableEntries; + if (!string.IsNullOrEmpty(fileConfig.EnvInclude)) + { + environmentEntries = UpdateEnvironmentWithEntry(environmentEntries, new EnvironmentEntry(IncludeEntryName, fileConfig.EnvInclude)); + } + if (fileConfig.IsHeaderFile) + { + environmentEntries = UpdateEnvironmentWithEntry(environmentEntries, new EnvironmentEntry(IsHeaderEntryName, "true")); + } + return environmentEntries; + } + + private ImmutableList UpdateEnvironmentWithEntry(ImmutableList environmentEntries, EnvironmentEntry newEntry) + { + EnvironmentEntry oldEntry = environmentEntries.FirstOrDefault(x => x.Name == newEntry.Name); + + if (oldEntry.Name != null) + { + logger.LogVerbose($"[VCXCompilationDatabaseProvider] Overwriting the value of environment variable \"{newEntry.Name}\". Old value: \"{oldEntry.Value}\", new value: \"{newEntry.Value}\""); + } + else + { + logger.LogVerbose($"[VCXCompilationDatabaseProvider] Setting environment variable \"{newEntry.Name}\". Value: \"{newEntry.Value}\""); + } + return environmentEntries.RemoveAll(x => x.Name == newEntry.Name).Add(newEntry); + } private readonly struct EnvironmentEntry(string name, string value) { public string Name { get; } = name; + public string Value { get; } = value; public string FormattedEntry { get; } = $"{name}={value}"; } } From 759073b7477d8a2f02a7ff1e80da104694272949 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:45:44 +0100 Subject: [PATCH 15/21] SLVS-1291 Remove IssueSeverity from RuleSettings (#5897) --- .../RuleSettingsSerializerTests.cs | 13 +++------- src/Core/UserRuleSettings/RulesSettings.cs | 13 ---------- .../Analysis/AnalyzeFilesResponseTests.cs | 24 +++++++++---------- .../Service/Analysis/Models/RawIssueDto.cs | 2 +- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/src/Core.UnitTests/RuleSettingsSerializerTests.cs b/src/Core.UnitTests/RuleSettingsSerializerTests.cs index 28a63f14d9..4c93ce8ad5 100644 --- a/src/Core.UnitTests/RuleSettingsSerializerTests.cs +++ b/src/Core.UnitTests/RuleSettingsSerializerTests.cs @@ -118,7 +118,7 @@ public void RealFile_RoundTripLoadAndSave_WithFirstCapsPropertyNames() 'Parameters': { 'key1': 'value1' }, - 'Severity': 'Critical' + 'Severity': 'Critical' }, 'xxx:yyy': { 'level': 'off', @@ -153,9 +153,6 @@ public void RealFile_RoundTripLoadAndSave_WithFirstCapsPropertyNames() loadedSettings.Rules["xxx:yyy"].Parameters["key2"].Should().Be("value2"); loadedSettings.Rules["xxx:yyy"].Parameters["key3"].Should().Be("value3"); - loadedSettings.Rules["typescript:S2685"].Severity.Should().Be(IssueSeverity.Critical); - loadedSettings.Rules["xxx:yyy"].Severity.Should().Be(IssueSeverity.Blocker); - // 2. Save and reload testSubject.SafeSave(filePath2, loadedSettings); File.Exists(filePath2).Should().BeTrue(); @@ -192,8 +189,8 @@ public void RealFile_RoundTripSaveAndLoad() { Rules = new Dictionary { - { "repo1:key1", new RuleConfig { Level = RuleLevel.Off, Severity = IssueSeverity.Major } }, - { "repo1:key2", new RuleConfig { Level = RuleLevel.On, Severity = IssueSeverity.Blocker } }, + { "repo1:key1", new RuleConfig { Level = RuleLevel.Off } }, + { "repo1:key2", new RuleConfig { Level = RuleLevel.On } }, { "repox:keyy", new RuleConfig { @@ -227,10 +224,6 @@ public void RealFile_RoundTripSaveAndLoad() reloadedSettings.Rules["repo1:key2"].Level.Should().Be(RuleLevel.On); reloadedSettings.Rules["repox:keyy"].Level.Should().Be(RuleLevel.On); - reloadedSettings.Rules["repo1:key1"].Severity.Should().Be(IssueSeverity.Major); - reloadedSettings.Rules["repo1:key2"].Severity.Should().Be(IssueSeverity.Blocker); - reloadedSettings.Rules["repox:keyy"].Severity.Should().BeNull(); - reloadedSettings.Rules["repo1:key1"].Parameters.Should().BeNull(); reloadedSettings.Rules["repo1:key2"].Parameters.Should().BeNull(); diff --git a/src/Core/UserRuleSettings/RulesSettings.cs b/src/Core/UserRuleSettings/RulesSettings.cs index 0a790b4856..65ea5ca146 100644 --- a/src/Core/UserRuleSettings/RulesSettings.cs +++ b/src/Core/UserRuleSettings/RulesSettings.cs @@ -72,10 +72,6 @@ public class RuleConfig // comparer, which is case-sensitive. [JsonProperty("parameters", NullValueHandling = NullValueHandling.Ignore)] public Dictionary Parameters { get; set; } - - [JsonProperty("severity", NullValueHandling = NullValueHandling.Ignore)] - [JsonConverter(typeof(StringEnumConverter))] - public IssueSeverity? Severity { get; set; } } public enum RuleLevel @@ -83,13 +79,4 @@ public enum RuleLevel On, Off } - - public enum IssueSeverity - { - Blocker = 0, - Critical = 1, - Major = 2, - Minor = 3, - Info = 4, - } } diff --git a/src/SLCore.UnitTests/Service/Analysis/AnalyzeFilesResponseTests.cs b/src/SLCore.UnitTests/Service/Analysis/AnalyzeFilesResponseTests.cs index af2b57921d..a4cb3856d1 100644 --- a/src/SLCore.UnitTests/Service/Analysis/AnalyzeFilesResponseTests.cs +++ b/src/SLCore.UnitTests/Service/Analysis/AnalyzeFilesResponseTests.cs @@ -22,7 +22,6 @@ using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Service.Analysis; using SonarLint.VisualStudio.SLCore.Service.Analysis.Models; -using IssueSeverity = SonarLint.VisualStudio.Core.UserRuleSettings.IssueSeverity; namespace SonarLint.VisualStudio.SLCore.UnitTests.Service.Analysis; @@ -30,7 +29,7 @@ namespace SonarLint.VisualStudio.SLCore.UnitTests.Service.Analysis; public class AnalyzeFilesResponseTests { [TestMethod] - public void Serialize_AsExpected() + public void Deserialize_AsExpected() { var failedFile = new FileUri("C:\\tmp\\junit14012097140227905793\\Foo.cs"); var textRange = new TextRangeDto(1, 0, 1, 20); @@ -42,25 +41,24 @@ public void Serialize_AsExpected() var failedAnalysisFiles = new HashSet { failedFile }; var rawIssues = new List { - new RawIssueDto(IssueSeverity.Major, RuleType.BUG, CleanCodeAttribute.IDENTIFIABLE, - new Dictionary(), "S123", "PRIMARY MESSAGE", failedFile, + new(IssueSeverity.MAJOR, RuleType.BUG, CleanCodeAttribute.IDENTIFIABLE, new(){[SoftwareQuality.MAINTAINABILITY] = ImpactSeverity.HIGH}, "S123", "PRIMARY MESSAGE", failedFile, rawIssueFlow, [], textRange, "RULE DESCRIPTION CONTEXT KEY", VulnerabilityProbability.HIGH) }; - var testSubject = new AnalyzeFilesResponse(failedAnalysisFiles, rawIssues); + var expectedResponse = new AnalyzeFilesResponse(failedAnalysisFiles, rawIssues); - const string expectedString = """ + const string serializedString = """ { "failedAnalysisFiles": [ "file:///C:/tmp/junit14012097140227905793/Foo.cs" ], "rawIssues": [ { - "severity": 2, - "type": 1, - "cleanCodeAttribute": 2, - "impacts": {}, + "severity": "MAJOR", + "type": "BUG", + "cleanCodeAttribute": "IDENTIFIABLE", + "impacts": { "MAINTAINABILITY" : "HIGH"}, "ruleKey": "S123", "primaryMessage": "PRIMARY MESSAGE", "fileUri": "file:///C:/tmp/junit14012097140227905793/Foo.cs", @@ -93,9 +91,9 @@ public void Serialize_AsExpected() ] } """; - - var serializedString = JsonConvert.SerializeObject(testSubject, Formatting.Indented); - serializedString.Should().Be(expectedString); + var actualResponse = JsonConvert.DeserializeObject(serializedString); + + actualResponse.Should().BeEquivalentTo(expectedResponse, options => options.ComparingByMembers().ComparingByMembers().ComparingByMembers()); } } diff --git a/src/SLCore/Service/Analysis/Models/RawIssueDto.cs b/src/SLCore/Service/Analysis/Models/RawIssueDto.cs index 2eb1b80bb6..f5bb6e9c30 100644 --- a/src/SLCore/Service/Analysis/Models/RawIssueDto.cs +++ b/src/SLCore/Service/Analysis/Models/RawIssueDto.cs @@ -19,7 +19,7 @@ */ using SonarLint.VisualStudio.SLCore.Common.Models; -using IssueSeverity = SonarLint.VisualStudio.Core.UserRuleSettings.IssueSeverity; +using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; namespace SonarLint.VisualStudio.SLCore.Service.Analysis.Models; From 7a0c30de5043e61dd49daf4bf94e0c9782d5a2b2 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:54:20 +0100 Subject: [PATCH 16/21] SLVS-1705 Adapt AnalysisStatusNotifier to issue streaming (#5899) --- .../Analysis/HotspotsPublisherTests.cs | 4 ++ .../Analysis/IssuePublisherTests.cs | 4 ++ src/Core/Analysis/HotspotPublisher.cs | 2 + src/Core/Analysis/IAnalysisStatusNotifier.cs | 3 +- src/Core/Analysis/IFindingsPublisher.cs | 1 + src/Core/Analysis/IssuePublisher.cs | 2 + src/Core/CoreStrings.Designer.cs | 19 ++++++- src/Core/CoreStrings.resx | 6 ++ .../Analysis/AnalysisStatusNotifierTests.cs | 45 +++++++++------ .../Analysis/AnalysisStatusNotifier.cs | 13 +++-- .../Analysis/AnalysisStrings.Designer.cs | 3 +- .../Analysis/AnalysisStrings.resx | 2 +- .../Analysis/RaisedFindingProcessorTests.cs | 57 ++++++++++++++----- .../Analysis/RaisedFindingProcessor.cs | 2 +- .../Analysis/SLCoreAnalyzerTests.cs | 4 +- src/SLCore/Analysis/SLCoreAnalyzer.cs | 6 ++ 16 files changed, 128 insertions(+), 45 deletions(-) diff --git a/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs b/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs index 346672f84f..bcbe605bb4 100644 --- a/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs +++ b/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs @@ -46,6 +46,10 @@ public void TestInitialize() testSubject = new HotspotPublisher(issueConsumerStorage); } + [TestMethod] + public void FindingsType_ReturnsCorrectValue() => + testSubject.FindingsType.Should().Be(CoreStrings.FindingType_Hotspot); + [TestMethod] public void PublishHotspots_NoConsumerInStorage_DoesNothing() { diff --git a/src/Core.UnitTests/Analysis/IssuePublisherTests.cs b/src/Core.UnitTests/Analysis/IssuePublisherTests.cs index 237d187890..953b1999fa 100644 --- a/src/Core.UnitTests/Analysis/IssuePublisherTests.cs +++ b/src/Core.UnitTests/Analysis/IssuePublisherTests.cs @@ -46,6 +46,10 @@ public void TestInitialize() testSubject = new IssuePublisher(issueConsumerStorage); } + [TestMethod] + public void FindingsType_ReturnsCorrectValue() => + testSubject.FindingsType.Should().Be(CoreStrings.FindingType_Issue); + [TestMethod] public void PublishIssues_NoConsumerInStorage_DoesNothing() { diff --git a/src/Core/Analysis/HotspotPublisher.cs b/src/Core/Analysis/HotspotPublisher.cs index 9cbe88f6af..0e061770ae 100644 --- a/src/Core/Analysis/HotspotPublisher.cs +++ b/src/Core/Analysis/HotspotPublisher.cs @@ -27,6 +27,8 @@ namespace SonarLint.VisualStudio.Core.Analysis; [method:ImportingConstructor] internal class HotspotPublisher(IIssueConsumerStorage issueConsumerStorage) : IHotspotPublisher { + public string FindingsType => CoreStrings.FindingType_Hotspot; + public void Publish(string filePath, Guid analysisId, IEnumerable findings) { if (issueConsumerStorage.TryGet(filePath, out var currentAnalysisId, out var issueConsumer) diff --git a/src/Core/Analysis/IAnalysisStatusNotifier.cs b/src/Core/Analysis/IAnalysisStatusNotifier.cs index 8c79f68af7..d33314f819 100644 --- a/src/Core/Analysis/IAnalysisStatusNotifier.cs +++ b/src/Core/Analysis/IAnalysisStatusNotifier.cs @@ -25,7 +25,8 @@ namespace SonarLint.VisualStudio.Core.Analysis public interface IAnalysisStatusNotifier { void AnalysisStarted(); - void AnalysisFinished(int issueCount, TimeSpan analysisTime); + void AnalysisProgressed(int issueCount, string findingType, bool isIntermediate); + void AnalysisFinished(TimeSpan analysisTime); void AnalysisCancelled(); void AnalysisFailed(Exception ex); void AnalysisFailed(string failureMessage); diff --git a/src/Core/Analysis/IFindingsPublisher.cs b/src/Core/Analysis/IFindingsPublisher.cs index 89bb3e6203..dd38205afc 100644 --- a/src/Core/Analysis/IFindingsPublisher.cs +++ b/src/Core/Analysis/IFindingsPublisher.cs @@ -22,6 +22,7 @@ namespace SonarLint.VisualStudio.Core.Analysis; public interface IFindingsPublisher { + string FindingsType { get; } /// /// Handles analysis results /// diff --git a/src/Core/Analysis/IssuePublisher.cs b/src/Core/Analysis/IssuePublisher.cs index df09fbe228..cf985584be 100644 --- a/src/Core/Analysis/IssuePublisher.cs +++ b/src/Core/Analysis/IssuePublisher.cs @@ -27,6 +27,8 @@ namespace SonarLint.VisualStudio.Core.Analysis; [method:ImportingConstructor] internal class IssuePublisher(IIssueConsumerStorage issueConsumerStorage) : IIssuePublisher { + public string FindingsType => CoreStrings.FindingType_Issue; + public void Publish(string filePath, Guid analysisId, IEnumerable findings) { if (issueConsumerStorage.TryGet(filePath, out var currentAnalysisId, out var issueConsumer) diff --git a/src/Core/CoreStrings.Designer.cs b/src/Core/CoreStrings.Designer.cs index e6267e555f..f22c464441 100644 --- a/src/Core/CoreStrings.Designer.cs +++ b/src/Core/CoreStrings.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -87,6 +86,24 @@ public static string CSharpLanguageName { } } + /// + /// Looks up a localized string similar to hotspot. + /// + public static string FindingType_Hotspot { + get { + return ResourceManager.GetString("FindingType_Hotspot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to issue. + /// + public static string FindingType_Issue { + get { + return ResourceManager.GetString("FindingType_Issue", resourceCulture); + } + } + /// /// Looks up a localized string similar to Solution/folder is not in a git repository. /// diff --git a/src/Core/CoreStrings.resx b/src/Core/CoreStrings.resx index 359e51278f..49f18b8669 100644 --- a/src/Core/CoreStrings.resx +++ b/src/Core/CoreStrings.resx @@ -193,4 +193,10 @@ https://sonarcloud.io + + issue + + + hotspot + \ No newline at end of file diff --git a/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs b/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs index 75ef113b6c..4f43cd601f 100644 --- a/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs +++ b/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs @@ -18,15 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Integration.Vsix.Analysis; using SonarLint.VisualStudio.Integration.Vsix.Helpers; -using SonarLint.VisualStudio.TestInfrastructure; namespace SonarLint.VisualStudio.Integration.UnitTests.Analysis { @@ -65,6 +59,24 @@ public void AnalysisStarted_LogToOutputWindow() logger.OutputStrings.Count.Should().Be(1); } + [DataRow(true)] + [DataRow(false)] + [DataTestMethod] + public void AnalysisProgressed_LogToOutputWindow(bool isIntermediate) + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + testSubject.AnalysisProgressed(123, "finding", isIntermediate); + + var expectedMessage = string.Format(AnalysisStrings.MSG_FoundIssues, 123, "finding", filePath, analysisId, !isIntermediate); + logger.AssertPartialOutputStringExists(expectedMessage); + logger.AssertPartialOutputStringExists(analyzerName); + } + [TestMethod] [DataRow("foo-finished.cpp", "foo-finished.cpp")] [DataRow("c:\\test\\foo-finished.cpp", "foo-finished.cpp")] @@ -72,9 +84,9 @@ public void AnalysisStarted_LogToOutputWindow() public void AnalysisFinished_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) { var statusBarMock = new Mock(); - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); - testSubject.AnalysisFinished(1, TimeSpan.Zero); + + testSubject.AnalysisFinished(TimeSpan.FromSeconds(3)); var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFinished, expectedNotifiedFileName); @@ -91,17 +103,14 @@ public void AnalysisFinished_LogToOutputWindow() var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); - testSubject.AnalysisFinished(123, TimeSpan.FromSeconds(6.54321)); + testSubject.AnalysisFinished(TimeSpan.FromSeconds(6.54321)); var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisComplete, filePath, analysisId, 6.543); logger.AssertPartialOutputStringExists(expectedMessage); - expectedMessage = string.Format($"Found {123} issue(s) for {filePath}"); - logger.AssertPartialOutputStringExists(expectedMessage); - logger.AssertPartialOutputStringExists(analyzerName); - logger.OutputStrings.Count.Should().Be(2); + logger.OutputStrings.Count.Should().Be(1); } [TestMethod] @@ -136,7 +145,7 @@ public void AnalysisCancelled_LogToOutputWindow() logger.AssertPartialOutputStringExists(expectedMessage); logger.OutputStrings.Count.Should().Be(1); } - + [TestMethod] [DataRow("foo-timedout.cpp")] [DataRow("c:\\test\\foo-timedout.cpp")] @@ -151,7 +160,7 @@ public void AnalysisNotReady_RemoveMessageAndStopSpinner(string filePath) VerifyStatusBarMessageAndIcon(statusBarMock, "", false); } - + [TestMethod] public void AnalysisNotReady_LogToOutputWindow() { @@ -170,7 +179,7 @@ public void AnalysisNotReady_LogToOutputWindow() logger.AssertPartialOutputStringExists(expectedMessage); logger.OutputStrings.Count.Should().Be(1); } - + [TestMethod] [DataRow("foo-failed.cpp", "foo-failed.cpp")] @@ -188,8 +197,8 @@ public void AnalysisFailed_DisplayMessageAndStopSpinner(string filePath, string VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); } - - + + [TestMethod] [DataRow("foo-failed.cpp", "foo-failed.cpp")] diff --git a/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs b/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs index 0b0cdd3c0b..685bcda64f 100644 --- a/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs +++ b/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs @@ -50,10 +50,15 @@ public void AnalysisStarted() Notify(AnalysisStrings.Notifier_AnalysisStarted, true); } - public void AnalysisFinished(int issueCount, TimeSpan analysisTime) + public void AnalysisProgressed( + int issueCount, + string findingType, + bool isIntermediate) => + Log(AnalysisStrings.MSG_FoundIssues, issueCount, findingType, filePath, analysisId, !isIntermediate); + + public void AnalysisFinished(TimeSpan analysisTime) { Log(AnalysisStrings.MSG_AnalysisComplete, filePath, analysisId, Math.Round(analysisTime.TotalSeconds, 3)); - Log(AnalysisStrings.MSG_FoundIssues, issueCount, filePath); Notify(AnalysisStrings.Notifier_AnalysisFinished, false); } @@ -61,7 +66,7 @@ public void AnalysisFinished(int issueCount, TimeSpan analysisTime) public void AnalysisCancelled() { Log(AnalysisStrings.MSG_AnalysisAborted, filePath, analysisId); - + Notify("", false); } @@ -80,7 +85,7 @@ public void AnalysisFailed(string failureMessage) public void AnalysisNotReady(string reason) { Log(AnalysisStrings.MSG_AnalysisNotReady, filePath, analysisId, reason); - + Notify("", false); } diff --git a/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs b/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs index 0b70f38c19..69cbe983ab 100644 --- a/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs +++ b/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -180,7 +179,7 @@ internal static string MSG_AnalysisStarted { } /// - /// Looks up a localized string similar to Found {0} issue(s) for {1}. + /// Looks up a localized string similar to Found {0} {1}(s) in {2} [id: {3}, final: {4}]. /// internal static string MSG_FoundIssues { get { diff --git a/src/Integration.Vsix/Analysis/AnalysisStrings.resx b/src/Integration.Vsix/Analysis/AnalysisStrings.resx index db4a804e97..58ad21366a 100644 --- a/src/Integration.Vsix/Analysis/AnalysisStrings.resx +++ b/src/Integration.Vsix/Analysis/AnalysisStrings.resx @@ -170,7 +170,7 @@ Analyzing {0} with id {1} - Found {0} issue(s) for {1} + Found {0} {1}(s) in {2} [id: {3}, final: {4}] Binding has changed. Open documents will be re-analysed. diff --git a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs index 8c3f6ebb3f..35f55590ee 100644 --- a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs +++ b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs @@ -28,8 +28,6 @@ using SonarLint.VisualStudio.SLCore.Listeners.Implementation.Analysis; using SonarLint.VisualStudio.SLCore.Protocol; using SonarLint.VisualStudio.SLCore.Service.Rules.Models; -using CleanCodeAttribute = SonarLint.VisualStudio.SLCore.Common.Models.CleanCodeAttribute; -using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; using SloopLanguage = SonarLint.VisualStudio.SLCore.Common.Models.Language; namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation.Analysis; @@ -37,6 +35,8 @@ namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation.Analy [TestClass] public class RaisedFindingProcessorTests { + private const string FindingsType = "FINDING"; + [TestMethod] public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( @@ -91,8 +91,9 @@ public void RaiseFindings_NoSupportedLanguages_PublishesEmpty() var findingsByFileUri = new Dictionary> { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - var publisher = Substitute.For(); + var isIntermediatePublication = false; + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediatePublication, analysisId); + var publisher = CreatePublisher(); IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); @@ -105,7 +106,8 @@ public void RaiseFindings_NoSupportedLanguages_PublishesEmpty() raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); - analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); + analysisStatusNotifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + analysisStatusNotifier.Received().AnalysisProgressed(0, FindingsType, isIntermediatePublication); } [TestMethod] @@ -116,8 +118,9 @@ public void RaiseFindings_NoKnownLanguages_PublishesEmpty() var findingsByFileUri = new Dictionary> { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - var publisher = Substitute.For(); + var isIntermediatePublication = false; + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediatePublication, analysisId); + var publisher = CreatePublisher(); IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); @@ -131,7 +134,8 @@ public void RaiseFindings_NoKnownLanguages_PublishesEmpty() raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); - analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); + analysisStatusNotifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + analysisStatusNotifier.Received().AnalysisProgressed(0, FindingsType, isIntermediatePublication); } [TestMethod] @@ -139,7 +143,7 @@ public void RaiseFindings_HasNoFileUri_FinishesAnalysis() { var analysisId = Guid.NewGuid(); var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, null, analysisId); - var publisher = Substitute.For(); + var publisher = CreatePublisher(); var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); var act = () => @@ -168,8 +172,9 @@ public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() var findingsByFileUri = new Dictionary> { { fileUri, raisedFindings } }; - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - var publisher = Substitute.For(); + var isIntermediatePublication = false; + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediatePublication, analysisId); + var publisher = CreatePublisher(); IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, filteredRaisedFindings, filteredIssues); @@ -187,7 +192,8 @@ public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() x => x.SequenceEqual(filteredRaisedFindings))); analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri.LocalPath, analysisId); - analysisStatusNotifier.Received(1).AnalysisFinished(2, TimeSpan.Zero); + analysisStatusNotifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + analysisStatusNotifier.Received().AnalysisProgressed(2, FindingsType, isIntermediatePublication); } [DataRow(true)] @@ -208,12 +214,13 @@ public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isInterm var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediate, analysisId); - var publisher = Substitute.For(); + var publisher = CreatePublisher(); var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri1, Arg.Any>()).Returns([analysisIssue1]); raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri2, Arg.Any>()).Returns([analysisIssue2]); - var analysisStatusNotifierFactory = Substitute.For(); + var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var notifier1, fileUri1.LocalPath, analysisId); + SetUpNotifierForFile(out var notifier2, fileUri2.LocalPath, analysisId, analysisStatusNotifierFactory); var testSubject = CreateTestSubject( raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter, @@ -228,6 +235,10 @@ public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isInterm analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri1.LocalPath, analysisId); analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri2.LocalPath, analysisId); + notifier1.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + notifier1.Received().AnalysisProgressed(1, FindingsType, isIntermediate); + notifier2.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + notifier2.Received().AnalysisProgressed(1, FindingsType, isIntermediate); } private RaisedFindingProcessor CreateTestSubject( @@ -261,9 +272,18 @@ private IAnalysisStatusNotifierFactory CreateAnalysisStatusNotifierFactory(out I Guid? analysisId) { var analysisStatusNotifierFactory = Substitute.For(); + SetUpNotifierForFile(out analysisStatusNotifier, filePath, analysisId, analysisStatusNotifierFactory); + return analysisStatusNotifierFactory; + } + + private static void SetUpNotifierForFile( + out IAnalysisStatusNotifier analysisStatusNotifier, + string filePath, + Guid? analysisId, + IAnalysisStatusNotifierFactory analysisStatusNotifierFactory) + { analysisStatusNotifier = Substitute.For(); analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), filePath, analysisId).Returns(analysisStatusNotifier); - return analysisStatusNotifierFactory; } private TestFinding CreateTestFinding(string ruleKey) @@ -271,6 +291,13 @@ private TestFinding CreateTestFinding(string ruleKey) return new TestFinding(default, default, ruleKey, default, default, default, default, default, default, default, default, default); } + private static IFindingsPublisher CreatePublisher() + { + var publisher = Substitute.For(); + publisher.FindingsType.Returns(FindingsType); + return publisher; + } + private static IAnalysisIssue CreateAnalysisIssue(string ruleKey) { var analysisIssue1 = Substitute.For(); diff --git a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs index 53e93b6c2e..c0430d285c 100644 --- a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs +++ b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs @@ -85,7 +85,7 @@ private void PublishFindings(RaiseFindingParams parameters, IFindingsPubli findingsPublisher.Publish(localPath, parameters.analysisId!.Value, raiseFindingToAnalysisIssueConverter.GetAnalysisIssues(fileUri, supportedRaisedIssues)); - analysisStatusNotifier.AnalysisFinished(supportedRaisedIssues.Length, TimeSpan.Zero); + analysisStatusNotifier.AnalysisProgressed(supportedRaisedIssues.Length, findingsPublisher.FindingsType, parameters.isIntermediatePublication); } } diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index b49d05faf7..a5c2631859 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -226,7 +226,7 @@ public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() } [TestMethod] - public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysis() + public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsByFinishingAnalysis() { SetUpInitializedConfigScope(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); @@ -236,7 +236,7 @@ public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysi notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(string)); - notifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default, default); + notifier.Received().AnalysisFinished(Arg.Is(x => x > TimeSpan.Zero)); } [TestMethod] diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index d3598dba12..7077ae6013 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -102,6 +102,8 @@ private async Task ExecuteAnalysisInternalAsync( Dictionary properties = []; using var temporaryResourcesHandle = EnrichPropertiesForCFamily(properties, path, detectedLanguages); + Stopwatch stopwatch = Stopwatch.StartNew(); + var (failedAnalysisFiles, _) = await analysisService.AnalyzeFilesAndTrackAsync( new AnalyzeFilesAndTrackParams( configScopeId, @@ -116,6 +118,10 @@ [new FileUri(path)], { analysisStatusNotifier.AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } + else + { + analysisStatusNotifier.AnalysisFinished(stopwatch.Elapsed); + } } catch (OperationCanceledException) { From 3197ad3667189115e1983d8b9ffe1d061363fd30 Mon Sep 17 00:00:00 2001 From: Mostafa Mohammed Date: Wed, 18 Dec 2024 14:46:07 +0100 Subject: [PATCH 17/21] SLVS-1710 Fix clang-cl compiler path we add a new fallback way to get the compiler path correctly. This fixes the `clang-cl` support, which is currently broken because the `ClCompilerPath` property we rely on evaluates to `/clang-cl.exe`. The new fallback method searches in the `ExecutablePath` path list for the `ClToolExe` executable, which gives the correct answer in the case of `clang-cl.exe` Co-authored-by: Georgii Borovinskikh Co-authored-by: Michael Jabbour <117195239+michael-jabbour-sonarsource@users.noreply.github.com> --- .../VcxProject/FileConfigProviderTests.cs | 14 +- .../CFamily/VcxProject/FileConfigTests.cs | 138 ++++++++++++++++-- .../CFamily/VcxProject/FileConfig.cs | 109 ++++++++++++-- .../CFamily/VcxProject/FileConfigProvider.cs | 5 +- 4 files changed, 236 insertions(+), 30 deletions(-) diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs index ce3e36f637..38d25e8b8c 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs @@ -28,6 +28,8 @@ using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; using static SonarLint.VisualStudio.Integration.Vsix.CFamily.UnitTests.CFamilyTestUtility; +using System.IO.Abstractions; +using SonarLint.VisualStudio.Core.SystemAbstractions; namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject { @@ -40,11 +42,21 @@ public class FileConfigProviderTests private DTE2 dte; private IVsUIServiceOperation uiServiceOperation; private FileConfigProvider testSubject; + private const string ClFilePath = "C:\\path\\cl.exe"; + + private static IFileSystemService CreateFileSystemWithExistingFile(string fullPath) + { + var fileSystem = Substitute.For(); + fileSystem.File.Exists(fullPath).Returns(true); + return fileSystem; + } + private static IFileSystemService CreateFileSystemWithClCompiler() => CreateFileSystemWithExistingFile(ClFilePath); [TestMethod] public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); @@ -60,7 +72,7 @@ public void TestInitialize() dte = Substitute.For(); uiServiceOperation = CreateDefaultUiServiceOperation(dte); - testSubject = new FileConfigProvider(uiServiceOperation, fileInSolutionIndicator, logger, new NoOpThreadHandler()); + testSubject = new FileConfigProvider(uiServiceOperation, fileInSolutionIndicator, CreateFileSystemWithClCompiler(), logger, new NoOpThreadHandler()); } private static IFileInSolutionIndicator CreateDefaultFileInSolutionIndicator() diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs index 9263622f27..acf2963cac 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigTests.cs @@ -18,14 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Generic; +using System.IO.Abstractions; using EnvDTE; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.VCProjectEngine; -using Moq; using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; -using SonarLint.VisualStudio.TestInfrastructure; using static SonarLint.VisualStudio.Integration.Vsix.CFamily.UnitTests.CFamilyTestUtility; namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject @@ -34,6 +30,25 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject public class FileConfigTests { private readonly TestLogger testLogger = new TestLogger(); + private const string ClFilePath = "C:\\path\\cl.exe"; + private const string ClangClFilePath = "C:\\path\\clang-cl.exe"; + + private static IFileSystem CreateFileSystemWithNoFiles() + { + var fileSystem = Substitute.For(); + fileSystem.File.Exists(Arg.Any()).Returns(false); + return fileSystem; + } + + private static IFileSystem CreateFileSystemWithExistingFile(string fullPath) + { + var fileSystem = Substitute.For(); + fileSystem.File.Exists(fullPath).Returns(true); + return fileSystem; + } + + private static IFileSystem CreateFileSystemWithClCompiler() => CreateFileSystemWithExistingFile(ClFilePath); + private static IFileSystem CreateFileSystemWithClangClCompiler() => CreateFileSystemWithExistingFile(ClangClFilePath); [TestMethod] public void TryGet_NoVCProject_ReturnsNull() @@ -45,7 +60,7 @@ public void TryGet_NoVCProject_ReturnsNull() dteProjectItemMock.Setup(x => x.Object).Returns(Mock.Of()); dteProjectItemMock.Setup(x => x.ContainingProject).Returns(dteProjectMock.Object); - FileConfig.TryGet(testLogger, dteProjectItemMock.Object, "c:\\path") + FileConfig.TryGet(testLogger, dteProjectItemMock.Object, "c:\\path", CreateFileSystemWithClCompiler()) .Should().BeNull(); } @@ -59,7 +74,7 @@ public void TryGet_NoVCFile_ReturnsNull() dteProjectItemMock.Setup(x => x.Object).Returns(null); dteProjectItemMock.Setup(x => x.ContainingProject).Returns(dteProjectMock.Object); - FileConfig.TryGet(testLogger, dteProjectItemMock.Object, "c:\\path") + FileConfig.TryGet(testLogger, dteProjectItemMock.Object, "c:\\path", CreateFileSystemWithClCompiler()) .Should().BeNull(); } @@ -71,7 +86,7 @@ public void TryGet_UnsupportedItemType_ReturnsNull() var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); // Act - var fileConfig = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp"); + var fileConfig = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithClCompiler()); // Assert fileConfig.Should().BeNull(); @@ -86,7 +101,7 @@ public void TryGet_UnsupportedConfigurationType_ReturnsNull() var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); // Act - var fileConfig = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp"); + var fileConfig = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithClCompiler()); // Assert fileConfig.Should().BeNull(); @@ -101,7 +116,7 @@ public void TryGet_UnsupportedCustomBuild_ReturnsNull() var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); // Act - var fileConfig = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp"); + var fileConfig = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithClCompiler()); // Assert fileConfig.Should().BeNull(); @@ -134,7 +149,7 @@ public void TryGet_Full_Cmd() var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); // Act - var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp"); + var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithClCompiler()); // Assert request.Should().NotBeNull(); @@ -170,7 +185,7 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); // Act - var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h"); + var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h", CreateFileSystemWithClCompiler()); // Assert request.Should().NotBeNull(); @@ -183,7 +198,7 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() projectItemConfig.FileConfigProperties["ForcedIncludeFiles"] = "FHeader.h"; // Act - request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h"); + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h", CreateFileSystemWithClCompiler()); // Assert Assert.AreEqual("\"C:\\path\\cl.exe\" /FI\"FHeader.h\" /Yu\"pch.h\" /EHsc /RTCu /TC \"c:\\dummy\\file.h\"", request.CDCommand); @@ -194,7 +209,7 @@ public void TryGet_HeaderFileOptions_ReturnsValidConfig() projectItemConfig.FileConfigProperties["CompileAs"] = "CompileAsCpp"; // Act - request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h"); + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.h", CreateFileSystemWithClCompiler()); // Assert Assert.AreEqual("\"C:\\path\\cl.exe\" /FI\"FHeader.h\" /Yu\"pch.h\" /EHsc /RTCu /TP \"c:\\dummy\\file.h\"", request.CDCommand); @@ -220,7 +235,7 @@ public void TryGet_CompilerName_VS2017() var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); // Act - var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp"); + var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithExistingFile("C:\\path\\x86\\cl.exe")); // Assert request.Should().NotBeNull(); @@ -230,12 +245,103 @@ public void TryGet_CompilerName_VS2017() projectItemConfig.PlatformName = "x64"; projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); // Act - request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp"); + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithExistingFile("C:\\path\\x64\\cl.exe")); // Assert request.Should().NotBeNull(); Assert.IsTrue(request.CDCommand.StartsWith("\"C:\\path\\x64\\cl.exe\"")); } + [TestMethod] + public void TryGet_CompilerName_ClangCL() + { + // Arrange + var projectItemConfig = new ProjectItemConfig + { + ProjectConfigProperties = new Dictionary + { + ["ClCompilerPath"] = null, + ["IncludePath"] = "C:\\path\\includeDir1;C:\\path\\includeDir2;C:\\path\\includeDir3;", + ["ExecutablePath"] = "C:\\path\\no-compiler\\;C:\\path", + ["CLToolExe"] = "clang-cl.exe", + ["VC_ExecutablePath_x86"] = "C:\\path\\x86", + ["VC_ExecutablePath_x64"] = "C:\\path\\x64", + } + }; + + var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); + + // Act + var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithClangClCompiler()); + + // Assert + request.Should().NotBeNull(); + Assert.IsTrue(request.CDCommand.StartsWith("\"C:\\path\\clang-cl.exe\"")); + + // Arrange + projectItemConfig.ProjectConfigProperties["ClCompilerPath"] = "\\clang-cl.exe"; + + // Act + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithClangClCompiler()); + + // Assert + request.Should().NotBeNull(); + Assert.IsTrue(request.CDCommand.StartsWith("\"C:\\path\\clang-cl.exe\"")); + + // Act + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithNoFiles()); + + // Assert + request.Should().BeNull(); + testLogger.AssertOutputStringExists("Compiler is not supported."); + + // Arrange + projectItemConfig.ProjectConfigProperties["ClToolExe"] = null; + + // Act + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithExistingFile("C:\\path\\x86\\cl.exe")); + + // Assert + request.Should().NotBeNull(); + Assert.IsTrue(request.CDCommand.StartsWith("\"C:\\path\\x86\\cl.exe\"")); + + // Arrange + projectItemConfig.ProjectConfigProperties["ClToolExe"] = null; + + // Act + request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithNoFiles()); + + // Assert + request.Should().BeNull(); + testLogger.AssertOutputStringExists("Compiler is not supported."); + } + + [TestMethod] + public void TryGet_CompilerName_CL_No_ClCompilerPath_NoCLToolExe() + { + // Arrange + var projectItemConfig = new ProjectItemConfig + { + ProjectConfigProperties = new Dictionary + { + ["ClCompilerPath"] = null, + ["IncludePath"] = "C:\\path\\includeDir1;C:\\path\\includeDir2;C:\\path\\includeDir3;", + ["ExecutablePath"] = "C:\\path\\no-compiler\\;C:\\path", + ["CLToolExe"] = null, + ["VC_ExecutablePath_x86"] = "C:\\path\\x86", + ["VC_ExecutablePath_x64"] = "C:\\path\\x64", + } + }; + + var projectItemMock = CreateMockProjectItem("c:\\foo\\xxx.vcxproj", projectItemConfig); + + // Act + var request = FileConfig.TryGet(testLogger, projectItemMock.Object, "c:\\dummy\\file.cpp", CreateFileSystemWithClCompiler()); + + // Assert + request.Should().NotBeNull(); + Assert.IsTrue(request.CDCommand.StartsWith("\"C:\\path\\cl.exe\"")); + } + } } diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs index 8de56fbc91..a808bf24d7 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfig.cs @@ -20,6 +20,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; +using System.IO.Abstractions; using EnvDTE; using Microsoft.VisualStudio.VCProjectEngine; using SonarLint.VisualStudio.Core; @@ -29,7 +30,7 @@ namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject internal class FileConfig : IFileConfig { [ExcludeFromCodeCoverage] - public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, string absoluteFilePath) + public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, string absoluteFilePath, IFileSystem fileSystem) { if (!(dteProjectItem.ContainingProject.Object is VCProject vcProject) || !(dteProjectItem.Object is VCFile vcFile)) @@ -49,19 +50,11 @@ public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, stri bool isHeaderFile = vcFile.ItemType == "ClInclude"; CmdBuilder cmdBuilder = new CmdBuilder(isHeaderFile); - var compilerPath = vcConfig.GetEvaluatedPropertyValue("ClCompilerPath"); - if (string.IsNullOrEmpty(compilerPath)) + if (!GetCompilerPath(logger, vcConfig, fileSystem, out var compilerPath)) { - // in case ClCompilerPath is not available on VS2017 toolchains - var platform = ((VCPlatform)vcConfig.Platform).Name.Contains("64") ? "x64" : "x86"; - var exeVar = "VC_ExecutablePath_" + platform; - compilerPath = Path.Combine(vcConfig.GetEvaluatedPropertyValue(exeVar), "cl.exe"); - if (string.IsNullOrEmpty(compilerPath)) - { - logger.WriteLine("Compiler is not supported. \"ClCompilerPath\" and \"VC_ExecutablePath\" were not found."); - return null; - } + return null; } + logger.WriteLine(compilerPath); // command: add compiler cmdBuilder.AddCompiler(compilerPath); @@ -82,6 +75,98 @@ public static FileConfig TryGet(ILogger logger, ProjectItem dteProjectItem, stri }; } + private static bool TryGetCompilerPathFromClCompilerPath(ILogger logger, VCConfiguration vcConfig, IFileSystem fileSystem, out string compilerPath) + { + compilerPath = vcConfig.GetEvaluatedPropertyValue("ClCompilerPath"); + if (string.IsNullOrEmpty(compilerPath)) + { + logger.WriteLine("\"ClCompilerPath\" was not found."); + return false; + } + + if (!fileSystem.File.Exists(compilerPath)) + { + logger.WriteLine($"Compiler path (based on \"ClCompilerPath\") \"{compilerPath}\" does not exist."); + return false; + } + + return true; + } + + private static bool TryGetCompilerPathFromExecutablePath(ILogger logger, VCConfiguration vcConfig, IFileSystem fileSystem, out string compilerPath) + { + compilerPath = default; + var executablePath = vcConfig.GetEvaluatedPropertyValue("ExecutablePath"); + if (string.IsNullOrEmpty(executablePath)) + { + logger.WriteLine("\"ExecutablePath\" was not found."); + return false; + } + + var toolExe = vcConfig.GetEvaluatedPropertyValue("CLToolExe"); + if (string.IsNullOrEmpty(toolExe)) + { + // VS2022 sets "CLToolExe" only when clang-cl is chosen as the toolset. + logger.WriteLine("\"CLToolExe\" was not found, falling back to cl.exe."); + toolExe = "cl.exe"; + } + + foreach (var path in executablePath.Split(';')) + { + compilerPath = Path.Combine(path, toolExe); + if (fileSystem.File.Exists(compilerPath)) + { + return true; + } + else + { + logger.WriteLine($"Compiler path (based on \"ExecutablePath\") \"{compilerPath}\" does not exist."); + } + } + + return false; + } + + private static bool TryGetCompilerPathFromVCExecutablePath(ILogger logger, VCConfiguration vcConfig, IFileSystem fileSystem, out string compilerPath) + { + var platform = ((VCPlatform)vcConfig.Platform).Name.Contains("64") ? "x64" : "x86"; + var exeVar = "VC_ExecutablePath_" + platform; + compilerPath = Path.Combine(vcConfig.GetEvaluatedPropertyValue(exeVar), "cl.exe"); + if (fileSystem.File.Exists(compilerPath)) + { + return true; + } + else + { + logger.WriteLine($"Compiler path (based on \"{exeVar}\") \"{compilerPath}\" does not exist."); + return false; + } + } + + private static bool GetCompilerPath(ILogger logger, VCConfiguration vcConfig, IFileSystem fileSystem, out string compilerPath) + { + if (TryGetCompilerPathFromClCompilerPath(logger, vcConfig, fileSystem, out compilerPath)) + { + return true; + } + + // Fallback to ExecutablePath and CLToolExe + if (TryGetCompilerPathFromExecutablePath(logger, vcConfig, fileSystem, out compilerPath)) + { + return true; + } + + // Fallback to VC_ExecutablePath, which is used to be used in VS2017 toolchains + // In case ClCompilerPath isn't available, and ExecutablePath matching fails + if (TryGetCompilerPathFromVCExecutablePath(logger, vcConfig, fileSystem, out compilerPath)) + { + return true; + } + + logger.WriteLine("Compiler is not supported."); + return false; + } + private static IVCRulePropertyStorage GetVcFileSettings(ILogger logger, string absoluteFilePath, VCConfiguration vcConfig, VCFile vcFile) { var projectKind = vcConfig.ConfigurationType; diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs index efdafb4959..cc62dca69f 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs @@ -27,6 +27,8 @@ using SonarLint.VisualStudio.CFamily.Analysis; using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.Integration.Helpers; +using System.IO.Abstractions; +using SonarLint.VisualStudio.Core.SystemAbstractions; namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject { @@ -41,6 +43,7 @@ internal interface IFileConfigProvider internal class FileConfigProvider( IVsUIServiceOperation uiServiceOperation, IFileInSolutionIndicator fileInSolutionIndicator, + IFileSystemService fileSystem, ILogger logger, IThreadHandling threadHandling) : IFileConfigProvider { @@ -75,7 +78,7 @@ private FileConfig GetInternal(string analyzedFilePath, DTE2 dte, ILogger analys // Note: if the C++ tools are not installed then it's likely an exception will be thrown when // the framework tries to JIT-compile the TryGet method (since it won't be able to find the MS.VS.VCProjectEngine // types). - return FileConfig.TryGet(analysisLogger, projectItem, analyzedFilePath); + return FileConfig.TryGet(analysisLogger, projectItem, analyzedFilePath, fileSystem); } catch (Exception ex) when (!Microsoft.VisualStudio.ErrorHandler.IsCriticalException(ex)) { From 7255987c66df2dd8cdc785e2016d5b3a00fa45fc Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:02:19 +0100 Subject: [PATCH 18/21] SLVS-1709 Cleanup CFamily build (#5908) [SLVS-1709](https://sonarsource.atlassian.net/browse/SLVS-1709) [SLVS-1709]: https://sonarsource.atlassian.net/browse/SLVS-1709?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- SonarLint.VisualStudio.Integration.sln | 15 -- .../CFamilyJarPreProcessor.csproj | 19 -- .../CFamilyJarPreProcessor.sln | 25 -- build/tools/CFamilyJarPreProcessor/Common.cs | 251 ------------------ build/tools/CFamilyJarPreProcessor/ILogger.cs | 36 --- .../CFamilyJarPreProcessor/Preprocessor.cs | 126 --------- build/tools/CFamilyJarPreProcessor/Program.cs | 45 ---- build/tools/CFamilyJarPreProcessor/ReadMe.txt | 21 -- .../CFamilyJarPreProcessor/packages.lock.json | 68 ----- src/Integration.Vsix/Integration.Vsix.csproj | 48 +--- tools/CleanRebuild.cmd | 2 +- 11 files changed, 2 insertions(+), 654 deletions(-) delete mode 100644 build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.csproj delete mode 100644 build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.sln delete mode 100644 build/tools/CFamilyJarPreProcessor/Common.cs delete mode 100644 build/tools/CFamilyJarPreProcessor/ILogger.cs delete mode 100644 build/tools/CFamilyJarPreProcessor/Preprocessor.cs delete mode 100644 build/tools/CFamilyJarPreProcessor/Program.cs delete mode 100644 build/tools/CFamilyJarPreProcessor/ReadMe.txt delete mode 100644 build/tools/CFamilyJarPreProcessor/packages.lock.json diff --git a/SonarLint.VisualStudio.Integration.sln b/SonarLint.VisualStudio.Integration.sln index 1957a5be32..23277701b1 100644 --- a/SonarLint.VisualStudio.Integration.sln +++ b/SonarLint.VisualStudio.Integration.sln @@ -5,7 +5,6 @@ VisualStudioVersion = 17.0.31815.197 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Integration.Vsix", "src\Integration.Vsix\Integration.Vsix.csproj", "{FF2AD819-28F4-493A-8E9B-1D3F16BD4689}" ProjectSection(ProjectDependencies) = postProject - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA} = {0E50D9A0-693D-4B4E-81E2-8B8028231CFA} {2BB16C6F-BF06-4225-99A0-F1CFE70CBE44} = {2BB16C6F-BF06-4225-99A0-F1CFE70CBE44} EndProjectSection EndProject @@ -104,10 +103,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.Suppressions", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.Suppressions.UnitTests", "src\Roslyn.Suppressions\Roslyn.Suppressions.UnitTests\Roslyn.Suppressions.UnitTests.csproj", "{C478DAE7-58BC-4D02-929E-E413B40F2517}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{628A579F-0D27-4F9F-A756-8CC0A67A7B3D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CFamilyJarPreProcessor", "build\tools\CFamilyJarPreProcessor\CFamilyJarPreProcessor.csproj", "{0E50D9A0-693D-4B4E-81E2-8B8028231CFA}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConnectedMode", "ConnectedMode", "{3B4A8B40-9821-4964-8EAB-1D8A0B078292}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectedMode", "src\ConnectedMode\ConnectedMode.csproj", "{0BE551DB-3C46-42A5-BB38-DA80E83F8ABD}" @@ -347,14 +342,6 @@ Global {C478DAE7-58BC-4D02-929E-E413B40F2517}.Release|Any CPU.Build.0 = Release|Any CPU {C478DAE7-58BC-4D02-929E-E413B40F2517}.Release|x86.ActiveCfg = Release|Any CPU {C478DAE7-58BC-4D02-929E-E413B40F2517}.Release|x86.Build.0 = Release|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Debug|x86.ActiveCfg = Debug|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Debug|x86.Build.0 = Debug|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Release|Any CPU.Build.0 = Release|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Release|x86.ActiveCfg = Release|Any CPU - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA}.Release|x86.Build.0 = Release|Any CPU {0BE551DB-3C46-42A5-BB38-DA80E83F8ABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0BE551DB-3C46-42A5-BB38-DA80E83F8ABD}.Debug|Any CPU.Build.0 = Debug|Any CPU {0BE551DB-3C46-42A5-BB38-DA80E83F8ABD}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -489,8 +476,6 @@ Global {30E1FF8F-94BA-4A39-A737-8FFD7B4A0CD3} = {11D4BFC7-C1F2-45AC-888E-25A6A216AD1D} {082D5D8E-F914-4139-9AE3-3F48B679E3DA} = {16BF2D77-AE3B-4218-A3E8-875829D73B00} {C478DAE7-58BC-4D02-929E-E413B40F2517} = {16BF2D77-AE3B-4218-A3E8-875829D73B00} - {628A579F-0D27-4F9F-A756-8CC0A67A7B3D} = {E93C2CF9-69A6-4669-BE8A-6060B18FEDCA} - {0E50D9A0-693D-4B4E-81E2-8B8028231CFA} = {628A579F-0D27-4F9F-A756-8CC0A67A7B3D} {0BE551DB-3C46-42A5-BB38-DA80E83F8ABD} = {3B4A8B40-9821-4964-8EAB-1D8A0B078292} {2BD38A3A-6F0E-452B-A5B2-200113A32184} = {3B4A8B40-9821-4964-8EAB-1D8A0B078292} {67BEB251-4EA5-44EE-92A7-B4F57D9A6867} = {25DE7210-DFC0-448B-894E-84C1C9CA223E} diff --git a/build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.csproj b/build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.csproj deleted file mode 100644 index 7ad4097cb1..0000000000 --- a/build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net472 - - - true - - - - - - - - - - - diff --git a/build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.sln b/build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.sln deleted file mode 100644 index 56920f123c..0000000000 --- a/build/tools/CFamilyJarPreProcessor/CFamilyJarPreProcessor.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32516.85 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CFamilyJarPreProcessor", "CFamilyJarPreProcessor.csproj", "{CE1DC67B-EEBE-4EAF-B91C-BC622DCBBA39}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CE1DC67B-EEBE-4EAF-B91C-BC622DCBBA39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE1DC67B-EEBE-4EAF-B91C-BC622DCBBA39}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE1DC67B-EEBE-4EAF-B91C-BC622DCBBA39}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE1DC67B-EEBE-4EAF-B91C-BC622DCBBA39}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {EA20A11F-9428-41DA-82A9-3403C09EB81F} - EndGlobalSection -EndGlobal diff --git a/build/tools/CFamilyJarPreProcessor/Common.cs b/build/tools/CFamilyJarPreProcessor/Common.cs deleted file mode 100644 index 4ade790c69..0000000000 --- a/build/tools/CFamilyJarPreProcessor/Common.cs +++ /dev/null @@ -1,251 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net.Http; -using SharpCompress.Compressors.Xz; - -namespace CFamilyJarPreProcessor -{ - internal static class Common - { - /// - /// Returns the full path to the local directory in which the plugin will be cached at build time - /// - /// Per-plugin unique folder name - /// By default the plugins will be stored under the user's %LocalAppData% folder e.g. C:\Users\JoeBloggs\AppData\Local. - /// An alternative root directory can be specified by setting the environment variable SONARLINT_INTERNAL_PLUGIN_CACHE_DIR. - /// This might be necessary if the user name is long so the full paths of the files being unpacked under the root folder - /// exceed the maximum path length. - public static string GetLocalBuildTimePluginCacheDir(string pluginFolderName) - { - var baseFolder = Environment.GetEnvironmentVariable("SONARLINT_INTERNAL_PLUGIN_CACHE_DIR"); - if (string.IsNullOrEmpty(baseFolder)) - { - baseFolder = Environment.GetEnvironmentVariable("LocalAppData"); - } - - var fullPath = Path.Combine(baseFolder, pluginFolderName); - return fullPath; - } - - public static void EnsureWorkingDirectoryExist(string localWorkingFolder, ILogger logger) - { - if (!Directory.Exists(localWorkingFolder)) - { - LogMessage($"Creating working folder: {localWorkingFolder}", logger); - Directory.CreateDirectory(localWorkingFolder); - } - } - - public static void DownloadJarFile(string url, string targetFilePath, ILogger logger) - { - LogMessage($"Download url: {url}", logger); - - if (File.Exists(targetFilePath)) - { - // Downloading the file is slow so skip if possible - LogMessage($"Jar file already exists at {targetFilePath}", logger); - return; - } - - LogMessage($"Downloading file from {url} to {targetFilePath}...", logger); - - var timer = Stopwatch.StartNew(); - - using (var httpClient = new HttpClient()) - using (var response = httpClient.GetAsync(url).Result) - { - if (response.IsSuccessStatusCode) - { - using (var fileStream = new FileStream(targetFilePath, FileMode.Create, FileAccess.Write)) - { - response.Content.CopyToAsync(fileStream).Wait(); - } - } - else - { - logger.LogError($"Failed to download the CFamily plugin: {response.Content}"); - } - } - timer.Stop(); - LogElapsedTime("Download completed. ", timer, logger); - } - - public static string ExtractPluginFileNameFromUrl(string url, ILogger logger) - { - if (!url.EndsWith(".jar", StringComparison.InvariantCultureIgnoreCase)) - { - throw new ArgumentException("Expecting the url to end with '.jar'"); - } - - var fileName = url.Split('/').Last(); - - LogMessage($"Plugin file name: {fileName}", logger); - return fileName; - } - - public static void UnzipJar(string jarFilePath, string destinationFolder, ILogger logger) - { - if (Directory.GetFiles(destinationFolder).Length > 1) - { - // Unzipping the jar is slow so skip if possible - LogMessage($"Skipping unzipping the jar because the destination folder already contains multiple files.", logger); - return; - } - - LogMessage($"Unzipping jar file...", logger); - var timer = Stopwatch.StartNew(); - - ZipFile.ExtractToDirectory(jarFilePath, destinationFolder); - - timer.Stop(); - LogElapsedTime("Unzipped jar file", timer, logger); - } - - public static void UncompressAndUnzipTgx(string tarFilePath, string destinationFolder, ILogger logger) - { - // The txz file is compressed using XZ compression and zipped using the tar format. - // There is no built-in framework support for these format so we are using two - // open source libraries, both licensed under the MIT license. - Common.EnsureWorkingDirectoryExist(destinationFolder, logger); - - var uncompresssedFile = Common.DecompressXZFile(tarFilePath, destinationFolder, logger); - Common.ExtractTarToDirectory(uncompresssedFile, destinationFolder, logger); - } - - public static string DecompressXZFile(string sourceFilePath, string destinationDirectory, ILogger logger) - { - var destFile = Path.Combine(destinationDirectory, Path.GetFileName(sourceFilePath)) + ".uncompressed"; - if (File.Exists(destFile)) - { - LogMessage($"Uncompressed tar file already exists: {destFile}", logger); - return destFile; - } - - using (Stream xz = new XZStream(File.OpenRead(sourceFilePath))) - using (Stream outputStream = new FileStream(destFile, FileMode.CreateNew)) - { - xz.CopyTo(outputStream); - } - return destFile; - } - - public static void UncompressAndUnzipTgz(string tarFilePath, string destinationFolder, ILogger logger) - { - // The txz file is compressed using XZ compression and zipped using the tar format. - // There is no built-in framework support for these format so we are using two - // open source libraries, both licensed under the MIT license. - EnsureWorkingDirectoryExist(destinationFolder, logger); - - var uncompresssedFile = DecompressGZipFile(tarFilePath, destinationFolder, logger); - ExtractTarToDirectory(uncompresssedFile, destinationFolder, logger); - } - - public static string DecompressGZipFile(string sourceFilePath, string destinationDirectory, ILogger logger) - { - var destFile = Path.Combine(destinationDirectory, Path.GetFileName(sourceFilePath)) + ".uncompressed"; - if (File.Exists(destFile)) - { - LogMessage($"Uncompressed tar file already exists: {destFile}", logger); - return destFile; - } - - using (Stream xz = new GZipStream(File.OpenRead(sourceFilePath), CompressionMode.Decompress)) - using (Stream outputStream = new FileStream(destFile, FileMode.CreateNew)) - { - xz.CopyTo(outputStream); - } - return destFile; - } - - public static void ExtractTarToDirectory(string sourceFilePath, string destinationDirectory, ILogger logger) - { - using (var outputStream = new FileStream(sourceFilePath, FileMode.Open)) - { - ICSharpCode.SharpZipLib.Tar.TarArchive tarArchive = ICSharpCode.SharpZipLib.Tar.TarArchive.CreateInputTarArchive(outputStream); - tarArchive.ExtractContents(destinationDirectory); - } - } - - public static List FindSingleFiles(string searchRoot, IEnumerable patterns, ILogger logger) - { - var files = new List(); - - foreach (var file in patterns) - { - files.Add(FindSingleFile(searchRoot, file, logger)); - } - - return files; - } - - public static List FindMultipleFiles(string searchRoot, IEnumerable patterns, ILogger logger) - { - var files = new List(); - - foreach (var pattern in patterns) - { - var matches = Directory.GetFiles(searchRoot, pattern, SearchOption.AllDirectories); - if (matches.Any()) - { - LogMessage($"Found {matches.Count()} files matching for '{pattern}'", logger); - files.AddRange(matches); - } - else - { - throw new InvalidOperationException($"Failed to find any files matching the pattern '{pattern}'"); - } - } - - return files; - } - - public static string FindSingleFile(string searchFolder, string pattern, ILogger logger) - { - var files = Directory.GetFiles(searchFolder, pattern, SearchOption.AllDirectories); - - if (files.Length != 1) - { - throw new InvalidOperationException($"Failed to locate one and only one file matching pattern {pattern} in {searchFolder}. Matching files: {files.Length}"); - } - - LogMessage($"Located file: {files[0]}", logger); - - return files[0]; - } - - private static void LogElapsedTime(string message, Stopwatch timer, ILogger logger) - { - LogMessage($"{message} {timer.Elapsed.ToString("g")}", logger); - } - - private static void LogMessage(string message, ILogger logger) - { - logger.LogMessage(message); - } - } -} diff --git a/build/tools/CFamilyJarPreProcessor/ILogger.cs b/build/tools/CFamilyJarPreProcessor/ILogger.cs deleted file mode 100644 index 602e4639ed..0000000000 --- a/build/tools/CFamilyJarPreProcessor/ILogger.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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 CFamilyJarPreProcessor -{ - internal interface ILogger - { - void LogMessage(string message); - void LogError(string message); - } - - internal class ConsoleLogger : ILogger - { - void ILogger.LogError(string message) => Console.Error.WriteLine(message); - - void ILogger.LogMessage(string message) => Console.WriteLine(message); - } -} diff --git a/build/tools/CFamilyJarPreProcessor/Preprocessor.cs b/build/tools/CFamilyJarPreProcessor/Preprocessor.cs deleted file mode 100644 index 80dbe4b23f..0000000000 --- a/build/tools/CFamilyJarPreProcessor/Preprocessor.cs +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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.Collections.Generic; -using System.IO; - -namespace CFamilyJarPreProcessor -{ - internal class Preprocessor - { - // The txz archive containing the subprocess.exe - private const string WindowsTxzFilePattern = "clang*-win.txz"; - - // Sub-folder into which the tar file should be unzipped - private const string TarUnzipSubFolder = "tar_xz"; - - // List of patterns to match single files in the uncompressed output - private readonly string[] SingleFilePatterns = new string[] - { - "Sonar_way_profile.json", - "RulesList.json", - "MisraRulesList.json", - TarUnzipSubFolder + @"\subprocess.exe", - TarUnzipSubFolder + @"\LICENSE_THIRD_PARTY.txt" - }; - - // List of patterns to match multiple files in the uncompressed output - private readonly string[] MultipleFilesPatterns = new string[] - { - @"org\sonar\l10n\cpp\rules\params\*_params.json", - @"org\sonar\l10n\cpp\rules\cpp\*.json", - @"org\sonar\l10n\cpp\rules\misra23\*.json", - }; - - private readonly ILogger logger; - public Preprocessor(ILogger logger) - { - this.logger = logger; - } - - /// - /// Downloads plugin, extracts files, and copies the requires file to the output folder - /// - /// The method is lazy i.e. won't download the plugin if it exists, won't copy a file - /// unless it is newer than the target file. - public void Execute(string downloadUrl, string destinationDir) - { - var pluginFileName = Common.ExtractPluginFileNameFromUrl(downloadUrl, logger); - - // Ensure working directories exists - var localWorkingFolder = Common.GetLocalBuildTimePluginCacheDir("SLVS_CFamily_Build"); - var perVersionPluginFolder = Path.Combine(localWorkingFolder, Path.GetFileNameWithoutExtension(pluginFileName)); - Common.EnsureWorkingDirectoryExist(perVersionPluginFolder, logger); - - // Download and unzip the jar - var jarFilePath = Path.Combine(perVersionPluginFolder, pluginFileName); - Common.DownloadJarFile(downloadUrl, jarFilePath, logger); - Common.UnzipJar(jarFilePath, perVersionPluginFolder, logger); - - // Uncompress and extract the windows tar archive to get the subprocess exe - var tarFilePath = Common.FindSingleFile(perVersionPluginFolder, WindowsTxzFilePattern, logger); - var tarSubFolder = Path.Combine(perVersionPluginFolder, TarUnzipSubFolder); - Common.UncompressAndUnzipTgx(tarFilePath, tarSubFolder, logger); - - // Locate the required files from the uncompressed jar and tar - var fileList = FindFiles(perVersionPluginFolder); - - // Copy the files to the output directory - CopyFilesToOutputDirectory(fileList, destinationDir); - } - - private List FindFiles(string searchRoot) - { - var files = new List(); - - files.AddRange(Common.FindSingleFiles(searchRoot, SingleFilePatterns, logger)); - files.AddRange(Common.FindMultipleFiles(searchRoot, MultipleFilesPatterns, logger)); - - return files; - } - - private void CopyFilesToOutputDirectory(IList files, string destinationDir) - { - Common.EnsureWorkingDirectoryExist(destinationDir, logger); - - foreach(var file in files) - { - CopyIfNewer(file, destinationDir); - } - } - - private void CopyIfNewer(string file, string destinationDir) - { - // Overwrite if newer - var sourceFileInfo = new FileInfo(file); - var destinationFileInfo = new FileInfo(Path.Combine(destinationDir, sourceFileInfo.Name)); - - if (!destinationFileInfo.Exists || sourceFileInfo.LastWriteTimeUtc > destinationFileInfo.LastWriteTimeUtc) - { - logger.LogMessage($" Copying file: {file}"); - File.Copy(sourceFileInfo.FullName, destinationFileInfo.FullName, true); - } - else - { - logger.LogMessage($" Skipping copying file - not newer: {file}"); - } - } - } -} diff --git a/build/tools/CFamilyJarPreProcessor/Program.cs b/build/tools/CFamilyJarPreProcessor/Program.cs deleted file mode 100644 index 3752b9e84e..0000000000 --- a/build/tools/CFamilyJarPreProcessor/Program.cs +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 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. - */ - -namespace CFamilyJarPreProcessor -{ - internal static class Program - { - static int Main(string[] args) - { - ILogger logger = new ConsoleLogger(); - - if (args.Length != 2) - { - logger.LogError("Expected parameters: [plugin download url] [full path to destination directory]"); - return -1; - } - - var downloadUrl = args[0]; - var destinationDir = args[1]; - - var preprocessor = new Preprocessor(logger); - - preprocessor.Execute(downloadUrl, destinationDir); - - return 0; - } - } -} diff --git a/build/tools/CFamilyJarPreProcessor/ReadMe.txt b/build/tools/CFamilyJarPreProcessor/ReadMe.txt deleted file mode 100644 index 1dcc8d9d16..0000000000 --- a/build/tools/CFamilyJarPreProcessor/ReadMe.txt +++ /dev/null @@ -1,21 +0,0 @@ -CFamily plugin pre-processor ----------------------------- - -Ultimately, we want to embed multiple artefacts in the SLVS VSIX: -1) the subprocess.exe, -2) the "LICENSE_THIRD_PARTY.txt" file, and -3) a file that contains all of rules metadata. - -Artefacts (1) and (2) exist somewhere in the jar. -However, artefact (3) does not. Instead, there are some well-known json files and hundreds of per-rule files. We need to find and load all of these files and generate artefact (3). - -Format of artefact (3) ----------------------- -TODO - - -Integration with the rest of the build --------------------------------------- -The application is a standalone exe that is called as part of the main solution build. - --> the solution needs to reference the build tools. diff --git a/build/tools/CFamilyJarPreProcessor/packages.lock.json b/build/tools/CFamilyJarPreProcessor/packages.lock.json deleted file mode 100644 index f0d7e6a2fb..0000000000 --- a/build/tools/CFamilyJarPreProcessor/packages.lock.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "version": 1, - "dependencies": { - ".NETFramework,Version=v4.7.2": { - "SharpCompress": { - "type": "Direct", - "requested": "[0.32.1, )", - "resolved": "0.32.1", - "contentHash": "9Cwj3lK/p7wkiBaQPCvaKINuHYuZ0ACDldA4M3o5ISSq7cjbbq3yqigTDBUoKjtyyXpqmQHUkw6fhLnjNF30ow==", - "dependencies": { - "System.Memory": "4.5.4", - "System.Text.Encoding.CodePages": "6.0.0" - } - }, - "SharpZipLib": { - "type": "Direct", - "requested": "[1.3.3, )", - "resolved": "1.3.3", - "contentHash": "N8+hwhsKZm25tDJfWpBSW7EGhH/R7EMuiX+KJ4C4u+fCWVc1lJ5zg1u3S1RPPVYgTqhx/C3hxrqUpi6RwK5+Tg==" - }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" - }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Numerics.Vectors": "4.5.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, - "System.Numerics.Vectors": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" - }, - "System.Text.Encoding.CodePages": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==", - "dependencies": { - "System.Memory": "4.5.4", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - } - }, - ".NETFramework,Version=v4.7.2/win7-x86": { - "System.Text.Encoding.CodePages": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==", - "dependencies": { - "System.Memory": "4.5.4", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - } - } - } -} \ No newline at end of file diff --git a/src/Integration.Vsix/Integration.Vsix.csproj b/src/Integration.Vsix/Integration.Vsix.csproj index 09a6fbb6da..87b3add947 100644 --- a/src/Integration.Vsix/Integration.Vsix.csproj +++ b/src/Integration.Vsix/Integration.Vsix.csproj @@ -326,60 +326,14 @@ - - - + - - https://binaries.sonarsource.com/CommercialDistribution/sonar-cfamily-plugin/sonar-cfamily-plugin-$(EmbeddedSonarCFamilyAnalyzerVersion).jar - - lib DownloadedJars EmbeddedDotnetAnalyzerDLLs Sloop - - - $(MSBuildThisFileDirectory)$(CFamilyFolderName) - - - $(PrepareForBuildDependsOn);GetCFamilyFiles - - - - - - - - - - - - - - - PreserveNewest - True - - - $(CFamilyFolderName) - false - - - - diff --git a/tools/CleanRebuild.cmd b/tools/CleanRebuild.cmd index 37fde4f9de..87b1755252 100644 --- a/tools/CleanRebuild.cmd +++ b/tools/CleanRebuild.cmd @@ -2,7 +2,7 @@ @REM %~dp0 gives the directory containing this batch file -(for %%a in ("%~dp0..\src\Integration.Vsix\lib" "%LOCALAPPDATA%\SLVS_Build_DownloadedJars" "%LOCALAPPDATA%\SLVS_CFamily_Build" "%LOCALAPPDATA%\SLVS_TypeScript_Build" "%LOCALAPPDATA%\SLVS_Build_SLOOP" "%LOCALAPPDATA%\SLVS_Build_Dotnet") do rd /s /q "%%~a") +(for %%a in ("%LOCALAPPDATA%\SLVS_Build_DownloadedJars" "%LOCALAPPDATA%\SLVS_Build_SLOOP" "%LOCALAPPDATA%\SLVS_Build_Dotnet") do rd /s /q "%%~a") call msbuild.exe %~dp0..\build\DownloadDependencies -t:Rebuild call msbuild.exe "%~dp0..\SonarLint.VisualStudio.Integration.sln" -t:Rebuild From 8760b85d5925b8ef8119ae2a13b7294510cc09b1 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:38:49 +0100 Subject: [PATCH 19/21] SLVS-1697 Integrate Reproducer command with SLCore analysis (#5905) [SLVS-1697](https://sonarsource.atlassian.net/browse/SLVS-1697) [SLVS-1697]: https://sonarsource.atlassian.net/browse/SLVS-1697?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../Analysis/CFamilyAnalyzerOptions.cs | 8 +- .../CFamily/CFamilyReproducerCommandTests.cs | 9 +- .../VcxProject/FileConfigProviderTests.cs | 23 +--- .../VCXCompilationDatabaseProviderTests.cs | 16 +-- .../CFamily/CFamilyReproducerCommand.cs | 1 + .../CFamily/VcxProject/FileConfigProvider.cs | 111 ++++++------------ .../VCXCompilationDatabaseProvider.cs | 2 +- .../Analysis/SLCoreAnalyzerTests.cs | 41 +++++++ src/SLCore/Analysis/SLCoreAnalyzer.cs | 15 ++- 9 files changed, 115 insertions(+), 111 deletions(-) rename src/{CFamily => Core}/Analysis/CFamilyAnalyzerOptions.cs (85%) diff --git a/src/CFamily/Analysis/CFamilyAnalyzerOptions.cs b/src/Core/Analysis/CFamilyAnalyzerOptions.cs similarity index 85% rename from src/CFamily/Analysis/CFamilyAnalyzerOptions.cs rename to src/Core/Analysis/CFamilyAnalyzerOptions.cs index c43377b26a..322c4ccc3e 100644 --- a/src/CFamily/Analysis/CFamilyAnalyzerOptions.cs +++ b/src/Core/Analysis/CFamilyAnalyzerOptions.cs @@ -22,10 +22,14 @@ namespace SonarLint.VisualStudio.CFamily.Analysis { - public class CFamilyAnalyzerOptions : IAnalyzerOptions + public interface ICFamilyAnalyzerOptions : IAnalyzerOptions + { + bool CreateReproducer { get; set; } + } + + public class CFamilyAnalyzerOptions : ICFamilyAnalyzerOptions { public bool CreateReproducer { get; set; } - public bool CreatePreCompiledHeaders { get; set; } public bool IsOnOpen { get; set; } } } diff --git a/src/Integration.Vsix.UnitTests/CFamily/CFamilyReproducerCommandTests.cs b/src/Integration.Vsix.UnitTests/CFamily/CFamilyReproducerCommandTests.cs index fe3665510f..b15abb5f0e 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/CFamilyReproducerCommandTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/CFamilyReproducerCommandTests.cs @@ -167,8 +167,9 @@ public void Execute_ReanalysisTriggered() // Assert logger.AssertOutputStringExists(CFamilyStrings.ReproCmd_ExecutingReproducer); - actualOptions.Should().BeOfType(); - ((CFamilyAnalyzerOptions)actualOptions).CreateReproducer.Should().BeTrue(); + var cFamilyAnalyzerOptions = actualOptions.Should().BeAssignableTo().Subject; + cFamilyAnalyzerOptions.CreateReproducer.Should().BeTrue(); + cFamilyAnalyzerOptions.IsOnOpen.Should().BeFalse(); actualFilePaths.Should().BeEquivalentTo(ValidTextDocument.FilePath); } @@ -213,12 +214,12 @@ public void Execute_CriticalErrorNotSuppressed() docLocatorMock.Setup(x => x.FindActiveDocument()).Throws(new StackOverflowException("exception xxx")); Action act = () => testSubject.Invoke(); - // Act + // Act act.Should().ThrowExactly().And.Message.Should().Be("exception xxx"); logger.AssertPartialOutputStringDoesNotExist("exception xxx"); } - private static MenuCommand CreateCFamilyReproducerCommand(IActiveDocumentLocator documentLocator, + private static MenuCommand CreateCFamilyReproducerCommand(IActiveDocumentLocator documentLocator, ISonarLanguageRecognizer languageRecognizer, IAnalysisRequester analysisRequester, ILogger logger) { var dummyMenuService = new DummyMenuCommandService(); diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs index 38d25e8b8c..31cce75a77 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/FileConfigProviderTests.cs @@ -24,11 +24,9 @@ using NSubstitute.ExceptionExtensions; using NSubstitute.ReturnsExtensions; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.CFamily.Analysis; using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; using static SonarLint.VisualStudio.Integration.Vsix.CFamily.UnitTests.CFamilyTestUtility; -using System.IO.Abstractions; using SonarLint.VisualStudio.Core.SystemAbstractions; namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject @@ -94,29 +92,18 @@ public void Get_FailsToRetrieveProjectItem_NonCriticalException_ExceptionCaughtA { dte.Solution.ThrowsForAnyArgs(); - var result = testSubject.Get(SourceFilePath, new CFamilyAnalyzerOptions()); + var result = testSubject.Get(SourceFilePath); result.Should().BeNull(); logger.AssertPartialOutputStringExists(nameof(NotImplementedException), SourceFilePath); } - [TestMethod] - public void Get_FailsToRetrieveProjectItem_NonCriticalException_Pch_ExceptionCaughtNotLoggedAndNullReturned() - { - dte.Solution.ThrowsForAnyArgs(); - - var result = testSubject.Get(SourceFilePath, new CFamilyAnalyzerOptions{CreatePreCompiledHeaders = true}); - - result.Should().BeNull(); - logger.AssertNoOutputMessages(); - } - [TestMethod] public void Get_FailsToRetrieveProjectItem_CriticalException_ExceptionThrown() { dte.Solution.ThrowsForAnyArgs(); - Action act = () => testSubject.Get(SourceFilePath, new CFamilyAnalyzerOptions()); + Action act = () => testSubject.Get(SourceFilePath); act.Should().Throw(); } @@ -126,7 +113,7 @@ public void Get_NoProjectItem_ReturnsNull() { dte.Solution.FindProjectItem(SourceFilePath).ReturnsNull(); - testSubject.Get(SourceFilePath, null) + testSubject.Get(SourceFilePath) .Should().BeNull(); Received.InOrder(() => @@ -143,7 +130,7 @@ public void Get_ProjectItemNotInSolution_ReturnsNull() dte.Solution.FindProjectItem(SourceFilePath).Returns(mockProjectItem.Object); fileInSolutionIndicator.IsFileInSolution(mockProjectItem.Object).Returns(false); - testSubject.Get(SourceFilePath, null) + testSubject.Get(SourceFilePath) .Should().BeNull(); Received.InOrder(() => @@ -160,7 +147,7 @@ public void Get_SuccessfulConfig_ConfigReturned() var mockProjectItem = CreateMockProjectItem(SourceFilePath); dte.Solution.FindProjectItem(SourceFilePath).Returns(mockProjectItem.Object); - testSubject.Get(SourceFilePath, null) + testSubject.Get(SourceFilePath) .Should().NotBeNull(); } } diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs index 025ddb6d11..041d27dcaf 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs @@ -66,7 +66,7 @@ public void MefCtor_CheckIsExported() [TestMethod] public void CreateOrNull_NoFileConfig_ReturnsNull() { - fileConfigProvider.Get(SourceFilePath, default).ReturnsNull(); + fileConfigProvider.Get(SourceFilePath).ReturnsNull(); var testSubject = new VCXCompilationDatabaseProvider( storage, envVarProvider, @@ -82,7 +82,7 @@ public void CreateOrNull_NoFileConfig_ReturnsNull() public void CreateOrNull_FileConfig_CantStore_ReturnsNull() { var fileConfig = GetFileConfig(); - fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + fileConfigProvider.Get(SourceFilePath).Returns(fileConfig); storage.CreateDatabase(default, default, default, default).ReturnsNullForAnyArgs(); var testSubject = new VCXCompilationDatabaseProvider( storage, @@ -99,7 +99,7 @@ public void CreateOrNull_FileConfig_CantStore_ReturnsNull() public void CreateOrNull_FileConfig_StoresAndReturnsHandle() { var fileConfig = GetFileConfig(); - fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + fileConfigProvider.Get(SourceFilePath).Returns(fileConfig); var compilationDatabaseHandle = Substitute.For(); storage.CreateDatabase(CDFile, CDDirectory, CDCommand, Arg.Any>()).Returns(compilationDatabaseHandle); var testSubject = new VCXCompilationDatabaseProvider( @@ -115,7 +115,7 @@ public void CreateOrNull_FileConfig_StoresAndReturnsHandle() public void CreateOrNull_NoEnvIncludeInFileConfig_UsesStatic() { var fileConfig = GetFileConfig(null); - fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + fileConfigProvider.Get(SourceFilePath).Returns(fileConfig); envVarProvider.GetAll().Returns([("Var1", "Value1"), ("INCLUDE", "static"), ("Var2", "Value2")]); var testSubject = new VCXCompilationDatabaseProvider( storage, @@ -132,7 +132,7 @@ public void CreateOrNull_NoEnvIncludeInFileConfig_UsesStatic() public void CreateOrNull_FileConfigHasEnvInclude_UsesDynamic() { var fileConfig = GetFileConfig(EnvInclude); - fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + fileConfigProvider.Get(SourceFilePath).Returns(fileConfig); envVarProvider.GetAll().Returns([("Var1", "Value1"), ("INCLUDE", "static"), ("Var2", "Value2")]); var testSubject = new VCXCompilationDatabaseProvider( storage, @@ -150,7 +150,7 @@ public void CreateOrNull_FileConfigHasEnvInclude_UsesDynamic() public void CreateOrNull_NoStaticInclude_UsesDynamic() { var fileConfig = GetFileConfig(EnvInclude); - fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + fileConfigProvider.Get(SourceFilePath).Returns(fileConfig); envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]); var testSubject = new VCXCompilationDatabaseProvider( storage, @@ -168,7 +168,7 @@ public void CreateOrNull_NoStaticInclude_UsesDynamic() public void CreateOrNull_StaticEnvVarsAreCached() { var fileConfig = GetFileConfig(); - fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + fileConfigProvider.Get(SourceFilePath).Returns(fileConfig); envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]); var testSubject = new VCXCompilationDatabaseProvider( storage, @@ -187,7 +187,7 @@ public void CreateOrNull_StaticEnvVarsAreCached() public void CreateOrNull_EnvVarsContainHeaderPropertyForHeaderFiles() { var fileConfig = GetFileConfig(EnvInclude, true); - fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); + fileConfigProvider.Get(SourceFilePath).Returns(fileConfig); envVarProvider.GetAll().Returns([("Var1", "Value1"), ("Var2", "Value2")]); var testSubject = new VCXCompilationDatabaseProvider( storage, diff --git a/src/Integration.Vsix/CFamily/CFamilyReproducerCommand.cs b/src/Integration.Vsix/CFamily/CFamilyReproducerCommand.cs index dcd93b2dff..9fce12421c 100644 --- a/src/Integration.Vsix/CFamily/CFamilyReproducerCommand.cs +++ b/src/Integration.Vsix/CFamily/CFamilyReproducerCommand.cs @@ -164,6 +164,7 @@ private void TriggerReproducer() { var options = new CFamilyAnalyzerOptions { + IsOnOpen = false, CreateReproducer = true }; diff --git a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs index cc62dca69f..eeaafa3536 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/FileConfigProvider.cs @@ -18,105 +18,64 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; -using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio.Shell.Interop; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.CFamily.Analysis; using SonarLint.VisualStudio.Infrastructure.VS; -using SonarLint.VisualStudio.Integration.Helpers; -using System.IO.Abstractions; using SonarLint.VisualStudio.Core.SystemAbstractions; -namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject +namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +internal interface IFileConfigProvider +{ + IFileConfig Get(string analyzedFilePath); +} + +[Export(typeof(IFileConfigProvider))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method: ImportingConstructor] +internal class FileConfigProvider( + IVsUIServiceOperation uiServiceOperation, + IFileInSolutionIndicator fileInSolutionIndicator, + IFileSystemService fileSystem, + ILogger logger, + IThreadHandling threadHandling) : IFileConfigProvider { - internal interface IFileConfigProvider + + public IFileConfig Get(string analyzedFilePath) { - IFileConfig Get(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions); + return uiServiceOperation.Execute(dte => + GetInternal(analyzedFilePath, dte)); } - [Export(typeof(IFileConfigProvider))] - [PartCreationPolicy(CreationPolicy.Shared)] - [method: ImportingConstructor] - internal class FileConfigProvider( - IVsUIServiceOperation uiServiceOperation, - IFileInSolutionIndicator fileInSolutionIndicator, - IFileSystemService fileSystem, - ILogger logger, - IThreadHandling threadHandling) : IFileConfigProvider + private FileConfig GetInternal(string analyzedFilePath, DTE2 dte) { - private static readonly NoOpLogger noOpLogger = new NoOpLogger(); - - public IFileConfig Get(string analyzedFilePath, CFamilyAnalyzerOptions analyzerOptions) - { - var analysisLogger = GetAnalysisLogger(analyzerOptions); + threadHandling.ThrowIfNotOnUIThread(); - return uiServiceOperation.Execute(dte => - GetInternal(analyzedFilePath, dte, analysisLogger)); - } - - private FileConfig GetInternal(string analyzedFilePath, DTE2 dte, ILogger analysisLogger) + try { - threadHandling.ThrowIfNotOnUIThread(); + var projectItem = dte.Solution.FindProjectItem(analyzedFilePath); - try + if (projectItem == null) { - var projectItem = dte.Solution.FindProjectItem(analyzedFilePath); - - if (projectItem == null) - { - return null; - } - - if (!fileInSolutionIndicator.IsFileInSolution(projectItem)) - { - analysisLogger.LogVerbose($"[VCX:FileConfigProvider] The file is not part of a VCX project. File: {analyzedFilePath}"); - return null; - } - // Note: if the C++ tools are not installed then it's likely an exception will be thrown when - // the framework tries to JIT-compile the TryGet method (since it won't be able to find the MS.VS.VCProjectEngine - // types). - return FileConfig.TryGet(analysisLogger, projectItem, analyzedFilePath, fileSystem); - } - catch (Exception ex) when (!Microsoft.VisualStudio.ErrorHandler.IsCriticalException(ex)) - { - analysisLogger.WriteLine(CFamilyStrings.ERROR_CreatingConfig, analyzedFilePath, ex); return null; } - } - private ILogger GetAnalysisLogger(CFamilyAnalyzerOptions analyzerOptions) - { - if (analyzerOptions is CFamilyAnalyzerOptions cFamilyAnalyzerOptions && - cFamilyAnalyzerOptions.CreatePreCompiledHeaders) + if (!fileInSolutionIndicator.IsFileInSolution(projectItem)) { - // In case the requeset is coming from PCH generation, we don't log failures. - // This is to avoid redundant messages while navigating unsupported files. - return noOpLogger; + logger.LogVerbose($"[VCX:FileConfigProvider] The file is not part of a VCX project. File: {analyzedFilePath}"); + return null; } - - return logger; + // Note: if the C++ tools are not installed then it's likely an exception will be thrown when + // the framework tries to JIT-compile the TryGet method (since it won't be able to find the MS.VS.VCProjectEngine + // types). + return FileConfig.TryGet(logger, projectItem, analyzedFilePath, fileSystem); } - - - private class NoOpLogger : ILogger + catch (Exception ex) when (!Microsoft.VisualStudio.ErrorHandler.IsCriticalException(ex)) { - public void WriteLine(string message) - { - // no-op - } - - public void WriteLine(string messageFormat, params object[] args) - { - // no-op - } - - public void LogVerbose(string message, params object[] args) - { - // no-op - } + logger.WriteLine(CFamilyStrings.ERROR_CreatingConfig, analyzedFilePath, ex); + return null; } } } diff --git a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs index b4ff05f137..f0011aadee 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -51,7 +51,7 @@ public VCXCompilationDatabaseProvider( } public ICompilationDatabaseHandle CreateOrNull(string filePath) => - fileConfigProvider.Get(filePath, null) is { } fileConfig + fileConfigProvider.Get(filePath) is { } fileConfig ? storage.CreateDatabase(fileConfig.CDFile, fileConfig.CDDirectory, fileConfig.CDCommand, GetEnvironmentEntries(fileConfig).Select(x => x.FormattedEntry)) : null; diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index a5c2631859..8e393a916a 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -19,6 +19,7 @@ */ using NSubstitute.ExceptionExtensions; +using SonarLint.VisualStudio.CFamily.Analysis; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Core.CFamily; @@ -182,6 +183,37 @@ public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraPropertie compilationDatabaseHandle.Received().Dispose(); } + [TestMethod] + public void ExecuteAnalysis_CFamilyReproducerEnabled_SetsExtraProperty() + { + const string filePath = @"C:\file\path\myclass.cpp"; + SetUpCompilationDatabaseLocator(filePath, CreateCompilationDatabaseHandle("somepath")); + SetUpInitializedConfigScope(); + var cFamilyAnalyzerOptions = CreateCFamilyAnalyzerOptions(true); + + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], cFamilyAnalyzerOptions, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.extraProperties != null + && a.extraProperties["sonar.cfamily.reproducer"] == filePath), + Arg.Any()); + } + + [TestMethod] + public void ExecuteAnalysis_CFamilyReproducerDisabled_DoesNotSetExtraProperty() + { + const string filePath = @"C:\file\path\myclass.cpp"; + SetUpCompilationDatabaseLocator(filePath, CreateCompilationDatabaseHandle("somepath")); + SetUpInitializedConfigScope(); + var cFamilyAnalyzerOptions = CreateCFamilyAnalyzerOptions(false); + + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], cFamilyAnalyzerOptions, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.extraProperties == null || !a.extraProperties.ContainsKey("sonar.cfamily.reproducer")), + Arg.Any()); + } + [TestMethod] public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDisposed() { @@ -297,4 +329,13 @@ private static ICompilationDatabaseHandle CreateCompilationDatabaseHandle(string private void SetUpCompilationDatabaseLocator(string filePath, ICompilationDatabaseHandle handle) => compilationDatabaseLocator.GetOrNull(filePath).Returns(handle); + + + private static ICFamilyAnalyzerOptions CreateCFamilyAnalyzerOptions(bool createReproducer) + { + var cFamilyAnalyzerOptions = Substitute.For(); + cFamilyAnalyzerOptions.IsOnOpen.Returns(false); + cFamilyAnalyzerOptions.CreateReproducer.Returns(createReproducer); + return cFamilyAnalyzerOptions; + } } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index 7077ae6013..d60fbadf75 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -20,6 +20,7 @@ using System.ComponentModel.Composition; using Microsoft.VisualStudio.Threading; +using SonarLint.VisualStudio.CFamily.Analysis; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Core.CFamily; @@ -36,6 +37,7 @@ namespace SonarLint.VisualStudio.SLCore.Analysis; public class SLCoreAnalyzer : IAnalyzer { private const string CFamilyCompileCommandsProperty = "sonar.cfamily.compile-commands"; + private const string CFamilyReproducerProperty = "sonar.cfamily.reproducer"; private readonly ISLCoreServiceProvider serviceProvider; private readonly IActiveConfigScopeTracker activeConfigScopeTracker; @@ -100,7 +102,7 @@ private async Task ExecuteAnalysisInternalAsync( try { Dictionary properties = []; - using var temporaryResourcesHandle = EnrichPropertiesForCFamily(properties, path, detectedLanguages); + using var temporaryResourcesHandle = EnrichPropertiesForCFamily(properties, path, detectedLanguages, analyzerOptions); Stopwatch stopwatch = Stopwatch.StartNew(); @@ -133,13 +135,22 @@ [new FileUri(path)], } } - private IDisposable EnrichPropertiesForCFamily(Dictionary properties, string path, IEnumerable detectedLanguages) + private IDisposable EnrichPropertiesForCFamily( + Dictionary properties, + string path, + IEnumerable detectedLanguages, + IAnalyzerOptions analyzerOptions) { if (!IsCFamily(detectedLanguages)) { return null; } + if (analyzerOptions is ICFamilyAnalyzerOptions {CreateReproducer: true}) + { + properties[CFamilyReproducerProperty] = path; + } + var compilationDatabaseHandle = compilationDatabaseLocator.GetOrNull(path); if (compilationDatabaseHandle == null) { From 12b26fe1c9a95c0083fdfc08f445c59d27455952 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 24 Dec 2024 10:36:32 +0100 Subject: [PATCH 20/21] fix rebase problems --- .../UnintrusiveBindingControllerTests.cs | 1 - src/SLCore/SLCoreStrings.Designer.cs | 58 +++++++++---------- src/SLCore/SLCoreStrings.resx | 1 + 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs b/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs index 2192a3228c..7a19eba585 100644 --- a/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs @@ -112,7 +112,6 @@ public async Task BindAsync_CallsBindingProcessInOrder() { bindingProcessFactory.Create(Arg.Is(b => b.ProjectToBind == AnyBoundProject)); bindingProcess.DownloadQualityProfileAsync(null, cancellationToken); - bindingProcess.SaveServerExclusionsAsync(cancellationToken); }); } diff --git a/src/SLCore/SLCoreStrings.Designer.cs b/src/SLCore/SLCoreStrings.Designer.cs index b45c16638a..655b10580d 100644 --- a/src/SLCore/SLCoreStrings.Designer.cs +++ b/src/SLCore/SLCoreStrings.Designer.cs @@ -10,8 +10,8 @@ namespace SonarLint.VisualStudio.SLCore { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -23,15 +23,15 @@ namespace SonarLint.VisualStudio.SLCore { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class SLCoreStrings { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal SLCoreStrings() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -45,7 +45,7 @@ internal SLCoreStrings() { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -59,7 +59,7 @@ internal SLCoreStrings() { resourceCulture = value; } } - + /// /// Looks up a localized string similar to Internal analysis failure. See logs above.. /// @@ -68,7 +68,7 @@ public static string AnalysisFailedReason { return ResourceManager.GetString("AnalysisFailedReason", resourceCulture); } } - + /// /// Looks up a localized string similar to Updated analysis readiness: {0}. /// @@ -77,7 +77,7 @@ public static string AnalysisReadinessUpdate { return ResourceManager.GetString("AnalysisReadinessUpdate", resourceCulture); } } - + /// /// Looks up a localized string similar to [CertificateChainValidator] Certificate validation failed for the following reason(s):. /// @@ -86,7 +86,7 @@ public static string CertificateValidator_Failed { return ResourceManager.GetString("CertificateValidator_Failed", resourceCulture); } } - + /// /// Looks up a localized string similar to [CertificateChainValidator] {0}: {1}. /// @@ -95,7 +95,7 @@ public static string CertificateValidator_FailureReasonTemplate { return ResourceManager.GetString("CertificateValidator_FailureReasonTemplate", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCoreAnalyzer] No compilation database found for file: {0}. Check that the file is part of a supported project type in the current solution.. /// @@ -104,7 +104,7 @@ public static string CompilationDatabaseNotFound { return ResourceManager.GetString("CompilationDatabaseNotFound", resourceCulture); } } - + /// /// Looks up a localized string similar to Configuration scope conflict. /// @@ -113,7 +113,7 @@ public static string ConfigScopeConflict { return ResourceManager.GetString("ConfigScopeConflict", resourceCulture); } } - + /// /// Looks up a localized string similar to Configuration scope not initialized. /// @@ -122,7 +122,7 @@ public static string ConfigScopeNotInitialized { return ResourceManager.GetString("ConfigScopeNotInitialized", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCore.Http] Received server trust verification request.... /// @@ -131,7 +131,7 @@ public static string HttpConfiguration_ServerTrustVerificationRequest { return ResourceManager.GetString("HttpConfiguration_ServerTrustVerificationRequest", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCore.Http] Server verification result: {0}. /// @@ -140,7 +140,7 @@ public static string HttpConfiguration_ServerTrustVerificationResult { return ResourceManager.GetString("HttpConfiguration_ServerTrustVerificationResult", resourceCulture); } } - + /// /// Looks up a localized string similar to Unexpected enum value. /// @@ -149,7 +149,7 @@ public static string ModelExtensions_UnexpectedValue { return ResourceManager.GetString("ModelExtensions_UnexpectedValue", resourceCulture); } } - + /// /// Looks up a localized string similar to Service Provider is unavailable. /// @@ -158,7 +158,7 @@ public static string ServiceProviderNotInitialized { return ResourceManager.GetString("ServiceProviderNotInitialized", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCoreHandler] Creating SLCore instance. /// @@ -167,7 +167,7 @@ public static string SLCoreHandler_CreatingInstance { return ResourceManager.GetString("SLCoreHandler_CreatingInstance", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCoreHandler] Error creating SLCore instance. /// @@ -176,7 +176,7 @@ public static string SLCoreHandler_CreatingInstanceError { return ResourceManager.GetString("SLCoreHandler_CreatingInstanceError", resourceCulture); } } - + /// /// Looks up a localized string similar to Current instance is alive. /// @@ -185,7 +185,7 @@ public static string SLCoreHandler_InstanceAlreadyRunning { return ResourceManager.GetString("SLCoreHandler_InstanceAlreadyRunning", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCoreHandler] SLCore instance exited. /// @@ -194,7 +194,7 @@ public static string SLCoreHandler_InstanceDied { return ResourceManager.GetString("SLCoreHandler_InstanceDied", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCoreHandler] Starting SLCore instance. /// @@ -203,7 +203,7 @@ public static string SLCoreHandler_StartingInstance { return ResourceManager.GetString("SLCoreHandler_StartingInstance", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCoreHandler] Error starting SLCore instance. /// @@ -212,7 +212,7 @@ public static string SLCoreHandler_StartingInstanceError { return ResourceManager.GetString("SLCoreHandler_StartingInstanceError", resourceCulture); } } - + /// /// Looks up a localized string similar to [SLCoreServiceProvider]Cannot Create Service. Error: {0}. /// @@ -221,7 +221,7 @@ public static string SLCoreServiceProvider_CreateServiceError { return ResourceManager.GetString("SLCoreServiceProvider_CreateServiceError", resourceCulture); } } - + /// /// Looks up a localized string similar to SonarQube for Visual Studio background service failed to start. /// @@ -230,7 +230,7 @@ public static string SloopRestartFailedNotificationService_GoldBarMessage { return ResourceManager.GetString("SloopRestartFailedNotificationService_GoldBarMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Restart SonarQube for Visual Studio. /// @@ -239,7 +239,7 @@ public static string SloopRestartFailedNotificationService_Restart { return ResourceManager.GetString("SloopRestartFailedNotificationService_Restart", resourceCulture); } } - + /// /// Looks up a localized string similar to Unexpected server connection type. /// @@ -248,7 +248,7 @@ public static string UnexpectedServerConnectionType { return ResourceManager.GetString("UnexpectedServerConnectionType", resourceCulture); } } - + /// /// Looks up a localized string similar to Proxy type can not be determined from scheme '{0}'. Returning HTTP proxy type.. /// diff --git a/src/SLCore/SLCoreStrings.resx b/src/SLCore/SLCoreStrings.resx index a57a56a5c2..dbf2242e14 100644 --- a/src/SLCore/SLCoreStrings.resx +++ b/src/SLCore/SLCoreStrings.resx @@ -179,6 +179,7 @@ Proxy type can not be determined from scheme '{0}'. Returning HTTP proxy type. + [SLCoreAnalyzer] No compilation database found for file: {0}. Check that the file is part of a supported project type in the current solution. From 04a9153e0a5dacfb606492685fe333b85c5bd8e5 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:11:00 +0100 Subject: [PATCH 21/21] Fix QualityGate issues (#5923) Part of SLVS-1167 --- .../Binding/BindingProcessFactoryTests.cs | 5 +--- .../Binding/BindingProcessImplTests.cs | 30 ++++--------------- .../Binding/BindingProcessImpl.cs | 14 --------- .../Binding/IBindingProcessFactory.cs | 6 ---- .../SonarLintTagger/TaggerProvider.cs | 2 -- 5 files changed, 7 insertions(+), 50 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs b/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs index 80bcdf2bcb..c10eb1c1e1 100644 --- a/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/BindingProcessFactoryTests.cs @@ -37,7 +37,6 @@ public class BindingProcessFactoryTests public void MefCtor_CheckIsExported() { MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -55,15 +54,13 @@ public void Create_ReturnsProcessImpl() } private static BindingProcessFactory CreateTestSubject( - ISonarQubeService service = null, IQualityProfileDownloader qualityProfileDownloader = null, ILogger logger = null) { - service ??= Mock.Of(); qualityProfileDownloader ??= Mock.Of(); logger ??= new TestLogger(logToConsole: true); - return new BindingProcessFactory(service, qualityProfileDownloader, logger); + return new BindingProcessFactory(qualityProfileDownloader, logger); } } diff --git a/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs b/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs index c579792fd3..3c763d1577 100644 --- a/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/BindingProcessImplTests.cs @@ -29,7 +29,6 @@ using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client; -using SonarQube.Client.Models; using SonarQube.Client.Helpers; using System.Security; @@ -45,36 +44,22 @@ public void Ctor_ArgChecks() { var bindingArgs = CreateBindCommandArgs(); var qpDownloader = Mock.Of(); - var sonarQubeService = Mock.Of(); + Mock.Of(); var logger = Mock.Of(); // 1. Null binding args - Action act = () => new BindingProcessImpl(null, sonarQubeService, qpDownloader, logger); + Action act = () => new BindingProcessImpl(null, qpDownloader, logger); act.Should().ThrowExactly().And.ParamName.Should().Be("bindingArgs"); - // 3. Null SonarQube service - act = () => new BindingProcessImpl(bindingArgs, null, qpDownloader, logger); - act.Should().ThrowExactly().And.ParamName.Should().Be("sonarQubeService"); - - // 4. Null QP downloader - act = () => new BindingProcessImpl(bindingArgs, sonarQubeService, null, logger); + // 2. Null QP downloader + act = () => new BindingProcessImpl(bindingArgs, null, logger); act.Should().ThrowExactly().And.ParamName.Should().Be("qualityProfileDownloader"); - // 5. Null logger - act = () => new BindingProcessImpl(bindingArgs, sonarQubeService, qpDownloader, null); + // 3. Null logger + act = () => new BindingProcessImpl(bindingArgs, qpDownloader, null); act.Should().ThrowExactly().And.ParamName.Should().Be("logger"); } - private static ServerExclusions CreateSettings() - { - return new ServerExclusions - { - Inclusions = new string[] { "inclusion1", "inclusion2" }, - Exclusions = new string[] { "exclusion" }, - GlobalExclusions = new string[] { "globalExclusion" } - }; - } - [TestMethod] public async Task DownloadQualityProfile_CreatesBoundProjectAndCallsQPDownloader() { @@ -132,17 +117,14 @@ public async Task DownloadQualityProfile_HandlesInvalidOperationException() #region Helpers private BindingProcessImpl CreateTestSubject(BindCommandArgs bindingArgs = null, - ISonarQubeService sonarQubeService = null, IQualityProfileDownloader qpDownloader = null, ILogger logger = null) { bindingArgs = bindingArgs ?? CreateBindCommandArgs(); - sonarQubeService ??= Mock.Of(); qpDownloader ??= Mock.Of(); logger ??= new TestLogger(logToConsole: true); return new BindingProcessImpl(bindingArgs, - sonarQubeService, qpDownloader, logger); } diff --git a/src/ConnectedMode/Binding/BindingProcessImpl.cs b/src/ConnectedMode/Binding/BindingProcessImpl.cs index 08e6d4c2ee..e7caa46762 100644 --- a/src/ConnectedMode/Binding/BindingProcessImpl.cs +++ b/src/ConnectedMode/Binding/BindingProcessImpl.cs @@ -18,37 +18,23 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarLint.VisualStudio.ConnectedMode.QualityProfiles; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Binding; -using SonarQube.Client; -using SonarQube.Client.Models; -using Language = SonarLint.VisualStudio.Core.Language; namespace SonarLint.VisualStudio.ConnectedMode.Binding { internal class BindingProcessImpl : IBindingProcess { private readonly BindCommandArgs bindingArgs; - private readonly ISonarQubeService sonarQubeService; private readonly IQualityProfileDownloader qualityProfileDownloader; private readonly ILogger logger; public BindingProcessImpl( BindCommandArgs bindingArgs, - ISonarQubeService sonarQubeService, IQualityProfileDownloader qualityProfileDownloader, ILogger logger) { this.bindingArgs = bindingArgs ?? throw new ArgumentNullException(nameof(bindingArgs)); - this.sonarQubeService = sonarQubeService ?? throw new ArgumentNullException(nameof(sonarQubeService)); this.qualityProfileDownloader = qualityProfileDownloader ?? throw new ArgumentNullException(nameof(qualityProfileDownloader)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } diff --git a/src/ConnectedMode/Binding/IBindingProcessFactory.cs b/src/ConnectedMode/Binding/IBindingProcessFactory.cs index c68bb69b8a..382611e723 100644 --- a/src/ConnectedMode/Binding/IBindingProcessFactory.cs +++ b/src/ConnectedMode/Binding/IBindingProcessFactory.cs @@ -21,8 +21,6 @@ using System.ComponentModel.Composition; using SonarLint.VisualStudio.ConnectedMode.QualityProfiles; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarQube.Client; namespace SonarLint.VisualStudio.ConnectedMode.Binding { @@ -38,17 +36,14 @@ internal interface IBindingProcessFactory [PartCreationPolicy(CreationPolicy.Shared)] internal class BindingProcessFactory : IBindingProcessFactory { - private readonly ISonarQubeService sonarQubeService; private readonly IQualityProfileDownloader qualityProfileDownloader; private readonly ILogger logger; [ImportingConstructor] public BindingProcessFactory( - ISonarQubeService sonarQubeService, IQualityProfileDownloader qualityProfileDownloader, ILogger logger) { - this.sonarQubeService = sonarQubeService; this.qualityProfileDownloader = qualityProfileDownloader; this.logger = logger; } @@ -56,7 +51,6 @@ public BindingProcessFactory( public IBindingProcess Create(BindCommandArgs bindingArgs) { return new BindingProcessImpl(bindingArgs, - sonarQubeService, qualityProfileDownloader, logger); } diff --git a/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs b/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs index 1254a5fa0e..9b135252ba 100644 --- a/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs +++ b/src/Integration.Vsix/SonarLintTagger/TaggerProvider.cs @@ -162,8 +162,6 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag var tagger = singletonTaggerManager.CreateTagger(buffer); return tagger as ITagger; - - return null; } private TextBufferIssueTracker InternalCreateTextBufferIssueTracker(ITextDocument textDocument, IEnumerable analysisLanguages) =>