From 03808f7fcbc281833983109689f3c57f45916fc3 Mon Sep 17 00:00:00 2001 From: nojaf Date: Wed, 20 Sep 2023 08:59:46 +0200 Subject: [PATCH 1/2] Extract any duplicatish code from the main README. --- README.md | 99 ++------------------- docs/content/Getting Started.fsx | 143 +++++++++++++++++++++++++------ docs/index.md | 98 +-------------------- docs/style.css | 4 + 4 files changed, 132 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index 5e25dcc..29fa049 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# FSharp.Analyzers.SDK +# Ionide FSharp.Analyzers.SDK Library used for building custom analyzers for FSAC / F# editors. @@ -8,110 +8,21 @@ F# analyzers are live, real-time, project based plugins that enables to diagnose 1. Install the .NET SDK version specified in `global.json` 2. `dotnet tool restore` -2. Open and build in your favorite IDE, or use `dotnet build` +3. Open and build in your favorite IDE, or use `dotnet build` ## How to run sample 1. `dotnet build -c Release` -2. +2. Run the console application: + ```shell dotnet run --project src\FSharp.Analyzers.Cli\FSharp.Analyzers.Cli.fsproj -- --project ./samples/OptionAnalyzer/OptionAnalyzer.fsproj --analyzers-path ./samples/OptionAnalyzer/bin/Release --verbose ``` - 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. ## Writing Analyzers -Analyzers that are consumed by this SDK and from Ionide are simply .NET core class libraries. These class libraries expose a *value* of type `Analyzer` which is effectively a function that has input of type `Context` and returns a list of `Message` records: -```fsharp -module BadCodeAnalyzer - -open FSharp.Analyzers.SDK - -[] -let badCodeAnalyzer : Analyzer = - fun (context: Context) -> - // inspect context to determine the error/warning messages - [ ] -``` -Notice how we expose the function `BadCodeAnalyzer.badCodeAnalyzer` with an attribute `[]` that allows the SDK to detect the function. The input `Context` is a record that contains information about a single F# file such as the typed AST, the AST, the file content, the file name and more. The SDK runs this function against all files of a project during editing. The output messages that come out of the function are eventually used by Ionide to highlight the inspected code as a warning or error depending on the `Severity` level of each message. - -Analyzers can also be named which allows for better logging if something went wrong while using the SDK from Ionide: -```fs -[] -let badCodeAnalyzer : Analyzer = - fun (context: Context) -> - // inspect context to determine the error/warning messages - [ ] -``` -### Analyzer Requirements - -Analyzers are .NET core class libraries and they are distributed as such. However, since the SDK relies on dynamically loading the analyzers during runtime, there are some requirements to get them to work properly: - - The analyzer class library has to target the `net6.0` framework - - The analyzer has to reference the latest `FSharp.Analyzers.SDK` (at least the version used by FsAutoComplete which is subsequently used by Ionide) - -### 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. Simply run `dotnet pack --configuration Release` against the analyzer project to get a nuget package and publish it with - -``` -dotnet nuget push {NugetPackageFullPath} -s nuget.org -k {NugetApiKey} -``` - -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 has be there *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: -``` -dotnet publish --configuration Release --framework net6.0 -``` -against the analyzer project and put every file from that output into the `./lib/net6.0` directory of the nuget package. This requires some manual work by unzipping the nuget package first (because it is just an archive), modifying the directories then zipping the package again. It can be done using a FAKE build target to automate the work: -```fs -// make ZipFile available -#r "System.IO.Compression.FileSystem.dll" - -let releaseNotes = ReleaseNotes.load "RELEASE_NOTES.md" - -Target.create "PackAnalyzer" (fun _ -> - let analyzerProject = "src" "BadCodeAnalyzer" - let args = - [ - "pack" - "--configuration Release" - sprintf "/p:PackageVersion=%s" releaseNotes.NugetVersion - sprintf "/p:PackageReleaseNotes=\"%s\"" (String.concat "\n" releaseNotes.Notes) - sprintf "--output %s" (__SOURCE_DIRECTORY__ "dist") - ] - - // create initial nuget package - let exitCode = Shell.Exec("dotnet", String.concat " " args, analyzerProject) - if exitCode <> 0 then - failwith "dotnet pack failed" - else - match Shell.Exec("dotnet", "publish --configuration Release --framework net6.0", analyzerProject) with - | 0 -> - let nupkg = - System.IO.Directory.GetFiles(__SOURCE_DIRECTORY__ "dist") - |> Seq.head - |> IO.Path.GetFullPath - - let nugetParent = DirectoryInfo(nupkg).Parent.FullName - let nugetFileName = IO.Path.GetFileNameWithoutExtension(nupkg) - - let publishPath = analyzerProject "bin" "Release" "net6.0" "publish" - // Unzip the nuget - ZipFile.ExtractToDirectory(nupkg, nugetParent nugetFileName) - // delete the initial nuget package - File.Delete nupkg - // remove stuff from ./lib/net6.0 - Shell.deleteDir (nugetParent nugetFileName "lib" "net6.0") - // move the output of publish folder into the ./lib/net6.0 directory - Shell.copyDir (nugetParent nugetFileName "lib" "net6.0") publishPath (fun _ -> true) - // re-create the nuget package - ZipFile.CreateFromDirectory(nugetParent nugetFileName, nupkg) - // delete intermediate directory - Shell.deleteDir(nugetParent nugetFileName) - | _ -> - failwith "dotnet publish failed" -) -``` +Checkout our [Getting Started](https://ionide.io/FSharp.Analyzers.SDK/content/Getting%20Started.html) guide! ## How to contribute diff --git a/docs/content/Getting Started.fsx b/docs/content/Getting Started.fsx index 5b00166..79bf7b3 100644 --- a/docs/content/Getting Started.fsx +++ b/docs/content/Getting Started.fsx @@ -7,6 +7,11 @@ index: 1 # Getting started +## Premise + +Analyzers that are consumed by this SDK and from Ionide are simply .NET core class libraries. +These class libraries expose a *value* of type [Analyzer<'TContext>](../reference/fsharp-analyzers-sdk-analyzer-1.html) which is effectively a function that has input of type [Context](../reference/fsharp-analyzers-sdk-context.html) and returns a list of [Message](../reference/fsharp-analyzers-sdk-message.html) records. + ## Create project Create a new class library targeting `net6.0` @@ -23,6 +28,8 @@ Add a reference to the analyzers SDK: dotnet add package FSharp.Analyzers.SDK ``` +⚠️ Note: To utilize the analyzers in FsAutoComplete (which is subsequently utilized by Ionide), it is essential to ensure that the SDK version matches correctly. + ```shell paket add FSharp.Analyzers.SDK ``` @@ -49,31 +56,39 @@ In the following example we will be #r "../../src/FSharp.Analyzers.Cli/bin/Release/net6.0/FSharp.Analyzers.SDK.dll" #r "../../src/FSharp.Analyzers.Cli/bin/Release/net6.0/FSharp.Compiler.Service.dll" (** *) +open FSharp.Analyzers.SDK + +// This attribute is required and needs to match the correct context type! +[] +let optionValueAnalyzer: Analyzer = + fun (context: CliContext) -> + async { + // inspect context to determine the error/warning messages + // A potential implementation might traverse the untyped syntax tree + // to find any references of `Option.Value` + return + [ + { + Type = "Option.Value analyzer" + Message = "Option.Value shouldn't be used" + Code = "OV001" + Severity = Warning + Range = FSharp.Compiler.Text.Range.Zero + Fixes = [] + } + ] + } + +(** +Analyzers can also be named which allows for better logging if something went wrong while using the SDK from Ionide: +*) -module OptionAnalyzer = - - open FSharp.Analyzers.SDK - - // This attribute is required and needs to match the correct context type! - [] - let optionValueAnalyzer: Analyzer = - fun (context: CliContext) -> - async { - // inspect context to determine the error/warning messages - // A potential implementation might traverse the untyped syntax tree - // to find any references of `Option.Value` - return - [ - { - Type = "Option.Value analyzer" - Message = "Option.Value shouldn't be used" - Code = "OV001" - Severity = Warning - Range = FSharp.Compiler.Text.Range.Zero - Fixes = [] - } - ] - } +[] +let badCodeAnalyzer: Analyzer = + fun (context: EditorContext) -> + async { // inspect context to determine the error/warning messages + return [] + } (** ## Running your first analyzer @@ -89,6 +104,86 @@ dotnet tool install --global fsharp-analyzers fsharp-analyzers --project YourProject.fsproj --analyzers-path ./OptionAnalyzer/bin/Release --verbose ``` +### 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. +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} +``` + +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 has be there *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 +dotnet publish --configuration Release --framework net6.0 +``` + +against the analyzer project and put every file from that output into the `./lib/net6.0` directory of the nuget package. This requires some manual work by unzipping the nuget package first (because it is just an archive), modifying the directories then zipping the package again. It can be done using a FAKE build target to automate the work: +*) + +// make ZipFile available +#r "System.IO.Compression.FileSystem.dll" +#r "nuget: Fake.Core.Target, 6.0.0" +#r "nuget: Fake.Core.ReleaseNotes, 6.0.0" +#r "nuget: Fake.IO.Zip, 6.0.0" + +open System.IO +open System.IO.Compression +open Fake.Core +open Fake.IO +open Fake.IO.FileSystemOperators + +let releaseNotes = ReleaseNotes.load "RELEASE_NOTES.md" + +Target.create + "PackAnalyzer" + (fun _ -> + let analyzerProject = "src" "BadCodeAnalyzer" + + let args = + [ + "pack" + "--configuration Release" + sprintf "/p:PackageVersion=%s" releaseNotes.NugetVersion + sprintf "/p:PackageReleaseNotes=\"%s\"" (String.concat "\n" releaseNotes.Notes) + sprintf "--output %s" (__SOURCE_DIRECTORY__ "dist") + ] + + // create initial nuget package + let exitCode = Shell.Exec("dotnet", String.concat " " args, analyzerProject) + + if exitCode <> 0 then + failwith "dotnet pack failed" + else + match Shell.Exec("dotnet", "publish --configuration Release --framework net6.0", analyzerProject) with + | 0 -> + let nupkg = + System.IO.Directory.GetFiles(__SOURCE_DIRECTORY__ "dist") + |> Seq.head + |> Path.GetFullPath + + let nugetParent = DirectoryInfo(nupkg).Parent.FullName + let nugetFileName = Path.GetFileNameWithoutExtension(nupkg) + + let publishPath = analyzerProject "bin" "Release" "net6.0" "publish" + // Unzip the nuget + ZipFile.ExtractToDirectory(nupkg, nugetParent nugetFileName) + // delete the initial nuget package + File.Delete nupkg + // remove stuff from ./lib/net6.0 + Shell.deleteDir (nugetParent nugetFileName "lib" "net6.0") + // move the output of publish folder into the ./lib/net6.0 directory + Shell.copyDir (nugetParent nugetFileName "lib" "net6.0") publishPath (fun _ -> true) + // re-create the nuget package + ZipFile.CreateFromDirectory(nugetParent nugetFileName, nupkg) + // delete intermediate directory + Shell.deleteDir (nugetParent nugetFileName) + | _ -> failwith "dotnet publish failed" + ) + +(** + [Next]({{fsdocs-next-page-link}}) *) diff --git a/docs/index.md b/docs/index.md index dc3b0f7..758b975 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,111 +8,21 @@ F# analyzers are live, real-time, project based plugins that enables to diagnose 1. Install the .NET SDK version specified in `global.json` 2. `dotnet tool restore` -2. Open and build in your favorite IDE, or use `dotnet build` +3. Open and build in your favorite IDE, or use `dotnet build` ## How to run sample 1. `dotnet build -c Release` -2. Run +2. Run the console application: ```shell dotnet run --project src\FSharp.Analyzers.Cli\FSharp.Analyzers.Cli.fsproj -- --project ./samples/OptionAnalyzer/OptionAnalyzer.fsproj --analyzers-path ./samples/OptionAnalyzer/bin/Release --verbose ``` - 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. ## Writing Analyzers -Analyzers that are consumed by this SDK and from Ionide are simply .NET core class libraries. These class libraries expose a *value* of type `Analyzer` which is effectively a function that has input of type `Context` and returns a list of `Message` records: -```fsharp -module BadCodeAnalyzer - -open FSharp.Analyzers.SDK - -[] -let badCodeAnalyzer : Analyzer = - fun (context: CliContext) -> - // inspect context to determine the error/warning messages - [ ] -``` -Notice how we expose the function `BadCodeAnalyzer.badCodeAnalyzer` with an attribute `[]` that allows the SDK to detect the function. The input `Context` is a record that contains information about a single F# file such as the typed AST, the AST, the file content, the file name and more. The SDK runs this function against all files of a project during editing. The output messages that come out of the function are eventually used by Ionide to highlight the inspected code as a warning or error depending on the `Severity` level of each message. - -Analyzers can also be named which allows for better logging if something went wrong while using the SDK from Ionide: -```fs -[] -let badCodeAnalyzer : Analyzer = - fun (context: CliContext) -> - // inspect context to determine the error/warning messages - [ ] -``` -### Analyzer Requirements - -Analyzers are .NET core class libraries and they are distributed as such. However, since the SDK relies on dynamically loading the analyzers during runtime, there are some requirements to get them to work properly: -- The analyzer class library has to target the `net6.0` framework -- The analyzer has to reference the latest `FSharp.Analyzers.SDK` (at least the version used by FsAutoComplete which is subsequently used by Ionide) - -### 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. 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} -``` - -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 has be there *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 -dotnet publish --configuration Release --framework net6.0 -``` -against the analyzer project and put every file from that output into the `./lib/net6.0` directory of the nuget package. This requires some manual work by unzipping the nuget package first (because it is just an archive), modifying the directories then zipping the package again. It can be done using a FAKE build target to automate the work: -```fs -// make ZipFile available -#r "System.IO.Compression.FileSystem.dll" - -let releaseNotes = ReleaseNotes.load "RELEASE_NOTES.md" - -Target.create "PackAnalyzer" (fun _ -> - let analyzerProject = "src" "BadCodeAnalyzer" - let args = - [ - "pack" - "--configuration Release" - sprintf "/p:PackageVersion=%s" releaseNotes.NugetVersion - sprintf "/p:PackageReleaseNotes=\"%s\"" (String.concat "\n" releaseNotes.Notes) - sprintf "--output %s" (__SOURCE_DIRECTORY__ "dist") - ] - - // create initial nuget package - let exitCode = Shell.Exec("dotnet", String.concat " " args, analyzerProject) - if exitCode <> 0 then - failwith "dotnet pack failed" - else - match Shell.Exec("dotnet", "publish --configuration Release --framework net6.0", analyzerProject) with - | 0 -> - let nupkg = - System.IO.Directory.GetFiles(__SOURCE_DIRECTORY__ "dist") - |> Seq.head - |> IO.Path.GetFullPath - - let nugetParent = DirectoryInfo(nupkg).Parent.FullName - let nugetFileName = IO.Path.GetFileNameWithoutExtension(nupkg) - - let publishPath = analyzerProject "bin" "Release" "net6.0" "publish" - // Unzip the nuget - ZipFile.ExtractToDirectory(nupkg, nugetParent nugetFileName) - // delete the initial nuget package - File.Delete nupkg - // remove stuff from ./lib/net6.0 - Shell.deleteDir (nugetParent nugetFileName "lib" "net6.0") - // move the output of publish folder into the ./lib/net6.0 directory - Shell.copyDir (nugetParent nugetFileName "lib" "net6.0") publishPath (fun _ -> true) - // re-create the nuget package - ZipFile.CreateFromDirectory(nugetParent nugetFileName, nupkg) - // delete intermediate directory - Shell.deleteDir(nugetParent nugetFileName) - | _ -> - failwith "dotnet publish failed" -) -``` +Checkout our [Getting Started](content/Getting%20Started.html) guide! ## How to contribute @@ -138,4 +48,4 @@ the project and submit pull requests. The library is available under [MIT license](https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK/blob/master/LICENSE.md), which allows modification and redistribution for both commercial and non-commercial purposes. -[Next]({{fsdocs-next-page-link}}) \ No newline at end of file +[Next]({{fsdocs-next-page-link}}) diff --git a/docs/style.css b/docs/style.css index 9544ee3..b2afe06 100644 --- a/docs/style.css +++ b/docs/style.css @@ -383,6 +383,10 @@ p { line-height: 1.8; } +ol li { + margin-bottom: var(--unit-2); +} + /* Navigation links at the end of a page */ #fsdocs-content > p:last-of-type { display: flex; From f62e8da15198b3d3580a697f36fdb808a6b0edd5 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Wed, 20 Sep 2023 09:48:55 +0200 Subject: [PATCH 2/2] Update docs/content/Getting Started.fsx Co-authored-by: dawe --- docs/content/Getting Started.fsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/Getting Started.fsx b/docs/content/Getting Started.fsx index 79bf7b3..ce458eb 100644 --- a/docs/content/Getting Started.fsx +++ b/docs/content/Getting Started.fsx @@ -113,7 +113,7 @@ Simply run `dotnet pack --configuration Release` against the analyzer project to dotnet nuget push {NugetPackageFullPath} -s nuget.org -k {NugetApiKey} ``` -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 has be there *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: +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 dotnet publish --configuration Release --framework net6.0