From d06e140f90f24a4979c7a39fe64fbb02f5a9834e Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:54:21 +0100 Subject: [PATCH] SLVS-1678 Use unique generated compilation database names (#5875) [SLVS-1678](https://sonarsource.atlassian.net/browse/SLVS-1678) [SLVS-1678]: https://sonarsource.atlassian.net/browse/SLVS-1678?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- ...egatingCompilationDatabaseProviderTests.cs | 4 +- .../ExternalCompilationDatabaseHandleTests.cs | 52 +++++ .../AggregatingCompilationDatabaseProvider.cs | 4 +- .../ExternalCompilationDatabaseHandle.cs | 33 +++ .../IVCXCompilationDatabaseProvider.cs | 4 +- ...IAggregatingCompilationDatabaseProvider.cs | 8 +- ...TemporaryCompilationDatabaseHandleTests.cs | 87 ++++++++ .../VCXCompilationDatabaseProviderTests.cs | 9 +- .../VCXCompilationDatabaseStorageTests.cs | 46 +++- .../IVCXCompilationDatabaseStorage.cs | 25 ++- .../TemporaryCompilationDatabaseHandle.cs | 49 +++++ .../VCXCompilationDatabaseProvider.cs | 7 +- .../Analysis/SLCoreAnalyzerTests.cs | 206 +++++++++--------- src/SLCore/Analysis/SLCoreAnalyzer.cs | 47 ++-- 14 files changed, 422 insertions(+), 159 deletions(-) create mode 100644 src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs create mode 100644 src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs create mode 100644 src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs create mode 100644 src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs diff --git a/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs b/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs index 579ab1bcd..f4924fed0 100644 --- a/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs +++ b/src/CFamily.UnitTests/CompilationDatabase/AggregatingCompilationDatabaseProviderTests.cs @@ -58,7 +58,7 @@ public void GetOrNull_CmakeAvailable_ReturnsCmakeLocation() var result = testSubject.GetOrNull("some path"); - result.Should().Be(location); + result.Should().BeOfType().And.BeEquivalentTo(new ExternalCompilationDatabaseHandle(location)); vcx.DidNotReceiveWithAnyArgs().CreateOrNull(default); } @@ -66,7 +66,7 @@ public void GetOrNull_CmakeAvailable_ReturnsCmakeLocation() public void GetOrNull_CmakeUnavailable_VcxAvailable_ReturnsVcxLocation() { var sourcePath = "some path"; - var location = "some location"; + var location = Substitute.For(); cmake.Locate().ReturnsNull(); vcx.CreateOrNull(sourcePath).Returns(location); diff --git a/src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs b/src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs new file mode 100644 index 000000000..5dcf3b482 --- /dev/null +++ b/src/CFamily.UnitTests/CompilationDatabase/ExternalCompilationDatabaseHandleTests.cs @@ -0,0 +1,52 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.CFamily.CompilationDatabase; + +namespace SonarLint.VisualStudio.CFamily.UnitTests.CompilationDatabase; + +[TestClass] +public class ExternalCompilationDatabaseHandleTests +{ + [TestMethod] + public void Ctor_AssignsExpectedValues() + { + const string filePath = "some path"; + var testSubject = new ExternalCompilationDatabaseHandle(filePath); + + testSubject.FilePath.Should().BeSameAs(filePath); + } + + [TestMethod] + public void Ctor_NullPath_Throws() + { + var act = () => new ExternalCompilationDatabaseHandle(null); + + act.Should().Throw().Which.ParamName.Should().Be("filePath"); + } + + [TestMethod] + public void Dispose_NoOp_DoesNotThrow() + { + var act = () => new ExternalCompilationDatabaseHandle("some path").Dispose(); + + act.Should().NotThrow(); + } +} diff --git a/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs b/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs index dcf86a69c..300f74e86 100644 --- a/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs +++ b/src/CFamily/CompilationDatabase/AggregatingCompilationDatabaseProvider.cs @@ -32,11 +32,11 @@ internal class AggregatingCompilationDatabaseProvider( IVCXCompilationDatabaseProvider vcxCompilationDatabaseProvider) : IAggregatingCompilationDatabaseProvider { - public string GetOrNull(string sourceFilePath) + public ICompilationDatabaseHandle GetOrNull(string sourceFilePath) { if (cMakeCompilationDatabaseLocator.Locate() is {} cmakeCompilationDatabasePath) { - return cmakeCompilationDatabasePath; + return new ExternalCompilationDatabaseHandle(cmakeCompilationDatabasePath); } return vcxCompilationDatabaseProvider.CreateOrNull(sourceFilePath); diff --git a/src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs b/src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs new file mode 100644 index 000000000..f3c294c95 --- /dev/null +++ b/src/CFamily/CompilationDatabase/ExternalCompilationDatabaseHandle.cs @@ -0,0 +1,33 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.Core.CFamily; + +namespace SonarLint.VisualStudio.CFamily.CompilationDatabase; + +internal sealed class ExternalCompilationDatabaseHandle(string filePath) : ICompilationDatabaseHandle +{ + public string FilePath { get; } = filePath ?? throw new ArgumentNullException(nameof(filePath)); + + public void Dispose() + { + // do nothing + } +} diff --git a/src/CFamily/IVCXCompilationDatabaseProvider.cs b/src/CFamily/IVCXCompilationDatabaseProvider.cs index 4b99a37ef..a1636368e 100644 --- a/src/CFamily/IVCXCompilationDatabaseProvider.cs +++ b/src/CFamily/IVCXCompilationDatabaseProvider.cs @@ -18,9 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using SonarLint.VisualStudio.Core.CFamily; + namespace SonarLint.VisualStudio.CFamily; public interface IVCXCompilationDatabaseProvider { - string CreateOrNull(string filePath); + ICompilationDatabaseHandle CreateOrNull(string filePath); } diff --git a/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs b/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs index e11851096..fb55fe015 100644 --- a/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs +++ b/src/Core/CFamily/IAggregatingCompilationDatabaseProvider.cs @@ -22,5 +22,11 @@ namespace SonarLint.VisualStudio.Core.CFamily; public interface IAggregatingCompilationDatabaseProvider { - string GetOrNull(string sourceFilePath); + ICompilationDatabaseHandle GetOrNull(string sourceFilePath); } + +public interface ICompilationDatabaseHandle : IDisposable +{ + string FilePath { get; } +} + diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs new file mode 100644 index 000000000..eff96d552 --- /dev/null +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/TemporaryCompilationDatabaseHandleTests.cs @@ -0,0 +1,87 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.IO.Abstractions; +using NSubstitute.ReceivedExtensions; +using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; + +[TestClass] +public class TemporaryCompilationDatabaseHandleTests +{ + private const string FilePath = "some path"; + private IFile file; + private TestLogger logger; + private TemporaryCompilationDatabaseHandle testSubject; + + [TestInitialize] + public void TestInitialize() + { + file = Substitute.For(); + logger = new TestLogger(); + testSubject = new TemporaryCompilationDatabaseHandle(FilePath, file, logger); + } + + [DataRow("path1")] + [DataRow(@"path1\path2")] + [DataTestMethod] + public void Ctor_AssignsExpectedValues(string path) => + new TemporaryCompilationDatabaseHandle(path, default, default).FilePath.Should().BeSameAs(path); + + [TestMethod] + public void Ctor_NullPath_Throws() + { + var act = () => new TemporaryCompilationDatabaseHandle(null, default, default); + + act.Should().Throw().Which.ParamName.Should().Be("filePath"); + } + + [TestMethod] + public void Dispose_DeletesFile() + { + testSubject.Dispose(); + + file.Received().Delete(FilePath); + logger.AssertNoOutputMessages(); + } + + [TestMethod] + public void Dispose_MultipleTimes_ActsOnlyOnce() + { + testSubject.Dispose(); + testSubject.Dispose(); + testSubject.Dispose(); + + file.ReceivedWithAnyArgs(1).Delete(default); + } + + [TestMethod] + public void Dispose_CatchesAndLogsExceptions() + { + var exception = new Exception("testexc"); + file.When(x => x.Delete(Arg.Any())).Throw(exception); + + var act = () => testSubject.Dispose(); + + act.Should().NotThrow(); + logger.AssertPartialOutputStringExists(exception.ToString()); + } +} diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs index 13e824a7c..6a8c4100b 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseProviderTests.cs @@ -20,6 +20,7 @@ using NSubstitute.ReturnsExtensions; using SonarLint.VisualStudio.CFamily; +using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; namespace SonarLint.VisualStudio.Integration.UnitTests.CFamily.VcxProject; @@ -74,13 +75,13 @@ public void CreateOrNull_FileConfig_CantStore_ReturnsNull() } [TestMethod] - public void CreateOrNull_FileConfig_StoresAndReturnsPath() + public void CreateOrNull_FileConfig_StoresAndReturnsHandle() { - const string databasePath = "database path"; var fileConfig = Substitute.For(); fileConfigProvider.Get(SourceFilePath, default).Returns(fileConfig); - storage.CreateDatabase(fileConfig).Returns(databasePath); + var compilationDatabaseHandle = Substitute.For(); + storage.CreateDatabase(fileConfig).Returns(compilationDatabaseHandle); - testSubject.CreateOrNull(SourceFilePath).Should().Be(databasePath); + testSubject.CreateOrNull(SourceFilePath).Should().Be(compilationDatabaseHandle); } } diff --git a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs index 60292595a..07a739e6b 100644 --- a/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs +++ b/src/Integration.Vsix.UnitTests/CFamily/VcxProject/VCXCompilationDatabaseStorageTests.cs @@ -85,18 +85,32 @@ public void CreateDatabase_CriticalException_Throws() public void CreateDatabase_FileWritten_ReturnsPathToDatabaseWithCorrectContent() { var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); - var expectedPath = Path.Combine(expectedDirectory, $"{SourceFileName}.{SourceFilePath.GetHashCode()}.json"); var fileConfig = SetUpFileConfig(); - var databasePath = testSubject.CreateDatabase(fileConfig); + var databaseHandle = testSubject.CreateDatabase(fileConfig); - databasePath.Should().Be(expectedPath); + var temporaryCompilationDatabaseHandle = databaseHandle.Should().BeOfType().Subject; + Directory.GetParent(temporaryCompilationDatabaseHandle.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); + Path.GetExtension(temporaryCompilationDatabaseHandle.FilePath).Should().Be(".json"); threadHandling.Received().ThrowIfOnUIThread(); fileSystemService.Directory.Received().CreateDirectory(expectedDirectory); - fileSystemService.File.Received().WriteAllText(expectedPath, Arg.Any()); + fileSystemService.File.Received().WriteAllText(temporaryCompilationDatabaseHandle.FilePath, Arg.Any()); VerifyDatabaseContents(); } + [TestMethod] + public void CreateDatabase_CreatesDifferentHandlesForSameFile() + { + var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); + var fileConfig = SetUpFileConfig(); + + var databaseHandle1 = testSubject.CreateDatabase(fileConfig); + var databaseHandle2 = testSubject.CreateDatabase(fileConfig); + + Directory.GetParent(databaseHandle1.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); + Directory.GetParent(databaseHandle2.FilePath).FullName.Should().BeEquivalentTo(expectedDirectory); + Path.GetFileNameWithoutExtension(databaseHandle1.FilePath).Should().NotBe(Path.GetFileNameWithoutExtension(databaseHandle2.FilePath)); + } [TestMethod] public void CreateDatabase_Disposed_Throws() @@ -114,11 +128,33 @@ public void Dispose_RemovesDirectory() { var expectedDirectory = Path.Combine(Path.GetTempPath(), "SLVS", "VCXCD", PathHelper.PerVsInstanceFolderName.ToString()); + testSubject.Dispose(); + + fileSystemService.Directory.Received().Delete(expectedDirectory, true); + testLogger.AssertNoOutputMessages(); + } + + [TestMethod] + public void Dispose_MultipleTimes_ActsOnlyOnce() + { testSubject.Dispose(); testSubject.Dispose(); testSubject.Dispose(); - fileSystemService.Directory.Received(1).Delete(expectedDirectory, true); + fileSystemService.Directory.ReceivedWithAnyArgs(1).Delete(default, default); + } + + + [TestMethod] + public void Dispose_CatchesAndLogsException() + { + var exception = new Exception("testexc"); + fileSystemService.Directory.When(x => x.Delete(Arg.Any(), Arg.Any())).Throw(exception); + + var act = () => testSubject.Dispose(); + + act.Should().NotThrow(); + testLogger.AssertPartialOutputStringExists(exception.ToString()); } private static IFileConfig SetUpFileConfig() diff --git a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs index 71c9b1fca..df2c92c6c 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/IVCXCompilationDatabaseStorage.cs @@ -20,10 +20,10 @@ using System.ComponentModel.Composition; using System.IO; -using System.IO.Abstractions; using Newtonsoft.Json; using SonarLint.VisualStudio.CFamily.CMake; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.CFamily; using SonarLint.VisualStudio.Core.Helpers; using SonarLint.VisualStudio.Core.SystemAbstractions; @@ -31,7 +31,7 @@ namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; internal interface IVCXCompilationDatabaseStorage : IDisposable { - string CreateDatabase(IFileConfig fileConfig); + ICompilationDatabaseHandle CreateDatabase(IFileConfig fileConfig); } [Export(typeof(IVCXCompilationDatabaseStorage))] @@ -43,7 +43,7 @@ internal sealed class VCXCompilationDatabaseStorage(IFileSystemService fileSyste private bool disposed; private readonly string compilationDatabaseDirectoryPath = PathHelper.GetTempDirForTask(true, "VCXCD"); - public string CreateDatabase(IFileConfig fileConfig) + public ICompilationDatabaseHandle CreateDatabase(IFileConfig fileConfig) { ThrowIfDisposed(); threadHandling.ThrowIfOnUIThread(); @@ -56,13 +56,13 @@ public string CreateDatabase(IFileConfig fileConfig) }; var compilationDatabase = new[] { compilationDatabaseEntry }; - var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(compilationDatabaseEntry); + var compilationDatabaseFullPath = GetCompilationDatabaseFullPath(); try { fileSystemService.Directory.CreateDirectory(compilationDatabaseDirectoryPath); fileSystemService.File.WriteAllText(compilationDatabaseFullPath, JsonConvert.SerializeObject(compilationDatabase)); - return compilationDatabaseFullPath; + return new TemporaryCompilationDatabaseHandle(compilationDatabaseFullPath, fileSystemService.File, logger); } catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) { @@ -79,9 +79,9 @@ private void ThrowIfDisposed() } } - private string GetCompilationDatabaseFullPath(CompilationDatabaseEntry compilationDatabaseEntry) + private string GetCompilationDatabaseFullPath() { - var compilationDatabaseFileName = $"{Path.GetFileName(compilationDatabaseEntry.File)}.{compilationDatabaseEntry.File!.GetHashCode()}.json"; + var compilationDatabaseFileName = $"{Guid.NewGuid()}.json"; var compilationDatabaseFullPath = Path.Combine(compilationDatabaseDirectoryPath, compilationDatabaseFileName); return compilationDatabaseFullPath; } @@ -92,8 +92,15 @@ public void Dispose() { return; } - disposed = true; - fileSystemService.Directory.Delete(compilationDatabaseDirectoryPath, true); + + try + { + fileSystemService.Directory.Delete(compilationDatabaseDirectoryPath, true); + } + catch (Exception e) + { + logger.LogVerbose(e.ToString()); + } } } diff --git a/src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs b/src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs new file mode 100644 index 000000000..5853b5ac9 --- /dev/null +++ b/src/Integration.Vsix/CFamily/VcxProject/TemporaryCompilationDatabaseHandle.cs @@ -0,0 +1,49 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.IO.Abstractions; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.CFamily; + +namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; + +internal sealed class TemporaryCompilationDatabaseHandle(string filePath, IFile file, ILogger logger) : ICompilationDatabaseHandle +{ + private bool disposed; + public string FilePath { get; } = filePath ?? throw new ArgumentNullException(nameof(filePath)); + + public void Dispose() + { + if (disposed) + { + return; + } + disposed = true; + + try + { + file.Delete(FilePath); + } + catch (Exception e) + { + logger.LogVerbose(e.ToString()); + } + } +} diff --git a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs index c43104538..c8e5b7c46 100644 --- a/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs +++ b/src/Integration.Vsix/CFamily/VcxProject/VCXCompilationDatabaseProvider.cs @@ -19,11 +19,8 @@ */ using System.ComponentModel.Composition; -using EnvDTE80; -using Microsoft.VisualStudio.Shell.Interop; using SonarLint.VisualStudio.CFamily; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Infrastructure.VS; +using SonarLint.VisualStudio.Core.CFamily; namespace SonarLint.VisualStudio.Integration.Vsix.CFamily.VcxProject; @@ -35,7 +32,7 @@ internal class VCXCompilationDatabaseProvider( IFileConfigProvider fileConfigProvider) : IVCXCompilationDatabaseProvider { - public string CreateOrNull(string filePath) => + public ICompilationDatabaseHandle CreateOrNull(string filePath) => fileConfigProvider.Get(filePath, null) is {} fileConfig ? storage.CreateDatabase(fileConfig) : null; diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index 55a7d87d8..95b2d145b 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -33,30 +33,55 @@ namespace SonarLint.VisualStudio.SLCore.UnitTests.Analysis; [TestClass] public class SLCoreAnalyzerTests { - [TestMethod] - public void MefCtor_CheckIsExported() + private const string ConfigScopeId = "ConfigScopeId"; + private const string FilePath = @"C:\file\path"; + private Guid analysisId; + private ISLCoreServiceProvider slCoreServiceProvider; + private IAnalysisSLCoreService analysisService; + private IActiveConfigScopeTracker activeConfigScopeTracker; + private IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; + private ICurrentTimeProvider currentTimeProvider; + private IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; + private IAnalysisStatusNotifier notifier; + private SLCoreAnalyzer testSubject; + + [TestInitialize] + public void TestInitialize() { + analysisId = Guid.NewGuid(); + slCoreServiceProvider = Substitute.For(); + analysisService = Substitute.For(); + SetUpServiceProvider(); + activeConfigScopeTracker = Substitute.For(); + analysisStatusNotifierFactory = Substitute.For(); + notifier = Substitute.For(); + SetUpDefaultNotifier(); + currentTimeProvider = Substitute.For(); + compilationDatabaseLocator = Substitute.For(); + testSubject = new SLCoreAnalyzer(slCoreServiceProvider, + activeConfigScopeTracker, + analysisStatusNotifierFactory, + currentTimeProvider, + compilationDatabaseLocator); + + void SetUpDefaultNotifier() => analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), FilePath, analysisId).Returns(notifier); + } + + [TestMethod] + public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); - } [TestMethod] - public void MefCtor_CheckIsSingleton() - { - MefTestHelpers.CheckIsSingletonMefComponent(); - } + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); [TestMethod] - public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() - { - var testSubject = CreateTestSubject(); - + public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() => testSubject.IsAnalysisSupported([]).Should().BeTrue(); - } [DataTestMethod] [DataRow(AnalysisLanguage.Javascript)] @@ -64,33 +89,24 @@ public void IsAnalysisSupported_ReturnsTrueForNoDetectedLanguage() [DataRow(AnalysisLanguage.CFamily)] [DataRow(AnalysisLanguage.CascadingStyleSheets)] [DataRow(AnalysisLanguage.RoslynFamily)] - public void IsAnalysisSupported_ReturnsTrueForEveryDetectedLanguage(AnalysisLanguage language) - { - var testSubject = CreateTestSubject(); - + public void IsAnalysisSupported_ReturnsTrueForEveryDetectedLanguage(AnalysisLanguage language) => testSubject.IsAnalysisSupported([language]).Should().BeTrue(); - } [TestMethod] public void ExecuteAnalysis_CreatesNotifierAndStarts() { - var analysisStatusNotifierFactory = CreateDefaultAnalysisStatusNotifier(out var notifier); - var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); - - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); - analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), @"C:\file\path"); + analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), FilePath, analysisId); notifier.Received().AnalysisStarted(); } [TestMethod] public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() { - var activeConfigScopeTracker = Substitute.For(); activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, CreateDefaultAnalysisStatusNotifier(out var notifier)); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); @@ -100,11 +116,9 @@ public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() [TestMethod] public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() { - var activeConfigScopeTracker = Substitute.For(); - activeConfigScopeTracker.Current.Returns(new ConfigurationScope("someconfigscopeid", IsReadyForAnalysis: false)); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, CreateDefaultAnalysisStatusNotifier(out var notifier)); + activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: false)); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); _ = activeConfigScopeTracker.Received().Current; analysisService.ReceivedCalls().Should().BeEmpty(); @@ -114,10 +128,10 @@ public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() [TestMethod] public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() { - var slCoreServiceProvider = CreatServiceProvider(out var analysisService, false); - var testSubject = CreateTestSubject(slCoreServiceProvider, CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpServiceProvider(false); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); analysisService.ReceivedCalls().Should().BeEmpty(); @@ -128,15 +142,15 @@ public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() { var expectedTimeStamp = DateTimeOffset.Now; - var analysisId = Guid.NewGuid(); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), currentTimeProvider:CreatCurrentTimeProvider(expectedTimeStamp)); + SetUpCurrentTimeProvider(expectedTimeStamp); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", analysisId, default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.analysisId == analysisId - && a.configurationScopeId == "someconfigscopeid" - && a.filesToAnalyze.Single() == new FileUri(@"C:\file\path") + && a.configurationScopeId == ConfigScopeId + && a.filesToAnalyze.Single() == new FileUri(FilePath) && a.extraProperties.Count == 0 && a.startTime == expectedTimeStamp.ToUnixTimeMilliseconds()), Arg.Any()); @@ -149,9 +163,9 @@ public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysisService(bool? value, bool expected) { IAnalyzerOptions options = value.HasValue ? new AnalyzerOptions { IsOnOpen = value.Value } : null; - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid")); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", default, default, default, options, default); + testSubject.ExecuteAnalysis(FilePath, default, default, default, options, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.shouldFetchServerIssues == expected), @@ -163,27 +177,42 @@ public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraPropertie { const string filePath = @"C:\file\path\myclass.cpp"; const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; - var compilationDatabaseLocator = WithCompilationDatabase(filePath, compilationDatabasePath); - var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); + var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); + SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(filePath, Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null && a.extraProperties["sonar.cfamily.compile-commands"] == compilationDatabasePath), Arg.Any()); + compilationDatabaseHandle.Received().Dispose(); + } + + [TestMethod] + public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDisposed() + { + const string filePath = @"C:\file\path\myclass.cpp"; + const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; + var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); + SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); + SetUpInitializedConfigScope(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(); + + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); + + compilationDatabaseHandle.Received().Dispose(); } [TestMethod] public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExtraProperty() { const string filePath = @"C:\file\path\myclass.cpp"; - var compilationDatabaseLocator = WithCompilationDatabase(filePath, null); - var activeConfigScopeTracker = CreateInitializedConfigScope("someconfigscopeid"); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), activeConfigScopeTracker, compilationDatabaseLocator: compilationDatabaseLocator); + SetUpCompilationDatabaseLocator(filePath, null); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(filePath, Guid.NewGuid(), [AnalysisLanguage.CFamily], default, default, default); + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default, default); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => a.extraProperties != null @@ -195,10 +224,9 @@ public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_DoesNotPassExt public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() { var cancellationTokenSource = new CancellationTokenSource(); - var analysisId = Guid.NewGuid(); - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid")); + SetUpInitializedConfigScope(); - testSubject.ExecuteAnalysis(@"C:\file\path", analysisId, default, default, default, cancellationTokenSource.Token); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, cancellationTokenSource.Token); analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), cancellationTokenSource.Token); @@ -207,10 +235,10 @@ public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() [TestMethod] public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysis() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpInitializedConfigScope(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); @@ -221,10 +249,10 @@ public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysi [TestMethod] public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); - analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet{new(@"C:\file\path")}, [])); + SetUpInitializedConfigScope(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet { new(@"C:\file\path") }, [])); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } @@ -232,11 +260,11 @@ public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() [TestMethod] public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpInitializedConfigScope(); var operationCanceledException = new OperationCanceledException(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.Received().AnalysisCancelled(); } @@ -244,74 +272,36 @@ public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() [TestMethod] public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() { - var testSubject = CreateTestSubject(CreatServiceProvider(out var analysisService), CreateInitializedConfigScope("someconfigscopeid"), CreateDefaultAnalysisStatusNotifier(out var notifier)); + SetUpInitializedConfigScope(); var exception = new Exception(); analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); - testSubject.ExecuteAnalysis(@"C:\file\path", Guid.NewGuid(), default, default, default, default); + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default, default); notifier.Received().AnalysisFailed(exception); } - private static ISLCoreServiceProvider CreatServiceProvider(out IAnalysisSLCoreService analysisService, bool result = true) - { - var service = Substitute.For(); - analysisService = service; - var slCoreServiceProvider = Substitute.For(); + private void SetUpServiceProvider(bool result = true) => slCoreServiceProvider.TryGetTransientService(out Arg.Any()) .Returns(info => { - info[0] = service; + info[0] = analysisService; return result; }); - return slCoreServiceProvider; - } - private static IActiveConfigScopeTracker CreateInitializedConfigScope(string id) - { - var activeConfigScopeTracker = Substitute.For(); - activeConfigScopeTracker.Current.Returns(new ConfigurationScope(id, IsReadyForAnalysis: true)); - return activeConfigScopeTracker; - } - - private static IAnalysisStatusNotifierFactory CreateDefaultAnalysisStatusNotifier(out IAnalysisStatusNotifier notifier) - { - var analysisStatusNotifierFactory = Substitute.For(); - notifier = Substitute.For(); - analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), Arg.Any()).Returns(notifier); - return analysisStatusNotifierFactory; - } + private void SetUpInitializedConfigScope() => + activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: true)); - private static ICurrentTimeProvider CreatCurrentTimeProvider(DateTimeOffset nowTime) - { - var currentTimeProvider = Substitute.For(); + private void SetUpCurrentTimeProvider(DateTimeOffset nowTime) => currentTimeProvider.Now.Returns(nowTime); - return currentTimeProvider; - } - - private static SLCoreAnalyzer CreateTestSubject(ISLCoreServiceProvider slCoreServiceProvider = null, - IActiveConfigScopeTracker activeConfigScopeTracker = null, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, - ICurrentTimeProvider currentTimeProvider = null, - IAggregatingCompilationDatabaseProvider compilationDatabaseLocator = null) + private static ICompilationDatabaseHandle CreateCompilationDatabaseHandle(string compilationDatabasePath) { - slCoreServiceProvider ??= Substitute.For(); - activeConfigScopeTracker ??= Substitute.For(); - analysisStatusNotifierFactory ??= Substitute.For(); - currentTimeProvider ??= Substitute.For(); - compilationDatabaseLocator ??= Substitute.For(); - return new SLCoreAnalyzer(slCoreServiceProvider, - activeConfigScopeTracker, - analysisStatusNotifierFactory, - currentTimeProvider, - compilationDatabaseLocator); + var handle = Substitute.For(); + handle.FilePath.Returns(compilationDatabasePath); + return handle; } - private static IAggregatingCompilationDatabaseProvider WithCompilationDatabase(string filePath, string compilationDatabasePath) - { - var compilationDatabaseLocator = Substitute.For(); - compilationDatabaseLocator.GetOrNull(filePath).Returns(compilationDatabasePath); - return compilationDatabaseLocator; - } + private void SetUpCompilationDatabaseLocator(string filePath, ICompilationDatabaseHandle handle) => + compilationDatabaseLocator.GetOrNull(filePath).Returns(handle); } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index e8b319449..d183fb126 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -43,7 +43,8 @@ public class SLCoreAnalyzer : IAnalyzer private readonly IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; [ImportingConstructor] - public SLCoreAnalyzer(ISLCoreServiceProvider serviceProvider, + public SLCoreAnalyzer( + ISLCoreServiceProvider serviceProvider, IActiveConfigScopeTracker activeConfigScopeTracker, IAnalysisStatusNotifierFactory analysisStatusNotifierFactory, ICurrentTimeProvider currentTimeProvider, @@ -56,15 +57,17 @@ public SLCoreAnalyzer(ISLCoreServiceProvider serviceProvider, this.compilationDatabaseLocator = compilationDatabaseLocator; } - public bool IsAnalysisSupported(IEnumerable languages) - { - return true; - } + public bool IsAnalysisSupported(IEnumerable languages) => true; - public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, IIssueConsumer consumer, - IAnalyzerOptions analyzerOptions, CancellationToken cancellationToken) + public void ExecuteAnalysis( + string path, + Guid analysisId, + IEnumerable detectedLanguages, + IIssueConsumer consumer, + IAnalyzerOptions analyzerOptions, + CancellationToken cancellationToken) { - var analysisStatusNotifier = analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), path); + var analysisStatusNotifier = analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), path, analysisId); analysisStatusNotifier.AnalysisStarted(); var configurationScope = activeConfigScopeTracker.Current; @@ -80,28 +83,30 @@ public void ExecuteAnalysis(string path, Guid analysisId, IEnumerable detectedLanguages, IAnalyzerOptions analyzerOptions, IAnalysisSLCoreService analysisService, IAnalysisStatusNotifier analysisStatusNotifier, - Dictionary extraProperties, CancellationToken cancellationToken) { try { + Dictionary properties = []; + using var temporaryResourcesHandle = EnrichPropertiesForCFamily(properties, path, detectedLanguages); + var (failedAnalysisFiles, _) = await analysisService.AnalyzeFilesAndTrackAsync( new AnalyzeFilesAndTrackParams( configScopeId, analysisId, [new FileUri(path)], - extraProperties, + properties, analyzerOptions?.IsOnOpen ?? false, currentTimeProvider.Now.ToUnixTimeMilliseconds()), cancellationToken); @@ -110,7 +115,6 @@ [new FileUri(path)], { analysisStatusNotifier.AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } - } catch (OperationCanceledException) { @@ -122,20 +126,19 @@ [new FileUri(path)], } } - private Dictionary GetExtraProperties(string path, IEnumerable detectedLanguages) + private IDisposable EnrichPropertiesForCFamily(Dictionary properties, string path, IEnumerable detectedLanguages) { - Dictionary extraProperties = []; if (!IsCFamily(detectedLanguages)) { - return extraProperties; + return null; } - var compilationDatabasePath = compilationDatabaseLocator.GetOrNull(path); - if (compilationDatabasePath != null) + var compilationDatabaseHandle = compilationDatabaseLocator.GetOrNull(path); + if (compilationDatabaseHandle != null) { - extraProperties[CFamilyCompileCommandsProperty] = compilationDatabasePath; + properties[CFamilyCompileCommandsProperty] = compilationDatabaseHandle.FilePath; } - return extraProperties; + return compilationDatabaseHandle; } private static bool IsCFamily(IEnumerable detectedLanguages) => detectedLanguages != null && detectedLanguages.Contains(AnalysisLanguage.CFamily);