Custom Plugin - understanding GeneratedDocumentService and Code Behind

Hi Mr,

I'm currently implementing a similar plugin to the T4 templates - handling a custom language implementation which generates c# code as output in "code behind".

Needless to say I have been analyzing the PSI plugin code - as well as the ForTea T4 templates plugin project (amazing work by the author there!)

I have more or less the basics setup:

* General custom language implementation etc... pretty much following the examples and your code...
* Parser / Lexer is implemented - files are parsed and PSI is generated...
* Simple syntax highlightning - with the CSharp implementation backing it...
* Simple error messages output for just the parsing (no code analysis yet...)

What I'm currently trying to figure out is the "CodeBehind" concept:

* GeneratedDocumentService

I was hoping that perhaps you could help me out understand what this is used for?
  - I do understand that this service generates a C# PSI(or whatever you choose as language) file for the custom language, and I have also plugged a custom GeneratedDocumentService in my solution, which generates some sample code...

  - But it doesn't seem like this is actually used in the compilation of the projects or anything like that right? / nor does it seem to add any additional "Document view" to the ProjectItem to be displayed.

  - My naive thought was that this probably generates the C# code that is generated in the "code behind" file that you can open in VS - but that seems to bee completely unrealated...

  - So is the result of this only used internally by Resharper to add "code navigation" / "syntax highlighting" / "code analysis"?


I would really appreciate if anyone could help me with some pointers on the subject! :)


While I'm at it - what is the purpose of the "FileDependency" class? - what is it used for?
  - Is it used for "code analysis" / "syntax coloring" internally by Resharper?
  - As it seems to be a ShellComponent - it seems like it's per Solution basis? (would that mean it also keeps track of the required extenral assemblies too? etc..?)

Thank you,
/Peter

5 comments
Comment actions Permalink

Hi Peter!

In genereal answer is Yes, generated docs are used by ReSharper for navigation, higlightings, analyses.
There are projections of regions of generated code to original file, and highlightings, features, analyses & etc of language of generated code automatically available there.
For example, C# context actions and quix fixes on c# inline code in ASPX pages, or JavaScript features inside script tag in Html.

0
Comment actions Permalink

Hi, thank you for taking the time to respond! :)

Ok, so lets say we have the following scenario:

* custom language integration, that should be interoperable with "existing 'typedeclarations' in c# code", for example the following syntax:

def MyClass {
     //Note that this is a variable with the ".net System.String type"
     myValue System.String
     
     def myMethod() {
          //Here we are using the variable ".net System.String type" - obviously we would ideally want all intellisens information etc when typing "myValue."
          myValue.Substring(3)
     }
}


1. Will the generated code behind affect the available "refactorings" / "quick fixes" in my custom language what so ever? (my unerstanding is that I would need to create custom error handling and quick fixes etc... so so in that perspective it will not affect at all?)

2. Will the code behind affect "referring" to ".net / c#" types in any way - or intellisense in my custom language file?

3. Or perhaps it is the other way around - that it will give intellisense to "my generated" files when editing some other .cs file?


* It would be really great if you could give me an example of what the generated document would do, for example:
 - generating the following, would mean that this type appears in ... etc...
 

Thank you, really appreciate it!

/Peter

0
Comment actions Permalink

1. If they are intersecting - yes. For example, rename. Should it rename C# type at caret or your custom rename at the same point? You decide.
2. Again, if you writing custom intellisense you decide what to show over there. If at the same point available C# construction - C# intellisense will be there.
3. Yes, that files are part of project. If you don't want appear anywhere else - generate them private.

About example, let take Razor.

For this file

@model MvcApplication9.Models.RegisterModel @{     ViewBag.Title = "Register"; } @AdministrationHelper.ToolTipForKendoGridColumn("#DataGrid","GetUserInfromation","username","usernameToolTip") <hgroup >     <h1>@ViewBag.Title.</h1>     <h2>Create a new account.</h2> </hgroup> @using (Html.BeginForm()) {     @Html.AntiForgeryToken()     @Html.ValidationSummary()     <fieldset>         <legend>Registration Form</legend>         <ol>             <li>                 @Html.LabelFor(m => m.UserName)                 @Html.TextBoxFor(m => m.UserName)             </li>             <li>                 @Html.LabelFor(m => m.Password)                 @Html.PasswordFor(m => m.Password)             </li>             <li>                 @Html.LabelFor(m => m.ConfirmPassword)                 @Html.PasswordFor(m => m.ConfirmPassword)             </li>         </ol>         <input type="submit" value="Register" />     </fieldset> } @section Scripts {     @Scripts.Render("~/bundles/jqueryval") }


We have this code-behind file

//------------------------------------------------------------------------------ // <auto-generated> //     This code was generated by a tool. //     Generator: ReSharper 8.0.14.856                       //     Runtime Version: 4.5                     // //     Changes to this file may cause incorrect behavior and will be lost if //     the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ namespace ASP {          [__ReSharperSynthetic]     public class _Page_Views_Account_Register_cshtml : System.Web.Mvc.WebViewPage<MvcApplication9.Models.RegisterModel> {       [__ReSharperSynthetic] private static void W‌rite(object value) {}       [__ReSharperSynthetic] private static void W‌rite(System.Web.WebPages.HelperResult result) {}       [__ReSharperSynthetic] private static void W‌riteTo(System.IO.TextWriter writer, object content) {}       [__ReSharperSynthetic] private static void W‌riteTo(System.IO.TextWriter writer, System.Web.WebPages.HelperResult content) {}       [__ReSharperSynthetic] private static void W‌riteLiteral(object value) {}       [__ReSharperSynthetic] private static void W‌riteLiteralTo(System.IO.TextWriter writer, object content) {}       [__ReSharperSynthetic] private static void DefineSection(System.String name, System.Web.WebPages.SectionWriter action) {} [System.Runtime.CompilerServices.CompilerGeneratedAttribute] public override void Execute() {/*Statement​Start*/     Layout = "~/Views/Shared/_Layout.cshtml"; /*Statement​End*/checked{unchecked{}} W‌riteLiteral(@" ");/*​*/ /*Statement​Start*/     ViewBag.Title = "Register"; /*Statement​End*/checked{unchecked{}} W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌rite(AdministrationHelper.ToolTipForKendoGridColumn("#DataGrid","GetUserInfromation","username","usernameToolTip"));/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@"<hgroup ");/*​*/ W‌riteLiteral(@"title""");/*​*/ W‌riteLiteral(@">");/*​*/ W‌riteLiteral(@"     ");/*​*/ W‌riteLiteral(@"<h1>");/*​*/ W‌rite(ViewBag.Title);/*​*/ W‌riteLiteral(@".</h1>");/*​*/ W‌riteLiteral(@"     ");/*​*/ W‌riteLiteral(@"<h2>Create");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@"a");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@"new");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@"account.</h2>");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@"</hgroup>");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@" ");/*​*/ /*Statement​Start*/using (Html.BeginForm()) {     W‌rite(Html.AntiForgeryToken());     W‌rite(Html.ValidationSummary());     W‌riteLiteral(@"<fieldset>");/*​*/ W‌riteLiteral(@"         ");/*​*/ W‌riteLiteral(@"<legend>Registration");/*​*/ W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@"Form</legend>");/*​*/ W‌riteLiteral(@"         ");/*​*/ W‌riteLiteral(@"<ol>");/*​*/ W‌riteLiteral(@"             ");/*​*/ W‌riteLiteral(@"<li>");/*​*/ W‌riteLiteral(@"                 ");/*​*/ W‌rite(Html.LabelFor(m => m.UserName));/*​*/ W‌riteLiteral(@"                 ");/*​*/ W‌rite(Html.TextBoxFor(m => m.UserName));/*​*/ W‌riteLiteral(@"             ");/*​*/ W‌riteLiteral(@"</li>");/*​*/ W‌riteLiteral(@"             ");/*​*/ W‌riteLiteral(@"<li>");/*​*/ W‌riteLiteral(@"                 ");/*​*/ W‌rite(Html.LabelFor(m => m.Password));/*​*/ W‌riteLiteral(@"                 ");/*​*/ W‌rite(Html.PasswordFor(m => m.Password));/*​*/ W‌riteLiteral(@"             ");/*​*/ W‌riteLiteral(@"</li>");/*​*/ W‌riteLiteral(@"             ");/*​*/ W‌riteLiteral(@"<li>");/*​*/ W‌riteLiteral(@"                 ");/*​*/ W‌rite(Html.LabelFor(m => m.ConfirmPassword));/*​*/ W‌riteLiteral(@"                 ");/*​*/ W‌rite(Html.PasswordFor(m => m.ConfirmPassword));/*​*/ W‌riteLiteral(@"             ");/*​*/ W‌riteLiteral(@"</li>");/*​*/ W‌riteLiteral(@"         ");/*​*/ W‌riteLiteral(@"</ol>");/*​*/ W‌riteLiteral(@"         ");/*​*/ W‌riteLiteral(@"<input type=""");/*​*/ W‌riteLiteral(@"submit""");/*​*/ W‌riteLiteral(@" value=""");/*​*/ W‌riteLiteral(@"Register""");/*​*/ W‌riteLiteral(@" />");/*​*/ W‌riteLiteral(@"     ");/*​*/ W‌riteLiteral(@"</fieldset>"); }/*Statement​End*/checked{unchecked{}} W‌riteLiteral(@" ");/*​*/ W‌riteLiteral(@" ");/*​*/ DefineSection("Scripts",()=>{W‌riteLiteral(@"     ");/*​*/ W‌rite(Scripts.Render("~/bundles/jqueryval"));/*​*/ W‌riteLiteral(@" ");}); W‌riteLiteral(@" ");/*​*/ }     } }


Some parts of generated code projecting to original file, and all feature and highlightings over there going to original razor file.
Some parts are internal, for correct syntax structure of code. Obiosly, they are depended on languge in which written code-behind file (here is C#)

0
Comment actions Permalink

Thank you again :)

So, given your example mentioned above - the generated codebehind is not used by the compiler in any way right?
  - I mean, the microsoft compiler can potentially generate something completely different code behind...

If that is true - what I'm having trouble understanding, is what intellisense / refactoring options would change depending on what I generate in the code behind.

If we take the example you specified:
Razor file:

     @Html.LabelFor(m => m.UserName)      @Html.TextBoxFor(m => m.UserName)


Generates the following output as you showed:
Code behind:

     WriteLiteral(@"<li>");      WriteLiteral(@"");      Write(Html.LabelFor(m => m.UserName));      fWriteLiteral(@"");      Write(Html.TextBoxFor(m => m.UserName));      WriteLiteral(@"");      WriteLiteral(@"</li>");


Now I do a refactoring in the "Razor file":
- I do a "Rename" on the "UserName" so that it is called "MyRenamedUserName":
Razor file after refactoring:

     @Html.LabelFor(m => m.MyRenamedUserName)
     @Html.TextBoxFor(m => m.MyRenamedUserName)


Given that I have mapped the ranges correctly between the two files, I expect the codebehind to be updated accordingly(i'm not sure if resharper reparses the file and generates new code behind, or just modyfies the codebehind "in-place"...): 

     WriteLiteral(@"<li>");
     WriteLiteral(@"");
     Write(Html.LabelFor(m => m.MyRenamedUserName));
     WriteLiteral(@"");
     Write(Html.TextBoxFor(m => m.MyRenamedUserName));
     WriteLiteral(@"");
     WriteLiteral(@"</li>");


So far so good - this I can understand, however, what I don't understad is why this matters?
- Lets say that I would implement this differently for Razor - lets say that instead of the output in your example, this would be the output instead:
Code behind:

     WriteLiteral(@"<li>");
     Write("<p>This is a hardcoded label</p>");
     WriteLiteral(@"");
     Write("<input>This is a hardcoded input</input>");
     WriteLiteral(@"");
     WriteLiteral(@"</li>");


How would this affect resharper in any way - I mean, the "Code behind" is just "in memory" for ReSharper, the user doesn't see this in any way?
 - how would this affect highlightning / intellisense / etc?
 
Another example, perhaps having more "refactoring" options:
My custom language file :

//This is a "custom language syntax"
//Class
def MyClass {
     //Private field
     _myValue System.String;

     //Constructor

     this() {
          _myValue = "Hello world"
     }
}


The following "CodeBehind" is generated:
Code behind:

//This is c#
public class MyClass {
     private System.String _myValue;


     public MyClass() {
          _myValue = "Hello world"
     }
}


What if I would generate the following in codebehind instead?:

using System; //Would adding this affect anything?
using System.Collections.Generic; //Same here?

public class MyClass {
     private List<string> _messages;
     private System.String _myValue;

     
     public MyClass() {
          //How would this affect? - does the "intellisense" change or something?
          _messages = new List<string>();
          _myValue = "Hello world"
          _messages.Add(_myValue);
     }

     public override string ToString() {
          var combinedStr = "";
          foreach(var s in _messages) {
               combinedStr += s;
          }
          return "s";
     }
}


Lets just imagine that this is the output that I would generate from my custom language definition - then my custom MSBuild task would pick this file during the compilation process... but what value does ReSharper have from this "Code Behind" genration?

I'm sorry for asking so many questions... I kind of almost feel dumb :p ... but I simply don't understand the "value" of this "code behind" since it's only used for reshaper internals?
   - but I do understand that I'm missing some point here, because all the other "custom language plugins" are generating some code behind :)

Thank you again :)
/Regards
/Peter

0
Comment actions Permalink

Hi Peter. The generated document is an in-memory representation only. It's not passed to the compilers, and is only used by ReSharper.

You define a mapping from the original file to this in-memory file. So, you can have "islands" of C# in your original file, and ReSharper knows the context of those islands by looking at their actual locations in the in-memory generated version. You now have all of the normal analyses, context actions, navigations and code completion as would be available at that point in the generated file.

For example, writing this in a .cshtml file:

@Html.AntiForgeryToken()


maps to this:

public override Execute()
{
 // [snipped]

 Write(Html.AntiForgeryToken());

 // [snipped]
}


Which means ReSharper knows that "Html.AntiForgeryToken()" is being passed as a parameter to Write, and is being called from the Execute method, which lives in a class named after the view and deriving from (something like) WebViewPage<YourModel>. So, it knows that there is a property called Html which is of type HtmlHelper. So, if you hit "." after Html, ReSharper knows to display code completion for the HtmlHelper type, and can give you the AntiForgeryToken as an item in the list. Similarly, it knows that the Model property has type "YourModel" and can provide code completion and navigation based on that.

If you try to use a property or method that doesn't exist, e.g. @UnknownProperty, ReSharper knows that property doesn't exist in the class in the in-memory version, so displays it as a red error. Similarly, if you define a helper method:

@helper DisplayPrice(Decimal price) {
  if (price == 0) {
    <span>Free!</span>
  }
  else {
    @String.Format("{0:C2}", price);
  }
}


Then ReSharper will generate this as a method on the class in the in-memory version. ReSharper now knows that the current class has a DisplayPrice method, and if you type "@this." then DisplayPrice will appear in the code completion list.

Does this help?

Thanks
Matt

0

Please sign in to leave a comment.