Personal tools

JavaScript:SpiderMonkey:Coding Style

From MozillaWiki

Jump to: navigation, search

Contents

Functions

  • Public function names begin with JS_ followed by capitalized "intercaps", e.g. JS_NewObject.
  • Extern but library-private function names use a js_ prefix and mixed case, e.g. js_SearchScope.
  • Most static function names have unprefixed, mixed-case names: GetChar.
  • But static native methods of JS objects have lowercase, underscore-separated or intercaps names, e.g., str_indexOf.
  • Function return types are on a separate line preceding the function name.
  • Function braces go on the line following the function name.
void DoThis()           /* bad */
{
    ...
}
void
DoThis()                /* OK */
{
    ...
}

Other Symbols

  • Library-private and static data use underscores, not intercaps (but library-private data do use a js_ prefix).
  • Scalar type names are lowercase and js-prefixed: jsdouble.
  • Aggregate type names are JS-prefixed and mixed-case: JSObject.
  • Macros are generally ALL_CAPS and underscored, to call out potential side effects, multiple uses of a formal argument, etc. Line continuation characters should all line up, in column 79 if that exceeds the width of all the macro text. Macro parameters should be of the form name_ (instead of something like __name).

Indentation

  • Use spaces, not tabs. There should be no tabs in source files.
  • Four spaces of indentation per statement nesting level.
  • "case L:" labels in switch statements count as half of a nesting level, so indent two spaces, with the labeled statements indenting two more for a standard four spaces indentation from switch to a case-controlled statement.
switch (discriminant) {
  case L1:
    DoSomething();
  . . .
}
  • Function arguments that overflow the first line of the call expression should be aligned to underhang the first argument (to start in overflow lines in the column after the opening parenthesis).
JS_SetContext(rt,         /* bad */
             cx);
JS_SetContext(rt,         /* OK */
              cx);

Whitespace in declarations

These rules are inconsistently applied. Be consistent with the code you're editing rather than adhere too closely to these guidelines!

  • In a declaration of a pointer, the * goes with the variable name:
char* s;                  /* bad */
char *s;                  /* OK */
  • Even in the return type of a method:
class JSString
{
    ...
    JSString* dependentBase() const;   /* bad */
    JSString * dependentBase() const;  /* bad */
    JSString *dependentBase() const;   /* OK */
    ...
};
  • But in top-level function declarations/definitions, put a line break immediately before the name being declared:
extern const char *
js_GetStringBytes(JSContext *cx, JSString *str);  /* OK */
  • And put a space in *JS_FASTCALL, because that would be crazy.
extern JSString *JS_FASTCALL           /* bad */
js_ConcatStrings(JSContext *cx, JSString *left, JSString *right);
extern JSString * JS_FASTCALL          /* OK */
js_ConcatStrings(JSContext *cx, JSString *left, JSString *right);
  • In C++ method declarations with default arguments, use spaces and comments like so:
static void
Frob(JSContext *cx, uint32_t defaultValue = 0);
static void
Frob(JSContext *cx, uint32_t defaultValue /* = 0 */)
{
    /* ... */
}

Other whitespace

  • Code should fit within 99 columns; comments should fit within 80 columns; both figures include indentation. Break down lines that are too long by splitting after a binary operator.
  • Exception: in a switch statement where each case is a trivially-short statement, it's ok to put the case, the statement, and the break; all on one line.
  • Comment /* FALL THROUGH */ in place of missing break when intentionally falling through from one case-controlled statement sequence into another, or into the default statements.
  • Do not use spaces between a function name and its arguments list, or between an array name and the square bracket. Also, do no use spaces after a bracket. Use a space after a comma to separate arguments.
JS_SetContext ( rt, cx ); /* bad */
JS_SetContext(rt, cx);    /* OK */
  • Use a space between a C keyword and parentheses.
if(condition)             /* bad */
if (condition)            /* OK */
  • In a conditional, if the consequent (and, if present, the alternate) is a single statement, no braces are used.
if (today == "Tuesday")
    puts("I don't have my wallet on me.");
else
    puts("I would gladly pay you on Tuesday for a hamburger today.");

However, if either the consequent or alternate is a block of multiple statements, braces are used on both.

if (canSwingFromWeb) {
    p->swingFromWeb();
} else {
    JS_ASSERT(p->isSpiderPig());
    p->doWhateverSpiderPigDoes();
}
  • Conditions with multi-line tests should put the brace on the new line to provide a visual separation between the condition and the body.
  types::TypeSet *types = frame.extra(lhs).types;
  if (JSOp(*PC) == JSOP_SETPROP && id == types::MakeTypeId(cx, id) &&
      types && !types->unknownObject() &&
      types->getObjectCount() == 1 &&
      types->getTypeObject(0) != NULL &&
      !types->getTypeObject(0)->unknownProperties())
  {
      JS_ASSERT(usePropCache);
      types::TypeObject *object = types->getTypeObject(0);
      types::TypeSet *propertyTypes = object->getProperty(cx, id, false);
      ...
  } else {
      ...
  }

However, if there is already a visual separation between the condition and the body, putting the { on a new line isn't necessary:

  if (forHead->pn_kid1 && NewSrcNote2(cx, cg, SRC_DECL,
                                      (forHead->pn_kid1->isOp(JSOP_DEFVAR))
                                      ? SRC_DECL_VAR
                                      : SRC_DECL_LET) < 0) {
      return false;
  }
  • for loop heads go on one line where possible; when not possible, initializer part, update, and termination parts each go on separate lines
for (int i = 0;
     i < 5;
     i++) {                 /* bad, could all fit on one line */
    doStuff();
}
for (int i = 0; i < 5; i++) /* OK */
    doStuff();
for (size_t ind = JSObject::JSSLOT_DATE_COMPONENTS_START;
     ind < JSObject::DATE_FIXED_RESERVED_SLOTS; ind++) {   /* bad */
    obj->setSlot(ind, DoubleValue(utcTime));
}
for (size_t ind = JSObject::JSSLOT_DATE_COMPONENTS_START;
     ind < JSObject::DATE_FIXED_RESERVED_SLOTS;
     ind++) {                                               /* OK */
    obj->setSlot(ind, DoubleValue(utcTime));
}
  • In comments, use one space, not two, between sentences and after a colon.

Control Flow

  • Minimize indentation using return, break, and continue where appropriate. Prefer return (break, continue) statements to cast out abnormal cases, instead of nesting "if/else" statements and indenting the common cases.
void
MyFunction(int n)
{
    if (n) {              /* bad */
        ...
    }
}
void
MyFunction(int n)
{
    if (!n)
        return;           /* OK */
    ...
}
  • If an "if" statement controls a "then" clause ending in a return statement, do not use "else" after return.
if (condition) {          /* bad */
    DoThis();
    return;
} else {
    DoThat();
}
if (condition) {          /* OK */
    DoThis();
    return;
}
DoThat();
  • Avoid similar arbitrary patterns and non-sequiturs:
if (condition) {          /* bad */
    DoThis();
    DoThat();
} else {
    CleanUp();
    return;
}
DoTheOther();
if (!condition) {         /* OK */
    CleanUp();
    return;
}
DoThis();
DoThat();
DoTheOther();

Comments

  • In C files, always use C style comments. C++ comments are ok otherwise.
  • Terminate a comment with a period (so try to make comments be complete sentences).
/* This is a good comment. */
// This is also a good comment.
  • For C-style multiline comments, align with any indentation, and start every line with an asterisk. Asterisks stack in the same column. Precede the multiline comment with one empty line unless the prior line ends in a left brace. The first line of the comment contains only leading space followed by /*. Multiline comments should also be bracketed.
if (condition) {
    /*
     * This is a lengthy C-style
     * multiline comment.
     */
    // This is a length
    // C++-style comment
    DoYourStuff();
}

Entry Points and Callbacks

  • DLL entry points have their return type expanded within a JS_PUBLIC_API() macro call, to get the right Windows secret type qualifiers in the right places for all build variants.
  • Callback functions that might be called from a DLL are similarly macroized with JS_STATIC_DLL_CALLBACK (if the function otherwise would be static to hide its name) or JS_DLL_CALLBACK (this macro takes no type argument; it should be used after the return type and before the function name).

Data Types and Alignments

  • As with all Mozilla code, SpiderMonkey needs to compile and execute correctly on many platforms, including 64-bits systems.
  • JS and NSPR have common roots in the dawn of time (Netscape 2), and the JS_THREADSAFE mode of building SpiderMonkey depends on NSPR, so reading the NSPR documentation is well worth your while.
  • Not all 64-bit systems use the same integer type model: some are "LP64" (long and pointer are 64 bits, int is 32 bits), while others are "LLP64" (only long long and pointer are 64 bits; long and int are 32 bits).
  • Use size_t for unsigned number of bytes variables, ptrdiff_t for signed pointer subtraction results. In particular, do not use uintN, which is just shorthand for unsigned int, and so may not be big enough.

Header files

#ifndef wrappers

Use this exact form for #ifndef wrappers in header files:

 #ifndef <guard>
 #define <guard>
 ...
 #endif // <guard>

GCC and clang recognize this idiom and avoid re-reading headers that use it. Don't put any code before the #ifndef or after the #endif, and don't put anything else in the #ifndef, otherwise the optimization will be thwarted and the file will be multiply-included. (Check with the -H option if you want to be sure.)

Include guards should be named by determining the fully-qualified include path, then substituting _ for '/' and '.' and '-' in it. For example, js/src/vm/Stack-inl.h's guard is vm_Stack_inl_h_, and js/public/Vector.h's guard is js_Vector_h (because its include path is js/Vector.h).

#include paths

All #include statements should use a fully-qualified (within SpiderMonkey) path, even if it's not necessary. For example, this:

 #include "vm/Stack.h"

not:

 #include "Stack.h"

This keeps things consistent and helps with the ordering.

For headers in js/public/, the prefix is "js/", e.g.:

 #include "js/Vector.h"

For headers in mfbt/, the prefix is "mozilla/", e.g.:

 #include "mozilla/Assertions.h"

#include ordering

The following order is used for module X:

  • If X-inl.h exists, it goes first. (And X-inl.h's first #include should be X.h.) Otherwise, X.h goes first. This rule ensures that X.h and X-inl.h both #include all the headers that they need themselves.
  • mozilla/*.h
  • <*.h>
  • js*.h
  • */*.h (this includes the public JSAPI headers in js/public/*.h which should be included using the form js/*.h)
  • js*inlines.h
  • */*-inl.h.

Keep (case-insensitive) lexicographic order with each section.

The presence of conditionally-compiled #include statements complicates thing. If you have a single #include statement within a #if/#ifdef/#ifndef block, placing the block in the appropriate section is straightforward. If you have multiple #include statements within a block, use your judgment as to where the best place for it is.

Example for X.cpp:

#include "X.h"    // put "X-inl.h" instead, if it exists

#include "mozilla/HashFunctions.h"

#include <string.h>

#include "jsbar.h"
#ifdef BAZ
# include "jsbaz.h"
#endif
#include "jscaz.h"

#include "ds/Baz.h"
#include "js/Bar.h"
#include "vm/Bat.h"

#include "jssqueeinlines.h"
#include "jswoot-inl.h"

#include "frontend/Thingamabob-inl.h"
#include "vm/VirtualReality-inl.h"

C++

Namespaces

  • Instead of JS/js and JS_ prefixing for public function and type names, respectively, use
namespace JS { ... }
  • Instead of js_ prefixing for library-private and "friend" functions,
namespace js { ... }
  • Compile-time-evaluated functions (i.e., template meta-functions) are kept in the "template library" namespace to avoid collision with their runtime counterparts.
namespace js { namespace tl { ... } }
  • Avoid unnamed namespaces unless they are necessary to give a class's members internal linkage. Although the C++ standard officially deprecates 'static' on functions, 'static' has the following advantages:
    • It is difficult to name functions in unnamed namespaces in gdb.
    • Static says "this is a translation-unit-local helper function" on the function, without having to look around for an enclosing "namespace {".
  • Other namespaces? Are these names too short and likely to collide?

Enums

Older code uses SHOUT_REALLY_LOUD for enum values, newer code uses InterCaps. Enums should be preferred to boolean arguments for ease of understanding at the invocation site.

Classical OOP

  • Most structs can become classes, with associated functions becoming methods. This already started for the upvar2 bug.
    • Style detailing: mrbkap suggests left brace before class body on its own line in column 1 to match function style and facilitate vim navigation. People do this already when inheriting, since the left brace can get lost in the superclass(es).
class JSObject
{
     ...
}
  • Member variable names, private or public, are sometimes decorated with a trailing '_'.
class Fail
{
    size_t capacity;  // common
    T *begin_;        // also common, being used more as time goes on
}

Sometimes a canonical argument name may conflict with a member name. In this case, one can disambiguate with "this->", although such explicit qualification should only be added when necessary:

class C
{
    size_t count;
    bool fresh;
    void change(size_t count) {
        this->count = count;
        this->fresh = false;  // verbose and unnecessary
    }
};
  • Most macros become inline helpers. With LiveConnect gone on mozilla-central, we can make the helpers methods of relevant structs or classes finally, instead of static inline functions.
  • Based on 20+ years of bad experiences, we are going to go slow and resist virtual methods and bases, MI, and the like. Function pointer tables for now, as before -- see JSObjectOps needs a make-over.
  • Templates are good, Mozilla has positive experience and the portability is there for the kind of RAII helpers we need.
  • No exceptions, so std is hard to use. There is initial work underway to make STL-like containers that mesh well with the rest of the JS engine (see js::Vector, js::HashMap, js::Pool).
    • There are still improvements to be made to the new hash table: double-hash implementation; improve bit-mixing into multiplicative hash if the cycle costs can be supported (measurement is required and we should understand down to the bits what is going on); a js::HashSet or js::HashMap<T,void> specialization such that set-like use of hashtables do not waste any space on values.

Initializer lists

Initializer lists can break in one of two ways. The first may be preferable when constructors take few arguments:

class Ninja : public WeaponWeilder, public Camouflagible,
              public Assassinatable, public ShapeShiftable
{
    Ninja() : WeaponWeilder(Weapons::SHURIKEN),
              Camouflagible(Garments::SHINOBI_SHOZOKU),
              Assassinatable(AssassinationDifficulty::HIGHLY_DIFFICULT),
              ShapeShiftable(MPCost(512)) {}
}

The other permitted style mitigates longer-identifiers-squishing-text-against-the-right-side-of-the-screen-syndrome by using a half-indented colon:

class Ninja
  : public WeaponWeilder, public Camouflagible, public Assassinatable,
    public ShapeShiftable
{
    Ninja()
      : WeaponWeilder(Weapons::SHURIKEN),
        Camouflagible(Garments::SHINOBI_SHOZOKU),
        Assassinatable(AssassinationDifficulty::HIGHLY_DIFFICULT),
        ShapeShiftable(MPCost(512)) {}
}

Inline methods

If the method in question fits on one line and is branch free, do a one liner:

class Eater
{
    void eat(Eaten &other) { other.setConsumed(); }
};

If it's too long, put the type, declarator including formals (unless they overflow), and left brace all on the first line:

class Eater
{
    Food *obtainFoodFromEatery(Eatery &eatery) {
        if (!eatery.hasFood())
            return NULL;
        return eatery.purchaseFood();
    }
};

For out-of-line inlines (when the definitions are too unwieldy to place in the class definition) use the inline keyword as an indicator that there's an out-of-line inline definition:

class SpaceGoo
{
    inline BlobbyWrapper *enblob(Entity &other);
};

inline BlobbyWrapper *
SpaceGoo::enblob(Entity &other)
{
    /* ... */
}

Boolean Types

  • We would like to use the C++ 'bool' type wherever possible, but we must avoid breaking public and 'friend' APIs that use JSBool. So public header files (and semi-public headers, like 'jsopcodes.h') should continue to use 'JSBool', 'JS_TRUE', and 'JS_FALSE'. In all SpiderMonkey '.cpp' files, use 'true' and 'false', never 'JS_TRUE' or 'JS_FALSE'. Use 'bool' whenever doing so would not conflict with a declaration in a public or semi-public header.

References

Exceptions to this coding style

SpiderMonkey contains some code imported from other projects, e.g. ctypes/libffi/, that is minimally modified. Such code does not have to follow SpiderMonkey style.