{"id":1075,"date":"2017-04-12T22:39:44","date_gmt":"2017-04-12T22:39:44","guid":{"rendered":"http:\/\/tpodolak.com\/blog\/?p=1075"},"modified":"2017-04-12T22:39:44","modified_gmt":"2017-04-12T22:39:44","slug":"vs-code-forcing-partial-intellisense-support-cake-scripts","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2017\/04\/12\/vs-code-forcing-partial-intellisense-support-cake-scripts\/","title":{"rendered":"VS Code &#8211; forcing partial IntelliSense support for Cake scripts"},"content":{"rendered":"<h3>1. Introduction <\/h3>\n<p>I&#8217;ve been using <i>Cake<\/i> for quite some time now, and I really like this tool, however the more complex my build scripts are, the more painful lack of <i>IntelliSense<\/i> is. Inspired a bit by this <a href=\"http:\/\/www.strathweb.com\/2016\/12\/writing-c-build-scripts-with-fake-omnisharp-and-vs-code\/\">post<\/a>, I decided to see what it takes to enable at least partial <i>IntelliSense<\/i> for <i>Cake<\/i> scripts in Visual Studio Code. <\/p>\n<h3>2. Investigation<\/h3>\n<p>The proper way of providing the code completion for <i>Cake<\/i> scripts would probably be a plugin to <i>Omnisharp-Roslyn<\/i>, as <i>Cake<\/i> script is basically a valid c# snippet. Unfortunately, at this moment <i>Omnisharp-Roslyn<\/i> doesn\u2019t have plugin infrastructure <a href=\"https:\/\/github.com\/OmniSharp\/omnisharp-roslyn\/issues\/208\">ready<\/a>, that is why I decided to go with a bit different path. As you might or might not be aware, <i>VS Code<\/i> already supports <i>csx<\/i> files, so if you add an empty <i>project.json<\/i> file (by empty I mean file with empty <i>JSON<\/i> object) to your build directory and change extension of your scripts to <i>csx<\/i>, you will immediately get a <i>C#<\/i> syntax highlighting and some <i>IntelliSense<\/i> support. Sadly this will not provide code completion for <i>Cake<\/i> method aliases. The reason why it fails on that is the fact that the <i>Cake<\/i> alias is <i>ICakeContext<\/i> extension method<\/p>\n<pre lang=\"csharp\">\r\n[CakeMethodAlias]\r\npublic static T Argument<T>(this ICakeContext context, string name)\r\n<\/pre>\n<p>but you use it as it was written as <\/p>\n<pre lang=\"csharp\">\r\n[CakeMethodAlias]\r\npublic static T Argument<T>(string name)\r\n<\/pre>\n<p>The <i>Cake<\/i> engine generates additional alias overloads during script compilation, and as these overloads exist only on runtime, <i>VS Code<\/i> just can&#8217;t &#8220;see&#8221; them.<\/p>\n<h3>3. My approach<\/h3>\n<p>Having in mind the way <i>Cake<\/i> works I decided to write an application which would be able to take any <i>Cake<\/i> or <i>Cake add-in<\/i> and produce a library containing proper alias overloads. For instance, if original <i>Cake<\/i> method looks as follows<\/p>\n<pre lang=\"csharp\">\r\n\/\/\/ <summary>\r\n\/\/\/ Contains functionality related to arguments.\r\n\/\/\/ <\/summary>\r\n[CakeAliasCategory(\"Arguments\")]\r\npublic static class ArgumentAliases\r\n{\r\n    \/\/\/ <summary>\r\n    \/\/\/ Determines whether or not the specified argument exist.\r\n    \/\/\/ <\/summary>\r\n    \/\/\/ <param name=\"context\">The context.<\/param>\r\n    \/\/\/ <param name=\"name\">The argument name.<\/param>\r\n    \/\/\/ <returns>Whether or not the specified argument exist.<\/returns>\r\n    \/\/\/ <example>\r\n    \/\/\/ This sample shows how to call the <see cref=\"HasArgument\"\/> method.\r\n    \/\/\/ <code>\r\n    \/\/\/ var argumentName = \"myArgument\";\r\n    \/\/\/ \/\/Cake.exe .\\hasargument.cake -myArgument=\"is specified\"\r\n    \/\/\/ if (HasArgument(argumentName))\r\n    \/\/\/ {\r\n    \/\/\/     Information(\"{0} is specified\", argumentName);\r\n    \/\/\/ }\r\n    \/\/\/ \/\/Cake.exe .\\hasargument.cake\r\n    \/\/\/ else\r\n    \/\/\/ {\r\n    \/\/\/     Warning(\"{0} not specified\", argumentName);\r\n    \/\/\/ }\r\n    \/\/\/ <\/code>\r\n    \/\/\/ <\/example>\r\n    [CakeMethodAlias]\r\n    public static bool HasArgument(this ICakeContext context, string name)\r\n    {\r\n        \/\/ rest of the code omitted for brevity\r\n    }\r\n}\r\n<\/pre>\n<p>the application will rewrite it into<\/p>\n<pre lang=\"csharp\">\r\n\/\/\/ <summary>\r\n\/\/\/ Contains functionality related to arguments.\r\n\/\/\/ <\/summary>\r\n[CakeAliasCategory(\"Arguments\")]\r\npublic static class ArgumentAliasesMetadata\r\n{\r\n    \/\/\/ <summary>\r\n    \/\/\/ Determines whether or not the specified argument exist.\r\n    \/\/\/ <\/summary>\r\n    \/\/\/ <param name=\"name\">The argument name.<\/param>\r\n    \/\/\/ <returns>Whether or not the specified argument exist.<\/returns>\r\n    \/\/\/ <example>\r\n    \/\/\/ This sample shows how to call the <see cref=\"HasArgument\"\/> method.\r\n    \/\/\/ <code>\r\n    \/\/\/ var argumentName = \"myArgument\";\r\n    \/\/\/ \/\/Cake.exe .\\hasargument.cake -myArgument=\"is specified\"\r\n    \/\/\/ if (HasArgument(argumentName))\r\n    \/\/\/ {\r\n    \/\/\/     Information(\"{0} is specified\", argumentName);\r\n    \/\/\/ }\r\n    \/\/\/ \/\/Cake.exe .\\hasargument.cake\r\n    \/\/\/ else\r\n    \/\/\/ {\r\n    \/\/\/     Warning(\"{0} not specified\", argumentName);\r\n    \/\/\/ }\r\n    \/\/\/ <\/code>\r\n    \/\/\/ <\/example>\r\n    public static bool HasArgument(string name)\r\n    {\r\n        return default(bool);\r\n    }\r\n}\r\n<\/pre>\n<p>The algorithm looks more or less like that<\/p>\n<ul>\n<li>Retrieve Cake or Cake add-in via NuGet<\/li>\n<li>Scan an assembly and find all classes containing alias methods<\/li>\n<li>Use <a href=\"http:\/\/source.roslyn.io\/#Microsoft.CodeAnalysis.CSharp.Workspaces\/CodeGeneration\/CSharpCodeGenerationService.cs,20912a440e5cda03\">CSharpCodeGenerationService<\/a> to generate metadata for Cake alias methods<\/li>\n<li>Parse generated code with <i>Roslyn<\/i> and produce <i>SyntaxTree<\/i><\/li>\n<li>Append Metadata suffix to classes containing alias method<\/li>\n<li>Remove <i>ICakeContext<\/i> parameter from alias method<\/li>\n<li>Generate dummy body for methods which require that (methods which have return type or which have out parameters)<\/li>\n<li>Update xml documentation<\/li>\n<li>Compile generated code and produce dll<\/li>\n<\/ul>\n<p>There is no point of doing more detailed description in here so if you want to take a closer look here is <a href=\"https:\/\/github.com\/tpodolak\/Cake.Intellisense\">source code<\/a>.<\/p>\n<h3>4. Generating metadata libraries<\/h3>\n<p>Before we start &#8220;hacking&#8221; <i>VS Code<\/i> to have <i>IntelliSense<\/i>, we have to prepare metadata libraries which will contain all necessary <i>Cake<\/i> alias overloads. In order to do that grab the application from <a href=\"https:\/\/www.nuget.org\/packages\/Cake.Intellisense\/\">NuGet<\/a> and generate <i>Cake.Common<\/i> and <i>Cake.Core <\/i> metadata libraries. The simplest way of doing it is to run these commands<\/p>\n<pre lang=\"bash\">\r\nCake.Intellisense.exe --Package Cake.Common --PackageVersion 0.19.2 --TargetFramework .NETFramework,Version=v4.5 --OutputFolder C:\\Output\r\n\t\r\nCake.Intellisense.exe --Package Cake.Core --PackageVersion 0.19.2 --TargetFramework .NETFramework,Version=v4.5 --OutputFolder C:\\Output\r\n<\/pre>\n<p>As a result, the application will produce following files:<\/p>\n<ul>\n<li>Cake.CommonMetadata.dll<\/li>\n<li>Cake.CommonMetadata.xml<\/li>\n<li>Cake.CoreMetadata.dll<\/li>\n<li>Cake.CoreMetadata.xml<\/li>\n<\/ul>\n<h3>5. Enabling <i>IntelliSense<\/i> in <i>VS Code<\/i><\/h3>\n<p>Having our metadata libraries prepared now we can adjust our build scripts to have code completion in <i>VS Code<\/i>. Here are the steps:<\/p>\n<ul>\n<li>Add <i>project.json<\/i> file with empty <i>JSON<\/i> object into your build directory.<\/li>\n<li>Change extension of all your build scripts to <i>csx<\/i>.<\/li>\n<li>Copy metadata libraries into your <i>Build<\/i> directory.<\/li>\n<li>Create <i>imports.csx<\/i> file. This is the file which contains all original namespace imports. It may look as follows\n<pre lang=\"csharp\">\r\n#load \".\/metadataImports.csx\"\r\nusing Cake.Core;\r\nusing Cake.Core.IO;\r\n\/\/ rest of imports\r\n<\/pre>\n<\/li>\n<li>Create <i>metadataimports.csx<\/i> file. This is the file which contains metadata namespaces imports and loads <i>Cake<\/i> and metadata references.\n<pre lang=\"csharp\">\r\n#r \".\/tools\/Cake\/Cake.Core.dll\"\r\n#r \".\/tools\/Cake\/Cake.Common.dll\"\r\n#r \".\/tools\/Cake.Metadata\/Cake.Core.Metadata.dll\"\r\n#r \".\/tools\/Cake.Metadata\/Cake.Common.Metadata.dll\"\r\nusing static Cake.Common.ArgumentAliasesMetadata;\r\nusing static Cake.Common.EnvironmentAliasesMetadata;\r\n\/\/ rest of imports\r\n<\/pre>\n<p> Each original <i>Cake<\/i> alias class has corresponding metadata class with Metadata suffix, for instance<br \/>\n<i>Cake.Common.ArgumentAliases -> Cake.Common.ArgumentAliasesMetadata<\/i><br \/>\n<i>Cake.Common.EnvironmentAliases -> Cake.Common.EnvironmentAliasesMetadata<\/i>\n<\/li>\n<li>Load <i>imports.csx<\/i> to your <i>build.csx<\/i> file via\n<pre lang=\"csharp\">\r\n#load \".\/imports.csx\"\r\n<\/pre>\n<\/li>\n<li>Run <i>VS Code<\/i> install <i><a href=\"\/\/tpodolak.com\/blog\/2017\/04\/09\/downgrading-visual-studio-code-extension\/\">ms-vscode.csharp 1.7.0<\/a><\/i>, open <i>Build<\/i> directory and write your build script with <i>IntelliSense<\/i> support<br \/>\n<a href=\"http:\/\/imgur.com\/ButIXBW\"><img decoding=\"async\" src=\"http:\/\/i.imgur.com\/ButIXBW.gif\" title=\"source: imgur.com\" \/><\/a>\n<\/li>\n<li>Before running the script remember to comment out\n<pre lang=\"csharp\">\r\n#load \".\/metadataImports.csx\"\r\n<\/pre>\n<p> from <i>imports.csx<\/i> file<\/li>\n<li>Run the build\n<pre lang=\"csharp\">\r\nbuild.ps1 -script build.csx -target Your-Target\r\n<\/pre>\n<\/li>\n<\/ul>\n<p>I do realize this is quite convoluted explanation so in case of any troubles take a look at my <a href=\"https:\/\/github.com\/tpodolak\/Cake.Intellisense\/tree\/master\/Build\">build<\/a><\/p>\n<h3>6. Known issues<\/h3>\n<ul>\n<li>\n<i>Cake.Intellisense<\/i> can only generate metadata libraries for a standard .NET frameworks, it will fail if you try to create metadata targeting <i>.NETStandard<\/i> or <i>.NET Core<\/i> framework\n<\/li>\n<li>\nDue to some <a href=\"https:\/\/github.com\/OmniSharp\/omnisharp-roslyn\/issues\/811\">breaking changes<\/a> in <i>Omnisharp-Roslyn<\/i> scripting support, <i>IntelliSense<\/i> will only work with <i>Omnisharp 1.7.0<\/i>\n<\/li>\n<\/ul>\n<h3>7. Summary<\/h3>\n<p>This approach is just a temporary solution. As you can see, it requires significant amount of work to have a code completion in <i>VS Code<\/i>. I believe that once <i>Omnisharp-Roslyn<\/i> has proper plugin support it should be possible to write some kind of custom <i>IntelliSense<\/i> provider for <i>Cake<\/i> scripts. There are already people who forked <i>Omnisharp-Roslyn<\/i> and play around with <a href=\"https:\/\/github.com\/mholo65\/omnisharp-roslyn\/tree\/wip\/new-cake-plugin\"> that<\/a>, so we just have to wait for something better than this solution. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Introduction I&#8217;ve been using Cake for quite some time now, and I really like this tool, however the more complex my build scripts are, the more painful lack of IntelliSense is. Inspired a bit by this post, I decided to see what it takes to enable at least partial IntelliSense for Cake scripts in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[301,321],"tags":[302,322],"class_list":["post-1075","post","type-post","status-publish","format-standard","hentry","category-cake","category-vscode","tag-cake","tag-vscode"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1075","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=1075"}],"version-history":[{"count":30,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1075\/revisions"}],"predecessor-version":[{"id":1105,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1075\/revisions\/1105"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=1075"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=1075"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=1075"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}