dotnet / reactive

The Reactive Extensions for .NET
http://reactivex.io
MIT License
6.7k stars 751 forks source link

Ideas for Roslyn code analyzers and code fix providers #1397

Open bartdesmet opened 4 years ago

bartdesmet commented 4 years ago

It'd be good to augment the library with Roslyn extensions that suggest on library-specific code improvements in user code. A couple of examples come to mind immediately (see below), but I can imagine many more based on typical Rx guidance, known anti-patterns, performance pitfalls, general LINQ patterns, etc.

At the very least, these should be semantics-preserving and hence "safe" code fixes to apply; it should make the user's code strictly better (and definitely/obviously not worse) so the bar for such analyzers and code fixes will be kept very high.

This could be a longer term design theme or workstream, where we can start with just a few simple ones, at least to get the shipping and packaging logistics for such analyzers set up.

We should also use https://apisof.net/catalog/System.Reactive.Linq.Observable to figure out areas that are most commonly used when ranking possible suggestions for analyzers. The few ideas below show fairly common usage; Observable is used in about 2.8% of apps, so all operator uses should be measured relative to this. For example, some reflection-based overloads of FromEventPattern are used in 0.2% of apps, which still accounts for almost 10% of Rx apps.

FromEvent with reflection

Suggest changing Observable.FromEvent[Pattern]([expr | type], "Foo") (and variants with a scheduler parameter) to another Observable.FromEvent[Pattern] overload that doesn't use reflection. This involves deriving the event handler type, its arguments, etc. in order to construct the replacement invocation, while also creating lambdas (possibly a conversion lambda, but at least add/remove handler lambdas).

This isn't always safe to do, because the way the reflection-based lookup works, using the dynamic type of expr (for better or for worse, but that's what we have done historically and we won't change it). So this may need checks for being sealed etc. which may limit its usefulness (though such issues don't apply to the overload that takes in Type rather than object, for static events). Even if there are limits due to the dynamic nature of the reflection-based binding process, we may still be able to suggest a nameof(Bar.Foo) fix to replace the string literal if we can find a candidate event (i.e. only we, the Rx library, understand that the string represents an event).

Design TBD, but the overall idea of avoiding reflection and magic names in strings is worthwhile.

Avoid blocking operations

E.g. use of Single, First, Last, or variants thereof (as well as other blocking operations) within an async method could be replaced by an await expression using the Async variant of these operators.

Design TBD, in particular with regards to ConfigureAwait behavior (we can suggest different options to the user as well). Either way, this reflects our intent to make the blocking operations obsolete, or at the very least discourage their use.

Random ideas

Either way, this issue can be used to brainstorm ideas. Curious to see what people come up with. Based on ideas, we can decide how to move forward.

quinmars commented 4 years ago

The analyzer could suggest to use a scheduler for the operator instead of a subsequent ObserveOn, e.g., Throttle(TimeSpan.FromSeconds(1), scheduler) instead of Throttle(TimeSpan.FromSeconds(1)).ObserveOn(scheduler).

bartdesmet commented 4 years ago

@quinmars - Great suggestion.

JohanLarsson commented 4 years ago

Refactoring that changes += to Observable.FromEvent I have a couple of analyzers and fixes that can be moved to this analyzer. My suggestion on moving forward is

  1. Create an analyzer and test project.
  2. Ship analyzers on nuget.