HACKING.md 9.63 KB
Newer Older
Florian Müllner's avatar
Florian Müllner committed
1
# Coding guide
2 3 4 5 6

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.

Florian Müllner's avatar
Florian Müllner committed
7
## A quick note
8 9 10 11 12

Life isn't fun if you can't break the rules. If a rule seems unnecessarily
restrictive while you're coding, ignore it, and let the patch reviewer decide
what to do.

13
## Indentation, braces and whitespace
14

15 16 17 18 19 20
* Use four-space indents.
* 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.
21

Florian Müllner's avatar
Florian Müllner committed
22
```javascript
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
    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);
        }
    }
Florian Müllner's avatar
Florian Müllner committed
39
```
40

Florian Müllner's avatar
Florian Müllner committed
41
## Semicolons
42 43 44 45

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

Florian Müllner's avatar
Florian Müllner committed
46
## js2-mode
47 48 49 50 51

If using Emacs, do not use js2-mode. It is outdated and hasn't worked for a
while. emacs now has a built-in JavaScript mode, js-mode, based on
espresso-mode. It is the de facto emacs mode for JavaScript.

Florian Müllner's avatar
Florian Müllner committed
52
## File naming and creation
53 54 55 56 57 58 59 60 61 62 63 64

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

We only use C where gjs/gobject-introspection is not available for the task, or
where C would be cleaner. To work around limitations in
gjs/gobject-introspection itself, add a new method in `shell-util.[ch]`.

Like many other GNOME projects, we prefix our C source filenames with the
library name followed by a dash, e.g. `shell-app-system.c`. Create a
`-private.h` header when you want to share code internally in the
library. These headers are not installed, distributed or introspected.

Florian Müllner's avatar
Florian Müllner committed
65
## Imports
66 67 68

Use UpperCamelCase when importing modules to distinguish them from ordinary
variables, e.g.
Florian Müllner's avatar
Florian Müllner committed
69
```javascript
70
    const GLib = imports.gi.GLib;
Florian Müllner's avatar
Florian Müllner committed
71
```
72 73 74 75 76 77 78 79 80 81
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". These
are the JS code that is in the gnome-shell codebase,
e.g. `imports.ui.popupMenu`.

Each import block should be sorted alphabetically. Don't import modules you
don't use.
Florian Müllner's avatar
Florian Müllner committed
82
```javascript
83
    const { GLib, Gio, St } = imports.gi;
84 85 86 87 88

    const Main = imports.ui.main;
    const Params = imports.misc.params;
    const Tweener = imports.ui.tweener;
    const Util = imports.misc.util;
Florian Müllner's avatar
Florian Müllner committed
89
```
90 91 92
The alphabetical ordering should be done independently of the location of the
location. Never reference `imports` in actual code.

Florian Müllner's avatar
Florian Müllner committed
93
## Constants
94 95 96

We use CONSTANTS_CASE to define constants. All constants should be directly
under the imports:
Florian Müllner's avatar
Florian Müllner committed
97
```javascript
98
    const MY_DBUS_INTERFACE = 'org.my.Interface';
Florian Müllner's avatar
Florian Müllner committed
99
```
100

Florian Müllner's avatar
Florian Müllner committed
101
## Variable declaration
102 103

Always use either `const` or `let` when defining a variable.
Florian Müllner's avatar
Florian Müllner committed
104
```javascript
105 106 107 108 109 110 111 112 113
    // 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) {
        ...
    }
Florian Müllner's avatar
Florian Müllner committed
114
```
115 116 117 118

If you use "var" then the variable is added to function scope, not block scope.
See [What's new in JavaScript 1.7](https://developer.mozilla.org/en/JavaScript/New_in_JavaScript/1.7#Block_scope_with_let_%28Merge_into_let_Statement%29)

Florian Müllner's avatar
Florian Müllner committed
119
## Classes
120

Florian Müllner's avatar
Florian Müllner committed
121 122
There are many approaches to classes in JavaScript. We use standard ES6 classes
whenever possible, that is when not inheriting from GObjects.
Florian Müllner's avatar
Florian Müllner committed
123
```javascript
Florian Müllner's avatar
Florian Müllner committed
124 125 126
    var IconLabelMenuItem = class extends PopupMenu.PopupMenuBaseItem {
        constructor(icon, label) {
            super({ reactive: false });
127 128
            this.actor.add_child(icon);
            this.actor.add_child(label);
Florian Müllner's avatar
Florian Müllner committed
129
        }
130

131
        open() {
132 133
            log("menu opened!");
        }
Florian Müllner's avatar
Florian Müllner committed
134
    };
Florian Müllner's avatar
Florian Müllner committed
135
```
136

Florian Müllner's avatar
Florian Müllner committed
137 138 139 140 141 142 143
For GObject inheritence, we use the GObject.registerClass() function provided
by gjs.
```javascript
    var MyActor = GObject.registerClass(
    class MyActor extends Clutter.Actor {
        _init(params) {
            super._init(params);
144

Florian Müllner's avatar
Florian Müllner committed
145 146 147 148
            this.name = 'MyCustomActor';
        }
    });
```
149

Florian Müllner's avatar
Florian Müllner committed
150
## GObject Introspection
151 152 153 154

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:
Florian Müllner's avatar
Florian Müllner committed
155
```javascript
Florian Müllner's avatar
Florian Müllner committed
156 157
    var MyClutterActor = GObject.registerClass(
    class MyClutterActor extends Clutter.Actor {
158

159
        vfunc_get_preferred_width(forHeight) {
160
             return [100, 100];
Florian Müllner's avatar
Florian Müllner committed
161
        }
162

163
        vfunc_get_preferred_height(forWidth) {
164
             return [100, 100];
Florian Müllner's avatar
Florian Müllner committed
165
        }
166

167
        vfunc_paint() {
168 169 170 171 172 173
             let alloc = this.get_allocation_box();
             Cogl.set_source_color4ub(255, 0, 0, 255);
             Cogl.rectangle(alloc.x1, alloc.y1,
                            alloc.x2, alloc.y2);
        }
    });
Florian Müllner's avatar
Florian Müllner committed
174
```
175

Florian Müllner's avatar
Florian Müllner committed
176
## Translatable strings, `environment.js`
177 178 179 180 181 182 183 184 185 186 187 188 189

We use gettext to translate the GNOME Shell into all the languages that GNOME
supports. The `gettext` function is aliased globally as `_`, you do not need to
explicitly import it. This is done through some magic in the
[environment.js](http://git.gnome.org/browse/gnome-shell/tree/js/ui/environment.js)
file. If you can't find a method that's used, it's probably either in gjs itself
or installed on the global object from the Environment.

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.

Florian Müllner's avatar
Florian Müllner committed
190
## `actor` and `_delegate`
191 192 193 194 195 196 197 198

gjs allows us to set so-called "expando properties" on introspected objects,
allowing us to treat them like any other. Because the Shell was built before
you could inherit from GTypes natively in JS, we usually have a wrapper class
that has a property called `actor`. We call this wrapper class the "delegate".

We sometimes use expando properties to set a property called `_delegate` on
the actor itself:
Florian Müllner's avatar
Florian Müllner committed
199
```javascript
Florian Müllner's avatar
Florian Müllner committed
200 201
    var MyClass = class {
        constructor() {
202 203 204
            this.actor = new St.Button({ text: "This is a button" });
            this.actor._delegate = this;

205
            this.actor.connect('clicked', this._onClicked.bind(this));
Florian Müllner's avatar
Florian Müllner committed
206
        }
207

208
        _onClicked(actor) {
209 210
            actor.set_label("You clicked the button!");
        }
Florian Müllner's avatar
Florian Müllner committed
211
    };
Florian Müllner's avatar
Florian Müllner committed
212
```
213 214 215 216 217 218 219

The 'delegate' property is important for anything which trying to get the
delegate object from an associated actor. For instance, the drag and drop
system calls the `handleDragOver` function on the delegate of a "drop target"
when the user drags an item over it. If you do not set the `_delegate`
property, your actor will not be able to be dropped onto.

Florian Müllner's avatar
Florian Müllner committed
220
## Functional style
221 222 223 224 225 226

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.

Florian Müllner's avatar
Florian Müllner committed
227
## Closures
228 229 230 231 232 233

`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.

234 235
All closures should be wrapped with Function.prototype.bind or use arrow
notation.
Florian Müllner's avatar
Florian Müllner committed
236
```javascript
237 238
    let closure1 = () => { this._fnorbate(); };
    let closure2 = this._fnorbate.bind(this);
Florian Müllner's avatar
Florian Müllner committed
239
```
240 241 242

A more realistic example would be connecting to a signal on a method of a
prototype:
Florian Müllner's avatar
Florian Müllner committed
243
```javascript
244 245
    const FnorbLib = imports.fborbLib;

Florian Müllner's avatar
Florian Müllner committed
246
    var MyClass = class {
247
        _init() {
248
            let fnorb = new FnorbLib.Fnorb();
249
            fnorb.connect('frobate', this._onFnorbFrobate.bind(this));
Florian Müllner's avatar
Florian Müllner committed
250
        }
251

252
        _onFnorbFrobate(fnorb) {
253 254
            this._updateFnorb();
        }
Florian Müllner's avatar
Florian Müllner committed
255
    };
Florian Müllner's avatar
Florian Müllner committed
256
```
257

Florian Müllner's avatar
Florian Müllner committed
258
## Object literal syntax
259 260

In JavaScript, these are equivalent:
Florian Müllner's avatar
Florian Müllner committed
261
```javascript
262 263
    foo = { 'bar': 42 };
    foo = { bar: 42 };
Florian Müllner's avatar
Florian Müllner committed
264
```
265 266

and so are these:
Florian Müllner's avatar
Florian Müllner committed
267
```javascript
268 269
    var b = foo['bar'];
    var b = foo.bar;
Florian Müllner's avatar
Florian Müllner committed
270
```
271 272 273 274 275 276 277 278 279

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']`.

Florian Müllner's avatar
Florian Müllner committed
280
## Getters, setters, and Tweener
281 282 283 284 285

Getters and setters should be used when you are dealing with an API that is
designed around setting properties, like Tweener. If you want to animate an
arbitrary property, create a getter and setter, and use Tweener to animate the
property.
Florian Müllner's avatar
Florian Müllner committed
286
```javascript
287
    var ANIMATION_TIME = 2000;
288

Florian Müllner's avatar
Florian Müllner committed
289 290
    var MyClass = class {
        constructor() {
291 292
            this.actor = new St.BoxLayout();
            this._position = 0;
Florian Müllner's avatar
Florian Müllner committed
293
        }
294 295 296

        get position() {
            return this._position;
Florian Müllner's avatar
Florian Müllner committed
297
        }
298 299 300 301 302

        set position(value) {
            this._position = value;
            this.actor.set_position(value, value);
        }
Florian Müllner's avatar
Florian Müllner committed
303
    };
304 305 306 307 308 309

    let myThing = new MyClass();
    Tweener.addTween(myThing,
                     { position: 100,
                       time: ANIMATION_TIME,
                       transition: 'easeOutQuad' });
Florian Müllner's avatar
Florian Müllner committed
310
```