Program Summary

Generate a summary of a JavaScript program.

Usage

var analyze = require( '@stdlib/_tools/static-analysis/js/program-summary' );

analyze( src )

Generates a summary of a JavaScript program, where src may be either an input string or Buffer.

var summary = analyze( 'var beep = "boop";' );
/* returns
    {
        "length": 18,            // number of characters, including whitespace
        "whitespace": 3,         // number of whitespace characters
        "lines": 1,              // number of physical lines
        "empty": 0,              // number of empty lines
        "sloc": 1,               // source lines of code (SLOC)
        "lloc": 1,               // logical lines of code (LLOC)
        "scope": 0,              // function scope level
        "depth": 0,              // nesting depth
        "statements": 1,         // total number of statements
        "branches" 0,            // total number of branches
        "comments": {            // comment summary
            "count": 0,          // total number of comments
            "length": 0,         // total combined comment length
            "whitespace": 0,     // total number of whitespace characters
            "lines": 0,          // total number of comment lines
            "inline": 0,         // total number of inline comments
            "block": {           // block comment summary
                "count": 0,      // total number of block comments
                "length": 0,     // total combined block comment length
                "whitespace": 0, // total number of whitespace characters
                "lines": 0,      // total number of block comment lines
                "inline": 0      // total number of inline block comments
            },
            "line": {            // line comment summary
                "count": 0,      // total number of line comments
                "length": 0,     // total combined line comment length
                "whitespace": 0, // total number of whitespace characters
                "lines": 0,      // total number of line comment lines
                "inline": 0      // total number of inline line comments
            },
            "jsdoc": {           // JSDoc comment summary
                "count": 0,      // total number of JSDoc comments
                "length": 0,     // total combined JSDoc comment length
                "whitespace": 0, // total number of whitespace characters
                "lines": 0,      // total number of JSDoc comment lines
                "inline": 0      // total number of inline JSDoc comments
            },
            "data": []           // comment summaries
        },
        "dowhile": {             // doWhile statement summary
            "count": 0,          // total number of doWhile statements
            "data": []           // doWhile statement content summaries
        },
        "expressions": {         // expression summary
            "array": 0,          // total number of array expressions
            "assignment": 0,     // total number of assignment expressions
            "binary": 0,         // total number of binary expressions
            "call": 0,           // total number of call expressions
            "conditional": 0,    // total number of conditional expressions
            "function": 0,       // total number of function expressions
            "logical": 0,        // total number of logical expressions
            "member": 0,         // total number of member expressions
            "new": 0,            // total number of new expressions
            "object": 0,         // total number of object expressions
            "sequence": 0,       // total number of sequence expressions
            "this": 0,           // total number of this expressions
            "unary": 0,          // total number of unary expressions
            "update": 0,         // total number of update expressions
            "yield": 0           // total number of yield expressions
        },
        "for": {                 // for statement summary
            "count": 0,          // total number of for statements
            "data": []           // for statement content summaries
        },
        "forin": {               // for...in statement summary
            "count": 0,          // total number of for...in statements
            "data": []           // for...in statement content summaries
        },
        "forof": {               // for...of statement summary
            "count": 0,          // total number of for...of statements
            "data": []           // for...of statement content summaries
        },
        "function": {            // function summary
            "count": 0,          // total number of functions
            "data": []           // function content summaries
        },
        "if": {                  // if statement summary
            "count": 0,          // total number of if statements
            "data": []           // if statement content summaries
        },
        "literal": {             // literal summary
            "count": 0,          // total number of literals
            "data": []           // literal summaries
        },
        "switch": {              // switch statement summary
            "count": 0,          // total number of switch statements
            "data": []           // switch statement content summaries
        },
        "throw": 0,              // total number of throw statements
        "try": {                 // try statement summary
            "count": 0,          // total number of try statements
            "data": []           // try statement content summaries
        },
        "variable": {            // variable declaration summary
            "count": 0,          // total number of variable declarations
            "data": []           // variable declaration summaries
        },
        "while": {               // while statement summary
            "count": 0,          // total number of while statements
            "data": []           // while statement content summaries
        }
    }
*/

Notes

  • Counting methodology is biased toward the coding conventions of stdlib.

  • Source lines of code (SLOC) is defined as the total number of program lines, excluding empty lines (which includes lines containing only whitespace) and lines containing only comments. When calculating SLOC, analysis assumes specific stylistic forms. For example, analysis does not distinguish between "verbose" if statements and if statement shorthand, always opting for the verbose form.

    var x = -5.0;
    
    // Shorthand:
    if ( x < 0.0 ) x *= -1;
    
    // "Verbose":
    if ( x < 0.0 ) {
        x *= -1;
    }
    
  • Logical lines of code (LLOC) is defined as the number of executable statements. In comparison to SLOC, calculating LLOC is not sensitive to stylistic forms. In the following example,

    var x = -5.0;
    
    // Shorthand:
    if ( x < 0.0 ) x *= -1;
    
    // "Verbose":
    if ( x < 0.0 ) {
        x *= -1;
    }
    

    the shorthand and "verbose" form both correspond to 2 LLOC (1 LLOC for the if statement and 1 LLOC for the if block content).

  • The total number of statements is the total number of occurrences of the following statement types:

    • BreakStatement
    • ContinueStatement
    • DoWhileStatement
    • ExpressionStatement
    • ForInStatement
    • ForOfStatement
    • ForStatement
    • FunctionDeclaration
    • IfStatement
    • ReturnStatement
    • SwitchStatement
    • ThrowStatement
    • TryStatement
    • VariableDeclaration
    • WhileStatement
  • The total number of branches corresponds to the number of paths within a statement sequence, regardless of whether all paths are possible (e.g., if an if statement is encountered, two branches are always counted, even if an if statement condition is nonsensical based on value types, ranges, etc). The number of branches is calculated using the following heuristics:

    • IfStatement: two branches (if/else).
    • ConditionalExpression: two branches (ternary expression).
    • TryStatement: two branches (try/catch).
    • SwitchStatement: number of cases (including the default case).

    For nested if statements, the number of branches will not correspond to the number of unique code paths, as the if statement heuristic will over count possible paths.

  • A block comment is defined as a comment using /* */ syntax.

    /* This is a block comment. */
    
    /*
    * This is a block comment.
    */
    
    /**
    * This is a block comment.
    */
    
  • A JSDoc comment is defined as a comment using /** */ syntax.

    /**
    * This is a JSDoc block comment.
    *
    * @private
    */
    

    Note that JSDoc comments are also counted as block comments.

  • A line comment is defined as a comment using // syntax.

    // This is a line comment.
    
  • An inline comment is defined as a comment appearing on the same line as a line of source code.

    var x = 5.0; // this is an inline comment
    var y = 4.0; /* this is an inline comment */
    
  • With the exception of empty functions, function declarations are assumed to have the following form:

    function add( x, y ) {
        return x + y;
    }
    

    Accordingly, in the above example, the function occupies 3 physical lines, including the line for the function identifier and the line for the closing brace, 3 SLOC, and 2 LLOC.

    Empty functions occupy 1 physical line, 1 SLOC, and 1 LLOC.

  • If statements are assumed to have the following form:

    var x = 5.0;
    
    if ( x !== x ) {
        // Handle NaN...
    } else {
        // Do something...
    }
    

    Accordingly, in the above example, the if statement occupies 5 physical lines, including the line for the condition, else clause, and the line for the closing brace, 3 SLOC, and 1 LLOC.

  • Switch statements are assumed to have the following form:

    var x = 1;
    
    switch ( x ) {
    case 1:
        // Do something...
        break;
    case 2:
        // Do something...
        break;
    default:
        // Do something...
        break;
    }
    

    Accordingly, in the above example, the switch statement occupies 11 physical lines including the condition and the line for the closing brace, 8 SLOC, and 4 LLOC.

  • Try statements are assumed to have the following form:

    var x = 5.0;
    
    try {
        x *= 2;
    } catch ( err ) {
        throw err;
    }
    

    Accordingly, in the above example, the try statement occupies 5 physical lines, including the opening line, catch clause, and the line for the closing brace, 5 SLOC, and 3 LLOC.

  • While statements are assumed to have the following form:

    var i = 0;
    
    while ( i < 5 ) {
        // Do something...
        i += 1;
    }
    

    Accordingly, in the above example, the while statement occupies 4 physical lines, including the condition and the line for the closing brace, 3 SLOC, and 2 LLOC.

  • DoWhile statements are assumed to have the following form:

    var i = 0;
    
    do {
        // Do something...
        i += 1;
    } while ( i < 5 );
    

    Accordingly, in the above example, the doWhile statement occupies 4 physical lines, including the opening line and the condition, 3 SLOC, and 2 LLOC.

  • For statements are assumed to have the following form:

    var i;
    
    for ( i = 0; i < 5; i++ ) {
        // Do something...
    }
    

    Accordingly, in the above example, the for statement occupies 3 physical lines, including the line for loop initialization and the line for the closing brace, 2 SLOC, and 1 LLOC.

  • All expression statements are counted as 1 SLOC, including ternary conditions and function expressions. For example,

    var obj = {
        'x': 5.0
    };
    

    is counted as 3 physical lines, 1 SLOC, and 1 LLOC.

  • if/else if chains are interpreted as nested if statements. For example,

    var x = 5.0;
    
    if ( x < 3.0 ) {
        // Do something...
    } else if ( x < 5.0 ) {
        // Do something...
    } else if ( x < 7.0 ) {
        // Do something...
    } else {
        // Do something...
    }
    

    is interpreted in terms of its behavioral equivalent

    var x = 5.0;
    
    if ( x < 3.0 ) {
        // Do something...
    } else {
        if ( x < 5.0 ) {
            // Do something...
        } else {
            if ( x < 7.0 ) {
                // Do something...
            } else {
                // Do something...
            }
        }
    }
    
  • Unless documented below, nested summaries follow the same general structure as the documented example summary object shown above. One difference, however, is that the SLOC and LLOC values in the nested summaries correspond to block content (i.e., the sequence of statements surrounded by braces; e.g., the function body, but not the function declaration, opening brace, or closing brace).

  • Comment summaries include the following fields:

    • type: comment type. One of either block or line.
    • length: comment length (characters).
    • whitespace: number of whitespace characters.
    • lines: number of lines.
    • inline: boolean indicating whether a comment is inline.
    • jsdoc: boolean indicating whether a comment is a JSDoc comment.
  • Variable declaration summaries include the following fields:

    • type: declaration type (e.g., var).
    • name: variable identifier.
  • Literal summaries include the following fields:

    • type: literal type. One of null, boolean, string, number, or regexp.
  • Switch statement summaries include an extra case field which is the total number of case clauses (including the default clause).

    ...,
    "switch": {
        "count": 1,
        "data": [
          {
            "length": 187,
            "whitespace": 23,
            "lines": 11,
            "empty": 0,
            "sloc": 6,
            "lloc": 6,
            "scope": 0,
            "depth": 0,
            "statements": 6,
            "branches": 0,
            "comments": {...},
            "dowhile": {...},
            "expressions": {...},
            "for": {...},
            "forin": {...},
            "forof": {...},
            "function": {...},
            "if": {...},
            "literal": {...},
            "switch": {...},
            "throw": 0,
            "try": {...},
            "variable": {...},
            "while": {...},
            "case": 3              // total number of case clauses
          }
        ]
    },
    ...
    
  • A function summary includes the following additional fields:

    • type: function type. One of either declaration or expression.
    • name: function identifier. For anonymous functions, the name is (anonymous).
    • params: number of function parameters.
    ...,
    "function": {
        "count": 1,
        "data": [
          {
            "length": 34,
            "whitespace": 11,
            "lines": 3,
            "empty": 0,
            "sloc": 1,
            "lloc": 1,
            "scope": 0,
            "depth": 0,
            "statements": 1,
            "branches": 0,
            "comments": {...},
            "dowhile": {...},
            "expressions": {...},
            "for": {...},
            "forin": {...},
            "forof": {...},
            "function": {...},
            "if": {...},
            "literal": {...},
            "switch": {...},
            "throw": 0,
            "try": {...},
            "variable": {...},
            "while": {...},
            "type": "declaration",  // function type
            "name": "foo",          // function identifier
            "params": 2             // number of parameters
          }
        ]
    },
    ...
    

Examples

var join = require( 'path' ).join;
var readFileSync = require( '@stdlib/fs/read-file' ).sync;
var analyze = require( '@stdlib/_tools/static-analysis/js/program-summary' );

// Load a JavaScript file:
var fpath = join( __dirname, 'examples', 'fixtures', 'index.txt' );
var code = readFileSync( fpath, {
    'encoding': 'utf8'
});

// Generate a summary:
var out = analyze( code );
console.log( JSON.stringify( out ) );

CLI

Usage

Usage: js-program-summary [options] [<string>]

Options:

  -h,    --help                Print this message.
  -V,    --version             Print the package version.

Examples

$ js-program-summary 'var beep = "boop";'
{...}

To use as a standard stream,

$ echo -n $'var beep = "boop";' | js-program-summary
{...}