{"id":4272,"date":"2016-05-31T02:55:04","date_gmt":"2016-05-31T02:55:04","guid":{"rendered":"http:\/\/www.garysieling.com\/blog\/?p=4272"},"modified":"2016-05-31T02:55:04","modified_gmt":"2016-05-31T02:55:04","slug":"extracting-error-messages-javascript-file","status":"publish","type":"post","link":"https:\/\/www.garysieling.com\/blog\/extracting-error-messages-javascript-file\/","title":{"rendered":"Extracting the error messages from a Javascript file"},"content":{"rendered":"<p>In React, Javascript errors show up like this:<\/p>\n<pre lang=\"javascript\">\nnew Error(\"Cannot find module '\"+o+\"'\")\n<\/pre>\n<p>The nice thing about this style is it records some line number information with the error.<\/p>\n<p>To get a list of all of these, we&#8217;d need to parse the Javascript file for React, list out the strings, and filter them to things that are inside an error block.<\/p>\n<p>Parsing the file turns out to be pretty easy, using the ESPrima library (for parsing) and Estraverse (for walking the tree).<\/p>\n<p>In this example, we&#8217;ll track the stack of tokens parsed in the file, which gives us context:<\/p>\n<pre lang=\"javascript\">\nconst fs = require(\"fs\");\nconst esprima = require(\"esprima\");\nconst estraverse = require(\"estraverse\");\nconst _ = require(\"lodash\");\nconst filename = \"node_modules\/react\/dist\/react.js\";\n\nlet tree = [];\n\nconst ast = esprima.parse(\n  fs.readFileSync(filename)\n);\n\nestraverse.traverse(ast, {\n  enter: (node) => {\n    tree.push(node);\n\n    ...\n  },\n  leave: (node) => {\n    tree.pop();\n  }\n});\n<\/pre>\n<p>Inside the &#8220;enter&#8221; function, we can filter to just String values by checking the &#8220;type&#8221; part of the AST node. Every type has different properties &#8211; for instance when parsing a &#8220;+&#8221; there will be a &#8220;left&#8221; and &#8220;right&#8221;. For function calls, you&#8217;ll see &#8220;arguments&#8221;, and so on.<\/p>\n<pre lang=\"javascript\">\nif (node.type === \"Literal\") {\n  if (_.isString(node.value)) {\n    ...\n  }\n}\n<\/pre>\n<p>In order to check if the string we&#8217;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:<\/p>\n<pre lang=\"javascript\">\nconst newErrorArr =\n  _.filter(\n    tree,\n    x => \n      x.type === \"NewExpression\" &&\n      x.callee &&\n      x.callee.name === \"Error\"\n);\n\nconst newError = newErrorArr[0];\n<\/pre>\n<p>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.<\/p>\n<pre lang=\"javascript\">\nconst text = fs.readFileSync(filename);\n\nconst ast = esprima.parse(\n  text,\n  {\n    loc: true,\n    range: true,\n    tokens: true,\n    comment: true\n  });\n<\/pre>\n<p>Now, within the parsing code, we can print out the actual error message (what is inside &#8220;new Error(&#8230;)&#8221;), and the line number:<\/p>\n<pre lang=\"javascript\">\nconst x = newError.arguments[0].range[0],\n      y = newError.arguments[0].range[1];\n              \nconsole.log(\n  (text + \"\").substring(x, y)\n);\n\nconsole.log(\n  \"Line number: \" + newError.loc.start.line\n);  \n<\/pre>\n<p>This gets you data like this:<\/p>\n<pre>\n\"Cannot find module '\"+o+\"'\"\nLine number: 4\n<\/pre>\n<p>To make this work fully, you&#8217;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.<\/p>\n<pre lang=\"javascript\">\nlet found = {};\n...\nif (newErrorArr && newErrorArr.length > 0) {\n  const newError = newErrorArr[0];        \n  \n  const r1 = newError.range[0],\n        r2 = newError.range[1];\n          \n  const r3 = newError.arguments[0].range[0],\n        r4 = newError.arguments[0].range[1];\n              \n  let key = r1 + \",\" + r2;\n  if (!found[key]) {\n    console.log(key);\n            \n    found[key] = true;\n                          \n    console.log((text + \"\").substring(r3, r4));\n    console.log(\"Line number: \" + newError.loc.start.line);\n  }  \n}\n<\/pre>\n<p>Now that you&#8217;ve done this, you can extract all errors from a library like React. These aren&#8217;t all the errors (for instance a warning), but extending this technique will work on a variety of libraries.<\/p>\n<pre>\n\"Cannot find module '\"+o+\"'\"\nLine number: 4\n\n'You provided a `value` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' +\n 'the field should be mutable use `defaultValue`. Otherwise, ' + 'set either `onChange` or `readOnly`.'\nLine number: 3495\n\n'You provided a `checked` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If '\n + 'the field should be mutable use `defaultChecked`. Otherwise, ' + 'set either `onChange` or `readOnly`.'\nLine number: 3501\n\n'Required ' + locationName + ' `' + propFullName + '` was not specified in ' + ('`' + componentName + '`.')\nLine number: 12595\n\n'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '\n`, expected ') + ('`' + expectedType + '`.')\nLine number: 12620\n\n'Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.'\nLine number: 12634\n\n'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`,\nexpected an array.')\nLine number: 12640\n\n'Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a single ReactElem\nent.')\nLine number: 12657\n\n'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName\n + '`, expected ') + ('instance of `' + expectedClassName + '`.')\nLine number: 12670\n\n'Invalid argument supplied to oneOf, expected an instance of array.'\nLine number: 12680\n\n'Invalid ' + locationName + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`\n, expected one of ' + valuesString + '.')\nLine number: 12694\n\n'Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.'\nLine number: 12702\n\n'Invalid ' + locationName + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`,\nexpected an object.')\nLine number: 12708\n\n'Invalid argument supplied to oneOfType, expected an instance of array.'\nLine number: 12726\n\n'Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.')\nLine number: 12739\n\n'Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.')\nLine number: 12748\n\n'Invalid ' + locationName + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`,\nexpected `object`.')\nLine number: 12761\n\n'invariant requires an error message argument'\nLine number: 18882\n\n'Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful w\narnings.'\nLine number: 18889\n\n'`warning(condition, format, ...args)` requires a warning ' + 'message argument'\nLine number: 19274\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>How to extract the error messages from a Javascript library<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[12],"tags":[302],"aioseo_notices":[],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts\/4272"}],"collection":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/comments?post=4272"}],"version-history":[{"count":0,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/posts\/4272\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/media?parent=4272"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/categories?post=4272"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.garysieling.com\/blog\/wp-json\/wp\/v2\/tags?post=4272"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}