JavaScript:SpiderMonkey:Coding Style

From MozillaWiki
Jump to: navigation, search

Code Organization

See JavaScript:SpiderMonkey:Directories.

Functions

  • Public function names go into the JS namespace, with the function name "intercaps", e.g. JS_NewObject.
    • Older functions may begin with JS_. Use the namespace instead in new code.
  • Extern but library-private function names use the js namespace 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 type:
char *s;                  /* bad */
char* s;                  /* OK */
  • 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 */
  • Always use braces for conditionals, even if the condition and consequent occupy single lines.
if (!isAwake) {
    return false;
}
if (canSwingFromWeb) {
    p->swingFromWeb();
} else {
    MOZ_ASSERT(p->isSpiderPig());
    p->doWhateverSpiderPigDoes();
}
if (hungryForBananas && // and a comment makes the line long enough for a linebreak
    wantsAttentionInTheWorstWay)
{
    p->eatBananasAndPreen();
}
  • 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) != nullptr &&
      !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();
  • Avoid using && or || to mix deciding-whether-to-do-something with error-checking.
if (obj->hasProblems() && !obj->rectify()) {   /* bad */
    return false;
}

if (obj->hasProblems()) {                      /* OK */
    if (!obj->rectify()) {
        return false;
    }
}

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

  • Public functions and types should be in the JS:: namespace, in preference to the old JS/JS_ name prefixes.
  • Library-private and friend functions should be in the js:: namespace, in preference to the old js_ name prefix.
  • Compile-time-evaluated functions (i.e., template meta-functions) should be in 'js::tl::', the "template library" namespace, to avoid collision with their runtime counterparts.
  • In SpiderMonkey .cpp files, it is okay to have 'using namespace js;', but it is not okay to do the same with any other namespace, like JS:: or mozilla::. These can introduce ambiguities that come and go depending on which .cpp files the build system decides to unify.
  • If you do have names in JS:: that should be readily available throughout SpiderMonkey, you may add a 'using' declaration to js/src/NamespaceImports.h.
  • 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 over unnamed namespaces:
    • 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 {".

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.

Classes

  • For reasonable-sized classes, put all the fields together, at the top, immediately after any necessary typedefs. For unreasonably large classes, do whatever seems best (but let's try to avoid making more of these).
  • Align the braces of a class/struct/enum definition like this:
class JSObject
{
     ...
};
  • Member variable names, private or public, should be decorated with a trailing '_'.
class Fail
{
    size_t capacity;  // common
    T* begin_;        // better, gravitate toward this
};

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
{
  public:
    size_t count;
    bool fresh;
    void change(size_t count) {
        this->count = count;
        this->fresh = false;  // verbose and unnecessary
    }
};
  • Prefer inline functions to macros.
  • We build with C++ exceptions disabled, so most of the std namespace is off-limits, including STL containers. The exception is <algorithm>, which is mostly OK to use. You can use MFBT. We have some STL-like containers that are used basically everywhere (see js::Vector, js::HashMap, etc.)

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 nullptr;
        }
        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)
{
    /* ... */
}

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.