Manual:Coding conventions/JavaScript
Contents |
This page describes the coding conventions for JavaScript files of the MediaWiki codebase. See also the general conventions.
Linting [edit]
We use JSHint for code quality through static analysis. The settings for MediaWiki core can be found in .jshintrc. Please use an editor that supports inline linting in your editor (see platform support for JSHint).
You are recommended to have nodejs (with npm). Install node-jshint from the command line for automatic linting. node-jshint automatically discovers the closest .jshintrc file and uses it. Or http://jshint.com/ (may produce different results due to absence of jshintrc settings)
The .jshintrc file is organised alphabetically and grouped as follows:
{ /* Common */ "common1": true, "common2": true, /* Local */ "localOnly1": true, "localOnly1": false, "predef": [ "globalA", "globalB" ] }
Projects can add additional enforcing options or relaxing options. However the following common options must be 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, // Legacy "onevar": true }
To avoid confusion between an enforcing option and a relaxing option projects are encouraged to group them by category (see http://jshint.com/docs/ for an overview). For example sub:true relaxes by allowing foo['prop'] in addition to foo.prop, whereas trailing:true is a restriction to not allow trailing whitespace.
Look at the JSHint configuration for MediaWiki core or that of VisualEditor for good examples.
Whitespace [edit]
- 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. - There should be no space between the function name and left parenthesis of the argument list. This gives visual distinction between keywords and function invocations.
- Don't use operators as if they are functions (such as
delete,void,typeof,new,return, ..).
| Yes | No |
|---|---|
a.foo = bar + baz; if ( foo ) { foo.bar = doBar(); } function foo() { return bar; } foo = function () { return 'bar'; }; foo = typeof bar; |
a.foo=bar+baz; if( foo ){ foo.bar = doBar () ; } function foo () { return bar; }; foo = function() { return('bar'); }; foo = typeof( bar ); |
Closure [edit]
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. JavaScript closures are explained here.
Declarations [edit]
All variables must be declared before used. JavaScript does not require this, but doing so makes the code easier to read and prevents common mistakes (such as implied globals).
The var statement should be the first statement in the function body.
All functions should be declared before they are used. Inner functions should follow the var statement.
Indentation [edit]
Indent blocks from their containing blocks with one tab (usually represented with 4 spaces).
Line Length [edit]
Lines should wrap at no more than 80-100 characters. When a statement does not fit on a single line, it may be necessary to span the statement over multiple lines. Place the line break after an operator (ideally after a comma). A break after an operator decreases the likelihood that a copy-paste error will be masked by semicolon insertion. The next line should be indented an extra level.
// The following code is invoked immediately. ( function ( mw, $ ) { // The line break is after &&. if ( mw.foo.hasBar() && // The continuation of the same statement is indented an extra level. !mw.foo.isQuuxified() ) { return; } }( mediaWiki, jQuery ) );
Comments [edit]
Please 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. Since MediaWiki 1.17 and higher 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]
- Strict equality checks (
===) should be used in favor of loose (==) wherever possible. - No Yoda conditionals[2]
Type checks [edit]
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"
- Local variables:
Quotes [edit]
Single quotes are preferred over double quotes for string literals.
Globals [edit]
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, MediaWiki exposes lots of wg-prefixed variables in the global scope (that is, if $wgLegacyJavaScriptGlobals is enabled). These are deprecated and will be removed at some point (bug 33837), so don't rely on this but use mw.config instead.
Naming [edit]
All variables and functions must use lowerCamelCase for their naming. For functions, verb phrases are preferred (so getFoo() instead of foo()).
The only exception to this are constructor functions used with the new operator. These names must start with a capital 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 the only defense we have.
Names with acronyms in them should treat the acronym as a normal word and only uppercase the first letter. For example ".getHtmlSource()" as opposed to ".getHTMLSource()".
jQuery [edit]
- See also jQuery
To avoid confusion with raw elements or other variables, we prefix variables storing an instance of jQuery with a dollar sign, e.g. $foo = $( '#bar' ), to make them easy to recognize. This matters because the DOM API (e.g. foo = document.getElementById( 'bar' )) returns null if no element matched the query, therefore (since null casts to boolean false) one would test the plain variable, in the example if ( foo ). jQuery objects on the other hand (like any object in JavaScript) cast to boolean true, so if you confuse a jQuery object with the return value of a DOM method, a conditional could fail badly. In the example one would use if ( $foo.length ) instead.
Creating elements [edit]
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):
tag = randomTagName(); // will return 'span', 'div' etc. $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).
Arrays vs. Objects vs jQuery [edit]
There is a lot of confusion around these types of collections. This confusion is mostly caused by the fact that Objects in javascript look a lot like associative arrays, but actual arrays in javascript do not use associative indexing. We use the following conventions:
| Convention | Arrays | Plain Objects | jQuery Objects |
|---|---|---|---|
| Declaration and empty initialization |
x = []; | x = {}; | $x = $([]); |
| Access value | x[0]; | x.key; | var element = $x.get( 0 );or var $element = $s.eq( 0 ); |
| Size | x.length; |
count = 0; for ( key in x ) { count++; } |
$x.length; |
| Iteration | for ( i = 0; i < x.length; i++ ) {} or: $.each( x, function ( i, value ) {} ); |
for ( key in x ) {} or: $.each( object, function ( key, value ) {} ); |
$x.each( function( i, element ) {} ); |
- Be careful when using a
for-inloop to iterate over an array (as opposed to a plain object). Afor-inwill iterate over the keys instead of over the indices (keys are strings, order not guaranteed, index can have gaps etc.).
Pitfalls [edit]
- Bracket notation on a string (
'foobar'[2]) doesn't work in older versions of IE. Use'foobar'.charAt(2)instead. - Be careful to preserve compatibility with left-to-right and right-to-left languages (ie.
float: rightortext-align: left), especially when styling text containers. This is another reason why such declarations should be in CSS files, so that they are automagically flipped by CSSJanus in ResourceLoader for RTL-languages. - Use
attr()andprop()appropriately. Read more at http://javascript.info/tutorial/attributes-and-custom-properties - Always 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 because it makes code harder to follow and it is unstable due to mixing of jQuery methods with element attributes (e.g. a plugin called "title" might convert an element into a heading, which means the title attribute can no longer be set through this method). Be explicit and call.attr(),.prop(),.on()etc. directly.
Documentation [edit]
Wikimedia projects have begun using JSDuck to build documentation from JavaScript comments. The documentation comment structure for JavaScript is broadly similar to the doxygen format we use for PHP comments but many details differ to accomodate for certain javascript specific language constructs (such as object inheritance, emitting events and arbitrary augmentation of the prototype of an existing class).
- Installation
- Requires Ruby and the Ruby
gempackage manager)
Exec:
gem install jsduck
See also the JSDuck readme.
- Generate documentation
- jsduck --config=path/to/jsduck-json-file
- Set up configuration for new projects
- Create a JSDuck configuration file (in
/jsduck.jsonor/jsduck/config.jsondepending on whether you have more JSDuck files (such ascategories.json,MetaTags.rb,externals.jsetc.). See for example, VisualEditor's jsduck configuration file.
Usage [edit]
Text in free-form blocks (e.g. description of methods, parameters, return values etc.) should be sentence case. If inside a tag when such a description wraps to a newline, indent with one extra space. Separate types with a bar ({string|Object}):
/**
* A long description of a method that span multiple
* lines.
*
* Or perhaps even an extra paragraph.
*
* @method
* @param {string} foo Some long description of a parameter that
* requires you to overflow onto the next line.
* @param {number} bar
Final notes [edit]
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.prototypeas it's considered harmful. - Both MediaWiki and jQuery do not support environments that have manipulated the global
undefinedvariable 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]
| Conventions | |
|---|---|
| General | All languages · Security for developers · Pre-commit checklist · Style guide (draft) |
| PHP | Code conventions · PHPUnit test conventions · Security checklist for developers |
| JavaScript | Code conventions · Learning JavaScript |
| CSS | Code conventions |
| Database | Code conventions |