GNOME JavaScript Style Guide

Coding style

Our goal is to have all JavaScript code in GNOME follow a consistent style. In a dynamic language like JavaScript, it is essential to be rigorous about style (and unit tests), or you rapidly end up with a spaghetti-code mess.

Semicolons

JavaScript allows omitting semicolons at the end of lines, but don't. Always end statements with a semicolon.

Indentation and Whitespace

  • Use four-space indents.
  • No trailing whitespace.
  • No tabs.
  • Braces are on the same line as their associated statements.
  • You should only omit braces if *both* sides of the statement are on one line.
  • One space after the function keyword.

  • No space between the function name in a declaration or a call.
  • One space before the parens in the if statements, or while, or for loops.

    function foo(a, b) {
        let bar;

        if (a > b)
            bar = do_thing(a);
        else
            bar = do_thing(b);

        if (var == 5) {
            for (let i = 0; i < 10; i++) {
                print(i);
            }
        } else {
            print(20);
        }
    }

File naming and Creation

For JavaScript files, use lowerCamelCase-style names, with a .js extension.

Only use C where gjs/gobject-introspection is not available for the task, or where C would be cleaner.

Imports

Use UpperCamelCase when importing modules to distinguish them from ordinary variables, e.g.

    const GLib = imports.gi.GLib;

Imports should be categorized into one of two places. The top-most import block should contain only "environment imports". These are either modules from gobject-introspection or modules added by gjs itself.

The second block of imports should contain only "application imports".

Each import block should be sorted alphabetically. Don't import modules you don't use.

    const GLib = imports.gi.GLib;
    const Gio = imports.gi.Gio;
    const Lang = imports.lang;
    const St = imports.gi.St;

    const Main = imports.ui.main;
    const Params = imports.misc.params;
    const Tweener = imports.ui.tweener;
    const Util = imports.misc.util;

The alphabetical ordering should be done independently of the location of the location. Never reference imports in actual code.

Constants

We use CONSTANTS_CASE to define constants. All constants should be directly under the imports:

    const MY_DBUS_INTERFACE = 'org.my.Interface';

Variable Declaration

Always use either const or let when defining a variable.

    // Iterating over an array
    for (let i = 0; i < arr.length; ++i) {
        let item = arr[i];
    }

    // Iterating over an object's properties
    for (let prop in someobj) {
        ...
    }

If you use "var" then the variable is added to function scope, not block scope. See What's new in JavaScript 1.7

Classes

There are many approaches to classes in JavaScript. We use our own class framework, which is built in gjs. The advantage is that it supports inheriting from GObjects.

    const IconLabelMenuItem = new Lang.Class({
        Name: 'IconLabelMenuItem',
        Extends: PopupMenu.PopupMenuBaseItem,

        _init: function(icon, label) {
            this.parent({ reactive: false });
            this.addActor(icon);
            this.addActor(label);
        },

        open: function() {
            log("menu opened!");
        }
    });
  • 'Name' is required. 'Extends' is optional. If you leave it out, you will automatically inherit from Object.
  • Leave a blank line between the "class header" (Name, Extends, and other things) and the "class body" (methods). Leave a blank line between each method.
  • No space before the colon, one space after.
  • No trailing comma after the last item.
  • Make sure to use a semicolon after the closing paren to the class. It's still a giant function call, even though it may resemble a more conventional syntax.

GObject Introspection

GObject Introspection is a powerful feature that allows us to have native bindings for almost any library built around GObject. If a library requires you to inherit from a type to use it, you can do so:

    const MyClutterActor = new Lang.Class({
        Name: 'MyClutterActor',
        Extends: Clutter.Actor,

        vfunc_get_preferred_width: function(actor, forHeight) {
             return [100, 100];
        },

        vfunc_get_preferred_height: function(actor, forWidth) {
             return [100, 100];
        },

        vfunc_paint: function(actor) {
             let alloc = this.get_allocation_box();
             Cogl.set_source_color4ub(255, 0, 0, 255);
             Cogl.rectangle(alloc.x1, alloc.y1,
                            alloc.x2, alloc.y2);
        }
    });

Translatable Strings

We use gettext to translate GNOME applications into all the supported languages. The gettext function should be aliased globally as _.

const _ = imports.gettext.gettext;

Use 'single quotes' for programming strings that should not be translated and "double quotes" for strings that the user may see. This allows us to quickly find untranslated or mistranslated strings by grepping through the sources for double quotes without a gettext call around them.

Functional Style

JavaScript Array objects offer a lot of common functional programming capabilities such as forEach, map, filter and so on. You can use these when they make sense, but please don't have a spaghetti mess of function programming messed in a procedural style. Use your best judgment.

Closures

this will not be captured in a closure, it is relative to how the closure is invoked, not to the value of this where the closure is created, because "this" is a keyword with a value passed in at function invocation time, it is not a variable that can be captured in closures.

All closures should be wrapped with a Lang.bind.

    const Lang = imports.lang;

    let closure = Lang.bind(this, function() { this._fnorbate(); });

A more realistic example would be connecting to a signal on a method of a prototype:

    const Lang = imports.lang;
    const FnorbLib = imports.fborbLib;

    const MyClass = new Lang.Class({
        _init: function() {
            let fnorb = new FnorbLib.Fnorb();
            fnorb.connect('frobate', Lang.bind(this, this._onFnorbFrobate));
        },

        _onFnorbFrobate: function(fnorb) {
            this._updateFnorb();
        }
    });

Object Literal Syntax

In JavaScript, these are equivalent:

    foo = { 'bar': 42 };
    foo = { bar: 42 };

and so are these:

    var b = foo['bar'];
    var b = foo.bar;

If your usage of an object is like an object, then you're defining "member variables." For member variables, use the no-quotes no-brackets syntax: { bar: 42 } foo.bar.

If your usage of an object is like a hash table (and thus conceptually the keys can have special chars in them), don't use quotes, but use brackets: { bar: 42 }, foo['bar'].

JavaScript Attributes

Don't use the getter/setter syntax when getting and setting has side effects, that is, the code:

    foo.bar = 10;

should not do anything other than save "10" as a property of foo. It's obfuscated otherwise; if the setting has side effects, it's better if it looks like a method.

In practice this means the only use of attributes is to create read-only properties:

    get bar() {
        return this._bar;
    }

If the property requires a setter, or if getting it has side effects, methods are probably clearer.

See Also

Attic/Gjs/StyleGuide (last edited 2018-03-10 16:57:13 by AndyHolmes)