diff --git a/FSharp.Analyzers.SDK.sln b/FSharp.Analyzers.SDK.sln index a446279..d89767c 100644 --- a/FSharp.Analyzers.SDK.sln +++ b/FSharp.Analyzers.SDK.sln @@ -38,6 +38,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "OptionAnalyzer.Test", "samp EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Analyzers.SDK.Testing", "src\FSharp.Analyzers.SDK.Testing\FSharp.Analyzers.SDK.Testing.fsproj", "{3C70D1B2-DDCE-439A-BAB2-AC6B2E0919D5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.Analyzers.Build", "src\FSharp.Analyzers.Build\FSharp.Analyzers.Build.csproj", "{34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -111,6 +113,18 @@ Global {3C70D1B2-DDCE-439A-BAB2-AC6B2E0919D5}.Release|x64.Build.0 = Release|Any CPU {3C70D1B2-DDCE-439A-BAB2-AC6B2E0919D5}.Release|x86.ActiveCfg = Release|Any CPU {3C70D1B2-DDCE-439A-BAB2-AC6B2E0919D5}.Release|x86.Build.0 = Release|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Debug|x64.ActiveCfg = Debug|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Debug|x64.Build.0 = Debug|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Debug|x86.ActiveCfg = Debug|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Debug|x86.Build.0 = Debug|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Release|Any CPU.Build.0 = Release|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Release|x64.ActiveCfg = Release|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Release|x64.Build.0 = Release|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Release|x86.ActiveCfg = Release|Any CPU + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {C1D38B7A-0193-46AA-B033-ADBBF642AAA0} = {95A9FA19-723D-4D2C-A936-F0B45656B0D6} @@ -120,5 +134,6 @@ Global {452A16E1-35C3-4392-B969-548E701748D5} = {7A9A1C69-ADF2-421C-90F8-AB3304D6E197} {9A9AC3F8-E34B-4C30-A52A-A507D6E0CA01} = {0FE81935-26A8-45E1-A62E-5148C73BA6A2} {3C70D1B2-DDCE-439A-BAB2-AC6B2E0919D5} = {95A9FA19-723D-4D2C-A936-F0B45656B0D6} + {34AD5A2D-5FDE-4A03-8AC5-100F54E6D2DF} = {95A9FA19-723D-4D2C-A936-F0B45656B0D6} EndGlobalSection EndGlobal diff --git a/docs/content/Getting Started Using.fsx b/docs/content/Getting Started Using.md similarity index 51% rename from docs/content/Getting Started Using.fsx rename to docs/content/Getting Started Using.md index 4b52afb..6cad89c 100644 --- a/docs/content/Getting Started Using.fsx +++ b/docs/content/Getting Started Using.md @@ -1,4 +1,3 @@ -(** --- category: end-users categoryindex: 1 @@ -13,6 +12,8 @@ We assume the analyzers you want to use are distributed as a nuget package. ## Using analyzers in a single project +### Raw command line + A dotnet CLI tool, called [fsharp-analyzers](https://www.nuget.org/packages/fsharp-analyzers), is used to run analyzers outside the context of an IDE. Add it to your tool-manifest with: ```shell @@ -22,7 +23,7 @@ dotnet tool install fsharp-analyzers Next, add the `PackageReference` pointing to your favorite analyzers to the `.fsproj` file of the project you want to analyze: ```xml - + all build @@ -32,38 +33,54 @@ At the time of writing, the [G-Research analyzers](https://github.com/g-research With the package downloaded, we can run the CLI tool: ```shell -dotnet fsharp-analyzers --project ./YourProject.fsproj --analyzers-path C:\Users\yourusername\.nuget\packages\g-research.fsharp.analyzers\0.1.6\analyzers\dotnet\fs\ --verbose +dotnet fsharp-analyzers --project ./YourProject.fsproj --analyzers-path C:\Users\yourusername\.nuget\packages\g-research.fsharp.analyzers\0.3.0\analyzers\dotnet\fs\ --verbose ``` +### Using an MSBuild target + As you can see, the path to the analyzer DLL files could be tricky to get right across a wide range of setups. Luckily, we can use an MSBuild custom target to take care of the path construction. -Add the following target to the `.fsproj` file for easy invocation of the analyzer: + +Add [FSharp.Analyzers.Build](https://www.nuget.org/packages/FSharp.Analyzers.Build) to your `fsproj`: ```xml - - - - - - - - + ``` -You may need to adjust the `Command` to be compatible with your specific analyzer. Think about how you want warnings to be treated. +This imports two targets to your project file: `AnalyzeFSharpProject` and `AnalyzeFSharpProjectUsingFscArgs`. +These will allow us to easily run the analyzers for our project. + +Before we can run `dotnet msbuild /t:AnalyzeFSharpProject`, we need to specify our settings in a property called `FSharpAnalyzersOtherFlags`: + +```xml + + --analyzers-path "$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs" --report "$(MSBuildProjectName)-$(TargetFramework).sarif" --treat-as-warning IONIDE-004 --verbose + +``` To locate the analyzer DLLs in the filesystem, we use the variable `$(PkgG-Research_FSharp_Analyzers)`. It's produced by NuGet and normalized to be usable by [MSBuild](https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#generatepathproperty). In general, a `Pkg` prefix is added and dots in the package ID are replaced by underscores. But make sure to look at the [nuget.g.props](https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#restore-outputs) file in the `obj` folder for the exact string. -The `\analyzers\dotnet\fs` subpath is a convention analyzer authors should follow when creating their packages. +The `/analyzers/dotnet/fs` subpath is a convention analyzer authors should follow when creating their packages. At last, you can run the analyzer from the project folder: ```shell -dotnet msbuild /t:AnalyzeProject +dotnet msbuild /t:AnalyzeFSharpProject ``` +Note: if your project has multiple `TargetFrameworks` the tool will be invoked for each target framework. + +### Analyze FSharp project using FscArgs + +So 🤔, what is the difference between `AnalyzeFSharpProject` and `AnalyzeFSharpProjectUsingFscArgs`? + +The way the analyzers work is that we will programmatically type-check a project and process the results with our analyzers. In order to do this programmatically we need to construct the [FSharpProjectOptions](https://fsharp.github.io/fsharp-compiler-docs/reference/fsharp-compiler-codeanalysis-fsharpprojectoptions.html). +This is essentially a type that represents all the fsharp compiler arguments. When using `--project`, we will use [ProjInfo](https://github.com/ionide/proj-info) to invoke a set of MSBuild targets in the project to perform a design-time build. +A design-time build is basically an empty invocation of a build. It won't produce assemblies but will have constructed the correct arguments to theoretically invoke the compiler. + +There's an alternative way to do this. Instead of using the `--project` argument, it's possible to use the `--fsc-args` argument to let the CLI tool construct the needed `FSharpProjectOptions`. +The `AnalyzeFSharpProjectUsingFscArgs` uses MSBuild to construct the raw `fsc` arguments. This can potentially be faster (due to MSBuild caching) and more accurate. + ## Using analyzers in a solution Adding the custom target from above to all `.fsproj` files of a solution doesn't scale very well. @@ -74,53 +91,46 @@ This adds the package reference to all `.fsproj` files that are in a subfolder o ```xml + - all - build + all + build ``` -Likewise we add the following custom target to the [Directory.Build.targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022) file. -This is effectively the same as adding a target to each `*proj` file which exists in a subfolder. +Likewise we add the `FSharpAnalyzersOtherFlags` property to the [Directory.Build.targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022) file. +This is effectively the same as adding a property to each `*proj` file which exists in a subfolder. ```xml - - - - - - - - - - - - + + ./ + . + --analyzers-path "$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs" --report "$(SarifOutput)$(MSBuildProjectName)-$(TargetFramework).sarif" --code-root $(CodeRoot) --treat-as-warning IONIDE-004 --verbose + ``` -You may need to adjust the `Command` to be compatible with your specific analyzer. Think about how you want warnings to be treated. +⚠️ We are adding the `FSharpAnalyzersOtherFlags` property to our **Directory.Build.targets** and **not to** any **Directory.Build.props** file! +MSBuild will first evaluate `Directory.Build.props` which has no access to the generated nuget.g.props. `$(PkgIonide_Analyzers)` won't be known at this point. `Directory.Build.targets` is evaluated after the project file and has access to `Pkg` generated properties. As we don't want to list all projects of the solution explicitly when analyzing the solution, we create a second custom MSBuild target that calls the project-specific target for all projects. Add the following custom target to the [Directory.Solution.targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-solution-build?view=vs-2022) file to be able to invoke analysis of the whole solution in one simple command: ```xml - - + - + + - ``` @@ -130,60 +140,6 @@ At last, you can run the analyzer from the solution folder: dotnet msbuild /t:AnalyzeSolution ``` -## Project Cracking - -If all this seems a bit complex to you, let us explain some inner details to give you a better understanding: - -The way the analyzers work is that we will programmatically type-check a project and process the results with our analyzers. In order to do this programmatically we need to construct the [FSharpProjectOptions](https://fsharp.github.io/fsharp-compiler-docs/reference/fsharp-compiler-codeanalysis-fsharpprojectoptions.html). -This is essentially a type that represents all the fsharp compiler arguments. When using `--project`, we will use [ProjInfo](https://github.com/ionide/proj-info) to invoke a set of MSBuild targets in the project to perform a design-time build. -A design-time build is basically an empty invocation of a build. It won't produce assemblies but will have constructed the correct arguments to theoretically invoke the compiler. - -There's an alternative way to do this. Instead of using the `--project` argument, it's possible to use the `--fsc-args` argument to let the CLI tool construct the needed `FSharpProjectOptions`. -This also uses MSBuild, but in a more efficient way to provide us with the needed information. -Here's how the `Directory.Solution.targets` file would look like to make the use of `--fsc-args` possible: - -```xml - - - - - - - - - - - - -``` - -And here's the `Directory.Build.targets`: -```xml - - - - - - - - - - - - - - -*) - -(** +Note: we passed the `--code-root` flag so that the `*.sarif` report files will report file paths relative to this root. This can be imported for certain editors to function properly. [Next]({{fsdocs-next-page-link}}) - -*) diff --git a/src/FSharp.Analyzers.Build/CHANGELOG.md b/src/FSharp.Analyzers.Build/CHANGELOG.md new file mode 100644 index 0000000..455e454 --- /dev/null +++ b/src/FSharp.Analyzers.Build/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +This is the changelog for the `FSharp.Analyzers.Build` package specifically. It's distinct from that of the overall libraries and command-line tool. + +## 0.1.0 - 2023-11-15 + +### Added +* Initial release \ No newline at end of file diff --git a/src/FSharp.Analyzers.Build/FSharp.Analyzers.Build.csproj b/src/FSharp.Analyzers.Build/FSharp.Analyzers.Build.csproj new file mode 100644 index 0000000..a7cdb89 --- /dev/null +++ b/src/FSharp.Analyzers.Build/FSharp.Analyzers.Build.csproj @@ -0,0 +1,15 @@ + + + netstandard1.0 + true + true + true + true + false + $(MSBuildThisFileDirectory)CHANGELOG.md + + + + + + \ No newline at end of file diff --git a/src/FSharp.Analyzers.Build/build/FSharp.Analyzers.Build.targets b/src/FSharp.Analyzers.Build/build/FSharp.Analyzers.Build.targets new file mode 100644 index 0000000..9c16f00 --- /dev/null +++ b/src/FSharp.Analyzers.Build/build/FSharp.Analyzers.Build.targets @@ -0,0 +1,34 @@ + + + + + + + + + + + true + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FSharp.Analyzers.Build/buildMultitargeting/FSharp.Analyzers.Build.targets b/src/FSharp.Analyzers.Build/buildMultitargeting/FSharp.Analyzers.Build.targets new file mode 100644 index 0000000..cd05b72 --- /dev/null +++ b/src/FSharp.Analyzers.Build/buildMultitargeting/FSharp.Analyzers.Build.targets @@ -0,0 +1,30 @@ + + + + + + + <_TFMItems Include="$(TargetFrameworks)"/> + <_SingleTfmAnalysis Include="$(MSBuildProjectFullPath)" + AdditionalProperties="TargetFramework=%(_TFMItems.Identity);" + UndefineProperties="TargetFrameworks"/> + + + + + + + + <_TFMItems Include="$(TargetFrameworks)"/> + <_SingleTfmAnalysis Include="$(MSBuildProjectFullPath)" + AdditionalProperties="TargetFramework=%(_TFMItems.Identity);" + UndefineProperties="TargetFrameworks"/> + + + + \ No newline at end of file