Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Housekeeping: Format using Fantomas and generally clean up code #56

Closed
wants to merge 16 commits into from
9 changes: 8 additions & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {}
"tools": {
"fantomas": {
"version": "5.1.5",
"commands": [
"fantomas"
]
}
}
}
20 changes: 20 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
root = true

[*]
charset = utf-8
max_line_length = 120
indent_style = space
indent_size = 2
end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true

[{nuget.config,*.*proj,*.props,*.xml}]
end_tag_newline = true
self_closing_tag_style = space

[*.fs]
indent_size = 4
fsharp_multiline_block_brackets_on_same_column = true
fsharp_experimental_stroustrup_style = true
fsharp_keep_max_number_of_blank_lines = 2
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ jobs:
- name: Restore tools
run: dotnet tool restore

- name: Check code format
run: dotnet fantomas --check --recurse .

- name: Restore dependencies
run: dotnet restore --locked-mode

Expand Down
2 changes: 2 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarnOn>FS1182;$(WarnOn)</WarnOn>
</PropertyGroup>

<ItemGroup>
Expand Down
113 changes: 73 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Library used for building custom analyzers for FSAC / F# editors.

F# analyzers are live, real-time, project based plugins that enables to diagnose source code and surface custom errors, warnings and code fixes into editor. Read more about analyzers here - https://medium.com/lambda-factory/introducing-f-analyzers-772487889429
F# analyzers are live, real-time, project based plugins that enables to diagnose source code and surface custom errors,
warnings and code fixes into editor. Read more about analyzers
here - https://medium.com/lambda-factory/introducing-f-analyzers-772487889429

## How to build

Expand All @@ -11,53 +13,79 @@ F# analyzers are live, real-time, project based plugins that enables to diagnose
2. Open and build in your favorite IDE, or use `dotnet build`

## How to run sample

1. `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.
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
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
open FSharp.Analyzers.SDK

[<Analyzer>]
let badCodeAnalyzer : Analyzer =
fun (context: Context) ->
// inspect context to determine the error/warning messages
[ ]
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 `[<Analyzer>]` 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.

Notice how we expose the function `BadCodeAnalyzer.badCodeAnalyzer` with an attribute `[<Analyzer>]` 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
[<Analyzer "BadCodeAnalyzer">]
let badCodeAnalyzer : Analyzer =
fun (context: Context) ->
// inspect context to determine the error/warning messages
[ ]
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)
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
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:
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:

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"
Expand All @@ -66,17 +94,18 @@ 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")
]

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
Expand All @@ -102,32 +131,36 @@ Target.create "PackAnalyzer" (fun _ ->
// re-create the nuget package
ZipFile.CreateFromDirectory(nugetParent </> nugetFileName, nupkg)
// delete intermediate directory
Shell.deleteDir(nugetParent </> nugetFileName)
| _ ->
failwith "dotnet publish failed"
)
Shell.deleteDir (nugetParent </> nugetFileName)
| _ -> failwith "dotnet publish failed")
```

## How to contribute

*Imposter syndrome disclaimer*: I want your help. No really, I do.

There might be a little voice inside that tells you you're not ready; that you need to do one more tutorial, or learn another framework, or write a few more blog posts before you can help me with this project.
There might be a little voice inside that tells you you're not ready; that you need to do one more tutorial, or learn
another framework, or write a few more blog posts before you can help me with this project.

I assure you, that's not the case.

This project has some clear Contribution Guidelines and expectations that you can [read here](https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK/blob/master/CONTRIBUTING.md).
This project has some clear Contribution Guidelines and expectations that you
can [read here](https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK/blob/master/CONTRIBUTING.md).

The contribution guidelines outline the process that you'll need to follow to get a patch merged. By making expectations and process explicit, I hope it will make it easier for you to contribute.
The contribution guidelines outline the process that you'll need to follow to get a patch merged. By making expectations
and process explicit, I hope it will make it easier for you to contribute.

And you don't just have to write code. You can help out by writing documentation, tests, or even by giving feedback about this work. (And yes, that includes giving feedback about the contribution guidelines.)
And you don't just have to write code. You can help out by writing documentation, tests, or even by giving feedback
about this work. (And yes, that includes giving feedback about the contribution guidelines.)

Thank you for contributing!


## Contributing and copyright

The project is hosted on [GitHub](https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK) where you can [report issues](https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK/issues), fork
the project and submit pull requests.
The project is hosted on [GitHub](https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK) where you
can [report issues](https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK/issues), fork 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.
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.
Loading