Skip to content

Commit

Permalink
Merge pull request #131 from dawedawe/add_getting_started_for_users
Browse files Browse the repository at this point in the history
Getting started for consumers/users
  • Loading branch information
nojaf authored Oct 27, 2023
2 parents 7b54a2a + 92ef442 commit 4d5ce26
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 37 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ dotnet run --project src\FSharp.Analyzers.Cli\FSharp.Analyzers.Cli.fsproj -- --p

You can also set up a run configuration of FSharp.Analyzers.Cli in your favorite IDE using similar arguments. This also allows you to debug FSharp.Analyzers.Cli.

## Using Analyzers

Checkout our [Getting Started](https://ionide.io/FSharp.Analyzers.SDK/content/Getting%20Started%20Using.html) guide for analyzer users!

## Writing Analyzers

Checkout our [Getting Started](https://ionide.io/FSharp.Analyzers.SDK/content/Getting%20Started.html) guide!
Checkout our [Getting Started](https://ionide.io/FSharp.Analyzers.SDK/content/Getting%20Started%20Writing.html) guide for analyzer authors!

## How to contribute

Expand Down
2 changes: 1 addition & 1 deletion docs/content/Dual Analyzer.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
---
category: end-users
categoryindex: 1
index: 2
index: 3
---
# Writing an analyzer for both console and editor
Expand Down
189 changes: 189 additions & 0 deletions docs/content/Getting Started Using.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
(**
---
category: end-users
categoryindex: 1
index: 1
---
# Getting started using analyzers
## Premise
We assume the analyzers you want to use are distributed as a nuget package.
## Using analyzers in a single project
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
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
<PackageReference Include="G-Research.FSharp.Analyzers" Version="0.1.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>build</IncludeAssets>
</PackageReference>
```
At the time of writing, the [G-Research analyzers](https://github.com/g-research/fsharp-analyzers) [package](https://www.nuget.org/packages/G-Research.FSharp.Analyzers) contains the only analyzers compatible with the latest CLI tool.
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
```
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:
```xml
<Target Name="AnalyzeProject">
<Message Importance="High" Text="Analyzing $(MSBuildProjectFile)"/>
<Exec
ContinueOnError="true"
Command="dotnet fsharp-analyzers --project &quot;$(MSBuildProjectFile)&quot; --analyzers-path &quot;$(PkgG-Research_FSharp_Analyzers)\analyzers\dotnet\fs&quot; --exclude-analyzer PartialAppAnalyzer --fail-on-warnings GRA-STRING-001 --verbose --report &quot;$(MSBuildProjectName)-analysis.sarif&quot;">
<Output TaskParameter="ExitCode" PropertyName="LastExitCode" />
</Exec>
<Error Condition="'$(LastExitCode)' == '-2'" Text="Problems were found $(MSBuildProjectFile)" />
</Target>
```
You may need to adjust the `Command` to be compatible with your specific analyzer. Think about how you want warnings to be treated.
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.
At last, you can run the analyzer from the project folder:
```shell
dotnet msbuild /t:AnalyzeProject
```
## Using analyzers in a solution
Adding the custom target from above to all `.fsproj` files of a solution doesn't scale very well.
So we use the MSBuild infrastructure to add the needed package reference and the MSBuild target to all projects in one go.
We start with adding the `PackageReference` pointing to your favorite analyzers to the [Directory.Build.props](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022) file.
This adds the package reference to all `.fsproj` files that are in a subfolder of the file location of `Directory.Build.props`:
```xml
<ItemGroup>
<PackageReference Include="G-Research.FSharp.Analyzers" Version="0.1.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
```
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.
```xml
<Project>
<Target
Name="AnalyzeProject">
<Message Importance="normal" Text="fsc arguments: @(FscCommandLineArgs)" />
<Message Importance="High" Text="Analyzing $(MSBuildProjectFile)"/>
<Exec
ContinueOnError="true"
Command="dotnet fsharp-analyzers --project &quot;$(MSBuildProjectFile)&quot; --analyzers-path &quot;$(PkgG-Research_FSharp_Analyzers)\analyzers\dotnet\fs&quot; --exclude-analyzer PartialAppAnalyzer --fail-on-warnings GRA-STRING-001 --verbose --report &quot;$(MSBuildProjectName)-analysis.sarif&quot;">
<Output TaskParameter="ExitCode" PropertyName="LastExitCode" />
</Exec>
<Error Condition="'$(LastExitCode)' == '-2'" Text="Problems were found $(MSBuildProjectFile)" />
</Target>
</Project>
```
You may need to adjust the `Command` to be compatible with your specific analyzer. Think about how you want warnings to be treated.
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
<Project>
<ItemGroup>
<ProjectsToAnalyze Include="src\**\*.fsproj" />
</ItemGroup>
<Target Name="AnalyzeSolution">
<MSBuild
Projects="@(ProjectsToAnalyze)"
Targets="AnalyzeProject" />
</Target>
</Project>
```
At last, you can run the analyzer from the solution folder:
```shell
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
<Project>
<ItemGroup>
<ProjectsToAnalyze Include="src\**\*.fsproj" />
</ItemGroup>
<Target Name="AnalyzeSolution">
<Exec Command="dotnet build -c Release $(SolutionFileName)" />
<MSBuild
Projects="@(ProjectsToAnalyze)"
Targets="AnalyzeProject"
Properties="DesignTimeBuild=True;Configuration=Release;ProvideCommandLineArgs=True;SkipCompilerExecution=True" />
</Target>
</Project>
```
And here's the `Directory.Build.targets`:
```xml
<Project>
<Target
Name="AnalyzeProject"
DependsOnTargets="Restore;ResolveAssemblyReferencesDesignTime;ResolveProjectReferencesDesignTime;ResolvePackageDependenciesDesignTime;FindReferenceAssembliesForReferences;_GenerateCompileDependencyCache;_ComputeNonExistentFileProperty;BeforeBuild;BeforeCompile;CoreCompile">
<Message Importance="normal" Text="fsc arguments: @(FscCommandLineArgs)" />
<Message Importance="High" Text="Analyzing $(MSBuildProjectFile)"/>
<Exec
ContinueOnError="true"
Command="dotnet fsharp-analyzers --fsc-args &quot;@(FscCommandLineArgs)&quot; --analyzers-path &quot;$(PkgG-Research_FSharp_Analyzers)\analyzers\dotnet\fs&quot; --exclude-analyzer PartialAppAnalyzer --fail-on-warnings GRA-STRING-001 --verbose --report &quot;$(MSBuildProjectName)-analysis.sarif&quot;">
<Output TaskParameter="ExitCode" PropertyName="LastExitCode" />
</Exec>
<Error Condition="'$(LastExitCode)' == '-2'" Text="Problems were found $(MSBuildProjectFile)" />
</Target>
</Project>
*)

(**
[Next]({{fsdocs-next-page-link}})
*)
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
---
category: end-users
categoryindex: 1
index: 1
index: 2
---
# Getting started
# Getting started writing an analyzer
## Premise
Expand Down Expand Up @@ -111,42 +111,15 @@ dotnet tool install --global fsharp-analyzers
fsharp-analyzers --project YourProject.fsproj --analyzers-path ./OptionAnalyzer/bin/Release --verbose
```
## Packaging and Distribution
### Packaging and Distribution
Since analyzers are just .NET core libraries, you can distribute them to the nuget registry just like you would with a normal .NET package.
In the Roslyn world, analyzers are stored in a special folder inside your nuget package. We recommend you follow this structure by adding some additional MSBuild properties:
```xml
<PropertyGroup>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<DevelopmentDependency>true</DevelopmentDependency>
<NoPackageAnalysis>true</NoPackageAnalysis>
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput</TargetsForTfmSpecificContentInPackage>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<Target Name="_AddAnalyzersToOutput">
<ItemGroup>
<TfmSpecificPackageFile Include="$(OutputPath)\$(AssemblyName).dll" PackagePath="analyzers/dotnet/fs" />
</ItemGroup>
</Target>
```
`SuppressDependenciesWhenPacking` will prevent NuGet from taking your analyzer dependencies into account when it is being installed in a project. This most common use-case here would be `FSharp.Core`.
`TargetsForTfmSpecificContentInPackage` will make sure the `_AddAnalyzersToOutput` runs to copy the assembly into the `analyzers/dotnet/fs` folder.
`IncludeBuildOutput` will ensure your `*.nupkg` doesn't have a `lib` folder.
Simply run `dotnet pack --configuration Release` against the analyzer project to get a nuget package and publish it with
```shell
dotnet nuget push {NugetPackageFullPath} -s nuget.org -k {NugetApiKey}
```
### Third-party dependencies
However, the story is different and slightly more complicated when your analyzer package has third-party dependencies also coming from nuget. Since the SDK dynamically loads the package assemblies (`.dll` files), the assemblies of the dependencies have to be right *next* to the main assembly of the analyzer. Using `dotnet pack` will **not** include these dependencies into the output Nuget package. More specifically, the `./lib/net6.0` directory of the nuget package must have all the required assemblies, also those from third-party packages. In order to package the analyzer properly with all the assemblies, you need to take the output you get from running:
```shell
Expand Down Expand Up @@ -217,7 +190,7 @@ Target.create
)

(**
[Previous]({{fsdocs-previous-page-link}})
[Next]({{fsdocs-next-page-link}})
*)
2 changes: 1 addition & 1 deletion docs/content/Programmatic access.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
---
category: end-users
categoryindex: 1
index: 3
index: 4
---
# Programmatically running an analyzer
Expand Down
2 changes: 1 addition & 1 deletion docs/content/Running during CI.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
category: end-users
categoryindex: 1
index: 5
index: 6
---

# Running analyzers during continuous integration
Expand Down
2 changes: 1 addition & 1 deletion docs/content/Unit Testing.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
---
category: end-users
categoryindex: 1
index: 4
index: 5
---
# Unit testing an analyzer
Expand Down
6 changes: 5 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ dotnet run --project src\FSharp.Analyzers.Cli\FSharp.Analyzers.Cli.fsproj -- --p

You can also set up a run configuration of FSharp.Analyzers.Cli in your favorite IDE using similar arguments. This also allows you to debug FSharp.Analyzers.Cli.

## Using Analyzers

Checkout our [Getting Started](https://ionide.io/FSharp.Analyzers.SDK/content/Getting%20Started%20Using.html) guide for analyzer users!

## Writing Analyzers

Checkout our [Getting Started](content/Getting%20Started.html) guide!
Checkout our [Getting Started](https://ionide.io/FSharp.Analyzers.SDK/content/Getting%20Started%20Writing.html) guide for analyzer authors!

## How to contribute

Expand Down

0 comments on commit 4d5ce26

Please sign in to comment.