Code Monkey home page Code Monkey logo

fsharp.analyzers.sdk's Introduction

Ionide FSharp.Analyzers.SDK

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

How to build

  1. Install the .NET SDK version specified in global.json
  2. dotnet tool restore
  3. Open and build in your favorite IDE, or use dotnet build

How to run sample

  1. dotnet build -c Release
  2. Run the console application:
dotnet run --project src\FSharp.Analyzers.Cli\FSharp.Analyzers.Cli.fsproj -- --project ./samples/OptionAnalyzer/OptionAnalyzer.fsproj --analyzers-path ./samples/OptionAnalyzer/bin/Release --verbosity d

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 guide for analyzer users!

Writing Analyzers

Checkout our Getting Started guide for analyzer authors!

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.

I assure you, that's not the case.

This project has some clear Contribution Guidelines and expectations that you can read here.

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.)

Thank you for contributing!

Contributing and copyright

The project is hosted on GitHub where you can report issues, fork the project and submit pull requests.

The library is available under MIT license, which allows modification and redistribution for both commercial and non-commercial purposes.

fsharp.analyzers.sdk's People

Contributors

1eyewonder avatar baronfel avatar cmeeren avatar damianreeves avatar dawedawe avatar dependabot[bot] avatar enovales avatar jcmrva avatar krzysztof-cieslak avatar nojaf avatar numpsy avatar smaug123 avatar tforkmann avatar theangrybyrd avatar thorium avatar zaid-ajaj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fsharp.analyzers.sdk's Issues

One analyzer with multiple messages/codes, or multiple analyzers?

I'm experimenting with an F# linter, Flinter. Like most linters, it will have quite a lot of rules.

Should I implement each rule as a separate analyzer, or should I implement one analyzer for the entire linter and then delegate work internally? I'm currently leaning toward the former, but that decision is not based on a lot of information.

This may need to be seen in light of other issues like #8, and may not make sense to consider at the moment. Still, I wanted to post the question here, at least just for future consideration and to make it easier to "keep in the back of the head" while working on other features like #8.

Any input is welcome.

Allow disabling analyzers for line/file/etc.

Sorry if I'm missing something obvious, but I can't find a way to disable an analyzer using e.g. a code comment.

I'm after something like what FSharpLint allows, like:

  • // fsharplint:disable TypePrefixing Hints
  • // fsharplint:disable-next-line TypePrefixing

Again, I'm coming at this from a linter perspective. This seems to me to be a crucial feature.

Cli option --fail-on-all-warnings

Is your feature request related to a problem? Please describe.
Currently, to fail on a warning, you have to list all the analyzer codes you want to fail on. If you are using many analyzers and you want to fail on all of them, that can be cumbersome.

Describe the solution you'd like
Provide a --fail-on-all-warnings Cli option.

Describe alternatives you've considered
Support globbing of analyzer codes.

Additional context
As always, I just raise this issue to keep track of it.

Rename to FSharp.Analysers.SDK ?

Describe the bug
A possible spelling mistake in the product name.

To Reproduce
Install en-UK language pack, and spell-check the project name.

Expected behaviour
No red line should appear.

Does the SDK discriminate between debug/release mode? Does it matter?

While working on an analyzer that uses the TAST, it occurred to me that maybe the TAST could be different in debug/release mode (e.g. due to optimizations). Is this the case? If so, how can I test my analyzer in debug vs. release mode, and what determines which mode FSharp.Analyzers.SDK uses?

Accept multiple folders as analyzer input path

Is your feature request related to a problem? Please describe.
We currently are playing around with integrating both analyzers from a NuGet package and are considering writing some very laser-focused custom analyzers that only make sense for our current project.

Describe the solution you'd like
Allow multiple --analyzers-path arguments.
So we can do something like --analyzers-path ./download-nuget-folder --analyzers-path ./src/LocalAnalyzer/bin/Debug

Describe alternatives you've considered
One could copy the local analyzer to another folder of course. Doing a dotnet publish wouldn't be a stretch I guess.

Additional context
Might play around with this idea in the weeks to come in the Fantomas repository.

Current Analyzers CLI needs to bundle msbuild locator

Describe the bug
running the current (0.6.0) release of the analyzer cli fails with the following error:

Unhandled exception. System.TypeInitializationException: The type initializer for 'Program' threw an exception.
 ---> System.TypeInitializationException: The type initializer for '<StartupCode$FSharp-Analyzers-Cli>.$Program' threw an exception.
 ---> System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Build.Locator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9dff12846e04bfbd'. The system cannot find the file specified.

File name: 'Microsoft.Build.Locator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9dff12846e04bfbd'
   at Ionide.ProjInfo.Init.init()
   at <StartupCode$FSharp-Analyzers-Cli>.$Program..cctor() in /Users/chethusk/oss/FSharp.Analyzers.SDK/src/FSharp.Analyzers.Cli/Program.fs:line 22
   --- End of inner exception stack trace ---
   at Program..cctor()
   --- End of inner exception stack trace ---
   at Program.main(String[] argv) in /Users/chethusk/oss/FSharp.Analyzers.SDK/src/FSharp.Analyzers.Cli/Program.fs:line 169

We need to bundle the locator with this tool, I believe.

RunSample failed to run on macOS

Describe the bug
Failed to run dotnet fake build -t RunSample on macOS.

To Reproduce
Steps to reproduce the behaviour:

  1. dotnet fake build -t RunSample

Expected behaviour
Successfully run

Screenshots
Following is a log of dotnet fake build -t RunSample on my Machine.
sample.log

Environment (please complete the following information):

  • OS: macOS 10.15.7
  • Ionide version:
  • VSCode version:
  • dotnet SDK version: 5.0.201
  • mono / .Net Framework version:

Additional context
On CentOS7 (dotnet SDK: 5.0.100), dotnet fake build -t RunSample works as expected

Can analyzers be run as a dotnet tool?

If I create an analyzer using this SDK, can it automatically be run using a dotnet tool? Or does this tool only provide integration with Ionide, and everything else (including running through a CLI) I will need to implement myself?

Use same severity levels as Roslyn analyzers

The Roslyn severity levels are (showing the .editorconfig values below):

  • error
  • warning
  • suggestion ("Info" in VS)
  • silent ("Hidden in VS; as far as I know, this will show quick-fixes in the IDE, but hide the squiggles/message)
  • none (completely disabled, including quick-fixes)
  • default (the default set by the analyzer for that rule)

We could consider using the same values. Currently, we only have Error, Warning, and Info (which could be renamed to "Suggestion"; I find that a more descriptive name, and it fits the Roslyn editorconfig values).

Note that Default of course is just for configuration; analyzers should not be able to set this.

Add whitelisting parameter to CLI

Is your feature request related to a problem? Please describe.
Currently, it's only possible to ignore files with a pattern when using the CLI. For CI stuff it would be nice to have a whitelisting parameter in the CLI

Describe the solution you'd like
Add a whitelisting parameter to the CLI.

Describe alternatives you've considered
Sure, the ignore pattern can be used to get something like the requestet feature. But it's just harder then it needs to be.

P.S. Of course, I don't expect other people to do this work for me. It's more a note to myself, to keep track.

Modernize/simplify tooling/CI/packaging?

As may be evident from recent issues and comments, I'd like to help out in the development if FSharp.Analyzers.SDK. In that light, I am wondering if you are open to changes that I think would make it simpler to contribute and make at least me feel more at home.

I am willing to make any or all of these changes myself through PRs.

In short, I would like to be able to open the solution in VS/Rider/VSCode and have it "just work" using standard .NET tooling, which is really good nowadays (better than some years ago). Moreover, I would like to not have the additional complexity of Fake and Paket to think about.

These suggestions are based on several years of experience with several other F# OSS packages I have created and maintain, as well as tens of F# projects at work.

I realize these may be controversial suggestions and fully expect to be turned down, but given the lack of activity in this repo (last commit over 8 months ago), the current maintainers may want to lower the barrier of entry for contibutions or even bring on additional maintainers, so I'd like to ask anyway.

  • Use NuGet (with lock files) instead of Paket. I see no compelling reason to use Paket now that NuGet has become so good in recent years, with support for lock files. NuGet is more compatible with stock tooling (e.g. VS), and is the "default" for the .NET ecosystem.
  • Remove Fake, unless it does something that is difficult to accomplish otherwise. When it comes to packaging and releasing, I have yet to experience problems with stock GitHub and dotnet CLI functionality, assembly/package information in project files, etc. For releases, I keep a manually curated RELEASE_NOTES.md and have always found this more than adequate, and if I need more documentation than fits in the readme, I have used a separate easily searchable DOCUMENTATION.md file like Giraffe does.
  • Install Fantomas as a dotnet tool and reformat the code, then enforce code style during CI. I do this in Flinter with this config, which is basically the standard except Stroustrup brackets are enabled (which I find gives more readable code that is easier to edit and has less line damage in diffs).
  • Build on GitHub. Why use an external service when everything you need is right here? (I realize that may not have been the case when the project was set up, of course.)
  • Set up a Directory.Build.props that looks like this. It uses DotNet.ReproducibleBuilds, enables warnings as errors, and warns (errors) on unused values.
  • Add a strict global.json to ensure repeatable builds (minor versions should be compatible, but bugs in FSharp.Core packaging have broken my builds even on minor updates to dotnet tooling on build agents)
  • Add nuget.config to ensure repeatable builds
  • Probably some other stuff, too; I haven't looked too closely at all the files.

Default branch name

As many F# projects have moved to main for the default branch name, I would find it convenient to switch here as well.

Community analyzers project

Would it make sense to have a community project with analyzers in this repository?
It should be a set of generic analyzers generally conceived as best practices when writing F# code.

Some motivations to have this:

  • Writing your analyzer, packaging it, shipping it and consuming it might be a too big step for most people. If there were a safe community space where an individual analyzer could be contributed, it would lower the threshold.
  • We could have a nightly branch and use preview FCS releases to identify breaking changes quite early.
  • It would also allow us to test these analyzers every time someone contributes to the SDK.
  • I believe this would be insightful to see what building blocks are missing right now.

Some downsides:

  • This might bring us into some discussion territory on whether proposals are generic enough.
  • Changing the SDK would mean fixing a bunch of analyzers.
  • The barrier to contributing an analyzer would still somewhat high. To save ourselves some trouble, the minimum requirements would be to have: an analyzer, a well-covering test suite and a documentation page entry.
  • The same folks would need to maintain it.

What is Message.Type used for?

What is Message.Type used for (or intended to be used for in the future)? I'm unsure what to set it to for my linter rules.

How to use

Hello @Krzysztof-Cieslak,

This project looks very interesting ๐Ÿ˜„ is this ready for use with Ionide? Is there any sample out there to checkout?

What's an analyzer?

I arrived here from F# weekly and am trying to understand more about this project. Clearly it's for building analyzers. What's an analyzer? ;)

Are they like roslyn analyzers for F#?

How do you use one? Did they exist before FSharp.Analyzers.SDK came along?

Say I have an idea for a new squiggle in my F# projects, and I manage to build a custom analyzer. How do I use that analyzer in my F# project?

Can you run them with a command-line tool? (e.g. dotnet analyze-fs MyCustomAnalyzer my-project.fsproj)

Sorry for the barrage of questions! Hopefully some improvement to the readme attracts more users and contributors.

What's the story for breaking changes in FCS when using FSharp.Analyzers.SDK?

If I write an analyzer using FSharp.Analyzers.SDK, is it possible to run this analyzer when Ionide is updated to use new versions of FSharp.Analyzers.SDK (which may use a new version of FCS with binary breaking changes)? Or will I need to always keep my analyzer up-to-date with the latest version of this SDK, in order for it to be usable in new versions of Ionide?

Proposal: More flexible Analyzer probing rules

I propose that we change the current Analyzer discovery mechanism to be more like the probing rules for type providers, in an effort to aid discoverability and usability of analzyers.

Currently, the analzyer probing logic is described as:

  • from a given directory
  • look in that directory and all subdirectories
  • for any dlls named Analyzer.dll

This is brittle for a few reasons:

  • it requires the analyzer assemblies to live at a common subdirectory (which often leads folks to use paket, which we may not want to force)
  • it can load an analyzer incorrectly if the analyzer has multiple dlls for different target framework monikers
  • it can miss analyzers if the author didn't name them correctly

Describe the solution you'd like

I propose that we adopt a directory structure similar to that of type providers, something like (assuming a package root of /)

/analyzers/<tfm>
/analyzers/runtime/<rid>-<architecture>

For a complete example see the nuget package documentation here.

The idea would be to mimic that logic, underneath a directory that specifically opts the package into analyzers.

Assuming this, the logic for discovering analyzers in tooling would be

  • from the set of referenced packages in a project
  • filter down to those that contain analyzers
  • filter down to the correct TFM directories for the tool being run and load the dlls from those directories
  • from the assemblies loaded, probe for members of type Analyzer or equivalent as we do today

Describe alternatives you've considered
An alternative would be to not do this and attempt to probe all of project dependencies, not just loading dlls from a folder. I disagree with this approach due to the immense amount of probing work that would be done, especially given the current logic of just taking every dll from every subfolder of the probed folder root.

Additional context
Add any other context or screenshots about the feature request here.

Exclude analyzers

Is your feature request related to a problem? Please describe.
We are currently loading a binary with a set of analyzers and would like to exclude certain ones from running.

Describe the solution you'd like
Adding a flag like --exclude-analyzer MyAnalyzerName (multiple allowed)

Describe alternatives you've considered
If you don't control the binary you load I'm not sure this is possible right now.

Additional context
We want to use an analyzer that is available on NuGet but comes with a set of analyzers we don't care about.

Support multiple project parameters in the Cli tool

Is your feature request related to a problem? Please describe.
Calling the Cli tool multiple times for a collection of projects (e.g. in a build pipeline for a solution) wastes some CPU cycles loading the same analyzers again and again.

Describe the solution you'd like
I suggest we support taking in multiple projects as Cli parameters, so analyzer DLLs are only loaded once and reused for all given projects.

Describe alternatives you've considered
Making DLL loading and reflection so fast, that it doesn't matter. But that's above my paygrade.

Additional context
As always, I don't expect other people to do the work, I raise the issue to keep track of it for myself.

Add a SDK.Tests package

Is your feature request related to a problem? Please describe.
With the growing number of analyzers, writing tests for it becomes fairly tricky. I came up with loading files/projects via my analyzers loader that copies most of the loading logic from the CLI project. This could get out of sync with the main repo.

Describe the solution you'd like

I think we should provide a package for analyzer authors to consume in their test projects to keep loading consistent. This would involve publishing the ProjectSystem, as listed in ionide/FsAutoComplete#564

Describe alternatives you've considered

Potentially splitting it into a file and consume it via paket's github dependencies.

Additional context

Analyzer report

Is your feature request related to a problem? Please describe.

I'm wondering if we could dump some sort of analyzer report (via a flag) that contains a list of the messages.
Perhaps it would be easy to have GitHub Actions pick this up

Describe the solution you'd like
Adding the --report flag to dump the messages in a text format (maybe JSON?)

Describe alternatives you've considered
/

Additional context
Could be useful in GitHub Actions. //cc @dawedawe

Make F# Analyzer API independent of FCS?

I've been thinking about the problem of binary compatibility for F# analyzers, in the context of trying to get F# analyzers available in all F# tooling.

This is a blocking problem for incorporation of analyzers into the Visual F# Tools. This applies even if those tools switch to FsAutoComplete - all analyzers must still be loadable into all future iterations of F# tooling.

The main issue is binary compatibility for the information being drawn from the FCS API (FSharpSymbol etc.). I am wondering if we might consider making this F# Analyzer API completely independent of FCS. When hosted, FSharpAutoComplete would provide necessary shims.

This would mean the F# Analyzer API would include abstract types like IFSharpSymbol, ISyntaxTree and so on. It would be a complete self-contained, zero-dependency facade over all the information analyzers typically require.

FCS could then take a dependency on this component (which I called FSharp.Compiler.Analyzers in the link above).

Add a `-p` flag to allow passing MSBuild properties through to the MSBuild evaluation portion of checking.

Is your feature request related to a problem? Please describe.

MSBuild/.NET CLI flags often change behavior - notably flags like -c, -r, --arch, --os, and -p. At minimum we need to allow for MSBuild properties to be passed in to allow for checking of AST shapes/compiler defines that actually match what the .NET CLI will compile against.

Describe the solution you'd like

Minimum:

  • -p|--property flag that can be applied multiple times and accepts MSBuild properties. These are key=value pairs.

Bonus - parity with the .NET CLI:

  • -r|--runtime flag for passing through the RuntimeIdentifier MSBuild property
  • --arch flag for inferring the OS portion of the RID only and explicitly specifying the architecture part
  • --os flag for inferring the architecture portion of the RID only and explicitly specifying the OS part
  • -c flag for passing through the Configuration MSBuild property

How to traverse AST in analyzers?

I am trying to create an analyzer using this SDK. (Specifically, a linter containing an analyzer for each rule, though that is beside the point here.)

As a proof of concept, I am trying to create an analyzer that returns a warning for any function parameter that starts with an uppercase letter.

To traverse the AST in the Context, I am trying to use SyntaxTraversal.Traverse (previously AstTraversal.Traverse), which is the only part of FSharp.Compiler.Service I could find that seems like it can help me traverse the AST without having to implement a set of mutually recursive, awfully long and noisy functions that match against all known parts of the AST and traverse all its sub-parts (as is implemented in FSharp.Compiler.Service itself as far as I can see, as well as in some other projects using FSharp,Analyzers.SDK).

However, I have no idea what to use for the pos parameter to Traverse, and I am having trouble traversing the whole source and not just the first declaration.

How can I most easily traverse the whole AST, scanning for what I'm after?

For clarity, here is my visitor. It visits bindings and creates a message for invalid parameters.

type private Visitor() =
    inherit SyntaxVisitorBase<Message list>()

    override this.VisitBinding(_, _, synBinding) =
        match synBinding with
        | SynBinding(_, _, _, _, _, _, SynValData(_, SynValInfo(curriedArgInfos, _), _), _, _, _, _, _) ->
            curriedArgInfos
            |> List.collect id
            |> List.choose (fun i -> i.Ident |> Option.filter isInvalid |> Option.map createMsg)
            |> Some

And here is how I call Traverse:

SyntaxTraversal.Traverse(Position.pos0, context.ParseTree, visitor)

(I even tried asking ChatGPT for help. While its F# knowledge is impressive, it unfortunately has little knowledge about how FSharp.Compiler.Service is supposed to be used.)

Allow analyzers to process .fsi files

Is your feature request related to a problem? Please describe.
Currently, .fsi files are filtered out before analyzers can process them.

Describe the solution you'd like
Remove the filter and let the analyzers decide what they want to do regarding .fsi files.

Describe alternatives you've considered
Some kind of configuration to control the filtering. But seems overkill for such a simple thing.

P.S. Of course, I don't expect other people to do this work for me. It's more a note to myself, to keep track.

Analyzer cannot use FSharp.Core 8

Describe the bug
When your analyzer is using a higher version of F# Core (say 8.0.100-beta.23475.2), it will fail at runtime during loading.

Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
File name: 'FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at System.Signature.GetSignature(Void* pCorSig, Int32 cCorSig, RuntimeFieldHandleInternal fieldHandle, IRuntimeMethodInfo methodHandle, RuntimeType declaringType)
   at System.Reflection.RuntimeMethodInfo.<get_Signature>g__LazyCreateSignature|25_0()
   at System.Reflection.RuntimeMethodInfo.get_ReturnType()
   at FSharp.Analyzers.SDK.Client.getAnalyzerFromMemberInfo@61[TContext,a](a mi) in /_//src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs:line 69
   at FSharp.Analyzers.SDK.Client.analyzerFromMember[TAnalyzerAttribute,TContext](String path, MemberInfo mi) in /_//src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs:line 88
   at [email protected](MemberInfo mi)
   at Microsoft.FSharp.Collections.Internal.IEnumerator.choose@240.System.Collections.IEnumerator.MoveNext() in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 252
   at Microsoft.FSharp.Collections.SeqModule.ToList[T](IEnumerable`1 source) in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 1003
   at <StartupCode$FSharp-Analyzers-SDK>[email protected](Type t)
   at Microsoft.FSharp.Collections.Internal.IEnumerator.map@128.DoMoveNext(b& curr) in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 131
   at Microsoft.FSharp.Collections.Internal.IEnumerator.MapEnumerator`1.System.Collections.IEnumerator.MoveNext() in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 113
   at Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.takeOuter@319[T,TResult](ConcatEnumerator`2 x, Unit unitVar0) in D:\a\_work\1\s\src\FSharp.Core\seqcore.fs:line 320
   at Microsoft.FSharp.Collections.Internal.IEnumerator.next@277[T](FSharpFunc`2 f, IEnumerator`1 e, FSharpRef`1 started, Unit unitVar0) in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 281
   at Microsoft.FSharp.Collections.Internal.IEnumerator.filter@267.System.Collections.IEnumerator.MoveNext() in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 283
   at Microsoft.FSharp.Collections.SeqModule.ToList[T](IEnumerable`1 source) in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 1003
   at FSharp.Analyzers.SDK.Client`2.LoadAnalyzers(String dir) in /_//src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs:line 150

To Reproduce

  <ItemGroup>
    <PackageReference Update="FSharp.Core" Version="8.0.100-beta.23475.2" />
    <PackageReference Include="FSharp.Analyzers.SDK" Version="0.16.0" />
  </ItemGroup>

Expected behaviour
Either we tell the user that the F# version should be 7.0.400 instead. Because that is the version the CLI tool is using. Or we find a way around this problem during loading?

We should at the very least provide a better UX.

Bump framework target to netcoreapp3.1

At present there's a restriction that the analyser much target netcoreapp2.0 whereas the latest is netcoreapp3.1.

Is there a reason that this restriction can't be loosened? I've hit a problem where I want to use FSharp.Control.AsyncSeq to convert IAsynEnumerable to AsyncSeq but that's only available in the netstandard2.1 package, which matches netcoreapp3.1, so I can't take a dependency on it.

Feature: Passing options through the context to named analyzers

Is your feature request related to a problem? Please describe.
The end user does not have any way to control the behavior the used analyzer within a project. Just like with code linters, it would great if the user could provide options for the analyzers on a per-project basis. One use-case would be to control the strictness of an analyzer and changing the severity of certain errors from compile errors into warning.

Describe the solution you'd like
For this to work, the SDK must have a way to identify the used analyzers. One way to do that is by giving analyzers an optional name from the Analyzer attribute:

[<Analyzer "MyAnalyzerName">]

Then with the help of ionide, the SDK would pick up a configuration file called fsharp-analyzers.json that has the following structure:

{
  "MyAnalyzerName": {
     "Mode": "Strict",
     "ErrorsAsWarnings": "true" 
  }, 

  "OtherAnalyzer": { 
     "ErrorsAsWarnings": "true" 
   }
}

The SDK can then read this JSON file and parse the options as Map<string, string> per analyzer where this Map<string, string> would be available through the context input of the analyzer. For example, for the "MyAnalyzerName", the options would be

Map.ofList [ "Mode", "Strict"; "ErrorsAsWarnings", "true" ]

Describe alternatives you've considered
N/A

Additional context
N/A

Support analysis for projects using Nuget

Is your feature request related to a problem? Please describe.
Allow ionide to detect packages installed from nuget. Right now only paket seems to be supported since it installs the packages locally within packages/analyzers. Many projects might not be using paket (Feliz template for example). Requiring paket also makes the entry barrier higher if you just want to try out an analyzer with a console project.

Describe the solution you'd like
Finding the assemblies of analyzers will require some sort of local package resolution since nuget packages aren't installed in a local directory within a project (AFAIK at least).

Using a combination of the following commands, you can figure out where to find the analyzers:

dotnet nuget locals global-packages --list
info : global-packages: C:\Users\zaidn\.nuget\packages\

dotnet list package
Project 'OptionTest' has the following package references
   [netcoreapp3.1]:
   Top-level Package              Requested   Resolved
   > FSharp.Analyzers.Sample      1.2.0       1.2.0
   > FSharp.Core                  4.7.0       4.7.0

This way you can construct the path of the analyzer:

C:\Users\zaidn\.nuget\packages\{Analyzer}\{Resolved-Version}\lib\{ProjectFramework}\{Analyzer}.dll

For {ProjectFramework} I am not entirely sure how to figure out which target framework the analyzer was compiled with. One option is to find the first directory that starts with net or just loading whatever target framework that can be loaded

Describe alternatives you've considered
Require paket always

Additional context
N/A

Custom analyzer not working in 0.0.4

Hi, I'm working on a project that creates custom analyzers to improve error detection in FSharp following your tutorial here

I have a bracket analyzer that works under FSharp.Analyzers.SDK = 0.0.1, however when I upgrade to FSharp.Analyzers.SDK = 0.04 the analyzer no longer works.

For example, in SDK 0.0.1 I can generate and print out the context of the code in the Fsharp Language server logs:

ctx [|"// <auto-generated>";
  "//     Generated by the FSharp WriteCodeFragment class.";
  "// </auto-generated>"; "namespace FSharp"; ""; "open System";
  "open System.Reflection"; ""; "";
  "[<assembly: System.Reflection.AssemblyCompanyAttribute("SampleAnalyzerUsage")>]";
...

by writing

[<Analyzer>]
let paranthesis : Analyzer =
    printfn "Inside custom analyzer!"
    
    fun ctx ->
        printfn "ctx %A" ctx.Content
       ....

In 0.0.4 only the Inside custom analyzer! appears, the context doesn't get called. I am using Ionide 3.33 - perhaps 0.04 requires the later version of Ionide?

However in Ionide >= 3.34 I am having trouble registering the analyzers regardless of whether I'm using 0.0.1 of the SDK or 0.0.4

Could not load type 'range' from assembly 'FSharp.Compiler.Service, Version=28.0.0.0, Culture=neutral, PublicKeyToken=null

(from the FSharp language service logs)

And

[14:47:06 ERROR] RES (010) <- {registerAnalyzer} ERROR in 543 ms: {stack: Error: Request failed with status code 500
	at createError (C:\Users\Louis\.vscode\extensions\ionide.ionide-fsharp-3.36.2\fsharp.js:14130:15)
	at settle (C:\Users\Louis\.vscode\extensions\ionide.ionide-fsharp-3.36.2\fsharp.js:18695:12)
	at IncomingMessage.handleStreamEnd (C:\Users\Louis\.vscode\extensions\ionide.ionide-fsharp-3.36.2\fsharp.js:28146:11)
	at IncomingMessage.emit (events.js:187:15)
	at endReadableNT (_stream_readable.js:1090:12)
	at process._tickCallback (internal/process/next_tick.js:63:19), message: Request failed with status code 500, config: [object Object], request: [object Object], response: [object Object]} Data=undefined

From the FSharp Language Service (server)

Any help is greatly appreciated. Also please do let me know if I should head over to the ionide forums.

Many thanks!

Add inspect sub command

Is your feature request related to a problem? Please describe.
Right now, there is no good way to tell which dll has which analyzers.
It is hard to tell what codes the analyzer can produce and what the name is in case you potentially want to exclude it.

Describe the solution you'd like
I'm thinking some sub-command could be interesting. Something like dotnet fsharp-analyzers inspect MyAnalyzer.dll.

This prints a table overview of all the analyzers found in the tool.

Describe alternatives you've considered
Maybe adding the name to the sarif output might also work.
Although you need to trigger a message before seeing it, which isn't always super obvious.

--project value should be tested if path exists

The error message should improve if the --project value does not exist.
Right now you get:

Unhandled exception. System.InvalidOperationException: The input list was empty.
   at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4136
   at [email protected](Unit unitVar) in /_//src/FSharp.Analyzers.Cli/Program.fs:line 71
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 510
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 112
--- End of stack trace from previous location ---
   at Microsoft.FSharp.Control.AsyncResult`1.Commit() in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 454
   at Microsoft.FSharp.Control.AsyncPrimitives.QueueAsyncAndWaitForResultSynchronously[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 1140
   at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronously[T](CancellationToken cancellationToken, FSharpAsync`1 computation, FSharpOption`1 timeout) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 1167
   at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 1511
   at Program.main(String[] argv) in /_//src/FSharp.Analyzers.Cli/Program.fs:line 392

BUG: Unable to load analyzer assembly when it has 3rd-party dependencies

Unable to load analyzer assembly when it has 3rd-party dependencies. The problem seems to come from the simplistic strategy of loading just one assembly for the plugins:

  ///Loads any analyzers defined in any assembly matching `*Analyzer*.dll` in given directory (and any subdirectories)
  let loadAnalyzers (dir: string): Analyzer list =
    if Directory.Exists dir then
      let dlls =
          Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories)
          |> Array.choose (fun n ->
            try
              Some (Assembly.LoadFile n)
            with
            | _ -> None)
      dlls
      |> Array.collect (fun a -> a.GetExportedTypes())
      |> Seq.collect (analyzersFromType)
      |> Seq.toList
    else
      []

More specifically, the function Assembly.LoadFile doesn't seem to care about 3rd-party dependencies. According to Create a .NET Core application with plugins, one should use a custom AssemblyLoadContext to load each plugin which seems to contains a specialized AssemblyDependencyResolver. This is the C# version that looks easy enough to translate to F#:

using System;
using System.Reflection;
using System.Runtime.Loader;

namespace AppWithPlugin
{
    class PluginLoadContext : AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;

        public PluginLoadContext(string pluginPath)
        {
            _resolver = new AssemblyDependencyResolver(pluginPath);
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
            if (assemblyPath != null)
            {
                return LoadFromAssemblyPath(assemblyPath);
            }

            return null;
        }

        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (libraryPath != null)
            {
                return LoadUnmanagedDllFromPath(libraryPath);
            }

            return IntPtr.Zero;
        }
    }
}

Then the loadAnalyzers function would look something like this:

  ///Loads any analyzers defined in any assembly matching `*Analyzer*.dll` in given directory (and any subdirectories)
  let loadAnalyzers (dir: string): Analyzer list =
    if Directory.Exists dir then
      let dlls =
          Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories)
          |> Array.choose (fun n ->
            try
              let analyzerParent = System.IO.DirectoryInfo(n).Parent.FullName
              let loadContext = new PluginLoadContext(analyzerParent)
              let assemblyName = System.IO.Path.GetFileNameWithoutExtension(n)
              let assembly = loadContext.LoadFromAssemblyName(new AssemblyName(assemblyName))
              Some assembly
            with
            | _ -> None)
      dlls
      |> Array.collect (fun a -> a.GetExportedTypes())
      |> Seq.collect (analyzersFromType)
      |> Seq.toList
    else
      []

I was trying to build the repository locally but couldn't do so but that is another issue

Surface FSharpCheckFileResults in Context

The Context type surfaces several fields that are based on FSharpCheckFileResults, but not FSharpCheckFileResults itself. This type has several useful methods, such as GetSymbolUseAtLocation, that should be available to analyzers. I therefore propose surfacing this type in Context.

Update to net5

Hi there,

I would like to update the FSharp.Analyzers.SDK to .net5.0 bc I'm working on a AzureTackle.Analyzer.

While updating I ran into following issue:

The loadProject helper is not working anymore bc the fsautocomplete got updated.

Any idea how to load project with the new FSAutocomplete?
FSharp.Analyzers.SDK
image

Thanks in advance!

CLI fails on projects that contain FSI signature files

Describe the bug
Running the CLI analyzer on a project that contains a signature file fails, with the error could not get context for file %s.

To Reproduce
Steps to reproduce the behaviour:

  1. Create a project and add a signature file to it. FsProjWithFsi.zip
  2. Run FSharp.Analyzers.Cli --project "FsProjWithFsi.fsproj" --verbose
  3. Observe the error could not get context for file %s.

Expected behaviour
The analyzer CLI app shouldn't crash.

Environment (please complete the following information):

  • OS: Win10
  • Ionide version: n/a
  • VSCode version: n/a
  • dotnet SDK version: 5

Additional context
The root cause is that in createContext, FSharpCheckFileResults.ImplementationFile is None, which then short-circuits in runProject.

I'm not sure if signature files should simply be skipped for analysis by default, or if there's still potentially useful output that could be produced.

Add ability to return tooltips from analyzer

It would be interesting to have the possibility to return tooltips from the analyzer. I haven't thought about it much yet but it seems it would require:

  1. Internal analyzer mutable state that could be updated while the analyzer is running keeping some kind of Dictionary<Range, TooltipContent>
  2. API for querying this internal state that can be used by clients: Position-> TooltipContent list

Could not load file or assembly FSharp.Compiler.Service

Describe the bug
When trying to add an analyzer I get:

Starting target 'FSharpAnalyzers'
C:\Development\halcwb\libs\GenUtils> "C:\Program Files\dotnet\dotnet.EXE" fsharp-analyzers --analyzers-path "C:\Development\halcwb\libs\GenUtils\packages/analyzers" --project "C:\Development\halcwb\libs\GenUtils\src\Informedica.GenUtils.Lib\Informedica.GenUtils.Lib.fsproj" --fail-on-warnings BDH0002 --verbose (In: false, Out: false, Err: false)
Info : Running in verbose mode
Info : Fail On Warnings: [BDH0002]
Info : Ignore Files: []
Info : Loading analyzers from C:\Development\halcwb\libs\GenUtils\packages/analyzers
Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'FSharp.Compiler.Service, Version=35.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'FSharp.Compiler.Service, Version=35.0.0.0, Culture=neutral, PublicKeyToken=null'
at System.Reflection.RuntimeAssembly.GetExportedTypes()
at FSharp.Analyzers.SDK.Client.mapping@1(String tupledArg0, Assembly tupledArg1) in D:\Programowanie\Projekty\FSharp.Analyzers.SDK\src\FSharp.Analyzers.SDK\FSharp.Analyzers.SDK.Client.fs:line 90
at FSharp.Analyzers.SDK.Client.loadAnalyzers(String dir) in D:\Programowanie\Projekty\FSharp.Analyzers.SDK\src\FSharp.Analyzers.SDK\FSharp.Analyzers.SDK.Client.fs:line 88
at Program.main(String[] argv) in D:\Programowanie\Projekty\FSharp.Analyzers.SDK\src\FSharp.Analyzers.Cli\Program.fs:line 208

To Reproduce
Steps to reproduce the behaviour:

  1. I use the MiniScaffold template as a starting point
  2. I upgrade the project to be a netstandard2.1 and net472
  3. Then I run the FSharpAnalyzers target
  4. Which results in the above error

However the Compiler service is installed:

ls packages/analyzers/FSharp.Compiler.Service/
'[Content_Types].xml' _rels/ fsharp.compiler.service.35.0.0.nupkg FSharp.Compiler.Service.nuspec lib/ logo.png package/

A direct example of this problem can be found here: https://github.com/halcwb/GenUtils/tree/miniscaffold.

Environment (please complete the following information):
.NET Core SDK (reflecting any global.json):
Version: 3.1.102
Commit: 573d158fea

Runtime Environment:
OS Name: Windows
OS Version: 10.0.18362
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\3.1.102\

Host (useful for support):
Version: 3.1.2
Commit: 916b5cba26

.NET Core SDKs installed:
2.1.801 [C:\Program Files\dotnet\sdk]
2.1.802 [C:\Program Files\dotnet\sdk]
2.2.401 [C:\Program Files\dotnet\sdk]
2.2.402 [C:\Program Files\dotnet\sdk]
3.0.100 [C:\Program Files\dotnet\sdk]
3.1.102 [C:\Program Files\dotnet\sdk]

Please add License to fsharp-analyzers dotnet tool publication

I work in an environment where .NET packages and tools go through an internal package manager and doesn't allow us to use nuget.org directly (not uncommon). Our policies block unlicensed packages and the scanning for licenses is automated.

Would it be possibly to include the license information with fsharp-analyzers? The currently published packages don't include licensing info.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.