Click here to Skip to main content
Click here to Skip to main content

Visual Studio JavaScript Intellisense Revisited

, 22 Apr 2010
Rate this:
Please Sign up or sign in to vote.
Or: “What I hope is not just ‘Yet Another Visual Studio JavaScript Intellisense Walkthrough’”

Or: “What I hope is not just ‘Yet Another Visual Studio JavaScript Intellisense Walkthrough’”.

Author note: If this content seems dated, it is. When I started this blog last year, I began by resurrecting a monster JavaScript documentation project that had been dormant for a year. Then I got busy and dropped it again. So here I am picking it back up again to provide some motivation for actually finishing a POJO documentation generator using XML doc comments and SCHB. While VS JScript Intellisense is nothing new, I think that there are topics covered that I have not seen in other posts.

UPDATE: JS enum intellisense is broken in VS10 RC.  I will update when resolved.

UPDATE: 04-22-2010 - Ok, am in the process of exploring VS10 JS intellisense support and will update shortly. 

The VS08 JavaScript intellisense engine is driven, in part, by two separate but related types of artifacts; Ajax specific object properties and XML doc comments.

XML doc comments are a fairly well known quantity these days, so let’s focus first on the Ajax specific properties.

MsAjax has three primary methods that comprise the typing system. These are:

  • Type.registerNamespace()
  • Type.registerClass()
  • Type.registerEnum()

When these methods are called, a lot of moving parts are involved in the context of MsAjax but in the scope of this discussion, we are only interested in what the object looks like when it comes back.

If you compare the properties of an object before and after it is passed to one of the register methods, you will notice a handful of new properties, all prefixed with double underscore.

The three we are most interested in are __namespace, __class and __enum

These properties have special meaning to Visual Studio and change the visual cues that are presented in the intellisense window.

Consider Listing 1 and the accompanying image. This represents the typical view a developer has when coding against some POJO code.

Listing 1 - POJO

// declare a 'namespace'
var MyNamespace = {};

// declare a 'class'
MyClass = function() {
}

// declare an 'enum'
MyEnum = function() { }
MyEnum.prototype = { None: 0, One: 1, Two: 2 };
MyEnum.None = 0;
MyEnum.One = 1;
MyEnum.Two = 2;

Now, if we simply decorate each object with a boolean property that describes the type, Visual Studio will adjust its visual cues accordingly. These properties are predictably named __namespace, __class and __enum. As you can see, in the case of enum, there is a little more happening to provide a fairly accurate implementation of an enum in JavaScript and we will cover that in some detail.

Consider Listing 2 and the accompanying image. While this represents no functional change to the code and the value of these visual cues is totally subjective, I have found that in large object models, these visual cues have definite value and I implement them whenever it makes sense to do so.

Listing 2 - AJAX

// declare a 'namespace'
var MyNamespace = {};
MyNamespace.__namespace = true;

// declare a 'class'
MyClass = function() {
}

MyClass.__class = true;

// declare an 'enum'
MyEnum = function() { }
MyEnum.prototype = { None: 0, One: 1, Two: 2 };
MyEnum.None = 0;
MyEnum.One = 1;
MyEnum.Two = 2;
MyEnum.__enum = true;

JavaScript Enums and Intellisense

We are now going to start to blur the lines between eyecandy and true value add.

Enums are a data structure that provide both human friendly labels and type-safe values to a variable and are widely used in most programming languages. The virtues and benefits of the proper use of enums should be a given to any programmer and will not be further discussed here.

Over the years, I have attempted and encountered many techniques at implementing enums in JavaScript to varying degrees of success. Each carried its own smell/value ratio but in no case did the implementation lead to intellisense and code completion.

The technique I finally settled with was to specify the Name/Value pairs in both the prototype and on the function itself as illustrated in Listings 6 and 7. The results were functional but unspectacular from an intellisense perspective. Peep this…

It is not obvious from the image but this is me dotting my enum and getting nothing.

So, it may be understandable why, when I initially started playing with msajax and I actually saw JavaScript enum intellisense and code completion with pretty enum-y visual cues, I went diving into MicrosoftAjax.js.

When I did find the code that created the structure, registerEnum, I was both surprised and confused. What I found was a technique functionally equivalent to what I was doing but had no idea as to why my enums were flat with no code completion for the values.

It wasn’t until I read through the entire MicrosoftAjax.js file that I encountered the only two enums declared, "Sys.UI.MouseButton" and "Sys.UI.Key" that it made sense.

The code included C# style XML doc comments inside the function that described the fields. I was definitely interested in this in general but at the time I was anxious to apply it to my own enums and see the results.

Listing 3 – JavaScript XML Doc Comments on Enums

// declare an 'enum'
MyEnum = function() {
/// <field name="None" type="Number" integer="true" static="true"/>
/// <field name="One" type="Number" integer="true" static="true"/>
/// <field name="Two" type="Number" integer="true" static="true"/>
}
MyEnum.prototype = { None: 0, One: 1, Two: 2 };
MyEnum.None = 0;
MyEnum.One = 1;
MyEnum.Two = 2;
MyEnum.__enum = true;

Now that is purty and, unlike the previous eyecandy, does represent a value add to the Visual Studio JavaScript coding story.

So, with much excitement, I re-reviewed the Ajax code and sussed out a pretty sound understanding of how to provide visual cues and intellisense for properties and events.

  • Property accessors: prototype functions prefixed with ‘get_’ and ‘set_
  • Event handler accessors: prototype methods prefixed with ‘add_’ and ‘remove_

Listing 4 – Fully documented model

MyNamespace = {
    MyEnum: function() {
        /// <span class="code-SummaryComment"><summary>enum summary</summary>
</span>        /// <span class="code-SummaryComment"><field name="None" type="Number" integer="true" 
        /// static="true">enum field summary</field>
</span>        /// <span class="code-SummaryComment"><field name="One" type="Number" integer="true" 
        /// static="true">enum field summary</field>
</span>        /// <span class="code-SummaryComment"><field name="Two" type="Number" integer="true" 
        /// static="true">enum field summary</field>
</span>        throw new Error();
    },

    MyClass: function(publicFieldValue) {
        /// <span class="code-SummaryComment"><summary>class summary</summary>
</span>        /// <span class="code-SummaryComment"><param name="publicFieldValue" type="String">ctor param summary</param>
</span>        /// <span class="code-SummaryComment"><field name="publicField" type="String">public field summary</field>
</span>        /// <span class="code-SummaryComment"><returns type="MyNamespace.MyClass">return type summary</returns>
</span>
        if (this instanceof MyNamespace.MyClass) {

            this.publicField = publicFieldValue; 	// public fields can be exposed 
						// and documented
            this._propertyBacker = publicFieldValue; 	// prefix underscore 
						// usually hides 
						// 'private' fields

            this._priveledgedMethod = function(param) {
                // comments on privileged methods are ignored by visual studio
                // <summary>priveledgedMethod summary</summary>
                // <param name="param" type="MyNamespace.MyEnum">
                // priveledgedMethod method param summary</param>
                // <returns type="Array">priveledgedMethod return type summary</returns>

            }
        } else {
            return new MyNamespace.MyClass(properties);
        }
    }
    , __namespace: true
};


MyNamespace.MyEnum.prototype = { None: 0, One: 1, Two: 2 };
MyNamespace.MyEnum.None = 0;
MyNamespace.MyEnum.One = 1;
MyNamespace.MyEnum.Two = 2;
MyNamespace.MyEnum.__enum = true;

MyNamespace.MyClass.prototype = {
    publicMethod: function(param) {
        /// <span class="code-SummaryComment"><summary>publicMethodmethod summary</summary>
</span>        /// <span class="code-SummaryComment"><param name="param" type="MyNamespace.MyEnum">
</span>        /// publicMethod param summary<span class="code-SummaryComment"></param>
</span>        /// <span class="code-SummaryComment"><returns type="Array">publicMethod return type summary</returns>
</span>        return this._privelegdedMethod(param);
    },
    get_propertyBacker: function() {
        /// <span class="code-SummaryComment"><summary>property getter summary : proscribed</summary>
</span>        /// <span class="code-SummaryComment"><value type="String">property value summary</value>
</span>        return this._propertyBacker;
    },
    set_propertyBacker: function(value) {
        /// <span class="code-SummaryComment"><summary>property setter summary : proscribed</summary>
</span>        /// <span class="code-SummaryComment"><param name="value" type="String">property setter param summary : 
</span>        /// implicitly proscribed<span class="code-SummaryComment"></param>
</span>        this._propertyBacker = value;
    },
    add_eventHandler: function(handler) {
        /// <span class="code-SummaryComment"><summary>event handler add summary</summary>
</span>        /// <span class="code-SummaryComment"><param name="handler" type="Function">event handler add param summary: 
</span>        /// not mentioned<span class="code-SummaryComment"></param>
</span>        this._eventHandler = handler;
    },
    remove_eventHandler: function(handler) {
        /// <span class="code-SummaryComment"><summary>event handler remove summary : implicitly proscribed</summary>
</span>        /// <span class="code-SummaryComment"><param name="handler" type="Function">
</span>        /// event handler remove param summary : implicitly proscribed<span class="code-SummaryComment"></param>
</span>        this._eventHandler = handler;
    }

}
MyNamespace.MyClass.__class = true;

When I found the typed <returns/> tag in MsAjax, I realized that this was the true gem. Of the various facets of Visual Studio JavaScript Intellisense, Type Inference alone is enough to justify XML doc comments. Type Inference via the <returns/> tag is what makes possible, amongst many other things, the elegantly fluent jQuery API when used in Visual Studio.

Here is Listing 4 in action

Sometime later, I encountered a blog post by Bertrand LeRoy that provided a complete specification of the format for JavaScript Intellisense comments. Other references include the Gu’s post on the subject, a condensed version on StackOverflow and, of course, there is also MSDN content but it is of limited value, in my opinion.

I will let you get the ‘official’ lowdown from those posts. What follows are my results and observations.

It came as some surprise to me that some of the XML-doc structures I had discovered either by extrapolating what I read in MicrosoftAjax.js or as a result of trial and error that work so well were either not mentioned in any of the official sources listed above or explicitly advised against with no rationale or justification, leaving me in a somewhat untenable position. I don't live to question authority but when something that seems to be working for me is proscribed without explanation, I tend to resist.

Amongst the ‘rules’ I found myself breaking yet still providing valuable intellisense are:

  • summary tags on property getters
  • summary and param tags on property setters
  • param tags on event handler adders
  • summary and param tags on event handler removers

When I put on my ‘let’s be reasonable and try to infer these apparently unknowable reasons not to do this” hat, the only thing I can come up with, since Visual Studio seems to be quite content, is that perhaps documentation generators may be confused by them. When I consider that the only jscript XML doc comment generator that I know of is Betrand’s AjaxDoc and it consumes object models built with MsAjax in the context of MsAjax, I find myself not overly concerned. A few years ago, I made an initial stab at a POJO doc generator using XML doc comments and will most likely revive it once VS10 goes gold.

As a side note: After years of dormancy, I have noticed some recent activity in AjaxDoc and can only assume it is in preparation for the latest version of Ajax and Visual Studio. This is a good thing, I would like to see the benefit of the past few years experience manifest in improvements to that project. It has great potential.

Buggy Behavior of the Visual Studio Jscript Intellisense Provider

After having gone to all the effort to emplace XML doc comments and seeing the results you may find yourself confused and dismayed when, for some unknown reason, your visual cues revert to generic blue or maroon jscript members and you lose the summary tooltip.

Say you have an object similar to our MyClass object. New one up, then add an event handler. So far so good. Now assign a value to publicField. Dot your instance… Oops, add_eventHandler has reverted icons and lost its tooltip! This odd behavior appears to be a side effect of the intellisense parsing process and represents a serious bug that I doubt will get any attention this late in the life of VS08. If you are scoped when it breaks, rest assured it will be normal when starting a different scope but if you break it at the window level, well… no more purty pitchers and tooltips for you for the rest of that file.

While the visual cues dissipate, the type inference afforded by the <returns/> tag are still functional. This in itself justifies the effort and the rest is gravy.

And I know what you are thinking…. “Sky, what do you expect when you emplace XML doc comments that you have been advised by those on high should not be emplaced?”. To which I respond: Of course I have given much time and effort in implementing jscript doc comments in the prescribed manner in the interest of resolving this odd behavior and it simply made no difference. Ultimately, I believe the proscription of certain tags by Bertrand is motivated by the fact that they may confuse the current automatic documentation generation implementation. And since that implementation only handles Ajax classes in the context of MicrosoftAjax, this does not concern me much. I have an dormant POJO documentation generator project just waiting to be revived.

There is encouraging news, though. This odd behavior appears to have been remedied in VS10 RC. I was only able to uncover one obvious bug in the Jscript intellisense for VS10 in the handling of Enum field tag content and submitted the bug. It is nice to know that all of this effort is not to be wasted when migrating to VS10.

Note: VS08 Jscript intellisense only reliably works in ‘other’ files., i.e. you will not reliably see intellisense cues for the code in the currently active file. This has been fixed in VS10. For VS08, simply place your libs in ‘other’ files and use script and reference tags. And VS08 JScript Intellisense does not work reliably at all unless you patch your VS. Although the patch specifies x86, it is meant for x64 as well. And a good thing that is because there is no x64 specific version Wink | ;-)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sky Sanders
Software Developer (Senior) Salient Solutions
United States United States
My name is Sky Sanders and I am an end-to-end, front-to-back software solutions architect with more than 20 years experience in IT infrastructure and software development, the last 10 years being focused primarily on the Microsoft .NET platform.
 
My motto is 'I solve problems.' and I am currently available for hire.
 
I can be contacted at sky.sanders@gmail.com

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberstian.net30-Nov-11 3:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 22 Apr 2010
Article Copyright 2010 by Sky Sanders
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid