Extracting the error messages from a Javascript file

In React, Javascript errors show up like this:

new Error("Cannot find module '"+o+"'")

The nice thing about this style is it records some line number information with the error.

To get a list of all of these, we’d need to parse the Javascript file for React, list out the strings, and filter them to things that are inside an error block.

Parsing the file turns out to be pretty easy, using the ESPrima library (for parsing) and Estraverse (for walking the tree).

In this example, we’ll track the stack of tokens parsed in the file, which gives us context:

const fs = require("fs");
const esprima = require("esprima");
const estraverse = require("estraverse");
const _ = require("lodash");
const filename = "node_modules/react/dist/react.js";

let tree = [];

const ast = esprima.parse(
  fs.readFileSync(filename)
);

estraverse.traverse(ast, {
  enter: (node) => {
    tree.push(node);

    ...
  },
  leave: (node) => {
    tree.pop();
  }
});

Inside the “enter” function, we can filter to just String values by checking the “type” part of the AST node. Every type has different properties – for instance when parsing a “+” there will be a “left” and “right”. For function calls, you’ll see “arguments”, and so on.

if (node.type === "Literal") {
  if (_.isString(node.value)) {
    ...
  }
}

In order to check if the string we’ve found exists within an error block, we can filter the list of parent values, to see if the string is contained inside an error:

const newErrorArr =
  _.filter(
    tree,
    x => 
      x.type === "NewExpression" &&
      x.callee &&
      x.callee.name === "Error"
);

const newError = newErrorArr[0];

To really make this useful, we should go back to the parsing code, save off the text, and request range and line number information. This will let us print out information that a consumer of the Javascript file would actually want.

const text = fs.readFileSync(filename);

const ast = esprima.parse(
  text,
  {
    loc: true,
    range: true,
    tokens: true,
    comment: true
  });

Now, within the parsing code, we can print out the actual error message (what is inside “new Error(…)”), and the line number:

const x = newError.arguments[0].range[0],
      y = newError.arguments[0].range[1];
              
console.log(
  (text + "").substring(x, y)
);

console.log(
  "Line number: " + newError.loc.start.line
);  

This gets you data like this:

"Cannot find module '"+o+"'"
Line number: 4

To make this work fully, you’ll also want to make sure that once you find an error, you skip to the next one, regardless of how many tokens are in it.

let found = {};
...
if (newErrorArr && newErrorArr.length > 0) {
  const newError = newErrorArr[0];        
  
  const r1 = newError.range[0],
        r2 = newError.range[1];
          
  const r3 = newError.arguments[0].range[0],
        r4 = newError.arguments[0].range[1];
              
  let key = r1 + "," + r2;
  if (!found[key]) {
    console.log(key);
            
    found[key] = true;
                          
    console.log((text + "").substring(r3, r4));
    console.log("Line number: " + newError.loc.start.line);
  }  
}

Now that you’ve done this, you can extract all errors from a library like React. These aren’t all the errors (for instance a warning), but extending this technique will work on a variety of libraries.

"Cannot find module '"+o+"'"
Line number: 4

'You provided a `value` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' +
 'the field should be mutable use `defaultValue`. Otherwise, ' + 'set either `onChange` or `readOnly`.'
Line number: 3495

'You provided a `checked` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If '
 + 'the field should be mutable use `defaultChecked`. Otherwise, ' + 'set either `onChange` or `readOnly`.'
Line number: 3501

'Required ' + locationName + ' `' + propFullName + '` was not specified in ' + ('`' + componentName + '`.')
Line number: 12595

'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '
`, expected ') + ('`' + expectedType + '`.')
Line number: 12620

'Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.'
Line number: 12634

'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`,
expected an array.')
Line number: 12640

'Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a single ReactElem
ent.')
Line number: 12657

'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName
 + '`, expected ') + ('instance of `' + expectedClassName + '`.')
Line number: 12670

'Invalid argument supplied to oneOf, expected an instance of array.'
Line number: 12680

'Invalid ' + locationName + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`
, expected one of ' + valuesString + '.')
Line number: 12694

'Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.'
Line number: 12702

'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`,
expected an object.')
Line number: 12708

'Invalid argument supplied to oneOfType, expected an instance of array.'
Line number: 12726

'Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.')
Line number: 12739

'Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.')
Line number: 12748

'Invalid ' + locationName + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`,
expected `object`.')
Line number: 12761

'invariant requires an error message argument'
Line number: 18882

'Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful w
arnings.'
Line number: 18889

'`warning(condition, format, ...args)` requires a warning ' + 'message argument'
Line number: 19274