/HACKING.md
Markdown | 327 lines | 255 code | 72 blank | 0 comment | 0 complexity | aa68110275f6611ad61f467b398a9288 MD5 | raw file
- # Coding guide
- 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.
- ## A quick note
- 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.
- ## Indentation, braces and whitespace
- * 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.
- ```javascript
- 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);
- }
- }
- ```
- ## Semicolons
- JavaScript allows omitting semicolons at the end of lines, but don't. Always
- end statements with a semicolon.
- ## js2-mode
- 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.
- ## File naming and creation
- 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.
- ## Imports
- Use UpperCamelCase when importing modules to distinguish them from ordinary
- variables, e.g.
- ```javascript
- 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". 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.
- ```javascript
- 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:
- ```javascript
- const MY_DBUS_INTERFACE = 'org.my.Interface';
- ```
- ## Variable declaration
- Always use either `const` or `let` when defining a variable.
- ```javascript
- // 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](https://developer.mozilla.org/en/JavaScript/New_in_JavaScript/1.7#Block_scope_with_let_%28Merge_into_let_Statement%29)
- ## Classes
- There are many approaches to classes in JavaScript. We use our own class framework
- (sigh), which is built in gjs. The advantage is that it supports inheriting from
- GObjects, although this feature isn't used very often in the Shell itself.
- ```javascript
- var IconLabelMenuItem = new Lang.Class({
- Name: 'IconLabelMenuItem',
- Extends: PopupMenu.PopupMenuBaseItem,
- _init(icon, label) {
- this.parent({ reactive: false });
- this.actor.add_child(icon);
- this.actor.add_child(label);
- },
- open() {
- 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:
- ```javascript
- var MyClutterActor = new Lang.Class({
- Name: 'MyClutterActor',
- Extends: Clutter.Actor,
- vfunc_get_preferred_width(actor, forHeight) {
- return [100, 100];
- },
- vfunc_get_preferred_height(actor, forWidth) {
- return [100, 100];
- },
- vfunc_paint(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, `environment.js`
- 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.
- ## `actor` and `_delegate`
- 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:
- ```javascript
- var MyClass = new Lang.Class({
- Name: 'MyClass',
- _init() {
- this.actor = new St.Button({ text: "This is a button" });
- this.actor._delegate = this;
- this.actor.connect('clicked', this._onClicked.bind(this));
- },
- _onClicked(actor) {
- actor.set_label("You clicked the button!");
- }
- });
- ```
- 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.
- ## 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 Function.prototype.bind or use arrow
- notation.
- ```javascript
- const Lang = imports.lang;
- let closure1 = () => { this._fnorbate(); };
- let closure2 = this._fnorbate.bind(this);
- ```
- A more realistic example would be connecting to a signal on a method of a
- prototype:
- ```javascript
- const Lang = imports.lang;
- const FnorbLib = imports.fborbLib;
- var MyClass = new Lang.Class({
- _init() {
- let fnorb = new FnorbLib.Fnorb();
- fnorb.connect('frobate', this._onFnorbFrobate.bind(this));
- },
- _onFnorbFrobate(fnorb) {
- this._updateFnorb();
- }
- });
- ```
- ## Object literal syntax
- In JavaScript, these are equivalent:
- ```javascript
- foo = { 'bar': 42 };
- foo = { bar: 42 };
- ```
- and so are these:
- ```javascript
- 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']`.
- ## Getters, setters, and Tweener
- 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.
- ```javascript
- var ANIMATION_TIME = 2000;
- var MyClass = new Lang.Class({
- Name: 'MyClass',
- _init() {
- this.actor = new St.BoxLayout();
- this._position = 0;
- },
- get position() {
- return this._position;
- },
- set position(value) {
- this._position = value;
- this.actor.set_position(value, value);
- }
- });
- let myThing = new MyClass();
- Tweener.addTween(myThing,
- { position: 100,
- time: ANIMATION_TIME,
- transition: 'easeOutQuad' });
- ```