From dd65347e6c847b73a9858eb4de49dcaf5f25f727 Mon Sep 17 00:00:00 2001 From: dawe Date: Mon, 11 Sep 2023 19:25:57 +0200 Subject: [PATCH 01/15] - refactor to add a testing infrastructure - add a sample unit test project for the option sample --- Directory.Packages.props | 45 ++-- FSharp.Analyzers.SDK.sln | 15 ++ .../OptionAnalyzer.Test.fsproj | 32 +++ samples/OptionAnalyzer.Test/UnitTests.fs | 34 +++ .../OptionAnalyzer.Test/packages.lock.json | 249 ++++++++++++++++++ src/FSharp.Analyzers.Cli/Program.fs | 64 +---- .../FSharp.Analyzers.SDK.Client.fs | 2 +- .../FSharp.Analyzers.SDK.TestHelpers.fs | 57 ++++ .../FSharp.Analyzers.SDK.fs | 79 +++++- .../FSharp.Analyzers.SDK.fsproj | 1 + 10 files changed, 493 insertions(+), 85 deletions(-) create mode 100644 samples/OptionAnalyzer.Test/OptionAnalyzer.Test.fsproj create mode 100644 samples/OptionAnalyzer.Test/UnitTests.fs create mode 100644 samples/OptionAnalyzer.Test/packages.lock.json create mode 100644 src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs diff --git a/Directory.Packages.props b/Directory.Packages.props index d10cb5f..8f4059f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,22 +1,27 @@ - - true - 17.2.0 - - - - - - - - - - - - - - - - - + + true + 17.2.0 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FSharp.Analyzers.SDK.sln b/FSharp.Analyzers.SDK.sln index 6169984..c0f192c 100644 --- a/FSharp.Analyzers.SDK.sln +++ b/FSharp.Analyzers.SDK.sln @@ -34,6 +34,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\ci.yml = .github\workflows\ci.yml EndProjectSection EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "OptionAnalyzer.Test", "samples\OptionAnalyzer.Test\OptionAnalyzer.Test.fsproj", "{9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -83,6 +85,18 @@ Global {E4FAFA1A-4E26-4553-81F2-844C94B85349}.Release|x64.Build.0 = Release|Any CPU {E4FAFA1A-4E26-4553-81F2-844C94B85349}.Release|x86.ActiveCfg = Release|Any CPU {E4FAFA1A-4E26-4553-81F2-844C94B85349}.Release|x86.Build.0 = Release|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Debug|x64.Build.0 = Debug|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Debug|x86.Build.0 = Debug|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Release|Any CPU.Build.0 = Release|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Release|x64.ActiveCfg = Release|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Release|x64.Build.0 = Release|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Release|x86.ActiveCfg = Release|Any CPU + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {C1D38B7A-0193-46AA-B033-ADBBF642AAA0} = {95A9FA19-723D-4D2C-A936-F0B45656B0D6} @@ -90,5 +104,6 @@ Global {E4FAFA1A-4E26-4553-81F2-844C94B85349} = {0FE81935-26A8-45E1-A62E-5148C73BA6A2} {7A9A1C69-ADF2-421C-90F8-AB3304D6E197} = {937D2F4A-7EF4-469F-8DDA-3D75F3D32C69} {452A16E1-35C3-4392-B969-548E701748D5} = {7A9A1C69-ADF2-421C-90F8-AB3304D6E197} + {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01} = {0FE81935-26A8-45E1-A62E-5148C73BA6A2} EndGlobalSection EndGlobal diff --git a/samples/OptionAnalyzer.Test/OptionAnalyzer.Test.fsproj b/samples/OptionAnalyzer.Test/OptionAnalyzer.Test.fsproj new file mode 100644 index 0000000..dc26a9f --- /dev/null +++ b/samples/OptionAnalyzer.Test/OptionAnalyzer.Test.fsproj @@ -0,0 +1,32 @@ + + + + net6.0 + LatestMajor + + false + false + true + Library + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs new file mode 100644 index 0000000..30b3d6c --- /dev/null +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -0,0 +1,34 @@ +module OptionAnalyzer.Test + +#nowarn "57" + +open NUnit.Framework +open FSharp.Compiler.Text +open FSharp.Analyzers.SDK + +[] +let Setup () = () + +[] +let ``warning is emitted`` () = + + let source = + """ +module M + +let notUsed() = + let option : Option = None + option.Value +""" + + let expectedMsg = + { Code = "OV001" + Fixes = [] + Message = "Option.Value shouldn't be used" + Range = Range.mkRange "A.fs" (Position.mkPos 6 4) (Position.mkPos 6 16) + Severity = Severity.Warning + Type = "Option.Value analyzer" } + + let msgs = TestHelpers.getAnalyzerMsgs source + Assert.IsNotEmpty msgs + Assert.IsTrue(msgs |> Array.contains expectedMsg) diff --git a/samples/OptionAnalyzer.Test/packages.lock.json b/samples/OptionAnalyzer.Test/packages.lock.json new file mode 100644 index 0000000..2b0d951 --- /dev/null +++ b/samples/OptionAnalyzer.Test/packages.lock.json @@ -0,0 +1,249 @@ +{ + "version": 2, + "dependencies": { + "net6.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[3.2.0, )", + "resolved": "3.2.0", + "contentHash": "xjY8xBigSeWIYs4I7DgUHqSNoGqnHi7Fv7/7RZD02rvZyG3hlsjnQKiVKVWKgr9kRKgmV+dEfu8KScvysiC0Wg==" + }, + "DotNet.ReproducibleBuilds": { + "type": "Direct", + "requested": "[1.1.1, )", + "resolved": "1.1.1", + "contentHash": "+H2t/t34h6mhEoUvHi8yGXyuZ2GjSovcGYehJrS2MDm2XgmPfZL2Sdxg+uL2lKgZ4M6tTwKHIlxOob2bgh0NRQ==", + "dependencies": { + "Microsoft.SourceLink.AzureRepos.Git": "1.1.1", + "Microsoft.SourceLink.Bitbucket.Git": "1.1.1", + "Microsoft.SourceLink.GitHub": "1.1.1", + "Microsoft.SourceLink.GitLab": "1.1.1" + } + }, + "FSharp.Compiler.Service": { + "type": "Direct", + "requested": "[43.7.400, )", + "resolved": "43.7.400", + "contentHash": "lg3iNkZMvuj9hOxSJ0TM1/ntcFDPRQgr1+e4bLj/avNX3FkpUtPrzCRNRpy0VP8UCNa362al5NGkVWiihxjeUQ==", + "dependencies": { + "FSharp.Core": "[7.0.400]", + "System.Buffers": "4.5.1", + "System.Collections.Immutable": "7.0.0", + "System.Diagnostics.DiagnosticSource": "7.0.2", + "System.Memory": "4.5.5", + "System.Reflection.Emit": "4.7.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "FSharp.Core": { + "type": "Direct", + "requested": "[7.0.400, )", + "resolved": "7.0.400", + "contentHash": "kJQ7TBQqd1d2VoODSgwFSAaApaBN0Fuu8mZt2XExmd3UzUxLjUsMn5Y5XhpsUWdnxOL8amp7VFg7cwhPllR4Qw==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.6.0, )", + "resolved": "17.6.0", + "contentHash": "tHyg4C6c89QvLv6Utz3xKlba4EeoyJyIz59Q1NrjRENV7gfGnSE6I+sYPIbVOzQttoo2zpHDgOK/p6Hw2OlD7A==", + "dependencies": { + "Microsoft.CodeCoverage": "17.6.0", + "Microsoft.TestPlatform.TestHost": "17.6.0" + } + }, + "NUnit": { + "type": "Direct", + "requested": "[3.13.3, )", + "resolved": "3.13.3", + "contentHash": "KNPDpls6EfHwC3+nnA67fh5wpxeLb3VLFAfLxrug6JMYDLHH6InaQIWR7Sc3y75d/9IKzMksH/gi08W7XWbmnQ==", + "dependencies": { + "NETStandard.Library": "2.0.0" + } + }, + "NUnit.Analyzers": { + "type": "Direct", + "requested": "[3.6.1, )", + "resolved": "3.6.1", + "contentHash": "RKP9tpKfl3DmRgUDGgh3XM3XzeLMrCXXMZm6vm1nMsObZ6vtQL1L9NrK7+oZh1jWearvNsbMis2+AIOY3NFmow==" + }, + "NUnit3TestAdapter": { + "type": "Direct", + "requested": "[4.4.2, )", + "resolved": "4.4.2", + "contentHash": "vA/iHYcR+LYw+pRWQugckC/zW2fXHaqMr2uA82NOBt8v4YK4wMJrQ7QC8XLc7PjetEZ96cPbBTWsDDtmQiRZTA==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.6.0", + "contentHash": "5v2GwzpR7JEuQUzupjx3zLwn2FutADW/weLzLt726DR3WXxsM+ICPoJG6pxuKFsumtZp890UrVuudTUhsE8Qyg==" + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.SourceLink.AzureRepos.Git": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "qB5urvw9LO2bG3eVAkuL+2ughxz2rR7aYgm2iyrB8Rlk9cp2ndvGRCvehk3rNIhRuNtQaeKwctOl1KvWiklv5w==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "Microsoft.SourceLink.Bitbucket.Git": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "cDzxXwlyWpLWaH0em4Idj0H3AmVo3L/6xRXKssYemx+7W52iNskj/SQ4FOmfCb8YQt39otTDNMveCZzYtMoucQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "Microsoft.SourceLink.GitLab": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "tvsg47DDLqqedlPeYVE2lmiTpND8F0hkrealQ5hYltSmvruy/Gr5nHAKSsjyw5L3NeM/HLMI5ORv7on/M4qyZw==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.6.0", + "contentHash": "AA/rrf5zwC5/OBLEOajkhjbVTM3SvxRXy8kcQ8e4mJKojbyZvqqhpfNg362N9vXU94DLg9NUTFOAnoYVT0pTJw==", + "dependencies": { + "NuGet.Frameworks": "5.11.0", + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.6.0", + "contentHash": "7YdgUcIeCPVKLC7n7LNKDiEHWc7z3brkkYPdUbDnFsvf6WvY9UfzS0VSUJ8P2NgN0CDSD223GCJFSjSBLZRqOQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.6.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "NuGet.Frameworks": { + "type": "Transitive", + "resolved": "5.11.0", + "contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "7.0.2", + "contentHash": "hYr3I9N9811e0Bjf2WNwAGGyTuAFbbTgX1RPLt/3Wbm68x3IGcX5Cl75CMmgT6WlNwLQ2tCCWfqYPpypjaf2xA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "fsharp.analyzers.sdk": { + "type": "Project", + "dependencies": { + "FSharp.Compiler.Service": "[43.7.400, )", + "FSharp.Core": "[7.0.400, )", + "McMaster.NETCore.Plugins": "[1.4.0, )" + } + }, + "optionanalyzer": { + "type": "Project", + "dependencies": { + "FSharp.Analyzers.SDK": "[1.0.0, )", + "FSharp.Core": "[7.0.400, )" + } + }, + "McMaster.NETCore.Plugins": { + "type": "CentralTransitive", + "requested": "[1.4.0, )", + "resolved": "1.4.0", + "contentHash": "UKw5Z2/QHhkR7kiAJmqdCwVDMQV0lwsfj10+FG676r8DsJWIpxtachtEjE0qBs9WoK5GUQIqxgyFeYUSwuPszg==", + "dependencies": { + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Microsoft.Extensions.DependencyModel": "5.0.0" + } + } + } + } +} \ No newline at end of file diff --git a/src/FSharp.Analyzers.Cli/Program.fs b/src/FSharp.Analyzers.Cli/Program.fs index c9288fd..a97c1f4 100644 --- a/src/FSharp.Analyzers.Cli/Program.fs +++ b/src/FSharp.Analyzers.Cli/Program.fs @@ -20,12 +20,7 @@ with let mutable verbose = false - -let createFCS () = - let checker = FSharpChecker.Create(projectCacheSize = 200, keepAllBackgroundResolutions = true, keepAssemblyContents = true) - // checker.ImplicitlyStartBackgroundWork <- true - checker -let fcs = createFCS () +let fcs = Utils.createFCS None let parser = ArgumentParser.Create(errorHandler = ProcessExiter ()) @@ -63,61 +58,6 @@ let loadProject toolsPath projPath = return fcsPo } |> Async.RunSynchronously -let typeCheckFile (file,opts) = - let text = File.ReadAllText file - let st = SourceText.ofString text - let (parseRes, checkAnswer) = fcs.ParseAndCheckFileInProject(file,0,st,opts) |> Async.RunSynchronously //ToDo: Validate if 0 is ok - match checkAnswer with - | FSharpCheckFileAnswer.Aborted -> - printError "Checking of file %s aborted" file - None - | FSharpCheckFileAnswer.Succeeded result -> - Some (file, text, parseRes, result) - -let entityCache = EntityCache() - -let getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list = - try - let res = [ - yield! AssemblyContent.GetAssemblySignatureContent AssemblyContentType.Full checkResults.PartialAssemblySignature - let ctx = checkResults.ProjectContext - let assembliesByFileName = - ctx.GetReferencedAssemblies() - |> Seq.groupBy (fun asm -> asm.FileName) - |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) - |> Seq.toList - |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to - // get Content.Entities from it. - - for fileName, signatures in assembliesByFileName do - let contentType = if publicOnly then AssemblyContentType.Public else AssemblyContentType.Full - let content = AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures - yield! content - ] - res - with - | _ -> [] - -let createContext - (checkProjectResults: FSharpCheckProjectResults, allSymbolUses: FSharpSymbolUse array) - (file, text: string, p: FSharpParseFileResults, c: FSharpCheckFileResults) = - match c.ImplementationFile with - | Some tast -> - let context : Context = { - ParseFileResults = p - CheckFileResults = c - CheckProjectResults = checkProjectResults - FileName = file - Content = text.Split([|'\n'|]) - TypedTree = tast - GetAllEntities = getAllEntities c - AllSymbolUses = allSymbolUses - SymbolUsesOfFile = allSymbolUses |> Array.filter (fun s -> s.FileName = file) - } - Some context - | _ -> None - - let runProject toolsPath proj (globs: Glob list) = let path = Path.Combine(Environment.CurrentDirectory, proj) @@ -142,7 +82,7 @@ let runProject toolsPath proj (globs: Glob list) = false | None -> true ) - |> Array.choose (fun f -> typeCheckFile (f, opts) |> Option.map (createContext (checkProjectResults, allSymbolUses))) + |> Array.choose (fun f -> Utils.typeCheckFile fcs (Utils.SourceOfSource.Path f, f, opts) |> Option.map (Utils.createContext (checkProjectResults, allSymbolUses))) |> Array.collect (fun ctx -> match ctx with | Some c -> diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs index 2f89344..251891c 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs @@ -75,7 +75,7 @@ module Client = if Directory.Exists dir then let analyzerAssemblies = Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories) - |> Array.filter(fun a -> not (a.EndsWith("FSharp.Analyzers.SDK.dll"))) + |> Array.filter(fun a -> not (a.EndsWith("FSharp.Analyzers.SDK.dll") || a.EndsWith("Test.dll"))) |> Array.choose (fun analyzerDll -> try // loads an assembly and all of it's dependencies diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs new file mode 100644 index 0000000..7e48443 --- /dev/null +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -0,0 +1,57 @@ +module FSharp.Analyzers.SDK.TestHelpers + +#nowarn "57" + +open System +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Text + +let getAnalyzerMsgs source = + let loadProject (fcs: FSharpChecker) fileName sourceText = + fcs.GetProjectOptionsFromScript(fileName, sourceText) + |> Async.RunSynchronously + |> fst + + let fileName = "A.fs" + let files = Map.ofArray [| (fileName, SourceText.ofString source) |] + + let documentSource fileName = + Map.tryFind fileName files |> async.Return + + let fcs = Utils.createFCS (Some documentSource) + let printError (s: string) = Console.WriteLine(s) + let pathToAnalyzerDlls = System.IO.Path.GetFullPath(".") + + let foundDlls, registeredAnalyzers = + Client.loadAnalyzers printError pathToAnalyzerDlls + + if foundDlls = 0 then + failwith $"no Dlls found in {pathToAnalyzerDlls}" + + if registeredAnalyzers = 0 then + failwith $"no Analyzers found in {pathToAnalyzerDlls}" + + let opts = loadProject fcs fileName files[fileName] + + let opts = + { opts with + SourceFiles = [| fileName |] } + + fcs.NotifyFileChanged(fileName, opts) |> Async.RunSynchronously // workaround for https://github.com/dotnet/fsharp/issues/15960 + let checkProjectResults = fcs.ParseAndCheckProject(opts) |> Async.RunSynchronously + let allSymbolUses = checkProjectResults.GetAllUsesOfAllSymbols() + + if Array.isEmpty allSymbolUses then + failwith "no symboluses" + + match Utils.typeCheckFile fcs (Utils.SourceOfSource.DiscreteSource source, fileName, opts) with + | Some(file, text, parseRes, result) -> + let ctx = + Utils.createContext (checkProjectResults, allSymbolUses) (file, text, parseRes, result) + + match ctx with + | Some c -> + let msgs = Client.runAnalyzers c + msgs + | None -> failwith "Context creation failed" + | None -> failwith "typechecking file failed" diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs index c90d595..47a8655 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs @@ -1,9 +1,11 @@ namespace FSharp.Analyzers.SDK +#nowarn "57" + open System open FSharp.Compiler open FSharp.Compiler.CodeAnalysis -open FSharp.Compiler.Syntax +open FSharp.Compiler.Text open FSharp.Compiler.Symbols open FSharp.Compiler.EditorServices open System.Runtime.InteropServices @@ -44,4 +46,77 @@ type Message = Range: Text.Range Fixes: Fix list } -type Analyzer = Context -> Message list \ No newline at end of file +type Analyzer = Context -> Message list + +module Utils = + + let private entityCache = EntityCache() + + let private getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list = + try + let res = [ + yield! AssemblyContent.GetAssemblySignatureContent AssemblyContentType.Full checkResults.PartialAssemblySignature + let ctx = checkResults.ProjectContext + let assembliesByFileName = + ctx.GetReferencedAssemblies() + |> Seq.groupBy (fun asm -> asm.FileName) + |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) + |> Seq.toList + |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to + // get Content.Entities from it. + + for fileName, signatures in assembliesByFileName do + let contentType = if publicOnly then AssemblyContentType.Public else AssemblyContentType.Full + let content = AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures + yield! content + ] + res + with + | _ -> [] + + let createContext + (checkProjectResults: FSharpCheckProjectResults, allSymbolUses: FSharpSymbolUse array) + (file, text: string, p: FSharpParseFileResults, c: FSharpCheckFileResults) = + match c.ImplementationFile with + | Some tast -> + let context : Context = { + ParseFileResults = p + CheckFileResults = c + CheckProjectResults = checkProjectResults + FileName = file + Content = text.Split([|'\n'|]) + TypedTree = tast + GetAllEntities = getAllEntities c + AllSymbolUses = allSymbolUses + SymbolUsesOfFile = allSymbolUses |> Array.filter (fun s -> s.FileName = file) + } + Some context + | _ -> None + + let createFCS documentSource = + let ds = + documentSource + |> Option.map DocumentSource.Custom + |> Option.defaultValue DocumentSource.FileSystem + FSharpChecker.Create(projectCacheSize = 200, keepAllBackgroundResolutions = true, keepAssemblyContents = true, documentSource = ds) + + [] + type SourceOfSource = + | Path of string + | DiscreteSource of String + + let typeCheckFile (fcs: FSharpChecker) (source, file, opts) = + let text = + match source with + | SourceOfSource.Path path -> + let text = System.IO.File.ReadAllText path + text + | SourceOfSource.DiscreteSource s -> s + let st = SourceText.ofString text + let parseRes, checkAnswer = fcs.ParseAndCheckFileInProject(file,0,st,opts) |> Async.RunSynchronously + match checkAnswer with + | FSharpCheckFileAnswer.Aborted -> + printfn "Checking of file %s aborted" file + None + | FSharpCheckFileAnswer.Succeeded result -> + Some (file, text, parseRes, result) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj index 3ab743f..a4c642a 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj @@ -14,6 +14,7 @@ + From fd9e851c7720882a76e8680f8add3fbd4d8bf6f4 Mon Sep 17 00:00:00 2001 From: dawe Date: Mon, 11 Sep 2023 19:45:40 +0200 Subject: [PATCH 02/15] format --- samples/OptionAnalyzer.Test/UnitTests.fs | 14 +-- src/FSharp.Analyzers.Cli/Program.fs | 12 ++- .../FSharp.Analyzers.SDK.Client.fs | 2 +- .../FSharp.Analyzers.SDK.TestHelpers.fs | 3 +- .../FSharp.Analyzers.SDK.fs | 89 ++++++++++++------- 5 files changed, 73 insertions(+), 47 deletions(-) diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index 30b3d6c..e5b01d8 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -22,12 +22,14 @@ let notUsed() = """ let expectedMsg = - { Code = "OV001" - Fixes = [] - Message = "Option.Value shouldn't be used" - Range = Range.mkRange "A.fs" (Position.mkPos 6 4) (Position.mkPos 6 16) - Severity = Severity.Warning - Type = "Option.Value analyzer" } + { + Code = "OV001" + Fixes = [] + Message = "Option.Value shouldn't be used" + Range = Range.mkRange "A.fs" (Position.mkPos 6 4) (Position.mkPos 6 16) + Severity = Severity.Warning + Type = "Option.Value analyzer" + } let msgs = TestHelpers.getAnalyzerMsgs source Assert.IsNotEmpty msgs diff --git a/src/FSharp.Analyzers.Cli/Program.fs b/src/FSharp.Analyzers.Cli/Program.fs index f770d45..4ef14bf 100644 --- a/src/FSharp.Analyzers.Cli/Program.fs +++ b/src/FSharp.Analyzers.Cli/Program.fs @@ -59,15 +59,13 @@ let loadProject toolsPath projPath = } |> Async.RunSynchronously -let runProject toolsPath proj (globs: Glob list) = - let path = - Path.Combine(Environment.CurrentDirectory, proj) - |> Path.GetFullPath +let runProject toolsPath proj (globs: Glob list) = + let path = Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath let opts = loadProject toolsPath path - + let checkProjectResults = fcs.ParseAndCheckProject(opts) |> Async.RunSynchronously let allSymbolUses = checkProjectResults.GetAllUsesOfAllSymbols() - + opts.SourceFiles |> Array.filter (fun file -> match Path.GetExtension(file).ToLowerInvariant() with @@ -84,7 +82,7 @@ let runProject toolsPath proj (globs: Glob list) = | None -> true ) |> Array.choose (fun f -> - Utils.typeCheckFile fcs (Utils.SourceOfSource.Path f, f, opts) + Utils.typeCheckFile fcs (Utils.SourceOfSource.Path f, f, opts) |> Option.map (Utils.createContext (checkProjectResults, allSymbolUses)) ) |> Array.collect (fun ctx -> diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs index ad75cac..b00e211 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs @@ -85,7 +85,7 @@ module Client = if Directory.Exists dir then let analyzerAssemblies = Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories) - |> Array.filter(fun a -> not (a.EndsWith("FSharp.Analyzers.SDK.dll") || a.EndsWith("Test.dll"))) + |> Array.filter (fun a -> not (a.EndsWith("FSharp.Analyzers.SDK.dll") || a.EndsWith("Test.dll"))) |> Array.choose (fun analyzerDll -> try // loads an assembly and all of it's dependencies diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index 7e48443..e3c9e2e 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -35,7 +35,8 @@ let getAnalyzerMsgs source = let opts = { opts with - SourceFiles = [| fileName |] } + SourceFiles = [| fileName |] + } fcs.NotifyFileChanged(fileName, opts) |> Async.RunSynchronously // workaround for https://github.com/dotnet/fsharp/issues/15960 let checkProjectResults = fcs.ParseAndCheckProject(opts) |> Async.RunSynchronously diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs index fcc7c0f..556b531 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs @@ -55,47 +55,63 @@ type Message = type Analyzer = Context -> Message list module Utils = - + let private entityCache = EntityCache() let private getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list = - try - let res = [ - yield! AssemblyContent.GetAssemblySignatureContent AssemblyContentType.Full checkResults.PartialAssemblySignature - let ctx = checkResults.ProjectContext - let assembliesByFileName = + try + let res = + [ + yield! + AssemblyContent.GetAssemblySignatureContent + AssemblyContentType.Full + checkResults.PartialAssemblySignature + let ctx = checkResults.ProjectContext + + let assembliesByFileName = ctx.GetReferencedAssemblies() |> Seq.groupBy (fun asm -> asm.FileName) |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) |> Seq.toList |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to - // get Content.Entities from it. + // get Content.Entities from it. + + for fileName, signatures in assembliesByFileName do + let contentType = + if publicOnly then + AssemblyContentType.Public + else + AssemblyContentType.Full + + let content = + AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures - for fileName, signatures in assembliesByFileName do - let contentType = if publicOnly then AssemblyContentType.Public else AssemblyContentType.Full - let content = AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures yield! content - ] - res - with - | _ -> [] + ] + + res + with _ -> + [] let createContext (checkProjectResults: FSharpCheckProjectResults, allSymbolUses: FSharpSymbolUse array) - (file, text: string, p: FSharpParseFileResults, c: FSharpCheckFileResults) = + (file, text: string, p: FSharpParseFileResults, c: FSharpCheckFileResults) + = match c.ImplementationFile with | Some tast -> - let context : Context = { - ParseFileResults = p - CheckFileResults = c - CheckProjectResults = checkProjectResults - FileName = file - Content = text.Split([|'\n'|]) - TypedTree = tast - GetAllEntities = getAllEntities c - AllSymbolUses = allSymbolUses - SymbolUsesOfFile = allSymbolUses |> Array.filter (fun s -> s.FileName = file) - } + let context: Context = + { + ParseFileResults = p + CheckFileResults = c + CheckProjectResults = checkProjectResults + FileName = file + Content = text.Split([| '\n' |]) + TypedTree = tast + GetAllEntities = getAllEntities c + AllSymbolUses = allSymbolUses + SymbolUsesOfFile = allSymbolUses |> Array.filter (fun s -> s.FileName = file) + } + Some context | _ -> None @@ -104,13 +120,19 @@ module Utils = documentSource |> Option.map DocumentSource.Custom |> Option.defaultValue DocumentSource.FileSystem - FSharpChecker.Create(projectCacheSize = 200, keepAllBackgroundResolutions = true, keepAssemblyContents = true, documentSource = ds) - + + FSharpChecker.Create( + projectCacheSize = 200, + keepAllBackgroundResolutions = true, + keepAssemblyContents = true, + documentSource = ds + ) + [] type SourceOfSource = | Path of string | DiscreteSource of String - + let typeCheckFile (fcs: FSharpChecker) (source, file, opts) = let text = match source with @@ -118,11 +140,14 @@ module Utils = let text = System.IO.File.ReadAllText path text | SourceOfSource.DiscreteSource s -> s + let st = SourceText.ofString text - let parseRes, checkAnswer = fcs.ParseAndCheckFileInProject(file,0,st,opts) |> Async.RunSynchronously + + let parseRes, checkAnswer = + fcs.ParseAndCheckFileInProject(file, 0, st, opts) |> Async.RunSynchronously + match checkAnswer with | FSharpCheckFileAnswer.Aborted -> printfn "Checking of file %s aborted" file None - | FSharpCheckFileAnswer.Succeeded result -> - Some (file, text, parseRes, result) + | FSharpCheckFileAnswer.Succeeded result -> Some(file, text, parseRes, result) From cf0cef030066152ef8a7076fa8aeb88330330b28 Mon Sep 17 00:00:00 2001 From: dawe Date: Tue, 12 Sep 2023 18:29:58 +0200 Subject: [PATCH 03/15] - refactor creation of FSharpProjectOptions to work on !Windows OS --- .fantomasignore | 1 + Directory.Packages.props | 2 + samples/OptionAnalyzer.Test/UnitTests.fs | 46 ++- .../OptionAnalyzer.Test/packages.lock.json | 291 ++++++++++++++++- samples/OptionAnalyzer/packages.lock.json | 294 ++++++++++++++++++ src/FSharp.Analyzers.Cli/packages.lock.json | 18 ++ .../FSharp.Analyzers.SDK.TestHelpers.fs | 153 ++++++++- .../FSharp.Analyzers.SDK.TestHelpers.fsi | 19 ++ .../FSharp.Analyzers.SDK.fs | 2 +- .../FSharp.Analyzers.SDK.fsproj | 3 + src/FSharp.Analyzers.SDK/packages.lock.json | 292 +++++++++++++++++ 11 files changed, 1099 insertions(+), 22 deletions(-) create mode 100644 .fantomasignore create mode 100644 src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi diff --git a/.fantomasignore b/.fantomasignore new file mode 100644 index 0000000..01309d9 --- /dev/null +++ b/.fantomasignore @@ -0,0 +1 @@ +**/bin/**/*.fs diff --git a/Directory.Packages.props b/Directory.Packages.props index 8f4059f..0cf9f42 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,6 +4,7 @@ 17.2.0 + @@ -19,6 +20,7 @@ + diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index e5b01d8..647773b 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -1,21 +1,51 @@ module OptionAnalyzer.Test -#nowarn "57" - +open FSharp.Compiler.CodeAnalysis open NUnit.Framework open FSharp.Compiler.Text open FSharp.Analyzers.SDK +open FSharp.Analyzers.SDK.TestHelpers + +let mutable projectOptions: FSharpProjectOptions = FSharpProjectOptions.zero [] -let Setup () = () +let Setup () = + projectOptions <- + mkOptionsFromProject + DotNetVersion.Seven + (Some + { + Name = "Newtonsoft.Json" + Version = "13.0.3" + }) + +[] +let ``warnings are emitted`` () = + + let source = + """ +module M + +let notUsed() = + let option : Option = None + option.Value +""" + + let ctx = getContext projectOptions source + let msgs = optionValueAnalyzer ctx + Assert.IsNotEmpty msgs [] -let ``warning is emitted`` () = +let ``expected warning is emitted`` () = let source = """ module M +open Newtonsoft.Json + +let json = JsonConvert.SerializeObject([1;2;3]) + let notUsed() = let option : Option = None option.Value @@ -26,11 +56,11 @@ let notUsed() = Code = "OV001" Fixes = [] Message = "Option.Value shouldn't be used" - Range = Range.mkRange "A.fs" (Position.mkPos 6 4) (Position.mkPos 6 16) + Range = Range.mkRange "A.fs" (Position.mkPos 10 4) (Position.mkPos 10 16) Severity = Severity.Warning Type = "Option.Value analyzer" } - let msgs = TestHelpers.getAnalyzerMsgs source - Assert.IsNotEmpty msgs - Assert.IsTrue(msgs |> Array.contains expectedMsg) + let ctx = getContext projectOptions source + let msgs = optionValueAnalyzer ctx + Assert.IsTrue(msgs |> List.contains expectedMsg) diff --git a/samples/OptionAnalyzer.Test/packages.lock.json b/samples/OptionAnalyzer.Test/packages.lock.json index 2b0d951..5fb36c3 100644 --- a/samples/OptionAnalyzer.Test/packages.lock.json +++ b/samples/OptionAnalyzer.Test/packages.lock.json @@ -93,10 +93,24 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.NET.StringTools": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "ZYVcoDM0LnSyT5nWoRGfShYdOecCw2sOXWwP6j1Z0u48Xq3+BVvZ+EiPCX9/8Gz439giW+O1H1kWF9Eb/w6rVg==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" + }, + "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.SourceLink.AzureRepos.Git": { "type": "Transitive", @@ -157,6 +171,29 @@ "Newtonsoft.Json": "13.0.1" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Lw1/VwLH1yxz6SfFEjVRCN0pnflLEsWgnV4qsdJ512/HhTwnKXUG+zDQ4yTO3K/EJQemGoNaBHX5InISNKTzUQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0" + } + }, "NETStandard.Library": { "type": "Transitive", "resolved": "2.0.0", @@ -180,6 +217,16 @@ "resolved": "4.5.1", "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "7.0.0", @@ -188,6 +235,15 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "/anOTeSZCNNI2zDilogWrZ8pNqCmYbzGNexUnNhjW8k0sHqEZ2nHJBp147jBV3hGYswu5lINpNg1vxR7bnqvVA==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "4.7.0", + "System.Security.Permissions": "4.7.0" + } + }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "7.0.2", @@ -196,11 +252,54 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.SystemEvents": "4.7.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, "System.Memory": { "type": "Transitive", "resolved": "4.5.5", "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.7.0", @@ -214,16 +313,166 @@ "System.Collections.Immutable": "7.0.0" } }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "dkOV6YYVBnYRa15/yv004eCGRBVADXw8qRbbNiCn/XpdJSUXkkUeIvdvFHkvnko4CdKMqG8yRHC4ox83LSlMsQ==", + "dependencies": { + "System.Security.AccessControl": "4.7.0", + "System.Windows.Extensions": "4.7.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", + "dependencies": { + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "CeWTdRNfRaSh0pm2gDTJFwVaXfTq6Xwv/sA887iwPTneW7oMtMlpvDIO+U60+3GWTB7Aom6oQwv5VZVUhQRdPQ==", + "dependencies": { + "System.Drawing.Common": "4.7.0" + } + }, "fsharp.analyzers.sdk": { "type": "Project", "dependencies": { + "CliWrap": "[3.6.4, )", "FSharp.Compiler.Service": "[43.7.400, )", "FSharp.Core": "[7.0.400, )", + "MSBuild.StructuredLogger": "[2.1.815, )", "McMaster.NETCore.Plugins": "[1.4.0, )" } }, @@ -234,6 +483,12 @@ "FSharp.Core": "[7.0.400, )" } }, + "CliWrap": { + "type": "CentralTransitive", + "requested": "[3.6.4, )", + "resolved": "3.6.4", + "contentHash": "KVGVZlR0GWgN3Xr88oZMSzYu38TXIogwLz588e6wku3mIfg6lPchxpYWtZSZfurpTY63ANF61xWp8EZF3jkN4g==" + }, "McMaster.NETCore.Plugins": { "type": "CentralTransitive", "requested": "[1.4.0, )", @@ -243,6 +498,40 @@ "Microsoft.DotNet.PlatformAbstractions": "3.1.6", "Microsoft.Extensions.DependencyModel": "5.0.0" } + }, + "Microsoft.Build.Framework": { + "type": "CentralTransitive", + "requested": "[17.2.0, )", + "resolved": "16.10.0", + "contentHash": "uD2GUw3AYlFSpU42c/80DouuJL6w1Kb06q4FEjQhW/9wjhBwukgx13T5MPIpSvQ8ssahKINanHfMUL89EVQHgQ==", + "dependencies": { + "System.Security.Permissions": "4.7.0" + } + }, + "Microsoft.Build.Utilities.Core": { + "type": "CentralTransitive", + "requested": "[17.2.0, )", + "resolved": "16.10.0", + "contentHash": "R8eATgdaGCfdepd67LMe1qhJz6iQOTuI9gVoOqXrHwhc77sBDqG0XD9zKvrgOqfS6NJ03KKTAhbbXnLgD5fKCA==", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.NET.StringTools": "1.0.0", + "Microsoft.Win32.Registry": "4.3.0", + "System.Collections.Immutable": "5.0.0", + "System.Configuration.ConfigurationManager": "4.7.0", + "System.Security.Permissions": "4.7.0", + "System.Text.Encoding.CodePages": "4.0.1" + } + }, + "MSBuild.StructuredLogger": { + "type": "CentralTransitive", + "requested": "[2.1.815, )", + "resolved": "2.1.815", + "contentHash": "5UfWYgsWBGI3w0npjYLeXPv8TUcNmGB/QBhoYLDeYTosuvmG6pz6HPb+xPg/Boi1bmw2Bgnjo+XopcwzCbZAqg==", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.Build.Utilities.Core": "16.10.0" + } } } } diff --git a/samples/OptionAnalyzer/packages.lock.json b/samples/OptionAnalyzer/packages.lock.json index dcf852a..4c61ff4 100644 --- a/samples/OptionAnalyzer/packages.lock.json +++ b/samples/OptionAnalyzer/packages.lock.json @@ -35,6 +35,25 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.NET.StringTools": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "ZYVcoDM0LnSyT5nWoRGfShYdOecCw2sOXWwP6j1Z0u48Xq3+BVvZ+EiPCX9/8Gz439giW+O1H1kWF9Eb/w6rVg==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, "Microsoft.SourceLink.AzureRepos.Git": { "type": "Transitive", "resolved": "1.1.1", @@ -76,11 +95,44 @@ "Microsoft.SourceLink.Common": "1.1.1" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Lw1/VwLH1yxz6SfFEjVRCN0pnflLEsWgnV4qsdJ512/HhTwnKXUG+zDQ4yTO3K/EJQemGoNaBHX5InISNKTzUQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "7.0.0", @@ -89,6 +141,15 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "/anOTeSZCNNI2zDilogWrZ8pNqCmYbzGNexUnNhjW8k0sHqEZ2nHJBp147jBV3hGYswu5lINpNg1vxR7bnqvVA==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "4.7.0", + "System.Security.Permissions": "4.7.0" + } + }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "7.0.2", @@ -97,11 +158,54 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.SystemEvents": "4.7.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, "System.Memory": { "type": "Transitive", "resolved": "4.5.5", "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.7.0", @@ -115,19 +219,175 @@ "System.Collections.Immutable": "7.0.0" } }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "dkOV6YYVBnYRa15/yv004eCGRBVADXw8qRbbNiCn/XpdJSUXkkUeIvdvFHkvnko4CdKMqG8yRHC4ox83LSlMsQ==", + "dependencies": { + "System.Security.AccessControl": "4.7.0", + "System.Windows.Extensions": "4.7.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", + "dependencies": { + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "CeWTdRNfRaSh0pm2gDTJFwVaXfTq6Xwv/sA887iwPTneW7oMtMlpvDIO+U60+3GWTB7Aom6oQwv5VZVUhQRdPQ==", + "dependencies": { + "System.Drawing.Common": "4.7.0" + } + }, "fsharp.analyzers.sdk": { "type": "Project", "dependencies": { + "CliWrap": "[3.6.4, )", "FSharp.Compiler.Service": "[43.7.400, )", "FSharp.Core": "[7.0.400, )", + "MSBuild.StructuredLogger": "[2.1.815, )", "McMaster.NETCore.Plugins": "[1.4.0, )" } }, + "CliWrap": { + "type": "CentralTransitive", + "requested": "[3.6.4, )", + "resolved": "3.6.4", + "contentHash": "KVGVZlR0GWgN3Xr88oZMSzYu38TXIogwLz588e6wku3mIfg6lPchxpYWtZSZfurpTY63ANF61xWp8EZF3jkN4g==" + }, "FSharp.Compiler.Service": { "type": "CentralTransitive", "requested": "[43.7.400, )", @@ -153,6 +413,40 @@ "Microsoft.DotNet.PlatformAbstractions": "3.1.6", "Microsoft.Extensions.DependencyModel": "5.0.0" } + }, + "Microsoft.Build.Framework": { + "type": "CentralTransitive", + "requested": "[17.2.0, )", + "resolved": "16.10.0", + "contentHash": "uD2GUw3AYlFSpU42c/80DouuJL6w1Kb06q4FEjQhW/9wjhBwukgx13T5MPIpSvQ8ssahKINanHfMUL89EVQHgQ==", + "dependencies": { + "System.Security.Permissions": "4.7.0" + } + }, + "Microsoft.Build.Utilities.Core": { + "type": "CentralTransitive", + "requested": "[17.2.0, )", + "resolved": "16.10.0", + "contentHash": "R8eATgdaGCfdepd67LMe1qhJz6iQOTuI9gVoOqXrHwhc77sBDqG0XD9zKvrgOqfS6NJ03KKTAhbbXnLgD5fKCA==", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.NET.StringTools": "1.0.0", + "Microsoft.Win32.Registry": "4.3.0", + "System.Collections.Immutable": "5.0.0", + "System.Configuration.ConfigurationManager": "4.7.0", + "System.Security.Permissions": "4.7.0", + "System.Text.Encoding.CodePages": "4.0.1" + } + }, + "MSBuild.StructuredLogger": { + "type": "CentralTransitive", + "requested": "[2.1.815, )", + "resolved": "2.1.815", + "contentHash": "5UfWYgsWBGI3w0npjYLeXPv8TUcNmGB/QBhoYLDeYTosuvmG6pz6HPb+xPg/Boi1bmw2Bgnjo+XopcwzCbZAqg==", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.Build.Utilities.Core": "16.10.0" + } } } } diff --git a/src/FSharp.Analyzers.Cli/packages.lock.json b/src/FSharp.Analyzers.Cli/packages.lock.json index 465daf1..4019be9 100644 --- a/src/FSharp.Analyzers.Cli/packages.lock.json +++ b/src/FSharp.Analyzers.Cli/packages.lock.json @@ -584,11 +584,19 @@ "fsharp.analyzers.sdk": { "type": "Project", "dependencies": { + "CliWrap": "[3.6.4, )", "FSharp.Compiler.Service": "[43.7.400, )", "FSharp.Core": "[7.0.400, )", + "MSBuild.StructuredLogger": "[2.1.815, )", "McMaster.NETCore.Plugins": "[1.4.0, )" } }, + "CliWrap": { + "type": "CentralTransitive", + "requested": "[3.6.4, )", + "resolved": "3.6.4", + "contentHash": "KVGVZlR0GWgN3Xr88oZMSzYu38TXIogwLz588e6wku3mIfg6lPchxpYWtZSZfurpTY63ANF61xWp8EZF3jkN4g==" + }, "FSharp.Compiler.Service": { "type": "CentralTransitive", "requested": "[43.7.400, )", @@ -614,6 +622,16 @@ "Microsoft.DotNet.PlatformAbstractions": "3.1.6", "Microsoft.Extensions.DependencyModel": "5.0.0" } + }, + "MSBuild.StructuredLogger": { + "type": "CentralTransitive", + "requested": "[2.1.815, )", + "resolved": "2.1.815", + "contentHash": "5UfWYgsWBGI3w0npjYLeXPv8TUcNmGB/QBhoYLDeYTosuvmG6pz6HPb+xPg/Boi1bmw2Bgnjo+XopcwzCbZAqg==", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.Build.Utilities.Core": "16.10.0" + } } } } diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index e3c9e2e..8dda472 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -2,16 +2,149 @@ module FSharp.Analyzers.SDK.TestHelpers #nowarn "57" +open FSharp.Compiler.Text +open Microsoft.Build.Logging.StructuredLogger +open CliWrap open System +open System.IO open FSharp.Compiler.CodeAnalysis -open FSharp.Compiler.Text -let getAnalyzerMsgs source = - let loadProject (fcs: FSharpChecker) fileName sourceText = - fcs.GetProjectOptionsFromScript(fileName, sourceText) - |> Async.RunSynchronously - |> fst +type DotNetVersion = + | Six + | Seven + + override this.ToString() = + match this with + | Six -> "net6.0" + | Seven -> "net7.0" + +type FSharpProjectOptions with + + static member zero = + { + ProjectFileName = "" + ProjectId = None + SourceFiles = [||] + OtherOptions = [||] + ReferencedProjects = [||] + IsIncompleteTypeCheckEnvironment = false + UseScriptResolutionRules = false + LoadTime = DateTime.UtcNow + UnresolvedReferences = None + OriginalLoadReferences = [] + Stamp = None + } + +type Package = { Name: string; Version: string } + +let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |] + +let isFSharpFile (file: string) = + Seq.exists (fun (ext: string) -> file.EndsWith ext) fsharpFiles + +let readCompilerArgsFromBinLog file = + let build = BinaryLog.ReadBuild file + + let projectName = + build.Children + |> Seq.choose ( + function + | :? Project as p -> Some p.Name + | _ -> None + ) + |> Seq.distinct + |> Seq.exactlyOne + + let message (fscTask: FscTask) = + fscTask.Children + |> Seq.tryPick ( + function + | :? Message as m when m.Text.Contains "fsc" -> Some m.Text + | _ -> None + ) + + let mutable args = None + build.VisitAllChildren(fun task -> + match task with + | :? FscTask as fscTask -> + match fscTask.Parent.Parent with + | :? Project as p when p.Name = projectName -> args <- message fscTask + | _ -> () + | _ -> () + ) + + match args with + | None -> failwith $"Could not parse binlog at {file}, does it contain CoreCompile?" + | Some args -> + let idx = args.IndexOf "-o:" + args.Substring(idx).Split [| '\n' |] + +let mkOptions (compilerArgs: string array) = + let sourceFiles = + compilerArgs + |> Array.filter (fun (line: string) -> isFSharpFile line && File.Exists line) + + let otherOptions = + compilerArgs |> Array.filter (fun line -> not (isFSharpFile line)) + + { + ProjectFileName = "Project" + ProjectId = None + SourceFiles = sourceFiles + OtherOptions = otherOptions + ReferencedProjects = [||] + IsIncompleteTypeCheckEnvironment = false + UseScriptResolutionRules = false + LoadTime = DateTime.Now + UnresolvedReferences = None + OriginalLoadReferences = [] + Stamp = None + } + +let mkOptionsFromBinaryLog binLogPath = + let compilerArgs = readCompilerArgsFromBinLog binLogPath + mkOptions compilerArgs + +let mkOptionsFromProject (version: DotNetVersion) (additionalPkg: Package option) = + let id = Guid.NewGuid().ToString("N") + let tmpDir = Path.Combine(Path.GetTempPath(), id) + let binLogPath = Path.Combine(tmpDir, $"{id}.binlog") + + Directory.CreateDirectory(tmpDir) |> ignore + + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"new classlib -f {version.ToString()} -lang F#") + .WithValidation(CommandResultValidation.None) + .ExecuteAsync() + .Task.Result + |> ignore + + additionalPkg + |> Option.iter (fun p -> + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"add package {p.Name} --version {p.Version}") + .WithValidation(CommandResultValidation.None) + .ExecuteAsync() + .Task.Result + |> ignore + ) + + Cli + .Wrap("dotnet") + .WithArguments($"build {tmpDir} -bl:{binLogPath}") + .WithValidation(CommandResultValidation.None) + .ExecuteAsync() + .Task.Result + |> ignore + + mkOptionsFromBinaryLog binLogPath + +let getContext (opts: FSharpProjectOptions) source = let fileName = "A.fs" let files = Map.ofArray [| (fileName, SourceText.ofString source) |] @@ -20,7 +153,7 @@ let getAnalyzerMsgs source = let fcs = Utils.createFCS (Some documentSource) let printError (s: string) = Console.WriteLine(s) - let pathToAnalyzerDlls = System.IO.Path.GetFullPath(".") + let pathToAnalyzerDlls = Path.GetFullPath(".") let foundDlls, registeredAnalyzers = Client.loadAnalyzers printError pathToAnalyzerDlls @@ -31,8 +164,6 @@ let getAnalyzerMsgs source = if registeredAnalyzers = 0 then failwith $"no Analyzers found in {pathToAnalyzerDlls}" - let opts = loadProject fcs fileName files[fileName] - let opts = { opts with SourceFiles = [| fileName |] @@ -51,8 +182,6 @@ let getAnalyzerMsgs source = Utils.createContext (checkProjectResults, allSymbolUses) (file, text, parseRes, result) match ctx with - | Some c -> - let msgs = Client.runAnalyzers c - msgs + | Some c -> c | None -> failwith "Context creation failed" | None -> failwith "typechecking file failed" diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi new file mode 100644 index 0000000..bc1283b --- /dev/null +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi @@ -0,0 +1,19 @@ +module FSharp.Analyzers.SDK.TestHelpers + +open FSharp.Compiler.CodeAnalysis + +type DotNetVersion = + | Six + | Seven + + override ToString: unit -> string + +type FSharpProjectOptions with + + static member zero: FSharpProjectOptions + +type Package = { Name: string; Version: string } + +val mkOptionsFromProject: version: DotNetVersion -> additionalPkg: Package option -> FSharpProjectOptions + +val getContext: opts: FSharpProjectOptions -> source: string -> Context diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs index 556b531..630c183 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs @@ -131,7 +131,7 @@ module Utils = [] type SourceOfSource = | Path of string - | DiscreteSource of String + | DiscreteSource of string let typeCheckFile (fcs: FSharpChecker) (source, file, opts) = let text = diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj index a4c642a..25c2fec 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsproj @@ -14,14 +14,17 @@ + + + diff --git a/src/FSharp.Analyzers.SDK/packages.lock.json b/src/FSharp.Analyzers.SDK/packages.lock.json index 7e6d905..561db09 100644 --- a/src/FSharp.Analyzers.SDK/packages.lock.json +++ b/src/FSharp.Analyzers.SDK/packages.lock.json @@ -2,6 +2,12 @@ "version": 2, "dependencies": { "net6.0": { + "CliWrap": { + "type": "Direct", + "requested": "[3.6.4, )", + "resolved": "3.6.4", + "contentHash": "KVGVZlR0GWgN3Xr88oZMSzYu38TXIogwLz588e6wku3mIfg6lPchxpYWtZSZfurpTY63ANF61xWp8EZF3jkN4g==" + }, "DotNet.ReproducibleBuilds": { "type": "Direct", "requested": "[1.1.1, )", @@ -52,6 +58,16 @@ "Microsoft.Extensions.DependencyModel": "5.0.0" } }, + "MSBuild.StructuredLogger": { + "type": "Direct", + "requested": "[2.1.815, )", + "resolved": "2.1.815", + "contentHash": "5UfWYgsWBGI3w0npjYLeXPv8TUcNmGB/QBhoYLDeYTosuvmG6pz6HPb+xPg/Boi1bmw2Bgnjo+XopcwzCbZAqg==", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.Build.Utilities.Core": "16.10.0" + } + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "1.1.1", @@ -67,6 +83,25 @@ "resolved": "5.0.0", "contentHash": "umBECCoMC+sOUgm083yFr8SxTobUOcPFH4AXigdO2xJiszCHAnmeDl4qPphJt+oaJ/XIfV1wOjIts2nRnki61Q==" }, + "Microsoft.NET.StringTools": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "ZYVcoDM0LnSyT5nWoRGfShYdOecCw2sOXWwP6j1Z0u48Xq3+BVvZ+EiPCX9/8Gz439giW+O1H1kWF9Eb/w6rVg==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "3.1.0", + "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, "Microsoft.SourceLink.AzureRepos.Git": { "type": "Transitive", "resolved": "1.1.1", @@ -108,11 +143,44 @@ "Microsoft.SourceLink.Common": "1.1.1" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Lw1/VwLH1yxz6SfFEjVRCN0pnflLEsWgnV4qsdJ512/HhTwnKXUG+zDQ4yTO3K/EJQemGoNaBHX5InISNKTzUQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "7.0.0", @@ -121,6 +189,15 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "/anOTeSZCNNI2zDilogWrZ8pNqCmYbzGNexUnNhjW8k0sHqEZ2nHJBp147jBV3hGYswu5lINpNg1vxR7bnqvVA==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "4.7.0", + "System.Security.Permissions": "4.7.0" + } + }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "7.0.2", @@ -129,11 +206,54 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.SystemEvents": "4.7.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, "System.Memory": { "type": "Transitive", "resolved": "4.5.5", "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.7.0", @@ -147,10 +267,182 @@ "System.Collections.Immutable": "7.0.0" } }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "System.Security.Principal.Windows": "4.7.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "dkOV6YYVBnYRa15/yv004eCGRBVADXw8qRbbNiCn/XpdJSUXkkUeIvdvFHkvnko4CdKMqG8yRHC4ox83LSlMsQ==", + "dependencies": { + "System.Security.AccessControl": "4.7.0", + "System.Windows.Extensions": "4.7.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.0.11", + "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", + "dependencies": { + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "CeWTdRNfRaSh0pm2gDTJFwVaXfTq6Xwv/sA887iwPTneW7oMtMlpvDIO+U60+3GWTB7Aom6oQwv5VZVUhQRdPQ==", + "dependencies": { + "System.Drawing.Common": "4.7.0" + } + }, + "Microsoft.Build.Framework": { + "type": "CentralTransitive", + "requested": "[17.2.0, )", + "resolved": "16.10.0", + "contentHash": "uD2GUw3AYlFSpU42c/80DouuJL6w1Kb06q4FEjQhW/9wjhBwukgx13T5MPIpSvQ8ssahKINanHfMUL89EVQHgQ==", + "dependencies": { + "System.Security.Permissions": "4.7.0" + } + }, + "Microsoft.Build.Utilities.Core": { + "type": "CentralTransitive", + "requested": "[17.2.0, )", + "resolved": "16.10.0", + "contentHash": "R8eATgdaGCfdepd67LMe1qhJz6iQOTuI9gVoOqXrHwhc77sBDqG0XD9zKvrgOqfS6NJ03KKTAhbbXnLgD5fKCA==", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.NET.StringTools": "1.0.0", + "Microsoft.Win32.Registry": "4.3.0", + "System.Collections.Immutable": "5.0.0", + "System.Configuration.ConfigurationManager": "4.7.0", + "System.Security.Permissions": "4.7.0", + "System.Text.Encoding.CodePages": "4.0.1" + } } } } From 0da90a2f509ef2661b361f37dd839fc722aa3a8c Mon Sep 17 00:00:00 2001 From: dawe Date: Tue, 12 Sep 2023 19:42:20 +0200 Subject: [PATCH 04/15] use regex to filter out test project assemblies --- src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs index b00e211..1be08bf 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs @@ -4,6 +4,7 @@ open System open System.IO open System.Reflection open System.Runtime.Loader +open System.Text.RegularExpressions open McMaster.NETCore.Plugins open System.Collections.Concurrent @@ -84,8 +85,13 @@ module Client = let loadAnalyzers (printError: string -> unit) (dir: string) : (int * int) = if Directory.Exists dir then let analyzerAssemblies = + let regex = Regex(@".*test.*\.dll$") + Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories) - |> Array.filter (fun a -> not (a.EndsWith("FSharp.Analyzers.SDK.dll") || a.EndsWith("Test.dll"))) + |> Array.filter (fun a -> + let s = Path.GetFileName(a).ToLowerInvariant() + not (s.EndsWith("fsharp.analyzers.sdk.dll") || regex.IsMatch(s)) + ) |> Array.choose (fun analyzerDll -> try // loads an assembly and all of it's dependencies From d7e0b4b4e2b2a6454eb8fbd812aec1995504e80e Mon Sep 17 00:00:00 2001 From: dawe Date: Wed, 13 Sep 2023 09:05:02 +0200 Subject: [PATCH 05/15] fix reincarnation of .fsi filter --- src/FSharp.Analyzers.Cli/Program.fs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/FSharp.Analyzers.Cli/Program.fs b/src/FSharp.Analyzers.Cli/Program.fs index 4ef14bf..295d65f 100644 --- a/src/FSharp.Analyzers.Cli/Program.fs +++ b/src/FSharp.Analyzers.Cli/Program.fs @@ -67,13 +67,6 @@ let runProject toolsPath proj (globs: Glob list) = let allSymbolUses = checkProjectResults.GetAllUsesOfAllSymbols() opts.SourceFiles - |> Array.filter (fun file -> - match Path.GetExtension(file).ToLowerInvariant() with - | ".fsi" -> - printInfo $"Ignoring signature file %s{file}" - false - | _ -> true - ) |> Array.filter (fun file -> match globs |> List.tryFind (fun g -> g.IsMatch file) with | Some g -> From 7f0ada5d6cd527509ea0c2c6a17a478d0f70c91b Mon Sep 17 00:00:00 2001 From: dawe Date: Wed, 13 Sep 2023 13:05:12 +0200 Subject: [PATCH 06/15] - use a string for the target framework - use a list of packages - make creation of FSharpProjectOptions more robust and add debug infos --- samples/OptionAnalyzer.Test/UnitTests.fs | 17 ++- .../FSharp.Analyzers.SDK.TestHelpers.fs | 103 ++++++++++++------ .../FSharp.Analyzers.SDK.TestHelpers.fsi | 12 +- 3 files changed, 86 insertions(+), 46 deletions(-) diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index 647773b..c0bd2c4 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -12,12 +12,18 @@ let mutable projectOptions: FSharpProjectOptions = FSharpProjectOptions.zero let Setup () = projectOptions <- mkOptionsFromProject - DotNetVersion.Seven - (Some + // Todo: changing this to net8.0 makes "dotnet test" fail when run inside the repo, from outside or in the IDE the tests work fine + "net7.0" + [ { Name = "Newtonsoft.Json" Version = "13.0.3" - }) + } + { + Name = "Fantomas.FCS" + Version = "6.2.0" + } + ] [] let ``warnings are emitted`` () = @@ -43,9 +49,12 @@ let ``expected warning is emitted`` () = module M open Newtonsoft.Json +open Fantomas.FCS let json = JsonConvert.SerializeObject([1;2;3]) +let p = Fantomas.FCS.Text.Position.mkPos 23 2 + let notUsed() = let option : Option = None option.Value @@ -56,7 +65,7 @@ let notUsed() = Code = "OV001" Fixes = [] Message = "Option.Value shouldn't be used" - Range = Range.mkRange "A.fs" (Position.mkPos 10 4) (Position.mkPos 10 16) + Range = Range.mkRange "A.fs" (Position.mkPos 13 4) (Position.mkPos 13 16) Severity = Severity.Warning Type = "Option.Value analyzer" } diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index 8dda472..ea0244c 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -9,15 +9,6 @@ open System open System.IO open FSharp.Compiler.CodeAnalysis -type DotNetVersion = - | Six - | Seven - - override this.ToString() = - match this with - | Six -> "net6.0" - | Seven -> "net7.0" - type FSharpProjectOptions with static member zero = @@ -45,6 +36,9 @@ let isFSharpFile (file: string) = let readCompilerArgsFromBinLog file = let build = BinaryLog.ReadBuild file + if not build.Succeeded then + failwith $"Build failed: {file}" + let projectName = build.Children |> Seq.choose ( @@ -106,43 +100,82 @@ let mkOptionsFromBinaryLog binLogPath = let compilerArgs = readCompilerArgsFromBinLog binLogPath mkOptions compilerArgs -let mkOptionsFromProject (version: DotNetVersion) (additionalPkg: Package option) = - let id = Guid.NewGuid().ToString("N") - let tmpDir = Path.Combine(Path.GetTempPath(), id) - let binLogPath = Path.Combine(tmpDir, $"{id}.binlog") +let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = + let stdOutBuffer = System.Text.StringBuilder() + let stdErrBuffer = System.Text.StringBuilder() - Directory.CreateDirectory(tmpDir) |> ignore + try + let id = Guid.NewGuid().ToString("N") + let tmpDir = Path.Combine(Path.GetTempPath(), id) + let binLogPath = Path.Combine(tmpDir, $"{id}.binlog") - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) - .WithArguments($"new classlib -f {version.ToString()} -lang F#") - .WithValidation(CommandResultValidation.None) - .ExecuteAsync() - .Task.Result - |> ignore + Directory.CreateDirectory(tmpDir) |> ignore - additionalPkg - |> Option.iter (fun p -> + // Todo remove Cli .Wrap("dotnet") .WithWorkingDirectory(tmpDir) - .WithArguments($"add package {p.Name} --version {p.Version}") - .WithValidation(CommandResultValidation.None) + .WithArguments("--version") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) .ExecuteAsync() .Task.Result |> ignore - ) - Cli - .Wrap("dotnet") - .WithArguments($"build {tmpDir} -bl:{binLogPath}") - .WithValidation(CommandResultValidation.None) - .ExecuteAsync() - .Task.Result - |> ignore + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"new classlib -f {framework} -lang F#") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + .Task.Result + |> ignore + + // Todo remove + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments("--version") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + .Task.Result + |> ignore + + additionalPkgs + |> List.iter (fun p -> + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"add package {p.Name} --version {p.Version}") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + .Task.Result + |> ignore + ) + + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"build -bl:{Path.GetFileName(binLogPath)}") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + .Task.Result + |> ignore - mkOptionsFromBinaryLog binLogPath + mkOptionsFromBinaryLog binLogPath + with e -> + printfn $"%s{stdOutBuffer.ToString()}" + printfn $"%s{stdErrBuffer.ToString()}" + reraise () let getContext (opts: FSharpProjectOptions) source = let fileName = "A.fs" diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi index bc1283b..a332a34 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi @@ -2,18 +2,16 @@ module FSharp.Analyzers.SDK.TestHelpers open FSharp.Compiler.CodeAnalysis -type DotNetVersion = - | Six - | Seven - - override ToString: unit -> string - type FSharpProjectOptions with static member zero: FSharpProjectOptions type Package = { Name: string; Version: string } -val mkOptionsFromProject: version: DotNetVersion -> additionalPkg: Package option -> FSharpProjectOptions +/// Creates a classlib project in a temporary folder to gather the needed FSharpProjectOptions. +/// The target framework for the tested code to use. E.g. net6.0, net7.0 +/// A list of additional packages that should be referenced. The tested code can use these. +/// FSharpProjectOptions +val mkOptionsFromProject: framework: string -> additionalPkgs: Package list -> FSharpProjectOptions val getContext: opts: FSharpProjectOptions -> source: string -> Context From f5404b72643d1e4875860efa3d390097618cbc7c Mon Sep 17 00:00:00 2001 From: dawe Date: Wed, 13 Sep 2023 13:46:55 +0200 Subject: [PATCH 07/15] let mkOptionsFromProject use a task CE --- samples/OptionAnalyzer.Test/UnitTests.fs | 32 ++-- .../FSharp.Analyzers.SDK.TestHelpers.fs | 143 +++++++++--------- .../FSharp.Analyzers.SDK.TestHelpers.fsi | 3 +- 3 files changed, 91 insertions(+), 87 deletions(-) diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index c0bd2c4..f682237 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -10,20 +10,24 @@ let mutable projectOptions: FSharpProjectOptions = FSharpProjectOptions.zero [] let Setup () = - projectOptions <- - mkOptionsFromProject - // Todo: changing this to net8.0 makes "dotnet test" fail when run inside the repo, from outside or in the IDE the tests work fine - "net7.0" - [ - { - Name = "Newtonsoft.Json" - Version = "13.0.3" - } - { - Name = "Fantomas.FCS" - Version = "6.2.0" - } - ] + task { + let! opts = + mkOptionsFromProject + // Todo: changing this to net8.0 makes "dotnet test" fail when run inside the repo, from outside or in the IDE the tests work fine + "net7.0" + [ + { + Name = "Newtonsoft.Json" + Version = "13.0.3" + } + { + Name = "Fantomas.FCS" + Version = "6.2.0" + } + ] + + projectOptions <- opts + } [] let ``warnings are emitted`` () = diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index ea0244c..018c935 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -104,78 +104,77 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = let stdOutBuffer = System.Text.StringBuilder() let stdErrBuffer = System.Text.StringBuilder() - try - let id = Guid.NewGuid().ToString("N") - let tmpDir = Path.Combine(Path.GetTempPath(), id) - let binLogPath = Path.Combine(tmpDir, $"{id}.binlog") - - Directory.CreateDirectory(tmpDir) |> ignore - - // Todo remove - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) - .WithArguments("--version") - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) - .WithValidation(CommandResultValidation.ZeroExitCode) - .ExecuteAsync() - .Task.Result - |> ignore - - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) - .WithArguments($"new classlib -f {framework} -lang F#") - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) - .WithValidation(CommandResultValidation.ZeroExitCode) - .ExecuteAsync() - .Task.Result - |> ignore - - // Todo remove - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) - .WithArguments("--version") - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) - .WithValidation(CommandResultValidation.ZeroExitCode) - .ExecuteAsync() - .Task.Result - |> ignore - - additionalPkgs - |> List.iter (fun p -> - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) - .WithArguments($"add package {p.Name} --version {p.Version}") - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) - .WithValidation(CommandResultValidation.ZeroExitCode) - .ExecuteAsync() - .Task.Result - |> ignore - ) - - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) - .WithArguments($"build -bl:{Path.GetFileName(binLogPath)}") - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) - .WithValidation(CommandResultValidation.ZeroExitCode) - .ExecuteAsync() - .Task.Result - |> ignore - - mkOptionsFromBinaryLog binLogPath - with e -> - printfn $"%s{stdOutBuffer.ToString()}" - printfn $"%s{stdErrBuffer.ToString()}" - reraise () + task { + try + let id = Guid.NewGuid().ToString("N") + let tmpDir = Path.Combine(Path.GetTempPath(), id) + let binLogPath = Path.Combine(tmpDir, $"{id}.binlog") + + Directory.CreateDirectory(tmpDir) |> ignore + + // Todo remove + let! _ = + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments("--version") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + + let! _ = + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"new classlib -f {framework} -lang F#") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + + // Todo remove + let! _ = + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments("--version") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + + for p in additionalPkgs do + let! _ = + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"add package {p.Name} --version {p.Version}") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + + () + + let! _ = + Cli + .Wrap("dotnet") + .WithWorkingDirectory(tmpDir) + .WithArguments($"build -bl:{Path.GetFileName(binLogPath)}") + .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) + .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) + .WithValidation(CommandResultValidation.ZeroExitCode) + .ExecuteAsync() + + return mkOptionsFromBinaryLog binLogPath + + with e -> + printfn $"StdOut:\n%s{stdOutBuffer.ToString()}" + printfn $"StdErr:\n%s{stdErrBuffer.ToString()}" + printfn $"Exception:\n%s{e.ToString()}" + return FSharpProjectOptions.zero + } let getContext (opts: FSharpProjectOptions) source = let fileName = "A.fs" diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi index a332a34..2925dd4 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi @@ -1,5 +1,6 @@ module FSharp.Analyzers.SDK.TestHelpers +open System.Threading.Tasks open FSharp.Compiler.CodeAnalysis type FSharpProjectOptions with @@ -12,6 +13,6 @@ type Package = { Name: string; Version: string } /// The target framework for the tested code to use. E.g. net6.0, net7.0 /// A list of additional packages that should be referenced. The tested code can use these. /// FSharpProjectOptions -val mkOptionsFromProject: framework: string -> additionalPkgs: Package list -> FSharpProjectOptions +val mkOptionsFromProject: framework: string -> additionalPkgs: Package list -> Task val getContext: opts: FSharpProjectOptions -> source: string -> Context From 0aefdfc1dadc12a4e678481d93a7c6cd925a80ee Mon Sep 17 00:00:00 2001 From: dawe Date: Wed, 13 Sep 2023 17:06:19 +0200 Subject: [PATCH 08/15] cache the binlog files and reuse them --- .../FSharp.Analyzers.SDK.Client.fs | 7 +-- .../FSharp.Analyzers.SDK.TestHelpers.fs | 58 ++++++++++++++----- .../FSharp.Analyzers.SDK.fs | 4 ++ 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs index 1be08bf..c52654d 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs @@ -108,9 +108,6 @@ module Client = None ) - let currentFSharpAnalyzersSDKVersion = - Assembly.GetExecutingAssembly().GetName().Version - let findFSharpAnalyzerSDKVersion (assembly: Assembly) = let references = assembly.GetReferencedAssemblies() let fas = references |> Array.find (fun ra -> ra.Name = "FSharp.Analyzers.SDK") @@ -121,11 +118,11 @@ module Client = |> Array.filter (fun (name, analyzerAssembly) -> let version = findFSharpAnalyzerSDKVersion analyzerAssembly - if version = currentFSharpAnalyzersSDKVersion then + if version = Utils.currentFSharpAnalyzersSDKVersion then true else printError - $"Trying to load %s{name} which was built using SDK version %A{version}. Expect %A{currentFSharpAnalyzersSDKVersion} instead. Assembly will be skipped." + $"Trying to load %s{name} which was built using SDK version %A{version}. Expect %A{Utils.currentFSharpAnalyzersSDKVersion} instead. Assembly will be skipped." false ) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index 018c935..91184a5 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -26,7 +26,13 @@ type FSharpProjectOptions with Stamp = None } -type Package = { Name: string; Version: string } +type Package = + { + Name: string + Version: string + } + + override x.ToString() = $"{x.Name}_{x.Version}" let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |] @@ -100,23 +106,19 @@ let mkOptionsFromBinaryLog binLogPath = let compilerArgs = readCompilerArgsFromBinLog binLogPath mkOptions compilerArgs -let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = +let createProject (binLogPath: string) (tmpProjectDir: string) (framework: string) (additionalPkgs: Package list) = let stdOutBuffer = System.Text.StringBuilder() let stdErrBuffer = System.Text.StringBuilder() task { try - let id = Guid.NewGuid().ToString("N") - let tmpDir = Path.Combine(Path.GetTempPath(), id) - let binLogPath = Path.Combine(tmpDir, $"{id}.binlog") - - Directory.CreateDirectory(tmpDir) |> ignore + Directory.CreateDirectory(tmpProjectDir) |> ignore // Todo remove let! _ = Cli .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) + .WithWorkingDirectory(tmpProjectDir) .WithArguments("--version") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) @@ -126,7 +128,7 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = let! _ = Cli .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) + .WithWorkingDirectory(tmpProjectDir) .WithArguments($"new classlib -f {framework} -lang F#") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) @@ -137,7 +139,7 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = let! _ = Cli .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) + .WithWorkingDirectory(tmpProjectDir) .WithArguments("--version") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) @@ -148,7 +150,7 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = let! _ = Cli .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) + .WithWorkingDirectory(tmpProjectDir) .WithArguments($"add package {p.Name} --version {p.Version}") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) @@ -160,19 +162,45 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = let! _ = Cli .Wrap("dotnet") - .WithWorkingDirectory(tmpDir) - .WithArguments($"build -bl:{Path.GetFileName(binLogPath)}") + .WithWorkingDirectory(tmpProjectDir) + .WithArguments($"build -bl:{binLogPath}") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .WithValidation(CommandResultValidation.ZeroExitCode) .ExecuteAsync() - return mkOptionsFromBinaryLog binLogPath - + return () with e -> printfn $"StdOut:\n%s{stdOutBuffer.ToString()}" printfn $"StdErr:\n%s{stdErrBuffer.ToString()}" printfn $"Exception:\n%s{e.ToString()}" + } + +let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = + task { + try + let id = Guid.NewGuid().ToString("N") + let tmpProjectDir = Path.Combine(Path.GetTempPath(), id) + + let uniqueBinLogName = + let packages = + additionalPkgs |> List.map (fun p -> p.ToString()) |> String.concat "_" + + $"v{Utils.currentFSharpAnalyzersSDKVersion}_{framework}_{packages}.binlog" + + let binLogCache = + Path.Combine(Path.GetTempPath(), "FSharp.Analyzer.SDK.BinLogCache") + + let binLogPath = Path.Combine(binLogCache, uniqueBinLogName) + + if not (File.Exists(binLogPath)) then + Directory.CreateDirectory(binLogCache) |> ignore + let! _ = createProject binLogPath tmpProjectDir framework additionalPkgs + () + + return mkOptionsFromBinaryLog binLogPath + with e -> + printfn $"Exception:\n%s{e.ToString()}" return FSharpProjectOptions.zero } diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs index 630c183..4d1aec6 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs @@ -9,6 +9,7 @@ open FSharp.Compiler.Text open FSharp.Compiler.Symbols open FSharp.Compiler.EditorServices open System.Runtime.InteropServices +open System.Reflection /// Marks an analyzer for scanning [] @@ -56,6 +57,9 @@ type Analyzer = Context -> Message list module Utils = + let currentFSharpAnalyzersSDKVersion = + Assembly.GetExecutingAssembly().GetName().Version + let private entityCache = EntityCache() let private getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list = From 6843efa92d1297c95688f8ed333d44905e665401 Mon Sep 17 00:00:00 2001 From: dawe Date: Thu, 14 Sep 2023 10:12:25 +0200 Subject: [PATCH 09/15] delete cached binlog files if they contain a failed build --- samples/OptionAnalyzer.Test/UnitTests.fs | 3 +- .../FSharp.Analyzers.SDK.TestHelpers.fs | 45 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index f682237..ca703eb 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -13,7 +13,8 @@ let Setup () = task { let! opts = mkOptionsFromProject - // Todo: changing this to net8.0 makes "dotnet test" fail when run inside the repo, from outside or in the IDE the tests work fine + // Todo: changing this to net8.0 makes "dotnet test" fail when run inside the repo + // from outside the repo or in the IDE (but not in a debug session) the tests work fine with net8 "net7.0" [ { diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index 91184a5..d6dfcad 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -39,11 +39,9 @@ let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |] let isFSharpFile (file: string) = Seq.exists (fun (ext: string) -> file.EndsWith ext) fsharpFiles -let readCompilerArgsFromBinLog file = - let build = BinaryLog.ReadBuild file - +let readCompilerArgsFromBinLog (build: Build) = if not build.Succeeded then - failwith $"Build failed: {file}" + failwith $"Build failed: {build.LogFilePath}" let projectName = build.Children @@ -75,7 +73,7 @@ let readCompilerArgsFromBinLog file = ) match args with - | None -> failwith $"Could not parse binlog at {file}, does it contain CoreCompile?" + | None -> failwith $"Could not parse binlog at {build.LogFilePath}, does it contain CoreCompile?" | Some args -> let idx = args.IndexOf "-o:" args.Substring(idx).Split [| '\n' |] @@ -102,10 +100,22 @@ let mkOptions (compilerArgs: string array) = Stamp = None } -let mkOptionsFromBinaryLog binLogPath = - let compilerArgs = readCompilerArgsFromBinLog binLogPath +let mkOptionsFromBinaryLog build = + let compilerArgs = readCompilerArgsFromBinLog build mkOptions compilerArgs +let getCachedIfOldBuildSucceeded binLogPath = + if File.Exists binLogPath then + let build = BinaryLog.ReadBuild binLogPath + + if build.Succeeded then + Some build + else + File.Delete binLogPath + None + else + None + let createProject (binLogPath: string) (tmpProjectDir: string) (framework: string) (additionalPkgs: Package list) = let stdOutBuffer = System.Text.StringBuilder() let stdErrBuffer = System.Text.StringBuilder() @@ -114,7 +124,7 @@ let createProject (binLogPath: string) (tmpProjectDir: string) (framework: strin try Directory.CreateDirectory(tmpProjectDir) |> ignore - // Todo remove + // Todo remove after net8 issue is solved let! _ = Cli .Wrap("dotnet") @@ -135,7 +145,7 @@ let createProject (binLogPath: string) (tmpProjectDir: string) (framework: strin .WithValidation(CommandResultValidation.ZeroExitCode) .ExecuteAsync() - // Todo remove + // Todo remove after net8 issue is solved let! _ = Cli .Wrap("dotnet") @@ -193,12 +203,19 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = let binLogPath = Path.Combine(binLogCache, uniqueBinLogName) - if not (File.Exists(binLogPath)) then - Directory.CreateDirectory(binLogCache) |> ignore - let! _ = createProject binLogPath tmpProjectDir framework additionalPkgs - () + let! binLogFile = + let cached = getCachedIfOldBuildSucceeded binLogPath + + match cached with + | Some f -> task { return f } + | None -> + task { + Directory.CreateDirectory(binLogCache) |> ignore + let! _ = createProject binLogPath tmpProjectDir framework additionalPkgs + return BinaryLog.ReadBuild binLogPath + } - return mkOptionsFromBinaryLog binLogPath + return mkOptionsFromBinaryLog binLogFile with e -> printfn $"Exception:\n%s{e.ToString()}" return FSharpProjectOptions.zero From 5582633e55ec92ecf96abdbcb9a1e68c5ceefb05 Mon Sep 17 00:00:00 2001 From: dawe Date: Thu, 14 Sep 2023 10:43:27 +0200 Subject: [PATCH 10/15] typo --- src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index d6dfcad..889fa96 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -199,7 +199,7 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = $"v{Utils.currentFSharpAnalyzersSDKVersion}_{framework}_{packages}.binlog" let binLogCache = - Path.Combine(Path.GetTempPath(), "FSharp.Analyzer.SDK.BinLogCache") + Path.Combine(Path.GetTempPath(), "FSharp.Analyzers.SDK.BinLogCache") let binLogPath = Path.Combine(binLogCache, uniqueBinLogName) From a9a7ff98e49144c19c15e0ed03e330341c2895d3 Mon Sep 17 00:00:00 2001 From: dawe Date: Fri, 15 Sep 2023 12:36:02 +0200 Subject: [PATCH 11/15] add some assertion helper predicates --- .../FSharp.Analyzers.SDK.TestHelpers.fs | 19 +++++++++++++++++++ .../FSharp.Analyzers.SDK.TestHelpers.fsi | 12 ++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index 889fa96..072092e 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -262,3 +262,22 @@ let getContext (opts: FSharpProjectOptions) source = | Some c -> c | None -> failwith "Context creation failed" | None -> failwith "typechecking file failed" + +module AssertionHelpers = + + let areWarningsInLines (msgs: FSharp.Analyzers.SDK.Message list) (expectedLines: Set) = + let msgLines = msgs |> List.map (fun m -> m.Range.StartLine) |> Set.ofList + msgLines = expectedLines + + let messageContains (expectedContent: string) (msg: FSharp.Analyzers.SDK.Message) = + not (String.IsNullOrWhiteSpace(msg.Message)) + && msg.Message.Contains(expectedContent) + + let allMessagesContain (expectedContent: string) (msgs: FSharp.Analyzers.SDK.Message list) = + msgs |> List.forall (messageContains expectedContent) + + let messageContainsAny (expectedContents: string list) (msg: FSharp.Analyzers.SDK.Message) = + expectedContents |> List.exists msg.Message.Contains + + let messagesContainAny (expectedContents: string list) (msgs: FSharp.Analyzers.SDK.Message list) = + msgs |> List.forall (messageContainsAny expectedContents) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi index 2925dd4..8b069b7 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi @@ -16,3 +16,15 @@ type Package = { Name: string; Version: string } val mkOptionsFromProject: framework: string -> additionalPkgs: Package list -> Task val getContext: opts: FSharpProjectOptions -> source: string -> Context + +module AssertionHelpers = + + val areWarningsInLines: msgs: Message list -> expectedLines: Set -> bool + + val messageContains: expectedContent: string -> msg: Message -> bool + + val allMessagesContain: expectedContent: string -> msgs: Message list -> bool + + val messageContainsAny: expectedContents: string list -> msg: Message -> bool + + val messagesContainAny: expectedContents: string list -> msgs: Message list -> bool From 8413575cb7d2b0dc5a792e2ee52504d6d362bade Mon Sep 17 00:00:00 2001 From: dawe Date: Fri, 15 Sep 2023 12:41:39 +0200 Subject: [PATCH 12/15] use assertion helper --- samples/OptionAnalyzer.Test/UnitTests.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index ca703eb..e7b1ca1 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -45,6 +45,7 @@ let notUsed() = let ctx = getContext projectOptions source let msgs = optionValueAnalyzer ctx Assert.IsNotEmpty msgs + Assert.IsTrue(AssertionHelpers.messageContains "Option.Value" msgs[0]) [] let ``expected warning is emitted`` () = From 7bba100e5439900a30a88c52353d5bd25ef0f3e4 Mon Sep 17 00:00:00 2001 From: dawe Date: Fri, 15 Sep 2023 14:01:13 +0200 Subject: [PATCH 13/15] explain situation regarding net8 and fail accordingly --- samples/OptionAnalyzer.Test/UnitTests.fs | 2 -- .../FSharp.Analyzers.SDK.TestHelpers.fs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index e7b1ca1..7e01ea1 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -13,8 +13,6 @@ let Setup () = task { let! opts = mkOptionsFromProject - // Todo: changing this to net8.0 makes "dotnet test" fail when run inside the repo - // from outside the repo or in the IDE (but not in a debug session) the tests work fine with net8 "net7.0" [ { diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index 072092e..1d10c76 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -187,6 +187,18 @@ let createProject (binLogPath: string) (tmpProjectDir: string) (framework: strin } let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = + // Todo: using net8.0 (newer than our global.json) makes "dotnet test" fail when run inside the repo + // from outside the repo or in the IDE (but not in a debug session) the tests work fine with net8 + // It seems to be related to unit testing contexts as the code works in normal console projects + // tried, but failed, ideas: + // - moving global.json in Analyzers repo to xxxglobal.json while doing this + // - generating global.json in tmp proj folder + // - double spawning pwsh in CliWrap (works in console proj) + // - other testing framework + // - create .bat file with command and execute bat file in unit test (works in console proj) + if framework.Contains("net8") then + failwith $"Framework {framework} not supported yet" + task { try let id = Guid.NewGuid().ToString("N") From 4f20f84e376eb7e9ebffc12c2f4f84ade1696af8 Mon Sep 17 00:00:00 2001 From: dawe Date: Sat, 16 Sep 2023 21:59:29 +0200 Subject: [PATCH 14/15] fix the net8 testing issue --- .../FSharp.Analyzers.SDK.TestHelpers.fs | 43 +++++-------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index 1d10c76..cc7473a 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -1,5 +1,6 @@ module FSharp.Analyzers.SDK.TestHelpers +// Don't warn about using NotifyFileChanged of the FCS API #nowarn "57" open FSharp.Compiler.Text @@ -7,6 +8,8 @@ open Microsoft.Build.Logging.StructuredLogger open CliWrap open System open System.IO +open System.Collections.Generic +open System.Collections.ObjectModel open FSharp.Compiler.CodeAnalysis type FSharpProjectOptions with @@ -124,16 +127,11 @@ let createProject (binLogPath: string) (tmpProjectDir: string) (framework: strin try Directory.CreateDirectory(tmpProjectDir) |> ignore - // Todo remove after net8 issue is solved - let! _ = - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpProjectDir) - .WithArguments("--version") - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) - .WithValidation(CommandResultValidation.ZeroExitCode) - .ExecuteAsync() + // needed to escape the global.json circle of influence in a unit testing process + let envDic = Dictionary() + envDic["MSBuildExtensionsPath"] <- null + envDic["MSBuildSDKsPath"] <- null + let roDic = ReadOnlyDictionary(envDic) let! _ = Cli @@ -145,21 +143,11 @@ let createProject (binLogPath: string) (tmpProjectDir: string) (framework: strin .WithValidation(CommandResultValidation.ZeroExitCode) .ExecuteAsync() - // Todo remove after net8 issue is solved - let! _ = - Cli - .Wrap("dotnet") - .WithWorkingDirectory(tmpProjectDir) - .WithArguments("--version") - .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) - .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) - .WithValidation(CommandResultValidation.ZeroExitCode) - .ExecuteAsync() - for p in additionalPkgs do let! _ = Cli .Wrap("dotnet") + .WithEnvironmentVariables(roDic) .WithWorkingDirectory(tmpProjectDir) .WithArguments($"add package {p.Name} --version {p.Version}") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) @@ -172,6 +160,7 @@ let createProject (binLogPath: string) (tmpProjectDir: string) (framework: strin let! _ = Cli .Wrap("dotnet") + .WithEnvironmentVariables(roDic) .WithWorkingDirectory(tmpProjectDir) .WithArguments($"build -bl:{binLogPath}") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) @@ -187,18 +176,6 @@ let createProject (binLogPath: string) (tmpProjectDir: string) (framework: strin } let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = - // Todo: using net8.0 (newer than our global.json) makes "dotnet test" fail when run inside the repo - // from outside the repo or in the IDE (but not in a debug session) the tests work fine with net8 - // It seems to be related to unit testing contexts as the code works in normal console projects - // tried, but failed, ideas: - // - moving global.json in Analyzers repo to xxxglobal.json while doing this - // - generating global.json in tmp proj folder - // - double spawning pwsh in CliWrap (works in console proj) - // - other testing framework - // - create .bat file with command and execute bat file in unit test (works in console proj) - if framework.Contains("net8") then - failwith $"Framework {framework} not supported yet" - task { try let id = Guid.NewGuid().ToString("N") From de6f0e28c0ed4621f3fd4f792f53da5149bc4426 Mon Sep 17 00:00:00 2001 From: dawe Date: Tue, 19 Sep 2023 10:20:05 +0200 Subject: [PATCH 15/15] - rename module "TestHelpers" to "Testing" - rename module "AssertionHelpers" to "Assert" - rename funcition "areWarningsInLines" to "hasWarningsInLines" - shuffle order of "hasWarningsInLines" --- samples/OptionAnalyzer.Test/UnitTests.fs | 4 ++-- .../FSharp.Analyzers.SDK.TestHelpers.fs | 6 +++--- .../FSharp.Analyzers.SDK.TestHelpers.fsi | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index 7e01ea1..0ac9c83 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -4,7 +4,7 @@ open FSharp.Compiler.CodeAnalysis open NUnit.Framework open FSharp.Compiler.Text open FSharp.Analyzers.SDK -open FSharp.Analyzers.SDK.TestHelpers +open FSharp.Analyzers.SDK.Testing let mutable projectOptions: FSharpProjectOptions = FSharpProjectOptions.zero @@ -43,7 +43,7 @@ let notUsed() = let ctx = getContext projectOptions source let msgs = optionValueAnalyzer ctx Assert.IsNotEmpty msgs - Assert.IsTrue(AssertionHelpers.messageContains "Option.Value" msgs[0]) + Assert.IsTrue(Assert.messageContains "Option.Value" msgs[0]) [] let ``expected warning is emitted`` () = diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs index cc7473a..04097d3 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fs @@ -1,4 +1,4 @@ -module FSharp.Analyzers.SDK.TestHelpers +module FSharp.Analyzers.SDK.Testing // Don't warn about using NotifyFileChanged of the FCS API #nowarn "57" @@ -252,9 +252,9 @@ let getContext (opts: FSharpProjectOptions) source = | None -> failwith "Context creation failed" | None -> failwith "typechecking file failed" -module AssertionHelpers = +module Assert = - let areWarningsInLines (msgs: FSharp.Analyzers.SDK.Message list) (expectedLines: Set) = + let hasWarningsInLines (expectedLines: Set) (msgs: FSharp.Analyzers.SDK.Message list) = let msgLines = msgs |> List.map (fun m -> m.Range.StartLine) |> Set.ofList msgLines = expectedLines diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi index 8b069b7..31f49d1 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.TestHelpers.fsi @@ -1,4 +1,4 @@ -module FSharp.Analyzers.SDK.TestHelpers +module FSharp.Analyzers.SDK.Testing open System.Threading.Tasks open FSharp.Compiler.CodeAnalysis @@ -17,9 +17,9 @@ val mkOptionsFromProject: framework: string -> additionalPkgs: Package list -> T val getContext: opts: FSharpProjectOptions -> source: string -> Context -module AssertionHelpers = +module Assert = - val areWarningsInLines: msgs: Message list -> expectedLines: Set -> bool + val hasWarningsInLines: expectedLines: Set -> msgs: Message list -> bool val messageContains: expectedContent: string -> msg: Message -> bool