Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

The format for JavaScript doc comments

Xml documentation annotations are going to drive JavaScript IntelliSense in Visual Studio Orcas (the next version of Visual Studio). For more than a year now, we've been including xml doc comments to all public APIs in the Microsoft Ajax Library (which are a great place to check out some examples of those comments). We also use the doc comments to automatically generate parameter validation code in the debug version of our scripts.

The format we've been using is an extension of the format that C# and VB.NET are using so it should look familiar to .NET developers. The main differences are the additional attributes we added to convey the (optional) type information that is not provided by the language itself and the place where those comments are written.

Doc comment position

In C# or VB.NET, the doc comment are written before what they describe:

/// <summary>
/// Summary description for Class1
/// </summary>
public class Class1

In JavaScript, we decided to put that information *inside* what it describes so that it can be available at runtime: JavaScript has the interesting feature that calling toString on a function returns the code of this function (when it's propertly implemented), including the comments and thus the doc comments. Here's a similar example in JavaScript where the documentation is written inside of a class constructor:

MyNamespace.Class1 = function() {
    /// <summary>
    /// Summary description for Class1
    /// </summary>

Another difference with C# or VB.NET is that property and event accessors are two different entities in JavaScript. For this reason, we had to choose where the documentation should be for those members. Properties should be documented in the getter and events in the "adder" for this reason:

get_myProperty: function() {
    /// <value>Documentation goes on the getter.</value>
    return this._myProperty;
},
set_myProperty: function(value) {
    // No doc comments here
    this._myProperty = value;
}

Doc comment tags

The locid attribute

Most of the tags that I'm about to describe can have their description text inside of the tag itself or the tag can reference an external description using an identifier as the value of the locid attribute. This of course enables documentation to be localized.

The choice of the identifier is free but our team is using the following convention (the identifiers are actually being automatically generated by our preprocessor):

  • “T:J#MyNameSpace.MyType” for types
  • “M:J#MyNameSpace.MyType.myMethod” for methods (use #ctor as the method name for the constructor)
  • “P:J#MyNameSpace.MyType.myProperty” for properties (in this case the getter would be get_myProperty)
  • “F:J#MyNameSpace.MyType.myField” for fields
  • “E:J#MyNameSpace.MyType.myEvent” for events (in this case the adder would be add_myEvent)
Type attributes

The param, returns, value and field tags that are described below all share the same description of types using attributes. All type information is always optional but you should note that not specifying the type is not the same as specifying type="Object" as everything is not an instance of Object in JavaScript.

The type attribute is used to specify the name of the type of the parameter, return value, property or field.

There are a few cases though where that type is not enough. Namely, in JavaScript, there is no integer type, only a generic Number type. For that reason, and because it's often useful to make that distinction, you can explicitly specify that something is an integer by using both Number as the type and setting the integer attribute to true:

/// <field name="index" type="Number" integer="true"/>

Similarly, DOM elements don't have a type that's well-defined across all browsers. You can specify that somthing is a DOM element by setting the domElement attribute to true. No type should be specified in this case.

Recent research shows that non-null types are extremely useful and should probably represent the norm rather than the exception. For that reason, we added the mayBeNull attribute and made its default value false.

Finally, if the type is Array, it may be desirable to specify the type of the elements of the array (JavaScript has only one variant array type, there is no int[] or String[]). The elementType, elementInteger, elementDomElement and elementMayBeNull are the equivalents for the array elements of the type, integer, domElement and mayBeNull attributes.

<summary locid="descriptionID">Description</summary>

The summary tag can be used in a constructor to describe its class, interface or enumeration, or inside a method, property getter or event "adder" to describe respectively the method, property or event.

<param name="parameterName"
    mayBeNull="true|false" optional="true|false"
    type="ParameterType" parameterArray="true|false"
    integer="true|false" domElement="true|false"
    elementType="ArrayElementType" elementInteger="true|false"
    elementDomElement="true|false"
    elementMayBeNull="true|false">Description</param>

The param tag is used to describe the parameters of a method or constructor. The param tags should be in the same order as the method or constructor's parameters and have the same names.

Optional parameters (which are a way to work around the inexistence of overloads in JavaScript) can be specified using the optional attribute. Optional parameters should come last in the list of parameters and it's good practice to put the "most optional" parameters last.

The parameterArray attribute can be used on the last parameter to specify that this parameter may be repeated as many times as the caller desires. A typical example of this is String.format, which can take as many arguments as the format string enables.

<returns
    type="ValueType" integer="true|false" domElement="true|false"
    mayBeNull="true|false" elementType="ArrayElementType"
    elementInteger="true|false" elementDomElement="true|false"
    elementMayBeNull="true|false">Description</returns>

The returns tag is used to describe the return value of a method. It should be used only if the function returns a value (in other words if it contains a return statement that is not just "return;").

<value
    type="ValueType" integer="true|false" domElement="true|false"
    mayBeNull="true|false" elementType="ArrayElementType"
    elementInteger="true|false" elementDomElement="true|false"
    elementMayBeNull="true|false"
    locid="descriptionID">Description</value>

The value tag describes a property (which shouldn't have a summary tag).

<field name="fieldName" type="FieldType"
    integer="true|false" domElement="true|false" mayBeNull="true|false"
    elementType="ArrayElementType" elementInteger="true|false"
    elementDomElement="true|false" elementMayBeNull="true|false"
    locid="descriptionID">Description</field>

The field tag (which doesn't exist in C# and VB.NET because in those languages the summary tag can be used) is used inside of a class, interface or enumeration constructor to describe a field. A field can't be described near the field itself in JavaScript because the field itself doesn't have a body to contain that description.

<reference path="path/to/the/script/reference.js"
    assembly="Assembly.Name" name="ScriptResourceName.js"/>

The reference tag is used by Visual Studio to describe a dependency a script has with another script file. This enables the IDE to provide IntelliSense on the namespaces and types that are defined in this other file. This tag should be at the top of the file, outside of any code.

The description

The contents of the description itself, whether the tag is summary, param, returns, field or value, is described by the exact same convention that works for C# and VB.NET doc comments. It can be plain text or text containing other XML tags, as described here:

http://msdn2.microsoft.com/en-us/library/5ast78ax(VS.80).aspx

What's next?

This is the convention we're using to document and enhance our scripts and we encourage you to do the same, whether you use Microsoft Ajax or not if only to give your users IntelliSense in Visual Studio.

Similarly, there is no reason why Visual Studio should be the only IDE to handle XML doc comments. We would love it if other IDEs chose to use that format to provide better IntelliSense (or auto-completion).

In a future post, I'll describe how you can extract those XML documentation comments to build usable documentation using existing tools that were designed for managed code.

UPDATE: added the optional attribute for params, which I had omitted.

UPDATE 2: Omar Khan gave a great talk at Mix 07 on the new features in Visual Studio Orcas. Around minute 45, he starts demonstrating JavaScript IntelliSense:
http://sessions.visitmix.com/default.asp?event=1011&session=2012&pid=DEV14&disc=&id=1523&year=2007&search=DEV14

UPDATE 3: added "J#" to naming convention for the locids, to enable managed code and script to implement the same namespaces without a collision in the locids. Also removed locid from param and returns.

UPDATE 4: ScriptDoc, the documentation extraction tool that builds xml files from these doc comments for consumption by Sandcastle, is now available on Codeplex.

Comments

Wallym said:

Awesome, thanks Bertrand.

# April 23, 2007 10:25 PM

Bertrand Le Roy said:

Fabio: for several reasons. First, JSDoc comments are not accessible at runtime. Second, they're missing many features that we need. And most of all, xml doc comments exist in .NET since its inception. .NET developers are very much used to them and how they work. There are also many, many tools that already work with xml doc comments (more about this in future posts) and that will work unmodified with this convention.

# April 25, 2007 1:31 PM

Bertrand Le Roy said:

Speednet: not for the moment but I'll keep that in mind. If you include those tags today, the worst that can happen is that they will get ignored by the tools. Thanks for the suggestion.

# May 11, 2007 2:35 PM

Bertrand Le Roy said:

Caterwomtious: I agree, and I've passed this suggestion along to the right team for a future version (a while ago :) ).

# June 29, 2007 4:50 PM

Bertrand Le Roy said:

Morten: Yes, I agree. Thanks for the suggestion.

# September 5, 2007 1:28 PM

Morten said:

I've created a set of webcontrols that registers scripts (both at runtime and designtime), and these scripts also reference other scripts using the <reference assembly.../> tags.

When I add the webcontrol to the page, I would think that the scripts also gets included in the page so I get intellisense on them, but apparently not.

I might be doing something wrong, but if not, I'd like to see this kind of support in Orcas too.

# October 1, 2007 3:55 PM

Bertrand Le Roy said:

Morten: you can get Intellisense on those files by including an explicit script reference to the page.

# October 1, 2007 3:57 PM

Ev said:

Is there a limit to the amount of js that can be commented?  I'm getting a strange error when the .js file with comments is over 256K.  I've tried splitting into two files and same result:

"Error updating JScript IntelliSense: JScript IntelliSense has been disabled for referenced scripts. A referenced script may have caused a stack overflow. Reopen this file to reenable IntelliSense."

# November 14, 2007 11:22 AM

Bertrand Le Roy said:

Ev: not that I know of, but send me mail through the contact form of this blog and I'll get you the right contacts to debug that issue.

# November 14, 2007 2:14 PM

Jim Baltika said:

WebSite.WebCameraInfo.prototype = {

   ZoomLevel: 11,

....

Hi guys. Does any have any ideas how to comment prototype getter like this?

# December 15, 2007 1:03 PM

Bertrand Le Roy said:

Jim: that's not technically a getter, but you can document it from the constructor using a field tag.

# December 17, 2007 4:30 PM

Bertrand Le Roy said:

It should be.

# January 29, 2008 1:27 PM

Brock Allen said:

In VS2008, if I use:

function Foo()

{

/// <param name='x' domElement='true'>x</param>

}

Should I get DOMElement intellisense for x? If so, it doesn't seem to be working. Same for:

/// <returns domElement='true'>

# February 25, 2008 2:17 PM

Bertrand Le Roy said:

@Brock: I don't think that's handled in 2008, no.

# February 25, 2008 2:27 PM

whatispunk said:

I'm also getting the same error as Ev.

"Error updating JScript IntelliSense: JScript IntelliSense has been disabled for referenced scripts. A referenced script may have caused a stack overflow. Reopen this file to reenable IntelliSense."

Any progress on that one? This is the only Google result for that error message.

# February 26, 2008 12:29 PM

Jeff King (msft) said:

@whatispunk: There actually is a limit to the XML Comment data per section.  This was a implementation limitation we could not work around.  I'm sorry to say that you'll just have to shorten your XML Comment data.  I'll keep this in mind to fix for our next release.  Thanks!

# February 27, 2008 1:42 AM

Mina said:

create comments by php

# April 14, 2008 11:51 AM

VR2 said:

Could we have a static "parse" method added to all classes, in order for clients to be able to cast to type?

eg

Sys.SomeClass.parse = function(obj)

{///<returns type="Sys.SomeClass"/>

   return obj;

}

Client Code would be

function onSomeClassClick(sender, args)

{

   sender = Sys.SomeClass.parse(sender);

   args   = Sys.SomeClassEventArgs.parse(args);

   // Full JS intellisense now enabled.

}

Thanks

# May 12, 2008 9:31 AM

benizi said:

I think I'm missing something, but it might be related to Brock's question to which you replied, "@Brock: I don't think that's handled in 2008, no."

A simplified example:

/// <reference path="Stuff.js" />

var outside = new MyNamespace.Stuff();

outside.+++

Other.Thing.prototype = {

foo: function (inside) {

 /// <param name="inside" type="MyNamespace.Stuff">arg</param>

 inside.---

At the '+++', I get working Intellisense completion. At the '---', I don't. The point being that it obviously found the proper comments for the 'Stuff' class, since it works at one point in the file. Am I doing something wrong (specifying the class name improperly?)? or does VS2008 simply not do Intellisense for <param>'s?

Best,

Ben

# July 11, 2008 12:51 AM

VR2 said:

Again, Ben, an ability to statically parse or cast to type would solve that for you.

This ability can be added by us, retrospectively, via an extensions library applied after MS Ajax is downloaded.

eg

Sys.ApplicationLoadEventArgs.cast = function(obj)

{///<returns type="Sys.ApplicationLoadEventArgs"/>

   return obj;

}

By also doing this in your own classes, your constructor could have code like this:

inside = MyNamespace.Stuff.cast(inside);

inside.---; // Intellisense is now fully enabled.

Also, the lack of "in file" intellisense can be largely overcome by structuring your code differently to the way advertised/recommended.

Firstly you need to ensure that your namespace is defined in an external file (so the namespacing code can run and create the namespace for you ready to go). I have found adding a "_namespace.js" file to contain js intellisense references as well as namespace definition works well. This file is not required to be downloaded to the client. These can also be chained from child to parent folders, for each namespace required.

Secondly, after declaring your class (function) you can get intellisense by setting your function's prototype to that of the class you intend to inherit from. This duplexing (actual inheritance/temp inheritance) can be offset by setting these lines of code together.

Thirdly, when setting out your prototype, rather than set the whole thing (and overwriting the prototype you have just faked), writing it out longhand to add each item to the prototype adds a few more keystrokes but, if done in the correct order, I've found can yield 99% working intellisense at all times.

eg

if ($methodThatAlwaysReturnsFalse())

{

   MyNamespace.Stuff.prototype = Sys.xxxx.yyyy.prototype // Fake inheritance

}

...

MyNamespace.Stuff.prototype._myVar = null;

MyNamespace.Stuff.prototype.foo = function()

{

   this._myVar; // intellisense now enabled

   this.initialize(); // inherited methods are also available.

...

}

# July 22, 2008 7:07 PM

Xun said:

Stumbled into this blog when I was searching for JavaScript doc comments. Great post, more so, great blog, more so, great fun cachy hippy evil banner. I did not see your trademark avatar here, but that is great too.

# August 2, 2008 9:32 PM

Bertrand Le Roy said:

@Jimmy: sorry, I know nothing about XPIDL.

# August 21, 2008 1:57 AM

89 said:

Hi, Could we have a static "parse" method added to all classes, in order for clients to be able to cast to type?

eg

Sys.SomeClass.parse = function(obj)

{///<returns type="Sys.SomeClass"/>

  return obj;

}

Client Code would be

function onSomeClassClick(sender, args)

{

  sender = Sys.SomeClass.parse(sender);

  args   = Sys.SomeClassEventArgs.parse(args);

  // Full JS intellisense now enabled.

}

Thanks

# August 22, 2008 6:07 PM

Bertrand Le Roy said:

@89: that's been suggested before but it requires to write code in order to support IntelliSense. Instead, we prefer to make IntelliSense work in that case with no extra code.

# August 25, 2008 2:55 PM

VR2 said:

The "89" comment was left by me but I did not use that name nor that URL.

I can see why you might like to have code comments solve this problem of "casting" but

#1 Is there even a doc comment syntax for doing what is being done in the above example yet?

#2 To implement and call to a cast method is not alot of (additional) code.

#3 Even in C# we need to make use of casting, not always do we know the exact type being passed in. Even then we may wish to cast down or up to various types in the inheritance chain.

#4 Sometimes one might want to cast one object into multiple seperate interfaces.

So while I appreciate your keen-ness on not adding any additional code lines, I'm not sure you could cater for these scenarios with doc comments alone?

# August 27, 2008 7:39 AM

Bertrand Le Roy said:

@VR2: we're looking at ways to enable type hint comments in the next version of Visual Studio.

# August 27, 2008 5:34 PM

James said:

Is there a way to add doc comments to global variables that are not part of a class? For example:

/// <field name="MyVar" type="String" mayBeNull="false">

/// This is my global variable.

/// </field>

var MyVar = "my global value";

# September 22, 2008 4:23 PM

James said:

Also, is there a way to emulate an enumeration using doc comments?  For example:

ConnectionState = function()

{

  /// <summary>An enumeration of connection states</summary>

  /// <field name="Open" type="Number" integer="true" mayBeNull="false">

  /// The connection is open.

  /// </field>

  /// <field name="Closed" type="Number" integer="true" mayBeNull="false">

  /// The connection is closed.

  /// </field>

}

The code above doesn't work (e.g. no intellisense), but is there another variation that could make it work?  I would like to be able to type "ConnectionState." and get a list of all the enumeration values, with intellisense for each value.

# September 22, 2008 4:28 PM

Neil Stevens said:

Hi,

Great blog, i just wanted to know if there is a way to add comments in javascript like we can in C#, simply by typing /// and then the ide would insert the code comment like C# does.

Is something like this possible?

# October 1, 2008 11:11 AM

NanhTrang said:

return should have attribute like numParam and allow multi return comment. example:

function func(a, b) {

/// <returns type="String" numberParam="1"></returns>

/// <returns type="Array"></returns>

if (b === undefined) {

return a;

} else {

return [a, b];

}

}

# October 27, 2008 2:53 PM

Angus McDonald said:

Looking forward to Code Snippets Manager supporting JavaScript files/language, or better yet being able to do the /// inside a function and having it create template comments for me ...

# November 19, 2008 7:41 PM

Bertrand Le Roy said:

@Angus: If you haven't already, check out this presentation by Jeff: channel9.msdn.com/.../TL48

# November 19, 2008 7:52 PM

Erik said:

Finally figured out a way to document member variables for Intellisense: a param tag is needed along the field tag.

myClass = function() {

   ///<summary>MyClass comment</summary>

   ///<param name="field" type="string">Dummy not visible.</param>

   ///<field name="field" type="String">member variable comment</field>    

};

# September 15, 2009 2:19 PM

Kris said:

It would be very nice if Visual Studio recognized jsdoc toolkit style comments in it's intellisense.  The old xml style comments are very time consuming to type by hand.  The only reason they were usable in C# was because when you type three slashes it autocompletes a comment structure for you.  Having to type that by hand just isn't going to happen.  The Jsdoc toolkit comment style has become pretty much the defacto standard.  They are significantly easier to type by hand.

# June 3, 2010 1:42 AM

Andrew Powell said:

Great post B. One question is still bothering me though and I haven't been able to find any resources which explain it: what the heck does the 'locid' attribute do on summary tags? Any chance you've stumbled across an answer to that?

# November 11, 2010 1:46 PM

Bertrand Le Roy said:

@Andrew: the locid is a hook that can be used to handle translations. It doesn't "do" anything out of the box.

# November 11, 2010 3:30 PM

Luke Page said:

Any idea why vs2010 shows the type from a properties value tag, but not the comment inside the tag? For that you have to add a summary to both getter and setter...

# July 18, 2011 12:40 PM

Bertrand Le Roy said:

@Luke: you would have to ask the Visual Studio team about their implementation.

# July 18, 2011 2:11 PM