Manual talk:Coding conventions/JavaScript

About this board

Class descriptions in JSDoc

3
Summary by APaskulin (WMF)

Chose option 2 and updated the page

APaskulin (WMF) (talkcontribs)

Hi all, I'd like to discuss documentation comment conventions for classes as part of the migration to JSDoc. In JSDoc, specifically for classes:

  • The main description in the documentation comment (or the @description tag) is interpreted as the description of the constructor
  • The @classdesc tag is interpreted as the description of the class[1]

These two description fields appear in the documentation site as:

# mw.MyClass
Class description

## Constructor
### new mw.MyClass()
Constructor description

There are two ways this can be formatted in the comment:

1. Constructor description first: Simpler but in reverse order from how the information appears on the documentation site

/**
 * Constructor description first line.
 * 
 * Constructor description continued...
 * 
 * @classdesc Class description first line.
 * 
 * Class description continued...
 * 
 * @param string myParam
 */

2. Class description first: More verbose but reflects the same order as the documentation site

/**
 * @classdesc Class description first line.
 * 
 * Class description continued...
 * 
 * @description Constructor description first line.
 * 
 * Constructor description continued...
 * 
 * @param string myParam
 */

The empty lines aren't required by JSDoc, but I've included them for readability and consistency with Manual:Coding conventions/Documentation.

Which option should we standardize on? Ideally, we'd be able to use @constructor instead of @description, but JSDoc doesn't support that. Of the two options, I think option 2 is the least ambiguous and easiest to reason about.

Krinkle (talkcontribs)

I think developers have a tendency to, whatever comes first in that block, is where we put high-level explainers, detailed descriptions, and usage examples for the class as a whole.

Having that be tucked away in the constructor description would be unfortunate and make the information less discoverable and thus waste investments in it somewhat.

For that reason, and for parity with PHP conventions wherever possible, I'd suggest option 2.

@APaskulin (WMF) I believe your code example is for the lower-level ("ES5-style") class, where the class and constructor are defined together as function FooBar(…) {…}.

When ES6 syntax sugar is used (which makes it look like the class and constructor are separate things), there is usually two separate doc blocks:

class FooBar {
 constructor() {…}
}

Can you add an example for this as well? Does it require the same tags? I'm hoping @description would no longer be needed in that case, and would @class stay in the first block?

APaskulin (WMF) (talkcontribs)

That's a great point. This is much cleaner and works correctly:

/**
 * Class description.
 */
class myClass {
	/**
	 * Constructor description.
	 * 
	 * @param ...
	 */
	constructor() {...}
};

Adding a @class tag to the ES6 example (in either comment) actually messes it up and causes JSDoc to duplicate the class.

In the ES5 example, the @class tag isn't necessary in most cases, but it doesn't break anything. I included it because we have a lot of @class tags in MediaWiki core, but I'll update that post to remove it since it's not required.

Adding @stable as a defined tag

3
Summary by MusikAnimal
MusikAnimal (talkcontribs)

With the advent of the frontend stable interface policy, there's a need to mark things as stable. This is especially true in the near-term as most frontend code doesn't have any indication of stability, so visibly seeing @stable tells the developer that method/class is there to stay.

JSDoc/JSDuck does not currently support @stable but a similar feature has been proposed. While our generated docs won't do anything special when they see this tag, it'd be nice to be able to use it without eslint yelling.

Thus, I'm proposing we allowlist @stable through the use of the definedTags option for the check-tag-names rule.

Later, once the new frontend stable interface policy has matured, we may wish to be more granular like we do for PHP and tag things as "@stable to call", "@stable to extend", etc., but for now just being able to use "@stable" at all (without having to add an ignore rule) I think is a good start.

Samwilson (talkcontribs)

Support Sounds like a good idea to me. Sam Wilson 05:40, 18 October 2023 (UTC)

Jdlrobson (talkcontribs)

Support Jdlrobson (talk) 18:39, 20 October 2023 (UTC)

Discouraging from using a single "var" for multiple variable declarations.

1
Summary by MusikAnimal

The "one-var" rule was removed last year. See Topic:W5dh34nv202fzq7f for more.

Danwe (talkcontribs)

I have been thinking and researching about the topic of multiple vs. single var statements recently and have changed my ways entirely. I switched from using var do declare bunches of variables at the same time to using one var per line. Here are some reasons, going so far that I would completely discourage from using a single var for anything more complex than var a, b, c;

  • Multiple var are better readable in diffs. Each line ends with a ";" and is completely self-contained. E.g.:

var whatever = 0,
    whatever2 = 2;

vs.

var whatever = 0; 
var whatever2 = 2;

Two lines changed One line changed
The multiple var version will therefore keep the git blame for the first line in tact, which is a huge advantage. Changing a line of code as a side effect of syntactical sugar or preferences is bad.
  • Huge difference when debugging. If you have a list of five variables using one var and you want to see the value of the second one before creating the third one, then this is not really possible in any debugger known to me since all five variables are created in one step.
  • Whenever tabs are displayed with 8 spaces, the whole one var thing looks incredibly ugly:
var foo = 123,
        bar = 321;
vs.
var foo = 123;
var bar = 321;
  • When assigning initial values does not fit into one line, one is left with the question how to spread the assignment over multiple lines and I have seen various models, none of which makes me feel comfortable when looking at. Some examples (there are many more variations, you get the idea I hope):
// Quick skimming over the code could give you
// the expression "xxx" is a variable as well. 
var foo = foo(
    xxx + "someverylongstring..." ),
    anothervar = false;
// less misleading but still kind of ugly
var foo = foo(
        xxx + "someverylongstring..." ),
    anothervar = false;
var foo = {
    field: 1
},
    bar = {
        field: 2
    };
vs.
var foo = {
    field: 1
};
var bar = {
    field: 2
};
or, adding tabs for
formatting the first
var as soon as the
second gets added, sucks
a lot when diffing again:

var foo = {
■■■■    field: 1
■■■■};,
    bar = {
        field: 2
    };

vs.

var foo = {
    field: 1
};
var bar = {
    field: 2
};

  • Multiple var offer higher aesthetics for comments on top of variables without even thinking about it:
// some comment about foo
var foo = true,
    // some come comment about bar
    bar = false;

or (better, but haven't seen this one so often)

// some comment about foo
var foo = true,
// some come comment about bar
    bar = false;
vs.
// some comment about foo
var foo = true;
// some come comment about bar
var bar = false;

Compared to the first multi var version this gives more space for comments and does not look odd or like the one comment is more important than the other one.
Also, the first single var version would look quite horrible in a diff when removing the first variable declaration.

Most of this has also been covered in a more extensive article by Ben Alman.

I am not saying we should change all var statements now, but add this to the coding guidelines and have an eye for it during code reviews. Currently the convention does even discourage from using multiple var, I highly disagree with this.

Turn off "one-var" ESLint rule for es5

2
Summary by Krinkle

Done.

Krinkle (talkcontribs)
Jdforrester (WMF) (talkcontribs)
Summary by Krinkle

Single quotes indeed.

Amire80 (talkcontribs)

What is the convention for quotes - double or single?

It has little meaning, but would be nice for consistency.

jQuery suggests double.

He7d3r (talkcontribs)

If I remember correctly it is single.

Krinkle (talkcontribs)

Single quotes indeed.

For JavaScript files quotation is purely stylistic as string literals in JavaScript are specified as either wrapped in double quotes or single quotes. There is no magic functionality or different escaping (e.g. no need for double quotes to use \n or something).

In PHP we use both because of magic quotes, though in PHP we also prefer single quotes when magic functionality is not intended.

Whitespace conventions for control structures should match those for PHP

4
Danwe (talkcontribs)

To my surprise, I just got a hint from a fellow developer that I am not doing whitespaces as stated by the conventions. e.g. I am writing if(...) {} rather than if (...) {}. This has been legal before Revision 547172 as of 13:45, 6 June 2012. That revision has introduced the line Keywords that are followed by ( should be separated by one space. I think the given justification (This helps distinguish between keywords and function invocations) is not really a good one. First, you should really know your JS keywords, second, function calls are not followed by any "{" but by ";", so this should make it clear as well.

Since the not so spacey convention is (for a long time already) explicitly allowed by the PHP coding conventions (which were the general conventions not so long ago), I would like to demand bringing these on the same level again, allowing the not so spacey style in JS as well. As the PHP conventions put it, "Opinions differ" on this, and yes, that's no different when it comes to JavaScript. Please let me know if there are any objections, otherwise I would just adjust this soonish.

In general, I would appreciate if coding conventions could be discussed a little more, or perhaps someone could just point me to where they are being discussed... Thanks.

Krinkle (talkcontribs)
  • Some operators require a space by syntax, others tolerate both (in the case of if, both are allowed). In that case it comes down to convention and consistency.
  • Our JS code base is relatively new, therefore it doesn't make sense to start off with allowing arbitrary variations that are inconsistent in nature. Incorporating exceptions in the code conventions (the convention to separate all keywords by a space) might make sense for an established code base, but not for a new one.
  • What do you mean by "know your keywords"? Are you suggesting someone might not know what is an operator and what a function? Though I think one should know what is and isn't an operator, that's easily looked up if you don't know, and then there is editors that do the highlighting for you, and of course the whitespace convention to make it easy to detect. And to be fair, it is a reasonable expectation to know the basic operators in a language before writing in it, of course (if, else, function, throw, ..).

As for discussion place, I think this talk page is as good as any.

Provided the following use cases for enforcing the "space after if":

  • Consistency in style (sometimes a space and sometimes not is inconsistent)
  • Consistency in syntax (some operators require it, some operators don't)
  • Simplicity in distinguishing operators/keywords from function invocations.

Can you respond with a few use cases for leaving it "differed" ?

Danwe (talkcontribs)

One could simply turn around your use cases if one would say that we are talking about spaces before parentheses rather than spaces after keywords, and perhaps that's actually the logical ting to do:

  1. Consistency in style (sometimes a space before parentheses and sometimes not is inconsistent, e.g. someFunc() is very inconsistent with if (...) {...}).
  2. Consistency in syntax (some operators require it, some operators don't) - Sorry, but I don't understand. I am not sure whether all keywords are operators, but not all operators are keywords. So we are not consistent here at all right now. E.g. ++i; rather than ++ i; or !someVar instead of ! someVar.
  3. Simplicity in distinguishing operators/keywords from function invocations. So, that one is a contradiction to #1 now. But it's also not needed since you still have several indicators making clear whether the thing, preceding parentheses, is a keyword or a function-call:
  • Your IDEs syntax highlighting (come on, probably all serious IDEs have this, even most modern debuggers do)
  • Your knowledge (and yes, you understood me right when I said "know your keywords" :)
  • Keywords followed by parantheses "( )" (control structures) will always be followed by braces "{ }" as well (per our own conventions where we don't allow Braceless control structures).
  • Function calls should be followed by ";", control structures don't require this. The only exception here would be something like var foo = function() {};. This function example is actually also the only case where a keyword followed by parantheses, followed by braces, doesn't necessarily have subsequent, indented line.

I guess there could evolve a huge discussion around this and I am sure you are convinced that the current style is the one to go, I am convinced that this is the style which makes a lot more sense. For me the current style is just as inconsistent as the one I'd suggest is for you. There must have been a similar discussion when people decided over spacey or not so spacey style in PHP and perhaps we should learn from history and be consistent with PHP - it still is MW code and both languages have C-like syntax, so why not leave this rule to the common coding conventions. I believe the more rules are described on a common basis, the better. It makes things simpler and lets you concentrate on the essential things like actually writing code and getting stuff done rather than studying conventions for different syntactically very similar languages.

Krinkle (talkcontribs)

The only argument I'm willing to push is consistency for exact usage and the reason thereof:

  • Not if() in one module and if () in another.
  • We don't need to tolerate both variations, because unlike the PHP code base, the JS code base is relatively new and we can easily enforce one variation and stick to it.

The other arguments such as consistency among different operators are harder to document because, as you say, ++ and ! are also operators. So let's not argue that now. If you want you could specify it as follows:
"A keyword followed by ( (left parenthesis) should be separated by a space."

Which is effectively the same as what I meant with "Consistency in syntax (some operators require it, some operators don't)" because operators that don't require it are followed by a left parenthesis, and the ones that don't require a space by syntax.

Regarding "being consistent with PHP", we aren't inconsistent with PHP because our PHP code conventions don't enforce if(), it currently tolerates both. See my first point above.

Regarding "more common code conventions", I oppose this because of two reasons:

  • It means we have to read two pages for a language's conventions instead of one.
  • It encourages (and has led to) bad coding habits. Different languages have different syntaxes and internals, despite looking similar. Applying code structure conventions decoupled from the language is dangerous and harder to maintain and use. I've been working on phasing out those "common code conventions". For example, another PHP/JS project I work on preferred placing the curly place on the next line. This is fine in PHP where the distinction is purely stylistic, but in JS there are various cases where this changes the behaviour of the program (usually due to Automatic Semicolon Insertion).

I agree that "More rules" (that is, in order to removing ambiguity) is better. But more variations ("switch cases intended or not intended, if with or without space") or more edge cases ("foo like bar, except when bar is quuxified or when quuxification happens within baz") is not a good thing and serves no purpose what's however other than reducing consistency and asking for problems and conflict resolution. We stick with one and you get used to it.

    Place for Extensions Objects in JavaScript

    3
    Danwe (talkcontribs)

    In many cases, if MW extensions bring some JavaScript, they create a module like object in the global scope which will hold all the extensions JS stuff, e.g. in further objects in fields of that extension object.

    e.g. window.dataValues, window.dataValues.DataValue, etc.

    I think there is common agreement that it is bad for extensions to pollute the global scope with those objects. Some have suggested putting those objects into the global mediaWiki object instead. This would result in mediaWiki.dataValues and mediaWiki.dataValues.DataValue etc.

    I think this is equally bad. Take an extension "messages" for example. Putting it in mediaWiki.messages would overwrite the "messages" field introduced by MW core. You are simply moving the problem from global scope into the MW object and making it worse since the MW object is occupied by some stuff already. I think there should be some coding convention where to put those objects instead. In my opinion, a mw.ext would be the way to go. This would result in a mediaWiki.ext.dataValues and mediaWiki.ext.dataValues.DataValue. The slightly longer name doesn't really make it worse, and if you use it a lot in one file, you would still alias it via the files outer closure e.g.

    ( function( mw, dv, $, undefined ) {
    	'use strict';
    	// ... now you can use dv.DataValue here.
    }( mediaWiki, mediaWiki.ext.dataValues, jQuery ) );
    
    Krinkle (talkcontribs)

    Per the conventions, no globals other than mw and jQuery should be used.

    I agree adding third-party libraries on one of these globals directly is probably bad.

    Extensions providing third-party libraries could be aliased under mw.libs if we don't want to use them directly.

    As for the structure, I'd recommend this:

    -- Provider
    ( function () {
        'use strict';
    
        /* local scope */
    
        /* public API */
        var dv = {
    
        };
    
        // Expose
        mw.dataValues = dv;
    }() );
    
    -- Consumer
    ( function () {
        'use strict';
        var dv = mw.dataValues;
    
    }() );
    
    Danwe (talkcontribs)

    I agree, mw.libs should be separate. I guess we should offer a mw.ext then, so all extensions can start using that one rather than putting stuff in other places. I might upload a patch set soon, shouldn't be a big thing really.

    JGonera (WMF) (talkcontribs)

    I noticed something in the mobile extension code and discussed it with Jon (although I'm not sure if I convinced him).

    In the existing code a DOM element from a jQuery object is often passed instead of simply passing the jQuery object, e.g.:

    setupReferences( $( '#content' )[ 0 ] );

    Jon said that he wants to be sure that only one element is passed. I think it just makes the code look more confusing and doesn't really solve the problem. I mean we should simply assume that the selector selects only one element. If it selects more than one element it's obviously a mistake and we actually can't say if [0] is the right one or maybe [1] or [54]. If we really want to be sure we can check .length at the beginning of the invoked function.

    Anyone disagrees? Could we include that somewhere in the coding conventions?

    Krinkle (talkcontribs)

    ID selectors are incapable of returning more than one element. So if you're referring specifically to code using [0] on an object from an ID selector, that doesn't deserve mention in code conventions as it is imho just plain stupid. Something you might be confused by once, but if we start including things like that there is no telling where it will end end. Code conventions isn't a training guide to how the browser works and how the DOM works.

    For passing around 1 element, you can use .eq( 0 ) instead of [0] to yield a cloned jQuery object that only contains the first node of the jQuery object collection.

    Regarding passing HTMLElement objects or jQuery collections between functions, it depends on what makes sense for the situation. I don't think this is the kind of thing where there is 1 right solution or where using different approaches in different modules makes code harder to use.

    jQuery plugins obviously operate on a jQuery collection as they are methods on the jQuery prototype itself. Utilities often operate on 1 single element. In the latter case it is imho preferred to document the method as taking an HTMLElement as opposed to a "jQuery object that must have only 1 element". That way the responsibility of extracting the right data is in the hands of the caller. So that you have Foo.bar( $foo[0] ) instead of Foo.bar( $foo ). That way Foo.bar doesn't have to verify it has length, take the first one, and maybe throw an error if it has more than one?

    Also note that constructing a jQuery object for a node is very cheap. It is the first executive use case in the constructor. Nothing to worry about there. Things like $( this ) in DOM callbacks are very common and very fast.

    So something like this would be fine:

    /** @param {HTMLElement} el */
    Foo.getState = function (el) {
      return $(el).data('foo-state');
    };
    

    However if utilities are more efficient when they can cache data outside a loop, then you may want to change the function to take an array-like object instead of requiring the callee to make a loop where Foo.bar then has to do initialisation for every single call inside the loop.

    So, instead of this:

    /** @param {HTMLElement} el */
    Foo.setStuff = function (el, stuff) {
      var state = Foo.getState(el) || {};
      state.stuff = Foo.normaliseStuff(stuff);
      return $(el).data('foo-state', state);
    };
    
    // One
    Foo.setStuff($('#foo-container')[0], /* .. */);
    
    // Many
    $('.foo-items').each(function () {
        Foo.setStuff(this, /* .. */);
    });
    

    The following would likely be better (so that stuff is only normalised once):

    /** @param {Array|jQuery} els */
    Foo.setStuff = function (els, stuff) {
      var state, i, len;
      stuff = Foo.normaliseStuff(stuff);
      for (i = 0, len = els.length; i < len; i++) {
        state = Foo.getState(el) || {};
        state.stuff = stuff;
        $(els[i]).data('foo-state', state);
      }
    };
    
    // One
    Foo.setStuff($('#foo-container'), /* .. */);
    // Many
    Foo.setStuff($('.foo-items'), /* .. */);
    

    This sort of a bad example since jQuery methods support setting on the entire collection at once, so this is probably better done like $('.foo-items').data('foo-state-stuff, /* .. */); but you get the idea. Also, you could extend Foo.setStuff with els = els.nodeType ? [ els ] : els; so that it supports both:

    /** @param {Array|jQuery|HTMLElement} els */
    Foo.setStuff = function (els, stuff) {
      var state, i, len;
      els = els.nodeType ? [ els ] : els;
      stuff = Foo.normaliseStuff(stuff);
      for (i = 0, len = els.length; i < len; i++) {
        state = Foo.getState(el) || {};
        state.stuff = stuff;
        $(els[i]).data('foo-state', state);
      }
    };
    
    // Alternative:
    
    /** @param {Array|jQuery|HTMLElement} els */
    Foo.setStuff = function (els, stuff) {
      $(els).data('foo-state-stuff', stuff);
    };
    
    // Alternative (2):
    
    /** @param {jQuery} $els */
    Foo.setStuff = function ($els, stuff) {
      $els.data('foo-state-stuff', stuff);
    };
    
    
    // One (node)
    Foo.setStuff($('#foo-container')[0], /* .. */);
    Foo.setStuff(document.getElementById('foo-container'), /* .. */);
    // One
    Foo.setStuff($('#foo-container'), /* .. */);
    // Many
    Foo.setStuff($('.foo-items'), /* .. */);
    
    Wikinaut (talkcontribs)

    @Krinkle

    (I suggest that) perhaps a shortened version of your reply should go to the article page.

    JGonera (WMF) (talkcontribs)

    Thanks for the explanation. Maybe I sounded stupid bringing up a problem like that. It just bothered me to see $(something)[0] all around the code and I needed some arguments to convince Jon to stop doing that :)

    Rillke (talkcontribs)

    Where MediaWiki devs agreed about this, I would like to be pointed to. It was inserted by Krinkle. Yoda is awesome. Yoda is readable. Save is Yoda, also. -- Rillke (talk) 14:22, 10 July 2012 (UTC)

    Krinkle (talkcontribs)

    The Wikipedia article points out that the main cricism against this is cognitive load and counter-intuition when developing, reviewing, testing, and debugging code. These remain relevant today and why we don't use this style.

    The points in favour of Yoda are, to my knowledge, limited to points about detecting mistakes in new code. They do not apply to how one would intentionally write code, or how any code is read or debugged later. That is where the majority of time is spent, and thus where the priority sits in my opinion.

    In addition to that, the points about preventing mistakes in newly written code, refer either to unrelated program languages, or to issues that no longer apply to present-day compilers and linters, which all prevent, detect, or warn against such mistakes.

    Summary by Krinkle

    ESLint provides an automated "fix" command for this.

    Rillke (talkcontribs)

    Please provide a script like jsBeautifier for automatically applying these white space conventions.