-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathIntroducing-F-Analyzers.html
387 lines (242 loc) · 23.6 KB
/
Introducing-F-Analyzers.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Introducing F# Analyzers</title>
<meta name="description" content="Introducing F# Analyzers">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/favicon/favicon.ico">
<link rel="apple-touch-icon-precomposed" href="/favicon/lattice-optic-illusion-152-204321.png" >
<meta name="msapplication-TileColor" content="#ffffff" />
<meta name="msapplication-TileImage" content="/favicon/lattice-optic-illusion-144-204321.png">
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Lato:400,400italic,700,700italic" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/styles/default.min.css">
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/font-awesome.min.css" />
<link rel="canonical" href="http://localhost:4000/Introducing-F-Analyzers">
<link rel="alternate" type="application/rss+xml" title="F# Reflections Feed" href="http://localhost:4000/feed.xml">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
</head>
<body>
<nav id="top" class="site-navigation">
<div class="inner">
<button id="menu-toggle" aria-expanded="false">Menu</button>
<div class="nav-menu">
<ul class="menu">
<li class="">
<a class="" href="/">Home</a>
</li>
<li class="">
<a class="" href="/about">About</a>
</li>
<li class="">
<a class="" href="/tags/">Archives</a>
</li>
<li class="">
<a class="" href="/2016">2016</a>
</li>
</ul>
</div><!-- .nav-menu -->
<div class="subscribe-button">
<a class="fa-feed square fill-horizontal" href="/feed.xml"><span class="screen-reader-text">RSS</span></a>
</div><!-- .social-links -->
</div><!-- .inner -->
</nav><!-- .site-navigation -->
<header class="site-header">
<div class="inner">
<h1 class="site-title"><a class="logo-text" href="/">F# Reflections</a></h1>
<p class="site-description">Thoughts, stories and ideas.</p>
</div><!-- .inner -->
</header>
<div class="site-content">
<div class="inner">
<main class="site-main">
<article class="post">
<header class="entry-header">
<div class="entry-header-wrap">
<div class="post-thumbnail">
<img src="/images/posts/landscape_6.jpg" alt="Introducing F# Analyzers">
</div><!-- .post-thumbnail -->
<h1 class="entry-title">Introducing F# Analyzers</h1>
</div><!-- .entry-header-wrap -->
<div class="entry-meta">
by <span class="post-author">Krzysztof Cieslak</span> on <time class="published" datetime="2018-09-14">September 14, 2018</time>
</div><!-- .entry-meta -->
</header><!-- .entry-header -->
<div class="entry-content">
<h1>Introducing F# Analyzers</h1>
<p>One of the most exciting features of Roslyn (modern C# compiler) is ability to plug into it custom extensions called <a href="https://docs.microsoft.com/en-us/visualstudio/extensibility/getting-started-with-roslyn-analyzers?view=vs-2017">Roslyn Analyzers</a>. They are live, real-time, project based plugins that enables to diagnose source code and surface custom errors, warnings and code fixes into Visual Studio. What’s really important is fact that there are project based, and distributed through NuGet which means they are easy to install, and ensure that all developers working on the project have exactly same analyzers installed. This is really useful if you want to ensure common best practices, style etc while working on the project.</p>
<p>Today I’d like to introduce preview of the project called F# Analyzers which adds similar capabilities to <a href="http://ionide.io">Ionide</a> (F# support in VS Code). Project is still in the early days, so design and technical details can change in time, but I’m really excited to share it with users even in its current state.</p>
<!--more-->
<h3>Architecture overview</h3>
<p>Before diving deeper into Analyzers themselves, it’s important to understand general architecture of Ionide, and where Analyzers fits into this architecture. This has really important impact on the Analyzers capabilities and limitations.</p>
<p>On the one hand we have Ionide itself. It’s <a href="https://code.visualstudio.com/docs/extensions/overview">VS Code plugin</a> which means it’s using NodeJS runtime, running as a child process of the editor, communicating with VS Code using set of APIs provided by VS Code. On the other hand we have F# compiler, or exactly speaking <a href="https://fsharp.github.io/FSharp.Compiler.Service/index.html">FSharp.Compiler.Service</a> which is library version of F# compiler, exposing some additional APIs useful for creating tooling for F#. Of course, <code>FSharp.Compiler.Service</code> is .Net library which means it can’t be used directly by NodeJS process.</p>
<p>Last important part of the architecture is <a href="https://github.com/fsharp/FsAutoComplete">FsAutoComplete</a>. It’s a project that provides communication layer and high level API between FCS and external world. It’s used by vim, emacs and Ionide as all those plugins are hosted in non .Net environments.</p>
<p>The F# Analyzers are using extension point provided by the FsAutoComplete which means that in current shape they can be supported only by the editors using it (and they requires some work on the plugin side of things - currently only Ionide supports them). And it means that diagnostics provided by them are visible only in the editor, not during compilation.</p>
<h3>Analyzers basics</h3>
<p>Analyzer is normal F# function. As an input it takes Context object which contains current information about the file - Parse Tree, Type Abstract Syntax Tree, Symbols information, and returns list of the Diagnostics - records containing diagnostic message, type, description, and potentially list of fixes that can be used to fix the problem.</p>
<p>F# Analyzer contains 3 parts - first one is an extension point in FSAC which is responsible for loading and running analyzers, second one is a editor handler (part of Ionide, in this case) that displays diagnostics and fixes. Last part is <a href="https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.SDK">FSharp.Analayzers.SDK</a> project that’s used to create analyzers.</p>
<h3>Building your first analyzer</h3>
<p>From infrastructure point of view, creating analyzer is simple - you need to create normal F# project, <code>netstandard2.0</code> , add reference to <code>FSharp.Analyzers.SDK</code> (avaliable on NuGet - <a href="https://www.nuget.org/packages/FSharp.Analyzers.SDK/">https://www.nuget.org/packages/FSharp.Analyzers.SDK</a>) and create analyzer functions - normal F# functions taking Context returning list of diagnostics, and marked with Analyzer attribute.</p>
<blockquote>
<p>Due to unresolved technical limitations your Analyzer must contain “Analyzer” in the name of output dll.</p>
</blockquote>
<p>However, from technical point of view creating analyzer is fairly difficult task - you need to be familiar with processing typed or untyped abstract syntax tree, or accessing FSharp Symbols information. Currently SDK doesn’t provide any helper functions, but we hope to add some higher level abstractions in the future. Currently, I recommend going through <a href="http://fsharp.github.io/FSharp.Compiler.Service/">FCS documentation</a>, to get more familiar with the concepts</p>
<p>Generally speaking processing Abstract Syntax Trees is usually done by recursive pattern matching on the DU representing syntax nodes. The following snippet shows how to travers Type Abstract Syntax Tree and call some function on every node that represents call of the member. While it looks bit scary, and complex if you take a look on it bit longer you will notice that it’s just big recursive pattern matching. Also, similar pattern matching will be base for most analyzers, which means you can reuse the code pretty easily.</p>
<script src="https://gist.github.com/Krzysztof-Cieslak/34c2a43c52119fde8d55315c7831244d.js"></script>
<p>Having this bit of infrastructure code, let’s now move to the analyzer itself. As I’ve mentioned before, analyzers are normal functions that have particular signature and are marked with attribute.</p>
<script src="https://gist.github.com/Krzysztof-Cieslak/42829ce7b5dcc937bece698a5af9ff2b.js"></script>
<p>In the above snippet we create analyzer that will detect any call of <code>x.Value</code> where x is an F# Option. As you probably know calling <code>.Value</code> on an <code>option</code> is not total function and can throw exception if an <code>option</code> instance is <code>None</code>, so checking for it may be good idea.</p>
<p>The handler function in above example is executed for every member call. In it we check if the name of the function we call, and if it’s <code>Option.Value</code> we add the range of the symbol into the state. After we’ve processed whole tree we map all ranges into the warnings that will be displayed in the editor. There also exist possibility of providing set of code fixes, but we don’t use it in this example.</p>
<blockquote>
<p>Whole code of the sample analyzer is available on GitHub - <a href="https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.Sample">https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.Sample</a>, and analyzer is published on NuGet - <a href="https://www.nuget.org/packages/FSharp.Analyzers.Sample/">https://www.nuget.org/packages/FSharp.Analyzers.Sample/</a></p>
</blockquote>
<h3>Using analyzer</h3>
<p>Analyzers are enabled by <code>FSharp.enableAnalyzers</code> setting inside VSCode. By default it’s disabled, so you need to edit settings to enable analyzers support. Using analyzers is as simple as adding Analyzer NuGet package with Paket - for out of the box support in Iondie you need to use Analyzer group for it.</p>
<script src="https://gist.github.com/Krzysztof-Cieslak/dc71359d81354129187584c502cc60e6.js"></script>
<p>After the package is added to <code>paket.dependencies</code> and restored, Ionide will automatically detect and load analyzers. It may require editor restart as analyzers are loaded on plugin startup.</p>
<blockquote>
<p>Ionide also contains setting <code>FSharp.analyzersPath</code> that will enable you to configure paths from which Ionide loads analyzers, by default it’s using <code>packages/Analyzers</code> and <code>analyzers</code> folders.</p>
</blockquote>
<p>For example let’s test our new analyzer on the following snippet.</p>
<script src="https://gist.github.com/Krzysztof-Cieslak/c1962db70231ec9d2581955324f6ac82.js"></script>
<p>This will result in underlying <code>x.Value</code> with a warning saying that Option.Value shouldn’t be used</p>
<p><img src="https://cdn-images-1.medium.com/max/2000/1*axoWiRB3EPANiOljA_PWzg.png" alt="" /></p>
<blockquote>
<p>Sample project using analyzer can be found on GitHub - <a href="https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.Sample.Usage">https://github.com/Krzysztof-Cieslak/FSharp.Analyzers.Sample.Usage</a></p>
</blockquote>
<h3>So what’s next?</h3>
<p>Currently there are several important limitation of the F# Analyzers. First of all they’re supported only by Ionide. Secondly to write analyzer you need to be familiar with F# (T)AST pretty well. And in the end writing fixes requires providing new version of code in textual form (unlike in case of Roslyn Analyzers where fixes can be provided as AST transformation), what may be difficult and error prone. Also, whole mechanism is currently in an experimental phase, relies on Reflection etc. so it can be bit unstable.</p>
<p>However, I treat this as an experiment and proof of concept, that may be used as a base for discussing similar functionalities being added to F# compiler or other IDEs. Also I’m super excited to see what potentially great things can F# Community create having such extension point in the editor (Evil Hint: You can use this feature to run any code inside of the language server and return some information to editor… which sounds awesome and crazy at the same time)</p>
<h3>Summary</h3>
<p>In this post I’ve introduced preview of new tooling related project that enables users to add custom analyzers to your editor, bringing functionality similar to Roslyn Analyzers. While this is only experiment, and proof-of-concept I’m looking forward to seeing all innovative things it can enable. Analyzers support has been released in new Ionide version - <code>3.27.0</code> - so it’s out there and ready for experimenting!</p>
</div><!-- .entry-content -->
<footer class="entry-footer">
<div class="share-post">
<span>Share</span>
<a class="fa-twitter" target="_blank" href="https://twitter.com/intent/tweet?text=Introducing%20F#%20Analyzers&url=http://localhost:4000/Introducing-F-Analyzers"><span class="screen-reader-text">Twitter</span></a>
<a class="fa-facebook" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=http://localhost:4000/Introducing-F-Analyzers&t=Introducing%20F#%20Analyzers"><span class="screen-reader-text">Facebook</span></a>
<a class="fa-google-plus" target="_blank" href="http://plus.google.com/share?url=http://localhost:4000/Introducing-F-Analyzers"><span class="screen-reader-text">Google+</span></a>
</div><!-- .share-post -->
<div class="tag-links">
Tags: <a href="/tags/index.html#F%23" title="Pages tagged F#" rel="tag">F#</a><a href="/tags/index.html#VS+Code" title="Pages tagged VS Code" rel="tag">VS Code</a><a href="/tags/index.html#Tooling" title="Pages tagged Tooling" rel="tag">Tooling</a>
</div>
</footer><!-- .entry-footer -->
</article><!-- .post -->
<div class="author-box">
<div class="author-info">
<a href="http://kcieslak.io/about" target="_blank"> <div class="author-avatar" style="background-image: url(/images/authorimage.jpg)"><span class="screen-reader-text">Krzysztof Cieslak's Picture</span></div> </a>
<div class="author-details">
<h2 class="author-title">About Krzysztof Cieslak</h2>
<p class="author-bio">Krzysztof is a F# developer and consultant, open source contributor, conference speaker</p>
<span class="author-location"><i class="fa-map-marker" aria-hidden="true"></i> Lodz, Poland</span>
<span class="author-website"><i class="fa-chain" aria-hidden="true"></i> <a href="http://kcieslak.io" target="_blank"> http://kcieslak.io</a></span>
</div><!-- .author-details -->
</div><!-- .author-info -->
</div><!-- .author-box -->
<nav class="post-navigation">
<h2 class="screen-reader-text">Post navigation</h2>
<div class="nav-previous">
<div class="nav-inside">
<a href="/Building-an-MVP-with-F-and-Saturn" class="nav-thumb" style="background-image: url(/images/posts/landscape_13.jpg)"></a>
<div class="nav-before">Previous</div>
<div class="nav-title"><a href="/Building-an-MVP-with-F-and-Saturn">Building an MVP with F# and Saturn</a></div>
<div class="nav-date">August 16, 2018</div>
</div><!-- .nav-inside -->
</div><!-- .nav-previous -->
<div class="nav-next">
<div class="nav-inside">
<a href="/Future-of-F-Cross-platform-editor-tooling" class="nav-thumb" style="background-image: url(/images/posts/landscape_14.jpg)"></a>
<div class="nav-before">Next</div>
<div class="nav-title"><a href="/Future-of-F-Cross-platform-editor-tooling">Future of F# cross-platform editor tooling</a></div>
<div class="nav-date">December 31, 2018</div>
</div><!-- .nav-inside -->
</div><!-- .nav-next -->
</nav><!-- .post-navigation -->
</main><!-- .site-main -->
<aside class="sidebar">
<section class="widget widget-text">
<h2 class="widget-title">About Krzysztof Cieslak</h2>
<p>Krzysztof is a F# developer and consultant, open source contributor, conference speaker</p>
</section><!-- .widget-text -->
<section class="widget widget-text">
<p><a href="http://lambdafactory.io" target="_blank"><img src="/images/banner.png" alt="Optional Banner Ad" /></a></p>
</section><!-- .widget-text-->
<section class="widget widget-recent-posts">
<h2 class="widget-title">Latest Posts</h2>
<ul class="recent-posts">
<li class="recent-item"><a href="/F-Editor-Tooling-Part1">Behind the F# editor tootling. Part 1: introduction to compilers.</a> <span>August 27, 2019</span></li>
<li class="recent-item"><a href="/Ionide-Introducing-Info-Panel">Ionide — Introducing Info Panel</a> <span>May 6, 2019</span></li>
<li class="recent-item"><a href="/Ionide-New-Hope">Ionide — A New Hope</a> <span>March 24, 2019</span></li>
<li class="recent-item"><a href="/PureScript-on-Azure-Functions">PureScript on Azure Functions</a> <span>January 12, 2019</span></li>
<li class="recent-item"><a href="/Future-of-F-Cross-platform-editor-tooling">Future of F# cross-platform editor tooling</a> <span>December 31, 2018</span></li>
</ul><!-- .recent-posts -->
</section><!-- .widget-recent-posts -->
<!--Create a sorted array of tags-->
<section class="widget widget-tags">
<h2 class="widget-title">Tags</h2>
<div class="tagcloud">
<a href="/tags/#Azure">Azure</a>
<a href="/tags/#Azure+Functions">Azure Functions</a>
<a href="/tags/#Community">Community</a>
<a href="/tags/#Compiler">Compiler</a>
<a href="/tags/#F%23">F#</a>
<a href="/tags/#FAKE">FAKE</a>
<a href="/tags/#FSharp.Compiler.Services">FSharp.Compiler.Services</a>
<a href="/tags/#Fable">Fable</a>
<a href="/tags/#JS">JS</a>
<a href="/tags/#MVP">MVP</a>
<a href="/tags/#OAuth">OAuth</a>
<a href="/tags/#Open+Source">Open Source</a>
<a href="/tags/#Paket">Paket</a>
<a href="/tags/#PureScript">PureScript</a>
<a href="/tags/#Saturn">Saturn</a>
<a href="/tags/#Serverless">Serverless</a>
<a href="/tags/#Tooling">Tooling</a>
<a href="/tags/#VS+Code">VS Code</a>
<a href="/tags/#Web">Web</a>
<a href="/tags/#Webpack">Webpack</a>
</div><!-- .tagcloud -->
</section><!-- .widget -->
<section class="widget widget-text">
<h2 class="widget-title">Contact me</h2>
<a href="https://twitter.com/k_cieslak" class="fa-twitter square fill-horizontal"><span class="screen-reader-text">Twitter</span></a>
<a href="https://github.com/Krzysztof-Cieslak" class="fa-github-alt square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="https://gitlab.com/Krzysztof-Cieslak" class="fa-gitlab square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="mailto:[email protected]" class="fa-envelope square fill-horizontal"><span class="screen-reader-text">E-Mail</span></a>
</section><!-- .widget-text -->
</aside><!-- .sidebar -->
</div><!-- .inner -->
</div><!-- .site-content -->
<footer class="site-footer">
<div class="inner">
<div class="social-links">
<a href="https://twitter.com/k_cieslak" class="fa-twitter square fill-horizontal"><span class="screen-reader-text">Twitter</span></a>
<a href="https://github.com/Krzysztof-Cieslak" class="fa-github-alt square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="https://gitlab.com/Krzysztof-Cieslak" class="fa-gitlab square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="mailto:[email protected]" class="fa-envelope square fill-horizontal"><span class="screen-reader-text">E-Mail</span></a>
<a class="fa-feed square fill-horizontal" href="/feed.xml"><span class="screen-reader-text">RSS</span></a>
</div><!-- .social-links -->
<div class="site-info">
© <a href="/">Krzysztof Cieslak</a> all rights reserved. <br />Powered by <a target="_blank" href="https://jekyllrb.com/">Jekyll</a>. BlogInn theme by <a target="_blank" href="http://justgoodthemes.com/">JustGoodThemes</a>
<a href="#top" id="top-link" class="fa-chevron-up top-link square fill-horizontal"><span class="screen-reader-text">Back to the top</span></a>
</div><!-- .site-info -->
</div><!-- .inner -->
</footer><!-- .site-footer -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/highlight.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/languages/fsharp.min.js"></script>
<script type="text/javascript" src="/js/plugins.js"></script>
<script type="text/javascript" src="/js/custom.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<!-- Asynchronous Google Analytics snippet -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-74514389-1', 'auto');
ga('require', 'linkid', 'linkid.js');
ga('send', 'pageview');
</script>
</body>
</html>