Skip to content

Instantly share code, notes, and snippets.

@xeioex
Created August 28, 2025 04:24
Show Gist options
  • Save xeioex/32b09d34acb90ad2a32cf2bdd505ff95 to your computer and use it in GitHub Desktop.
Save xeioex/32b09d34acb90ad2a32cf2bdd505ff95 to your computer and use it in GitHub Desktop.

In addition to the previous instructions, check PRs for coding style.

Njs project style guide

  • applies to C source code in src/ directory

General rules

  • maximum text width is 80 characters
  • indentation is 4 spaces
  • no tabs, no trailing spaces
  • list elements on the same line are separated with spaces
  • hexadecimal literals are lowercase
  • file names, function and type names, and global variables have the njs_ prefix
size_t
njs_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

    for (len = 0; p < last; len++) {

        c = *p;

        if (c < 0x80) {
            p++;
            continue;
        }

        if (njs_utf8_decode(&p, last - p) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

Files

A typical source file may contain the following sections separated by two empty lines:

  • copyright statements
  • includes
  • preprocessor definitions
  • type definitions
  • function prototypes
  • variable definitions
  • function definitions

Copyright statements look like this:

/*
 * Copyright (C) Author Name
 * Copyright (C) Organization, Inc.
 */

If the file is modified significantly, the list of authors should be updated, the new author is added to the top.

The njs_main.h file is always included first. Then follow optional external header files:

#include <njs_main.h>

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxslt/xslt.h>

#if (NJS_HAVE_EXSLT)
#include <libexslt/exslt.h>
#endif

Header files should include the so called "header protection":

#ifndef _NJS_VM_H_INCLUDED_
#define _NJS_VM_H_INCLUDED_
...
#endif /* _NJS_VM_H_INCLUDED_ */

Comments

  • "//" comments are not used
  • text is written in English, American spelling is preferred
  • multi-line comments are formatted like this:
/*
 * The red-black tree code is based on the algorithm described in
 * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
 */
/* find the server configuration for the address:port */

Macro names start from njs_ or NJS_ prefix. Macro names for constants are uppercase. Parameterized macros and macros for initializers are lowercase. The macro name and value are separated by at least two spaces:

#define NJS_STRING_MAX_LENGTH  0x7fffffff

#define njs_queue_is_empty(queue)  ((queue) == (queue)->prev)

#define njs_queue_insert_head(queue, link)                                   \
    (link)->next = (queue)->next;                                            \
    (link)->next->prev = (link);                                             \
    (link)->prev = (queue);                                                  \
    (queue)->next = (link)

#define njs_null_string  { 0, NULL }

Conditions are inside parentheses, negation is outside:

#if (NJS_HAVE_OPENSSL)
...
#elif (NJS_HAVE_QUICKJS)
...
#elif (NJS_HAVE_ADDR2LINE)
...
#else /* default */
...
#endif /* NJS_HAVE_OPENSSL */

Type names end with the _t suffix. A defined type name is separated by at least two spaces:

typedef uint8_t  njs_vmcode_t;

Structure types are defined using typedef. Inside structures, member types and names are aligned:

typedef struct {
    size_t  length;
    u_char  *start;
} njs_str_t;

Keep alignment identical among different structures in the file. A structure that points to itself has the name, ending with _s. Adjacent structure definitions are separated with two empty lines:

typedef struct njs_queue_link_s  njs_queue_link_t;

struct njs_queue_link_s {
    njs_queue_link_t  *prev;
    njs_queue_link_t  *next;
};


typedef struct {
    njs_queue_link_t  head;
} njs_queue_t;

Each structure member is declared on its own line. The asterisk (*) in pointer declarations is aligned with the field name, not the type:

typedef struct {
    u_char            *start;
    u_char            *end;
    njs_str_t         file;
    njs_str_t         name;
    njs_arr_t         *lines;  /* of njs_vm_line_num_t */
} njs_vm_code_t;

Enumerations have types ending with _t:

typedef enum {
    NJS_NULL,
    NJS_UNDEFINED,
    NJS_BOOLEAN,
    NJS_NUMBER,
    NJS_STRING,
    NJS_SYMBOL
} njs_value_type_t;

Variables are declared sorted by length of a base type, then alphabetically. Type names and variable names are aligned. The type and name "columns" are separated with two spaces. Large arrays are put at the end of a declaration block:

u_char               *rv, *p;
njs_vm_t             *vm;
njs_uint_t           i, j, k;
njs_parser_t         *parser;
njs_value_t          *values;
unsigned int         len;
njs_generator_t      *generator;
struct sockaddr      *sa;
const unsigned char  *data;
u_char               text[NJS_STRING_MAX_LENGTH];

Static and global variables may be initialized on declaration:

static njs_str_t  njs_string_constructor = njs_str("String");

static njs_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static uint32_t  njs_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

There is a bunch of commonly used type/name combinations:

u_char           *p, *start, *end;
njs_int_t         ret;
njs_vm_t         *vm;
njs_value_t      *value;
njs_parser_t     *parser;
njs_function_t   *function;
njs_generator_t  *generator;

All functions (even static ones) should have prototypes. Prototypes include argument names. Long prototypes are wrapped with a single indentation on continuation lines:

static njs_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end);
static njs_int_t njs_parser_statement(njs_parser_t *parser,
    njs_parser_node_t *parent);

static njs_int_t njs_generator_function(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);

The function name in a definition starts with a new line. The function body opening and closing braces are on separate lines. The body of a function is indented. There are two empty lines between functions:

static njs_int_t
njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end)
{
    ...
}


static njs_int_t
njs_parser_statement(njs_parser_t *parser, njs_parser_node_t *parent,
    njs_token_type_t token)
{
    ...
}

There is no space after the function name and opening parenthesis. Long function calls are wrapped such that continuation lines start from the position of the first function argument. If this is impossible, format the first continuation line such that it ends at position 79:

njs_printf(vm, "parser: %V at %uz",
           &token->text, parser->lexer->start);

value = njs_mp_alloc(vm->mem_pool,
                     parser->scope->items * sizeof(njs_value_t));

The njs_inline macro should be used instead of inline:

static njs_inline njs_int_t njs_utf8_byte(const u_char ***start, const u_char *end);

Binary operators except "." and "−>" should be separated from their operands by one space. Unary operators and subscripts are not separated from their operands by spaces:

width = width * 10 + (*fmt++ - '0');

ch = (u_char) ((decoded << 4) + (ch - '0'));

value->data.u.string = &parser->lexer->text.start[i + 1];

Type casts are separated by one space from casted expressions. An asterisk inside type cast is separated with space from type name:

len = njs_utf8_decode((const u_char **) &start, end);

If an expression does not fit into single line, it is wrapped. The preferred point to break a line is a binary operator. The continuation line is lined up with the start of expression:

if (type == NJS_NULL
    || type == NJS_UNDEFINED
    || type == NJS_BOOLEAN
    || type == NJS_NUMBER
    || type == NJS_STRING)
{
    ...
}

parser->error = "unexpected token "
                "in expression";

As a last resort, it is possible to wrap an expression so that the continuation line ends at position 79:

values = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)
                                  + count * sizeof(njs_object_prop_t));

The above rules also apply to sub-expressions, where each sub-expression has its own indentation level:

if (((parser->scope->type & NJS_SCOPE_FUNCTION)
     || generator->context)
    && !vm->options.backtrace
    && parser->scope->nesting_level > 1)
{
    ...
}

Sometimes, it is convenient to wrap an expression after a cast. In this case, the continuation line is indented:

value = (njs_value_t *)
            ((u_char *) prop - offsetof(njs_object_prop_t, value));

Pointers are explicitly compared to NULL (not 0):

if (ptr != NULL) {
    ...
}

The if keyword is separated from the condition by one space. Opening brace is located on the same line, or on a dedicated line if the condition takes several lines. Closing brace is located on a dedicated line, optionally followed by else if / else. Usually, there is an empty line before the else if / else part:

if (value->type == NJS_NULL) {
    result = njs_value_undefined;
    goto done;

} else if (value->type == NJS_UNDEFINED) {
    result = njs_value_null;
    goto done;

} else {
    result = njs_value_to_string(vm, value);

    if (result == NULL) {
        goto error;

    } else {
        goto done;
    }
}

Similar formatting rules are applied to do and while loops:

while (p < end && *p == ' ') {
    p++;
}

do {
    parser->node = current;
    parser = parser->parent;
} while (parser);

The switch keyword is separated from the condition by one space. Opening brace is located on the same line. Closing brace is located on a dedicated line. The case keywords are lined up with switch:

switch (token) {
case NJS_TOKEN_FUNCTION:
    parser->state = njs_parser_function;
    break;

case NJS_TOKEN_VAR:
    parser->state = njs_parser_var;
    break;

default:
    parser->state = njs_parser_statement;
    break;
}

Most for loops are formatted like this:

for (i = 0; i < parser->scope->items; i++) {
    ...
}

for (prop = njs_object_prop(object);
     prop < njs_object_prop_end(object);
     prop = njs_object_prop_next(prop))
{
    ...
}

If some part of the for statement is omitted, this is indicated by the /* void */ comment:

for (i = 0; /* void */ ; i++) {
    ...
}

A loop with an empty body is also indicated by the /* void */ comment which may be put on the same line:

for (prop = object->start; prop->next; prop = prop->next) { /* void */ }

An endless loop looks like this:

for ( ;; ) {
    ...
}

Labels are surrounded with empty lines and are indented at the previous level:

    if (i == 0) {
        vm->error = "function not found";
        goto failed;
    }

    values = njs_mp_alloc(vm->mem_pool, i * sizeof(njs_value_t));
    if (values == NULL) {
        goto failed;
    }

    parser->values = i;

    ...

    return NJS_OK;

failed:

    njs_mp_free(vm->mem_pool, values);

    return NJS_ERROR;

Nginx modules style guide

  • applies to C source code in nginx/ directory

General rules

  • maximum text width is 80 characters
  • indentation is 4 spaces
  • no tabs, no trailing spaces
  • list elements on the same line are separated with spaces
  • hexadecimal literals are lowercase
  • file names, function and type names, and global variables have the ngx_ or more specific prefix such as ngx_http_ and ngx_mail_
size_t
ngx_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

    for (len = 0; p < last; len++) {

        c = *p;

        if (c < 0x80) {
            p++;
            continue;
        }

        if (ngx_utf8_decode(&p, last - p) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

Files

A typical source file may contain the following sections separated by two empty lines:

  • copyright statements
  • includes
  • preprocessor definitions
  • type definitions
  • function prototypes
  • variable definitions
  • function definitions

Copyright statements look like this:

/*
 * Copyright (C) Author Name
 * Copyright (C) Organization, Inc.
 */

If the file is modified significantly, the list of authors should be updated, the new author is added to the top.

The ngx_config.h and ngx_core.h files are always included first, followed by one of ngx_http.h, ngx_stream.h, or ngx_mail.h. Then follow optional external header files:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxslt/xslt.h>

#if (NGX_HAVE_EXSLT)
#include <libexslt/exslt.h>
#endif

Header files should include the so called "header protection":

#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
#define _NGX_PROCESS_CYCLE_H_INCLUDED_
...
#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */

Comments

  • "//" comments are not used
  • text is written in English, American spelling is preferred
  • multi-line comments are formatted like this:
/*
 * The red-black tree code is based on the algorithm described in
 * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
 */
/* find the server configuration for the address:port */

Macro names start from ngx_ or NGX_ (or more specific) prefix. Macro names for constants are uppercase. Parameterized macros and macros for initializers are lowercase. The macro name and value are separated by at least two spaces:

#define NGX_CONF_BUFFER  4096

#define ngx_buf_in_memory(b)  (b->temporary || b->memory || b->mmap)

#define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                            (b->file_last - b->file_pos))

#define ngx_null_string  { 0, NULL }

Conditions are inside parentheses, negation is outside:

#if (NGX_HAVE_KQUEUE)
...
#elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
       || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT)))
...
#elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL))
...
#elif (NGX_HAVE_POLL)
...
#else /* select */
...
#endif /* NGX_HAVE_KQUEUE */

Type names end with the _t suffix. A defined type name is separated by at least two spaces:

typedef ngx_uint_t  ngx_rbtree_key_t;

Structure types are defined using typedef. Inside structures, member types and names are aligned:

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

Keep alignment identical among different structures in the file. A structure that points to itself has the name, ending with _s. Adjacent structure definitions are separated with two empty lines:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};


typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

Each structure member is declared on its own line:

typedef struct {
    ngx_uint_t        hash;
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;
} ngx_table_elt_t;

Function pointers inside structures have defined types ending with _pt:

typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);
typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);

typedef struct {
    ngx_recv_pt        recv;
    ngx_recv_chain_pt  recv_chain;
    ngx_recv_pt        udp_recv;
    ngx_send_pt        send;
    ngx_send_pt        udp_send;
    ngx_send_chain_pt  udp_send_chain;
    ngx_send_chain_pt  send_chain;
    ngx_uint_t         flags;
} ngx_os_io_t;

Enumerations have types ending with _e:

typedef enum {
    ngx_http_fastcgi_st_version = 0,
    ngx_http_fastcgi_st_type,
    ...
    ngx_http_fastcgi_st_padding
} ngx_http_fastcgi_state_e;

Variables are declared sorted by length of a base type, then alphabetically. Type names and variable names are aligned. The type and name "columns" are separated with two spaces. Large arrays are put at the end of a declaration block:

u_char                         *rv, *p;
ngx_conf_t                     *cf;
ngx_uint_t                      i, j, k;
unsigned int                    len;
struct sockaddr                *sa;
const unsigned char            *data;
ngx_peer_connection_t          *pc;
ngx_http_core_srv_conf_t      **cscfp;
ngx_http_upstream_srv_conf_t   *us, *uscf;
u_char                          text[NGX_SOCKADDR_STRLEN];

Static and global variables may be initialized on declaration:

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");

static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static uint32_t  ngx_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

There is a bunch of commonly used type/name combinations:

u_char                        *rv;
ngx_int_t                      rc;
ngx_conf_t                    *cf;
ngx_connection_t              *c;
ngx_http_request_t            *r;
ngx_peer_connection_t         *pc;
ngx_http_upstream_srv_conf_t  *us, *uscf;

All functions (even static ones) should have prototypes. Prototypes include argument names. Long prototypes are wrapped with a single indentation on continuation lines:

static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf);

static char *ngx_http_merge_servers(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
    ngx_uint_t ctx_index);

The function name in a definition starts with a new line. The function body opening and closing braces are on separate lines. The body of a function is indented. There are two empty lines between functions:

static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
    ...
}


static ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
    ...
}

There is no space after the function name and opening parenthesis. Long function calls are wrapped such that continuation lines start from the position of the first function argument. If this is impossible, format the first continuation line such that it ends at position 79:

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
               "http header: \"%V: %V\"",
               &h->key, &h->value);

hc->busy = ngx_palloc(r->connection->pool,
                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));

The ngx_inline macro should be used instead of inline:

static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);

Binary operators except "." and "−>" should be separated from their operands by one space. Unary operators and subscripts are not separated from their operands by spaces:

width = width * 10 + (*fmt++ - '0');

ch = (u_char) ((decoded << 4) + (ch - '0'));

r->exten.data = &r->uri.data[i + 1];

Type casts are separated by one space from casted expressions. An asterisk inside type cast is separated with space from type name:

len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);

If an expression does not fit into single line, it is wrapped. The preferred point to break a line is a binary operator. The continuation line is lined up with the start of expression:

if (status == NGX_HTTP_MOVED_PERMANENTLY
    || status == NGX_HTTP_MOVED_TEMPORARILY
    || status == NGX_HTTP_SEE_OTHER
    || status == NGX_HTTP_TEMPORARY_REDIRECT
    || status == NGX_HTTP_PERMANENT_REDIRECT)
{
    ...
}

p->temp_file->warn = "an upstream response is buffered "
                     "to a temporary file";

As a last resort, it is possible to wrap an expression so that the continuation line ends at position 79:

hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
                                     + size * sizeof(ngx_hash_elt_t *));

The above rules also apply to sub-expressions, where each sub-expression has its own indentation level:

if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
     || c->stale_updating) && !r->background
    && u->conf->cache_background_update)
{
    ...
}

Sometimes, it is convenient to wrap an expression after a cast. In this case, the continuation line is indented:

node = (ngx_rbtree_node_t *)
           ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));

Pointers are explicitly compared to NULL (not 0):

if (ptr != NULL) {
    ...
}

The if keyword is separated from the condition by one space. Opening brace is located on the same line, or on a dedicated line if the condition takes several lines. Closing brace is located on a dedicated line, optionally followed by else if / else. Usually, there is an empty line before the else if / else part:

if (node->left == sentinel) {
    temp = node->right;
    subst = node;

} else if (node->right == sentinel) {
    temp = node->left;
    subst = node;

} else {
    subst = ngx_rbtree_min(node->right, sentinel);

    if (subst->left != sentinel) {
        temp = subst->left;

    } else {
        temp = subst->right;
    }
}

Similar formatting rules are applied to do and while loops:

while (p < last && *p == ' ') {
    p++;
}

do {
    ctx->node = rn;
    ctx = ctx->next;
} while (ctx);

The switch keyword is separated from the condition by one space. Opening brace is located on the same line. Closing brace is located on a dedicated line. The case keywords are lined up with switch:

switch (ch) {
case '!':
    looked = 2;
    state = ssi_comment0_state;
    break;

case '<':
    copy_end = p;
    break;

default:
    copy_end = p;
    looked = 0;
    state = ssi_start_state;
    break;
}

Most for loops are formatted like this:

for (i = 0; i < ccf->env.nelts; i++) {
    ...
}

for (q = ngx_queue_head(locations);
     q != ngx_queue_sentinel(locations);
     q = ngx_queue_next(q))
{
    ...
}

If some part of the for statement is omitted, this is indicated by the /* void */ comment:

for (i = 0; /* void */ ; i++) {
    ...
}

A loop with an empty body is also indicated by the /* void */ comment which may be put on the same line:

for (cl = *busy; cl->next; cl = cl->next) { /* void */ }

An endless loop looks like this:

for ( ;; ) {
    ...
}

Labels are surrounded with empty lines and are indented at the previous level:

    if (i == 0) {
        u->err = "host not found";
        goto failed;
    }

    u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
    if (u->addrs == NULL) {
        goto failed;
    }

    u->naddrs = i;

    ...

    return NGX_OK;

failed:

    freeaddrinfo(res);
    return NGX_ERROR;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment