I spoke in a previous post about the basics of getting Visual Studio to offer you some help when you’re coding.  I wanted to expand a little on what you can do, especially if you’re producing libraries for other people to use.

Optional Parameters

One thing I didn’t talk about last time was how you represent optional parameters.  In JavaScript, a function doesn’t have to be given all the arguments it asks for to be called.  The function could recognise it hasn’t been given all the arguments and instead, use defaults.

function getPage(id, editMode){
/// <param name="id" type="Number">ID of page</param>
/// <param name="editMode" type="Boolean" optional="true">
///  Should the page be opened in edit mode.  Defaults to false.
/// </param>
  if (editMode) {
    return new editPage(id);
  } else {
    return new Page(id);
  }
}

When you view this in intellisense, the optional argument will be in square brackets, which neatly displays that you can leave it out with confidence.

Fields

In the following code sample, intellisense won’t know what type x and y should be when returned (although it will work out they’re the same as what went in).  But it also won’t be able to figure out any info about what x and y really represent.

function point(x, y){
    return {x:x, y:y};
}

If we add the field annotation, we can add descriptions and types to our properties.

function point(x, y){
    /// <field name="x" type="Number">The x co-ordinate of the point</field>
    /// <field name="y" type="Number">The y co-ordinate of the point</field>
    return {x:x, y:y};
}

Note though that the following code won’t make intellisense allways add the x and y properties to our object

function point(x, y, isCartesian){
    /// <field name="x" type="Number">The x co-ordinate of the point</field>
    /// <field name="y" type="Number">The y co-ordinate of the point</field>
    if(isCartesian) {
      return {x:x, y:y};
    } else {
      return {magnitude:x, direction:y };
    }
}

Intellisense doesn’t actually run the code, so it won’t necessarily spot that x and y are there in certain cases, it might not always even be possible to tell (for example if isCartesian is given a value downloaded off a web-server.

Separating intellisense files from Code

As you can see, there’s now likely to be a lot of code at the top of some functions dedicated to intellisense.  A summary, a param for each parameter, a field for each field in the result and a returns statement to give a type to the returned value.  Although well thought out comments can enhance readability of code.  The excessive commenting produced by someone trying to produce perfect intellisense tends to hurt readability.  Couple this with the fact that intellisense struggles if our code isn’t totally easily deterministic and we have a problem.

What we need is for intellisense to use a different code file to the running code.  Then we can have one file which is virtually all comments and is clearly deterministic, but wouldn’t actually work if you tried running it and another file which contains the code, and just the relevant comments to understand what it’s doing.

Visual Studio uses a clever trick when looking at the naming of your files.  If the file your code is in is called “app.js” or “app.min.js” then intellisense will check for a file called “app-vsdoc.js” in the same folder and if there is one, it will use that.  This means that typically what you will actually have in your project is 3 JavaScript files for each library/component.  The file which just ends .js will contain the source code.  The file ending .min.js will contain a minified copy of the source code with all comments and unnecessary white-space removed.   And the file ending -vsdoc.js will contain your vsdocs and a vastly simplified version of the code which doesn’t actually need to work.  For example, here are 3 versions of the function add in their respective files.

math.js

function add(x, y){
    //sum two numbers
    return x + y;
}

math.min.js (this would be automatically generated using one of the many minification tools

function add(x,y){return x+y;}

math-vsdoc.js (note how we don’t have to use the same argument names [although we could if we wanted to] and that we can return any result, as long as it’s of the correct type to produce the right intellisense).

function add(arg1, arg2){
   /// <summary>Returns the sum of two numbers</summary>
   /// <param name="arg1" type="Number">The first value to sum</param>
   /// <param name="arg2" type="Number">The second value to sum</param>
   /// <returns type="Number"/>
   return 0;
}

Overloading

Although vsdocs doesn’t offer any support for overloading as such, but there are 3 things that we can test in order to give ourselves a better chance of returning the correct value.

Ideally I recommend doing everything by testing the number of arguments you’re given.  Intellisense is relatively likely to be able to figure out the correct values for this.

arguments.length

When that’s not enough, you can use typeof and .constructor to try and work out what type you have been given.

 typeof value === "string"

value.constructor === Array

The key to doing this right is always have a default return value.  If intellisense doesn’t know what to return, you will lose ALL help.  I therefore recommend that you default to returning whatever object is most complex, so as not to remove all help from the user.

For an example of them in use, this is part of the vsdocs for raphael:

 Element.prototype.attr = function (name, value) {
 /// <summary>Sets or gets attributes of the element.
 /// It accepts either a name/value pair, an object of name/value pairs, an attribute name to get, an array of attribute names to retrieve as an array.</summary>
 /// <param name="name" type="String/Array/Object">Name of attribute, Array of attribute names or object specifying attribute values.</param>
 /// <param name="value" type="Object" optional="true">The value to set the attribute to.</param>
 /// <returns type="Element/Object/Array" />
 if (arguments.length === 1 && typeof name === "string") { return {}; }
 if (arguments.length === 1 && name.constructor == Array) { return[]; }
 return this;
 };

Here “this” refers to a raphael element and has many complex methods so you don’t want to accidentally lose all intellisense on it, but if intellisense knows it’s giving the function an Array or String, then ideally we want to return an object or array as appropriate.

The rest of this is just my rant, neither vsdocs nor JavaScript offer any true built in support for overloading.

JavaScript offers the ability to fake out the “overloading” of methods by testing the types of arguments and the number of arguments given and performing different tasks based on that information.  I would therefore like a way to represent this in vsdocs.

The way I would imagine this would work is something like the following.  The user would then see the typical up&down arrows on the intellisense popup to choose between overloads when calling the method.  I wouldn’t want the JavaScript interpretation to change, this would just be an extension of the vsdoc format.

//!!Warning, not actual code!!
function add(arg1, arg2){
   /// <overload>
   /// <summary>Returns the sum of two numbers</summary>
   /// <param name="arg1" displayName="x" type="Number">The first value to sum</param>
   /// <param name="arg2" displayName="y" type="Number">The second value to sum</param>
   /// <returns type="Number"/>
   /// </overload>
   /// <overload>
   /// <param name="arg1" displayName="pair" type="Pair"> A pair (x,y) to sum</param>
   /// </overload>
   return 0;
}
Advertisements