From 496ce2a1bea4c5085cd4e12536f1d225b87c5adc Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Thu, 28 Nov 2024 12:59:04 +0100 Subject: [PATCH 01/10] Migrate VCX analysis to SLCore --- ...> CMakeCompilationDatabaseLocatorTests.cs} | 32 +- .../CMake/CMakeRequestFactoryTests.cs | 226 ------------ .../CMake/CompilationConfigProviderTests.cs | 345 ------------------ .../PchCacheCleanerTests.cs | 136 ------- .../PreCompiledHeadersEventListenerTests.cs | 201 ---------- src/CFamily/Analysis/CLangAnalyzer.cs | 72 ---- ....cs => CMakeCompilationDatabaseLocator.cs} | 27 +- 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 ++ ...egration.Vsix_Baseline_WithStrongNames.txt | 9 +- ...ation.Vsix_Baseline_WithoutStrongNames.txt | 9 +- .../IVCXCompilationDatabaseStorage.cs | 76 ++++ .../VCXCompilationDatabaseProvider.cs | 65 ++++ .../SonarLintDaemonPackage.cs | 16 +- .../Analysis/SLCoreAnalyzerTests.cs | 22 +- src/SLCore/Analysis/SLCoreAnalyzer.cs | 10 +- 21 files changed, 277 insertions(+), 1549 deletions(-) rename src/CFamily.UnitTests/CMake/{CompilationDatabaseLocatorTests.cs => CMakeCompilationDatabaseLocatorTests.cs} (92%) delete mode 100644 src/CFamily.UnitTests/CMake/CMakeRequestFactoryTests.cs delete mode 100644 src/CFamily.UnitTests/CMake/CompilationConfigProviderTests.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} (86%) 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/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 92% rename from src/CFamily.UnitTests/CMake/CompilationDatabaseLocatorTests.cs rename to src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs index bd63dfa77e..a8156d068b 100644 --- a/src/CFamily.UnitTests/CMake/CompilationDatabaseLocatorTests.cs +++ b/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs @@ -18,22 +18,18 @@ * 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.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,7 +39,7 @@ public class CompilationDatabaseLocatorTests [TestMethod] public void MefCtor_CheckIsExported() { - MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -132,11 +128,11 @@ 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); @@ -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, + IFileSystem fileSystem = null, ILogger logger = null, IMacroEvaluationService macroEvaluationService = null) { @@ -238,14 +234,14 @@ private static CompilationDatabaseLocator CreateTestSubject(string rootDirectory fileSystem ??= new FileSystem(); 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; } @@ -291,7 +287,7 @@ public MacroEvalContext(string macroServiceReturnValue, string unevaluatedBuildR var fileSystem = new Mock(); Func nonNullFilesExist = x => x != null; fileSystem.Setup(x => x.File.Exists(It.IsAny())).Returns(nonNullFilesExist); - + MacroEvalService = new Mock(); MacroEvalService.Setup(x => x.Evaluate(unevaluatedBuildRoot, @@ -310,7 +306,7 @@ public MacroEvalContext(string macroServiceReturnValue, string unevaluatedBuildR fileSystem.Object, 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/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 86% rename from src/CFamily/CMake/CompilationDatabaseLocator.cs rename to src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs index dbed78147f..ef8dd7530c 100644 --- a/src/CFamily/CMake/CompilationDatabaseLocator.cs +++ b/src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs @@ -21,15 +21,18 @@ 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(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}"; @@ -42,21 +45,21 @@ internal class CompilationDatabaseLocator : ICompilationDatabaseLocator private readonly ILogger logger; [ImportingConstructor] - public CompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, ILogger logger) + public CMakeCompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, ILogger logger) : this(folderWorkspaceService, new BuildConfigProvider(logger), - new CMakeSettingsProvider(logger), + new CMakeSettingsProvider(logger), new MacroEvaluationService(logger), new FileSystem(), logger) { } - public CompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, + public CMakeCompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, IBuildConfigProvider buildConfigProvider, ICMakeSettingsProvider cMakeSettingsProvider, IMacroEvaluationService macroEvaluationService, - IFileSystem fileSystem, + IFileSystem fileSystem, ILogger logger) { this.folderWorkspaceService = folderWorkspaceService; @@ -73,7 +76,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 +102,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 +132,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/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt index ae4dde4b0d..d9313608c0 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithStrongNames.txt @@ -1,7 +1,7 @@ --- ################################ # Assembly references report -# Report date/time: 2024-11-27T13:19:57.1708469Z +# Report date/time: 2024-11-27T15:00:06.4220979Z ################################ # # Generated by Devtility CheckAsmRefs v0.11.0.223 @@ -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.8.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.8.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.8.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.8.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 7fbfe23e2c..0c33bb7b9b 100644 --- a/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt +++ b/src/Integration.Vsix/AsmRef_Integration.Vsix_Baseline_WithoutStrongNames.txt @@ -1,7 +1,7 @@ --- ################################ # Assembly references report -# Report date/time: 2024-11-27T13:19:57.1708469Z +# Report date/time: 2024-11-27T15:00:06.4220979Z ################################ # # Generated by Devtility CheckAsmRefs v0.11.0.223 @@ -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.8.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.8.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.8.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.8.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..7f00ac7758 --- /dev/null +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -0,0 +1,76 @@ +/* + * 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; + +namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +internal interface IVCXCompilationDatabaseStorage : IDisposable +{ + string CreateDatabase(IFileConfig fileConfig); +} + +[Export(typeof(IVCXCompilationDatabaseStorage))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method: ImportingConstructor] +internal class VcxCompilationDatabaseStorage(IThreadHandling threadHandling) : IVCXCompilationDatabaseStorage +{ + private bool disposed; + private readonly IFileSystem fileSystem = new FileSystem(); + private string workDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); + + public string CreateDatabase(IFileConfig fileConfig) + { + threadHandling.ThrowIfOnUIThread(); + + var compilationDatabaseEntry = new CompilationDatabaseEntry { Directory = fileConfig.CDDirectory, Command = fileConfig.CDCommand, File = fileConfig.CDFile }; + var compilationDatabase = new[] { compilationDatabaseEntry }; + + try + { + fileSystem.Directory.CreateDirectory(workDirectoryPath); + var path = Path.Combine(workDirectoryPath, $"{Path.GetFileNameWithoutExtension(compilationDatabaseEntry.File)}_{compilationDatabaseEntry.File!.GetHashCode()}.json"); + fileSystem.File.WriteAllText(path, JsonConvert.SerializeObject(compilationDatabase)); + return path; + } + catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) + { + //todo log + return null; + } + } + + public void Dispose() + { + if (disposed) + { + return; + } + + disposed = true; + fileSystem.Directory.Delete(workDirectoryPath, 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..309c4ef94a --- /dev/null +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -0,0 +1,65 @@ +/* + * 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( + IVsUIServiceOperation uiServiceOperation, + IVCXCompilationDatabaseStorage storage, + ILogger logger, + IThreadHandling threadHandling) + : IVCXCompilationDatabaseProvider +{ + private readonly IFileConfigProvider fileConfigProvider = new FileConfigProvider(logger); + + public string CreateOrNull(string filePath) + { + IFileConfig fileConfig = null; + uiServiceOperation.Execute(dte => + { + threadHandling.ThrowIfNotOnUIThread(); + + var projectItem = dte.Solution.FindProjectItem(filePath); + if (projectItem == null) + { + return; + } + + fileConfig = fileConfigProvider.Get(projectItem, filePath, null); + }); + + if (fileConfig is null) + { + return null; + } + + return storage.CreateDatabase(fileConfig); + } +} 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 Date: Thu, 28 Nov 2024 13:06:34 +0100 Subject: [PATCH 02/10] explicit constructor --- .../IVCXCompilationDatabaseStorage.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index 7f00ac7758..bd4fd256f8 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -35,18 +35,29 @@ internal interface IVCXCompilationDatabaseStorage : IDisposable [Export(typeof(IVCXCompilationDatabaseStorage))] [PartCreationPolicy(CreationPolicy.Shared)] -[method: ImportingConstructor] -internal class VcxCompilationDatabaseStorage(IThreadHandling threadHandling) : IVCXCompilationDatabaseStorage +internal class VcxCompilationDatabaseStorage : IVCXCompilationDatabaseStorage { private bool disposed; private readonly IFileSystem fileSystem = new FileSystem(); - private string workDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); + private readonly string workDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); + private readonly IThreadHandling threadHandling; + + [ImportingConstructor] + public VcxCompilationDatabaseStorage(IThreadHandling threadHandling) + { + this.threadHandling = threadHandling; + } public string CreateDatabase(IFileConfig fileConfig) { threadHandling.ThrowIfOnUIThread(); - var compilationDatabaseEntry = new CompilationDatabaseEntry { Directory = fileConfig.CDDirectory, Command = fileConfig.CDCommand, File = fileConfig.CDFile }; + var compilationDatabaseEntry = new CompilationDatabaseEntry + { + Directory = fileConfig.CDDirectory, + Command = fileConfig.CDCommand, + File = fileConfig.CDFile + }; var compilationDatabase = new[] { compilationDatabaseEntry }; try From 04e362ef9a2a77a84cf7ec618da7210e5501e4ef Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Thu, 28 Nov 2024 14:11:48 +0100 Subject: [PATCH 03/10] small refectoring --- .../IVCXCompilationDatabaseStorage.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index bd4fd256f8..0da0d865be 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -35,11 +35,11 @@ internal interface IVCXCompilationDatabaseStorage : IDisposable [Export(typeof(IVCXCompilationDatabaseStorage))] [PartCreationPolicy(CreationPolicy.Shared)] -internal class VcxCompilationDatabaseStorage : IVCXCompilationDatabaseStorage +internal sealed class VcxCompilationDatabaseStorage : IVCXCompilationDatabaseStorage { private bool disposed; private readonly IFileSystem fileSystem = new FileSystem(); - private readonly string workDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); + private readonly string compilationDatabaseDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); private readonly IThreadHandling threadHandling; [ImportingConstructor] @@ -62,10 +62,10 @@ public string CreateDatabase(IFileConfig fileConfig) try { - fileSystem.Directory.CreateDirectory(workDirectoryPath); - var path = Path.Combine(workDirectoryPath, $"{Path.GetFileNameWithoutExtension(compilationDatabaseEntry.File)}_{compilationDatabaseEntry.File!.GetHashCode()}.json"); - fileSystem.File.WriteAllText(path, JsonConvert.SerializeObject(compilationDatabase)); - return path; + var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(compilationDatabaseEntry); + fileSystem.Directory.CreateDirectory(compilationDatabaseDirectoryPath); + fileSystem.File.WriteAllText(compilationDatabaseFullPath, JsonConvert.SerializeObject(compilationDatabase)); + return compilationDatabaseFullPath; } catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) { @@ -74,6 +74,13 @@ public string CreateDatabase(IFileConfig fileConfig) } } + 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) @@ -82,6 +89,6 @@ public void Dispose() } disposed = true; - fileSystem.Directory.Delete(workDirectoryPath, true); + fileSystem.Directory.Delete(compilationDatabaseDirectoryPath, true); } } From da33bd5715315728997dbcdaa37ea3c3b87af0e1 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Thu, 28 Nov 2024 15:05:46 +0100 Subject: [PATCH 04/10] WIP migrate to IFileSystemService --- .../CMakeCompilationDatabaseLocatorTests.cs | 8 +++++--- .../CMake/CMakeCompilationDatabaseLocator.cs | 9 +++++---- .../IVCXCompilationDatabaseStorage.cs | 19 +++++++------------ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs b/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs index a8156d068b..f7fdabb4c6 100644 --- a/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs +++ b/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs @@ -23,6 +23,7 @@ using Moq; using SonarLint.VisualStudio.CFamily.CMake; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.SystemAbstractions; using SonarLint.VisualStudio.TestInfrastructure; using static SonarLint.VisualStudio.TestInfrastructure.Extensions.FileSystemExtensions; @@ -41,6 +42,7 @@ public void MefCtor_CheckIsExported() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -134,7 +136,7 @@ public void Locate_HasCMakeSettingsFile_ReturnsConfiguredPathIfItExists(bool fil var compilationDatabaseFullLocation = Path.GetFullPath( Path.Combine("folder", CMakeCompilationDatabaseLocator.CompilationDatabaseFileName)); - var fileSystem = new Mock(); + var fileSystem = new Mock(); fileSystem.SetFileExists(compilationDatabaseFullLocation, fileExists); var testSubject = CreateTestSubject(RootDirectory, configProvider, cmakeSettingsProvider.Object, fileSystem.Object, @@ -221,7 +223,7 @@ private static string GetDefaultDatabaseFileLocation(string activeBuildConfigura private static CMakeCompilationDatabaseLocator CreateTestSubject(string rootDirectory, IBuildConfigProvider buildConfigProvider = null, ICMakeSettingsProvider cMakeSettingsProvider = null, - IFileSystem fileSystem = null, + IFileSystemService fileSystem = null, ILogger logger = null, IMacroEvaluationService macroEvaluationService = null) { @@ -231,7 +233,7 @@ private static CMakeCompilationDatabaseLocator CreateTestSubject(string rootDire cMakeSettingsProvider ??= Mock.Of(); buildConfigProvider ??= Mock.Of(); logger ??= Mock.Of(); - fileSystem ??= new FileSystem(); + fileSystem ??= new FileSystemService(); macroEvaluationService ??= Mock.Of(); return new CMakeCompilationDatabaseLocator(folderWorkspaceService.Object, buildConfigProvider, cMakeSettingsProvider, macroEvaluationService, fileSystem, logger); diff --git a/src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs b/src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs index ef8dd7530c..252d165e27 100644 --- a/src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs +++ b/src/CFamily/CMake/CMakeCompilationDatabaseLocator.cs @@ -22,6 +22,7 @@ using System.IO; using System.IO.Abstractions; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.SystemAbstractions; namespace SonarLint.VisualStudio.CFamily.CMake { @@ -38,19 +39,19 @@ internal class CMakeCompilationDatabaseLocator : ICMakeCompilationDatabaseLocato 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 CMakeCompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, ILogger logger) + public CMakeCompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceService, IFileSystemService fileSystem, ILogger logger) : this(folderWorkspaceService, new BuildConfigProvider(logger), new CMakeSettingsProvider(logger), new MacroEvaluationService(logger), - new FileSystem(), + fileSystem, logger) { } @@ -59,7 +60,7 @@ public CMakeCompilationDatabaseLocator(IFolderWorkspaceService folderWorkspaceSe IBuildConfigProvider buildConfigProvider, ICMakeSettingsProvider cMakeSettingsProvider, IMacroEvaluationService macroEvaluationService, - IFileSystem fileSystem, + IFileSystemService fileSystem, ILogger logger) { this.folderWorkspaceService = folderWorkspaceService; diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index 0da0d865be..4db96051b5 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -25,6 +25,7 @@ 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; @@ -35,18 +36,12 @@ internal interface IVCXCompilationDatabaseStorage : IDisposable [Export(typeof(IVCXCompilationDatabaseStorage))] [PartCreationPolicy(CreationPolicy.Shared)] -internal sealed class VcxCompilationDatabaseStorage : IVCXCompilationDatabaseStorage +[method: ImportingConstructor] +internal sealed class VcxCompilationDatabaseStorage(IFileSystemService fileSystemService, IThreadHandling threadHandling) + : IVCXCompilationDatabaseStorage { private bool disposed; - private readonly IFileSystem fileSystem = new FileSystem(); private readonly string compilationDatabaseDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); - private readonly IThreadHandling threadHandling; - - [ImportingConstructor] - public VcxCompilationDatabaseStorage(IThreadHandling threadHandling) - { - this.threadHandling = threadHandling; - } public string CreateDatabase(IFileConfig fileConfig) { @@ -63,8 +58,8 @@ public string CreateDatabase(IFileConfig fileConfig) try { var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(compilationDatabaseEntry); - fileSystem.Directory.CreateDirectory(compilationDatabaseDirectoryPath); - fileSystem.File.WriteAllText(compilationDatabaseFullPath, JsonConvert.SerializeObject(compilationDatabase)); + fileSystemService.Directory.CreateDirectory(compilationDatabaseDirectoryPath); + fileSystemService.File.WriteAllText(compilationDatabaseFullPath, JsonConvert.SerializeObject(compilationDatabase)); return compilationDatabaseFullPath; } catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) @@ -89,6 +84,6 @@ public void Dispose() } disposed = true; - fileSystem.Directory.Delete(compilationDatabaseDirectoryPath, true); + fileSystemService.Directory.Delete(compilationDatabaseDirectoryPath, true); } } From 1da439fc281be390e1d092d71fa60082143b391a Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Thu, 28 Nov 2024 15:28:17 +0100 Subject: [PATCH 05/10] Fix FileSystemExtensions --- .../CMakeCompilationDatabaseLocatorTests.cs | 17 +++++----- .../Extensions/FileSystemExtensions.cs | 31 ++++--------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs b/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs index f7fdabb4c6..ccf41187d4 100644 --- a/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs +++ b/src/CFamily.UnitTests/CMake/CMakeCompilationDatabaseLocatorTests.cs @@ -70,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(); @@ -136,10 +135,9 @@ public void Locate_HasCMakeSettingsFile_ReturnsConfiguredPathIfItExists(bool fil var compilationDatabaseFullLocation = Path.GetFullPath( 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(); @@ -286,9 +284,8 @@ 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 => @@ -305,7 +302,7 @@ 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 CMakeCompilationDatabaseLocator TestSubject { get; } diff --git a/src/TestInfrastructure/Extensions/FileSystemExtensions.cs b/src/TestInfrastructure/Extensions/FileSystemExtensions.cs index 8d9a3f0c3b..73bc4edafd 100644 --- a/src/TestInfrastructure/Extensions/FileSystemExtensions.cs +++ b/src/TestInfrastructure/Extensions/FileSystemExtensions.cs @@ -28,35 +28,16 @@ namespace SonarLint.VisualStudio.TestInfrastructure.Extensions /// 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 ab5b00879da9f6943c378f1ad1e7bfd333d2b545 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Thu, 28 Nov 2024 15:40:49 +0100 Subject: [PATCH 06/10] Add AggregatingCompilationDatabaseProviderTests --- ...egatingCompilationDatabaseProviderTests.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs 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(); + } +} From 7acbb9478f8210ec4f68cb2266c6194604e18de1 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 29 Nov 2024 15:12:54 +0100 Subject: [PATCH 07/10] Upd --- .../VCXCompilationDatabaseProviderTests.cs | 86 +++++++++++++++++++ .../VCXCompilationDatabaseProvider.cs | 33 ++----- 2 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs 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..6dd5d386a2 --- /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(); + + fileConfigProvider.DidNotReceiveWithAnyArgs().Get(default, 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/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs index 309c4ef94a..c431045387 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -31,35 +31,12 @@ namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; [PartCreationPolicy(CreationPolicy.Shared)] [method: ImportingConstructor] internal class VCXCompilationDatabaseProvider( - IVsUIServiceOperation uiServiceOperation, IVCXCompilationDatabaseStorage storage, - ILogger logger, - IThreadHandling threadHandling) + IFileConfigProvider fileConfigProvider) : IVCXCompilationDatabaseProvider { - private readonly IFileConfigProvider fileConfigProvider = new FileConfigProvider(logger); - - public string CreateOrNull(string filePath) - { - IFileConfig fileConfig = null; - uiServiceOperation.Execute(dte => - { - threadHandling.ThrowIfNotOnUIThread(); - - var projectItem = dte.Solution.FindProjectItem(filePath); - if (projectItem == null) - { - return; - } - - fileConfig = fileConfigProvider.Get(projectItem, filePath, null); - }); - - if (fileConfig is null) - { - return null; - } - - return storage.CreateDatabase(fileConfig); - } + public string CreateOrNull(string filePath) => + fileConfigProvider.Get(filePath, null) is {} fileConfig + ? storage.CreateDatabase(fileConfig) + : null; } From a8ed11e6e3d2d361263f7a8612b05b3effd77956 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 29 Nov 2024 15:51:15 +0100 Subject: [PATCH 08/10] Fix tests --- .../CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs index 6dd5d386a2..13e824a7cc 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs @@ -58,7 +58,7 @@ public void CreateOrNull_NoFileConfig_ReturnsNull() testSubject.CreateOrNull(SourceFilePath).Should().BeNull(); - fileConfigProvider.DidNotReceiveWithAnyArgs().Get(default, default); + storage.DidNotReceiveWithAnyArgs().CreateDatabase(default); } [TestMethod] From cbc4f430724020ab890a9bd34d069f3df9a6c6ab Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 29 Nov 2024 15:57:40 +0100 Subject: [PATCH 09/10] VCXCompilationDatabaseStorageTests --- src/Core/Helpers/PathHelper.cs | 14 +-- .../VCXCompilationDatabaseStorageTests.cs | 115 ++++++++++++++++++ .../IVCXCompilationDatabaseStorage.cs | 7 +- 3 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs 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/VCXCompilationDatabaseStorageTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs new file mode 100644 index 0000000000..07560cab65 --- /dev/null +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs @@ -0,0 +1,115 @@ +/* + * 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(); + } + + 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/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index 4db96051b5..02e962e1f2 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -37,7 +37,7 @@ internal interface IVCXCompilationDatabaseStorage : IDisposable [Export(typeof(IVCXCompilationDatabaseStorage))] [PartCreationPolicy(CreationPolicy.Shared)] [method: ImportingConstructor] -internal sealed class VcxCompilationDatabaseStorage(IFileSystemService fileSystemService, IThreadHandling threadHandling) +internal sealed class VCXCompilationDatabaseStorage(IFileSystemService fileSystemService, IThreadHandling threadHandling, ILogger logger) : IVCXCompilationDatabaseStorage { private bool disposed; @@ -55,16 +55,17 @@ public string CreateDatabase(IFileConfig fileConfig) }; var compilationDatabase = new[] { compilationDatabaseEntry }; + var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(compilationDatabaseEntry); + try { - var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(compilationDatabaseEntry); fileSystemService.Directory.CreateDirectory(compilationDatabaseDirectoryPath); fileSystemService.File.WriteAllText(compilationDatabaseFullPath, JsonConvert.SerializeObject(compilationDatabase)); return compilationDatabaseFullPath; } catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) { - //todo log + logger.LogVerbose(e.ToString()); return null; } } From 412df02bbdc10aec6111419940a962d78730fc3e Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 29 Nov 2024 16:24:07 +0100 Subject: [PATCH 10/10] Add disposal checks and tests --- .../VCXCompilationDatabaseStorageTests.cs | 24 +++++++++++++++++++ .../IVCXCompilationDatabaseStorage.cs | 9 +++++++ 2 files changed, 33 insertions(+) diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs index 07560cab65..60292595a6 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs @@ -97,6 +97,30 @@ public void CreateDatabase_FileWritten_ReturnsPathToDatabaseWithCorrectContent() 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(); diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index 02e962e1f2..71c9b1fca5 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -45,6 +45,7 @@ internal sealed class VCXCompilationDatabaseStorage(IFileSystemService fileSyste public string CreateDatabase(IFileConfig fileConfig) { + ThrowIfDisposed(); threadHandling.ThrowIfOnUIThread(); var compilationDatabaseEntry = new CompilationDatabaseEntry @@ -70,6 +71,14 @@ public string CreateDatabase(IFileConfig fileConfig) } } + 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";