Manual:Coding conventions/JavaScript

From MediaWiki.org
Jump to: navigation, search
shortcut: CC/JS

This page describes the coding conventions for JavaScript files of the MediaWiki codebase. See also the general conventions.

Linting[edit | edit source]

We use JSHint as our code quality tool. The settings for MediaWiki core can be found in .jshintrc. Please use an editor that supports inline linting (see platform support for JSHint).

You are recommended to install jshint (via npm, requires nodejs) for usage the command line.

JSHint automatically uses the closest .jshintrc and .jshintignore files, relative to the individual javascript file being read (from the current directory or one of its parents). This allows running jshint on a subset of files.

As MediaWiki core's .jshintignore file includes "extensions/", if you have your extension repository set up as a subdirectory of MediaWiki core, and the extension does not have its own .jshintignore file (regardless of .jshintrc), then running jshint (even when specifically run from inside the extension directory) may skip all files in the extension and thus hide any errors. Consider creating a .jshintignore file for your extension, even if it would only contain a comment referencing this issue.

The .jshintrc file is organised alphabetically and sectioned as follows:

{
    /* Common */
 
    "common1": true,
    "common2": true,
 
    /* Local */
 
    "localOnly1": true,
    "localOnly1": false,
 
    "predef": [
        "globalA",
        "globalB"
    ]
}

Projects can add additional enforcing or relaxing options in the local section. The following common options are used in all projects:

{
    /* Common */
 
    // Enforcing
    "camelcase": true,
    "curly": true,
    "eqeqeq": true,
    "immed": true,
    "latedef": true,
    "newcap": true,
    "noarg": true,
    "noempty": true,
    "nonew": true,
    "quotmark": "single",
    "trailing": true,
    "undef": true,
    "unused": true
}

To avoid confusion between an enforcing and a relaxing option, group them by category (see http://jshint.com/docs/options/ for an overview). For example loopfunc:true is a relaxing option to allow functions in a loop, whereas undef:true is a restriction to disallow undefined variables.

Look at the JSHint configuration for MediaWiki core or that of VisualEditor for examples. Your extension should have its own .jshintrc file to avoid relying on whatever the closest jshintrc file is in the local installation (because if that other file changes, it would instantly invalidate your code, thus causing inability to merge new changes until it's fixed).

Whitespace[edit | edit source]

  • Blank lines separate one block of logically related code from another.
  • One space on both sides of binary operators and assignment operators.
  • Keywords followed by a "(" (left parenthesis) must be separated by one space. This gives visual distinction between keywords and function invocations.
  • There should be no space between the function name and left parenthesis of the argument list.
  • When a function has parameters, there should be a space on the inside side of both parentheses, for both function definitions and function calls.
  • Don't use operators as if they are functions (such as delete, void, typeof, new, return, ..).
Whitespace examples
Yes No
a.foo = bar + baz;
 
if ( foo ) {
    foo.bar = doBar();
}
 
function foo() {
    return bar;
}
 
foo = function () {
    return 'bar';
};
 
foo = typeof bar;
 
function baz( foo, bar ) {
    return 'gaz';
}
 
baz( 'banana', 'pear' );
a.foo=bar+baz;
 
if( foo ){
    foo.bar = doBar () ;
}
 
function foo () {
    return bar;
};
 
foo = function() {
    return('bar');
};
 
foo = typeof( bar );
 
function baz(foo, bar) {
    return 'gaz';
}
 
baz('banana', 'pear');

Closure[edit | edit source]

Avoid leakage of variables from or to other modules by wrapping files in a closure. Use the following closure (aka "Immediately-Invoked Function Expression"[1], or iffy):

( function ( mw, $ ) {
        // Code here will be invoked immediately
}( mediaWiki, jQuery ) );

Files that don't reference MediaWiki modules (e.g. a jQuery plugin) should omit the mediaWiki and mw arguments from the closure.

In terms of parentheses, both enclosing either the entire expression or just the function expression works (see Ben Alman's blog post). For consistency use the above format (enclosing the entire expression).

Declarations[edit | edit source]

Variables must be declared before use. JavaScript will hoist them at run-time if you don't. Specifying them this way makes code easier to read by reflecting what happens at run-time. It helps prevents common mistakes such as implied globals. Variables should be declared in rough order of being used, with unassigned (valueless) variables before assigned ones.

Functions should be declared before use. Local functions should follow the var statement.

Indentation[edit | edit source]

Indent blocks from their containing blocks with one tab. Often, editors display this as the same width as 4 spaces, but the display configuration is up to the developer.

Line length[edit | edit source]

Lines should wrap at no more than 80–100 characters. If a statement does not fit on a line, split the statement over multiple lines. If possible, place the line break after an operator (e.g. a comma or logical operator). The next line should be indented an extra level.

Function calls and objects should either be on a single line or split over multiple lines with 1 line for each segment. Avoid mixing the number of segments per line or ending a call or object in the middle of an indentation level.

Line breaking examples
Yes No
// One line
if ( mw.foo.hasBar() && mw.foo.getThis() === 'that' ) {
    return { first: 'Who', second: 'What' };
} else {
    mw.foo( 'first', 'second' );
}
 
// Multi-line (one component per line)
if ( mw.foo.hasBar() &&
    mw.foo.getThis() === 'that' &&
    !mw.foo.getThatFrom('this')
) {
    // Line break after "&&".
    // Continuation of condition indented one level.
    // Condition closed at original indentation.
    return {
        first: 'Who',
        second: 'What',
        third: 'I don\'t know'
    };
}  else {
    mw.foo(
        'first',
        'second'
    );
}
if ( mw.foo.hasBar() && mw.foo.getThis() === 'that' &&
    !mw.foo.getThatFrom('this') ) {
    // No varying number of segments per line.
    // Closing parenthesis should be on the left.
    return { first: 'Who', second: 'What',
        third: 'I don\'t know'
    };
} else {
    mw.foo( 'first', 'second',
        'third' );
 
    // Avoid statements looking like they are broken
    // up but still together (looks like a call with
    // one parameter). 
    mw.foo(
        'first', 'second', 'third'
    );
 
    mw.foo(
        ['first', 'array', 'parameter'], 'second'
    );
}

Comments[edit | edit source]

Be generous with comments in your code, as they help future readers of the code (possibly yourself). In older versions of MediaWiki, JavaScript code was often very poorly commented to keep it small. Nowadays modules are automatically minified by ResourceLoader.

Generally use (single or multi-line) // line comments.

Save /* block comments */ for documentation blocks and for commenting out code.

Equality[edit | edit source]

  • Strict equality checks (===) should be used in favour of loose (==) wherever possible.
  • No Yoda conditionals[2]

Type checks[edit | edit source]

The below is the result of a long history of browser compatibility, edge cases, performance and other research. Don't take it lightly. There is a reason behind every single detail.

  • string: typeof x === 'string'
  • number: typeof x === 'number'
  • boolean: typeof x === 'boolean'
  • null: x === null
  • object: x === Object(x)
  • Plain Object: jQuery.isPlainObject( x )
  • Function: jQuery.isFunction( x )
  • Array: jQuery.isArray( x )
  • HTMLElement: !!x.nodeType
  • undefined:
    • Local variables: x === undefined
    • Properties: x.prop === undefined
    • Global variables: typeof x === 'undefined'

Quotes[edit | edit source]

Single quotes are preferred over double quotes for string literals.

Globals[edit | edit source]

Only mediaWiki and jQuery should be used (in addition to the browser's API).

Any and all code should be written as an extension to either of these. General purpose (not MediaWiki-specific) modules that manipulate DOM elements should be written as a jQuery plugin. And MediaWiki core or extension modules should extend the global mediaWiki object.

If code is made available standalone and/or provided by a third party that exposes itself as a global, then (for use in MediaWiki context) alias or instantiate it into a property of mediaWiki.libs and use it from there.

For backward compatibility, lots of wg-prefixed variables are exposed in the global scope (if $wgLegacyJavaScriptGlobals is enabled, see bug 33837). Don't rely on these and use mw.config instead.

Naming[edit | edit source]

All variables and functions must use lowerCamelCase for their naming. For functions, verb phrases are preferred (so getFoo() instead of foo()).

The only exception are constructors used with the new operator. Those names must start with an uppercase letter (UpperCamelCase). JavaScript has no dedicated syntax for classes or constructors, they are declared as any other function. As such there is no compile-time or run-time warning for instantiating a regular function or omitting the new operator on a constructor. This naming convention is our only defence.

Names with acronyms in them should treat the acronym as a normal word and only uppercase the first letter. For example ".getHtmlApiSource()" as opposed to ".getHTMLAPISource()".

jQuery[edit | edit source]

See also jQuery

To avoid confusion with raw elements and other variables, prefix variables storing an instance of jQuery with a dollar sign ( e.g. $foo = $( '#bar' )). This matters because the DOM (e.g. foo = document.getElementById( 'bar' )) returns null if no elements were found, therefore (since null casts to boolean false) one would test the plain variable like if ( foo ). jQuery objects on the other hand (like any array or object in JavaScript) cast to boolean true. If you confuse a jQuery object with the return value of a DOM method, a condition could fail badly. In such case one would use if ( $foo.length ) instead.

Creating elements[edit | edit source]

To create a plain element, use the simple <tag> syntax in the jQuery constructor:

$hello = $( '<div>' )
    .text( 'Hello' );

When creating elements based on the tag name from a variable (which may contain arbitrary html):

// Fetch 'span' or 'div' etc.
tag = randomTagName();
$who = $( document.createElement( tag ) );

Only use $('<a title="valid html" href="#syntax">like this</a>'); when you need to parse HTML (as opposed to creating a plain element).

Collections[edit | edit source]

Different types of collections sometimes look similar but have different behaviour and should be treated as such. This confusion is mostly caused by the fact that arrays in javascript look a lot like arrays in other languages, but are in fact just an extension of Object. We use the following conventions:

Avoid using a for-in loop to iterate over an array (as opposed to a plain object). A for-in will iterate over the keys instead of over the indices:

  • keys are strings
  • order not guaranteed
  • index can have gaps
  • might include non-numerical properties

Pitfalls[edit | edit source]

  • Be careful to preserve compatibility with left-to-right and right-to-left languages (i.e. float: right or text-align: left), especially when styling text containers. Putting those declarations in CSS file will allow them to be automagically flipped for RTL-languages by ResourceLoader.
  • Use attr() and prop() appropriately.
    Read more:
  • Consistently quote attribute selector values: [foo="bar"] instead of [foo=bar] (jqbug 8229).
  • As of jQuery 1.4 the jQuery constructor has a new feature that allows passing an object as second argument, like jQuery( '<div>', { foo: 'bar', click: function () {}, css: { .. } } );. Don't use this. It makes code harder to follow, fails on attributes (such as 'size') that are also methods, and is unstable due to this mixing of jQuery methods with element attributes. A future jQuery method or plugin or called "title" might convert an element into a heading, which means the title attribute can also no longer be set through this method. Be explicit and call .attr(), .prop(), .on() etc. directly.

Documentation[edit | edit source]

Use JSDuck to build documentation (see https://doc.wikimedia.org/). The documentation comment structure is broadly similar to the doxygen format we use in PHP but details differ to accommodate for javascript-specific language constructs (such as object inheritance, emitting events and arbitrary augmentation of an constructor's prototype).

Installation
Standard RubyGems install: gem install jsduck
See the installation guide for more information.
Generate documentation
jsduck --config=path/to/jsduck-json-file
Set up configuration for new projects
Create a JSDuck configuration file (in /jsduck.json or /jsduck/config.json depending on whether you have more JSDuck files (such as categories.json, MetaTags.rb, externals.js etc.). See for example, MediaWiki's jsduck configuration file and VisualEditor's jsduck configuration file.

Documentation comments[edit | edit source]

  • Text in free-form blocks (e.g. description of methods, parameters, return values etc.) should be sentence case.
  • End sentences in a full stop.
  • Continue sentences belonging to an annotation on the next line, indented with one additional space.
  • Value types should be separated by a pipe character. Use only types that are listed in the Types section or the identifier of a different class as specified in your project (e.g. {mw.Title}).
/**
 * Get the user name.
 *
 * Elaborate in an extra paragraph after the first one-line summary in
 * the imperative mood.
 *
 * @param {string} foo Description of a parameter that spans over on the
 *  next line of the comment.
 * @param {number} bar
 * @return {string} User name
 */

Tags[edit | edit source]

We use the following annotations. They should be used in the order as they are described here, for consistency. See JSDuck/Tags for more documentation about how these work.

  • @abstract
  • @private
  • @static
  • @class Name (name is optional, engine will guess name from context)
  • @singleton
  • @extends ClassName
  • @mixins ClassName
  • @constructor
  • @inheritable
  • @method name (name is optional, guessed)
  • @property name (name is optional, guessed)
  • @param {Type} name Optional text.
  • @return {Type} Optional text.
  • @chainable
  • @throws {Type}

Types[edit | edit source]

Special values:

  • undefined
  • null
  • this

Primitive types:

  • boolean
  • number
  • string

Built-in classes:

  • Array
  • Date
  • Function
  • RegExp
  • Object

Browser classes:

  • HTMLElement

Final notes[edit | edit source]

Use CSS for styling many elements
Don't apply styling to lots of elements at once; this has poor performance. Instead use a common parent's class (or add one) and apply CSS in a .css file. Thanks to ResourceLoader, this will all be loaded in the same HTTP request, so there's no performance penalty for having a separate CSS file. Do not set CSS into inline "style" attributes, don't insert "style" elements from JS either.

Environment
There are a few things that MediaWiki specifically (or inherently due to use of jQuery), does not support:

  • jQuery doesn't support environments that have manipulated the Object.prototype as it's considered harmful.
  • Both MediaWiki and jQuery do not support environments that have manipulated the global undefined variable as it's considered harmful. As of ECMAScript5 this is no longer an issue since it is made read-only (as it should be), but in older browsers this can cause issues.

References[edit | edit source]


  1. http://benalman.com/news/2010/11/immediately-invoked-function-expression/
  2. yodaconditional.jpg
Conventions
General All languages · Security for developers · Pre-commit checklist · Style guide (draft) · Accessibility guide for developers (draft)
PHP Code conventions · PHPUnit test conventions · Security checklist for developers
JavaScript Code conventions · Learning JavaScript
CSS Code conventions
Database Code conventions
Python Code conventions
Selenium/Cucumber Code conventions