Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 259
Coding style guide
This document attempts to explain the basic styles and patterns that are used in the Jetpack codebase. While existing code may not always comply to this style, new code should try to conform to these standards so that it is as easy to maintain as existing code. Of course every rule has an exception, but it's important to know the rules nonetheless!
2-column indentation. 80-character limit for all lines.
Use double quotation symbol " for strings as they are a
standard in rest of Mozilla code base. Although some files may
be authored with single quotes ', stay consistent with the
style use by a file when making changes to it and use '. Do
not mix styles in the same file:
// Good!const{ Panel }=require("panel");const{ Widget }=require("widget");// Acceptableconst{ Panel }=require('panel');const{ Widget }=require('widget');// Badconst{ Panel }=require('panel');const{ Widget }=require("widget");Use dots at the end of lines, not at the front:
// Goodconstbrowsers=Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator).getEnumerator("navigator:browser");// Badconstbrowsers=Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator).getEnumerator("navigator:browser");Avoid use of error prone undefined (which can be redefined) and prefer
void(0) instead.
// Goodif(x===void(0)){// ...}// Badif(x===undefined){// ...}Forget about var and use const instead. Use let only when
variable will be (re)assigned different value later in the code.
It's only ok to use var if code is going to be used in other
runtimes, but such files should not use let or const.
// Goodconstcount=observers.length;letindex=0;while(index<count){constobserver=observers[index];observer(...args);index=index+1;}// Bad// Reader expects count to change!letcount=observers.length;letindex=0;while(index<count){constobserver=observers[index];observer(...args);index=index+1;}// Badconstcount=observers.length;letindex=0;while(index<count){// Reader expect observer to change with in the block!letobserver=observers[index];observer(...args);index=index+1;}// Badconstcount=observers.length;// Do not use `var` if `let` or `var` used in file.varindex=0;while(index<count){observers[index](...args);index=index+1;}// Acceptable if file only uses varvarcount=observers.length;// Do not use `var` if `let` or `const` used in file.varindex=0;while(index<count){observers[index](...args);index=index+1;}For constants values use const declaration and ALL_CAPITAL_SNAKE_CASE
naming convention.
// GoodconstNORMAL_FILE_TYPE=0;constDIRECTORY_TYPE=1;// BadconstnormalFileType=0;constDirectoryType=1;letNORMAL_FILE_TYPE=0;varDIRECTORY_TYPE=1;Use arrow functions unless you are defining a "class" or a method.
// Goodconstfind=(collection,predicate,fallback)=>{for(letitemofcollection)if(predicate(item))returnitem;returnfallback;};// GoodconstCurse=function(){// ...}Curse.prototype.cry=function(){// ...}// Badfunctionfind(collection,predicate,fallback){for(letitemofcollection)if(predicate(item))returnitem;returnfallback;};constfind=function(collection,predicate,fallback){for(letitemofcollection)if(predicate(item))returnitem;returnfallback;};Curse.prototype.cry=()=>{// ...}Prefer shorter syntax with implicit return. Never use Mozilla's specific short functions syntax though.
// Goodconstadd=(x,y)=>x+y// Badfunctionadd(x,y){returnx+y;}// No No No!!functionadd(x,y)x+y;Use short arrow syntax even if single expression does not fits on
one / same line. Do not add {} or return keyword unless function
contains multiple statements.
// GoodconstgetInnerId=window=>window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;// BadconstgetInnerId=window=>{returnwindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;}// BadfunctiongetInnerId(window){returnwindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;};Identify arguments that function ignores via _ name:
// Goodconstconstant=x=>_=>xconstfive=constant(5);five();// => 5Use rest parameters syntax for capturing arguments in the array:
// Goodconstmultiply=(multiplier, ...operands)=>operands.map(operand=>multiplier*operand)// Badconstmultiply=(multiplier)=>Array.slice(arguments,1).map(operand=>multiplier*operand)// Badconstmultiply=(multiplier)=>Array.prototype.slice.call(arguments,1).map(operand=>multiplier*operand)Use spread operator for applying arguments to a function. Forget that
.apply exists!
// Goodconstwrap=(f,g)=>(...args)=>g(f, ...args)instance.method(...args)// Badconstwrap=(f,g)=>(...args)=>g.apply(g,[f].concat(args))instance.method.apply(instance,args);Only functions with Referential transparency should return
non void values. If function causes any side effects on given
arguments or on bindings in the outer scope it should return
undefined. This makes it clear to a reader / user whether
invoked function causes any side-effects or not without looking
into implementation.
// Good// Define does not returns value since it mutates `object`.constdefine=(object,properties)=>{letdescriptor={};Object.getOwnPropertyNames(properties).forEach(name=>{descriptor[name]=Object.getOwnPropertyDescriptor(properties,name);});Object.defineProperties(object,properties);}// `extend` is referentially transparent, as it does not// mutates given arguments nor things in the outer scope.constextend=(target,properties)=>{letresult=Object.create(target);define(result,properties);returnresult}// Bad// This is bad because it's no longer clear if `a = define(b, c)` has// changed anything or not.constdefine=(object,properties)=>{letdescriptor={};Object.getOwnPropertyNames(properties).forEach(name=>{descriptor[name]=Object.getOwnPropertyDescriptor(properties,name);});Object.defineProperties(object,properties);returnobject;}Do not declare function within blocks.
// GoodconstreadURIs=(uris,callback)=>{letpending=uris.length;// Using `let` to signify that output is going// to be mutated.letoutput=[];uris.reduce((index,uri)=>{// Defining functions with-in the functions is ok.readURI(uri,content=>{output[index]=content;pending=pending-1;if(!pending)callback(output)});returnindex+1;},0);}// GoodconstreadURIs=(uris,callback)=>{letpending=uri.length;letresults=[];constmakeFetchHandler=index=>content=>{output[index]=content;pending=pending-1;if(!pending)callback(output)}letindex=0;letcount=uris.length;while(index<count){readURI(uri,makeFetchHandler(index));index=index+1;}}// BadconstreadURIs=(uris,callback)=>{letpending=uris.length;// Using `let` to signify that output is going// to be mutated.letoutput=[];constcount=uris.length;letindex=0;while(index<count){letid=index;// Defining functions with-in the functions is ok.readURI(uri,content=>{output[id]=content;pending=pending-1;if(!pending)callback(output)});index=index+1;}}If you ever find yourself in a need of using .call or .apply to pass
in this pseudo-variable, you are doing it wrong. That method should have
being a function in first place:
constTarget=function(){// ...}constFancyTarget=function(){}FancyTarget.prototype=Object.create(Target.prototype);// Good// GoodTarget.prototype.registerListener=function(listener){registerListener(this,listener);}// BestconstregisterListener=(target,listener)=>{// ...}// See: https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/lang/functional.html#method%28lambda%29Target.prototype.registerListener=method(registerListener);// ...FancyTarget.prototype.registerListener=function(listener){this.listenerCount=this.listenerCount+1;registerListener(this,listener);}// BadFancyTarget.prototype.registerListener=function(listener){this.listenerCount=this.listenerCount+1;this.registerListener.call(this,listener);}- Use
camelCasefor functions and variables. - Use capitalized
CamelCasefor classes / constructor functions. - Use all lowercase for file names in order to avoid confusion on case-sensitive
platforms. Filenames should end in
.jsand should contain no punctuation except for-delimiters.
A branch follows its conditional on a new line and is indented:
if(foo)bar();If all branches of a conditional are one-liner single statements, no braces needed:
if(foo)bar();elseif(baz)beep();elsequx();A single-statement branch that requires multiple lines also requires braces. The opening brace follows the conditional on the same line:
if(foo){if(bar)baz();}if(foo){Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator).getEnumerator('navigator:browser').doSomethingOnce();}If any branch requires braces, use them for all:
if(foo){bar();}else{doThis();andThat();}Do not cuddle else:
// Goodif(foo){bar();baz();}else{qux();qix();}// Badif(foo){bar();baz();}else{qux();qix();}Use triple equal === instead of double == unless there
is a reason not to. If in a given case == is preferred add
a comment to explain why.
// Goodif(password===secret)authorize()elsedeny()// Badif(password==secret)authorize()elsedeny()Do not compare to booleans unless exactly true or false
is expected (add comment if that's a case):
// Goodif(x)doThis()elseif(!y)doThat()elsedoSomethingElse()// Badif(x===true)doThis()elseif(y!=false)doThat()elsedoSomethingElse()Conditional style also applies to loop style.
for(leti=0;i<arr.len;i++)arr[i]=0;for(leti=0;i<arr.len;i++){if(i%2)arr[i]=0;}Prefer array methods to avoid loops, if you need loop for
whatever reason prefer for of and if it's not a good fit
then while, plain for loops is a last resort! If you have
internal doubts read Learnable Programming essay.
// bestxs.reduce((sum,x)=>sum+x)// goodletsum=0;for(letxofxs)sum=sum+x// okconstcount=xs.length;letindex=0;letsum=0;while(index<count)sum=sum+xs[index];// badconstcount=xs.length;letsum=0;for(leti=0;i<count;i++)sum=sum+xs[i];Do not cuddle catch:
// Goodtry{bar();}catch(err){baz();}// Badtry{bar();}catch(err){baz();}Reuse functions where possible, creating closures on every call has worse performance and generates more garbage to be GC-ed.
// GoodconstisOdd=x=>x%2constsum=(x,y)=>x+yconstfoo=nums=>nums.filter(isOdd).reduce(sum);// Badconstfoo=nums=>{returnnums.filter(function(x){returnx%2;}).reduce(function(a,b){returna+b;});}This applies to in-source docs only, not nice docs meant for end users.
All exported functions should be documented in JSDoc style. Their purpose is to help people looking at your code.
/** * This function registers given user. * @param {String} name * The name of the user. * @param {String|Number} id * Unique user ID. * @param {String[]} aliases * Array of aliases user * @param {String} [accessLevel='user'] * Optional `accessLevel` for a user. */constregister=(name,id,aliases,accessLevel)=>{// ...}/** * Registers user and returns associated ID. * @param {Object} options * Information about the user. * @param {String} options.name * The name of the user. * @param {String} [options.aliases] * Optional array of aliases */constregisterUser=options=>{// ...}Module internal utility functions don't need to be documented this way, but it's encouraged.
For all other comments use single line comments. If a comment is a full sentence, capitalize and punctuate it. If a comment is almost a full sentence, make it a full sentence. Full sentences are generally preferable to fragments, but fragments are sometimes more effective. Fragments should be very terse. Don't capitalize or punctuate fragments.
Quote identifiers with backticks:
// Returns string content under given `uri`. Throws// exception if such `uri` does not exists.constreadURI=uri=>{}Exported functions should be named and defined at the top
level module scope. Assignment to exports should follow
a definition as separate statement:
// GoodconstdoSomething=()=>{// ....}exports.doSomething=doSomething;// Badexports.doSomething=()=>{// ...}// BadvardoSomething=exports.doSomething=()=>{// ...}Exported functions should be referenced via local name and not as
an exported property. This is both faster and future proof, since
in upcoming standard JS modules export will be a statement and
exports will have to be referenced via local name:
// Good constdoThis=()=>{// ...runExportedF()// ...}// BadconstdoThis=()=>{// ...exports.runExportedF()// ...}Same rules apply to non function exports as well:
// Goodvarfoo={// ...};exports.foo=foo;constbar=()=>{// ...doSomething(foo);// ...}// Badexports.foo={// ...};constbar=()=>{// ...doSomething(exports.foo);// ...}- List Comprehensions. Prefer
map,filter,reduceas they are short enough but a lot easier to read & understand.
constresult=[fn()for(xinsomeArray)];// badconstresult=someArray.map(fn);// better